Files
codebase-memory-mcp/tests/test_resolver_rust.c
T

640 lines
31 KiB
C

#include "test_framework.h"
#include "test_helpers.h"
#include "pipeline/pipeline.h"
#include "store/store.h"
#include "sqlite3.h"
#include "cbm.h"
#include "lsp/rust_proof.h"
#include "lsp/rust_lsp.h"
#include "resolve/binding.h"
#include "resolve/policy.h"
#include "resolver_quality.h"
static const char RUST_SERVICE[] = "pub struct Service;\nimpl Service {\n pub fn go(&self) {}\n}\n";
static int rust_type_rows(const char *source, const char *library, int duplicate,
CBMResolvedKind kind) {
CBMArena arena;
cbm_arena_init(&arena);
CBMFileResult *lib = cbm_extract_file(library, (int)strlen(library), CBM_LANG_RUST, "p",
"lib.rs", 0, NULL, NULL);
CBMFileResult *file =
cbm_extract_file(source, (int)strlen(source), CBM_LANG_RUST, "p", "main.rs", 0, NULL, NULL);
CBMLSPDef defs[3] = {{.qualified_name = "p.lib.Service",
.graph_qualified_name = "p.lib.Service",
.short_name = "Service",
.label = "Struct",
.source_start_line = 1,
.declaration_identity = "lib:0",
.def_module_qn = "p.lib",
.lang = CBM_LANG_RUST},
{.qualified_name = "p.lib.Service.go",
.graph_qualified_name = "p.lib.Service.go",
.short_name = "go",
.label = "Method",
.receiver_type = "p.lib.Service",
.source_start_line = 3,
.declaration_identity = "lib:1",
.def_module_qn = "p.lib",
.lang = CBM_LANG_RUST}};
bool facts =
cbm_rust_collect_source_facts(&arena, library, (int)strlen(library), "p.lib", defs, 2);
int count = 2;
if (duplicate) {
defs[2] = defs[duplicate == 1 ? 0 : 1];
defs[2].declaration_identity = "other:0";
count = 3;
}
CBMTypeRegistry *registry = cbm_rust_build_cross_registry(&arena, defs, count);
const char *names[] = {"Svc"}, *targets[] = {"p.lib.Service"};
CBMResolvedCallArray out = {0};
if (facts && registry && file)
cbm_rust_proof_resolve(&arena, source, (int)strlen(source), registry, file, names, targets,
1, &out);
int found = 0;
for (int i = 0; i < out.count; i++) {
CBMResolvedCall *row = &out.items[i];
cbm_resolve_result_t proof = {.binding = CBM_RESOLVE_BINDING_UNIQUE,
.domain = CBM_RESOLVE_DOMAIN_PROJECT,
.analysis = row->evidence.analysis,
.rule = row->evidence.rule,
.facts = row->evidence.facts,
.target = row->callee_qn,
.candidate_count = 1};
if (row->kind == kind && cbm_resolve_admit(&proof) &&
row->site_end_byte > row->site_start_byte)
found++;
}
cbm_free_result(lib);
cbm_free_result(file);
cbm_arena_destroy(&arena);
return found;
}
TEST(resolver_rust_shared_reference_and_alias) {
const char *source = "use crate::lib::Service as Svc;\npub fn run(value: &Svc) { value.go(); "
"let alias=value; alias.go(); }\n";
ASSERT_EQ(rust_type_rows(source, RUST_SERVICE, 0, CBM_RESOLVED_INVOCATION), 2);
ASSERT_EQ(rust_type_rows(source, RUST_SERVICE, 0, CBM_RESOLVED_TYPE_REFERENCE), 1);
PASS();
}
TEST(resolver_rust_receiver_shadow_and_unsupported_flow_abstain) {
const char *sources[] = {
("use crate::lib::Service as Svc;\npub fn run(value: &Svc) { let value=other; value.go(); "
"}\n"),
("use crate::lib::Service as Svc;\npub fn run(value: &Svc) { value=other; value.go(); }\n"),
("use crate::lib::Service as Svc;\npub fn run(value: &Svc) { value.go(1); }\n"),
("use crate::lib::Service as Svc;\npub fn run(value: &mut Svc) { value.go(); }\n"),
("use crate::lib::Service as Svc;\npub fn run(value: Svc) { value.go(); }\n"),
("use crate::lib::Service as Svc;\npub fn run<Svc>(value: &Svc) { value.go(); }\n"),
("use crate::lib::Service as Svc;\npub fn run(value: &Svc) { unknown!(); value.go(); }\n"),
("use crate::lib::Service as Svc;\npub fn run(value: &Svc) { if cond { value=other; } "
"value.go(); }\n")};
for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) {
int count = rust_type_rows(sources[i], RUST_SERVICE, 0, CBM_RESOLVED_INVOCATION);
if (count)
fprintf(stderr, "Rust unexpected proof %zu: %d\n", i, count);
ASSERT_EQ(count, 0);
}
PASS();
}
TEST(resolver_rust_visibility_traits_attributes_and_duplicates_abstain) {
const char *source =
"use crate::lib::Service as Svc;\npub fn run(value: &Svc) { value.go(); }\n";
const char *libraries[] = {
("struct Service;\nimpl Service {\n pub fn go(&self) {}\n}\n"),
("pub struct Service;\nimpl Service {\n fn go(&self) {}\n}\n"),
("pub struct Service;\nimpl Other for Service {\n fn go(&self) {}\n}\n"),
("pub struct Service;\nimpl Service {\n pub fn go(&mut self) {}\n}\n"),
("pub struct Service;\nimpl Service {\n pub fn go<T>(&self) {}\n}\n"),
("#[cfg(feature=\"other\")]\npub struct Service;\nimpl Service { pub fn go(&self) {} }\n")};
for (size_t i = 0; i < sizeof(libraries) / sizeof(libraries[0]); i++)
ASSERT_EQ(rust_type_rows(source, libraries[i], 0, CBM_RESOLVED_INVOCATION), 0);
ASSERT_EQ(rust_type_rows(source, RUST_SERVICE, 1, CBM_RESOLVED_INVOCATION), 0);
ASSERT_EQ(rust_type_rows(source, RUST_SERVICE, 2, CBM_RESOLVED_INVOCATION), 0);
PASS();
}
typedef struct {
const char *path, *text;
} rust_proof_file_t;
static int rust_proof_count(const rust_proof_file_t *files, size_t count, const char *sql,
const char *target, const char *name, const char *label) {
const char *root = th_mktempdir("cbm_rust_proof");
if (!root)
return -1;
char repo[768], database[768];
snprintf(repo, sizeof(repo), "%s/repo", root);
snprintf(database, sizeof(database), "%s/index.db", root);
bool ok = cbm_mkdir_p(repo, 0755) && cbm_mkdir_p(TH_PATH(repo, "src"), 0755);
for (size_t i = 0; ok && i < count; i++)
ok = th_write_file(TH_PATH(repo, files[i].path), files[i].text) == 0;
cbm_pipeline_t *pipeline = ok ? cbm_pipeline_new(repo, database, CBM_MODE_FULL) : NULL;
int rc = pipeline && cbm_pipeline_set_project_name(pipeline, "python-proof")
? cbm_pipeline_run(pipeline)
: -1;
cbm_pipeline_free(pipeline);
cbm_store_t *store = rc == 0 ? cbm_store_open_path_existing(database) : NULL;
sqlite3_stmt *statement = NULL;
int edges = -1;
if (store &&
sqlite3_prepare_v2(cbm_store_get_db(store), sql, -1, &statement, NULL) == SQLITE_OK) {
const char *parameters[] = {target, name, label};
for (int i = 0; i < sqlite3_bind_parameter_count(statement); i++)
sqlite3_bind_text(statement, i + 1, parameters[i], -1, SQLITE_STATIC);
if (sqlite3_step(statement) == SQLITE_ROW)
edges = sqlite3_column_int(statement, 0);
}
sqlite3_finalize(statement);
if (store)
cbm_store_close(store);
th_cleanup(root);
return edges;
}
static int rust_proof_edges(const rust_proof_file_t *files, size_t count, const char *target,
const char *name, const char *label) {
return rust_proof_count(
files, count,
"SELECT count(*) FROM edges e JOIN nodes s ON s.id=e.source_id "
"JOIN nodes t ON t.id=e.target_id WHERE e.type='CALLS' AND s.file_path='src/main.rs' "
"AND t.file_path=?1 AND t.name=?2 AND t.label=?3",
target, name, label);
}
/* Check the exact symbol the import donated, before a language resolver can
* conceal incorrect binding evidence by rejecting a later call. */
static int rust_proof_bindings(const rust_proof_file_t *files, size_t count, const char *target,
const char *name, const char *label) {
return rust_proof_count(
files, count,
"SELECT count(*) FROM nodes s, json_each(s.properties, '$.rust_import_bindings') b "
"JOIN nodes t ON t.qualified_name=json_extract(b.value, '$.target') "
"WHERE s.label='File' AND s.file_path='src/main.rs' "
"AND t.file_path=?1 AND t.name=?2 AND t.label=?3",
target, name, label);
}
static int rust_proof_imports(const rust_proof_file_t *files, size_t count, const char *target) {
return rust_proof_count(files, count,
"SELECT count(*) FROM edges e JOIN nodes s ON s.id=e.source_id "
"JOIN nodes t ON t.id=e.target_id WHERE e.type='IMPORTS' "
"AND s.label='File' AND s.file_path='src/main.rs' "
"AND t.label='File' AND t.file_path=?1",
target, NULL, NULL);
}
TEST(resolver_rust_import_alias_preserves_binding_name) {
const char *source = "use crate::service::Service as Svc;\n";
CBMFileResult *file =
cbm_extract_file(source, (int)strlen(source), CBM_LANG_RUST, "p", "main.rs", 0, NULL, NULL);
ASSERT_NOT_NULL(file);
bool exact = file->imports.count == 1 && !strcmp(file->imports.items[0].local_name, "Svc");
cbm_free_result(file);
ASSERT(exact);
PASS();
}
TEST(resolver_rust_pipeline_keeps_exact_method_and_rejects_module_alias) {
const rust_proof_file_t files[] = {
{"src/service.rs", RUST_SERVICE},
{"src/decoy.rs", RUST_SERVICE},
{"src/main.rs", "mod service; mod decoy;\nuse crate::service::Service as Svc;\npub fn "
"run(value: &Svc) { value.go(); }\nfn main() {}\n"}};
ASSERT_EQ(rust_proof_edges(files, 3, "src/service.rs", "go", "Method"), 1);
ASSERT_EQ(rust_proof_edges(files, 3, "src/decoy.rs", "go", "Method"), 0);
const rust_proof_file_t module[] = {
{"src/service.rs", RUST_SERVICE},
{"src/main.rs", "mod service;\nuse crate::service as Svc;\npub fn run(value: &Svc) { "
"value.go(); }\nfn main() {}\n"}};
ASSERT_EQ(rust_proof_edges(module, 2, "src/service.rs", "go", "Method"), 0);
PASS();
}
TEST(resolver_rust_declared_module_and_use_keep_exact_target) {
const rust_proof_file_t files[] = {{"src/actual.rs", "pub fn target() {}\n"},
{"src/decoy.rs", "pub fn target() {}\n"},
{"src/main.rs",
"mod actual;\nuse crate::actual::target;\n"
"fn caller() { target(); }\nfn main() {}\n"}};
ASSERT(rust_proof_imports(files, 3, "src/actual.rs") > 0);
ASSERT_EQ(rust_proof_imports(files, 3, "src/decoy.rs"), 0);
ASSERT_EQ(rust_proof_bindings(files, 3, "src/actual.rs", "target", "Function"), 1);
ASSERT_EQ(rust_proof_bindings(files, 3, "src/decoy.rs", "target", "Function"), 0);
ASSERT_EQ(rust_proof_edges(files, 3, "src/actual.rs", "target", "Function"), 1);
ASSERT_EQ(rust_proof_edges(files, 3, "src/decoy.rs", "target", "Function"), 0);
PASS();
}
TEST(resolver_rust_inline_module_does_not_bind_top_level_namesake) {
const char *sources[] = {"pub struct Service;\n"
"mod inner { pub struct Service; }\n"
"use crate::inner::Service as Svc;\n"
"fn consume(value: &Svc) {}\nfn main() {}\n",
"pub struct Service;\n"
"mod inner { pub struct Service; use self::Service as Svc;\n"
"pub fn consume(value: &Svc) {} }\nfn main() {}\n"};
for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) {
const rust_proof_file_t files[] = {{"src/main.rs", sources[i]}};
int bindings = rust_proof_count(
files, 1,
"SELECT count(*) FROM nodes s, json_each(s.properties, '$.rust_import_bindings') b "
"JOIN nodes t ON t.qualified_name=json_extract(b.value, '$.target') "
"WHERE s.label='File' AND s.file_path='src/main.rs' AND t.start_line=1 "
"AND t.file_path=?1 AND t.name=?2 AND t.label=?3",
"src/main.rs", "Service", "Struct");
if (bindings)
fprintf(stderr, "Rust inline import binding %zu: %d\n", i, bindings);
ASSERT_EQ(bindings, 0);
}
PASS();
}
TEST(resolver_rust_attributes_and_macros_do_not_donate_import_binding) {
const char *libraries[] = {"#[cfg(feature=\"alternate\")]\npub fn target() {}\n",
"#![cfg(feature=\"alternate\")]\npub fn target() {}\n",
"#[transform]\npub fn target() {}\n",
"macro_rules! influence { () => {} }\npub fn target() {}\n",
"influence!();\npub fn target() {}\n"};
for (size_t i = 0; i < sizeof(libraries) / sizeof(libraries[0]); i++) {
const rust_proof_file_t files[] = {{"src/actual.rs", libraries[i]},
{"src/main.rs",
"mod actual;\nuse crate::actual::target;\n"
"fn caller() { target(); }\nfn main() {}\n"}};
int bindings = rust_proof_bindings(files, 2, "src/actual.rs", "target", "Function");
if (bindings)
fprintf(stderr, "Rust unsupported import binding %zu: %d\n", i, bindings);
ASSERT_EQ(bindings, 0);
ASSERT_EQ(rust_proof_edges(files, 2, "src/actual.rs", "target", "Function"), 0);
}
PASS();
}
static const char RUST_PRIMITIVE_MAIN[] =
"mod service;\n"
"mod decoy;\n"
"mod shadow;\n"
"use crate::service::Service as Svc;\n"
"pub fn run(value: &Svc, x: i32, b: bool) {\n"
" value.take(x);\n"
" value.take(7);\n"
" value.take(7i32);\n"
" value.flag(b);\n"
" value.flag(true);\n"
" let alias=x;\n"
" value.take(alias);\n"
" value.small(-128);\n"
" value.big(1u64);\n"
"}\n"
"pub fn bad(value: &Svc, wide: i64) {\n"
" value.take(wide); value.take(7i64); value.small(128); value.big(-1); value.flag(1); "
"value.hidden(1);\n"
"}\n"
"pub fn gaps(value: &Svc) { value.float(1.0f32); let local=7; value.take(local); "
"value.generic(1); }\n"
"pub fn owned(value: Svc) { value.take(1); }\n"
"pub fn mutable(value: &mut Svc) { value.take(2); }\n"
"pub fn nested(value: &Svc, x: i32) { { let x=1i64; value.take(x); } }\n"
"struct Calc { base: i32 }\n"
"impl Calc {\n"
" fn helper(&self, x: i32) -> i32 { self.base + x }\n"
" fn run(&self, y: i32) -> i32 { self.helper(y) }\n"
"}\n"
"fn local(value: &Calc, x: i32) { value.helper(x); }\n"
"fn owned_local() { let c=Calc { base: 1 }; c.run(2); }\n"
"fn main() {}\n";
static const char RUST_PRIMITIVE_SERVICE[] = "pub struct Service;\n"
"impl Service {\n"
" pub fn take(&self, value: i32) {}\n"
" pub fn flag(&self, value: bool) {}\n"
" pub fn small(&self, value: i8) {}\n"
" pub fn big(&self, value: u64) {}\n"
" pub fn float(&self, value: f32) {}\n"
" pub fn generic<T>(&self, value: T) {}\n"
" fn hidden(&self, value: i32) {}\n"
"}\n";
static const char RUST_PRIMITIVE_DECOY[] = "pub struct Service;\n"
"impl Service {\n"
" pub fn take(&self, value: i32) {}\n"
"}\n";
static const char RUST_PRIMITIVE_SHADOW[] =
"use crate::service::Service;\n"
"type i32 = i64;\n"
"pub fn shadow(value: &Service, x: i32) { value.take(x); }\n";
static const RQ_Expected RUST_PRIMITIVE_EXPECTED[] = {
{.case_id = "primitive-parameter",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.take(x)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"}},
{.case_id = "contextual-int-literal",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.take(7)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"}},
{.case_id = "explicit-int-literal",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.take(7i32)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"}},
{.case_id = "boolean-parameter",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.flag(b)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.flag",
.label = "Method",
.declaration_line = 4,
.declaration_text = " pub fn flag(&self, value: bool) {}"}},
{.case_id = "boolean-literal",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.flag(true)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.flag",
.label = "Method",
.declaration_line = 4,
.declaration_text = " pub fn flag(&self, value: bool) {}"}},
{.case_id = "typed-parameter-alias",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.take(alias)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"}},
{.case_id = "contextual-signed-boundary",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.small(-128)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.small",
.label = "Method",
.declaration_line = 5,
.declaration_text = " pub fn small(&self, value: i8) {}"}},
{.case_id = "explicit-unsigned-literal",
.site = {.file = "src/main.rs", .caller = "run", .needle = "value.big(1u64)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.big",
.label = "Method",
.declaration_line = 6,
.declaration_text = " pub fn big(&self, value: u64) {}"}},
{.case_id = "private-lexical-self",
.site = {.file = "src/main.rs", .caller = "Calc.run", .needle = "self.helper(y)"},
.relation = "CALLS",
.target = {.file = "src/main.rs",
.suffix = "Calc.helper",
.label = "Method",
.declaration_line = 25,
.declaration_text = " fn helper(&self, x: i32) -> i32 { self.base + x }"}},
{.case_id = "private-same-module-reference",
.site = {.file = "src/main.rs", .caller = "local", .needle = "value.helper(x)"},
.relation = "CALLS",
.target = {.file = "src/main.rs",
.suffix = "Calc.helper",
.label = "Method",
.declaration_line = 25,
.declaration_text = " fn helper(&self, x: i32) -> i32 { self.base + x }"}},
{.case_id = "float-literal",
.site = {.file = "src/main.rs", .caller = "gaps", .needle = "value.float(1.0f32)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.float",
.label = "Method",
.declaration_line = 7,
.declaration_text = " pub fn float(&self, value: f32) {}"},
.mode = RQ_KNOWN_GAP,
.gap_id = "rust-float-literal",
.why = "floating literal representation and range are not audited",
.tried = "explicit f32 suffix is genuine but only primitive parameter facts and bounded "
"integer/bool literals are proved"},
{.case_id = "local-literal-inference",
.site = {.file = "src/main.rs", .caller = "gaps", .needle = "value.take(local)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"},
.mode = RQ_KNOWN_GAP,
.gap_id = "rust-local-literal-inference",
.why = "local literal type inference is not modeled",
.tried =
"the source infers i32 from the call; parameter-to-local copying is proved separately"},
{.case_id = "generic-inference",
.site = {.file = "src/main.rs", .caller = "gaps", .needle = "value.generic(1)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.generic",
.label = "Method",
.declaration_line = 8,
.declaration_text = " pub fn generic<T>(&self, value: T) {}"},
.mode = RQ_KNOWN_GAP,
.gap_id = "rust-generic-inference",
.why = "generic source signatures and inference remain unsupported",
.tried = "the compiler infers an integer T; the bridge retains the complete unproved method "
"domain"},
{.case_id = "owned-receiver",
.site = {.file = "src/main.rs", .caller = "owned", .needle = "value.take(1)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"},
.mode = RQ_KNOWN_GAP,
.gap_id = "rust-owned-receiver",
.why = "owned receiver autoref is not part of the audited reference candidate domain",
.tried = "the valid owned Service can borrow as &Service; only explicit shared-reference "
"facts are currently accepted"},
{.case_id = "mutable-receiver",
.site = {.file = "src/main.rs", .caller = "mutable", .needle = "value.take(2)"},
.relation = "CALLS",
.target = {.file = "src/service.rs",
.suffix = "Service.take",
.label = "Method",
.declaration_line = 3,
.declaration_text = " pub fn take(&self, value: i32) {}"},
.mode = RQ_KNOWN_GAP,
.gap_id = "rust-mutable-receiver",
.why = "mutable receiver reborrow/autoref remains unsupported",
.tried = "a mutable reference can call an &self method, but the current type fact "
"deliberately preserves that missing adaptation"},
{.case_id = "constructor-receiver",
.site = {.file = "src/main.rs", .caller = "owned_local", .needle = "c.run(2)"},
.relation = "CALLS",
.target = {.file = "src/main.rs",
.suffix = "Calc.run",
.label = "Method",
.declaration_line = 26,
.declaration_text = " fn run(&self, y: i32) -> i32 { self.helper(y) }"},
.mode = RQ_KNOWN_GAP,
.gap_id = "rust-constructor-receiver",
.why = "struct construction and owned-receiver autoref are not proved",
.tried = "the same-file private self call is required; this distinct constructor path remains "
"genuine quality debt"},
};
static const RQ_Forbidden RUST_PRIMITIVE_FORBIDDEN[] = {
{.case_id = "no-numeric-widening",
.site = {.file = "src/main.rs",
.caller = "bad",
.needle = "value.take(wide)",
.occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
{.case_id = "wrong-explicit-suffix",
.site = {.file = "src/main.rs",
.caller = "bad",
.needle = "value.take(7i64)",
.occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
{.case_id = "out-of-range-contextual-literal",
.site = {.file = "src/main.rs",
.caller = "bad",
.needle = "value.small(128)",
.occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
{.case_id = "negative-unsigned",
.site =
{.file = "src/main.rs", .caller = "bad", .needle = "value.big(-1)", .occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
{.case_id = "integer-is-not-boolean",
.site =
{.file = "src/main.rs", .caller = "bad", .needle = "value.flag(1)", .occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
{.case_id = "private-method-outside-module",
.site = {.file = "src/main.rs",
.caller = "bad",
.needle = "value.hidden(1)",
.occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
{.case_id = "nested-shadow-not-outer-type",
.site = {.file = "src/main.rs",
.caller = "nested",
.needle = "value.take(x)",
.occurrence_index = 1},
.relation = "CALLS",
.any_target = true},
{.case_id = "primitive-name-shadowed",
.site = {.file = "src/shadow.rs",
.caller = "shadow",
.needle = "value.take(x)",
.occurrence_index = 0},
.relation = "CALLS",
.any_target = true},
};
TEST(resolver_rust_primitive_arguments_and_private_self_require_proofs) {
for (int driver = 0; driver < 2; driver++) {
const char *root = th_mktempdir("cbm_rust_primitive");
ASSERT_NOT_NULL(root);
char repo[768], database[768];
snprintf(repo, sizeof(repo), "%s/repo", root);
snprintf(database, sizeof(database), "%s/index.db", root);
const RQ_Source sources[] = {{.path = "src/main.rs", .text = RUST_PRIMITIVE_MAIN},
{.path = "src/service.rs", .text = RUST_PRIMITIVE_SERVICE},
{.path = "src/decoy.rs", .text = RUST_PRIMITIVE_DECOY},
{.path = "src/shadow.rs", .text = RUST_PRIMITIVE_SHADOW}};
bool ok = cbm_mkdir_p(repo, 0755) && cbm_mkdir_p(TH_PATH(repo, "src"), 0755);
for (size_t i = 0; ok && i < sizeof(sources) / sizeof(sources[0]); i++)
ok = th_write_file(TH_PATH(repo, sources[i].path), sources[i].text) == 0;
for (int i = 0; ok && i < 55; i++) {
char path[32];
snprintf(path, sizeof(path), "Filler%02d.rs", i);
ok = th_write_file(TH_PATH(repo, path), "// unrelated source file\n") == 0;
}
const char *old_workers = getenv("CBM_WORKERS");
char *saved_workers = old_workers ? strdup(old_workers) : NULL;
ok = ok && (!old_workers || saved_workers);
cbm_setenv("CBM_WORKERS", driver ? "4" : "1", 1);
cbm_pipeline_t *pipeline = ok ? cbm_pipeline_new(repo, database, CBM_MODE_FULL) : NULL;
ok = pipeline && cbm_pipeline_set_project_name(pipeline, "rust-direct") &&
cbm_pipeline_run(pipeline) == 0;
cbm_pipeline_free(pipeline);
if (saved_workers) {
cbm_setenv("CBM_WORKERS", saved_workers, 1);
free(saved_workers);
} else
cbm_unsetenv("CBM_WORKERS");
cbm_store_t *store = ok ? cbm_store_open_path_existing(database) : NULL;
RQ_Report report = {0};
ok = store &&
rq_check(store, "rust-direct", "primitive-rust-members", sources,
sizeof(sources) / sizeof(sources[0]), RUST_PRIMITIVE_EXPECTED,
sizeof(RUST_PRIMITIVE_EXPECTED) / sizeof(RUST_PRIMITIVE_EXPECTED[0]),
RUST_PRIMITIVE_FORBIDDEN,
sizeof(RUST_PRIMITIVE_FORBIDDEN) / sizeof(RUST_PRIMITIVE_FORBIDDEN[0]),
&report, stderr) &&
ok;
if (store)
cbm_store_close(store);
th_cleanup(root);
ASSERT(ok);
ASSERT_EQ(report.genuine, 16);
ASSERT_EQ(report.found, 10);
ASSERT_EQ(report.missing, 6);
ASSERT_EQ(report.required_missing + report.wrong, 0);
}
PASS();
}
TEST(resolver_rust_local_impl_preserves_import_domain) {
const char *sources[] = {RUST_PRIMITIVE_MAIN,
"use crate::service::Service as Svc;\nstruct Local<T>(T);\n"
"impl<T> Local<T> { fn local(&self, value: T) {} }\n",
"use crate::service::Service as Svc;\nstruct Local;\n"
"impl Local { generate!(); }\n",
"use crate::service::Service as Svc;\nstruct Local;\n"
"#[cfg(feature=\"alternate\")] impl Local { fn local(&self) {} }\n",
"use crate::service::Service as Svc;\nstruct Local;\n"
"trait Dynamic { fn dynamic(&self); }\n"
"impl Dynamic for Local { fn dynamic(&self) {} }\n",
"use crate::service::Service as Svc;\nstruct Local;\n"
"impl Local { fn local(&self) where Self: Sized {} }\n"};
for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) {
const char *source = sources[i];
CBMFileResult *file = cbm_extract_file(source, (int)strlen(source), CBM_LANG_RUST,
"rust-domain", "src/main.rs", 0, NULL, NULL);
ASSERT_NOT_NULL(file);
CBMArena arena;
cbm_arena_init(&arena);
cbm_resolve_binding_index_t *bindings =
cbm_resolve_binding_build(&arena, file, CBM_LANG_RUST, source, strlen(source));
bool imported = cbm_resolve_binding_import(bindings, "Svc");
bool parsed = !file->has_error && !file->parse_incomplete && !file->parse_unusable;
cbm_arena_destroy(&arena);
cbm_free_result(file);
ASSERT(parsed);
ASSERT_EQ(imported, i == 0);
}
PASS();
}
SUITE(resolver_rust) {
RUN_TEST(resolver_rust_local_impl_preserves_import_domain);
RUN_TEST(resolver_rust_primitive_arguments_and_private_self_require_proofs);
RUN_TEST(resolver_rust_declared_module_and_use_keep_exact_target);
RUN_TEST(resolver_rust_inline_module_does_not_bind_top_level_namesake);
RUN_TEST(resolver_rust_attributes_and_macros_do_not_donate_import_binding);
RUN_TEST(resolver_rust_import_alias_preserves_binding_name);
RUN_TEST(resolver_rust_shared_reference_and_alias);
RUN_TEST(resolver_rust_receiver_shadow_and_unsupported_flow_abstain);
RUN_TEST(resolver_rust_visibility_traits_attributes_and_duplicates_abstain);
RUN_TEST(resolver_rust_pipeline_keeps_exact_method_and_rejects_module_alias);
}