mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Break-glass merge PR #2625
This commit is contained in:
6 files changed
+305
-27
No files matched your search
@@ -967,8 +967,8 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
|
||||
lang, lsp->strategy, lsp->callee_qn, ctx->project_name);
|
||||
cbm_resolution_t res = {0};
|
||||
if (!rust_external && !php_vendor_call) {
|
||||
res = cbm_registry_resolve_lang(ctx->registry, call->callee_name, module_qn, imp_keys,
|
||||
imp_vals, imp_count, lang);
|
||||
res = cbm_registry_resolve_call_lang(ctx->registry, call->callee_name, module_qn, imp_keys,
|
||||
imp_vals, imp_count, lang);
|
||||
/* Cross-language veto: a name-only guess never binds another
|
||||
* language's symbol (a JS app.get must not become Python's
|
||||
* builtins.dict.get). The vetoed answer becomes EMPTY here, before
|
||||
|
||||
@@ -3252,8 +3252,8 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
||||
lang, lsp->strategy, lsp->callee_qn, rc->project_name);
|
||||
if ((!res.qualified_name || !res.qualified_name[0]) && !call->requires_lsp_resolution &&
|
||||
!rust_external && !php_vendor_call) {
|
||||
res = cbm_registry_resolve_lang(rc->registry, call->callee_name, module_qn, imp_keys,
|
||||
imp_vals, imp_count, lang);
|
||||
res = cbm_registry_resolve_call_lang(rc->registry, call->callee_name, module_qn,
|
||||
imp_keys, imp_vals, imp_count, lang);
|
||||
}
|
||||
/* `p->open(fd)`: the registry does not split a callee on the arrow.
|
||||
* The object's type decides, as for the dot call handled further
|
||||
|
||||
@@ -253,6 +253,13 @@ cbm_resolution_t cbm_registry_resolve_lang(const cbm_registry_t *r, const char *
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count,
|
||||
CBMLanguage lang);
|
||||
/* The CALLS variant: as cbm_registry_resolve_lang, except that a bare C call
|
||||
* (no `.` / `->`) does not consider Field or Method nodes, which it can never
|
||||
* call. */
|
||||
cbm_resolution_t cbm_registry_resolve_call_lang(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count,
|
||||
CBMLanguage lang);
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
void cbm_pxc_test_import_probe_fail_after(int successful_allocations);
|
||||
#endif
|
||||
|
||||
+99
-23
@@ -438,7 +438,8 @@ void cbm_registry_import_map_cache_end(void) {
|
||||
* eliminated. */
|
||||
typedef struct {
|
||||
cbm_resolution_t res;
|
||||
size_t scope_len; /* raw and Python-scoped requests may share one file cache */
|
||||
size_t scope_len; /* raw and Python-scoped requests may share one file cache */
|
||||
bool skip_members; /* a C call and a C reference to the same name share it too */
|
||||
} resolve_cache_entry_t;
|
||||
|
||||
static CBM_TLS CBMHashTable *_resolve_cache = NULL;
|
||||
@@ -1667,19 +1668,41 @@ static bool receiver_chain_admits(const char *callee_name, const char *candidate
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Strategy 3+4: Name lookup + suffix match */
|
||||
static cbm_resolution_t resolve_name_lookup(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_vals,
|
||||
int import_count) {
|
||||
const char *lookup = simple_name(callee_name);
|
||||
qn_array_t *arr = cbm_ht_get(r->by_name, lookup);
|
||||
if (!arr || arr->count == 0) {
|
||||
return empty_result();
|
||||
}
|
||||
if (arr->count > REG_MAX_CANDIDATES) {
|
||||
return empty_result(); /* unresolvably ambiguous — see REG_MAX_CANDIDATES */
|
||||
/* The same-name candidates without the members -- Field and Method nodes --
|
||||
* in `view` (backed by the caller's buffers, at most REG_MAX_CANDIDATES
|
||||
* entries). The side arrays are carried over only when the bucket has them
|
||||
* for every entry. */
|
||||
static void candidates_without_members(const cbm_registry_t *r, const qn_array_t *arr,
|
||||
qn_array_t *view, char **items, uint8_t *is_test,
|
||||
uint8_t *lang) {
|
||||
const uint8_t *flags = qn_test_flags(arr);
|
||||
const uint8_t *langs = qn_lang_flags(arr);
|
||||
bool side = flags && langs;
|
||||
int n = 0;
|
||||
for (int i = 0; i < arr->count; i++) {
|
||||
const char *label = cbm_registry_label_of(r, arr->items[i]);
|
||||
if (label && (strcmp(label, "Field") == 0 || strcmp(label, "Method") == 0)) {
|
||||
continue;
|
||||
}
|
||||
items[n] = arr->items[i];
|
||||
if (side) {
|
||||
is_test[n] = flags[i];
|
||||
lang[n] = langs[i];
|
||||
}
|
||||
n++;
|
||||
}
|
||||
*view = (qn_array_t){.items = items,
|
||||
.count = n,
|
||||
.cap = n,
|
||||
.is_test = side ? is_test : NULL,
|
||||
.is_test_cap = side ? n : 0,
|
||||
.lang = side ? lang : NULL};
|
||||
}
|
||||
|
||||
/* Strategies 3.5, 3 and 4 over one same-name candidate set. */
|
||||
static cbm_resolution_t resolve_name_candidates(const cbm_registry_t *r, const qn_array_t *arr,
|
||||
const char *callee_name, const char *module_qn,
|
||||
const char **import_vals, int import_count) {
|
||||
/* Strategy 3.5: a qualified callee disambiguates among multiple same-name
|
||||
* candidates by full qualified tail, before bare-name scoring collapses
|
||||
* them onto a single winner. */
|
||||
@@ -1719,12 +1742,44 @@ static cbm_resolution_t resolve_name_lookup(const cbm_registry_t *r, const char
|
||||
return empty_result();
|
||||
}
|
||||
|
||||
/* Strategy 3+4: Name lookup + suffix match. skip_members: the callee cannot
|
||||
* name a member (a bare C call: C has no methods, and a struct member is only
|
||||
* ever reached through its object), so Field and Method nodes do not compete.
|
||||
* With a Field in the bucket, a unique header function became a suffix_match
|
||||
* that the #725 guard then refused (.h is C++): kernel `put_page(page)`
|
||||
* 345 -> 9 callers (2026-10-13 graph audit). Dropping only the Fields made
|
||||
* libc `read()` the unique name of a C++ adapter's Method instead. */
|
||||
static cbm_resolution_t resolve_name_lookup(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_vals,
|
||||
int import_count, bool skip_members) {
|
||||
const char *lookup = simple_name(callee_name);
|
||||
const qn_array_t *arr = cbm_ht_get(r->by_name, lookup);
|
||||
if (!arr || arr->count == 0) {
|
||||
return empty_result();
|
||||
}
|
||||
if (arr->count > REG_MAX_CANDIDATES) {
|
||||
return empty_result(); /* unresolvably ambiguous — see REG_MAX_CANDIDATES */
|
||||
}
|
||||
if (skip_members) {
|
||||
qn_array_t view;
|
||||
char *view_items[REG_MAX_CANDIDATES];
|
||||
uint8_t view_test[REG_MAX_CANDIDATES];
|
||||
uint8_t view_lang[REG_MAX_CANDIDATES];
|
||||
candidates_without_members(r, arr, &view, view_items, view_test, view_lang);
|
||||
if (view.count == 0) {
|
||||
return empty_result();
|
||||
}
|
||||
return resolve_name_candidates(r, &view, callee_name, module_qn, import_vals, import_count);
|
||||
}
|
||||
return resolve_name_candidates(r, arr, callee_name, module_qn, import_vals, import_count);
|
||||
}
|
||||
|
||||
/* The strategy chain shared by both public resolve variants (no caching here —
|
||||
* cbm_registry_resolve owns the per-file cache). */
|
||||
static cbm_resolution_t registry_resolve_chain(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count,
|
||||
size_t scope_len) {
|
||||
size_t scope_len, bool skip_members) {
|
||||
/* Split callee at the first path separator: "pkg.Func" → prefix="pkg",
|
||||
* suffix="Func". Rust/C++ use "::" rather than ".", so honor whichever
|
||||
* separator appears first ("lib::square" → prefix="lib", suffix="square").
|
||||
@@ -1761,7 +1816,8 @@ static cbm_resolution_t registry_resolve_chain(const cbm_registry_t *r, const ch
|
||||
}
|
||||
if (!(res.qualified_name && res.qualified_name[0])) {
|
||||
/* Strategy 3+4: name lookup */
|
||||
res = resolve_name_lookup(r, callee_name, module_qn, import_map_vals, import_map_count);
|
||||
res = resolve_name_lookup(r, callee_name, module_qn, import_map_vals, import_map_count,
|
||||
skip_members);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -1812,7 +1868,7 @@ bool cbm_registry_name_guess_vetoed(const cbm_registry_t *r, CBMLanguage caller_
|
||||
static cbm_resolution_t registry_resolve_scoped(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count,
|
||||
size_t scope_len) {
|
||||
size_t scope_len, bool skip_members) {
|
||||
if (!r || !callee_name) {
|
||||
return empty_result();
|
||||
}
|
||||
@@ -1822,11 +1878,12 @@ static cbm_resolution_t registry_resolve_scoped(const cbm_registry_t *r, const c
|
||||
* cache key only needs callee_name. */
|
||||
resolve_cache_entry_t *cached =
|
||||
_resolve_cache ? (resolve_cache_entry_t *)cbm_ht_get(_resolve_cache, callee_name) : NULL;
|
||||
if (cached && cached->scope_len == scope_len)
|
||||
if (cached && cached->scope_len == scope_len && cached->skip_members == skip_members)
|
||||
return cached->res;
|
||||
|
||||
cbm_resolution_t res = registry_resolve_chain(r, callee_name, module_qn, import_map_keys,
|
||||
import_map_vals, import_map_count, scope_len);
|
||||
cbm_resolution_t res =
|
||||
registry_resolve_chain(r, callee_name, module_qn, import_map_keys, import_map_vals,
|
||||
import_map_count, scope_len, skip_members);
|
||||
|
||||
/* Data relations (Table/View) are lineage-only registry members: common
|
||||
* table names (users, orders, config) collide with code identifiers across
|
||||
@@ -1846,11 +1903,13 @@ static cbm_resolution_t registry_resolve_scoped(const cbm_registry_t *r, const c
|
||||
/* Same key, different effective scope: reuse its owned entry/key. */
|
||||
cached->res = res;
|
||||
cached->scope_len = scope_len;
|
||||
cached->skip_members = skip_members;
|
||||
} else if (_resolve_cache) {
|
||||
resolve_cache_entry_t *e = (resolve_cache_entry_t *)malloc(sizeof(*e));
|
||||
if (e) {
|
||||
e->res = res;
|
||||
e->scope_len = scope_len;
|
||||
e->skip_members = skip_members;
|
||||
char *kdup = strdup(callee_name);
|
||||
if (kdup) {
|
||||
cbm_ht_set(_resolve_cache, kdup, e);
|
||||
@@ -1866,17 +1925,34 @@ cbm_resolution_t cbm_registry_resolve(const cbm_registry_t *r, const char *calle
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count) {
|
||||
return registry_resolve_scoped(r, callee_name, module_qn, import_map_keys, import_map_vals,
|
||||
import_map_count, module_qn ? strlen(module_qn) : 0);
|
||||
import_map_count, module_qn ? strlen(module_qn) : 0, false);
|
||||
}
|
||||
|
||||
static size_t lang_scope_len(const char *module_qn, CBMLanguage lang) {
|
||||
return lang == CBM_LANG_PYTHON ? cbm_fqn_symbol_scope_len(module_qn)
|
||||
: (module_qn ? strlen(module_qn) : 0);
|
||||
}
|
||||
|
||||
cbm_resolution_t cbm_registry_resolve_lang(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count,
|
||||
CBMLanguage lang) {
|
||||
size_t scope_len = lang == CBM_LANG_PYTHON ? cbm_fqn_symbol_scope_len(module_qn)
|
||||
: (module_qn ? strlen(module_qn) : 0);
|
||||
return registry_resolve_scoped(r, callee_name, module_qn, import_map_keys, import_map_vals,
|
||||
import_map_count, scope_len);
|
||||
import_map_count, lang_scope_len(module_qn, lang), false);
|
||||
}
|
||||
|
||||
cbm_resolution_t cbm_registry_resolve_call_lang(const cbm_registry_t *r, const char *callee_name,
|
||||
const char *module_qn, const char **import_map_keys,
|
||||
const char **import_map_vals, int import_map_count,
|
||||
CBMLanguage lang) {
|
||||
/* A bare C call never names a member: a struct member is reached through
|
||||
* its object (cbm_call_onto_field_policy refuses every Field it lands on)
|
||||
* and C has no methods. So its Field and Method namesakes do not compete
|
||||
* for the name; a C member call and every other language keep them. */
|
||||
bool skip_members = lang == CBM_LANG_C && callee_name && !strchr(callee_name, '.') &&
|
||||
!strstr(callee_name, "->");
|
||||
return registry_resolve_scoped(r, callee_name, module_qn, import_map_keys, import_map_vals,
|
||||
import_map_count, lang_scope_len(module_qn, lang), skip_members);
|
||||
}
|
||||
|
||||
/* ── Class-qualified route handlers (PHP, #1146) ─────────────────── */
|
||||
@@ -2044,7 +2120,7 @@ cbm_resolution_t cbm_registry_resolve_lineage(const cbm_registry_t *r, const cha
|
||||
* it would poison one variant with the other's semantics. SQL files hold
|
||||
* few distinct relation refs, so the chain walk stays cheap. */
|
||||
return registry_resolve_chain(r, callee_name, module_qn, import_map_keys, import_map_vals,
|
||||
import_map_count, module_qn ? strlen(module_qn) : 0);
|
||||
import_map_count, module_qn ? strlen(module_qn) : 0, false);
|
||||
}
|
||||
|
||||
/* ── Fuzzy Resolve ──────────────────────────────────────────────── */
|
||||
|
||||
@@ -8193,6 +8193,156 @@ TEST(pipeline_go_bare_ref_never_binds_field_parallel) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A C caller of a header function that shares its name with a struct member
|
||||
* elsewhere (kernel: include/linux/mm.h put_page beside the arm64
|
||||
* kvm_pgtable_mm_ops.put_page member). pad_files pushes the run past
|
||||
* MIN_FILES_FOR_PARALLEL for the parallel twin. */
|
||||
static void write_c_header_namesake_fixture(const char *tmp, int pad_files) {
|
||||
write_temp_file(tmp, "include/linux/mm.h",
|
||||
"struct page;\n"
|
||||
"static inline void put_page(struct page *page)\n"
|
||||
"{\n"
|
||||
"\t(void)page;\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "arch/arm64/include/asm/kvm_pgtable.h",
|
||||
"struct kvm_pgtable_mm_ops {\n"
|
||||
"\tvoid (*put_page)(void *addr);\n"
|
||||
"};\n");
|
||||
write_temp_file(tmp, "arch/arm64/kernel/elfcore.c",
|
||||
"#include <linux/mm.h>\n"
|
||||
"#include <asm/kvm_pgtable.h>\n"
|
||||
"int mte_dump_tag_range(struct page *page)\n"
|
||||
"{\n"
|
||||
"\tput_page(page);\n"
|
||||
"\treturn 0;\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "drivers/net/rx.c",
|
||||
"#include <linux/mm.h>\n"
|
||||
"void recv_buffer_free(struct page *page)\n"
|
||||
"{\n"
|
||||
"\tput_page(page);\n"
|
||||
"}\n");
|
||||
for (int i = 0; i < pad_files; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
snprintf(name, sizeof(name), "pad/filler%d.c", i);
|
||||
snprintf(body, sizeof(body), "int filler%d(void) { return %d; }\n", i, i);
|
||||
write_temp_file(tmp, name, body);
|
||||
}
|
||||
}
|
||||
|
||||
/* CALLS edges from the nodes named src_name into nodes named tgt_name that
|
||||
* carry tgt_label: the name-only probes cannot tell the header Function from
|
||||
* its struct-member namesake. */
|
||||
static int labelled_call_count(cbm_store_t *s, const char *project, const char *src_name,
|
||||
const char *tgt_name, const char *tgt_label) {
|
||||
cbm_node_t *srcs = NULL;
|
||||
cbm_node_t *tgts = NULL;
|
||||
int sc = 0;
|
||||
int tc = 0;
|
||||
int n = 0;
|
||||
cbm_store_find_nodes_by_name(s, project, src_name, &srcs, &sc);
|
||||
cbm_store_find_nodes_by_name(s, project, tgt_name, &tgts, &tc);
|
||||
for (int i = 0; i < sc; i++) {
|
||||
cbm_edge_t *edges = NULL;
|
||||
int ec = 0;
|
||||
cbm_store_find_edges_by_source_type(s, srcs[i].id, "CALLS", &edges, &ec);
|
||||
for (int j = 0; j < ec; j++) {
|
||||
for (int k = 0; k < tc; k++) {
|
||||
if (edges[j].target_id == tgts[k].id && tgts[k].label &&
|
||||
strcmp(tgts[k].label, tgt_label) == 0) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (edges) {
|
||||
cbm_store_free_edges(edges, ec);
|
||||
}
|
||||
}
|
||||
if (srcs) {
|
||||
cbm_store_free_nodes(srcs, sc);
|
||||
}
|
||||
if (tgts) {
|
||||
cbm_store_free_nodes(tgts, tc);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
TEST(pipeline_c_call_reaches_header_function_beside_field) {
|
||||
/* 2026-10-13 kernel audit: `put_page(page)` 345 -> 9 callers. The struct
|
||||
* member in the short-name registry makes the bare name two candidates,
|
||||
* so the lookup answers by suffix_match; .h is C++ in the extension table
|
||||
* and the #725 guard dropped the C caller's edge as cross-language.
|
||||
* Reproduce-first: RED before the fix. Sequential-path twin. */
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_c_hdr_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_c_header_namesake_fixture(tmp, 0);
|
||||
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/c_hdr.db", tmp);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
|
||||
/* Both namesakes must exist for the probe to mean anything. */
|
||||
ASSERT_GTE(fixture_node_count(s, project, "include/linux/mm.h", "put_page", "Function"), 1);
|
||||
ASSERT_GTE(fixture_node_count(s, project, "arch/arm64/include/asm/kvm_pgtable.h", "put_page",
|
||||
"Field"),
|
||||
1);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "mte_dump_tag_range", "put_page", "Function"), 1);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "recv_buffer_free", "put_page", "Function"), 1);
|
||||
/* A bare C call never names the struct member. */
|
||||
ASSERT_EQ(labelled_call_count(s, project, "mte_dump_tag_range", "put_page", "Field"), 0);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "recv_buffer_free", "put_page", "Field"), 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_c_call_reaches_header_function_beside_field_parallel) {
|
||||
/* Parallel twin: the kernel resolves through pass_parallel.c, which asks
|
||||
* the same #725 guard. */
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_c_hdrp_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_c_header_namesake_fixture(tmp, 52);
|
||||
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/c_hdrp.db", tmp);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
|
||||
ASSERT_GTE(fixture_node_count(s, project, "include/linux/mm.h", "put_page", "Function"), 1);
|
||||
ASSERT_GTE(fixture_node_count(s, project, "arch/arm64/include/asm/kvm_pgtable.h", "put_page",
|
||||
"Field"),
|
||||
1);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "mte_dump_tag_range", "put_page", "Function"), 1);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "recv_buffer_free", "put_page", "Function"), 1);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "mte_dump_tag_range", "put_page", "Field"), 0);
|
||||
ASSERT_EQ(labelled_call_count(s, project, "recv_buffer_free", "put_page", "Field"), 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* True when a node named src_name has an edge of edge_type to the Field
|
||||
* `field` OWNED BY `owner` (qualified name ends in ".owner.field"). The
|
||||
* name-only cross_file_edge_exists cannot tell two same-named fields apart,
|
||||
@@ -22443,6 +22593,8 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_rust_std_receiver_never_binds_project_method_parallel);
|
||||
RUN_TEST(pipeline_go_bare_ref_never_binds_field);
|
||||
RUN_TEST(pipeline_go_bare_ref_never_binds_field_parallel);
|
||||
RUN_TEST(pipeline_c_call_reaches_header_function_beside_field);
|
||||
RUN_TEST(pipeline_c_call_reaches_header_function_beside_field_parallel);
|
||||
RUN_TEST(pipeline_c_member_access_binds_field_by_object_type);
|
||||
RUN_TEST(pipeline_c_member_access_binds_field_by_object_type_parallel);
|
||||
RUN_TEST(pipeline_tsjs_receiver_parallel_keeps_service_edges);
|
||||
|
||||
@@ -1259,6 +1259,48 @@ TEST(go_bare_ref_never_binds_field) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(c_bare_call_does_not_weigh_field_namesakes) {
|
||||
/* 2026-10-13 kernel audit: a struct member named like a header function
|
||||
* made the bare name two candidates, the answer a suffix_match, and the
|
||||
* #725 guard (.h is C++) refused it -- `put_page(page)` 345 -> 9 callers.
|
||||
* A bare C call never names a member, so its Field namesakes do not
|
||||
* compete: the function is the unique name it was in v0.11.0. */
|
||||
cbm_registry_t *r = cbm_registry_new();
|
||||
cbm_registry_add_lang(r, "put_page", "proj.include.linux.mm.put_page", "Function",
|
||||
CBM_LANG_CPP);
|
||||
cbm_registry_add_lang(r, "put_page",
|
||||
"proj.arch.arm64.include.asm.kvm_pgtable.kvm_pgtable_mm_ops.put_page",
|
||||
"Field", CBM_LANG_CPP);
|
||||
const char *mod = "proj.arch.arm64.kernel.elfcore";
|
||||
cbm_resolution_t res =
|
||||
cbm_registry_resolve_call_lang(r, "put_page", mod, NULL, NULL, 0, CBM_LANG_C);
|
||||
ASSERT_STR_EQ(res.qualified_name, "proj.include.linux.mm.put_page");
|
||||
ASSERT_STR_EQ(res.strategy, "unique_name");
|
||||
/* The reference resolver and C++ (which reaches its own members bare) still
|
||||
* weigh the member: two candidates. */
|
||||
res = cbm_registry_resolve_lang(r, "put_page", mod, NULL, NULL, 0, CBM_LANG_C);
|
||||
ASSERT_STR_EQ(res.strategy, "suffix_match");
|
||||
res = cbm_registry_resolve_call_lang(r, "put_page", mod, NULL, NULL, 0, CBM_LANG_CPP);
|
||||
ASSERT_STR_EQ(res.strategy, "suffix_match");
|
||||
/* A name only members carry is no bare C call target at all -- neither a
|
||||
* struct member nor a C++ method: redis's libc `read()` became the unique
|
||||
* name of a Qt adapter's `read` Method once only Fields were set aside. */
|
||||
cbm_registry_add_lang(r, "on_close", "proj.net.sock.sock_ops.on_close", "Field", CBM_LANG_C);
|
||||
res = cbm_registry_resolve_call_lang(r, "on_close", mod, NULL, NULL, 0, CBM_LANG_C);
|
||||
ASSERT_TRUE(res.qualified_name == NULL || res.qualified_name[0] == '\0');
|
||||
cbm_registry_add_lang(r, "read", "proj.deps.hiredis.adapters.qt.RedisQtAdapter.read", "Method",
|
||||
CBM_LANG_CPP);
|
||||
cbm_registry_add_lang(r, "read", "proj.src.connection.ConnectionType.read", "Field",
|
||||
CBM_LANG_C);
|
||||
res = cbm_registry_resolve_call_lang(r, "read", mod, NULL, NULL, 0, CBM_LANG_C);
|
||||
ASSERT_TRUE(res.qualified_name == NULL || res.qualified_name[0] == '\0');
|
||||
/* C++ still reaches the method by name. */
|
||||
res = cbm_registry_resolve_call_lang(r, "read", mod, NULL, NULL, 0, CBM_LANG_CPP);
|
||||
ASSERT_TRUE(res.qualified_name && res.qualified_name[0] != '\0');
|
||||
cbm_registry_free(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(call_onto_field_follows_language_and_shape) {
|
||||
/* A call the registry resolved onto a Field node. */
|
||||
/* Another language: only the spelling connects them. */
|
||||
@@ -2012,6 +2054,7 @@ SUITE(registry) {
|
||||
RUN_TEST(cross_language_ref_drops_go_vs_c);
|
||||
RUN_TEST(go_bare_ref_never_binds_field);
|
||||
RUN_TEST(call_onto_field_follows_language_and_shape);
|
||||
RUN_TEST(c_bare_call_does_not_weigh_field_namesakes);
|
||||
RUN_TEST(c_arrow_member_call_is_c_family_and_arrow_only);
|
||||
RUN_TEST(unique_field_qn_needs_exactly_one_field_of_that_name);
|
||||
RUN_TEST(c_member_access_binds_by_owner_only);
|
||||
|
||||
Reference in new issue
Block a user