diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index da6e720a2..449770d30 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -1156,6 +1156,54 @@ static void cbm_subtract_macro_invocation_regions(cbm_error_regions_t *regs, } /* Serialize collected regions as "start-end,start-end,..." into the arena. */ +/* #1929: the cgo preamble — the comment run directly above `import "C"` — is + * C source the Go grammar consumes as a comment, so every definition in it is + * absent from the graph while parse coverage reports the file as fully + * indexed. Collect that run as an unparsed region so the gap is visible. */ +static void cbm_flag_go_cgo_preamble(cbm_error_regions_t *regs, TSNode root, const char *source) { + uint32_t n = ts_node_named_child_count(root); + for (uint32_t i = 0; i < n; i++) { + TSNode child = ts_node_named_child(root, i); + if (strcmp(ts_node_type(child), "import_declaration") != 0) { + continue; + } + /* The spec path must be exactly "C" — the three bytes '"C"' appear in + * the clause text only for the pseudo-package (a longer path puts + * other characters inside the quotes). */ + uint32_t sb = ts_node_start_byte(child); + uint32_t eb = ts_node_end_byte(child); + bool is_cgo = false; + for (uint32_t b = sb; b + 2 < eb; b++) { + if (source[b] == '"' && source[b + 1] == 'C' && source[b + 2] == '"') { + is_cgo = true; + break; + } + } + if (!is_cgo) { + continue; + } + TSNode prev = ts_node_prev_named_sibling(child); + if (ts_node_is_null(prev) || strcmp(ts_node_type(prev), "comment") != 0) { + continue; /* no preamble — nothing hidden */ + } + uint32_t end_row = ts_node_end_point(prev).row; + uint32_t start_row = ts_node_start_point(prev).row; + while (true) { + TSNode before = ts_node_prev_named_sibling(prev); + if (ts_node_is_null(before) || strcmp(ts_node_type(before), "comment") != 0) { + break; + } + prev = before; + start_row = ts_node_start_point(prev).row; + } + if (regs->count < CBM_MAX_ERROR_REGIONS) { + regs->starts[regs->count] = start_row + 1; + regs->ends[regs->count] = end_row + 1; + regs->count++; + } + } +} + static const char *cbm_error_ranges_str(CBMArena *a, const cbm_error_regions_t *regs) { if (regs->count <= 0) { return NULL; @@ -1651,6 +1699,24 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua } } + /* #1929: a cgo file parses CLEAN — the C preamble is a comment to the Go + * grammar — so the block above never flags it, and coverage silently + * claims full indexing while every preamble definition is missing. Flag + * the preamble range explicitly. Detection aid only, like #963 above. */ + if (language == CBM_LANG_GO) { + cbm_error_regions_t cgo_regs = {{0}, {0}, 0}; + cbm_flag_go_cgo_preamble(&cgo_regs, root, source); + if (cgo_regs.count > 0) { + result->parse_incomplete = true; + result->error_region_count += cgo_regs.count; + const char *cgo_ranges = cbm_error_ranges_str(a, &cgo_regs); + result->error_ranges = + result->error_ranges + ? cbm_arena_sprintf(a, "%s,%s", result->error_ranges, cgo_ranges) + : cgo_ranges; + } + } + result->imports_count = result->imports.count; // Accumulate profiling counters diff --git a/internal/cbm/cbm.h b/internal/cbm/cbm.h index 4f06bebb7..31b5da42c 100644 --- a/internal/cbm/cbm.h +++ b/internal/cbm/cbm.h @@ -221,6 +221,11 @@ typedef struct { bool is_abstract; bool is_test; bool is_entry_point; + /* #1929: a cgo `//export Name` directive names this Go function's C + * symbol — a declared ABI contract (cgo requires Name to equal the func + * name). C-family callers bind it by contract (export_linkage) instead of + * short-name luck. Go only; false elsewhere. */ + bool is_cgo_export; const char *structural_profile; // AST structural profile (arena-allocated) or NULL const char *body_tokens; // space-separated raw identifier tokens from body (arena) or NULL /* Rust only: raw trait path from the exact `impl Trait for Type` block diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 15f96ba17..558d5a09d 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -2055,6 +2055,35 @@ static bool rust_def_is_test(const char *const *decorators) { return false; } +/* #1929: does a cgo `//export ` directive sit in the comment run + * directly above this Go func def, naming exactly this function? cgo requires + * the exported name to equal the func name, so anything else is not a + * contract for THIS def. */ +static bool go_def_has_cgo_export(CBMExtractCtx *ctx, TSNode func_node, const char *name) { + if (!name || !name[0]) { + return false; + } + static const char kPrefix[] = "//export "; + size_t name_len = strlen(name); + TSNode prev = ts_node_prev_named_sibling(func_node); + while (!ts_node_is_null(prev) && strcmp(ts_node_type(prev), "comment") == 0) { + char *text = cbm_node_text(ctx->arena, prev, ctx->source); + if (text && strncmp(text, kPrefix, sizeof(kPrefix) - 1) == 0) { + const char *exported = text + sizeof(kPrefix) - 1; + size_t elen = strlen(exported); + while (elen > 0 && (exported[elen - 1] == '\n' || exported[elen - 1] == '\r' || + exported[elen - 1] == ' ' || exported[elen - 1] == '\t')) { + elen--; + } + if (elen == name_len && strncmp(exported, name, name_len) == 0) { + return true; + } + } + prev = ts_node_prev_named_sibling(prev); + } + return false; +} + static const char *rust_cfg_qualified_name(CBMArena *a, const char *base_qn, const char *const *decorators) { if (!decorators) { @@ -3778,6 +3807,14 @@ static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec def.is_test = rust_def_is_test(def.decorators); } + // Go: a cgo `//export Name` directive above the func declares its C ABI + // symbol (#1929). cgo requires Name to equal the func name; record only an + // exact match so the resolver upgrade cannot be spoofed by an unrelated + // directive in the same comment run. + if (ctx->language == CBM_LANG_GO) { + def.is_cgo_export = go_def_has_cgo_export(ctx, node, name); + } + // C++/CUDA: GoogleTest macros are test functions (#1266). if (is_gtest) { def.is_test = true; diff --git a/src/pipeline/pass_calls.c b/src/pipeline/pass_calls.c index b25e9f592..b655c9fcf 100644 --- a/src/pipeline/pass_calls.c +++ b/src/pipeline/pass_calls.c @@ -486,6 +486,11 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, /* LSP-resolved calls take precedence over registry-textual matching. * Unique-tail fallbacks are JVM-only (see cbm_pipeline_lsp_allow_tail_match). */ + /* #1929: `C.` is the cgo pseudo-namespace — no resolution path may + * bind it to a project symbol. */ + if (cbm_go_suppress_cgo_callee(lang == CBM_LANG_GO, call->callee_name)) { + return 0; + } bool allow_tail = cbm_pipeline_lsp_allow_tail_match(lang); const CBMResolvedCall *lsp = cbm_pipeline_find_lsp_resolution_in_graph( lsp_calls, call, allow_tail, ctx->gbuf, ctx->project_name); @@ -654,6 +659,16 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, if (!target_node || source_node->id == target_node->id) { return 0; } + /* #1929: a Go //export function is a declared C ABI contract. A C-family + * caller naming it exactly binds by that contract, not by short-name luck + * — upgrade the strategy so the edge is honest and no weak-match guard + * can drop it. Mirrors pass_parallel.c. */ + if ((lang == CBM_LANG_C || lang == CBM_LANG_CPP) && call->callee_name && target_node->name && + strcmp(call->callee_name, target_node->name) == 0 && target_node->properties_json && + strstr(target_node->properties_json, "\"cgo_export\":true")) { + res.strategy = "export_linkage"; + res.confidence = CBM_EXPORT_LINKAGE_CONF; + } /* #725: suffix_match is language-agnostic and will attach a Python * Store.commit() call to a JS function named commit (or a Bash main * to a Python main). Drop that weak cross-language edge. */ diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index 7ac98e9cd..44de4b290 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -283,6 +283,12 @@ static void build_def_props(char *buf, size_t bufsize, const CBMDefinition *def) return; } size_t pos = (size_t)n; + if (def->is_cgo_export && pos + 1 < bufsize) { + int m = snprintf(buf + pos, bufsize - pos, ",\"cgo_export\":true"); + if (m > 0 && (size_t)m < bufsize - pos) { + pos += (size_t)m; + } + } append_json_string(buf, bufsize, &pos, "docstring", def->docstring); append_json_string(buf, bufsize, &pos, "signature", def->signature); append_json_string(buf, bufsize, &pos, "return_type", def->return_type); diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 1eeb55f83..f2290cc24 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -507,6 +507,12 @@ static void build_def_props(char *buf, size_t bufsize, const CBMDefinition *def) return; } size_t pos = (size_t)n; + if (def->is_cgo_export && pos + 1 < bufsize) { + int m = snprintf(buf + pos, bufsize - pos, ",\"cgo_export\":true"); + if (m > 0 && (size_t)m < bufsize - pos) { + pos += (size_t)m; + } + } append_json_string(buf, bufsize, &pos, "docstring", def->docstring); append_json_string(buf, bufsize, &pos, "signature", def->signature); append_json_string(buf, bufsize, &pos, "return_type", def->return_type); @@ -2447,6 +2453,12 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB continue; } + /* #1929: `C.` is the cgo pseudo-namespace — no resolution path + * may bind it to a project symbol. Mirrors pass_calls.c. */ + if (cbm_go_suppress_cgo_callee(lang == CBM_LANG_GO, call->callee_name)) { + continue; + } + _rc_t0 = extract_now_ns(); try_field_type_hint(rc, &res, call->callee_name, source_node->id); atomic_fetch_add_explicit(&rc->time_ns_rc_hint, extract_now_ns() - _rc_t0, @@ -2548,6 +2560,16 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB } atomic_fetch_add_explicit(&rc->time_ns_rc_target, extract_now_ns() - _rc_t0, memory_order_relaxed); + /* #1929: a Go //export function is a declared C ABI contract — a + * C-family caller naming it exactly binds by that contract. Mirrors + * pass_calls.c. */ + if (target_node && (lang == CBM_LANG_C || lang == CBM_LANG_CPP) && call->callee_name && + target_node->name && strcmp(call->callee_name, target_node->name) == 0 && + target_node->properties_json && + strstr(target_node->properties_json, "\"cgo_export\":true")) { + res.strategy = "export_linkage"; + res.confidence = CBM_EXPORT_LINKAGE_CONF; + } if (target_node && source_node->id != target_node->id && cbm_suppress_cross_language_suffix_match(lang, target_node->file_path, res.strategy)) { /* #725: same guard as pass_calls.c — do not emit a suffix_match diff --git a/src/pipeline/pipeline.h b/src/pipeline/pipeline.h index 4b1d15563..a49ded73d 100644 --- a/src/pipeline/pipeline.h +++ b/src/pipeline/pipeline.h @@ -280,6 +280,15 @@ bool cbm_perl_suppress_generic_match(bool is_perl, bool is_method, const char *c * Pure; unit-tested in test_registry.c. */ bool cbm_suppress_weak_member_match(bool enabled, bool is_method, const char *strategy); +/* #1929: confidence for a C-family call bound to a Go //export function by + * its declared ABI symbol — a contract, not a name guess. */ +#define CBM_EXPORT_LINKAGE_CONF 0.95 + +/* #1929: `C.` in a Go file names the cgo pseudo-namespace — never a + * project symbol ("C" is reserved by go/build). Veto the callee before any + * resolution path can bind it. Pure; unit-tested in test_registry.c. */ +bool cbm_go_suppress_cgo_callee(bool is_go, const char *callee_name); + /* #725: drop a suffix_match CALLS edge when the caller language and the * target file's language disagree. unique_name (candidates == 1) is #1572 * and is left alone; same_module / import_map / lsp_* are kept. JS/TS/TSX diff --git a/src/pipeline/registry.c b/src/pipeline/registry.c index 5126bcbfe..db9dcc292 100644 --- a/src/pipeline/registry.c +++ b/src/pipeline/registry.c @@ -464,6 +464,16 @@ bool cbm_suppress_weak_member_match(bool enabled, bool is_method, const char *st strcmp(strategy, "field_type_hint") == 0 || strcmp(strategy, "fuzzy") == 0; } +bool cbm_go_suppress_cgo_callee(bool is_go, const char *callee_name) { + /* #1929/#1926: `C.` in a Go file names the cgo pseudo-namespace. + * `"C"` is reserved by go/build — no project symbol can ever be behind it, + * so ANY binding the general resolver produces for such a callee is + * fabricated. Veto before resolution instead of after: the pseudo-package + * is a standing hijack surface of exactly the #1906 shape. */ + return is_go && callee_name && callee_name[0] == 'C' && callee_name[1] == '.' && + callee_name[2] != '\0'; +} + static bool js_ts_family(CBMLanguage lang) { return lang == CBM_LANG_JAVASCRIPT || lang == CBM_LANG_TYPESCRIPT || lang == CBM_LANG_TSX || lang == CBM_LANG_ARKTS; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 5b7e6a8f7..292cfc6ff 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -3015,6 +3015,67 @@ TEST(go_imports) { PASS(); } +TEST(extract_go_cgo_export_flag) { + /* #1929: `//export Name` above a Go func declares its C ABI symbol; cgo + * requires Name == the func name, so only an exact match sets the flag. */ + CBMFileResult *r = extract("package fx\n\n" + "/*\nstatic int helper(int a) { return a + 1; }\n*/\n" + "import \"C\"\n\n" + "//export GoCallback\n" + "func GoCallback(v int) int { return v }\n\n" + "//export SomethingElse\n" + "func Mismatched(v int) int { return v }\n\n" + "func Plain(v int) int { return v }\n", + CBM_LANG_GO, "t", "cgo.go"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + bool saw_cb = false; + bool saw_mismatch = false; + bool saw_plain = false; + for (int i = 0; i < r->defs.count; i++) { + const CBMDefinition *d = &r->defs.items[i]; + if (!d->name) { + continue; + } + if (strcmp(d->name, "GoCallback") == 0) { + ASSERT_TRUE(d->is_cgo_export); + saw_cb = true; + } + if (strcmp(d->name, "Mismatched") == 0) { + ASSERT_FALSE(d->is_cgo_export); + saw_mismatch = true; + } + if (strcmp(d->name, "Plain") == 0) { + ASSERT_FALSE(d->is_cgo_export); + saw_plain = true; + } + } + ASSERT_TRUE(saw_cb); + ASSERT_TRUE(saw_mismatch); + ASSERT_TRUE(saw_plain); + /* #1929: the preamble range is flagged as unparsed — the C definitions in + * it are invisible to the Go grammar and coverage must say so. */ + ASSERT_TRUE(r->parse_incomplete); + ASSERT_NOT_NULL(r->error_ranges); + ASSERT_NOT_NULL(strstr(r->error_ranges, "3-5")); + cbm_free_result(r); + + /* A Go file without cgo keeps a clean coverage signal. */ + r = extract("package fx\n\nfunc Clean(v int) int { return v }\n", CBM_LANG_GO, "t", "clean.go"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_FALSE(r->parse_incomplete); + cbm_free_result(r); + + /* `import "C"` with no preamble comment hides nothing — not flagged. */ + r = extract("package fx\n\nimport \"C\"\n\nfunc Bare(v int) int { return v }\n", CBM_LANG_GO, + "t", "bare.go"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->parse_incomplete); + cbm_free_result(r); + PASS(); +} + TEST(java_imports) { CBMFileResult *r = extract( "import java.util.List;\nimport java.util.ArrayList;\nimport static java.lang.Math.PI;\n" @@ -6699,6 +6760,7 @@ SUITE(extraction) { RUN_TEST(python_imports); RUN_TEST(js_imports); RUN_TEST(go_imports); + RUN_TEST(extract_go_cgo_export_flag); RUN_TEST(java_imports); RUN_TEST(rust_imports); RUN_TEST(c_imports); diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index a45541e57..a337654e4 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -4688,6 +4688,142 @@ TEST(pipeline_python_receiver_suppresses_weak_method_edge) { PASS(); } +/* #1929: does a CALLS edge src→tgt (by node names) carry this strategy? */ +static bool call_edge_with_strategy_exists(cbm_store_t *s, const char *project, + const char *src_name, const char *tgt_name, + const char *strategy) { + char needle[128]; + snprintf(needle, sizeof(needle), "\"strategy\":\"%s\"", strategy); + cbm_node_t *srcs = NULL; + cbm_node_t *tgts = NULL; + int sc = 0; + int tc = 0; + cbm_store_find_nodes_by_name(s, project, src_name, &srcs, &sc); + cbm_store_find_nodes_by_name(s, project, tgt_name, &tgts, &tc); + bool found = false; + for (int i = 0; i < sc && !found; i++) { + cbm_edge_t *edges = NULL; + int ec = 0; + cbm_store_find_edges_by_source_type(s, srcs[i].id, "CALLS", &edges, &ec); + for (int j = 0; j < ec && !found; j++) { + if (!edges[j].properties_json || !strstr(edges[j].properties_json, needle)) { + continue; + } + for (int k = 0; k < tc; k++) { + if (edges[j].target_id == tgts[k].id) { + found = true; + break; + } + } + } + if (edges) { + cbm_store_free_edges(edges, ec); + } + } + if (srcs) { + cbm_store_free_nodes(srcs, sc); + } + if (tgts) { + cbm_store_free_nodes(tgts, tc); + } + return found; +} + +TEST(pipeline_go_cgo_export_binds_by_contract) { + /* #1929: a C caller of a Go //export function used to bind by short-name + * luck (unique_name @ 0.38–0.75, or die in a weak-match guard). The + * directive is a declared ABI contract — the edge must carry + * export_linkage at contract confidence. */ + char tmp[256]; + snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_exp_XXXXXX"); + if (!cbm_mkdtemp(tmp)) { + FAIL("tmpdir"); + } + write_temp_file(tmp, "go.mod", "module example.com/fxexp\n\ngo 1.22\n"); + write_temp_file(tmp, "bridge/bridge.go", + "package bridge\n" + "\n" + "/*\n" + "static int shim(int v) { return v; }\n" + "*/\n" + "import \"C\"\n" + "\n" + "//export NotifyEvent\n" + "func NotifyEvent(v int) int {\n" + "\treturn v + 1\n" + "}\n"); + write_temp_file(tmp, "probe/probe.c", + "extern int NotifyEvent(int v);\n" + "\n" + "static int pump(void) {\n" + " return NotifyEvent(7);\n" + "}\n"); + + char db_path[512]; + snprintf(db_path, sizeof(db_path), "%s/go_exp.db", tmp); + cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL); + ASSERT_NOT_NULL(p); + ASSERT_EQ(cbm_pipeline_run(p), 0); + const char *project = cbm_pipeline_project_name(p); + + cbm_store_t *s = cbm_store_open_path(db_path); + ASSERT_NOT_NULL(s); + + /* Reproduce-first: RED before the fix — the edge exists but as a + * unique_name guess; with the contract it must be export_linkage. */ + ASSERT_TRUE( + call_edge_with_strategy_exists(s, project, "pump", "NotifyEvent", "export_linkage")); + + cbm_store_close(s); + cbm_pipeline_free(p); + th_rmtree(tmp); + PASS(); +} + +TEST(pipeline_go_cgo_callee_never_binds_project_symbol) { + /* #1929: `C.helper()` names the cgo pseudo-namespace. A same-named project + * symbol must never be bound — "C" is reserved by go/build. */ + char tmp[256]; + snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_cveto_XXXXXX"); + if (!cbm_mkdtemp(tmp)) { + FAIL("tmpdir"); + } + write_temp_file(tmp, "go.mod", "module example.com/fxveto\n\ngo 1.22\n"); + write_temp_file(tmp, "fx/cgo_use.go", + "package fx\n" + "\n" + "/*\n" + "static int helper(int a) { return a + 1; }\n" + "*/\n" + "import \"C\"\n" + "\n" + "func Run(a int) int {\n" + "\treturn int(C.helper(C.int(a)))\n" + "}\n"); + write_temp_file(tmp, "decoy/decoy.go", + "package decoy\n" + "\n" + "func helper(a int) int { return a - 1 }\n"); + + char db_path[512]; + snprintf(db_path, sizeof(db_path), "%s/go_cveto.db", tmp); + cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL); + ASSERT_NOT_NULL(p); + ASSERT_EQ(cbm_pipeline_run(p), 0); + const char *project = cbm_pipeline_project_name(p); + + cbm_store_t *s = cbm_store_open_path(db_path); + ASSERT_NOT_NULL(s); + + /* Reproduce-first: RED before the veto — Run binds the decoy helper. */ + ASSERT_FALSE(cross_file_call_exists(s, project, "Run", "helper")); + + cbm_store_close(s); + cbm_pipeline_free(p); + th_rmtree(tmp); + PASS(); +} + /* Count nodes with the given exact name in the project (e.g. a Route path). */ static int count_nodes_named(cbm_store_t *s, const char *project, const char *name) { cbm_node_t *ns = NULL; @@ -12805,6 +12941,8 @@ SUITE(pipeline) { #endif RUN_TEST(pipeline_tsjs_receiver_suppresses_weak_method_edge); RUN_TEST(pipeline_python_receiver_suppresses_weak_method_edge); + RUN_TEST(pipeline_go_cgo_export_binds_by_contract); + RUN_TEST(pipeline_go_cgo_callee_never_binds_project_symbol); RUN_TEST(pipeline_tsjs_receiver_parallel_keeps_service_edges); RUN_TEST(pipeline_python_receiver_parallel_suppresses_weak_method_edges); RUN_TEST(pipeline_parallel_python_cross_only_dunder_gets_synthetic_carrier); diff --git a/tests/test_registry.c b/tests/test_registry.c index ff81a50ee..8018a58a7 100644 --- a/tests/test_registry.c +++ b/tests/test_registry.c @@ -811,6 +811,22 @@ TEST(cross_language_suffix_match_drops_py_vs_js) { PASS(); } +TEST(go_cgo_callee_veto) { + /* #1929: `C.` in a Go file is the cgo pseudo-namespace — veto. */ + ASSERT_TRUE(cbm_go_suppress_cgo_callee(true, "C.helper")); + ASSERT_TRUE(cbm_go_suppress_cgo_callee(true, "C.int")); + /* Not the pseudo-namespace: ordinary selectors and identifiers. */ + ASSERT_FALSE(cbm_go_suppress_cgo_callee(true, "c.helper")); + ASSERT_FALSE(cbm_go_suppress_cgo_callee(true, "Cfg.load")); + ASSERT_FALSE(cbm_go_suppress_cgo_callee(true, "C")); + ASSERT_FALSE(cbm_go_suppress_cgo_callee(true, "C.")); + ASSERT_FALSE(cbm_go_suppress_cgo_callee(true, "helper")); + /* Other languages never hit the veto (a C++ class named C is legal). */ + ASSERT_FALSE(cbm_go_suppress_cgo_callee(false, "C.helper")); + ASSERT_FALSE(cbm_go_suppress_cgo_callee(true, NULL)); + PASS(); +} + TEST(dynamic_suppress_drops_weak_method_matches) { /* #592/#606/#1276: a member call whose receiver the LSP could not type, that * landed via a WEAK short-name strategy, is generic-resolver noise → drop. @@ -945,6 +961,7 @@ SUITE(registry) { RUN_TEST(perl_suppress_drops_weak_builtin_and_method_matches); RUN_TEST(perl_suppress_keeps_high_confidence_and_genuine_calls); RUN_TEST(cross_language_suffix_match_drops_py_vs_js); + RUN_TEST(go_cgo_callee_veto); RUN_TEST(dynamic_suppress_drops_weak_method_matches); RUN_TEST(dynamic_suppress_keeps_high_confidence_and_non_methods); }