@@ -4832,6 +4832,108 @@ TEST(pipeline_go_rw_usage_never_cross_into_c_parallel) {
48324832 PASS();
48334833}
48344834
4835+ static int count_nodes_named(cbm_store_t *s, const char *project, const char *name);
4836+
4837+ /* Fixture for the #1942 bare-reference-vs-Field probes: a Go struct field
4838+ * named like the commonest local (`err`), and a function whose local of the
4839+ * same name must NOT bind it — in Go a field is only reachable through a
4840+ * selector expression, never a bare identifier. Needs Go Field extraction
4841+ * (#1935) to have anything to falsely bind. */
4842+ static void write_go_bare_field_fixture(const char *tmp, int pad_files) {
4843+ write_temp_file(tmp, "go.mod", "module example.com/fxbare\n\ngo 1.22\n");
4844+ write_temp_file(tmp, "state/state.go",
4845+ "package state\n"
4846+ "\n"
4847+ "type Tracker struct {\n"
4848+ "\terr error\n"
4849+ "\tn int\n"
4850+ "}\n");
4851+ write_temp_file(tmp, "app/app.go",
4852+ "package app\n"
4853+ "\n"
4854+ "import \"errors\"\n"
4855+ "\n"
4856+ "func Run() error {\n"
4857+ "\terr := errors.New(\"x\")\n"
4858+ "\treturn err\n"
4859+ "}\n");
4860+ for (int i = 0; i < pad_files; i++) {
4861+ char name[64];
4862+ char body[128];
4863+ snprintf(name, sizeof(name), "pad/filler%d.go", i);
4864+ snprintf(body, sizeof(body), "package pad\n\nfunc filler%d() int { return %d }\n", i, i);
4865+ write_temp_file(tmp, name, body);
4866+ }
4867+ }
4868+
4869+ TEST(pipeline_go_bare_ref_never_binds_field) {
4870+ /* #1942: the READS/WRITES resolvers and the USAGE registry fallback hand
4871+ * bare reference text to the short-name registry, which contains Field
4872+ * nodes — so every Go local `err := …` bound whichever struct field was
4873+ * named err (21308 USAGE / 5191 WRITES onto Go fields on the measured
4874+ * repo, top target a test struct's field collecting 3013 edges).
4875+ * Sequential-path twin; the parallel twin follows. */
4876+ char tmp[256];
4877+ snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_bare_XXXXXX");
4878+ if (!cbm_mkdtemp(tmp)) {
4879+ FAIL("tmpdir");
4880+ }
4881+ write_go_bare_field_fixture(tmp, 0);
4882+
4883+ char db_path[512];
4884+ snprintf(db_path, sizeof(db_path), "%s/go_bare.db", tmp);
4885+ cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
4886+ ASSERT_NOT_NULL(p);
4887+ ASSERT_EQ(cbm_pipeline_run(p), 0);
4888+ const char *project = cbm_pipeline_project_name(p);
4889+
4890+ cbm_store_t *s = cbm_store_open_path(db_path);
4891+ ASSERT_NOT_NULL(s);
4892+
4893+ /* The field must exist for the probe to mean anything (#1935's fix). */
4894+ ASSERT_TRUE(count_nodes_named(s, project, "err") >= 1);
4895+ /* Reproduce-first: RED before the fix — the bare local binds the field. */
4896+ ASSERT_FALSE(cross_file_edge_exists(s, project, "Run", "err", "WRITES"));
4897+ ASSERT_FALSE(cross_file_edge_exists(s, project, "Run", "err", "READS"));
4898+ ASSERT_FALSE(cross_file_edge_exists(s, project, "Run", "err", "USAGE"));
4899+
4900+ cbm_store_close(s);
4901+ cbm_pipeline_free(p);
4902+ th_rmtree(tmp);
4903+ PASS();
4904+ }
4905+
4906+ TEST(pipeline_go_bare_ref_never_binds_field_parallel) {
4907+ /* Parallel twin: resolve_file_rw / resolve_file_usages are independent
4908+ * resolvers and must consult the same predicate (#1928's lesson). */
4909+ char tmp[256];
4910+ snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_barep_XXXXXX");
4911+ if (!cbm_mkdtemp(tmp)) {
4912+ FAIL("tmpdir");
4913+ }
4914+ write_go_bare_field_fixture(tmp, 52);
4915+
4916+ char db_path[512];
4917+ snprintf(db_path, sizeof(db_path), "%s/go_barep.db", tmp);
4918+ cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
4919+ ASSERT_NOT_NULL(p);
4920+ ASSERT_EQ(cbm_pipeline_run(p), 0);
4921+ const char *project = cbm_pipeline_project_name(p);
4922+
4923+ cbm_store_t *s = cbm_store_open_path(db_path);
4924+ ASSERT_NOT_NULL(s);
4925+
4926+ ASSERT_TRUE(count_nodes_named(s, project, "err") >= 1);
4927+ ASSERT_FALSE(cross_file_edge_exists(s, project, "Run", "err", "WRITES"));
4928+ ASSERT_FALSE(cross_file_edge_exists(s, project, "Run", "err", "READS"));
4929+ ASSERT_FALSE(cross_file_edge_exists(s, project, "Run", "err", "USAGE"));
4930+
4931+ cbm_store_close(s);
4932+ cbm_pipeline_free(p);
4933+ th_rmtree(tmp);
4934+ PASS();
4935+ }
4936+
48354937/* Count nodes with the given exact name in the project (e.g. a Route path). */
48364938static int count_nodes_named(cbm_store_t *s, const char *project, const char *name) {
48374939 cbm_node_t *ns = NULL;
@@ -12951,6 +13053,8 @@ SUITE(pipeline) {
1295113053 RUN_TEST(pipeline_python_receiver_suppresses_weak_method_edge);
1295213054 RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
1295313055 RUN_TEST(pipeline_go_rw_usage_never_cross_into_c_parallel);
13056+ RUN_TEST(pipeline_go_bare_ref_never_binds_field);
13057+ RUN_TEST(pipeline_go_bare_ref_never_binds_field_parallel);
1295413058 RUN_TEST(pipeline_tsjs_receiver_parallel_keeps_service_edges);
1295513059 RUN_TEST(pipeline_python_receiver_parallel_suppresses_weak_method_edges);
1295613060 RUN_TEST(pipeline_parallel_python_cross_only_dunder_gets_synthetic_carrier);
0 commit comments