mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(routes): read a ServeMux pattern only for Handle and HandleFunc
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 #2537
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
ad7899399b
commit
4d97d7819b
8 files changed
+137
-17
No files matched your search
@@ -4559,7 +4559,8 @@ CBMInvocationDescriptor handle_calls(CBMExtractCtx *ctx, TSNode node, const CBML
|
||||
if (call.first_string_arg &&
|
||||
(call.first_string_arg[0] == '/' ||
|
||||
(ctx->language == CBM_LANG_GO &&
|
||||
cbm_service_pattern_mux_split(call.first_string_arg, NULL, 0) != NULL)) &&
|
||||
cbm_service_pattern_mux_route_path(call.callee_name, call.first_string_arg) !=
|
||||
NULL)) &&
|
||||
!(laravel_uri && php_route_action_is_string(node))) {
|
||||
call.second_arg_name = extract_handler_arg(ctx, args);
|
||||
}
|
||||
|
||||
@@ -1227,6 +1227,21 @@ const char *cbm_service_pattern_mux_split(const char *pattern, char *method, siz
|
||||
return slash;
|
||||
}
|
||||
|
||||
const char *cbm_service_pattern_mux_route_path(const char *callee_name, const char *pattern) {
|
||||
static const char *const mux_callees[] = {".Handle", ".HandleFunc"};
|
||||
if (!callee_name || !pattern || strstr(pattern, "://") != NULL) {
|
||||
return NULL;
|
||||
}
|
||||
size_t clen = strlen(callee_name);
|
||||
for (size_t i = 0; i < sizeof(mux_callees) / sizeof(mux_callees[0]); i++) {
|
||||
size_t slen = strlen(mux_callees[i]);
|
||||
if (clen >= slen && strcmp(callee_name + clen - slen, mux_callees[i]) == 0) {
|
||||
return cbm_service_pattern_mux_split(pattern, NULL, 0);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *cbm_service_pattern_broker(const char *resolved_qn) {
|
||||
if (!resolved_qn) {
|
||||
return NULL;
|
||||
|
||||
@@ -95,6 +95,14 @@ const char *cbm_service_pattern_route_method(const char *callee_name);
|
||||
* on the file language. */
|
||||
const char *cbm_service_pattern_mux_split(const char *pattern, char *method, size_t method_sz);
|
||||
|
||||
/* The path of a ServeMux registration's pattern (#2537): only a callee ending
|
||||
* ".Handle" or ".HandleFunc" registers one, and never with a URL ("://").
|
||||
* Another route-verb call names no pattern: `store.Get("users/123")` is no
|
||||
* host "users" + path "/123", `client.Get("https://...")` no host "https:".
|
||||
* The split of cbm_service_pattern_mux_split for such a registration, else
|
||||
* NULL. The grammar is Go's: callers gate on the file language. */
|
||||
const char *cbm_service_pattern_mux_route_path(const char *callee_name, const char *pattern);
|
||||
|
||||
/* Get the broker name for an async QN (e.g., "pubsub" from a Pub/Sub QN).
|
||||
* Returns NULL if not an async pattern. */
|
||||
const char *cbm_service_pattern_broker(const char *resolved_qn);
|
||||
|
||||
@@ -573,8 +573,9 @@ cbm_svc_kind_t cbm_pipeline_external_import_service(cbm_external_import_index_t
|
||||
|
||||
/* The path a route registration's first argument names: the argument itself
|
||||
* when it opens with a slash, or in Go the path of a ServeMux pattern that
|
||||
* names a method or host first ("POST /v1/x", #2537). NULL when it names no
|
||||
* path. MUST match pass_parallel.c find_route_path_in_args. */
|
||||
* names a method or host first ("POST /v1/x", #2537; Handle / HandleFunc
|
||||
* only). NULL when it names no path. MUST match pass_parallel.c
|
||||
* find_route_path_in_args. */
|
||||
static const char *route_reg_path(const CBMCall *call, CBMLanguage lang) {
|
||||
const char *arg = call->first_string_arg;
|
||||
if (!arg) {
|
||||
@@ -583,7 +584,7 @@ static const char *route_reg_path(const CBMCall *call, CBMLanguage lang) {
|
||||
if (arg[0] == '/') {
|
||||
return arg;
|
||||
}
|
||||
return cbm_pipeline_mux_route_path(arg, lang);
|
||||
return cbm_pipeline_mux_route_path(call, lang);
|
||||
}
|
||||
|
||||
/* Handle a route registration call: create Route node + HANDLES edge.
|
||||
|
||||
@@ -2263,11 +2263,12 @@ static bool is_route_path_shaped(const char *val) {
|
||||
return val && val[0] == '/' && !cbm_service_pattern_is_comment_text(val);
|
||||
}
|
||||
|
||||
const char *cbm_pipeline_mux_route_path(const char *arg, CBMLanguage lang) {
|
||||
const char *cbm_pipeline_mux_route_path(const CBMCall *call, CBMLanguage lang) {
|
||||
const char *arg = call->first_string_arg;
|
||||
if (lang != CBM_LANG_GO || !arg || cbm_service_pattern_is_comment_text(arg)) {
|
||||
return NULL;
|
||||
}
|
||||
return cbm_service_pattern_mux_split(arg, NULL, 0);
|
||||
return cbm_service_pattern_mux_route_path(call->callee_name, arg);
|
||||
}
|
||||
|
||||
const char *cbm_pipeline_route_reg_method(const CBMCall *call, CBMLanguage lang, char *buf,
|
||||
@@ -2276,7 +2277,7 @@ const char *cbm_pipeline_route_reg_method(const CBMCall *call, CBMLanguage lang,
|
||||
if (method && strcmp(method, "ANY") != 0) {
|
||||
return method;
|
||||
}
|
||||
if (cbm_pipeline_mux_route_path(call->first_string_arg, lang) &&
|
||||
if (cbm_pipeline_mux_route_path(call, lang) &&
|
||||
cbm_service_pattern_mux_split(call->first_string_arg, buf, buf_sz) && buf[0] != '\0') {
|
||||
return buf;
|
||||
}
|
||||
@@ -2291,9 +2292,9 @@ static const char *find_route_path_in_args(const CBMCall *call, const char **out
|
||||
*out_handler = call->second_arg_name;
|
||||
return call->first_string_arg;
|
||||
}
|
||||
/* 1b. Go ServeMux pattern with a method or host before the path (#2537).
|
||||
* MUST match pass_calls.c route_reg_path. */
|
||||
const char *mux_path = cbm_pipeline_mux_route_path(call->first_string_arg, lang);
|
||||
/* 1b. Go ServeMux pattern with a method or host before the path (#2537),
|
||||
* read for Handle / HandleFunc only. MUST match pass_calls.c route_reg_path. */
|
||||
const char *mux_path = cbm_pipeline_mux_route_path(call, lang);
|
||||
if (mux_path) {
|
||||
*out_handler = call->second_arg_name;
|
||||
return mux_path;
|
||||
|
||||
@@ -788,11 +788,12 @@ bool cbm_pipeline_http_client_call_url(const cbm_gbuf_t *gbuf, const char *proje
|
||||
void cbm_pipeline_close_call_props(char *props, int n, const CBMCall *call, bool with_line);
|
||||
|
||||
/* Go 1.22 ServeMux registrations may name a method and a host before the path
|
||||
* ("POST /v1/x", "GET api.example.com/v1/x", #2537). For a Go file, the path
|
||||
* inside such a route-registration argument; NULL for any other language and
|
||||
* for an argument that is no such pattern (a plain "/v1/x" included). The one
|
||||
* reader of that grammar behind both passes (pass_parallel.c). */
|
||||
const char *cbm_pipeline_mux_route_path(const char *arg, CBMLanguage lang);
|
||||
* ("POST /v1/x", "GET api.example.com/v1/x", #2537). For a Go `Handle` /
|
||||
* `HandleFunc` call, the path inside its first argument; NULL for any other
|
||||
* language or callee (`store.Get("users/123")`), for a URL argument, and for an
|
||||
* argument that is no such pattern (a plain "/v1/x" included). The one reader
|
||||
* of that grammar behind both passes (pass_parallel.c). */
|
||||
const char *cbm_pipeline_mux_route_path(const CBMCall *call, CBMLanguage lang);
|
||||
|
||||
/* The method of a route registration's Route: the verb its callee names
|
||||
* (`router.GET`), else the method its Go ServeMux pattern names
|
||||
|
||||
+16
-1
@@ -551,7 +551,8 @@ TEST(infrascan_data_flows_test_handlers_pair_only_within_their_file) {
|
||||
|
||||
/* Issue #2537: the Go 1.22 ServeMux grammar "[METHOD ][HOST]/[PATH]". The path
|
||||
* is found inside the written pattern; a plain path, a lowercase word, a
|
||||
* pattern without a path and a method too long for the buffer are no split. */
|
||||
* pattern without a path and a method too long for the buffer are no split.
|
||||
* Only a Handle / HandleFunc registration is read with it. */
|
||||
TEST(infrascan_servemux_pattern_split_issue2537) {
|
||||
char m[16];
|
||||
const char *pat = "POST /v1/payments";
|
||||
@@ -579,6 +580,20 @@ TEST(infrascan_servemux_pattern_split_issue2537) {
|
||||
ASSERT_NULL(cbm_service_pattern_mux_split(NULL, m, sizeof(m)));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_split("ABCDEFGHIJKLMNOP /x", m, sizeof(m)));
|
||||
ASSERT_STR_EQ(m, "");
|
||||
|
||||
/* only Handle / HandleFunc register a pattern, and never with a URL */
|
||||
pat = "POST /v1/payments";
|
||||
ASSERT_TRUE(cbm_service_pattern_mux_route_path("mux.HandleFunc", pat) == pat + 5);
|
||||
pat = "api.example.com/v1/x";
|
||||
ASSERT_TRUE(cbm_service_pattern_mux_route_path("s.mux.Handle", pat) == pat + 15);
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("st.Get", "users/123"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("c.Get", "https://api.example.com/v1/x"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("mux.Handle", "https://api.example.com/v1/x"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("PatchCollector.Delete", "storage.k8s.io/v1"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("r.Route", "GET /v1/x"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("mux.HandleFunc", "/v1/x"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path(NULL, "GET /v1/x"));
|
||||
ASSERT_NULL(cbm_service_pattern_mux_route_path("mux.HandleFunc", NULL));
|
||||
PASS();
|
||||
}
|
||||
|
||||
|
||||
+79
-1
@@ -13376,6 +13376,53 @@ TEST(pipeline_route_prefix_bridge_is_order_free_and_root_bridges_nothing) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Route nodes whose qualified name contains `part`. */
|
||||
static int route_nodes_containing(cbm_store_t *s, const char *project, const char *part) {
|
||||
cbm_node_t *routes = NULL;
|
||||
int route_count = 0;
|
||||
if (cbm_store_find_nodes_by_label(s, project, "Route", &routes, &route_count) != CBM_STORE_OK) {
|
||||
return -1;
|
||||
}
|
||||
int matches = 0;
|
||||
for (int i = 0; i < route_count; i++) {
|
||||
if (routes[i].qualified_name && strstr(routes[i].qualified_name, part)) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (routes) {
|
||||
cbm_store_free_nodes(routes, route_count);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/* CALLS edges out of the function `source_name` into a Route node: the edge a
|
||||
* route registration emits beside its Route (an HTTP client call's edge to a
|
||||
* Route is HTTP_CALLS instead). */
|
||||
static int calls_into_route_count(cbm_store_t *s, const char *project, const char *source_name) {
|
||||
cbm_edge_t *edges = NULL;
|
||||
int edge_count = 0;
|
||||
if (cbm_store_find_edges_by_type(s, project, "CALLS", &edges, &edge_count) != CBM_STORE_OK) {
|
||||
return -1;
|
||||
}
|
||||
int matches = 0;
|
||||
for (int i = 0; i < edge_count; i++) {
|
||||
cbm_node_t source = {0};
|
||||
cbm_node_t target = {0};
|
||||
int source_ok = cbm_store_find_node_by_id(s, edges[i].source_id, &source) == CBM_STORE_OK;
|
||||
int target_ok = cbm_store_find_node_by_id(s, edges[i].target_id, &target) == CBM_STORE_OK;
|
||||
if (source_ok && target_ok && source.name && target.label &&
|
||||
strcmp(source.name, source_name) == 0 && strcmp(target.label, "Route") == 0) {
|
||||
matches++;
|
||||
}
|
||||
cbm_node_free_fields(&source);
|
||||
cbm_node_free_fields(&target);
|
||||
}
|
||||
if (edges) {
|
||||
cbm_store_free_edges(edges, edge_count);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/* Issue #2537: since Go 1.22 a ServeMux pattern may name a method and a host
|
||||
* before the path ("POST /v1/payments", "GET api.example.com/v1/x"). Such a
|
||||
* registration minted no Route and no HANDLES edge, because every registration
|
||||
@@ -13383,7 +13430,10 @@ TEST(pipeline_route_prefix_bridge_is_order_free_and_root_bridges_nothing) {
|
||||
* written with "/v1/payments" had both. The method now comes from the pattern
|
||||
* and a host is dropped from the path, in the sequential and the parallel pass
|
||||
* alike. Only Go reads that grammar: a Python registration of "GET /py" is still
|
||||
* no route. */
|
||||
* no route. Only a ServeMux registration (Handle / HandleFunc) is read with it:
|
||||
* another route-verb call names no pattern and mints no Route, neither
|
||||
* `st.Get("users/123")` on a project method (which keeps its CALLS edge) nor
|
||||
* `c.Get("https://api.example.com/v1/x")` on a client. */
|
||||
TEST(pipeline_go_servemux_method_patterns_issue2537) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_servemux_XXXXXX");
|
||||
@@ -13401,6 +13451,16 @@ TEST(pipeline_go_servemux_method_patterns_issue2537) {
|
||||
"func hostOnly(w http.ResponseWriter, r *http.Request) {}\n"
|
||||
"func legacyList(w http.ResponseWriter, r *http.Request) {}\n"
|
||||
"\n"
|
||||
"type Store struct{ m map[string]string }\n"
|
||||
"\n"
|
||||
"func (s *Store) Get(k string) string { return s.m[k] }\n"
|
||||
"\n"
|
||||
"func lookup(st *Store) string { return st.Get(\"users/123\") }\n"
|
||||
"\n"
|
||||
"func fetchRemote(c *http.Client) {\n"
|
||||
"\t_, _ = c.Get(\"https://api.example.com/v1/x\")\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"func main() {\n"
|
||||
"\tmux := http.NewServeMux()\n"
|
||||
"\tmux.HandleFunc(\"POST /v1/payments\", createPayment)\n"
|
||||
@@ -13438,6 +13498,11 @@ TEST(pipeline_go_servemux_method_patterns_issue2537) {
|
||||
int legacy[2] = {-1, -1};
|
||||
int py_any[2] = {-1, -1};
|
||||
int py_get[2] = {-1, -1};
|
||||
int store_calls[2] = {-1, -1};
|
||||
int store_route[2] = {-1, -1};
|
||||
int lookup_reg[2] = {-1, -1};
|
||||
int remote_route[2] = {-1, -1};
|
||||
int remote_reg[2] = {-1, -1};
|
||||
for (int mode = 0; mode < 2; mode++) {
|
||||
if (mode == 0) {
|
||||
cbm_setenv("CBM_INDEX_SINGLE_THREAD", "1", 1);
|
||||
@@ -13463,6 +13528,11 @@ TEST(pipeline_go_servemux_method_patterns_issue2537) {
|
||||
legacy[mode] = handles_to_route(s, project, "legacyList", "__route__ANY__/v1/legacy");
|
||||
py_any[mode] = handles_to_route(s, project, "py_view", "__route__ANY__/py");
|
||||
py_get[mode] = handles_to_route(s, project, "py_view", "__route__GET__/py");
|
||||
store_calls[mode] = named_edge_count(s, project, "CALLS", "lookup", "Get");
|
||||
store_route[mode] = route_nodes_containing(s, project, "__route__GET__/123");
|
||||
lookup_reg[mode] = calls_into_route_count(s, project, "lookup");
|
||||
remote_route[mode] = route_nodes_containing(s, project, "//api.example.com");
|
||||
remote_reg[mode] = calls_into_route_count(s, project, "fetchRemote");
|
||||
}
|
||||
if (s) {
|
||||
cbm_store_close(s);
|
||||
@@ -13496,6 +13566,14 @@ TEST(pipeline_go_servemux_method_patterns_issue2537) {
|
||||
/* another language's "GET /py" is not read as a ServeMux pattern */
|
||||
ASSERT_EQ(py_any[mode], 0);
|
||||
ASSERT_EQ(py_get[mode], 0);
|
||||
/* a project method named Get keeps its CALLS edge; "users/123" is no
|
||||
* host + path */
|
||||
ASSERT_EQ(store_calls[mode], 1);
|
||||
ASSERT_EQ(store_route[mode], 0);
|
||||
ASSERT_EQ(lookup_reg[mode], 0);
|
||||
/* a client Get on a URL registers no route: "https:" is no host */
|
||||
ASSERT_EQ(remote_route[mode], 0);
|
||||
ASSERT_EQ(remote_reg[mode], 0);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user