mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-09 09:11:00 +08:00
fix(rust-lsp): a Rust call never binds another language's definition
Both Rust registries were built from the pipeline's mixed-language def set. The shared Tier-2 build (cbm_rust_build_cross_registry) and the per-file conversion (pxc_lspdefs_to_rust) copied every def, so a Perl `sub new` or any C function became a Rust free function. The lsp_short_name_unique fallback then bound `Store::new()` to the Perl constructor at confidence 0.90 on both venues: its "crate scope" check compares only the first module-QN segment, which is the project name. (Found by the generic-engine lane on a 12-language fixture.) Register Rust defs only, at both conversion points, exactly as the Go builder filters to Go. Rust impl-relation records already carry CBM_LANG_RUST. As a side effect, the shared Rust build no longer registers the C defs of a mixed C+Rust tree. Tests: pipeline_rust_call_never_binds_another_language (sequential and parallel): no CALLS from the Rust caller into the Perl file, and the Rust associated function still binds. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
+25
-17
@@ -6323,7 +6323,10 @@ static void rust_resolve_against_registry(CBMArena *arena, const char *source, i
|
||||
* read-only, and shared across every Rust file's resolve (mirrors C/py/cs/ts).
|
||||
* Converts the pipeline's CBMLSPDef into CBMRustLSPDef inline (same field copy as
|
||||
* pass_lsp_cross.c's pxc_lspdefs_to_rust, including proven trait provenance).
|
||||
* module_qn=NULL is byte-identical because all_defs always carry def_module_qn. */
|
||||
* module_qn=NULL is byte-identical because all_defs always carry def_module_qn.
|
||||
* all_defs is mixed-language: only Rust defs are registered, exactly like the
|
||||
* per-file conversion, so a Perl `new` or a C function can never become the
|
||||
* target of a Rust call (the Go builder filters the same way). */
|
||||
CBMTypeRegistry *cbm_rust_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count) {
|
||||
if (!arena)
|
||||
return NULL;
|
||||
@@ -6331,29 +6334,34 @@ CBMTypeRegistry *cbm_rust_build_cross_registry(CBMArena *arena, CBMLSPDef *defs,
|
||||
if (!reg)
|
||||
return NULL;
|
||||
CBMRustLSPDef *rdefs = NULL;
|
||||
int n = 0;
|
||||
if (def_count > 0) {
|
||||
rdefs = (CBMRustLSPDef *)cbm_arena_alloc(arena, (size_t)def_count * sizeof(CBMRustLSPDef));
|
||||
if (!rdefs)
|
||||
return NULL;
|
||||
for (int i = 0; i < def_count; i++) {
|
||||
rdefs[i].qualified_name = defs[i].qualified_name;
|
||||
rdefs[i].short_name = defs[i].short_name;
|
||||
rdefs[i].label = defs[i].label;
|
||||
rdefs[i].receiver_type = defs[i].receiver_type;
|
||||
rdefs[i].def_module_qn = defs[i].def_module_qn;
|
||||
rdefs[i].return_types = defs[i].return_types;
|
||||
rdefs[i].embedded_types = defs[i].embedded_types;
|
||||
rdefs[i].field_defs = defs[i].field_defs;
|
||||
rdefs[i].method_names_str = defs[i].method_names_str;
|
||||
rdefs[i].signature_param_types = defs[i].signature_param_types;
|
||||
rdefs[i].signature_param_count = defs[i].signature_param_count;
|
||||
rdefs[i].trait_qn = defs[i].trait_qn;
|
||||
rdefs[i].is_interface = defs[i].is_interface;
|
||||
rdefs[i].is_rust_impl_relation = defs[i].is_rust_impl_relation;
|
||||
rdefs[i].is_abstract = defs[i].is_abstract;
|
||||
if (defs[i].lang != CBM_LANG_RUST) {
|
||||
continue;
|
||||
}
|
||||
rdefs[n].qualified_name = defs[i].qualified_name;
|
||||
rdefs[n].short_name = defs[i].short_name;
|
||||
rdefs[n].label = defs[i].label;
|
||||
rdefs[n].receiver_type = defs[i].receiver_type;
|
||||
rdefs[n].def_module_qn = defs[i].def_module_qn;
|
||||
rdefs[n].return_types = defs[i].return_types;
|
||||
rdefs[n].embedded_types = defs[i].embedded_types;
|
||||
rdefs[n].field_defs = defs[i].field_defs;
|
||||
rdefs[n].method_names_str = defs[i].method_names_str;
|
||||
rdefs[n].signature_param_types = defs[i].signature_param_types;
|
||||
rdefs[n].signature_param_count = defs[i].signature_param_count;
|
||||
rdefs[n].trait_qn = defs[i].trait_qn;
|
||||
rdefs[n].is_interface = defs[i].is_interface;
|
||||
rdefs[n].is_rust_impl_relation = defs[i].is_rust_impl_relation;
|
||||
rdefs[n].is_abstract = defs[i].is_abstract;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
rust_populate_cross_registry(reg, arena, rdefs, def_count, /*module_qn=*/NULL);
|
||||
rust_populate_cross_registry(reg, arena, n > 0 ? rdefs : NULL, n, /*module_qn=*/NULL);
|
||||
reg->read_only = true; /* seal: shared Tier-2 registry is read-only during resolve */
|
||||
return reg;
|
||||
}
|
||||
|
||||
@@ -1470,31 +1470,42 @@ const struct CBMCargoManifest *cbm_pxc_get_rust_manifest(void) {
|
||||
/* Convert a CBMLSPDef array (the pipeline's lingua franca, go_lsp.h:73)
|
||||
* into a CBMRustLSPDef array (rust_lsp.h) inside `arena`. The two structs
|
||||
* have similar fields but different layouts; CBMLSPDef adds
|
||||
* language/namespace metadata, so a memcpy is unsafe — copy field-by-field. */
|
||||
static CBMRustLSPDef *pxc_lspdefs_to_rust(CBMArena *arena, const CBMLSPDef *defs, int def_count) {
|
||||
* language/namespace metadata, so a memcpy is unsafe — copy field-by-field.
|
||||
* Only Rust defs are kept: the def set is mixed-language, and a Perl or C
|
||||
* function must never become a Rust call target (the Go registrar filters
|
||||
* the same way). *out_count gets the number kept. */
|
||||
static CBMRustLSPDef *pxc_lspdefs_to_rust(CBMArena *arena, const CBMLSPDef *defs, int def_count,
|
||||
int *out_count) {
|
||||
*out_count = 0;
|
||||
if (!defs || def_count <= 0)
|
||||
return NULL;
|
||||
CBMRustLSPDef *out =
|
||||
(CBMRustLSPDef *)cbm_arena_alloc(arena, (size_t)def_count * sizeof(CBMRustLSPDef));
|
||||
if (!out)
|
||||
return NULL;
|
||||
int n = 0;
|
||||
for (int i = 0; i < def_count; i++) {
|
||||
out[i].qualified_name = defs[i].qualified_name;
|
||||
out[i].short_name = defs[i].short_name;
|
||||
out[i].label = defs[i].label;
|
||||
out[i].receiver_type = defs[i].receiver_type;
|
||||
out[i].def_module_qn = defs[i].def_module_qn;
|
||||
out[i].return_types = defs[i].return_types;
|
||||
out[i].embedded_types = defs[i].embedded_types;
|
||||
out[i].field_defs = defs[i].field_defs;
|
||||
out[i].method_names_str = defs[i].method_names_str;
|
||||
out[i].signature_param_types = defs[i].signature_param_types;
|
||||
out[i].signature_param_count = defs[i].signature_param_count;
|
||||
out[i].trait_qn = defs[i].trait_qn;
|
||||
out[i].is_interface = defs[i].is_interface;
|
||||
out[i].is_rust_impl_relation = defs[i].is_rust_impl_relation;
|
||||
out[i].is_abstract = defs[i].is_abstract;
|
||||
if (defs[i].lang != CBM_LANG_RUST) {
|
||||
continue;
|
||||
}
|
||||
out[n].qualified_name = defs[i].qualified_name;
|
||||
out[n].short_name = defs[i].short_name;
|
||||
out[n].label = defs[i].label;
|
||||
out[n].receiver_type = defs[i].receiver_type;
|
||||
out[n].def_module_qn = defs[i].def_module_qn;
|
||||
out[n].return_types = defs[i].return_types;
|
||||
out[n].embedded_types = defs[i].embedded_types;
|
||||
out[n].field_defs = defs[i].field_defs;
|
||||
out[n].method_names_str = defs[i].method_names_str;
|
||||
out[n].signature_param_types = defs[i].signature_param_types;
|
||||
out[n].signature_param_count = defs[i].signature_param_count;
|
||||
out[n].trait_qn = defs[i].trait_qn;
|
||||
out[n].is_interface = defs[i].is_interface;
|
||||
out[n].is_rust_impl_relation = defs[i].is_rust_impl_relation;
|
||||
out[n].is_abstract = defs[i].is_abstract;
|
||||
n++;
|
||||
}
|
||||
*out_count = n;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1581,9 +1592,10 @@ void cbm_pxc_run_one_with_php_bindings(CBMLanguage lang, CBMFileResult *r, const
|
||||
* but diverge after, so convert into the scratch arena. The
|
||||
* workspace manifest (set once by the sequential driver) lets
|
||||
* `crate_a::foo` route across the crate boundary (#56). */
|
||||
CBMRustLSPDef *rdefs = pxc_lspdefs_to_rust(&scratch, defs, def_count);
|
||||
int rust_def_count = 0;
|
||||
CBMRustLSPDef *rdefs = pxc_lspdefs_to_rust(&scratch, defs, def_count, &rust_def_count);
|
||||
cbm_run_rust_lsp_cross_with_manifest(&scratch, source, source_len, module_qn, rdefs,
|
||||
def_count, imp_names, imp_qns, imp_count, tree,
|
||||
rust_def_count, imp_names, imp_qns, imp_count, tree,
|
||||
g_pxc_rust_manifest, &out, &synthetic_calls);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8757,6 +8757,105 @@ TEST(pipeline_go_rw_usage_never_cross_into_c) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* The Rust registries were built from the mixed-language def set: a Perl
|
||||
* `sub new` became a Rust free function, and the project-wide
|
||||
* lsp_short_name_unique fallback bound `Store::new()` to it at 0.90 (found by
|
||||
* the generic-engine lane on its 12-language fixture). A Rust call never
|
||||
* targets another language's definition. pad_files > 0 forces the parallel
|
||||
* resolver, whose Rust registry is the shared all-defs build. */
|
||||
static int rust_registry_rust_only_case(int pad_files) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_rust_only_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_temp_file(tmp, "Cargo.toml", "[package]\nname = \"app\"\nversion = \"0.1.0\"\n");
|
||||
write_temp_file(tmp, "src/store.rs",
|
||||
"pub struct Store {\n"
|
||||
" n: u32,\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"impl Store {\n"
|
||||
" pub fn new() -> Store {\n"
|
||||
" Store { n: 0 }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" pub fn get(&self, k: u32) -> u32 {\n"
|
||||
" self.n + k\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "src/main.rs",
|
||||
"mod store;\n"
|
||||
"\n"
|
||||
"use crate::store::Store;\n"
|
||||
"\n"
|
||||
"fn run() -> u32 {\n"
|
||||
" let s = Store::new();\n"
|
||||
" s.get(1)\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"fn main() {\n"
|
||||
" run();\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "perlsrc/Lib.pm",
|
||||
"package Lib;\n"
|
||||
"\n"
|
||||
"sub new {\n"
|
||||
" my ($class) = @_;\n"
|
||||
" return bless {}, $class;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"1;\n");
|
||||
for (int i = 0; i < pad_files; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
snprintf(name, sizeof(name), "src/filler%d.rs", i);
|
||||
snprintf(body, sizeof(body), "pub fn filler%d() -> u32 {\n %d\n}\n", i, i);
|
||||
write_temp_file(tmp, name, body);
|
||||
}
|
||||
|
||||
char *saved = NULL;
|
||||
if (pad_files > 0) {
|
||||
char *old_workers = getenv("CBM_WORKERS");
|
||||
saved = old_workers ? strdup(old_workers) : NULL;
|
||||
cbm_setenv("CBM_WORKERS", "4", 1);
|
||||
}
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/rust_only.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);
|
||||
|
||||
/* Never onto the Perl constructor ... */
|
||||
ASSERT_EQ(named_edge_to_file_count(s, project, "CALLS", "run", "new", "perlsrc/Lib.pm"), 0);
|
||||
/* ... and the Rust call still binds the Rust associated function. */
|
||||
ASSERT_GTE(named_edge_to_file_count(s, project, "CALLS", "run", "new", "src/store.rs"), 1);
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
if (pad_files > 0) {
|
||||
if (saved) {
|
||||
cbm_setenv("CBM_WORKERS", saved, 1);
|
||||
free(saved);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_WORKERS");
|
||||
}
|
||||
}
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_rust_call_never_binds_another_language) {
|
||||
return rust_registry_rust_only_case(0);
|
||||
}
|
||||
|
||||
TEST(pipeline_rust_call_never_binds_another_language_parallel) {
|
||||
return rust_registry_rust_only_case(52);
|
||||
}
|
||||
|
||||
TEST(pipeline_go_rw_usage_never_cross_into_c_parallel) {
|
||||
/* Parallel twin of the test above: USAGE and WRITES/READS each have a
|
||||
* second, independent resolver in pass_parallel.c (resolve_file_usages /
|
||||
@@ -23615,6 +23714,8 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_html_embedded_member_call_stays_unbound);
|
||||
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
|
||||
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c_parallel);
|
||||
RUN_TEST(pipeline_rust_call_never_binds_another_language);
|
||||
RUN_TEST(pipeline_rust_call_never_binds_another_language_parallel);
|
||||
RUN_TEST(pipeline_rust_std_receiver_never_binds_project_method);
|
||||
RUN_TEST(pipeline_rust_std_receiver_never_binds_project_method_parallel);
|
||||
RUN_TEST(pipeline_go_bare_ref_never_binds_field);
|
||||
|
||||
Reference in New Issue
Block a user