Skip to content

Commit 8809bd5

Browse files
authored
Merge pull request #1052 from DeusData/repro/964-header-qn-collision
test(repro): RED reproduction for #964 header QN collision
2 parents db330bf + b70812e commit 8809bd5

3 files changed

Lines changed: 124 additions & 0 deletions

File tree

Makefile.cbm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ TEST_REPRO_SRCS = \
450450
tests/repro/repro_issue581.c \
451451
tests/repro/repro_issue787.c \
452452
tests/repro/repro_issue842.c \
453+
tests/repro/repro_issue964.c \
453454
tests/repro/repro_invariant_calls.c \
454455
tests/repro/repro_invariant_graph.c \
455456
tests/repro/repro_invariant_breadth.c \

tests/repro/repro_issue964.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* repro_issue964.c — Reproduce-first case for OPEN bug #964.
3+
*
4+
* Issue: #964 — "C/C++ #include not mapped as IMPORTS edges — expected or a
5+
* bug?" (triaged as a bug: header files look disconnected; the reporter's
6+
* zero-inbound query returns 194 of 214 files on a PlatformIO project).
7+
*
8+
* Root cause (deeper than the report guesses):
9+
* cbm_pipeline_fqn_compute (src/pipeline/fqn.c) strips the file extension,
10+
* so a header and its same-stem source collide on BOTH derived QNs:
11+
* NodeController.h -> module "proj.NodeController",
12+
* file "proj.NodeController.__file__"
13+
* NodeController.cpp -> module "proj.NodeController",
14+
* file "proj.NodeController.__file__"
15+
* Node upserts match by QN, so the header's File node is merged into the
16+
* source's — the header has NO node of its own in the graph at all. The
17+
* `#include "NodeController.h"` import then resolves to the shared module
18+
* (named after the .cpp), and the header can never receive an inbound
19+
* IMPORTS edge. Headers without a same-stem sibling keep a node but the
20+
* include-edge targeting still lands on the module, not the header file.
21+
*
22+
* NOTE: the module-QN unification itself is (at least partly) load-bearing
23+
* for C/C++ — header declarations and source definitions sharing a module
24+
* QN is what lets cross-file call resolution join them. The fix must give
25+
* headers their own FILE identity + point include edges at it WITHOUT
26+
* splitting the shared module QN. Both File-node creation sites (pipeline.c
27+
* full build; pipeline_incremental.c changed-file re-creation) must agree,
28+
* and the incremental site is concurrently being fixed by PR #995 (#994) —
29+
* implement this fix on top of that landing, never in parallel with it.
30+
*
31+
* Expected (correct) behaviour:
32+
* 1. NodeController.h has its own File node (distinct from the .cpp's).
33+
* 2. main.cpp's `#include "NodeController.h"` produces an IMPORTS edge
34+
* whose target is the HEADER's node, so the header is not disconnected.
35+
*
36+
* Why RED on current code: the header's File node does not exist (merged by
37+
* QN collision), so both assertions fail.
38+
*/
39+
40+
#include <foundation/compat.h>
41+
#include "test_framework.h"
42+
#include "repro_harness.h"
43+
44+
#include <string.h>
45+
#include <stdlib.h>
46+
#include <stdio.h>
47+
48+
/* True if a File-labelled node with exactly `name` exists. */
49+
static int r964_file_node_exists(cbm_store_t *store, const char *project, const char *name) {
50+
cbm_node_t *nodes = NULL;
51+
int count = 0;
52+
if (cbm_store_find_nodes_by_label(store, project, "File", &nodes, &count) != CBM_STORE_OK)
53+
return 0;
54+
int found = 0;
55+
for (int i = 0; i < count; i++) {
56+
if (nodes[i].name && strcmp(nodes[i].name, name) == 0)
57+
found = 1;
58+
}
59+
cbm_store_free_nodes(nodes, count);
60+
return found;
61+
}
62+
63+
/* Count inbound IMPORTS edges whose TARGET node name is exactly `name`. */
64+
static int r964_inbound_imports(cbm_store_t *store, const char *project, const char *name) {
65+
cbm_edge_t *edges = NULL;
66+
int n = 0;
67+
if (cbm_store_find_edges_by_type(store, project, "IMPORTS", &edges, &n) != CBM_STORE_OK)
68+
return -1;
69+
int hits = 0;
70+
for (int i = 0; i < n; i++) {
71+
cbm_node_t tgt;
72+
if (cbm_store_find_node_by_id(store, edges[i].target_id, &tgt) != CBM_STORE_OK)
73+
continue;
74+
if (tgt.name && strcmp(tgt.name, name) == 0)
75+
hits++;
76+
cbm_node_free_fields(&tgt);
77+
}
78+
cbm_store_free_edges(edges, n);
79+
return hits;
80+
}
81+
82+
TEST(repro_issue964_header_has_node_and_inbound_import) {
83+
static const RFile files[] = {
84+
{"NodeController.h", "#pragma once\n"
85+
"class NodeController {\n"
86+
"public:\n"
87+
" void run();\n"
88+
"};\n"},
89+
{"NodeController.cpp", "#include \"NodeController.h\"\n"
90+
"void NodeController::run() {}\n"},
91+
{"main.cpp", "#include \"NodeController.h\"\n"
92+
"#include <vector>\n"
93+
"\n"
94+
"int main() {\n"
95+
" NodeController c;\n"
96+
" c.run();\n"
97+
" return 0;\n"
98+
"}\n"}};
99+
100+
RProj lp;
101+
cbm_store_t *store = rh_index_files(&lp, files, 3);
102+
ASSERT_NOT_NULL(store);
103+
104+
int header_node = r964_file_node_exists(store, lp.project, "NodeController.h");
105+
int header_inbound = r964_inbound_imports(store, lp.project, "NodeController.h");
106+
if (!header_node || header_inbound < 1) {
107+
fprintf(stderr,
108+
" [964] FAIL header_file_node=%d inbound_imports=%d (header merged into "
109+
"same-stem .cpp by extension-stripped QN collision)\n",
110+
header_node, header_inbound);
111+
}
112+
ASSERT_TRUE(header_node); /* header must keep its own File node */
113+
ASSERT_TRUE(header_inbound >= 1); /* #include must land an edge on it */
114+
115+
rh_cleanup(&lp, store);
116+
PASS();
117+
}
118+
119+
SUITE(repro_issue964) {
120+
RUN_TEST(repro_issue964_header_has_node_and_inbound_import);
121+
}

tests/repro/repro_main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ extern void suite_repro_issue363(void);
8282
extern void suite_repro_issue581(void);
8383
extern void suite_repro_issue787(void);
8484
extern void suite_repro_issue842(void);
85+
extern void suite_repro_issue964(void);
8586
/* NEW bugs found by the discovery sweep */
8687
extern void suite_repro_new_ts_class_field_arrow(void);
8788
extern void suite_repro_new_py_tuple_unpack(void);
@@ -168,6 +169,7 @@ int main(void) {
168169
RUN_SUITE(repro_issue581);
169170
RUN_SUITE(repro_issue787);
170171
RUN_SUITE(repro_issue842);
172+
RUN_SUITE(repro_issue964);
171173
RUN_SUITE(repro_invariant_calls);
172174
RUN_SUITE(repro_invariant_graph);
173175
RUN_SUITE(repro_invariant_breadth);

0 commit comments

Comments
 (0)