fix: resolve URL-builder helper calls to Route/HTTP_CALLS - #1010
Conversation
|
Pushed a follow-up commit: |
9d58769 to
fd03211
Compare
|
Rebased onto the updated #1008 with sign-offs on both commits; no content changes. Full suite passes locally on the stacked branch. |
fd03211 to
26c22d0
Compare
|
Rebased the stack onto the updated #1008, which now sits on current main. The only conflict was the adjacent handler registration from another merged PR in extract_unified.c, resolved keeping both. Full suite on the stacked branch: 6360 passed, 1 skipped, 0 failures. lint-format clean. Once #1008 lands this rebases onto main trivially. |
|
Reviewed in depth. The mechanism is genuinely well built and the direction is right — but it needs one gate before it can merge, because as written it fires on C and Go too. What is good, and I want to be specific. The ambiguity tombstone — two different literal URLs turning a builder into a deliberate miss rather than a guess — is exactly the right instinct for this codebase. So is truncating at I also checked the things that usually go wrong here and found them handled: Route QNs are deterministic ( The blocker: The predicate accepts any absolute pathname, not just routes — So on a C codebase: static const char *cfg_path(void) { return "/etc/myapp/conf.d"; }
...
parse_config(cfg_path());
That shape is everywhere in exactly the C codebases we showcase: Under our rule that a wrong edge is worse than a missing one, that is a request-for-changes rather than a nit. The fix is small: gate the recording (and ideally the Two narrower false-positive shapes worth fixing while you are there:
Related and smaller: recording keys on the bare last QN component means a class method On tests: the three you added are good. What is missing is the negative side, which for a feature whose entire risk is false positives is the side that matters — nothing asserts that a helper returning a non-URL string does not populate One thing you should know about, which is not a defect: this PR physically contains #1008's commit Add the language gate and a negative test or two, and I would like to land this — the underlying idea is right and the implementation quality is high. |
|
Re-checked this while working through the backlog, and I can now confirm the cross-language concern from the July review as a verified finding rather than a suspicion.
The concrete failure that produces: a C or Go helper returning any absolute pathname — Your three tests are good and genuinely binding — they assert So the asks from the July review still stand, and the first is now confirmed rather than precautionary:
The branch also conflicts with None of this is a rejection — resolving URL-builder helpers to Route edges is a real gap and your approach to it is sound. It just cannot land while it can invent HTTP routes in C. If you'd rather hand it off at this point, say so and I'll finish it with you credited as co-author. |
) URL-shaped literals returned from small builder functions never reached call arguments, so client(buildPath(id)) produced no HTTP_CALLS edge and no Route node, static or template alike. handle_url_builders() records builderName -> returned URL in the same per-file constant map used for module-level consts, covering return statements and arrow expression bodies; a call_expression branch in extract_url_or_topic_arg() resolves client(buildPath(id)) through it. Composed builders inline already-recorded substitutions and truncate the query string, so `${basePath(id)}?${params}` joins the server route exactly. Ambiguous builders (two different URLs) are tombstoned. Signed-off-by: Charles Queiroz <fcqueiroz@liquibase.com>
detect_url_in_args() (the arg_url HTTP_CALLS emitter used when the callee
is a local client wrapper, not a known HTTP library) reads per-arg values,
not first_string_arg. Resolve call_expression args through the builder map
and flatten template_string args to the {} form there as well, so wrappers
like apiFetch(buildPath(id)) emit the edge and join the canonical route.
Signed-off-by: Charles Queiroz <fcqueiroz@liquibase.com>
|
A correction to my own review, in your favour — and a smaller remaining scope than I described. The example I gave you was wrong. I said a C helper returning something like The underlying concern is still real, just narrower than I made it sound. That prefix list is a fixed ten. An absolute path outside it — A wrong edge is worse than a missing one here, so the JS/TS language gate is still the fix I want — I just want you chasing the actual failure shape rather than the one I invented. Two things are now easier than when I last wrote.
The gap itself is confirmed still open. I also re-confirmed the thing I liked most about the design: you never mint a Route or an edge yourself. The change stops at populating So the remaining list is: gate the dispatch to JS/TS; add a negative test with a non-JS/TS fixture returning a path-shaped string outside those ten prefixes, asserting no Route appears; address the two narrower false-positive shapes (a builder with one literal and one computed return does not tombstone, so every call site is attributed to the literal; and a builder entry in the shared map means a bare function reference used as a callback resolves to the URL); and rebase. You have been quiet for five weeks and I offered yesterday to finish this with you credited as co-author. That offer stands and there is no wrong answer — but I would rather have a "go ahead" or a "not for me" than keep guessing. If I do not hear back, I will take the hand-off route and credit you, because the fix is worth having and #1009 has been open a long time. |
handle_url_builders ran for every vendored grammar, so a C helper returning an absolute pathname became a URL builder and minted a Route node plus an HTTP_CALLS edge in a language that speaks no HTTP. Gate recording to JavaScript, TypeScript and TSX. Builder entries now carry a flag in the per-file constant map: only a call_expression argument resolves one, so handing a builder to a callback builds no request. A builder whose returns are not all route-shaped literals is declined instead of attributing its one literal to call sites that take the computed branch. Five negative tests cover the cross-language case, the mixed builder, the ambiguity tombstone, the builder reference and the non-URL helper. Signed-off-by: Charles Queiroz <fcqueiroz@liquibase.com>
26c22d0 to
606052a
Compare
|
All three asks are in, plus the rebase onto current main. The dispatch is gated: Mixed builders are declined rather than tombstoned. A builder is recorded only if every Five negative tests: the C helper returning |
|
The one red check, Attribution: this diff touches only URL-builder extraction and never reaches the watcher, the store or the daemon. The new map field lives in a designated-initializer struct, so it is zero-filled and cannot be read uninitialized. The watcher suite passes locally, seventy-two of seventy-two, three runs in a row, and the full suite is green at 7576 passed. Could you re-run that job? I get a 403 on the rerun endpoint. |
DeusData
left a comment
There was a problem hiding this comment.
Approving and merging — and apologies for the wait on the rerun. You were right that it was the flake: I re-ran the failed leg and it went green. test-lsan-macos / watcher_unwatch_drains_pending_free is a known recurring failure on that leg specifically, independent of the diff (it has shown up on PRs touching only install.ps1 and only the CLI), and this change touches nothing outside extraction and tests.
I re-verified the three hardening asks rather than taking them on trust, and all three are genuinely closed in the code:
- Language gate —
handle_url_buildersreturns unless JS/TS/TSX, and resolution is transitively gated because only gated recording can populate a builder entry in the first place. That is the right place to put it. - Mixed returns —
builder_returns_only_urlsdeclines any builder with a non-route return, order-independently, and correctly excludes nested functions' returns viats_node_eqon the enclosing function. That was the subtle half. - Builder-as-callback — the new
is_url_builderflag meanslookup_string_constantskips builder entries, soids.map(thingPath)no longer resolves. Only call arguments do.
The ambiguity tombstone and the ? truncation both behave as described, and I like that the resolution mints nothing itself — it only populates values, leaving the existing route visitor to mint the Route by canonical QN, so client and server sides join instead of duplicating. Misses over guesses throughout, which is the right instinct for this subsystem.
One thing I am fixing on our side rather than asking you for another round. The gate test extract_c_url_builder_gated_issue1009 does not actually bind the gate. Its fixture returns /etc/myapp/conf.d, and is_filesystem_path() rejects /etc/ before is_rest_path() is ever consulted — so the value is never classified as a URL and never recorded, gate or no gate. Remove the language gate entirely and that test still passes.
That is my fault as much as anything: it is the example I used myself in July, and I only corrected it to "use a path outside those ten prefixes" on the 21st, a few hours before you pushed. I am landing a one-word follow-up (/srv/myapp/conf.d, which is not in the prefix list and so is genuinely URL-shaped) so the gate has real coverage. Nothing about your implementation changes — only the fixture.
Two smaller notes, neither blocking and neither needing action from you:
builder_returns_only_urlsre-walks the enclosing function body for every URL-shaped return, so a function with K such returns does K walks. It is bounded per-file and cannot go corpus-quadratic, but a visited-function memo would be a cheap improvement if you ever revisit this.- While reviewing I noticed something pre-existing and unrelated to your change: the per-arg minting path
detect_url_in_args→is_junk_urlappears to have no filesystem-path guard of its own. That is on my list to look at separately; your values are protected by the classify chain.
Thank you for the patience through a long review and for doing the hardening properly rather than arguing the FP shapes were unlikely. The negative tests in particular are the reason this was reviewable.
…shape The fixture returned "/etc/myapp/conf.d", which is_filesystem_path() rejects before is_rest_path() is consulted, so the value was never classified as a URL and never recorded as a builder -- with or without the language gate. The test passed identically with the gate removed, so it certified nothing. "/srv/" is not one of the ten filesystem prefixes, so the value is URL-shaped and only the JS/TS/TSX gate prevents it being recorded for C. Verified by revert-check: with the gate removed the test now FAILS at tests/test_extraction.c:3455; with the gate restored it passes. Follow-up to DeusData#1010. The gate implementation is unchanged and is the author's. Co-authored-by: Charles Queiroz <fcqueiroz@liquibase.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Fixes #1009. Stacked on #1008 (includes its commit; rebases cleanly once #1008 lands).
Root cause
URL-shaped literals returned from small builder functions never reach a call argument, so
client(buildPath(id))had nofirst_string_arg: no HTTP_CALLS edge, no Route node, static and template literals alike.lookup_string_constantonly covers module-level consts.Fix
handle_url_builders()(extract_unified.c, runs in the unified walk): when areturnstatement (or an arrow-function expression body) yields a URL-shaped literal, recordsbuilderName -> urlin the same per-file constant map used for module-level consts. Ambiguous builders (two different URLs for one name) are tombstoned so lookups miss instead of guessing.extract_url_or_topic_arg()(extract_calls.c): acall_expressionargument whose callee is a plain identifier resolves through that map, giving the call itsfirst_string_arg; the existing pipeline then mints the Route node and the HTTP_CALLS edge from the real HTTP caller (the hook), not from the builder.${...}substitution that is a bare identifier or a call to an already-recorded name inlines that value, everything else becomes{}, and the result is truncated at the first?(query strings are not part of a route's identity). This covers the real-world TanStack shape:Same-file scope, document order (same constraints the const map already has).
Validation
extract_ts_url_builder_issue1009(return + arrow bodies) andextract_ts_url_builder_composed_issue1009(composition + query truncation).queryFn -HTTP_CALLS-> /api/v1/team-members/{}/activitynow exists for the builder shape, joining the server-side__route__GET__/api/v1/team-members/{}/activityexactly.