Skip to content

THRIFT-6056: Limit recursion depth in Dart struct read/write#3561

Open
Jens-G wants to merge 1 commit into
apache:masterfrom
Jens-G:dart-recursion-depth
Open

THRIFT-6056: Limit recursion depth in Dart struct read/write#3561
Jens-G wants to merge 1 commit into
apache:masterfrom
Jens-G:dart-recursion-depth

Conversation

@Jens-G
Copy link
Copy Markdown
Member

@Jens-G Jens-G commented May 28, 2026

Summary

THRIFT-6056 — bound the recursion depth of Dart struct read/write.

On master the generated Dart read() / write() call readStructBegin /
writeStructBegin directly with no depth bound, and TProtocol has no
recursion counter, so a deeply nested or cyclic struct is processed without a
limit in the generated path. (This is unlike netstd/Delphi, whose generated
code already brackets every read/write with a recursion tracker — there the
equivalent change would double-count; here there is no pre-existing bound.)

This PR:

  • adds a _recursionDepth counter with incrementRecursionDepth() /
    decrementRecursionDepth() to TProtocol, bounded at 64
    (TProtocolErrorType.DEPTH_LIMIT on excess);
  • updates the Dart generator to wrap each generated struct read/write body in
    try { … } finally { decrementRecursionDepth(); } so the counter is always
    restored. The bound lives only in the generated path (one count per struct);
    readStructBegin / writeStructBegin are left untouched, so there is no
    double-counting.

Unknown-field skipping during read is handled separately by TProtocolUtil
(which carries its own depth parameter) and is left unchanged here.

Test

The previous test exercised increment/decrementRecursionDepth() in isolation,
which does not cover the real generated read/write path. It is replaced by
test/dart/recursion_depth_test — a generated-code round-trip over the
recursive IDL types in test/Recursive.thrift (CoRecCoRec2, RecTree),
run across the binary, compact and JSON protocols:

case expectation
chain at the limit (64) round-trips
chain one over (65), write DEPTH_LIMIT
crafted payload one over (65), read DEPTH_LIMIT
wide shallow tree (192 siblings) round-trips (decrement unwinds each sibling)
cyclic graph DEPTH_LIMIT instead of unbounded recursion

Validation with Dart 3.12 (dart:stable):

  • with this PR: 18/18 pass (6 cases × 3 protocols).
  • against a master baseline (unpatched generator + master TProtocol):
    the 9 over-limit / cyclic cases fail — writes and reads are not bounded and
    the cyclic case recurses to StackOverflowError — confirming the gap this
    change closes.

Generation and the test run are wired through make -C test/dart recursion-test
(mirroring the existing thrift_test stub rules) and kept out of the default
cross chain; the suite above was validated directly with dart pub get && dart test under Dart 3.x, since it needs a null-safe Dart ≥ 2.12 and the cross
images currently pin 2.7.2.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

@Jens-G Jens-G requested review from fishy and mhlakhani as code owners May 28, 2026 11:47
@mergeable mergeable Bot added dart Pull requests that update Dart code compiler labels May 28, 2026
@Jens-G Jens-G marked this pull request as draft May 28, 2026 22:45
Client: dart

Add a _recursionDepth counter with incrementRecursionDepth() and
decrementRecursionDepth() to TProtocol, bounded at 64, and update the
Dart generator to bracket each generated struct read/write body with
try/finally so the counter is always restored. This bounds the work
performed for deeply nested or cyclic structs, which previously had no
limit in the generated read()/write() path.

Add a regression test under test/dart/recursion_depth_test that drives
the recursive IDL types from test/Recursive.thrift through the generated
read()/write() over the binary, compact and JSON protocols:

  - chains at the limit round-trip; chains one past it are rejected with
    DEPTH_LIMIT on both write and read,
  - a wide (shallow) tree confirms decrementRecursionDepth() unwinds each
    sibling back to depth 1,
  - a cyclic object graph is rejected instead of recursing without bound.

The test needs a null-safe Dart SDK (>= 2.12); generation and execution
are wired via `make recursion-test` and kept out of the default cross
chain (the cross images currently pin an older Dart).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Jens-G Jens-G force-pushed the dart-recursion-depth branch from 103cd55 to 61ab25f Compare May 30, 2026 11:00
@mergeable mergeable Bot added the build and general CI cmake, automake and build system changes label May 30, 2026
@Jens-G Jens-G changed the title THRIFT-6056: Limit struct read/write recursion depth in Dart library THRIFT-6056: Limit recursion depth in Dart struct read/write May 30, 2026
@Jens-G Jens-G marked this pull request as ready for review May 30, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build and general CI cmake, automake and build system changes compiler dart Pull requests that update Dart code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant