Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions compiler/cpp/src/thrift/generate/t_php_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,9 @@ void t_php_generator::generate_php_struct_reader(ostream& out, t_struct* tstruct

// Declare stack tmp variables
if (!binary_inline_) {
indent(out) << "$input->incrementRecursionDepth();" << '\n';
indent(out) << "try {" << '\n';
indent_up();
indent(out) << "$xfer += $input->readStructBegin($fname);" << '\n';
}

Expand Down Expand Up @@ -1222,6 +1225,12 @@ void t_php_generator::generate_php_struct_reader(ostream& out, t_struct* tstruct

if (!binary_inline_) {
indent(out) << "$xfer += $input->readStructEnd();" << '\n';
indent_down();
indent(out) << "} finally {" << '\n';
indent_up();
indent(out) << "$input->decrementRecursionDepth();" << '\n';
indent_down();
indent(out) << "}" << '\n';
}

if (needs_php_read_validator(tstruct, is_result)) {
Expand Down Expand Up @@ -1265,6 +1274,9 @@ void t_php_generator::generate_php_struct_writer(ostream& out, t_struct* tstruct
indent(out) << "$xfer = 0;" << '\n';

if (!binary_inline_) {
indent(out) << "$output->incrementRecursionDepth();" << '\n';
indent(out) << "try {" << '\n';
indent_up();
indent(out) << "$xfer += $output->writeStructBegin('" << name << "');" << '\n';
}

Expand Down Expand Up @@ -1317,6 +1329,12 @@ void t_php_generator::generate_php_struct_writer(ostream& out, t_struct* tstruct
} else {
out << indent() << "$xfer += $output->writeFieldStop();" << '\n' << indent()
<< "$xfer += $output->writeStructEnd();" << '\n';
indent_down();
out << indent() << "} finally {" << '\n';
indent_up();
out << indent() << "$output->decrementRecursionDepth();" << '\n';
indent_down();
out << indent() << "}" << '\n';
}

out << '\n';
Expand Down
10 changes: 10 additions & 0 deletions lib/php/lib/Base/TBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ protected function readStruct(string $class, array $spec, TProtocol $input): int
$fname = null;
$ftype = 0;
$fid = 0;
$input->incrementRecursionDepth();
try {
$xfer += $input->readStructBegin($fname);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: the try { ... } block body isn't indented relative to the surrounding code, so the body sits at the same column as try { itself. PSR-12 expects the body to be one level deeper. The same shape is in writeStruct below. CI is green only because phpcs doesn't enforce Generic.WhiteSpace.ScopeIndent for this rule, but visually it reads as if the try had no body. Suggest:

Suggested change
$xfer += $input->readStructBegin($fname);
$input->incrementRecursionDepth();
try {
$xfer += $input->readStructBegin($fname);

…and reindent the matched closing brace + every line in between.

while (true) {
$xfer += $input->readFieldBegin($fname, $ftype, $fid);
Expand Down Expand Up @@ -257,6 +259,9 @@ protected function readStruct(string $class, array $spec, TProtocol $input): int
$xfer += $input->readFieldEnd();
}
$xfer += $input->readStructEnd();
} finally {
$input->decrementRecursionDepth();
}

return $xfer;
}
Expand Down Expand Up @@ -380,6 +385,8 @@ private function writeList(array $var, array $spec, TProtocol $output, bool $set
protected function writeStruct(string $class, array $spec, TProtocol $output): int
{
$xfer = 0;
$output->incrementRecursionDepth();
try {
$xfer += $output->writeStructBegin($class);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same indent issue as in readStruct — body of try { is one level shallower than it should be.

foreach ($spec as $fid => $fspec) {
$var = $fspec['var'];
Expand Down Expand Up @@ -410,6 +417,9 @@ protected function writeStruct(string $class, array $spec, TProtocol $output): i
}
$xfer += $output->writeFieldStop();
$xfer += $output->writeStructEnd();
} finally {
$output->decrementRecursionDepth();
}

return $xfer;
}
Expand Down
18 changes: 18 additions & 0 deletions lib/php/lib/Protocol/TProtocol.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
*/
abstract class TProtocol
{
public const DEFAULT_RECURSION_DEPTH = 64;

private int $recursionDepth = 0;

protected function __construct(protected TTransport $trans)
{
}
Expand All @@ -44,6 +48,20 @@ public function getTransport(): TTransport
return $this->trans;
}

public function incrementRecursionDepth(): void
{
++$this->recursionDepth;
if ($this->recursionDepth > self::DEFAULT_RECURSION_DEPTH) {
--$this->recursionDepth;
throw new TProtocolException('Maximum recursion depth exceeded', TProtocolException::DEPTH_LIMIT);
}
}

public function decrementRecursionDepth(): void
{
--$this->recursionDepth;
}

abstract public function writeMessageBegin(string $name, int $type, int $seqid): int;

abstract public function writeMessageEnd(): int;
Expand Down
34 changes: 34 additions & 0 deletions lib/php/test/Unit/Lib/Protocol/TProtocolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,40 @@ public function testSkipBinaryThrowsForUnknownType(): void
TProtocol::skipBinary(new TMemoryBuffer(), 999);
}

public function testIncrementRecursionDepthAllowsUpToLimit(): void
{
$transport = new TMemoryBuffer();
$protocol = new TBinaryProtocol($transport);
for ($i = 0; $i < TProtocol::DEFAULT_RECURSION_DEPTH; $i++) {
$protocol->incrementRecursionDepth();
}
$this->assertTrue(true); // no exception thrown
}

public function testIncrementRecursionDepthThrowsAtLimit(): void
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: PHPUnit canonical way to express “the code under test must not throw and has no value to assert on” is $this->expectNotToPerformAssertions(); at the top of the test, rather than a trailing assertTrue(true). Same for testDecrementRecursionDepthRestoresCapacity below.

$this->expectException(TProtocolException::class);
$this->expectExceptionCode(TProtocolException::DEPTH_LIMIT);

$transport = new TMemoryBuffer();
$protocol = new TBinaryProtocol($transport);
for ($i = 0; $i <= TProtocol::DEFAULT_RECURSION_DEPTH; $i++) {
$protocol->incrementRecursionDepth();
}
}

public function testDecrementRecursionDepthRestoresCapacity(): void
{
$transport = new TMemoryBuffer();
$protocol = new TBinaryProtocol($transport);
for ($i = 0; $i < TProtocol::DEFAULT_RECURSION_DEPTH; $i++) {
$protocol->incrementRecursionDepth();
}
$protocol->decrementRecursionDepth();
$protocol->incrementRecursionDepth(); // should not throw
$this->assertTrue(true);
}

private function buildBinaryBuffer(callable $writer): string
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit — prefer $this->expectNotToPerformAssertions(); over assertTrue(true).

$transport = new TMemoryBuffer();
Expand Down
Loading