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

404 lines
16 KiB
C

/*
* repro_invariant_calls.c — Source-position-aware CALLS attribution invariant.
*
* INVARIANT:
* For any project where EVERY call site is located INSIDE a function or
* method body (no top-level/module-level calls), EVERY CALLS edge in the
* graph must be sourced at a node whose label is "Function" or "Method".
* Zero CALLS edges may be sourced at a "Module" node.
*
* BASIS (QUALITY_ANALYSIS.md, 2026-06-24):
* Graph quality audit over the real codebase-memory-mcp repo showed only
* 3.69% of CALLS edges are callable-sourced (207/5607). The dominant
* failure mode is cbm_enclosing_func_qn returning the module QN when
* cbm_find_enclosing_func cannot walk the TSNode ancestry back to a
* function node. Root cause: func_kinds_for_lang (helpers.c:644) uses a
* hardcoded per-language list that is not always in sync with the actual
* grammar node types emitted by each tree-sitter grammar; when no ancestor
* type matches the list, cbm_find_enclosing_func returns a null node and
* cbm_enclosing_func_qn falls back to the module QN. The LSP rescue path
* (pass_lsp_cross.c) cannot compensate because it joins on exact
* caller_qn equality — a Module QN from tree-sitter is never equal to a
* Function QN from LSP, so the LSP result is silently discarded.
*
* EXPECTED per language (based on helpers.c func_kinds_for_lang):
* GREEN (callable source expected to work):
* Go — func_kinds_go = {function_declaration, method_declaration}
* Standard grammar; tree-sitter-go is mature; enclosing-func
* walk works reliably. Python/Go confirmed correct in
* QUALITY_ANALYSIS grep validation.
* Python — func_kinds_python = {function_definition}
* Standard grammar; confirmed correct in QUALITY_ANALYSIS.
*
* RED (callable source expected to fall back to Module on current code):
* C — func_kinds_cpp = {function_definition}
* C uses the same list as C++. QUALITY_ANALYSIS top-file
* list is dominated by C files (extract_defs.c: 182 Module-
* sourced CALLS, c_lsp.c: 86). The enclosing-func walk for
* C requires the call-expression's ancestor chain to include
* a function_definition node; C test failures are explicitly
* cited as expected-red in the quality contracts suite.
* C++ — same func_kinds as C. Out-of-line method definitions
* (Foo::bar) also lose the class qualifier (see issue #554).
* QUALITY_ANALYSIS explicitly lists C/C++ callable-source
* failures as known-red in the node_creation_probe contract.
* TypeScript — func_kinds_js = {function_declaration, method_definition,
* arrow_function, ...}. Method definitions and arrow
* function fields are supported, but class method bodies
* emitted by the TS grammar use "method_definition" — listed
* in func_kinds_js — so TS SHOULD be green for ordinary
* function bodies. HOWEVER, QUALITY_ANALYSIS section 6 lists
* TS in the breadth-suite gap set (ts_lsp.c: 95 Module-
* sourced CALLS in the real graph). This fixture uses a
* plain function calling another, the simplest case; we
* expect GREEN. If TS still fails the test will document it.
* Java — func_kinds_java = {method_declaration, constructor_declaration}
* Java LSP is supported. The real-graph audit shows
* java_lsp.h: 90 Module-sourced CALLS. A plain method
* calling another in the same class should be the simplest
* possible case; we expect GREEN but the audit evidence
* suggests it may be RED.
* C# — func_kinds_csharp = {method_declaration, constructor_declaration}
* Analogous to Java. Similar LSP support. Expected GREEN for
* the minimal case, but marked as potentially RED per breadth
* suite evidence.
* Rust — func_kinds_rust = {function_item}
* Rust LSP is hybrid but cbm_pxc_has_cross_lsp returns false
* for CBM_LANG_RUST (pass_lsp_cross.c:281). The enclosing-
* func walk uses only tree-sitter. Expected RED because
* QUALITY_ANALYSIS section 6 notes Rust in the failing set
* and rust_lsp.h: 102 Module-sourced CALLS appears in the
* top-file list.
*
* ASSERTION (per edge):
* For every cbm_edge_t e where e.type == "CALLS":
* cbm_store_find_node_by_id(store, e.source_id, &src) == CBM_STORE_OK
* AND (strcmp(src.label, "Function") == 0 || strcmp(src.label, "Method") == 0)
* Equivalently: module_sourced_count == 0.
*
* NOTE: inline comments below use line comments only (no block comments
* inside block comments per coding rules).
*/
#include "test_framework.h"
#include "repro_harness.h"
#include <store/store.h>
#include <string.h>
/* ── Shared runner ──────────────────────────────────────────────────────── */
/*
* assert_calls_callable_sourced
*
* Index `files[0..nfiles)` through the production pipeline, collect all CALLS
* edges, and assert that each edge's source node has label "Function" or
* "Method" (never "Module").
*
* Returns 0 (PASS) when the invariant holds.
* Returns 1 (FAIL) when one or more Module-sourced CALLS edges are found.
*
* lang_tag is a human-readable string used in failure messages only.
*/
static int assert_calls_callable_sourced(const char *lang_tag,
const RFile *files, int nfiles) {
RProj lp;
cbm_store_t *store = rh_index_files(&lp, files, nfiles);
if (!store) {
printf(" %sFAIL%s [%s] rh_index_files returned NULL\n",
"\033[31m", "\033[0m", lang_tag);
return 1;
}
cbm_edge_t *edges = NULL;
int nedges = 0;
int rc = cbm_store_find_edges_by_type(store, lp.project, "CALLS",
&edges, &nedges);
if (rc != CBM_STORE_OK) {
printf(" %sFAIL%s [%s] cbm_store_find_edges_by_type rc=%d\n",
"\033[31m", "\033[0m", lang_tag, rc);
rh_cleanup(&lp, store);
return 1;
}
/*
* We must find at least one CALLS edge — a fixture with zero calls would
* trivially satisfy the invariant and give no signal. Treat zero edges as
* a test-setup problem, not a pass.
*/
if (nedges == 0) {
printf(" %sFAIL%s [%s] no CALLS edges found (fixture problem: "
"expected >= 1)\n",
"\033[31m", "\033[0m", lang_tag);
cbm_store_free_edges(edges, nedges);
rh_cleanup(&lp, store);
return 1;
}
int module_sourced = 0;
for (int i = 0; i < nedges; i++) {
cbm_node_t src;
if (cbm_store_find_node_by_id(store, edges[i].source_id, &src)
!= CBM_STORE_OK) {
continue; /* dangling edge — ignore for this invariant */
}
const char *lbl = src.label ? src.label : "(null)";
if (strcmp(lbl, "Function") != 0 && strcmp(lbl, "Method") != 0) {
module_sourced++;
}
}
cbm_store_free_edges(edges, nedges);
rh_cleanup(&lp, store);
if (module_sourced > 0) {
printf(" %sFAIL%s [%s] %d/%d CALLS edge(s) sourced at non-callable "
"node (expected 0 module-sourced)\n",
"\033[31m", "\033[0m", lang_tag, module_sourced, nedges);
return 1;
}
return 0; /* all edges callable-sourced */
}
/* ── C ──────────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_c
*
* Expected: RED on current code.
* Root cause: func_kinds_cpp = {"function_definition"} is used for C too.
* The C files dominate the Module-sourced CALLS list in QUALITY_ANALYSIS
* (extract_defs.c: 182, c_lsp.c: 86). Even the simplest intra-file call
* between two C functions falls back to Module sourcing because the
* cbm_enclosing_func_qn path does not correctly resolve the caller QN and
* the LSP rescue is blocked by the exact-QN equality join requirement.
*/
TEST(repro_invariant_calls_c) {
static const char src[] =
"static int add(int a, int b) { return a + b; }\n"
"\n"
"int compute(int x) {\n"
" return add(x, 1);\n"
"}\n";
static const RFile files[] = {
{ "main.c", src },
};
return assert_calls_callable_sourced("C",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── C++ ────────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_cpp
*
* Expected: RED on current code.
* Shares the same func_kinds as C. Out-of-line method definitions additionally
* drop the class qualifier (issue #554 / helpers.c cbm_enclosing_func_qn).
* Uses both a free function and a member method so the test covers both forms.
*/
TEST(repro_invariant_calls_cpp) {
static const char src[] =
"static int helper(int x) { return x * 2; }\n"
"\n"
"class Processor {\n"
"public:\n"
" int run(int v);\n"
"};\n"
"\n"
"int Processor::run(int v) {\n"
" return helper(v);\n"
"}\n";
static const RFile files[] = {
{ "main.cpp", src },
};
return assert_calls_callable_sourced("C++",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── Go ─────────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_go
*
* Expected: GREEN on current code.
* func_kinds_go = {function_declaration, method_declaration}.
* Go grammar is mature; tree-sitter-go is stable. QUALITY_ANALYSIS confirms
* Python/Go callable attribution as correct via grep validation.
* This case is a regression guard: if it goes RED a future change has broken
* Go callable attribution.
*/
TEST(repro_invariant_calls_go) {
static const char src[] =
"package main\n"
"\n"
"func add(a, b int) int {\n"
" return a + b\n"
"}\n"
"\n"
"func compute(x int) int {\n"
" return add(x, 1)\n"
"}\n";
static const RFile files[] = {
{ "main.go", src },
};
return assert_calls_callable_sourced("Go",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── Python ─────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_python
*
* Expected: GREEN on current code.
* func_kinds_python = {function_definition}.
* QUALITY_ANALYSIS grep-validated Python callable attribution as correct.
* Regression guard.
*/
TEST(repro_invariant_calls_python) {
static const char src[] =
"def add(a, b):\n"
" return a + b\n"
"\n"
"def compute(x):\n"
" return add(x, 1)\n";
static const RFile files[] = {
{ "main.py", src },
};
return assert_calls_callable_sourced("Python",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── TypeScript ─────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_ts
*
* Expected: GREEN for a plain function-calls-function fixture (func_kinds_js
* includes function_declaration and arrow_function). However QUALITY_ANALYSIS
* shows ts_lsp.c with 95 Module-sourced CALLS in the real graph, so this may
* be RED. The test documents whichever state holds currently.
*/
TEST(repro_invariant_calls_ts) {
static const char src[] =
"function add(a: number, b: number): number {\n"
" return a + b;\n"
"}\n"
"\n"
"function compute(x: number): number {\n"
" return add(x, 1);\n"
"}\n";
static const RFile files[] = {
{ "main.ts", src },
};
return assert_calls_callable_sourced("TypeScript",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── Java ───────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_java
*
* Expected: likely RED, possibly GREEN.
* func_kinds_java = {method_declaration, constructor_declaration}.
* java_lsp.h shows 90 Module-sourced CALLS in the real graph. The simplest
* same-class method call is the minimal fixture; if even this fails the
* attribution gap is comprehensive.
*/
TEST(repro_invariant_calls_java) {
static const char src[] =
"public class Calculator {\n"
" private int add(int a, int b) {\n"
" return a + b;\n"
" }\n"
"\n"
" public int compute(int x) {\n"
" return add(x, 1);\n"
" }\n"
"}\n";
static const RFile files[] = {
{ "Calculator.java", src },
};
return assert_calls_callable_sourced("Java",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── C# ─────────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_csharp
*
* Expected: likely RED, possibly GREEN.
* func_kinds_csharp = {method_declaration, constructor_declaration}.
* Analogous evidence to Java from QUALITY_ANALYSIS breadth suite gaps.
*/
TEST(repro_invariant_calls_csharp) {
static const char src[] =
"public class Calculator {\n"
" private int Add(int a, int b) {\n"
" return a + b;\n"
" }\n"
"\n"
" public int Compute(int x) {\n"
" return Add(x, 1);\n"
" }\n"
"}\n";
static const RFile files[] = {
{ "Calculator.cs", src },
};
return assert_calls_callable_sourced("C#",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── Rust ───────────────────────────────────────────────────────────────── */
/*
* repro_invariant_calls_rust
*
* Expected: RED on current code.
* func_kinds_rust = {function_item}.
* cbm_pxc_has_cross_lsp returns false for CBM_LANG_RUST (pass_lsp_cross.c:281)
* so the cross-file LSP rescue path never runs for Rust. rust_lsp.h appears
* with 102 Module-sourced CALLS in the QUALITY_ANALYSIS top-file list.
* Even a single-file intra-function call will fall back to Module sourcing
* because the tree-sitter enclosing-func walk alone is insufficient.
*/
TEST(repro_invariant_calls_rust) {
static const char src[] =
"fn add(a: i32, b: i32) -> i32 {\n"
" a + b\n"
"}\n"
"\n"
"fn compute(x: i32) -> i32 {\n"
" add(x, 1)\n"
"}\n";
static const RFile files[] = {
{ "main.rs", src },
};
return assert_calls_callable_sourced("Rust",
files, (int)(sizeof(files) / sizeof(files[0])));
}
/* ── Suite ──────────────────────────────────────────────────────────────── */
SUITE(repro_invariant_calls) {
RUN_TEST(repro_invariant_calls_c);
RUN_TEST(repro_invariant_calls_cpp);
RUN_TEST(repro_invariant_calls_go);
RUN_TEST(repro_invariant_calls_python);
RUN_TEST(repro_invariant_calls_ts);
RUN_TEST(repro_invariant_calls_java);
RUN_TEST(repro_invariant_calls_csharp);
RUN_TEST(repro_invariant_calls_rust);
}