Skip to content

fix(routes): read Go 1.22 ServeMux method and host patterns - #2752

Merged
DeusData merged 10 commits into
DeusData:mainfrom
gaurav0107:fix/2537-go-net-http-servemux-patterns-with-a-met
Oct 31, 2026
Merged

DeusData merged 10 commits into
DeusData:mainfrom
gaurav0107:fix/2537-go-net-http-servemux-patterns-with-a-met

Conversation

@gaurav0107

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #2537. Since Go 1.22 a net/http ServeMux pattern may name a method and a host before the path (mux.HandleFunc("POST /v1/payments", h), "GET /v1/payments/{id}", "api.example.com/v1/x"). These registrations produced no Route and no HANDLES edge, because every route-registration gate required the first argument to start with /:

  • internal/cbm/extract_calls.c: the handler argument was captured only for a /-prefixed path
  • src/pipeline/pass_parallel.c: find_route_path_in_args / is_route_path_shaped
  • src/pipeline/pass_calls.c: the three sequential-pass gates (emit_classified_edge, the PHP/Laravel: routes inside Route::prefix()->group() produce no Route nodes #952 unresolved-callee fallback, the cross-language refusal fallback)

The change:

  • cbm_service_pattern_mux_split() parses the [METHOD ][HOST]/[PATH] grammar. METHOD is a run of uppercase letters ended by spaces/tabs (Go cuts at the first space or tab). HOST is a non-empty run with no whitespace before the first /. It returns a pointer to the path inside the written pattern.
  • Only for Go files, the gates above accept such a pattern and register its path. Every other language, and every plain /path, goes through the existing code unchanged.
  • The Route's method comes from the pattern only when the callee doesn't name one (.Handle / .HandleFunc are ANY). A callee that names its verb keeps it.
  • The host is dropped from the Route path. Recording it as a property would change Route identity and props in both passes, so I left it out of this fix.
  • Both passes use the same two helpers (cbm_pipeline_mux_route_path, cbm_pipeline_route_reg_method), so they produce byte-identical Routes.

Evidence

Machine: Amazon Linux 2023, x86_64, clang 15.0.7 (gcc here has no libasan, so the sanitizer build uses clang).

New tests (reproduce-first, run against the unfixed tree first):

Test Unfixed tree (main @ 3457b21 + tests) With fix
extraction: extract_go_servemux_method_pattern_handler_issue2537 FAIL: method->second_arg_name is NULL PASS
pipeline: pipeline_go_servemux_method_patterns_issue2537 (sequential and parallel, with 52 padding files to go past MIN_FILES_FOR_PARALLEL) FAIL: post[mode] == 0, expected 1 PASS
infrascan: infrascan_servemux_pattern_split_issue2537 (grammar edge cases: tabs, host:port, DELETE /, lowercase word, no path, oversize method) n/a (new function) PASS

The pipeline test also covers a host-only pattern, a legacy /v1/legacy control (still __route__ANY__), no __route__ANY__ twin for a method pattern, and a Python add_url_rule("GET /py", ...) that must still produce no route.

Commands:

scripts/test.sh --suites extraction,pipeline CC=clang CXX=clang++ SANITIZE="-fsanitize=address -fno-omit-frame-pointer"
  unfixed tree: 891 passed, 2 failed (the two new tests)
  with fix:     893 passed

scripts/test.sh --suites infrascan,extraction,pipeline,cross_repo,route_canon,openapi,go_lsp,parallel,incremental,integration,edge_types_probe,edge_structural,lang_contract,node_creation_probe,call_reference_contract,repro_call_node_behaviors,grammar_probe_f,simhash,mcp CC=clang CXX=clang++ SANITIZE="-fsanitize=address -fno-omit-frame-pointer"
  2150 passed, 4 skipped (Windows-only), 0 failed, no sanitizer reports

End to end with a product build (scripts/build.sh), using the issue's repro plus a host-only registration:

main @ 3457b21 this branch
Go 1.22 patterns: Routes / HANDLES 0 / 0 3 / 3: __route__POST__/v1/payments, __route__GET__/v1/payments/{}, __route__GET__/v1/hosted
Legacy /path control 3 / 3 __route__ANY__… identical

Not verified locally

  • The full default scripts/test.sh venue run: on main it stops at Step 0w (the published language-count contract, for docs/index.html, docs/llms.txt, pkg/npm/README.md, scripts/package-release.sh, server.json). scripts/test.sh --contracts-only gives the same five failures on this branch and on pristine main. This diff doesn't touch those files.
  • scripts/lint.sh: cppcheck 2.20 / clang-format-20 aren't installed here. The changed lines pass clang-format 15 (clang-format-diff). I'm relying on the lint CI job for the rest.

Checklist

  • Every commit is signed off (git commit -s): scripts/check-dco.sh upstream/main..HEAD OK
  • Tests pass locally (make -f Makefile.cbm test): the 19 related suites above pass. The full venue run wasn't done (see above).
  • Lint passes (make -f Makefile.cbm lint-ci): not run locally (see above).
  • New behavior is covered by a test (reproduce-first for bug fixes)

🤖 Opened with Superhuman, an open-source contribution agent.

Since Go 1.22 a net/http ServeMux pattern may name a method and a host
before the path: mux.HandleFunc("POST /v1/payments", h),
"GET /v1/payments/{id}", "api.example.com/v1/x". Every route
registration gate wanted the first argument to open with a slash (the
extractor's handler capture, find_route_path_in_args in the parallel
pass, and the three gates in the sequential pass), so such a
registration minted no Route and no HANDLES edge, while the same file
written with "/v1/payments" had both.

cbm_service_pattern_mux_split reads the "[METHOD ][HOST]/[PATH]"
grammar. For Go files only, the path of such a pattern now registers
the route, the Route takes the pattern's method when the callee names
none (Handle/HandleFunc are ANY), and a host is dropped from the path.
Both passes share cbm_pipeline_mux_route_path and
cbm_pipeline_route_reg_method so their Routes stay identical. Other
languages and plain "/path" patterns take the unchanged path.

Fixes DeusData#2537

Signed-off-by: Gaurav Dubey <gauravdubey0107@gmail.com>
@github-actions

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.

@gaurav0107

Copy link
Copy Markdown
Contributor Author

This comment was written by an AI agent operating this account on behalf of @gaurav0107.

The red test-unix (ubuntu, macos) and test-windows legs, plus shard-completeness and ci-ok which depend on them, come from a check that fails on main too, not from this change. scripts/test.sh stops at Step 0w, the published language-count contract, before any suite runs. That's why no shard manifests were uploaded:

=== Step 0w: published language-count contract ===
FAIL: docs/index.html is registered as a language-count surface but states no count ...
FAIL: docs/llms.txt ...
FAIL: pkg/npm/README.md ...
FAIL: scripts/package-release.sh ...
FAIL: server.json ...
FAIL: 5 language-count contract violation(s)

To check: scripts/test.sh --contracts-only on a clean checkout of main @ 3457b21 gives the same five FAIL lines (I diffed them against this branch: identical). This PR doesn't touch any of those files. The other jobs are green: lint, lint-mem, test-tsan, pr-smoke, codeql, security-static, dco. The suite results for the changed code are in the PR description (19 suites, 2150 passed, 0 failed, ASan+UBSan).

@gaurav0107
gaurav0107 marked this pull request as ready for review October 21, 2026 14:01
@gaurav0107
gaurav0107 requested a review from DeusData as a code owner October 21, 2026 14:01
@DeusData DeusData added bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges labels Oct 25, 2026
The Go 1.22 pattern split ran for every route-verb callee, so other Go
calls whose name ends in Get/Post/Put/Delete/Patch became route
registrations: st.Get("users/123") on a project method minted
__route__GET__/123 and lost its CALLS edge to Store.Get, and
c.Get("https://api.example.com/v1/x") split "https:" off as a host and
minted __route__GET__//api.example.com/v1/x, in both passes.

cbm_service_pattern_mux_route_path reads the pattern only for a callee
ending ".Handle" or ".HandleFunc", and never for an argument containing
"://". The extractor's handler capture and both passes (route_reg_path,
find_route_path_in_args, cbm_pipeline_route_reg_method) go through it,
so the two passes still mint identical Routes. Two negative cases
extend pipeline_go_servemux_method_patterns_issue2537 in both passes,
and the infrascan unit covers the gate.

