Draft
Add regression tests for JsonSerializer.Deserialize<JsonNode> MaxDepth enforcement#3
Conversation
…h enforcement Adds tests verifying that JsonSerializer.Deserialize<JsonNode/JsonArray/JsonObject> correctly throws JsonException when MaxDepth is exceeded for deeply nested arrays and objects, as reported in the issue. - DomTests.cs: DeserializeToNode_RespectsMaxDepth_Arrays/_Objects (parallel to existing SerializeToNode_RespectsMaxDepth) - JsonNodeTests.cs: Deserialize_JsonNode_RespectsMaxDepth_DeeplyNestedArrays Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix MaxDepth handling in JsonSerializer.Deserialize<JsonNode>
Add regression tests for JsonSerializer.Deserialize<JsonNode> MaxDepth enforcement
May 27, 2026
agocke
pushed a commit
that referenced
this pull request
Jul 30, 2026
…dotnet#131279) [wasm] Restrict enclosing-try throw-helper attach to the same funclet ## Problem crossgen2 emits an invalid WASM (wasm32) R2R module for methods with certain try/catch shapes: the terminal `end` opcode (0x0b) is dropped for an EH funclet, so `wasm-tools validate` (and V8) reject the module with *"function body must end with end opcode"*. A related symptom is a miscomputed branch target depth in the same funclet (tracked as dotnet#131252). ## Root cause The defect is in wasm block layout, in `FgWasm::VisitWasmSuccs` (`src/coreclr/jit/fgwasm.h`). dotnet#130945 added an "enclosing try" relaxation: when a block is a try side-entry and the throw-helper ACD key is `KD_TRY`, the helper is also attached as a successor if ```cpp comp->bbInTryRegions(key.RegionIndex(), block) ``` is true. `bbInTryRegions` is a purely **lexical** try-nesting test — it does not check that the throw helper and the side-entry live in the same **function region (funclet)**. So a throw helper for an outer try region that belongs to the **main method** can be attached to a catch-resumption side-entry (`BBF_CATCH_RESUMPTION`) that merely nests inside that try but physically lives in a **handler funclet**. RPO layout then lays the main-method helper *inside* the funclet, interleaving it with funclet blocks. Because a funclet is a distinct wasm function body, that interleaving drops the funclet's terminal `end` and corrupts its branch target depths. Concrete trace from `System.Data.DataColumn:set_Expression` (instrumented `VisitWasmSuccs`, deduped): ``` sideEntry BB50 (tryIdx=4 hndIdx=3) preds[async=0 catch=1 other=2] pulls dst BB76 (hndIdx=0) crossFunclet=1 sideEntry BB73 (tryIdx=4 hndIdx=3) preds[async=0 catch=1 other=0] pulls dst BB76 (hndIdx=0) crossFunclet=1 ``` BB76 is the throw helper for root try region #4 (`KD_TRY`, no handler index → main method); BB50/BB73 are catch-resumption side-entries in handler funclet #3 (which lexically nests in try #4), so the enclosing-try rule pulls the main-method helper into funclet #3. This is an ordinary catch-resumption EH shape (`async=0`); it is independent of runtime-async, and `fgwasm.h` is byte-identical to `main`. It is latent on `main` only because R2R-wasm codegen isn't enabled there yet. ## Fix Gate the enclosing-try relaxation on same-function-region ownership. A new `funcRegionOf` lambda returns the funclet index that physically contains an arbitrary block (0 == main method); it mirrors `funGetFuncIdx` but works for non-entry blocks and distinguishes a filter funclet from its filter-handler. The helper is attached only when it shares the side-entry's function region: ```cpp if (!viaEnclosingTry || (funcRegionOf(block) == funcRegionOf(acd->acdDstBlk))) { RETURN_ON_ABORT(func(acd->acdDstBlk)); } ``` The exact-match path (a helper keyed to the block's own region) is unchanged, and dotnet#130945's intended case (an inner-try side-entry pulling its enclosing try's helper *within the same funclet*) still matches — so this only removes the cross-funclet edge. ## Validation Standalone `System.Data.Common` R2R wasm crossgen, release JIT, `wasm-tools validate` (only `fgwasm.h` differs between runs): | | `wasm-tools validate` | |---|---| | baseline (`origin/main` layout) | `func 597 failed to validate` ❌ | | with this fix | passes ✅ | ## Notes - Supersedes dotnet#131251, which hardened the terminal-`end` emission (the *effect*); this fixes the *cause* in layout. Closing dotnet#131251 in favor of this. - Related: dotnet#129335 (incomplete predecessor for this defect class, do not reopen); dotnet#130945 (introduced the lexical enclosing-try match); dotnet#131252 (miscomputed branch target depth — same corrupted layout, very likely subsumed by this fix). - Follow-up idea (not in this PR): a JIT-time assert that a funclet's blocks form a contiguous RPO range, so any future mis-layout traps at compile time instead of surfacing as an invalid module. > [!NOTE] > This change was authored with the assistance of GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2e627b94-2658-41ce-a880-d9c27b7febd9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JsonSerializer.Deserialize<JsonNode>was reported as not honoringJsonSerializerOptions.MaxDepthfor deeply nested arrays. Investigation confirmed theUtf8JsonReaderdepth checks (StartArray/StartObject) already enforce the limit correctly — no production code fix was needed. This PR adds the missing regression test coverage.Tests added
DomTests.cs—DeserializeToNode_RespectsMaxDepth_ArraysandDeserializeToNode_RespectsMaxDepth_Objects: symmetric counterparts to the existingSerializeToNode_RespectsMaxDepth, coveringJsonNode,JsonArray, andJsonObjectdeserialization targets across depths 5, 32, and 70 (above default 64).JsonNodeTests.cs—Deserialize_JsonNode_RespectsMaxDepth_DeeplyNestedArrays: focused test in theJsonNode-specific class covering the exact repro scenario (e.g. 200 nested arrays withMaxDepth = 32).Each test verifies that JSON nested exactly at
MaxDepthdeserializes successfully, while JSON nested one level deeper throwsJsonException.