Skip to content

fix(tools): report deeply nested event JSON in check_conventions as a usage error - #12979

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
xXPBSXx:tools/q4-checkconv-event-recursion
Nov 2, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
xXPBSXx:tools/q4-checkconv-event-recursion

Conversation

@xXPBSXx

@xXPBSXx xXPBSXx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

What

check_conventions.py --event (and GITHUB_EVENT_PATH in CI) reads the event file with json.loads and reports an unreadable one as error: --event ... with exit code 2, but only for OSError and ValueError. A file nested deeper than the interpreter's recursion limit raises RecursionError, which is neither, so the checker printed a traceback and exited with 1 instead of the usage error that a malformed or missing event file gets. This catches RecursionError in the same handler, as the other tools do after #12266. The new test picks its nesting depth from sys.getrecursionlimit() and checks that the decoder really fails on it, so it does not depend on the standard library's message text and cannot pass without exercising the fix on Python versions whose decoder reads deeper input.

Tested

  • Windows 10 Pro, Python 3.12.10: the new test fails on main bb04a4f1ea (only the except clause differs) and passes with this change (head e6ddf01fa5); all 9 tests of tools/tests/test_check_conventions_body_bom.py pass.
  • Ubuntu 24.04 under WSL2 (a virtualised kernel, not bare-metal), Python 3.12.3: the same test fails on main and passes with this change; 9 of 9 pass.
  • check_conventions --base upstream/main passes.
  • Not run: Python 3.13 and newer, macOS.

Overlap

Two open PRs change tools/check_conventions.py: #12654 (also two tests) and #9966. I took the changed files of each of the 1432 open PRs from git diff --name-only against its merge base. A git merge-tree of each of them with this branch is clean, and so is each of them alone against main bb04a4f1ea; they change other parts of the file. Re-checked when opening against the open PRs created since then: none of them changes these files.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

AI-assisted (Claude Code).

🤖 Generated with Claude Code

… usage error

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@rooklz rooklz left a comment

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.

Approving. I ran this on macOS arm64 with Python 3.13 and 3.15, the two setups the body lists as not run, at head e6ddf01fa5.

Gates

  • Based on main bb04a4f1ea. git diff --check and git merge-tree against main are both clean.
  • check_conventions --base upstream/main passes, both alone and with this PR's title and body.
  • pr_overlap --branch: 1470 PRs probed, 2 share the file, both merge cleanly, so no overlap.
  • Relinker-only build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Python 3.13): ctest passes 494/494. That includes conventions_body_bom, which is registered at CMakeLists.txt:392.

RED/GREEN: test_check_conventions_body_bom.py

Python 3.13.13 Python 3.15.1
head 9/9 OK 9/9 OK
main checker + PR tests new test FAILS new test FAILS

Mutants (each one was run against the whole module)

  • Catch RecursionError and set event = [], so the run reaches the "not a JSON object" path: KILLED (by assertNotIn("not a JSON object")).
  • except RecursionError: sys.exit(1): KILLED.
  • except RecursionError: sys.exit(2) with no error: --event message: KILLED.

Probe behaviour, measured outside the test

  • 3.13: json.loads raises RecursionError ("maximum recursion depth exceeded while decoding…") at depth 17024, which is iteration 4 of 8. The checker exits 2 with error: --event … in 0.04 s.
  • 3.15: the error is now the C-stack guard ("Stack overflow (used 16352 kB) while decoding…"). It is still a RecursionError, so the handler catches it. It fires only at depth 136192, which is iteration 7 of 8. The checker exits 2 in 0.06 s. The result was the same with ulimit -s set to 8176, 16384, 32768 and 65520.

Optional, non-blocking: on 3.15 the probe has only one doubling of headroom left before self.fail("JSON decoder accepted every bounded nesting probe"). If a later CPython raises its stack budget again, the test fails while the checker is still correct. Raising range(8) to e.g. range(12) gives more margin. The largest input that would build is about 4 MiB per string at 3.13's starting depth, and the test exits at the first depth that raises, so this costs nothing on current versions.

Scope check: this is the only json.loads in check_conventions.py. The --body read does no parsing, so it needs no change.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 14212 tools/check_conventions.py

@BunnyFl BunnyFl left a comment

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.

Looks right, and it fills in the newer-Python case you didn't run. The other except (OSError, ValueError) in main (the --body read) is a plain read_text with no JSON decoding, so --event is the only site that needs RecursionError.

Tested on Windows 11, Python 3.14.7, with head e6ddf01fa5 merged onto current main: tools/tests/test_check_conventions_body_bom.py passes 9 tests. With check_conventions.py put back to main's version, test_excessively_nested_event_is_a_usage_error fails, so the depth probe still finds a nesting the 3.14 decoder rejects.

@oneandonlydean
oneandonlydean merged commit 6113756 into boykopovar:main Nov 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants