mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-02 07:53:41 +08:00
fix(pipeline): reject weak calls to data symbols
A unique_name registry fallback could bind a C call such as format() to an unrelated Variable extracted from a JSON schema. Registry insertion order then changed the CALLS target between incremental and clean benchmark graphs. Add cbm_pipeline_should_suppress_weak_noncallable_call_target in src/pipeline/pipeline_internal.h and apply it in resolve_single_call and resolve_file_calls. Keep Function, Method, type-like constructors, exact LSP targets, and strong same_module/import_map matches so callable C variables remain supported. Add focused coverage in tests/test_pipeline.c and tests/test_lang_contract.c. Verified CBM_ONLY_SUITE=pipeline (386 passed) and CBM_ONLY_SUITE=lang_contract (39 passed). Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -605,6 +605,9 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call,
|
||||
if (!target_node || source_node->id == target_node->id) {
|
||||
return 0;
|
||||
}
|
||||
if (cbm_pipeline_should_suppress_weak_noncallable_call_target(target_node, res.strategy)) {
|
||||
return 0;
|
||||
}
|
||||
if (emit_classified_edge(ctx, call, source_node, target_node, &res, module_qn, imp_keys,
|
||||
imp_vals, imp_count, tsjs_drop_plain_call) &&
|
||||
!cbm_service_pattern_is_global_fetch(call->callee_name)) {
|
||||
|
||||
@@ -2100,6 +2100,10 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (target_node != lsp_target &&
|
||||
cbm_pipeline_should_suppress_weak_noncallable_call_target(target_node, res.strategy)) {
|
||||
continue;
|
||||
}
|
||||
_rc_t0 = extract_now_ns();
|
||||
emit_service_edge(ws->local_edge_buf, source_node, target_node, call, &res, module_qn,
|
||||
rc->registry, rc->main_gbuf, imp_keys, imp_vals, imp_count,
|
||||
|
||||
@@ -160,6 +160,19 @@ static inline bool cbm_pipeline_node_is_callable_scope(const cbm_gbuf_node_t *no
|
||||
(strcmp(node->label, "Function") == 0 || strcmp(node->label, "Method") == 0);
|
||||
}
|
||||
|
||||
/* Weak registry fallbacks may resolve a call to an unrelated Variable or Field
|
||||
* with the same short name, including symbols extracted from another language.
|
||||
* Preserve type-like constructor targets and strong same-module/import matches,
|
||||
* which can legitimately identify callable variables such as C callbacks. */
|
||||
static inline bool cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
const cbm_gbuf_node_t *target, const char *strategy) {
|
||||
if (!target || !target->label || !cbm_registry_strategy_is_weak_short_name(strategy)) {
|
||||
return false;
|
||||
}
|
||||
return strcmp(target->label, "Function") != 0 && strcmp(target->label, "Method") != 0 &&
|
||||
!cbm_label_is_type_like(target->label);
|
||||
}
|
||||
|
||||
/* A textual `super().__init__` does not identify a local constructor without
|
||||
* receiver-type information. A registry suffix match can therefore select an
|
||||
* unrelated project Method merely because it is named `__init__`. Keep
|
||||
|
||||
@@ -1490,6 +1490,27 @@ TEST(contract_edge_commonjs_require_call_resolves_issue871) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A weak short-name call match must not bind executable source to a Variable
|
||||
* extracted from an unrelated data file. This exact shape previously made
|
||||
* incremental and clean benchmark graphs depend on registry insertion order. */
|
||||
TEST(contract_call_weak_match_rejects_noncallable_data_symbol) {
|
||||
LangProj lp;
|
||||
static const LangFile f[] = {
|
||||
{"caller.c", "void caller(void) {\n format();\n}\n"},
|
||||
{"benchmarks/schema/example.schema.json",
|
||||
"{\"type\":\"object\",\"properties\":{\"format\":{\"type\":\"string\"}}}\n"}};
|
||||
cbm_store_t *store = lang_index_files(&lp, f, 2);
|
||||
ASSERT_TRUE(store != NULL);
|
||||
int false_target = calls_edge_targets(store, lp.project, "Variable", ".format");
|
||||
if (false_target) {
|
||||
fprintf(stderr,
|
||||
" weak call resolution must not target unrelated JSON Variable `format`\n");
|
||||
}
|
||||
ASSERT_TRUE(!false_target);
|
||||
lang_cleanup(&lp, store);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* DEPENDS_ON — Helm Chart.yaml `dependencies:` -> per-dependency Chart node.
|
||||
* Basename must be exactly "Chart.yaml"; pass_k8s runs in both pipeline paths. */
|
||||
TEST(contract_edge_depends_on) {
|
||||
@@ -1704,6 +1725,7 @@ SUITE(lang_contract) {
|
||||
RUN_TEST(contract_edge_no_infra_routes_from_ci_configs_issue999);
|
||||
RUN_TEST(contract_edge_infra_routes_from_deploy_configs_still_minted);
|
||||
RUN_TEST(contract_edge_commonjs_require_call_resolves_issue871);
|
||||
RUN_TEST(contract_call_weak_match_rejects_noncallable_data_symbol);
|
||||
RUN_TEST(contract_edge_depends_on);
|
||||
RUN_TEST(contract_edge_parallel_service_edges);
|
||||
RUN_TEST(contract_edge_file_changes_with);
|
||||
|
||||
@@ -651,6 +651,28 @@ TEST(pipeline_call_edge_props_include_args_and_line) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_weak_call_target_suppression) {
|
||||
cbm_gbuf_node_t function_target = {.label = "Function"};
|
||||
cbm_gbuf_node_t class_target = {.label = "Class"};
|
||||
cbm_gbuf_node_t variable_target = {.label = "Variable"};
|
||||
cbm_gbuf_node_t field_target = {.label = "Field"};
|
||||
|
||||
ASSERT_FALSE(cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
&function_target, "unique_name"));
|
||||
ASSERT_FALSE(cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
&class_target, "suffix_match"));
|
||||
ASSERT_TRUE(cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
&variable_target, "unique_name"));
|
||||
ASSERT_TRUE(cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
&field_target, "suffix_match"));
|
||||
ASSERT_FALSE(cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
&variable_target, "same_module"));
|
||||
ASSERT_FALSE(cbm_pipeline_should_suppress_weak_noncallable_call_target(
|
||||
&variable_target, "import_map"));
|
||||
ASSERT_FALSE(cbm_pipeline_should_suppress_weak_noncallable_call_target(NULL, "unique_name"));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_sequential_call_edges_preserve_eighth_arg) {
|
||||
if (setup_test_repo() != 0) {
|
||||
FAIL("failed to create temp dir");
|
||||
@@ -18026,6 +18048,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_project_name_derived);
|
||||
RUN_TEST(pipeline_mode_global_semantic_edges_policy);
|
||||
RUN_TEST(pipeline_call_edge_props_include_args_and_line);
|
||||
RUN_TEST(pipeline_weak_call_target_suppression);
|
||||
RUN_TEST(pipeline_sequential_call_edges_preserve_eighth_arg);
|
||||
RUN_TEST(pipeline_fast_mode);
|
||||
/* Definitions pass */
|
||||
|
||||
Reference in New Issue
Block a user