Skip to content

fix(routes): find a route path wherever it sits in a decorator's arguments - #2008

Merged
DeusData merged 1 commit into
DeusData:mainfrom
CaptainMittens:fix/decorator-arg-scan-limit
Sep 2, 2026
Merged

fix(routes): find a route path wherever it sits in a decorator's arguments#2008
DeusData merged 1 commit into
DeusData:mainfrom
CaptainMittens:fix/decorator-arg-scan-limit

Conversation

@CaptainMittens

Copy link
Copy Markdown
Contributor

Java and Kotlin put no order on annotation attributes, so a path written after method, produces and consumes is ordinary source:

@RequestMapping(method = RequestMethod.GET,
                produces = MediaType.APPLICATION_JSON_VALUE,
                consumes = MediaType.APPLICATION_JSON_VALUE,
                path = "/orders")

extract_route_path_from_args stopped after the third argument, so the path was never read and no Route node formed. The endpoint sat in the source and not in the graph.

Reproduced before it was fixed

The test in this PR fails on current main:

[ET-ROUTE] FAIL expected=1 actual=1 missing: /api/orders available: /api

available: /api is the class-level @RequestMapping("/api"). Only the method route is lost.

The change

One loop bound. The loop now checks every argument.

That matches find_drf_kwarg_in_args a few lines below, which walks the same kind of node with no cap. Cost stays small: the loop returns on the first path-shaped string, and each argument keeps its own bounded subtree walk in find_route_path_literal.

Four call sites read this function, so the miss covered plain decorator arguments, Spring mappings, and both JAX-RS paths.

What is deliberately unchanged

find_route_path_literal keeps DECORATOR_SCAN_LIMIT. That loop is recursive and descends to CBM_DESCENDANT_MAX_DEPTH, so breadth 3 by depth 6 is 729 visits at worst and unbounded breadth there is not bounded at all. The constant now guards only that loop, which is what its name describes.

Tests

handles_spring_java_path_attribute_fourth in tests/test_edge_types_probe.c, beside the existing Spring tests. It asserts the exact Route set rather than a HANDLES count, because the class-level mapping still produces one route on its own and a count alone would pass.

Full C suite on this branch: 7780 passed, 7 skipped. Two test_cli.c install/uninstall tests fail on my machine with or without this change, because they read the coding agents actually installed there.

…ments

Java and Kotlin put no order on annotation attributes, so `path` can
appear after `method`, `produces` and `consumes`:

    @RequestMapping(method = RequestMethod.GET,
                    produces = MediaType.APPLICATION_JSON_VALUE,
                    consumes = MediaType.APPLICATION_JSON_VALUE,
                    path = "/orders")

extract_route_path_from_args stopped after the third argument, so the
path was never read and no Route node formed. The endpoint was in the
source and not in the graph.

Four call sites read this function, so the miss covered plain decorator
arguments, Spring mappings, and both JAX-RS paths.

The loop now checks every argument. That matches find_drf_kwarg_in_args
a few lines below, which walks the same kind of node with no cap. Cost
stays small: the loop returns on the first path-shaped string, and each
argument's own subtree walk is still bounded by find_route_path_literal.

The recursive breadth guard inside find_route_path_literal keeps
DECORATOR_SCAN_LIMIT. That loop descends to CBM_DESCENDANT_MAX_DEPTH, so
breadth 3 by depth 6 is 729 visits at worst and unbounded breadth there
is not bounded at all.

The test fails without the change, reporting the class-level "/api"
route present and "/api/orders" missing.

Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

@DeusData

DeusData commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Approved. Verified against main: DECORATOR_SCAN_LIMIT = 3 (extract_defs.c:38) bounds the loop in extract_route_path_from_args, so your description is exact — a path written fourth is never read, and the endpoint exists in the source but not in the graph.

The diagnosis is right for the right reason: Java and Kotlin put no order on annotation attributes, so @RequestMapping(method=…, produces=…, consumes=…, path=…) is not unusual code that deserves to lose — it is ordinary code, and a positional cap on an unordered construct is a bug rather than a tuning value. Three attributes before the path is a completely normal Spring mapping.

Two things I want to credit specifically:

The RED output proves the test is not vacuous. missing: /api/orders available: /api — the class-level @RequestMapping("/api") is still found, so the fixture really does exercise route extraction and only the method route is lost. A bare "expected 1 got 0" would not have shown that.

And you drew the line in the right place. Removing the cap here while keeping DECORATOR_SCAN_LIMIT on find_route_path_literal is the distinction that matters: this loop is flat and returns on the first path-shaped string, while that one recurses to CBM_DESCENDANT_MAX_DEPTH, where unbounded breadth genuinely is unbounded. Explaining the 729-visit worst case rather than removing both caps is what makes this safe to take.

Citing find_drf_kwarg_in_args a few lines below as the uncapped precedent for the same node shape settles the consistency question too.

Merging once CI reports green — our Actions pool is badly backlogged right now, so it may sit a while. That is on us, not on this PR.

@DeusData
DeusData merged commit aefd2a1 into DeusData:main Sep 2, 2026
35 checks passed
@DeusData

DeusData commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Merged as aefd2a18. Thank you.

The reason this was straightforward to accept is that you framed it correctly: Java and Kotlin impose no order on annotation attributes, so a positional cap on an unordered construct is a defect rather than a tuning value. @RequestMapping(method=…, produces=…, consumes=…, path=…) is ordinary Spring, not an exotic shape, and DECORATOR_SCAN_LIMIT = 3 meant the endpoint existed in the source and not in the graph.

Two things I want to credit specifically, because they are what separated this from a one-line constant bump:

The RED output proved the fixture was not vacuous. missing: /api/orders available: /api shows the class-level @RequestMapping("/api") was still being found — so route extraction genuinely ran and only the method route was lost. A bare "expected 1, got 0" would have left that ambiguous.

And you drew the line in the right place. Removing the cap on the flat loop while keeping DECORATOR_SCAN_LIMIT on the recursive find_route_path_literal is the distinction that matters: one returns on the first path-shaped string, the other descends to CBM_DESCENDANT_MAX_DEPTH where unbounded breadth really is unbounded. Working out the 729-visit worst case rather than removing both caps is what made this safe to take without a wider audit.

Citing find_drf_kwarg_in_args as the existing uncapped precedent for the same node shape settled the consistency question too.

#2009 is at 33 of 34 checks and I will merge it as soon as its last one reports — our Actions pool has been badly backlogged today, so the wait is ours, not yours.

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.

2 participants