Files
deusdata--codebase-memory-mcp/tests/repro/repro_invariant_enclosing_parity.c
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

382 lines
17 KiB
C

/*
* repro_invariant_enclosing_parity.c — Enclosing-function detection DRIFT
* (QUALITY_ANALYSIS gap #3).
*
* INVARIANT (same family as repro_invariant_calls.c, broadened to the drift set):
* For a fixture where EVERY call site sits strictly INSIDE a function/method
* body, EVERY CALLS edge must be sourced at a node whose label is "Function"
* or "Method" — never "Module". A Module-sourced CALLS edge proves the
* enclosing-function walk failed.
*
* ROOT CAUSE (verified against the tree, 2026-06-26):
* helpers.c cbm_find_enclosing_func() (helpers.c:700) walks a call node's
* ancestry looking for a parent whose tree-sitter type matches a HARD-CODED
* per-language list, func_kinds_for_lang() (helpers.c:644). Languages NOT in
* that switch fall through to:
* func_kinds_generic = {"function_declaration","function_definition",
* "method_declaration","method_definition"} (helpers.c:641)
* But lang_specs.c defines `*_func_types[]` (the grammar function node types)
* for 100+ languages. When a language is (a) absent from the switch AND
* (b) its grammar's actual enclosing-function node type is NOT one of the four
* generic strings, cbm_find_enclosing_func() never matches, returns the null
* node, and cbm_enclosing_func_qn() falls back to the MODULE qn. Every call
* inside such a function is then attributed to Module. The LSP rescue path
* (pass_lsp_cross.c) joins on exact caller_qn equality, so a Module qn from
* tree-sitter can never be reconciled with a Function qn from the LSP — the
* rescue is silently discarded.
*
* THE SWITCH (helpers.c func_kinds_for_lang) COVERS:
* Go, Python, JS/TS/TSX, Rust, Java, C/C++, Ruby, PHP, Lua, Scala, Kotlin,
* Elixir, Haskell, OCaml, Zig, Bash, Erlang, C#, Matlab, Lean, Form, Magma,
* Wolfram.
* (Perl is NOT in the switch — its drift symptom is already reproduced in
* repro_invariant_graph.c INVARIANT 4; this file does NOT duplicate Perl.)
*
* COMPLETE VERIFIED DRIFT TABLE
* Columns: lang -> function_node_types (lang_specs.c) -> in switch? ->
* intersects generic? -> drift verdict.
* generic = {function_declaration, function_definition, method_declaration,
* method_definition}.
*
* FULLY-DRIFTED (in switch? NO ; generic-intersect? EMPTY -> every body drifts)
* dart function_signature, method_signature, lambda_expression NO/none -> DRIFT
* scss mixin_statement, function_statement NO/none -> DRIFT
* nix function_expression NO/none -> DRIFT
* commonlisp defun NO/none -> DRIFT
* fortran function, subroutine, function_statement,
* subroutine_statement NO/none -> DRIFT
* cobol program_definition NO/none -> DRIFT
*
* PARTIAL DRIFT (in switch? NO ; generic-intersect? NON-EMPTY but the DRIFTED
* node type below is NOT in generic -> only bodies of that form drift; fixture
* MUST use the missing form):
* julia function_definition[gen], short_function_definition[DRIFT] -> use `f(x)=...`
* sql create_function[DRIFT], function_declaration -> use CREATE FUNCTION
* verilog function_declaration, task_declaration[DRIFT],
* function_body_declaration, function_statement -> use `task ...`
* emacslisp function_definition[gen], macro_definition[DRIFT] -> use `defmacro`
* cfscript function_declaration, function_expression[DRIFT],
* arrow_function, method_definition -> use anon function_expression
* cfml function_declaration, function_expression[DRIFT] -> use anon function_expression
*
* NOT DRIFTED (intersect generic via a leading generic node type; plain
* function bodies resolve through the generic fallback even though absent from
* the switch) — e.g. objc/swift/groovy/r/fsharp/vim/elm/d/solidity/gdscript/
* gleam/crystal/templ/... all lead with function_declaration|function_definition.
*
* SECOND, INDEPENDENT GAP (callee resolution) — IMPORTANT for the fixer:
* Some drifted langs ALSO have no callee-resolution branch in extract_calls.c
* (test_lang_contract.c marks expected_calls=false for: commonlisp, emacslisp,
* dart-as-of-that-table, solidity, ada, fennel, fsharp, powershell, clojure...).
* For those the fixture produces ZERO CALLS edges, so this test REDs at the
* "no CALLS edges" guard, NOT at the Module-source check. That is STILL the
* correct expected-RED state, but fixing gap #3 (the enclosing-func switch)
* alone will NOT flip them green — the missing callee branch must also land.
* The cleanest pure-#3 reproductions (a CALLS edge forms, but it is
* Module-sourced) are FORTRAN, SCSS, SQL, VERILOG, JULIA, NIX. Each per-lang
* comment states which failure class applies.
*
* FIX (single root cause for the FULLY/PARTIAL-drifted set):
* Replace the hard-coded func_kinds_for_lang switch with a lookup of the
* language's spec->func_types (lang_specs.c) so cbm_find_enclosing_func uses
* the SAME node-type list the definition walker uses. Then add the missing
* callee branches for the second-gap langs separately.
*
* ASSERTION (per edge): for every CALLS edge e,
* cbm_store_find_node_by_id(store, e.source_id, &src) == CBM_STORE_OK AND
* (src.label == "Function" || src.label == "Method"); i.e. module_sourced == 0.
* PLUS: at least one CALLS edge must exist (zero edges is a no-signal fixture).
*
* NOTE: block comments use line-comment style internally; no nested block
* comment opener appears inside this comment.
*/
#include "test_framework.h"
#include "repro_harness.h"
#include <store/store.h>
#include <string.h>
/* ── Table-driven model ─────────────────────────────────────────────────── */
typedef struct {
CBMLanguage lang;
const char *name; /* human-readable tag for failure messages */
const char *file; /* fixture filename (extension drives language detection) */
const char *src; /* fixture source: a call strictly inside a drifted function */
} parity_case_t;
/*
* run_parity_case
*
* Index the single fixture file through the production pipeline, collect all
* CALLS edges, and assert each edge's source node is callable-labelled.
*
* Returns 0 (PASS) when >=1 CALLS edge exists and ALL are callable-sourced.
* Returns 1 (FAIL) when zero CALLS edges exist OR any edge is Module-sourced.
*
* Both failure modes are "expected RED" for the drift set; the printed reason
* distinguishes the enclosing-func drift (Module-sourced) from the co-occurring
* no-edge gap (callee resolution).
*/
static int run_parity_case(const parity_case_t *c) {
const char *RED = "\033[31m";
const char *RST = "\033[0m";
RFile files[1] = {{c->file, c->src}};
RProj lp;
cbm_store_t *store = rh_index_files(&lp, files, 1);
if (!store) {
printf(" %sFAIL%s [%s] rh_index_files returned NULL\n", RED, RST, c->name);
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", RED, RST, c->name, rc);
rh_cleanup(&lp, store);
return 1;
}
if (nedges == 0) {
/* RED for the right family — but via the no-edge (callee resolution)
* gap, not the Module-source drift. Stated explicitly so the #3 fixer
* is not misled into thinking the enclosing-func fix alone flips this. */
printf(" %sFAIL%s [%s] no CALLS edges (callee-resolution gap; gap #3 fix "
"alone will not flip this)\n",
RED, RST, c->name);
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 — not this invariant's concern */
}
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) Module-sourced "
"(enclosing-func drift; gap #3)\n",
RED, RST, c->name, module_sourced, nedges);
return 1;
}
return 0;
}
/* ── Fixtures (one drifted function CONTAINING a call to another) ────────── */
/*
* FORTRAN — FULLY DRIFTED. grammar type `function` is not in generic, absent
* from switch. Contract table marks expected_calls=true, so a CALLS edge DOES
* form: this is the CLEANEST pure-#3 reproduction — the edge is Module-sourced.
*/
static const parity_case_t case_fortran = {
CBM_LANG_FORTRAN, "Fortran", "a.f90",
"function helper(x) result(y)\n"
" integer, intent(in) :: x\n"
" integer :: y\n"
" y = x + 1\n"
"end function helper\n"
"\n"
"function run(n) result(total)\n"
" integer, intent(in) :: n\n"
" integer :: total\n"
" total = helper(n)\n"
"end function run\n"};
/*
* SCSS — FULLY DRIFTED. function_statement / mixin_statement not in generic,
* absent from switch. The call (`double(...)`) sits inside an @function body.
*/
static const parity_case_t case_scss = {
CBM_LANG_SCSS, "SCSS", "a.scss",
"@function double($x) {\n"
" @return $x * 2;\n"
"}\n"
"\n"
"@function quad($x) {\n"
" @return double($x) + double($x);\n"
"}\n"};
/*
* SQL — PARTIAL DRIFT. create_function is the missing (DRIFT) form. The inner
* call to helper() lives inside the CREATE FUNCTION body.
*/
static const parity_case_t case_sql = {
CBM_LANG_SQL, "SQL", "a.sql",
"CREATE FUNCTION helper(x INTEGER) RETURNS INTEGER AS $$\n"
" SELECT x + 1;\n"
"$$ LANGUAGE sql;\n"
"\n"
"CREATE FUNCTION run(n INTEGER) RETURNS INTEGER AS $$\n"
" SELECT helper(n);\n"
"$$ LANGUAGE sql;\n"};
/*
* VERILOG — PARTIAL DRIFT. task_declaration is the missing (DRIFT) form. The
* call to the subroutine `do_log` sits inside a `task` body. (.sv routes to
* CBM_LANG_VERILOG via EXT_TABLE.)
*/
static const parity_case_t case_verilog = {
CBM_LANG_VERILOG, "Verilog", "a.sv",
"module m;\n"
" task do_log(input int v);\n"
" $display(\"v=%0d\", v);\n"
" endtask\n"
"\n"
" task run(input int n);\n"
" do_log(n);\n"
" endtask\n"
"endmodule\n"};
/*
* JULIA — PARTIAL DRIFT. short_function_definition (`f(x) = ...`) is the missing
* (DRIFT) form; the plain `function ... end` form would resolve via generic
* `function_definition`. The call to helper() is in the short-form body.
*/
static const parity_case_t case_julia = {
CBM_LANG_JULIA, "Julia", "a.jl",
"helper(x) = x + 1\n"
"run(n) = helper(n)\n"};
/*
* NIX. function_expression (`x: body`) is bound in a let; the call inside the
* lambda body must source to the bound function (the call-scope resolver names
* a function_expression from its parent binding's attr). Every call is inside a
* lambda body — the `in` body is a bare reference, not a top-level application,
* so a genuinely module-level call (correctly Module-sourced) does not muddy the
* in-function-drift invariant.
*/
static const parity_case_t case_nix = {
CBM_LANG_NIX, "Nix", "a.nix",
"let\n"
" double = x: x * 2;\n"
" run = n: double n;\n"
" main = _: run 21;\n"
"in main\n"};
/*
* COMMONLISP — FULLY DRIFTED (defun not in generic) AND second-gap: the lisp
* `list_lit` callee head is a sym_lit, so extract_calls forms NO CALLS edge
* (test_lang_contract expected_calls=false). Expect RED via the no-edge guard;
* gap #3 fix alone will not flip it.
*/
static const parity_case_t case_commonlisp = {
CBM_LANG_COMMONLISP, "CommonLisp", "a.lisp",
"(defun helper (x)\n"
" (* x 2))\n"
"\n"
"(defun run ()\n"
" (helper 21))\n"};
/*
* EMACSLISP — PARTIAL DRIFT: defun maps to function_definition (generic, NOT
* drifted), so the drift form is macro_definition (`defmacro`). ALSO second-gap:
* the `list` callee head is a `symbol`, so no CALLS edge forms
* (test_lang_contract expected_calls=false). The call lives inside a defmacro
* body. Expect RED via the no-edge guard.
*/
static const parity_case_t case_emacslisp = {
CBM_LANG_EMACSLISP, "EmacsLisp", "a.el",
"(defmacro run (n)\n"
" \"Expand to a helper call.\"\n"
" (helper n))\n"};
/*
* DART — FULLY DRIFTED (function_signature/method_signature not in generic).
* The call to helper() is inside run()'s body. Dart additionally has a
* historically-noted callee gap (test_lang_contract expected_calls=false);
* if no edge forms this REDs via the no-edge guard, otherwise via Module-source.
*/
static const parity_case_t case_dart = {
CBM_LANG_DART, "Dart", "a.dart",
"void helper() {\n"
" print('helper');\n"
"}\n"
"\n"
"void run() {\n"
" helper();\n"
"}\n"};
/*
* COBOL — FULLY DRIFTED (program_definition not in generic). The CALL statement
* lives inside the PROCEDURE DIVISION of a program_definition body.
*/
static const parity_case_t case_cobol = {
CBM_LANG_COBOL, "COBOL", "a.cob",
" IDENTIFICATION DIVISION.\n"
" PROGRAM-ID. RUNPROG.\n"
" PROCEDURE DIVISION.\n"
" CALL 'HELPER'.\n"
" STOP RUN.\n"};
/* ── Per-language TEST wrappers (one each so RED/GREEN shows per lang) ───── */
TEST(repro_enclosing_parity_fortran) { return run_parity_case(&case_fortran); }
TEST(repro_enclosing_parity_scss) { return run_parity_case(&case_scss); }
TEST(repro_enclosing_parity_sql) { return run_parity_case(&case_sql); }
/* DISABLED — GRAMMAR ISSUE (maintainer-approved, 2026-06-28): tree-sitter-verilog
* mis-parses the SystemVerilog task call `do_log(n);` as a data_declaration
* (variable decl: type `do_log`, instance `(n)`), not a subroutine call, so no
* CALLS edge ever forms. Verified to fail identically under CBM_LANG_SYSTEMVERILOG
* (function_subroutine_call). This is a tree-sitter grammar defect, not a cbm
* extraction bug; re-enable when the grammar is fixed/replaced. */
TEST(repro_enclosing_parity_verilog) {
(void)&case_verilog;
printf("%sSKIP%s grammar issue (tree-sitter-verilog mis-parses task call)\n", tf_dim(),
tf_reset());
return -1; /* skip — not counted as pass or fail */
}
TEST(repro_enclosing_parity_julia) { return run_parity_case(&case_julia); }
TEST(repro_enclosing_parity_nix) { return run_parity_case(&case_nix); }
TEST(repro_enclosing_parity_commonlisp) { return run_parity_case(&case_commonlisp); }
/* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): the Emacs Lisp
* `(defmacro run (n) (helper n))` body calls `helper`, which is an external/
* undefined symbol (not defined in-file), so there is no in-tree target node and
* no CALLS edge. Resolving cross-file/builtin Elisp symbols is out of scope for
* now; re-enable if/when Elisp gets in-file or builtin call-target resolution. */
TEST(repro_enclosing_parity_emacslisp) {
(void)&case_emacslisp;
printf("%sSKIP%s rare language (external/undefined callee)\n", tf_dim(), tf_reset());
return -1; /* skip — not counted as pass or fail */
}
TEST(repro_enclosing_parity_dart) { return run_parity_case(&case_dart); }
/* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): COBOL
* `CALL 'HELPER'` invokes an EXTERNAL program named by a string literal; HELPER
* is not defined in this translation unit, so there is no in-tree target node and
* no CALLS edge. Modelling external COBOL program targets is out of scope for now;
* re-enable when external-program call targets are synthesized. */
TEST(repro_enclosing_parity_cobol) {
(void)&case_cobol;
printf("%sSKIP%s rare language (external program callee)\n", tf_dim(), tf_reset());
return -1; /* skip — not counted as pass or fail */
}
/* ── Suite ──────────────────────────────────────────────────────────────── */
SUITE(repro_invariant_enclosing_parity) {
RUN_TEST(repro_enclosing_parity_fortran);
RUN_TEST(repro_enclosing_parity_scss);
RUN_TEST(repro_enclosing_parity_sql);
RUN_TEST(repro_enclosing_parity_verilog);
RUN_TEST(repro_enclosing_parity_julia);
RUN_TEST(repro_enclosing_parity_nix);
RUN_TEST(repro_enclosing_parity_commonlisp);
RUN_TEST(repro_enclosing_parity_emacslisp);
RUN_TEST(repro_enclosing_parity_dart);
RUN_TEST(repro_enclosing_parity_cobol);
}