Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

122 lines
5.1 KiB
C

/*
* 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 <foundation/compat.h>
#include "test_framework.h"
#include "repro_harness.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* 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 <vector>\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);
}