/* * repro_issue964.c — Reproduce-first case for OPEN bug #964. * * Issue: #964 — "C/C++ #include not mapped as IMPORTS edges — expected or a * bug?" (triaged as a bug: header files look disconnected; the reporter's * zero-inbound query returns 194 of 214 files on a PlatformIO project). * * Root cause (deeper than the report guesses): * cbm_pipeline_fqn_compute (src/pipeline/fqn.c) strips the file extension, * so a header and its same-stem source collide on BOTH derived QNs: * NodeController.h -> module "proj.NodeController", * file "proj.NodeController.__file__" * NodeController.cpp -> module "proj.NodeController", * file "proj.NodeController.__file__" * Node upserts match by QN, so the header's File node is merged into the * source's — the header has NO node of its own in the graph at all. The * `#include "NodeController.h"` import then resolves to the shared module * (named after the .cpp), and the header can never receive an inbound * IMPORTS edge. Headers without a same-stem sibling keep a node but the * include-edge targeting still lands on the module, not the header file. * * NOTE: the module-QN unification itself is (at least partly) load-bearing * for C/C++ — header declarations and source definitions sharing a module * QN is what lets cross-file call resolution join them. The fix must give * headers their own FILE identity + point include edges at it WITHOUT * splitting the shared module QN. Both File-node creation sites (pipeline.c * full build; pipeline_incremental.c changed-file re-creation) must agree, * and the incremental site is concurrently being fixed by PR #995 (#994) — * implement this fix on top of that landing, never in parallel with it. * * Expected (correct) behaviour: * 1. NodeController.h has its own File node (distinct from the .cpp's). * 2. main.cpp's `#include "NodeController.h"` produces an IMPORTS edge * whose target is the HEADER's node, so the header is not disconnected. * * Why RED on current code: the header's File node does not exist (merged by * QN collision), so both assertions fail. */ #include #include "test_framework.h" #include "repro_harness.h" #include #include #include /* True if a File-labelled node with exactly `name` exists. */ static int r964_file_node_exists(cbm_store_t *store, const char *project, const char *name) { cbm_node_t *nodes = NULL; int count = 0; if (cbm_store_find_nodes_by_label(store, project, "File", &nodes, &count) != CBM_STORE_OK) return 0; int found = 0; for (int i = 0; i < count; i++) { if (nodes[i].name && strcmp(nodes[i].name, name) == 0) found = 1; } cbm_store_free_nodes(nodes, count); return found; } /* Count inbound IMPORTS edges whose TARGET node name is exactly `name`. */ static int r964_inbound_imports(cbm_store_t *store, const char *project, const char *name) { cbm_edge_t *edges = NULL; int n = 0; if (cbm_store_find_edges_by_type(store, project, "IMPORTS", &edges, &n) != CBM_STORE_OK) return -1; int hits = 0; for (int i = 0; i < n; i++) { cbm_node_t tgt; if (cbm_store_find_node_by_id(store, edges[i].target_id, &tgt) != CBM_STORE_OK) continue; if (tgt.name && strcmp(tgt.name, name) == 0) hits++; cbm_node_free_fields(&tgt); } cbm_store_free_edges(edges, n); return hits; } TEST(repro_issue964_header_has_node_and_inbound_import) { static const RFile files[] = { {"NodeController.h", "#pragma once\n" "class NodeController {\n" "public:\n" " void run();\n" "};\n"}, {"NodeController.cpp", "#include \"NodeController.h\"\n" "void NodeController::run() {}\n"}, {"main.cpp", "#include \"NodeController.h\"\n" "#include \n" "\n" "int main() {\n" " NodeController c;\n" " c.run();\n" " return 0;\n" "}\n"}}; RProj lp; cbm_store_t *store = rh_index_files(&lp, files, 3); ASSERT_NOT_NULL(store); int header_node = r964_file_node_exists(store, lp.project, "NodeController.h"); int header_inbound = r964_inbound_imports(store, lp.project, "NodeController.h"); if (!header_node || header_inbound < 1) { fprintf(stderr, " [964] FAIL header_file_node=%d inbound_imports=%d (header merged into " "same-stem .cpp by extension-stripped QN collision)\n", header_node, header_inbound); } ASSERT_TRUE(header_node); /* header must keep its own File node */ ASSERT_TRUE(header_inbound >= 1); /* #include must land an edge on it */ rh_cleanup(&lp, store); PASS(); } SUITE(repro_issue964) { RUN_TEST(repro_issue964_header_has_node_and_inbound_import); }