Refs DeusData#2537

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you so much, Gaurav, for this careful fix. Your parser follows Go's "[METHOD ][HOST]/[PATH]" rule exactly, the shared helpers keep the sequential and parallel passes byte-identical, and the legacy "/path" and Python "GET /py" controls in the test made the change easy to trust. We pushed two small commits on top of your branch so it can land:

  1. fix(routes): read a ServeMux pattern only for Handle and HandleFunc (4d97d78). The pattern split ran for every call whose name ends in a route verb, so other Go calls were becoming Routes: store.Get("users/123") turned into a /123 Route and lost its CALLS edge to Store.Get, and client.Get("https://...") read "https:" as a host and became a //api.example.com/... Route instead of an ordinary call. The pattern is now read only for Handle and HandleFunc, and never for a string containing "://". Two negative cases in pipeline_go_servemux_method_patterns_issue2537 cover this in both passes, and the infrascan test covers the new check. On a real Go repository (deckhouse) the branch still adds every Go 1.22 method registration and no other Route.

  2. Merged current main (2ba6e64). This brings in the language-count contract repair, which fixes the test-unix, test-windows and shard-completeness failures that you correctly said did not come from this PR.

Please pull before pushing again, so your next push builds on these commits. Thanks again for the contribution!

@gaurav0107

Copy link
Copy Markdown
Contributor Author

This comment was written by an AI agent operating this account on behalf of @gaurav0107.

Thank you for the review and for the two follow-up commits. Restricting pattern parsing to Handle/HandleFunc and skipping strings that contain :// fixes the store.Get("users/123") and client.Get("https://...") false positives I missed, and the negative cases in both passes cover them well. Merging main for the language-count contract repair also explains the CI failures. Any further pushes will be based on 2ba6e64, so your commits stay in.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thanks again, @gaurav0107. Several other pull requests landed on main this evening, including changes in files this one also touches, so we merged current main into your branch once more (one signed-off merge commit, 20a6f1f; your changes are untouched). That keeps the required checks honest against today's tree; we will merge once they are green. Please pull before pushing again.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you for your patience, @gaurav0107. CI went green on this branch, but meanwhile #2430 merged changes into tests/test_pipeline.c, which this pull request also touches, so that green no longer covered today's main. We merged current main into the branch once more (one merge commit, 3b646da; your changes are untouched) and will merge as soon as the fresh checks are green. Please pull before pushing again.

origin/main (DeusData#2305) moved the route-registration checks of the sequential
resolver into calls_emit_route_call and made emit_classified_edge return
whether it emitted. Keep the structure and bookkeeping of main and carry
the ServeMux pattern reading over: emit_classified_edge and
calls_emit_route_call now take the registration path from route_reg_path,
so "POST /v1/x" patterns register in both passes as before.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you again @gaurav0107 for this fix. Reading the method and host out of Go 1.22 ServeMux patterns, so that "POST /v1/payments" becomes a POST route with its handler, closes a real gap for every modern net/http service, and the tests run the fixture through both the sequential and the parallel pass, which made this update easy to check.

We pushed one more commit to your branch: a merge of current main, signed off (f33f976). Main had moved the route-registration checks of the sequential call resolver into a shared helper (calls_emit_route_call) and now records whether each call emitted an edge. That conflicted in three small places in src/pipeline/pass_calls.c. We kept the new structure from main and carried your change over: emit_classified_edge and calls_emit_route_call now take the registration path from your route_reg_path, so ServeMux patterns register exactly as before. pass_parallel.c merged cleanly and is unchanged from your version. Your servemux tests and the extraction, infrascan, pipeline, parallel and incremental suites pass on the merged tree.

Please pull before pushing again so your local branch picks up the merge. Thanks for the careful work!

@gaurav0107

Copy link
Copy Markdown
Contributor Author

Thank you @DeusData for keeping this branch current with main (merges 20a6f1f, 3b646da and f33f976), and for carrying the route_reg_path change through the calls_emit_route_call refactor in src/pipeline/pass_calls.c. I have pulled the branch locally (now at f33f976) and will build on top of it if anything else is needed. Nothing further from my side; happy for this to merge once checks are green.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you again, @gaurav0107, for your patience with this one. Two pull requests merged into main a few minutes ago (#2754 and #2090), and they touch src/pipeline/pass_parallel.c, which this pull request also changes. So we merged current main into your branch once more (one merge commit, d758fd6; it merged cleanly and your own change is untouched), checked that the merged files still compile, and started a fresh CI run. We will merge as soon as the required checks are green. Please pull before pushing again so the merge commit is kept.

@gaurav0107

Copy link
Copy Markdown
Contributor Author

Thanks for merging main in again (d758fd6) and kicking off CI. I will not push to this branch again without pulling first, so the merge commit stays in place.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you for your patience, @gaurav0107. A pull request that merged into main a few minutes ago (#1710) changes internal/cbm/extract_calls.c, which this pull request also changes, so the CI run in progress here could no longer vouch for today's main. We merged current main into your branch once more (one merge commit, fec0b87; it merged cleanly and your own change is untouched) and started a fresh run. We will merge once the required checks are green. Please pull before pushing again.

@gaurav0107

Copy link
Copy Markdown
Contributor Author

Thanks for merging main in and restarting CI. Noted on fec0b87; I'll pull before any further push.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you for your patience, @gaurav0107. Main has moved since the last CI run on this branch, and some of the new commits change src/pipeline/pass_calls.c and pass_parallel.c, which this pull request also changes, so that run can no longer vouch for today's main. Separately, the Windows leg of that run failed with an intermittent crash at the start of a few test suites that we are tracking on our side; it is not caused by your change. We merged current main into your branch (one merge commit, 46c95bc; it merged cleanly and your own change is untouched) and started a fresh run. We will merge once the required checks are green. Please pull before pushing again.

@gaurav0107

Copy link
Copy Markdown
Contributor Author

Thanks for merging main in and restarting CI, and for the note about the Windows crash. I've synced my local branch to 46c95bc and will pull before any further push.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

Copy link
Copy Markdown
Owner

Thank you for your patience, @gaurav0107. The macOS legs of the last CI run here were cancelled by GitHub itself: it is retiring its macOS 14 runners on November 2 and ran scheduled brownouts beforehand, so those jobs never got a runner. Nothing in your change caused it. Main now runs CI on macOS 15 (#2849), so we merged current main into your branch (one merge commit, 3e075d5; it merged cleanly and your own change is untouched) to get a run on the new runners. Please pull before pushing again.

@gaurav0107

Copy link
Copy Markdown
Contributor Author

Thanks for explaining the macOS runner cancellations and for merging main in. I have pulled 3e075d5 locally and will build on it for any further pushes.

@DeusData
DeusData merged commit 747665d into DeusData:main Oct 31, 2026
26 checks passed
@DeusData

Copy link
Copy Markdown
Owner

Merged as 747665d - thank you, @gaurav0107! Go 1.22 ServeMux patterns with a method and an optional host now become Routes with their method, and the handler gets its HANDLES edge, while plain paths keep working as before. Thank you also for accepting the follow-up narrowing so cleanly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go: net/http ServeMux patterns with a method or host ("POST /v1/x", "GET host/x") produce no Route nodes

2 participants