Files
codebase-memory-mcp/tests/test_parallel.c
Martin Vogel 35154ab9f2 fix(pipeline): spill before resolve when the strict resolver would pass the budget
The projection that decides whether results spill was fitted before the strict resolver, at 160 bytes per reference. Resolve now grows by up to 3,250 bytes per reference, so elasticsearch projected 2,534 MB against 26,479 MB real, kept its results in memory, and reached 29,755 MB charged against a budget of 22,488 MB. No phase after extraction can spill.

The refit is 3,400 bytes per reference, fitted to Java, the most expensive language measured. Elasticsearch now spills by itself and peaks at 16,833 MB charged, with no slowdown and all 12 graph fingerprints the same as in memory. Spilling changes when results park, not what is resolved, so this is no work cap. It over-reads other languages, which only costs disk reads: kubernetes projects 18,479 MB for 8,384 MB real, and still keeps results in memory at the full budget.

Nothing updates the constant. A later change that costs more per reference has to refit it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:47 +01:00

7072 lines
315 KiB
C

/*
* test_parallel.c — Tests for the three-phase parallel pipeline.
*
* Validates parity between sequential (4-pass) and parallel (3-phase)
* pipeline modes on a small Go test fixture.
*
* Suite: suite_parallel
*/
#include "../src/foundation/compat.h"
#include "test_framework.h"
#include "test_helpers.h"
#include "pipeline/pipeline.h"
#include "pipeline/pipeline_internal.h"
#include "pipeline/pass_lsp_cross.h"
#include "pipeline/shared_resolver.h"
#include "pipeline/resolver_ecma.h"
#include "resolve/rust_names.h"
#include "pipeline/lsp_resolve.h"
#include "lsp/oo_strict.h"
#include "pipeline/worker_pool.h"
#include "graph_buffer/graph_buffer.h"
#include "discover/discover.h"
#include "foundation/platform.h"
#include "foundation/compat_fs.h"
#include "foundation/log.h"
#include "foundation/mem.h"
#include "cbm.h"
#include "result_spill.h"
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
#include <sys/stat.h>
/* ── Helper: create temp test repo ───────────────────────────────── */
static char g_par_tmpdir[256];
TEST(usage_semantic_reference_candidate_trusts_marked_producer) {
CBMUsage usage = {0};
usage.kind = CBM_USAGE_VALUE;
usage.may_be_call_reference = true;
ASSERT_TRUE(cbm_pipeline_usage_semantic_reference_candidate(&usage));
usage.may_be_call_reference = false;
ASSERT_FALSE(cbm_pipeline_usage_semantic_reference_candidate(&usage));
usage.kind = CBM_USAGE_CALL_REFERENCE;
ASSERT_TRUE(cbm_pipeline_usage_semantic_reference_candidate(&usage));
/* Lexical evidence gates only the later textual fallback. An exact
* occurrence row must still get the chance to prove one callable target. */
usage.semantic_reference_blocked = true;
ASSERT_TRUE(cbm_pipeline_usage_semantic_reference_candidate(&usage));
PASS();
}
static int setup_parallel_repo(void) {
snprintf(g_par_tmpdir, sizeof(g_par_tmpdir), "/tmp/cbm_par_XXXXXX");
if (!cbm_mkdtemp(g_par_tmpdir))
return -1;
char path[512];
/* main.go */
snprintf(path, sizeof(path), "%s/main.go", g_par_tmpdir);
FILE *f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "package main\n\nimport \"pkg\"\n\n"
"func main() {\n\tpkg.Serve()\n}\n");
fclose(f);
/* pkg/ */
snprintf(path, sizeof(path), "%s/pkg", g_par_tmpdir);
cbm_mkdir(path);
/* pkg/service.go */
snprintf(path, sizeof(path), "%s/pkg/service.go", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "package pkg\n\nimport \"pkg/util\"\n\n"
"func Serve() {\n\tutil.Help()\n}\n");
fclose(f);
/* pkg/util/ */
snprintf(path, sizeof(path), "%s/pkg/util", g_par_tmpdir);
cbm_mkdir(path);
/* pkg/util/helper.go */
snprintf(path, sizeof(path), "%s/pkg/util/helper.go", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "package util\n\nfunc Help() {}\n");
fclose(f);
/* Java interface/implements/extends trio: the parallel resolve path must
* make the same Interface-label edge split (IMPLEMENTS vs INHERITS) as
* the sequential semantic pass, and both must emit OVERRIDE for methods
* redefined from an explicit base. Without these files the IMPLEMENTS/
* INHERITS parity tests compare 0 == 0 and guard nothing (the demotion
* bug shipped: elasticsearch had 11k implements-as-INHERITS edges). */
snprintf(path, sizeof(path), "%s/probe", g_par_tmpdir);
cbm_mkdir(path);
snprintf(path, sizeof(path), "%s/probe/Shape.java", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "package probe;\npublic interface Shape {\n double area();\n}\n");
fclose(f);
snprintf(path, sizeof(path), "%s/probe/Circle.java", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "package probe;\npublic class Circle implements Shape {\n"
" @Override\n public double area() { return 1.0; }\n}\n");
fclose(f);
snprintf(path, sizeof(path), "%s/probe/Base.java", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "package probe;\npublic class Base extends Circle {\n"
" @Override\n public double area() { return 0.0; }\n}\n");
fclose(f);
/* NAMESPACE-declaring files whose import resolves through the namespace
* map (PHP `use`; C# `using` and Java package imports take the same path).
* Without these the spill-parity test compared a Go+Java fixture that never
* touches that map, so it passed while spilling silently changed import
* resolution for every namespaced repository: the map is built from the
* in-memory result cache, where a PARKED result is NULL, so a spilled file
* contributed no namespace at all and its imports fell through to the
* looser fallback (php corpus, 2026-09-18: 57,182 edges in memory against
* 59,379 while spilling, same binary, reproducible 3/3 each way). */
snprintf(path, sizeof(path), "%s/app", g_par_tmpdir);
cbm_mkdir(path);
snprintf(path, sizeof(path), "%s/app/Models.php", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "<?php\nnamespace App\\Models;\n\n"
"class User {\n public function name() { return \"u\"; }\n}\n");
fclose(f);
snprintf(path, sizeof(path), "%s/app/Services.php", g_par_tmpdir);
f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "<?php\nnamespace App\\Services;\n\nuse App\\Models\\User;\n\n"
"class UserService {\n"
" public function make() { $u = new User(); return $u->name(); }\n}\n");
fclose(f);
return 0;
}
static void rm_rf(const char *path) {
th_rmtree(path);
}
static void teardown_parallel_repo(void) {
if (g_par_tmpdir[0])
rm_rf(g_par_tmpdir);
g_par_tmpdir[0] = '\0';
}
/* The compact parity helpers normally omit the production structure pass.
* Tests that exercise graph-derived import maps must seed its File-node
* precondition explicitly, without changing legacy parity fixtures. */
static void seed_test_file_nodes(cbm_gbuf_t *gbuf, const char *project,
const cbm_file_info_t *files, int file_count) {
if (!gbuf || !project || !files) {
return;
}
for (int i = 0; i < file_count; i++) {
const char *rel = files[i].rel_path;
if (!rel) {
continue;
}
char *file_qn = cbm_pipeline_fqn_compute(project, rel, "__file__");
const char *slash = strrchr(rel, '/');
const char *basename = slash ? slash + 1 : rel;
if (file_qn) {
cbm_gbuf_upsert_node(gbuf, "File", basename, file_qn, rel, 0, 0, "{}");
}
free(file_qn);
}
}
/* ── Run sequential pipeline on files, returning gbuf ─────────────── */
/* Free the return-type table pass_calls may have built for a harness-owned
* ctx — mirrors the production teardown in pipeline.c; the harness never
* runs it, which leaked once the fixture gained typed (Java) methods. The
* fixture has no ObjectScript, so no macro table can exist here. */
static void harness_ctx_free_tables(cbm_pipeline_ctx_t *ctx) {
if (ctx->return_type_table) {
for (int i = 0; i < ctx->return_type_table->count; i++) {
free((void *)ctx->return_type_table->entries[i].return_type);
}
free((void *)ctx->return_type_table);
ctx->return_type_table = NULL;
}
cbm_ecma_surfaces_free(ctx->ecma_surfaces);
ctx->ecma_surfaces = NULL;
cbm_rust_project_free(ctx->rust_project);
ctx->rust_project = NULL;
}
/* Mirror production occurrence capture while the extraction cache, cross
* registry and shared export proofs are still alive. The graph must own all
* borrowed proof roots before any of those arenas is released. */
static bool harness_resolve_occurrences(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
int file_count) {
if (!ctx->shared_resolver)
return false;
bool ok = true;
for (int i = 0; ok && i < file_count; i++) {
bool loaded = false;
CBMFileResult *result =
cbm_pipeline_result_acquire(ctx, ctx->result_cache, i, NULL, &loaded);
if (!result)
continue;
const char **keys = NULL, **values = NULL;
int imports = 0;
ok = cbm_pxc_build_import_map(ctx, files[i].rel_path, files[i].language, result, &keys,
&values, &imports) == 0;
cbm_resolver_file_t input = {.ctx = ctx,
.registry = ctx->registry,
.graph = ctx->gbuf,
.output = ctx->gbuf,
.result = result,
.rel_path = files[i].rel_path,
.module_qn = result->module_qn,
.language = files[i].language,
.import_keys = keys,
.import_values = values,
.import_count = imports};
if (ok)
ok = cbm_shared_resolver_file(ctx->shared_resolver, &input);
cbm_pxc_free_import_map(keys, values, imports);
cbm_pipeline_result_release(result, loaded);
}
return ok && !atomic_load(&ctx->resolver_failed) && cbm_gbuf_resolutions_own_shared(ctx->gbuf);
}
static cbm_gbuf_t *run_sequential(const char *project, const char *repo_path,
cbm_file_info_t *files, int file_count) {
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, repo_path);
cbm_registry_t *reg = cbm_registry_new();
CBMFileResult **cache = calloc((size_t)file_count, sizeof(*cache));
if (!gbuf || !reg || !cache) {
cbm_gbuf_free(gbuf);
cbm_registry_free(reg);
free(cache);
return NULL;
}
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = project,
.repo_path = repo_path,
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
.result_cache = cache,
};
cbm_init();
bool ok = cbm_pipeline_pass_definitions(&ctx, files, file_count) == 0 &&
cbm_shared_resolver_capture_sources(&ctx, files, file_count, cache);
if (ok) {
ctx.shared_resolver = cbm_shared_resolver_build(&ctx, files, file_count, cache);
ok = ctx.shared_resolver != NULL;
}
if (ok)
ok = cbm_pipeline_pass_calls(&ctx, files, file_count) == 0 &&
cbm_pipeline_pass_usages(&ctx, files, file_count) == 0 &&
cbm_pipeline_pass_semantic(&ctx, files, file_count) == 0 &&
harness_resolve_occurrences(&ctx, files, file_count) &&
cbm_shared_resolver_project(gbuf);
if (!ok || atomic_load(&ctx.test_declarations_failed)) {
cbm_gbuf_free(gbuf);
gbuf = NULL;
}
cbm_shared_resolver_free(ctx.shared_resolver);
ctx.shared_resolver = NULL;
for (int i = 0; i < file_count; i++)
cbm_free_result(cache[i]);
free(cache);
ctx.result_cache = NULL;
harness_ctx_free_tables(&ctx);
cbm_registry_free(reg);
return gbuf;
}
typedef void (*parallel_result_mutator_fn)(CBMFileResult **result_cache, int file_count, void *ud);
/* Production's sequential pipeline retains one extraction cache and runs the
* cross-file LSP pass between definitions and edge materialization. Keep this
* explicit helper for tests whose semantic target is declared in another file;
* the lightweight run_sequential helper above intentionally predates that pass. */
static cbm_gbuf_t *run_sequential_with_lsp_cross_and_mutator(
const char *project, const char *repo_path, cbm_file_info_t *files, int file_count,
parallel_result_mutator_fn mutator, void *mutator_ud, bool seed_structure) {
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, repo_path);
cbm_registry_t *reg = cbm_registry_new();
CBMFileResult **cache = (CBMFileResult **)calloc((size_t)file_count, sizeof(CBMFileResult *));
if (!gbuf || !reg || !cache) {
cbm_gbuf_free(gbuf);
cbm_registry_free(reg);
free(cache);
return NULL;
}
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = project,
.repo_path = repo_path,
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
.result_cache = cache,
};
if (seed_structure) {
seed_test_file_nodes(gbuf, project, files, file_count);
}
cbm_init();
bool ok = cbm_pipeline_pass_definitions(&ctx, files, file_count) == 0 &&
cbm_shared_resolver_capture_sources(&ctx, files, file_count, cache);
if (ok) {
int cross_status = cbm_pipeline_pass_lsp_cross(&ctx, files, file_count, cache);
ok = cross_status != CBM_PIPELINE_ABORT_PRESERVE_DB &&
!atomic_load(&ctx.test_declarations_failed);
}
if (ok && mutator)
mutator(cache, file_count, mutator_ud);
if (ok) {
ctx.shared_resolver = cbm_shared_resolver_build(&ctx, files, file_count, cache);
ok = ctx.shared_resolver != NULL;
}
if (ok)
ok = cbm_pipeline_pass_calls(&ctx, files, file_count) == 0 &&
cbm_pipeline_pass_usages(&ctx, files, file_count) == 0 &&
cbm_pipeline_pass_semantic(&ctx, files, file_count) == 0 &&
harness_resolve_occurrences(&ctx, files, file_count) &&
cbm_shared_resolver_project(gbuf);
if (!ok || atomic_load(&ctx.test_declarations_failed)) {
cbm_gbuf_free(gbuf);
gbuf = NULL;
}
cbm_shared_resolver_free(ctx.shared_resolver);
ctx.shared_resolver = NULL;
for (int i = 0; i < file_count; i++) {
cbm_free_result(cache[i]);
}
free(cache);
ctx.result_cache = NULL;
harness_ctx_free_tables(&ctx);
cbm_registry_free(reg);
/* The cross pass builds its shared registries in the caller-owned
* ctx->seq_cross_arena precisely so they outlive pass_calls; production's
* run_sequential_pipeline destroys that arena after all passes, and this
* harness must too -- LSan flagged the whole registry arena (stdlib
* registrations included) leaking per test. */
if (ctx.seq_cross_arena_live) {
cbm_arena_destroy(&ctx.seq_cross_arena);
ctx.seq_cross_arena_live = false;
}
if (ctx.seq_cross_def_modules) {
for (int i = 0; i < ctx.seq_cross_def_module_count; i++) {
free(ctx.seq_cross_def_modules[i]);
}
free(ctx.seq_cross_def_modules);
ctx.seq_cross_def_modules = NULL;
}
return gbuf;
}
static cbm_gbuf_t *run_sequential_with_lsp_cross(const char *project, const char *repo_path,
cbm_file_info_t *files, int file_count) {
return run_sequential_with_lsp_cross_and_mutator(project, repo_path, files, file_count, NULL,
NULL, false);
}
/* ── Run parallel pipeline on files, returning gbuf ───────────────── */
/* Spill mode for the harness: the run below opts its context in (the way
* run_parallel_pipeline does) and records how many results the store parked. */
static bool g_harness_spill = false;
static int64_t g_harness_parked = -1;
/* Opt-in: hand cbm_parallel_resolve the production Tier-2 C# registry. */
static bool g_harness_cs_registry = false;
static cbm_gbuf_t *run_parallel_with_extract_opts_and_mutator(
const char *project, const char *repo_path, cbm_file_info_t *files, int file_count,
int worker_count, const cbm_parallel_extract_opts_t *extract_opts,
parallel_result_mutator_fn mutator, void *mutator_ud, bool seed_structure) {
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, repo_path);
cbm_registry_t *reg = cbm_registry_new();
CBMFileResult **result_cache = calloc((size_t)file_count, sizeof(*result_cache));
if (!gbuf || !reg || !result_cache) {
cbm_gbuf_free(gbuf);
cbm_registry_free(reg);
free(result_cache);
return NULL;
}
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = project,
.repo_path = repo_path,
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
.spill_allowed = g_harness_spill,
.result_cache = result_cache,
};
if (seed_structure) {
seed_test_file_nodes(gbuf, project, files, file_count);
}
_Atomic int64_t shared_ids;
int64_t gbuf_next = cbm_gbuf_next_id(gbuf);
atomic_init(&shared_ids, gbuf_next);
cbm_init();
int extract_status;
if (extract_opts) {
extract_status = cbm_parallel_extract_ex(&ctx, files, file_count, result_cache, &shared_ids,
worker_count, extract_opts);
} else {
extract_status =
cbm_parallel_extract(&ctx, files, file_count, result_cache, &shared_ids, worker_count);
}
cbm_gbuf_set_next_id(gbuf, atomic_load(&shared_ids));
if (g_harness_spill) {
int64_t bytes = 0;
int64_t loads = 0;
g_harness_parked = -1;
if (ctx.spill) {
cbm_result_spill_stats(ctx.spill, &g_harness_parked, &bytes, &loads);
}
}
bool ok = extract_status == 0 &&
cbm_shared_resolver_capture_sources(&ctx, files, file_count, result_cache);
if (ok && mutator) {
mutator(result_cache, file_count, mutator_ud);
}
if (ok)
ok = cbm_build_registry_from_cache(&ctx, files, file_count, result_cache) == 0;
int64_t registry_next = cbm_gbuf_next_id(gbuf);
if (registry_next > atomic_load(&shared_ids)) {
atomic_store(&shared_ids, registry_next);
}
/* Cross-file LSP — mirrors run_parallel_pipeline ordering in pipeline.c.
* Build the project-wide all_defs[] precondition, then feed it into
* cbm_parallel_resolve where the fused resolve_worker invokes
* cbm_pxc_run_one(_ts) per file BEFORE materializing CALLS edges. */
char **def_modules = (char **)calloc((size_t)file_count, sizeof(char *));
int def_count = 0;
CBMArena cross_arena;
cbm_arena_init(&cross_arena);
CBMLSPDef *all_defs =
ok && def_modules
? cbm_pxc_collect_all_defs(&ctx, &cross_arena, result_cache, files, file_count,
ctx.project_name, def_modules, &def_count, NULL)
: NULL;
if (def_count < 0 || atomic_load(&ctx.test_declarations_failed))
ok = false;
CBMModuleDefIndex *module_def_index =
all_defs ? cbm_pxc_build_module_def_index(all_defs, def_count) : NULL;
/* Production supplies a shared C-family registry: a visible external
* prototype needs the complete linkage domain across translation units,
* which the fallback module/import filter cannot provide. Keep the existing
* C# registry opt-in for the harness contracts that exercise that path. */
CBMCrossLspRegistries harness_registries = {0};
for (int i = 0; all_defs && i < file_count; i++) {
if (files[i].language == CBM_LANG_C || files[i].language == CBM_LANG_CPP ||
files[i].language == CBM_LANG_CUDA) {
harness_registries.c = cbm_c_build_cross_registry(&cross_arena, all_defs, def_count);
break;
}
}
if (g_harness_cs_registry && all_defs) {
harness_registries.cs = cbm_cs_build_cross_registry(&cross_arena, all_defs, def_count);
}
/* Java, Kotlin and C# static selection needs the declaration index production
* builds beside the registries; without it this route could not bind them. */
if (all_defs) {
harness_registries.oo =
cbm_pxc_build_oo_index(&ctx, &cross_arena, result_cache, files, file_count);
}
if (ok) {
ctx.shared_resolver = cbm_shared_resolver_build(&ctx, files, file_count, result_cache);
ok = ctx.shared_resolver != NULL;
}
if (ok)
ok = cbm_parallel_resolve(&ctx, files, file_count, result_cache, &shared_ids, worker_count,
all_defs, def_count, def_modules, module_def_index,
&harness_registries) == 0;
cbm_gbuf_set_next_id(gbuf, atomic_load(&shared_ids));
if (ok)
ok = !atomic_load(&ctx.resolver_failed) && !atomic_load(&ctx.test_declarations_failed) &&
cbm_gbuf_resolutions_own_shared(gbuf) && cbm_shared_resolver_project(gbuf);
if (!ok) {
cbm_gbuf_free(gbuf);
gbuf = NULL;
}
cbm_shared_resolver_free(ctx.shared_resolver);
ctx.shared_resolver = NULL;
cbm_pxc_free_module_def_index(module_def_index);
cbm_oo_index_free(harness_registries.oo);
free(all_defs);
cbm_arena_destroy(&cross_arena);
if (def_modules) {
for (int i = 0; i < file_count; i++) {
free(def_modules[i]);
}
free(def_modules);
}
for (int i = 0; i < file_count; i++)
if (result_cache[i])
cbm_free_result(result_cache[i]);
free(result_cache);
ctx.result_cache = NULL;
cbm_pipeline_spill_close(&ctx);
harness_ctx_free_tables(&ctx);
cbm_registry_free(reg);
/* cbm_parallel_extract installs a process-global package map whose
* production owner is the surrounding pipeline lifecycle. This direct
* pass harness owns that lifecycle itself, so mirror production teardown
* before returning the graph to the test. */
cbm_pkgmap_free(cbm_pipeline_get_pkgmap());
cbm_pipeline_set_pkgmap(NULL);
return gbuf;
}
static cbm_gbuf_t *run_parallel_with_extract_opts(const char *project, const char *repo_path,
cbm_file_info_t *files, int file_count,
int worker_count,
const cbm_parallel_extract_opts_t *extract_opts) {
return run_parallel_with_extract_opts_and_mutator(
project, repo_path, files, file_count, worker_count, extract_opts, NULL, NULL, false);
}
static cbm_gbuf_t *run_parallel(const char *project, const char *repo_path, cbm_file_info_t *files,
int file_count, int worker_count) {
return run_parallel_with_extract_opts(project, repo_path, files, file_count, worker_count,
NULL);
}
/* ── Parity Tests ─────────────────────────────────────────────────── */
static cbm_gbuf_t *g_seq_gbuf = NULL;
static cbm_gbuf_t *g_par_gbuf = NULL;
static int g_parity_setup_done = 0;
static int ensure_parity_setup(void) {
if (g_parity_setup_done)
return 0;
if (setup_parallel_repo() != 0)
return -1;
/* Discover files */
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
if (cbm_discover(g_par_tmpdir, &opts, &files, &file_count) != 0)
return -1;
const char *project = "par-test";
/* Build structure for both (need File/Folder nodes before definitions) */
/* For parity, we need the structure pass too. Let's just compare
* definition/call/usage/semantic edge counts. */
/* Run both modes */
g_seq_gbuf = run_sequential(project, g_par_tmpdir, files, file_count);
g_par_gbuf = run_parallel(project, g_par_tmpdir, files, file_count, 2);
cbm_discover_free(files, file_count);
g_parity_setup_done = 1;
return 0;
}
static void parity_teardown(void) {
if (g_seq_gbuf) {
cbm_gbuf_free(g_seq_gbuf);
g_seq_gbuf = NULL;
}
if (g_par_gbuf) {
cbm_gbuf_free(g_par_gbuf);
g_par_gbuf = NULL;
}
teardown_parallel_repo();
g_parity_setup_done = 0;
}
/* Node count parity */
TEST(parallel_node_count) {
if (ensure_parity_setup() != 0)
FAIL("setup failed");
int seq = cbm_gbuf_node_count(g_seq_gbuf);
int par = cbm_gbuf_node_count(g_par_gbuf);
ASSERT_GT(seq, 0);
ASSERT_EQ(seq, par);
PASS();
}
/* Edge type parity tests */
static int assert_edge_type_parity(const char *type) {
if (ensure_parity_setup() != 0)
return -1;
int seq = cbm_gbuf_edge_count_by_type(g_seq_gbuf, type);
int par = cbm_gbuf_edge_count_by_type(g_par_gbuf, type);
if (seq != par) {
printf(" FAIL: %s edges: seq=%d par=%d\n", type, seq, par);
return 1;
}
return 0;
}
TEST(parallel_calls_parity) {
int rc = assert_edge_type_parity("CALLS");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
TEST(parallel_defines_parity) {
int rc = assert_edge_type_parity("DEFINES");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
TEST(parallel_defines_method_parity) {
int rc = assert_edge_type_parity("DEFINES_METHOD");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
TEST(parallel_imports_parity) {
int rc = assert_edge_type_parity("IMPORTS");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
TEST(parallel_usage_parity) {
int rc = assert_edge_type_parity("USAGE");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
TEST(parallel_inherits_parity) {
int rc = assert_edge_type_parity("INHERITS");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
TEST(parallel_implements_parity) {
int rc = assert_edge_type_parity("IMPLEMENTS");
if (rc == -1)
FAIL("setup failed");
ASSERT_EQ(rc, 0);
PASS();
}
/* Absolute counts on the fixture — parity alone passes vacuously at 0==0,
* which is how the parallel-path IMPLEMENTS demotion shipped unnoticed. */
TEST(parallel_semantic_fixture_expected_counts) {
if (ensure_parity_setup() != 0)
FAIL("setup failed");
/* Circle implements Shape; Base extends Circle — in BOTH venues. */
ASSERT_EQ(cbm_gbuf_edge_count_by_type(g_seq_gbuf, "IMPLEMENTS"), 1);
ASSERT_EQ(cbm_gbuf_edge_count_by_type(g_par_gbuf, "IMPLEMENTS"), 1);
ASSERT_EQ(cbm_gbuf_edge_count_by_type(g_seq_gbuf, "INHERITS"), 1);
ASSERT_EQ(cbm_gbuf_edge_count_by_type(g_par_gbuf, "INHERITS"), 1);
/* Circle.area overrides Shape.area; Base.area overrides Circle.area. */
ASSERT_EQ(cbm_gbuf_edge_count_by_type(g_seq_gbuf, "OVERRIDE"), 2);
ASSERT_EQ(cbm_gbuf_edge_count_by_type(g_par_gbuf, "OVERRIDE"), 2);
PASS();
}
TEST(parallel_total_edges) {
if (ensure_parity_setup() != 0)
FAIL("setup failed");
int seq = cbm_gbuf_edge_count(g_seq_gbuf);
int par = cbm_gbuf_edge_count(g_par_gbuf);
ASSERT_GT(seq, 0);
ASSERT_EQ(seq, par);
PASS();
}
/* ── Spill mode: the graph is the in-memory graph ─────────────────── */
/* CBM_MEM_SPILL=1 parks every compacted result on disk the moment it is
* extracted; registry build, def collection, surfaces, resolve and the infra
* passes read each one back only for the moment they need it. The graph must
* not be able to tell: same nodes, same edges per type as the in-memory run
* of the same repo -- and every file must actually have gone through the
* store (a store that failed to open would silently test nothing). */
TEST(parallel_spill_mode_builds_the_same_graph) {
if (ensure_parity_setup() != 0)
FAIL("setup failed");
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
ASSERT_EQ(cbm_discover(g_par_tmpdir, &opts, &files, &file_count), 0);
ASSERT_GT(file_count, 0);
cbm_setenv("CBM_MEM_SPILL", "1", 1);
g_harness_spill = true;
cbm_gbuf_t *spilled = run_parallel("par-test", g_par_tmpdir, files, file_count, 2);
g_harness_spill = false;
cbm_unsetenv("CBM_MEM_SPILL");
cbm_discover_free(files, file_count);
ASSERT(spilled != NULL);
ASSERT_EQ((int)g_harness_parked, file_count);
ASSERT_EQ(cbm_gbuf_node_count(spilled), cbm_gbuf_node_count(g_par_gbuf));
ASSERT_EQ(cbm_gbuf_edge_count(spilled), cbm_gbuf_edge_count(g_par_gbuf));
static const char *const types[] = {"CALLS", "DEFINES", "DEFINES_METHOD", "IMPORTS",
"USES", "INHERITS", "IMPLEMENTS"};
for (size_t t = 0; t < sizeof(types) / sizeof(types[0]); t++) {
int in_memory = cbm_gbuf_edge_count_by_type(g_par_gbuf, types[t]);
int on_disk = cbm_gbuf_edge_count_by_type(spilled, types[t]);
if (in_memory != on_disk) {
printf(" FAIL: %s edges: in_memory=%d spilled=%d\n", types[t], in_memory, on_disk);
}
ASSERT_EQ(in_memory, on_disk);
}
cbm_gbuf_free(spilled);
PASS();
}
/* ── #2184: the post-extraction phases are charged before they run ─── */
/* Order-independent fingerprint of a whole graph: every node (label, QN, file,
* lines, properties) and every edge (type, source QN, target QN, properties)
* hashed and folded with a sum and an xor. Two graphs that differ in any field
* of any node or edge -- not just in a per-type count -- disagree here. */
typedef struct {
const cbm_gbuf_t *gb;
uint64_t sum;
uint64_t xr;
long count;
} graph_fp_t;
static uint64_t fp_mix(uint64_t h, const char *s) {
for (const unsigned char *p = (const unsigned char *)(s ? s : "\x01"); *p; p++) {
h = (h ^ *p) * 1099511628211ULL; /* FNV-1a */
}
return (h ^ 0xffU) * 1099511628211ULL; /* field separator */
}
static uint64_t fp_mix_int(uint64_t h, long long v) {
char buf[32];
snprintf(buf, sizeof(buf), "%lld", v);
return fp_mix(h, buf);
}
static void fp_fold(graph_fp_t *fp, uint64_t h) {
fp->sum += h;
fp->xr ^= h * 0x9E3779B97F4A7C15ULL;
fp->count++;
}
static void fp_visit_node(const cbm_gbuf_node_t *n, void *ud) {
uint64_t h = fp_mix(1469598103934665603ULL, "N");
h = fp_mix(h, n->label);
h = fp_mix(h, n->qualified_name);
h = fp_mix(h, n->file_path);
h = fp_mix_int(h, n->start_line);
h = fp_mix_int(h, n->end_line);
h = fp_mix(h, n->properties_json);
fp_fold(ud, h);
}
static void fp_visit_edge(const cbm_gbuf_edge_t *e, void *ud) {
graph_fp_t *fp = ud;
const cbm_gbuf_node_t *src = cbm_gbuf_find_by_id(fp->gb, e->source_id);
const cbm_gbuf_node_t *dst = cbm_gbuf_find_by_id(fp->gb, e->target_id);
uint64_t h = fp_mix(1469598103934665603ULL, "E");
h = fp_mix(h, e->type);
h = fp_mix(h, src ? src->qualified_name : NULL);
h = fp_mix(h, dst ? dst->qualified_name : NULL);
h = fp_mix(h, e->properties_json);
fp_fold(fp, h);
}
static graph_fp_t graph_fingerprint(const cbm_gbuf_t *gb) {
graph_fp_t fp = {.gb = gb};
cbm_gbuf_foreach_node(gb, fp_visit_node, &fp);
cbm_gbuf_foreach_edge(gb, fp_visit_edge, &fp);
return fp;
}
/* Mutator hook: runs between extraction and registry build, i.e. exactly where
* the post-extraction phases start. Records how many results are still held
* in memory there. */
static void count_cached_results(CBMFileResult **cache, int file_count, void *ud) {
int n = 0;
for (int i = 0; i < file_count; i++) {
n += cache[i] != NULL;
}
*(int *)ud = n;
}
/* #2184: spill was entered only DURING extraction, when the charge crossed
* budget - budget/16. A run that ended extraction just under that line kept
* every result in memory, and the phases that cannot spill (registry build,
* cross-LSP prepare, resolve) then grew the process past the budget -- openclaw
* at 8 workers, 4079 MB budget: 3678 MB at extraction end, 5829 MB in resolve.
* The extraction end now projects that growth from the result counts and
* spills first when budget - budget/16 would be crossed.
*
* The charge is pinned through the test seam (it is the process footprint
* otherwise): run A ends extraction ONE BYTE under the latch -- the extraction
* gate never fires, only the projection can spill; run B has the whole budget
* free -- the projection fits and nothing may spill. Both graphs must be
* identical, field for field, to each other and to the plain in-memory run. */
TEST(parallel_post_extract_projection_spills_before_resolve) {
if (ensure_parity_setup() != 0)
FAIL("setup failed");
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
ASSERT_EQ(cbm_discover(g_par_tmpdir, &opts, &files, &file_count), 0);
ASSERT_GT(file_count, 0);
const size_t saved_budget = cbm_mem_budget();
const size_t budget = (size_t)1024 * 1024 * 1024;
const size_t latch = budget - budget / 16; /* spill latches on charged > latch */
cbm_mem_set_budget_for_tests(budget);
g_harness_spill = true;
cbm_mem_set_charged_for_tests(latch);
int cached_near = -1;
cbm_gbuf_t *gb_near =
run_parallel_with_extract_opts_and_mutator("par-test", g_par_tmpdir, files, file_count, 2,
NULL, count_cached_results, &cached_near, false);
int64_t parked_near = g_harness_parked;
cbm_mem_set_charged_for_tests((size_t)1);
int cached_roomy = -1;
cbm_gbuf_t *roomy = run_parallel_with_extract_opts_and_mutator(
"par-test", g_par_tmpdir, files, file_count, 2, NULL, count_cached_results, &cached_roomy,
false);
int64_t parked_roomy = g_harness_parked;
cbm_mem_set_charged_for_tests(0);
g_harness_spill = false;
cbm_mem_set_budget_for_tests(saved_budget);
cbm_discover_free(files, file_count);
ASSERT(gb_near != NULL);
ASSERT(roomy != NULL);
/* A: every result went to disk before registry build. */
ASSERT_EQ((int)parked_near, file_count);
ASSERT_EQ(cached_near, 0);
/* B: the projection fits, so no store was opened and results stay cached. */
ASSERT_EQ((int)parked_roomy, -1);
ASSERT_GT(cached_roomy, 0);
graph_fp_t fp_near = graph_fingerprint(gb_near);
graph_fp_t fp_roomy = graph_fingerprint(roomy);
graph_fp_t fp_mem = graph_fingerprint(g_par_gbuf);
ASSERT_GT(fp_mem.count, 0);
ASSERT_EQ(fp_near.count, fp_mem.count);
ASSERT_EQ(fp_roomy.count, fp_mem.count);
ASSERT_TRUE(fp_near.sum == fp_mem.sum && fp_near.xr == fp_mem.xr);
ASSERT_TRUE(fp_roomy.sum == fp_mem.sum && fp_roomy.xr == fp_mem.xr);
cbm_gbuf_free(gb_near);
cbm_gbuf_free(roomy);
PASS();
}
/* What resolve was measured to add, in MB, on the release build. A projection under
* any of these keeps results in memory on a run that then goes over its budget. */
TEST(parallel_post_extract_projection_covers_the_measured_growth) {
static const struct {
int64_t defs, refs, files, growth_mb;
} measured[] = {
{747710, 8543542, 40698, 26486}, /* elasticsearch */
{714993, 5377047, 21907, 8384}, /* kubernetes */
{286702, 2111808, 8195, 3301}, /* kilocode */
{95488, 591412, 5462, 1250}, /* django */
{50286, 424639, 5247, 899}, /* exposed */
};
for (size_t i = 0; i < sizeof(measured) / sizeof(measured[0]); i++) {
size_t projected = cbm_parallel_post_extract_growth_bytes(
measured[i].defs, measured[i].refs, measured[i].files);
ASSERT_GTE(projected, (size_t)measured[i].growth_mb * 1024 * 1024);
}
PASS();
}
/* ── Sequential cache: held like the parallel one ─────────────────── */
/* The parallel extract caches each result without its parse tree and
* compacted (pass_parallel.c). The sequential definitions pass cached the raw
* result, tree and working arena included, and that cache lives through every
* later pass: about 5x the memory per file, for every file of the repo. PHP
* keeps its tree: the vendor-import check reads its sites' syntax from it. */
TEST(sequential_definitions_cache_drops_trees_and_compacts) {
if (ensure_parity_setup() != 0)
FAIL("setup failed");
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
ASSERT_EQ(cbm_discover(g_par_tmpdir, &opts, &files, &file_count), 0);
ASSERT_GT(file_count, 0);
cbm_gbuf_t *gbuf = cbm_gbuf_new("par-test", g_par_tmpdir);
cbm_registry_t *reg = cbm_registry_new();
CBMFileResult **cache = (CBMFileResult **)calloc((size_t)file_count, sizeof(CBMFileResult *));
ASSERT_NOT_NULL(cache);
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = "par-test",
.repo_path = g_par_tmpdir,
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
.result_cache = cache,
};
cbm_init();
cbm_work_arena_release(); /* start from a thread that keeps nothing */
ASSERT_EQ(cbm_pipeline_pass_definitions(&ctx, files, file_count), 0);
ASSERT_FALSE(cbm_work_arena_keeping()); /* ...and end the pass that way */
int cached = 0;
int php_trees = 0;
for (int i = 0; i < file_count; i++) {
const CBMFileResult *r = cache[i];
if (!r) {
continue;
}
cached++;
if (files[i].language == CBM_LANG_PHP) {
ASSERT_NOT_NULL(r->cached_tree);
php_trees++;
} else {
ASSERT_NULL(r->cached_tree);
}
/* One exact block: capacity == bytes used, no dead headroom. */
ASSERT_EQ(r->arena.nblocks, 1);
ASSERT_EQ(cbm_arena_capacity(&r->arena), cbm_arena_total(&r->arena));
}
ASSERT_EQ(cached, file_count);
ASSERT_GT(php_trees, 0); /* app/Models.php and app/Services.php */
for (int i = 0; i < file_count; i++) {
cbm_free_result(cache[i]);
}
free(cache);
cbm_registry_free(reg);
cbm_gbuf_free(gbuf);
cbm_discover_free(files, file_count);
PASS();
}
/* A configured C test convention whose file cannot be extracted aborts the
* sequential pass (CBM_PIPELINE_ABORT_PRESERVE_DB). The Python file compacted
* before it handed its working arena back to this thread, so that exit has to
* release it like the others. */
TEST(sequential_definitions_configured_failure_releases_work_arena) {
char dir[256];
snprintf(dir, sizeof(dir), "/tmp/cbm_seqcf_XXXXXX");
ASSERT_NOT_NULL(cbm_mkdtemp(dir));
char py_path[320];
char c_path[320];
snprintf(py_path, sizeof(py_path), "%s/a.py", dir);
snprintf(c_path, sizeof(c_path), "%s/z.c", dir);
ASSERT_EQ(th_write_file(py_path, "def first():\n return 1\n"), 0);
ASSERT_EQ(th_write_file(c_path, "void second(void) {}\n"), 0);
cbm_file_info_t files[] = {
{.path = py_path, .rel_path = "a.py", .language = CBM_LANG_PYTHON},
{.path = c_path, .rel_path = "z.c", .language = CBM_LANG_C},
};
static const char config[] =
"{\"test_impact\":{\"version\":1,\"tests\":{\"conventions\":[{\"language\":\"c\","
"\"role\":\"case\",\"define_macro\":\"CHECK\",\"name_args\":[0]}]}}}";
cbm_test_declarations_t *declarations =
cbm_test_declarations_parse(config, sizeof(config) - 1, false);
ASSERT_NOT_NULL(declarations);
cbm_gbuf_t *gbuf = cbm_gbuf_new("seq-cf", dir);
cbm_registry_t *reg = cbm_registry_new();
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = "seq-cf",
.repo_path = dir,
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
.test_declarations = declarations,
.test_fault_mask = CBM_PIPELINE_TEST_FAULT_EXTRACT_NULL, /* z.c extracts to NULL */
};
cbm_init();
cbm_work_arena_release(); /* start from a thread that keeps nothing */
int rc = cbm_pipeline_pass_definitions(&ctx, files, 2);
bool keeping = cbm_work_arena_keeping();
cbm_work_arena_release();
cbm_registry_free(reg);
cbm_gbuf_free(gbuf);
cbm_test_declarations_free(declarations);
rm_rf(dir);
ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB);
ASSERT_FALSE(keeping);
PASS();
}
/* ── Empty file list ──────────────────────────────────────────────── */
TEST(parallel_empty_files) {
cbm_gbuf_t *gbuf = cbm_gbuf_new("empty-proj", "/tmp");
cbm_registry_t *reg = cbm_registry_new();
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = "empty-proj",
.repo_path = "/tmp",
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
};
_Atomic int64_t shared_ids;
atomic_init(&shared_ids, 1);
CBMFileResult **cache = NULL;
int rc = cbm_parallel_extract(&ctx, NULL, 0, cache, &shared_ids, 2);
ASSERT_EQ(rc, 0);
ASSERT_EQ(cbm_gbuf_node_count(gbuf), 0);
cbm_registry_free(reg);
cbm_gbuf_free(gbuf);
PASS();
}
#include "resolver_quality.h"
#include "store/store_resolver.h"
/* Persist actual harness records through the production publication API.
* Expectations come only from fixture source; missing snapshots and
* unattested graph edges remain failures. */
static bool parallel_quality_check_with_forbidden(cbm_gbuf_t *graph, const char *project,
const char *suite, const RQ_Source *sources,
size_t source_count, const RQ_Expected *expected,
size_t expected_count,
const RQ_Forbidden *forbidden,
size_t forbidden_count, RQ_Report *report) {
cbm_store_t *store = cbm_store_open_memory();
size_t count = 0, file_count = 0, summary_count = 0;
const cbm_resolve_record_t *rows = cbm_gbuf_resolutions(graph, &count);
const cbm_resolve_file_t *files = cbm_gbuf_resolver_files(graph, &file_count);
const cbm_resolve_summary_t *summaries = cbm_gbuf_resolver_summaries(graph, &summary_count);
bool ok = store && cbm_gbuf_resolutions_complete(graph) &&
cbm_gbuf_flush_to_store(graph, store) == CBM_STORE_OK &&
cbm_store_resolver_replace_generation(store, project, rows, count, files, file_count,
summaries, summary_count) == CBM_STORE_OK;
if (ok)
ok = rq_check(store, project, suite, sources, source_count, expected, expected_count,
forbidden, forbidden_count, report, stderr);
if (store)
cbm_store_close(store);
return ok;
}
static bool parallel_quality_check(cbm_gbuf_t *graph, const char *project, const char *suite,
const RQ_Source *sources, size_t source_count,
const RQ_Expected *expected, size_t expected_count,
RQ_Report *report) {
return parallel_quality_check_with_forbidden(graph, project, suite, sources, source_count,
expected, expected_count, NULL, 0, report);
}
/* Read only this test's authored fixture, for independent occurrence anchors. */
static char *parallel_fixture_source(const char *path) {
FILE *file = cbm_fopen(path, "rb");
if (!file)
return NULL;
if (fseek(file, 0, SEEK_END)) {
fclose(file);
return NULL;
}
long size = ftell(file);
if (size < 0 || size > 1024 * 1024 || fseek(file, 0, SEEK_SET)) {
fclose(file);
return NULL;
}
char *source = malloc((size_t)size + 1);
if (!source || fread(source, 1, (size_t)size, file) != (size_t)size) {
free(source);
fclose(file);
return NULL;
}
source[size] = 0;
fclose(file);
return source;
}
/* ── Regression: args JSON must not overflow the props buffer ──────── */
/* A call with many long string arguments makes append_args_json()'s running
* position exceed the fixed CBM_SZ_2K `props` stack buffer in
* emit_normal_calls_edge(): format_call_arg() returns snprintf's UNtruncated
* length, so pos += n could run past the buffer and the trailing
* buf[pos]='\0' wrote out of bounds (stack-buffer-overflow; caught by the
* stack canary as a SIGABRT on real repos). Preserve the original sixty-arg
* pipeline fixture, then exercise the serializer with an additional source
* fixture whose eight captured arguments exceed 2 KB after formatting. The
* extraction cap must not make the ASan overflow regression vacuous. */
TEST(parallel_args_json_no_overflow) {
char dir[256];
snprintf(dir, sizeof(dir), "/tmp/cbm_argov_XXXXXX");
ASSERT_TRUE(cbm_mkdtemp(dir) != NULL);
char path[512];
snprintf(path, sizeof(path), "%s/app.ts", dir);
FILE *f = fopen(path, "w");
ASSERT_TRUE(f != NULL);
fputs("function sink(...xs: string[]) { return xs; }\n", f);
fputs("function caller() {\n sink(\n", f);
for (int i = 0; i < 60; i++) {
/* Keep the original sixty 100-character literals. Extraction captures
* the first eight; the independent longer control below overflows. */
fputs(" \"", f);
for (int j = 0; j < 100; j++)
fputc('a' + (i % 26), f);
fputs(i < 59 ? "\",\n" : "\"\n", f);
}
fputs(" );\n}\n", f);
fclose(f);
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
ASSERT_EQ(cbm_discover(dir, &opts, &files, &file_count), 0);
ASSERT_GT(file_count, 0);
cbm_gbuf_t *gbuf = run_parallel("argov-test", dir, files, file_count, 4);
ASSERT_TRUE(gbuf != NULL);
char *source = parallel_fixture_source(path);
ASSERT_NOT_NULL(source);
const char *call = strstr(source, " sink(");
ASSERT_NOT_NULL(call);
call += 2;
const char *end = strstr(call, " );");
ASSERT_NOT_NULL(end);
RQ_Source input = {.path = "app.ts", .text = source};
RQ_Expected expected = {
.case_id = "wide-rest-argument-call",
.site = {.file = "app.ts",
.caller = "app.caller",
.start_byte = (uint32_t)(call - source),
.end_byte = (uint32_t)(end + 3 - source)},
.relation = "CALLS",
.target = {.file = "app.ts",
.suffix = "app.sink",
.label = "Function",
.declaration_line = 1,
.declaration_text = "function sink(...xs: string[]) { return xs; }"}};
RQ_Report quality = {0};
ASSERT(parallel_quality_check(gbuf, "argov-test", "wide-call-arguments", &input, 1, &expected,
1, &quality));
ASSERT_EQ(quality.genuine, 1);
ASSERT_EQ(quality.missing, 0);
/* The original input's first eight 100-byte values fit. A second source
* fixture stays within CBM_MAX_CALL_ARGS while making each literal longer
* so the last JSON argument cannot fit. Neither control needs a speculative
* CALLS edge to reach the real production serializer. */
char long_source[CBM_SZ_4K];
int length = snprintf(long_source, sizeof(long_source),
"function sink(...xs: string[]) { return xs; }\n"
"function caller() {\n sink(\n");
ASSERT_GT(length, 0);
ASSERT_LT((size_t)length, sizeof(long_source));
for (int i = 0; i < CBM_MAX_CALL_ARGS; i++) {
char literal[201];
memset(literal, 'a' + i % 26, sizeof(literal) - 1);
literal[sizeof(literal) - 1] = '\0';
int added = snprintf(long_source + length, sizeof(long_source) - (size_t)length,
" \"%s\"%s\n", literal, i + 1 < CBM_MAX_CALL_ARGS ? "," : "");
ASSERT_GT(added, 0);
ASSERT_LT((size_t)added, sizeof(long_source) - (size_t)length);
length += added;
}
int added = snprintf(long_source + length, sizeof(long_source) - (size_t)length, " );\n}\n");
ASSERT_GT(added, 0);
ASSERT_LT((size_t)added, sizeof(long_source) - (size_t)length);
const char *serializer_sources[] = {source, long_source};
for (int control = 0; control < 2; control++) {
const char *text = serializer_sources[control];
const char *site = strstr(text, " sink(");
ASSERT_NOT_NULL(site);
site += 2;
const char *site_end = strstr(site, " );");
ASSERT_NOT_NULL(site_end);
CBMFileResult *extracted = cbm_extract_file(text, (int)strlen(text), CBM_LANG_TYPESCRIPT,
"argov-test", "app.ts", 0, NULL, NULL);
ASSERT_NOT_NULL(extracted);
const CBMCall *wide_call = NULL;
for (int i = 0; i < extracted->calls.count; i++) {
const CBMCall *candidate = &extracted->calls.items[i];
if (candidate->site_start_byte == (uint32_t)(site - text) &&
candidate->site_end_byte == (uint32_t)(site_end + 3 - text) &&
candidate->callee_name && !strcmp(candidate->callee_name, "sink")) {
ASSERT_NULL(wide_call);
wide_call = candidate;
}
}
ASSERT_NOT_NULL(wide_call);
ASSERT_EQ(wide_call->arg_count, CBM_MAX_CALL_ARGS);
for (int i = 0; i < wide_call->arg_count; i++) {
ASSERT_NOT_NULL(wide_call->args[i].value);
ASSERT_EQ(strlen(wide_call->args[i].value), control ? 200 : 100);
}
char props[CBM_SZ_2K];
int prefix = snprintf(props, sizeof(props),
"{\"callee\":\"sink\",\"confidence\":1.00,\"strategy\":\"exact\","
"\"candidates\":1");
cbm_pipeline_close_call_props(props, prefix, wide_call, true);
ASSERT_GT(strlen(props), 1024);
ASSERT_LT(strlen(props), sizeof(props));
sqlite3 *json_db = NULL;
sqlite3_stmt *json = NULL;
ASSERT_EQ(sqlite3_open(":memory:", &json_db), SQLITE_OK);
ASSERT_EQ(sqlite3_prepare_v2(json_db,
"SELECT json_extract(value, '$.i'), "
"json_extract(value, '$.v') FROM json_each(?1, '$.args')",
-1, &json, NULL),
SQLITE_OK);
ASSERT_EQ(sqlite3_bind_text(json, 1, props, -1, SQLITE_STATIC), SQLITE_OK);
int retained = 0, step;
while ((step = sqlite3_step(json)) == SQLITE_ROW) {
ASSERT_EQ(sqlite3_column_int(json, 0), retained);
const char *value = (const char *)sqlite3_column_text(json, 1);
ASSERT_NOT_NULL(value);
size_t value_length = strlen(value);
if (control) {
ASSERT_GT(value_length, 100);
ASSERT_LTE(value_length, 200);
} else {
ASSERT_EQ(value_length, 100);
}
for (size_t i = 0; i < value_length; i++)
ASSERT_EQ(value[i], 'a' + retained % 26);
retained++;
}
ASSERT_EQ(step, SQLITE_DONE);
if (control) {
ASSERT_GT(retained, 0);
ASSERT_LT(retained, CBM_MAX_CALL_ARGS);
} else {
ASSERT_EQ(retained, CBM_MAX_CALL_ARGS);
}
sqlite3_finalize(json);
sqlite3_close(json_db);
cbm_free_result(extracted);
}
free(source);
cbm_gbuf_free(gbuf);
cbm_discover_free(files, file_count);
th_rmtree(dir);
PASS();
}
/* F14: a definition's properties buffer was sized for its docstring and variants
* only. A wide signature (50 annotated parameters) filled the rest of the 2 KB, and
* the MinHash fingerprint "fp", structural profile "sp" and body tokens "bt"
* appended behind it were dropped whole: no SIMILAR_TO edge and fewer
* SEMANTICALLY_RELATED edges for exactly the widest definitions (jellyfin's
* ImageController actions, 2026-10-18). `small` has the same body and is the
* control that the fixture produces all three at all. Both resolvers. */
static const char f14_body[] = " total = 0\n"
" for item in range(10):\n"
" if item % 3 == 0:\n"
" total += item * 2\n"
" elif item % 5 == 1:\n"
" total -= len(str(item))\n"
" else:\n"
" total ^= hash((item, total))\n"
" values = [x * x for x in range(total % 7 + 3)]\n"
" mapping = {str(k): v for k, v in enumerate(values)}\n"
" while total > 100:\n"
" total //= 3\n"
" try:\n"
" result = sorted(mapping.items(), key=lambda kv: kv[1])\n"
" except ValueError as err:\n"
" result = [(\"error\", str(err))]\n"
" return result, total, mapping\n";
static bool f14_props_have(const cbm_gbuf_t *gb, const char *project, const char *name,
const char *venue) {
char *qn = cbm_pipeline_fqn_compute(project, "pkg/wide.py", name);
const cbm_gbuf_node_t *n = qn ? cbm_gbuf_find_by_qn(gb, qn) : NULL;
free(qn);
const char *p = n && n->properties_json ? n->properties_json : "";
bool fp = strstr(p, "\"fp\":\"") != NULL;
bool sp = strstr(p, "\"sp\":\"") != NULL;
bool bt = strstr(p, "\"bt\":\"") != NULL;
bool sig = strstr(p, "\"signature\":\"") != NULL;
printf(" f14 %s %s: found=%d len=%zu fp=%d sp=%d bt=%d signature=%d\n", venue, name, n != NULL,
strlen(p), fp, sp, bt, sig);
return n && fp && sp && bt && sig;
}
TEST(parallel_wide_definition_keeps_fp_sp_bt_f14) {
char params[CBM_SZ_4K];
size_t pos = 0;
for (int i = 0; i < 50; i++) {
pos += (size_t)snprintf(params + pos, sizeof(params) - pos,
"%sp%02d: Dict[str, int] = None", i ? ", " : "", i);
}
char src[CBM_SZ_8K];
snprintf(src, sizeof(src), "def small(a, b):\n%s\n\ndef wide(%s):\n%s", f14_body, params,
f14_body);
const char *project = "cbm_f14";
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_f14_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/pkg/wide.py", tmpdir);
if (th_write_file(path, src) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write the F14 fixture");
}
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"pkg/wide.py";
files[0].language = CBM_LANG_PYTHON;
cbm_gbuf_t *graphs[2] = {run_sequential(project, tmpdir, files, 1),
run_parallel(project, tmpdir, files, 1, 2)};
ASSERT_NOT_NULL(graphs[0]);
ASSERT_NOT_NULL(graphs[1]);
bool all = true;
for (int g = 0; g < 2; g++) {
const char *venue = g == 0 ? "sequential" : "parallel";
bool control = f14_props_have(graphs[g], project, "small", venue);
bool wide = f14_props_have(graphs[g], project, "wide", venue);
all = all && control && wide;
}
cbm_gbuf_free(graphs[0]);
cbm_gbuf_free(graphs[1]);
th_rmtree(tmpdir);
ASSERT_TRUE(all);
PASS();
}
/* ── Graph buffer merge tests ─────────────────────────────────────── */
TEST(gbuf_shared_ids_unique) {
_Atomic int64_t shared = 1;
cbm_gbuf_t *ga = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_t *gb = cbm_gbuf_new_shared_ids("proj", "/", &shared);
int64_t id1 = cbm_gbuf_upsert_node(ga, "Function", "foo", "proj.foo", "a.go", 1, 5, "{}");
int64_t id2 = cbm_gbuf_upsert_node(gb, "Function", "bar", "proj.bar", "b.go", 1, 3, "{}");
ASSERT_GT(id1, 0);
ASSERT_GT(id2, 0);
ASSERT_NEQ(id1, id2);
cbm_gbuf_free(ga);
cbm_gbuf_free(gb);
PASS();
}
TEST(gbuf_merge_nodes) {
_Atomic int64_t shared = 1;
cbm_gbuf_t *dst = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_t *src = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_upsert_node(dst, "Function", "a", "proj.a", "a.go", 1, 5, "{}");
cbm_gbuf_upsert_node(dst, "Function", "b", "proj.b", "a.go", 6, 10, "{}");
cbm_gbuf_upsert_node(src, "Function", "c", "proj.c", "b.go", 1, 5, "{}");
cbm_gbuf_upsert_node(src, "Function", "d", "proj.d", "b.go", 6, 10, "{}");
ASSERT_EQ(cbm_gbuf_node_count(dst), 2);
cbm_gbuf_merge(dst, src);
ASSERT_EQ(cbm_gbuf_node_count(dst), 4);
ASSERT_NOT_NULL(cbm_gbuf_find_by_qn(dst, "proj.c"));
ASSERT_NOT_NULL(cbm_gbuf_find_by_qn(dst, "proj.d"));
/* dst originals still there */
ASSERT_NOT_NULL(cbm_gbuf_find_by_qn(dst, "proj.a"));
ASSERT_NOT_NULL(cbm_gbuf_find_by_qn(dst, "proj.b"));
cbm_gbuf_free(src);
cbm_gbuf_free(dst);
PASS();
}
TEST(gbuf_merge_edges) {
_Atomic int64_t shared = 1;
cbm_gbuf_t *dst = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_t *src = cbm_gbuf_new_shared_ids("proj", "/", &shared);
int64_t a = cbm_gbuf_upsert_node(dst, "Function", "a", "proj.a", "a.go", 1, 5, "{}");
int64_t b = cbm_gbuf_upsert_node(dst, "Function", "b", "proj.b", "a.go", 6, 10, "{}");
/* Put an edge in src that references dst nodes (by ID) */
cbm_gbuf_insert_edge(src, a, b, "CALLS", "{}");
cbm_gbuf_merge(dst, src);
ASSERT_GT(cbm_gbuf_edge_count(dst), 0);
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
cbm_gbuf_find_edges_by_source_type(dst, a, "CALLS", &edges, &count);
ASSERT_EQ(count, 1);
ASSERT_EQ(edges[0]->target_id, b);
cbm_gbuf_free(src);
cbm_gbuf_free(dst);
PASS();
}
TEST(gbuf_merge_empty_src) {
_Atomic int64_t shared = 1;
cbm_gbuf_t *dst = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_t *src = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_upsert_node(dst, "Function", "a", "proj.a", "a.go", 1, 5, "{}");
int before = cbm_gbuf_node_count(dst);
cbm_gbuf_merge(dst, src);
ASSERT_EQ(cbm_gbuf_node_count(dst), before);
cbm_gbuf_free(src);
cbm_gbuf_free(dst);
PASS();
}
TEST(gbuf_merge_src_free_safe) {
_Atomic int64_t shared = 1;
cbm_gbuf_t *dst = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_t *src = cbm_gbuf_new_shared_ids("proj", "/", &shared);
cbm_gbuf_upsert_node(src, "Function", "x", "proj.x", "x.go", 1, 5, "{}");
cbm_gbuf_merge(dst, src);
cbm_gbuf_free(src); /* must not crash */
/* dst node still accessible */
ASSERT_NOT_NULL(cbm_gbuf_find_by_qn(dst, "proj.x"));
cbm_gbuf_free(dst);
PASS();
}
TEST(gbuf_next_id_accessors) {
cbm_gbuf_t *gb = cbm_gbuf_new("proj", "/");
ASSERT_EQ(cbm_gbuf_next_id(gb), 1);
cbm_gbuf_upsert_node(gb, "Function", "foo", "proj.foo", "f.go", 1, 5, "{}");
ASSERT_GT(cbm_gbuf_next_id(gb), 1);
cbm_gbuf_set_next_id(gb, 100);
int64_t id = cbm_gbuf_upsert_node(gb, "Function", "bar", "proj.bar", "f.go", 6, 10, "{}");
ASSERT_GTE(id, 100);
cbm_gbuf_free(gb);
PASS();
}
TEST(gbuf_raise_next_id_never_lowers) {
cbm_gbuf_t *gb = cbm_gbuf_new("proj", "/");
cbm_gbuf_raise_next_id(gb, 100);
ASSERT_EQ(cbm_gbuf_next_id(gb), 100);
cbm_gbuf_raise_next_id(gb, 40);
ASSERT_EQ(cbm_gbuf_next_id(gb), 100);
int64_t id = cbm_gbuf_upsert_node(gb, "Function", "foo", "proj.foo", "f.go", 1, 5, "{}");
ASSERT_EQ(id, 100);
cbm_gbuf_free(gb);
PASS();
}
/* Edge ids in the main buffer stay unique through the parallel phases.
* Merged worker nodes keep their shared ids, and merged edges take fresh ids
* from the main counter after the highest of them, so the main counter can
* end above the shared one (12 of 17 infra corpora, deckhouse by 229,233).
* The sync after each parallel phase used to set the main counter to the
* shared value anyway, and later passes reused the merged edges' ids. Where
* the counter ends depends on worker timing, so the seam pins it: right before
* each sync the hook takes ID_SYNC_PUSH edge ids from the main buffer. */
enum { ID_FILES = 64, ID_SYNC_PUSH = 3 };
typedef struct {
bool push;
int syncs;
} id_sync_probe_t;
static void id_sync_hook(cbm_gbuf_t *gb, void *ctx) {
id_sync_probe_t *probe = ctx;
probe->syncs++;
const cbm_gbuf_node_t **fns = NULL;
int n = 0;
if (!probe->push || cbm_gbuf_find_by_label(gb, "Function", &fns, &n) != 0 || n < 2) {
return;
}
char props[CBM_SZ_64];
for (int i = 0; i < ID_SYNC_PUSH; i++) {
snprintf(props, sizeof(props), "{\"id_sync_probe\":%d}", probe->syncs * ID_SYNC_PUSH + i);
cbm_gbuf_insert_edge(gb, fns[0]->id, fns[1]->id, "CALLS", props);
}
}
typedef struct {
int edges;
int duplicates;
} edge_id_probe_t;
static void probe_edge_ids(const cbm_gbuf_t *gb, void *ctx) {
edge_id_probe_t *probe = ctx;
probe->edges = cbm_gbuf_edge_count(gb);
probe->duplicates = cbm_gbuf_duplicate_edge_ids(gb);
}
/* `count` Go files; each function calls the next file's. */
static bool write_id_fixture(const char *repo, int count) {
char path[512];
char source[256];
for (int i = 0; i < count; i++) {
snprintf(path, sizeof(path), "%s/f%02d.go", repo, i);
snprintf(source, sizeof(source),
"package main\n\nfunc F%02d() int {\n\treturn F%02d() + 1\n}\n", i,
(i + 1) % count);
if (th_write_file(path, source) != 0) {
return false;
}
}
return true;
}
TEST(parallel_full_index_keeps_edge_ids_unique) {
char repo[256];
snprintf(repo, sizeof(repo), "/tmp/cbm_par_ids_XXXXXX");
ASSERT_NOT_NULL(cbm_mkdtemp(repo));
/* More files than MIN_FILES_FOR_PARALLEL (50): the parallel route. */
if (!write_id_fixture(repo, ID_FILES)) {
th_rmtree(repo);
FAIL("failed to write the edge-id fixture");
}
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/ids.db", repo);
char *old_workers = getenv("CBM_WORKERS");
char *saved_workers = old_workers ? strdup(old_workers) : NULL;
cbm_setenv("CBM_WORKERS", "4", 1);
id_sync_probe_t syncs = {.push = true};
edge_id_probe_t probe = {0};
cbm_pipeline_set_id_sync_hook_for_tests(id_sync_hook, &syncs);
cbm_pipeline_t *p = cbm_pipeline_new(repo, db_path, CBM_MODE_FULL);
int rc = CBM_NOT_FOUND;
if (p) {
cbm_pipeline_set_before_dump_hook_for_tests(p, probe_edge_ids, &probe);
rc = cbm_pipeline_run(p);
cbm_pipeline_free(p);
}
cbm_pipeline_set_id_sync_hook_for_tests(NULL, NULL);
if (saved_workers) {
cbm_setenv("CBM_WORKERS", saved_workers, 1);
free(saved_workers);
} else {
cbm_unsetenv("CBM_WORKERS");
}
th_rmtree(repo);
ASSERT_EQ(rc, 0);
/* Both full-route syncs (after extract, after resolve) go through the helper. */
ASSERT_EQ(syncs.syncs, 2);
ASSERT_GT(probe.edges, ID_FILES);
ASSERT_EQ(probe.duplicates, 0);
PASS();
}
/* An input change now rebuilds the resolver generation. Keep enough files to
* enter the parallel phases and require both ID syncs through the helper. */
TEST(parallel_closure_repair_syncs_ids_through_the_helper) {
char repo[256];
snprintf(repo, sizeof(repo), "/tmp/cbm_par_ids_XXXXXX");
ASSERT_NOT_NULL(cbm_mkdtemp(repo));
if (!write_id_fixture(repo, ID_FILES)) {
th_rmtree(repo);
FAIL("failed to write the edge-id fixture");
}
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/ids.db", repo);
cbm_pipeline_incremental_test_reset_faults();
cbm_pipeline_t *first = cbm_pipeline_new(repo, db_path, CBM_MODE_FULL);
int first_rc = first ? cbm_pipeline_run(first) : CBM_NOT_FOUND;
cbm_pipeline_free(first);
/* A body-only edit still changes the semantic input generation. */
char path[512];
snprintf(path, sizeof(path), "%s/f01.go", repo);
int write_rc =
th_write_file(path, "package main\n\nfunc F01() int {\n\treturn F02() + 40 + 2\n}\n");
const char *old_workers = getenv("CBM_WORKERS");
char *saved_workers = old_workers ? strdup(old_workers) : NULL;
cbm_setenv("CBM_WORKERS", "4", 1);
id_sync_probe_t syncs = {.push = false};
edge_id_probe_t probe = {0};
cbm_pipeline_set_id_sync_hook_for_tests(id_sync_hook, &syncs);
cbm_pipeline_incremental_test_reset_faults();
cbm_pipeline_t *second = cbm_pipeline_new(repo, db_path, CBM_MODE_FULL);
if (second) {
cbm_pipeline_set_before_dump_hook_for_tests(second, probe_edge_ids, &probe);
}
int second_rc = second ? cbm_pipeline_run(second) : CBM_NOT_FOUND;
cbm_incremental_route_t route = cbm_pipeline_incremental_test_last_route();
cbm_pipeline_free(second);
cbm_pipeline_set_id_sync_hook_for_tests(NULL, NULL);
if (saved_workers) {
cbm_setenv("CBM_WORKERS", saved_workers, 1);
free(saved_workers);
} else {
cbm_unsetenv("CBM_WORKERS");
}
th_rmtree(repo);
ASSERT_EQ(first_rc, 0);
ASSERT_EQ(write_rc, 0);
ASSERT_EQ(second_rc, 0);
ASSERT_EQ(route, CBM_INCREMENTAL_ROUTE_FORCED_FULL);
ASSERT_EQ(syncs.syncs, 2);
ASSERT_GT(probe.edges, ID_FILES);
ASSERT_EQ(probe.duplicates, 0);
PASS();
}
/* Legacy semantic results must reach the shared proof gate in both pipeline
* modes. The tests below require the exact source-proved receiver target and
* the public shared_resolver strategy, never a legacy confidence score. */
typedef struct {
int proof_strategy_count;
int total_calls;
} lsp_edge_count_ctx_t;
static void count_lsp_call_edges(const cbm_gbuf_edge_t *edge, void *ud) {
lsp_edge_count_ctx_t *c = ud;
if (!edge || !edge->type || strcmp(edge->type, "CALLS") != 0) {
return;
}
c->total_calls++;
if (edge->properties_json &&
strstr(edge->properties_json, "\"strategy\":\"shared_resolver\"")) {
c->proof_strategy_count++;
}
}
static const char *class_method_tail(const char *qn) {
if (!qn) {
return NULL;
}
const char *last = strrchr(qn, '.');
if (!last || last == qn) {
return NULL;
}
const char *second = last;
while (second > qn) {
second--;
if (*second == '.') {
return second == qn ? qn : second + 1;
}
}
return qn;
}
static const cbm_gbuf_node_t *find_unique_callable_node_by_tail(const cbm_gbuf_t *gbuf,
const char *tail) {
const char *method = tail ? strrchr(tail, '.') : NULL;
method = method ? method + 1 : tail;
if (!gbuf || !tail || !method) {
return NULL;
}
const cbm_gbuf_node_t **nodes = NULL;
int count = 0;
if (cbm_gbuf_find_by_name(gbuf, method, &nodes, &count) != 0) {
return NULL;
}
const cbm_gbuf_node_t *match = NULL;
for (int i = 0; i < count; i++) {
const cbm_gbuf_node_t *node = nodes[i];
if (!node || !node->label || !node->qualified_name) {
continue;
}
if (strcmp(node->label, "Method") != 0 && strcmp(node->label, "Function") != 0) {
continue;
}
const char *node_tail = class_method_tail(node->qualified_name);
if (!node_tail || strcmp(node_tail, tail) != 0) {
continue;
}
if (match) {
return NULL;
}
match = node;
}
return match;
}
static bool qn_has_segment_suffix(const char *qualified_name, const char *suffix) {
if (!qualified_name || !suffix) {
return false;
}
size_t qualified_len = strlen(qualified_name);
size_t suffix_len = strlen(suffix);
if (suffix_len > qualified_len ||
strcmp(qualified_name + qualified_len - suffix_len, suffix) != 0) {
return false;
}
return qualified_len == suffix_len || qualified_name[qualified_len - suffix_len - 1] == '.';
}
static const cbm_gbuf_node_t *find_unique_node_by_name_label_qn_suffix(const cbm_gbuf_t *gbuf,
const char *name,
const char *label,
const char *qn_suffix) {
if (!gbuf || !name || !label || !qn_suffix) {
return NULL;
}
const cbm_gbuf_node_t **nodes = NULL;
int count = 0;
if (cbm_gbuf_find_by_name(gbuf, name, &nodes, &count) != 0) {
return NULL;
}
const cbm_gbuf_node_t *match = NULL;
for (int i = 0; i < count; i++) {
const cbm_gbuf_node_t *node = nodes[i];
if (!node || !node->label || !node->qualified_name || strcmp(node->label, label) != 0 ||
!qn_has_segment_suffix(node->qualified_name, qn_suffix)) {
continue;
}
if (match) {
return NULL;
}
match = node;
}
return match;
}
static int count_edges_between_nodes(const cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source,
const cbm_gbuf_node_t *target, const char *edge_type) {
if (!gbuf || !source || !target || !edge_type) {
return -1;
}
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gbuf, source->id, edge_type, &edges, &count) != 0) {
return -1;
}
int matches = 0;
for (int i = 0; i < count; i++) {
if (edges[i] && edges[i]->target_id == target->id) {
matches++;
}
}
return matches;
}
static bool has_edge_from_callable_to_node(const cbm_gbuf_t *gbuf, const char *source_tail,
const cbm_gbuf_node_t *target, const char *edge_type) {
const cbm_gbuf_node_t *source = find_unique_callable_node_by_tail(gbuf, source_tail);
if (!source || !target || !edge_type) {
return false;
}
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gbuf, source->id, edge_type, &edges, &count) != 0) {
return false;
}
for (int i = 0; i < count; i++) {
if (edges[i] && edges[i]->target_id == target->id) {
return true;
}
}
return false;
}
static const cbm_gbuf_edge_t *find_calls_edge_by_tails(const cbm_gbuf_t *gbuf,
const char *source_tail,
const char *target_tail) {
const cbm_gbuf_node_t *source = find_unique_callable_node_by_tail(gbuf, source_tail);
const cbm_gbuf_node_t *target = find_unique_callable_node_by_tail(gbuf, target_tail);
if (!source || !target) {
return NULL;
}
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gbuf, source->id, "CALLS", &edges, &count) != 0) {
return NULL;
}
for (int i = 0; i < count; i++) {
if (edges[i] && edges[i]->target_id == target->id) {
return edges[i];
}
}
return NULL;
}
static bool has_edge_from_callable_to_qn(const cbm_gbuf_t *gbuf, const char *source_tail,
const char *target_qn, const char *edge_type) {
const cbm_gbuf_node_t *source = find_unique_callable_node_by_tail(gbuf, source_tail);
const cbm_gbuf_node_t *target = cbm_gbuf_find_by_qn(gbuf, target_qn);
if (!source || !target)
return false;
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gbuf, source->id, edge_type, &edges, &count) != 0)
return false;
for (int i = 0; i < count; i++) {
if (edges[i] && edges[i]->target_id == target->id)
return true;
}
return false;
}
static bool callable_has_call_target_fragment(const cbm_gbuf_t *gbuf, const char *source_tail,
const char *target_fragment) {
const cbm_gbuf_node_t *source = find_unique_callable_node_by_tail(gbuf, source_tail);
if (!source || !target_fragment)
return false;
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gbuf, source->id, "CALLS", &edges, &count) != 0)
return false;
for (int i = 0; i < count; i++) {
const cbm_gbuf_node_t *target =
edges[i] ? cbm_gbuf_find_by_id(gbuf, edges[i]->target_id) : NULL;
if (target && target->qualified_name && strstr(target->qualified_name, target_fragment))
return true;
}
return false;
}
static const cbm_gbuf_edge_t *find_call_edge_to_target_fragment(const cbm_gbuf_t *gbuf,
const char *source_tail,
const char *target_fragment) {
const cbm_gbuf_node_t *source = find_unique_callable_node_by_tail(gbuf, source_tail);
if (!source || !target_fragment)
return NULL;
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gbuf, source->id, "CALLS", &edges, &count) != 0)
return NULL;
for (int i = 0; i < count; i++) {
const cbm_gbuf_node_t *target =
edges[i] ? cbm_gbuf_find_by_id(gbuf, edges[i]->target_id) : NULL;
if (target && target->qualified_name && strstr(target->qualified_name, target_fragment))
return edges[i];
}
return NULL;
}
static int count_calls_edges_to_tail(const cbm_gbuf_t *gbuf, const char *target_tail) {
const cbm_gbuf_node_t *target = find_unique_callable_node_by_tail(gbuf, target_tail);
if (!target)
return -1;
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_target_type(gbuf, target->id, "CALLS", &edges, &count) != 0)
return -1;
return count;
}
typedef struct {
bool injected;
} lsp_legacy_injection_t;
/* Test-only cache mutation: preserve the real exact Alpha.render semantic
* record, then append a higher-confidence legacy 0:0 record for Beta.render.
* The shared linear matcher and the compact parallel index must both prefer
* the exact occurrence. */
static void inject_higher_confidence_legacy_render(CBMFileResult **result_cache, int file_count,
void *ud) {
lsp_legacy_injection_t *state = (lsp_legacy_injection_t *)ud;
if (!state) {
return;
}
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache ? result_cache[file] : NULL;
if (!result) {
continue;
}
const CBMResolvedCall *exact = NULL;
const char *beta_qn = NULL;
for (int i = 0; i < result->resolved_calls.count; i++) {
const CBMResolvedCall *candidate = &result->resolved_calls.items[i];
if (candidate->kind == CBM_RESOLVED_INVOCATION && candidate->caller_qn &&
strstr(candidate->caller_qn, "Caller.run") && candidate->callee_qn &&
strstr(candidate->callee_qn, "Alpha.render") &&
candidate->site_end_byte > candidate->site_start_byte) {
exact = candidate;
break;
}
}
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *definition = &result->defs.items[i];
if (definition->qualified_name && strstr(definition->qualified_name, "Beta.render")) {
beta_qn = definition->qualified_name;
break;
}
}
if (!exact || !beta_qn) {
continue;
}
CBMResolvedCall legacy = *exact;
legacy.callee_qn = beta_qn;
legacy.strategy = "test_legacy_zero_span";
legacy.confidence = 0.99f;
legacy.site_start_byte = 0;
legacy.site_end_byte = 0;
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, legacy);
state->injected = true;
return;
}
}
TEST(parallel_lsp_index_exact_site_beats_legacy_record_in_graph) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_lsp_site_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/app.py", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
fprintf(file, "class Alpha:\n"
" def render(self):\n"
" return 'alpha'\n"
"\n"
"class Beta:\n"
" def render(self):\n"
" return 'beta'\n"
"\n"
"class Caller:\n"
" def run(self):\n"
" value = Alpha()\n"
" return value.render()\n");
fclose(file);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"app.py";
files[0].language = CBM_LANG_PYTHON;
lsp_legacy_injection_t injection = {0};
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"cbm_par_lsp_site", tmpdir, files, 1, 1, NULL, inject_higher_confidence_legacy_render,
&injection, false);
ASSERT_NOT_NULL(gbuf);
ASSERT_TRUE(injection.injected);
const cbm_gbuf_edge_t *exact = find_calls_edge_by_tails(gbuf, "Caller.run", "Alpha.render");
const cbm_gbuf_edge_t *wrong = find_calls_edge_by_tails(gbuf, "Caller.run", "Beta.render");
if (!exact || wrong) {
printf(" lsp index occurrence diagnostic: exact=%s legacy_wrong=%s\n",
exact ? "present" : "absent", wrong ? "present" : "absent");
}
ASSERT_NOT_NULL(exact);
ASSERT_NULL(wrong);
cbm_gbuf_free(gbuf);
unlink(path);
rmdir(tmpdir);
PASS();
}
typedef struct {
bool injected;
} lsp_exact_ambiguity_injection_t;
/* Append a distinct semantic target at the same exact source occurrence and
* make the carrier semantic-only. Both the shared matcher and the compact
* parallel index must fail closed; neither confidence nor textual registry
* fallback may select one target. */
static void inject_distinct_exact_render_target(CBMFileResult **result_cache, int file_count,
void *ud) {
lsp_exact_ambiguity_injection_t *state = (lsp_exact_ambiguity_injection_t *)ud;
if (!state) {
return;
}
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache ? result_cache[file] : NULL;
if (!result) {
continue;
}
CBMCall *carrier = NULL;
const CBMResolvedCall *exact = NULL;
const char *beta_qn = NULL;
for (int i = 0; i < result->calls.count; i++) {
CBMCall *candidate = &result->calls.items[i];
if (candidate->callee_name && candidate->enclosing_func_qn &&
strstr(candidate->enclosing_func_qn, "Caller.run") &&
strcmp(cbm_pipeline_call_callee_leaf(candidate->callee_name), "render") == 0 &&
cbm_pipeline_source_site_present(candidate->site_start_byte,
candidate->site_end_byte)) {
carrier = candidate;
break;
}
}
for (int i = 0; i < result->resolved_calls.count; i++) {
const CBMResolvedCall *candidate = &result->resolved_calls.items[i];
if (candidate->kind == CBM_RESOLVED_INVOCATION && candidate->caller_qn &&
strstr(candidate->caller_qn, "Caller.run") && candidate->callee_qn &&
strstr(candidate->callee_qn, "Alpha.render") &&
cbm_pipeline_source_site_present(candidate->site_start_byte,
candidate->site_end_byte)) {
exact = candidate;
break;
}
}
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *definition = &result->defs.items[i];
if (definition->qualified_name && strstr(definition->qualified_name, "Beta.render")) {
beta_qn = definition->qualified_name;
break;
}
}
if (!carrier || !exact || !beta_qn ||
!cbm_pipeline_source_site_eq(carrier->site_start_byte, carrier->site_end_byte,
exact->site_start_byte, exact->site_end_byte)) {
continue;
}
carrier->requires_lsp_resolution = true;
CBMResolvedCall ambiguous = *exact;
ambiguous.callee_qn = beta_qn;
ambiguous.strategy = "test_distinct_exact_target";
ambiguous.confidence = 0.99f;
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, ambiguous);
state->injected = true;
return;
}
}
TEST(parallel_lsp_index_distinct_exact_targets_fail_closed_in_graph) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_lsp_ambiguous_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/app.py", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
fprintf(file, "class Alpha:\n"
" def render(self): return 'alpha'\n"
"class Beta:\n"
" def render(self): return 'beta'\n"
"class Caller:\n"
" def run(self):\n"
" value = Alpha()\n"
" return value.render()\n");
fclose(file);
cbm_file_info_t file_info = {0};
file_info.path = path;
file_info.rel_path = (char *)"app.py";
file_info.language = CBM_LANG_PYTHON;
lsp_exact_ambiguity_injection_t injection = {0};
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"cbm_par_lsp_ambiguous", tmpdir, &file_info, 1, 1, NULL,
inject_distinct_exact_render_target, &injection, false);
ASSERT_NOT_NULL(gbuf);
ASSERT_TRUE(injection.injected);
ASSERT_NOT_NULL(find_unique_callable_node_by_tail(gbuf, "Alpha.render"));
ASSERT_NOT_NULL(find_unique_callable_node_by_tail(gbuf, "Beta.render"));
const cbm_gbuf_edge_t *alpha = find_calls_edge_by_tails(gbuf, "Caller.run", "Alpha.render");
const cbm_gbuf_edge_t *beta = find_calls_edge_by_tails(gbuf, "Caller.run", "Beta.render");
if (!alpha || beta) {
printf(" lsp exact ambiguity diagnostic: alpha=%s beta=%s\n", alpha ? "present" : "absent",
beta ? "present" : "absent");
}
/* The injected row has no proof. The original source independently proves
* Alpha; an untrusted competing row must neither bind Beta nor veto Alpha. */
ASSERT_NOT_NULL(alpha);
ASSERT_NULL(beta);
cbm_gbuf_free(gbuf);
unlink(path);
rmdir(tmpdir);
PASS();
}
typedef struct {
lsp_exact_ambiguity_injection_t ambiguity;
bool legacy_injected;
bool carrier_allows_fallback;
bool shared_matcher_failed_closed;
} lsp_exact_ambiguity_legacy_probe_t;
/* Reuse the exact-ambiguity injection above, then add a unique 0:0 row while
* leaving the carrier eligible for legacy resolution. The authoritative
* matcher must keep the exact-site ambiguity authoritative; the compact
* parallel index must not reinterpret its tombstone as "try legacy". */
static void inject_legacy_after_exact_render_ambiguity(CBMFileResult **result_cache, int file_count,
void *ud) {
lsp_exact_ambiguity_legacy_probe_t *probe = (lsp_exact_ambiguity_legacy_probe_t *)ud;
if (!probe) {
return;
}
inject_distinct_exact_render_target(result_cache, file_count, &probe->ambiguity);
if (!probe->ambiguity.injected) {
return;
}
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache ? result_cache[file] : NULL;
if (!result) {
continue;
}
CBMCall *carrier = NULL;
const CBMResolvedCall *exact = NULL;
const char *legacy_qn = NULL;
for (int i = 0; i < result->calls.count; i++) {
CBMCall *candidate = &result->calls.items[i];
if (candidate->callee_name && candidate->enclosing_func_qn &&
strstr(candidate->enclosing_func_qn, "Caller.run") &&
strcmp(cbm_pipeline_call_callee_leaf(candidate->callee_name), "render") == 0 &&
cbm_pipeline_source_site_present(candidate->site_start_byte,
candidate->site_end_byte)) {
carrier = candidate;
break;
}
}
for (int i = 0; i < result->resolved_calls.count; i++) {
const CBMResolvedCall *candidate = &result->resolved_calls.items[i];
if (candidate->kind == CBM_RESOLVED_INVOCATION && candidate->caller_qn &&
strstr(candidate->caller_qn, "Caller.run") && candidate->callee_qn &&
strstr(candidate->callee_qn, "Alpha.render") &&
cbm_pipeline_source_site_present(candidate->site_start_byte,
candidate->site_end_byte)) {
exact = candidate;
break;
}
}
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *definition = &result->defs.items[i];
if (definition->qualified_name && strstr(definition->qualified_name, "Legacy.render")) {
legacy_qn = definition->qualified_name;
break;
}
}
if (!carrier || !exact || !legacy_qn ||
!cbm_pipeline_source_site_eq(carrier->site_start_byte, carrier->site_end_byte,
exact->site_start_byte, exact->site_end_byte)) {
continue;
}
carrier->requires_lsp_resolution = false;
CBMResolvedCall legacy = *exact;
legacy.callee_qn = legacy_qn;
legacy.strategy = "test_legacy_after_exact_ambiguity";
legacy.confidence = 0.99f;
legacy.site_start_byte = 0;
legacy.site_end_byte = 0;
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, legacy);
probe->legacy_injected = true;
probe->carrier_allows_fallback = !carrier->requires_lsp_resolution;
probe->shared_matcher_failed_closed =
cbm_pipeline_find_lsp_resolution(&result->resolved_calls, carrier, false) == NULL;
return;
}
}
TEST(parallel_lsp_index_exact_ambiguity_does_not_fall_through_to_legacy) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_lsp_ambiguous_legacy_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/app.py", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
fprintf(file, "class Alpha:\n"
" def render(self): return 'alpha'\n"
"class Beta:\n"
" def render(self): return 'beta'\n"
"class Legacy:\n"
" def render(self): return 'legacy'\n"
"class Caller:\n"
" def run(self):\n"
" value = Alpha()\n"
" return value.render()\n");
fclose(file);
cbm_file_info_t file_info = {0};
file_info.path = path;
file_info.rel_path = (char *)"app.py";
file_info.language = CBM_LANG_PYTHON;
lsp_exact_ambiguity_legacy_probe_t probe = {0};
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"cbm_par_lsp_ambiguous_legacy", tmpdir, &file_info, 1, 1, NULL,
inject_legacy_after_exact_render_ambiguity, &probe, false);
ASSERT_NOT_NULL(gbuf);
int alpha_edges = count_calls_edges_to_tail(gbuf, "Alpha.render");
int beta_edges = count_calls_edges_to_tail(gbuf, "Beta.render");
int legacy_edges = count_calls_edges_to_tail(gbuf, "Legacy.render");
const cbm_gbuf_edge_t *legacy_edge =
find_calls_edge_by_tails(gbuf, "Caller.run", "Legacy.render");
if (!probe.ambiguity.injected || !probe.legacy_injected || !probe.carrier_allows_fallback ||
!probe.shared_matcher_failed_closed || alpha_edges != 1 || beta_edges != 0 ||
legacy_edges != 0) {
printf(" exact ambiguity + legacy diagnostic: exact_ambiguity=%d legacy=%d "
"fallback_allowed=%d shared_failed_closed=%d alpha=%d beta=%d legacy_edge=%d "
"props=%s\n",
probe.ambiguity.injected, probe.legacy_injected, probe.carrier_allows_fallback,
probe.shared_matcher_failed_closed, alpha_edges, beta_edges, legacy_edges,
legacy_edge && legacy_edge->properties_json ? legacy_edge->properties_json : "");
}
cbm_gbuf_free(gbuf);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(probe.ambiguity.injected);
ASSERT_TRUE(probe.legacy_injected);
ASSERT_TRUE(probe.carrier_allows_fallback);
ASSERT_TRUE(probe.shared_matcher_failed_closed);
/* Legacy matcher ambiguity is still tested above. The shared source proof
* independently retains Alpha and rejects both unattested injected targets. */
ASSERT_EQ(alpha_edges, 1);
ASSERT_EQ(beta_edges, 0);
ASSERT_EQ(legacy_edges, 0);
PASS();
}
typedef struct {
bool injected;
bool authoritative_exact_wins;
bool legacy_key_fits;
bool exact_key_overflows;
} lsp_long_key_probe_t;
static void inject_long_caller_exact_and_legacy(CBMFileResult **result_cache, int file_count,
void *ud) {
lsp_long_key_probe_t *probe = (lsp_long_key_probe_t *)ud;
if (!probe)
return;
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache ? result_cache[file] : NULL;
if (!result)
continue;
CBMCall *carrier = NULL;
CBMResolvedCall *exact = NULL;
const char *beta_qn = NULL;
for (int i = 0; i < result->calls.count; i++) {
CBMCall *candidate = &result->calls.items[i];
if (candidate->callee_name &&
strcmp(cbm_pipeline_call_callee_leaf(candidate->callee_name), "render") == 0) {
carrier = candidate;
break;
}
}
for (int i = 0; i < result->resolved_calls.count; i++) {
CBMResolvedCall *candidate = &result->resolved_calls.items[i];
if (candidate->kind == CBM_RESOLVED_INVOCATION && candidate->callee_qn &&
strstr(candidate->callee_qn, "Alpha.render") &&
candidate->site_end_byte > candidate->site_start_byte) {
exact = candidate;
break;
}
}
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *definition = &result->defs.items[i];
if (definition->qualified_name && strstr(definition->qualified_name, "Beta.render")) {
beta_qn = definition->qualified_name;
break;
}
}
if (!carrier || !exact || !beta_qn)
continue;
CBMResolvedCall legacy = *exact;
legacy.callee_qn = beta_qn;
legacy.strategy = "test_long_key_legacy";
legacy.confidence = 0.99f;
legacy.site_start_byte = 0;
legacy.site_end_byte = 0;
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, legacy);
size_t leaf_len = strlen("render");
size_t legacy_len = strlen(carrier->enclosing_func_qn) + 1U + leaf_len;
char site_suffix[64];
int suffix_len = snprintf(site_suffix, sizeof(site_suffix), "|%u:%u",
carrier->site_start_byte, carrier->site_end_byte);
size_t exact_len = legacy_len + (suffix_len > 0 ? (size_t)suffix_len : 0U);
probe->legacy_key_fits = legacy_len < 1024U;
probe->exact_key_overflows = exact_len >= 1024U;
const CBMResolvedCall *authoritative =
cbm_pipeline_find_lsp_resolution(&result->resolved_calls, carrier, false);
probe->authoritative_exact_wins = authoritative && authoritative->callee_qn &&
strstr(authoritative->callee_qn, "Alpha.render") != NULL;
probe->injected = true;
return;
}
}
TEST(parallel_lsp_long_exact_key_never_yields_legacy_target) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_lsp_long_key_XXXXXX");
if (!cbm_mkdtemp(tmpdir))
FAIL("mkdtemp failed");
char path[512];
snprintf(path, sizeof(path), "%s/app.py", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
enum { FUNCTION_NAME_LEN = 997 };
char function_name[FUNCTION_NAME_LEN + 1];
memcpy(function_name, "run_", 4);
memset(function_name + 4, 'q', FUNCTION_NAME_LEN - 4);
function_name[FUNCTION_NAME_LEN] = '\0';
fputs("class Alpha:\n"
" def render(self):\n"
" return 'alpha'\n"
"class Beta:\n"
" def render(self):\n"
" return 'beta'\n",
file);
fprintf(file, "def %s(value: Alpha):\n return value.render()\n", function_name);
fclose(file);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"app.py";
files[0].language = CBM_LANG_PYTHON;
lsp_long_key_probe_t probe = {0};
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"cbm_long_key", tmpdir, files, 1, 1, NULL, inject_long_caller_exact_and_legacy, &probe,
false);
ASSERT_NOT_NULL(gbuf);
int alpha_edges = count_calls_edges_to_tail(gbuf, "Alpha.render");
int beta_edges = count_calls_edges_to_tail(gbuf, "Beta.render");
if (!probe.injected || !probe.authoritative_exact_wins || !probe.legacy_key_fits ||
!probe.exact_key_overflows || alpha_edges < 1 || beta_edges != 0) {
printf(" long exact-key diagnostic: injected=%d authoritative=%d legacy_fits=%d "
"exact_overflow=%d alpha=%d beta=%d\n",
probe.injected, probe.authoritative_exact_wins, probe.legacy_key_fits,
probe.exact_key_overflows, alpha_edges, beta_edges);
}
cbm_gbuf_free(gbuf);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(probe.injected);
ASSERT_TRUE(probe.authoritative_exact_wins);
ASSERT_TRUE(probe.legacy_key_fits);
ASSERT_TRUE(probe.exact_key_overflows);
ASSERT_GTE(alpha_edges, 1);
ASSERT_EQ(beta_edges, 0);
PASS();
}
typedef struct {
int carrier_count;
int exact_match_count;
} lsp_synthetic_index_probe_t;
static void inspect_synthetic_add_occurrences(CBMFileResult **result_cache, int file_count,
void *ud) {
lsp_synthetic_index_probe_t *probe = (lsp_synthetic_index_probe_t *)ud;
if (!probe) {
return;
}
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache[file];
if (!result) {
continue;
}
for (int i = 0; i < result->calls.count; i++) {
const CBMCall *call = &result->calls.items[i];
if (!call->requires_lsp_resolution || !call->callee_name ||
strcmp(call->callee_name, "__add__") != 0 ||
!cbm_pipeline_source_site_present(call->site_start_byte, call->site_end_byte)) {
continue;
}
probe->carrier_count++;
const CBMResolvedCall *resolved =
cbm_pipeline_find_lsp_resolution(&result->resolved_calls, call, false);
if (resolved &&
cbm_pipeline_source_site_eq(call->site_start_byte, call->site_end_byte,
resolved->site_start_byte, resolved->site_end_byte)) {
probe->exact_match_count++;
}
}
}
}
TEST(parallel_lsp_exact_index_handles_repeated_synthetic_occurrences_without_linear_scan) {
enum { OCCURRENCES = 128 };
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_lsp_perf_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/app.py", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
fprintf(file, "class Number:\n"
" def __add__(self, other):\n"
" return self\n"
"\n"
"def combine(left: Number, right: Number):\n");
for (int i = 0; i < OCCURRENCES; i++) {
fprintf(file, " value = left + right\n");
}
fprintf(file, " return value\ndef stable(): return 7\ndef direct(): return stable()\n");
fclose(file);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"app.py";
files[0].language = CBM_LANG_PYTHON;
lsp_synthetic_index_probe_t probe = {0};
cbm_pp_lsp_linear_fallback_rows_reset();
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"cbm_par_lsp_perf", tmpdir, files, 1, 1, NULL, inspect_synthetic_add_occurrences, &probe,
false);
ASSERT_NOT_NULL(gbuf);
ASSERT_EQ(probe.carrier_count, OCCURRENCES);
ASSERT_EQ(probe.exact_match_count, OCCURRENCES);
char *source = parallel_fixture_source(path);
ASSERT_NOT_NULL(source);
RQ_Source input = {.path = "app.py", .text = source};
RQ_Expected expected[OCCURRENCES + 1] = {0};
char ids[OCCURRENCES][32];
for (int i = 0; i < OCCURRENCES; i++) {
snprintf(ids[i], sizeof(ids[i]), "python-add-%d", i);
expected[i] = (RQ_Expected){
.case_id = ids[i],
.site = {.file = "app.py",
.caller = "app.combine",
.needle = "left + right",
.occurrence_index = (unsigned)i},
.relation = "CALLS",
.target = {.file = "app.py",
.suffix = "app.Number.__add__",
.label = "Method",
.declaration_line = 2,
.declaration_text = " def __add__(self, other):"},
.mode = RQ_KNOWN_GAP,
.gap_id = "python-implicit-operator-dispatch",
.why = "Python implicit operator applicability is not certified by the proof bridge.",
.tried = "All 128 exact legacy carriers and matches remain asserted; each original "
"operator occurrence is counted independently and a direct call is required."};
}
expected[OCCURRENCES] = (RQ_Expected){.case_id = "python-direct-control",
.site = {.file = "app.py",
.caller = "app.direct",
.needle = "stable()",
.occurrence_index = 1},
.relation = "CALLS",
.target = {.file = "app.py",
.suffix = "app.stable",
.label = "Function",
.declaration_line = OCCURRENCES + 7,
.declaration_text = "def stable(): return 7"},
.mode = RQ_REQUIRED};
RQ_Report quality = {0};
ASSERT(parallel_quality_check(gbuf, "cbm_par_lsp_perf", "python-operator-scale", &input, 1,
expected, OCCURRENCES + 1, &quality));
ASSERT_EQ(quality.genuine, OCCURRENCES + 1);
ASSERT_EQ(quality.found, 1);
ASSERT_EQ(quality.missing, OCCURRENCES);
free(source);
uint64_t fallback_rows = cbm_pp_lsp_linear_fallback_rows();
if (fallback_rows != 0) {
printf(" synthetic exact-index diagnostic: occurrences=%d linear_rows=%llu\n", OCCURRENCES,
(unsigned long long)fallback_rows);
}
ASSERT_EQ(fallback_rows, 0);
cbm_gbuf_free(gbuf);
unlink(path);
rmdir(tmpdir);
PASS();
}
#if defined(CBM_CALL_REFERENCE_LOOKUP_TEST_API) && CBM_CALL_REFERENCE_LOOKUP_TEST_API
enum { CALL_REFERENCE_SCALE_OCCURRENCES = 128 };
typedef struct {
bool injected;
int reference_count;
} call_reference_scale_probe_t;
/* Supply 128 real target names at fabricated off-source spans. The legacy
* site index must remain linear, but those rows cannot authorize edges. The
* test separately requires all 128 references from actual source arguments. */
static void inject_call_reference_scale_rows(CBMFileResult **result_cache, int file_count,
void *ud) {
call_reference_scale_probe_t *probe = (call_reference_scale_probe_t *)ud;
if (!probe || !result_cache) {
return;
}
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache[file];
if (!result) {
continue;
}
const char *caller_qn = NULL;
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *definition = &result->defs.items[i];
if (definition->name && strcmp(definition->name, "scale_caller") == 0) {
caller_qn = definition->qualified_name;
break;
}
}
if (!caller_qn) {
continue;
}
result->usages.count = 0;
result->resolved_calls.count = 0;
int inserted = 0;
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *target = &result->defs.items[i];
if (!target->name || !target->qualified_name ||
strncmp(target->name, "scale_target_", strlen("scale_target_")) != 0) {
continue;
}
uint32_t start = 10000U + (uint32_t)inserted * 16U;
CBMUsage usage = {0};
usage.ref_name = target->name;
usage.enclosing_func_qn = caller_qn;
usage.kind = CBM_USAGE_CALL_REFERENCE;
usage.may_be_call_reference = true;
/* Exact occurrence proof must win even when lexical evidence
* forbids the later raw-name fallback. */
usage.semantic_reference_blocked = true;
usage.semantic_reference_local_shadow = true;
usage.site_start_byte = start;
usage.site_end_byte = start + (uint32_t)strlen(target->name);
cbm_usages_push(&result->usages, &result->arena, usage);
CBMResolvedCall resolved = {0};
resolved.caller_qn = caller_qn;
resolved.callee_qn = target->qualified_name;
resolved.strategy = "test_exact_reference_scale";
resolved.confidence = 0.95f;
resolved.kind = CBM_RESOLVED_CALL_REFERENCE;
resolved.site_start_byte = usage.site_start_byte;
resolved.site_end_byte = usage.site_end_byte;
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, resolved);
inserted++;
}
probe->reference_count = inserted;
probe->injected = inserted == CALL_REFERENCE_SCALE_OCCURRENCES;
return;
}
}
TEST(parallel_call_reference_lookup_rows_grow_linearly) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_ref_scale_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/app.py", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
for (int i = 0; i < CALL_REFERENCE_SCALE_OCCURRENCES; i++) {
fprintf(file, "def scale_target_%03d():\n return %d\n", i, i);
}
fputs("def consume(callback): return None\ndef scale_caller():\n", file);
for (int i = 0; i < CALL_REFERENCE_SCALE_OCCURRENCES; i++)
fprintf(file, " consume(scale_target_%03d)\n", i);
fclose(file);
cbm_file_info_t file_info = {0};
file_info.path = path;
file_info.rel_path = (char *)"app.py";
file_info.language = CBM_LANG_PYTHON;
call_reference_scale_probe_t probe = {0};
cbm_pipeline_lsp_reference_lookup_test_reset();
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"cbm_par_ref_scale", tmpdir, &file_info, 1, 1, NULL, inject_call_reference_scale_rows,
&probe, false);
uint64_t rows_examined = cbm_pipeline_lsp_reference_lookup_test_rows_examined();
cbm_pipeline_lsp_reference_lookup_test_reset();
cbm_gbuf_t *source_graph = run_parallel("cbm_par_ref_scale", tmpdir, &file_info, 1, 1);
uint64_t source_rows_examined = cbm_pipeline_lsp_reference_lookup_test_rows_examined();
const cbm_gbuf_node_t *source_caller =
source_graph ? find_unique_callable_node_by_tail(source_graph, "app.scale_caller") : NULL;
const cbm_gbuf_edge_t **source_edges = NULL;
int source_count = 0;
int source_rc =
source_caller
? cbm_gbuf_find_edges_by_source_type(source_graph, source_caller->id, "CALL_REFERENCE",
&source_edges, &source_count)
: -1;
int source_targets = 0;
for (int i = 0; source_rc == 0 && i < source_count; i++) {
const cbm_gbuf_node_t *target =
cbm_gbuf_find_by_id(source_graph, source_edges[i]->target_id);
if (target && target->name && !strncmp(target->name, "scale_target_", 13))
source_targets++;
}
const cbm_gbuf_node_t *caller =
gbuf ? find_unique_callable_node_by_tail(gbuf, "app.scale_caller") : NULL;
const cbm_gbuf_edge_t **edges = NULL;
int edge_count = 0;
int lookup_rc = caller ? cbm_gbuf_find_edges_by_source_type(gbuf, caller->id, "CALL_REFERENCE",
&edges, &edge_count)
: -1;
int exact_target_edges = 0;
for (int i = 0; lookup_rc == 0 && i < edge_count; i++) {
const cbm_gbuf_node_t *target =
edges[i] ? cbm_gbuf_find_by_id(gbuf, edges[i]->target_id) : NULL;
if (target && target->name &&
strncmp(target->name, "scale_target_", strlen("scale_target_")) == 0) {
exact_target_edges++;
}
}
uint64_t linear_budget = (uint64_t)CALL_REFERENCE_SCALE_OCCURRENCES * 8U;
if (!probe.injected || lookup_rc != 0 || edge_count != 0 || exact_target_edges != 0 ||
rows_examined > linear_budget) {
printf(" call-reference lookup scale diagnostic: injected=%d references=%d "
"edges=%d exact_targets=%d rows=%llu budget=%llu\n",
probe.injected, probe.reference_count, edge_count, exact_target_edges,
(unsigned long long)rows_examined, (unsigned long long)linear_budget);
}
cbm_gbuf_free(source_graph);
cbm_gbuf_free(gbuf);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(probe.injected);
ASSERT_EQ(probe.reference_count, CALL_REFERENCE_SCALE_OCCURRENCES);
ASSERT_EQ(lookup_rc, 0);
/* Off-source synthetic spans have no occurrence proof. Real source
* arguments retain all 128 references through the same public pipeline. */
ASSERT_EQ(edge_count, 0);
ASSERT_EQ(exact_target_edges, 0);
ASSERT_EQ(source_rc, 0);
ASSERT_EQ(source_count, CALL_REFERENCE_SCALE_OCCURRENCES);
ASSERT_EQ(source_targets, CALL_REFERENCE_SCALE_OCCURRENCES);
/* The current full scan examines N rows for every one of N usages:
* 128*128 = 16,384. A site index should keep this below a small linear
* allowance while retaining the exact target/ambiguity checks. */
ASSERT_LTE(rows_examined, linear_budget);
ASSERT_LTE(source_rows_examined, linear_budget);
PASS();
}
#endif
/* A lexical callable named like a module component owns the JSX occurrence.
* Keep a direct component call as a positive control, and require both graph
* builders to reject textual fallback for the shadowed tag. */
TEST(parallel_tsx_local_component_shadow_has_no_false_call) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_tsx_shadow_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/main.tsx", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen main.tsx failed");
}
fputs("function Card(): JSX.Element { return <div/>; }\n"
"function Good(): JSX.Element { return <Card/>; }\n"
"function Shadowed(Card: () => JSX.Element): JSX.Element { return <Card/>; }\n",
file);
fclose(file);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"main.tsx";
files[0].language = CBM_LANG_TSX;
cbm_gbuf_t *sequential = run_sequential("cbm_tsx_shadow", tmpdir, files, 1);
cbm_gbuf_t *parallel = run_parallel("cbm_tsx_shadow", tmpdir, files, 1, 1);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const bool sequential_nodes = find_unique_callable_node_by_tail(sequential, "main.Card") &&
find_unique_callable_node_by_tail(sequential, "main.Good") &&
find_unique_callable_node_by_tail(sequential, "main.Shadowed");
const bool parallel_nodes = find_unique_callable_node_by_tail(parallel, "main.Card") &&
find_unique_callable_node_by_tail(parallel, "main.Good") &&
find_unique_callable_node_by_tail(parallel, "main.Shadowed");
const bool sequential_good =
find_calls_edge_by_tails(sequential, "main.Good", "main.Card") != NULL;
const bool parallel_good = find_calls_edge_by_tails(parallel, "main.Good", "main.Card") != NULL;
const bool sequential_wrong =
find_calls_edge_by_tails(sequential, "main.Shadowed", "main.Card") != NULL;
const bool parallel_wrong =
find_calls_edge_by_tails(parallel, "main.Shadowed", "main.Card") != NULL;
if (!sequential_nodes || !parallel_nodes || !sequential_good || !parallel_good ||
sequential_wrong || parallel_wrong) {
printf(" TSX shadow graph diagnostic: seq_nodes=%d par_nodes=%d seq_good=%d "
"par_good=%d seq_wrong=%d par_wrong=%d\n",
sequential_nodes, parallel_nodes, sequential_good, parallel_good, sequential_wrong,
parallel_wrong);
}
char *source = parallel_fixture_source(path);
ASSERT_NOT_NULL(source);
RQ_Source input = {.path = "main.tsx", .text = source};
RQ_Expected expected = {
.case_id = "jsx-direct-component",
.site = {.file = "main.tsx",
.caller = "main.Good",
.needle = "<Card/>",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "main.tsx",
.suffix = "main.Card",
.label = "Function",
.declaration_line = 1,
.declaration_text = "function Card(): JSX.Element { return <div/>; }"}};
RQ_Report quality[2] = {0};
ASSERT(parallel_quality_check(sequential, "cbm_tsx_shadow", "jsx-shadow-sequential", &input, 1,
&expected, 1, &quality[0]));
ASSERT(parallel_quality_check(parallel, "cbm_tsx_shadow", "jsx-shadow-parallel", &input, 1,
&expected, 1, &quality[1]));
ASSERT_EQ(quality[0].genuine, 1);
ASSERT_EQ(quality[1].genuine, 1);
ASSERT_EQ(quality[0].missing, 0);
ASSERT_EQ(quality[1].missing, 0);
free(source);
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(sequential_nodes);
ASSERT_TRUE(parallel_nodes);
ASSERT_TRUE(sequential_good);
ASSERT_TRUE(parallel_good);
ASSERT_FALSE(sequential_wrong);
ASSERT_FALSE(parallel_wrong);
PASS();
}
typedef struct {
bool injected;
} exact_noncallable_reference_probe_t;
static void inject_exact_noncallable_reference(CBMFileResult **result_cache, int file_count,
void *ud) {
exact_noncallable_reference_probe_t *probe = (exact_noncallable_reference_probe_t *)ud;
if (!probe || !result_cache) {
return;
}
for (int file = 0; file < file_count; file++) {
CBMFileResult *result = result_cache[file];
if (!result) {
continue;
}
const char *caller_qn = NULL;
const char *target_qn = NULL;
for (int i = 0; i < result->defs.count; i++) {
const CBMDefinition *definition = &result->defs.items[i];
if (definition->name && definition->qualified_name &&
strcmp(definition->name, "carrier") == 0) {
caller_qn = definition->qualified_name;
}
if (definition->name && definition->qualified_name && definition->label &&
strcmp(definition->name, "semanticValue") == 0 &&
strcmp(definition->label, "Variable") == 0) {
target_qn = definition->qualified_name;
}
}
if (!caller_qn || !target_qn) {
continue;
}
result->usages.count = 0;
result->resolved_calls.count = 0;
CBMUsage usage = {0};
usage.ref_name = "semanticAlias";
usage.enclosing_func_qn = caller_qn;
usage.kind = CBM_USAGE_VALUE;
usage.may_be_call_reference = true;
/* The raw-name path is deliberately unavailable and this span is
* outside the source. Neither the target label nor the legacy strategy
* can turn an unattested row into a USAGE edge. */
usage.semantic_reference_blocked = true;
usage.site_start_byte = 40000U;
usage.site_end_byte = 40000U + (uint32_t)strlen(usage.ref_name);
cbm_usages_push(&result->usages, &result->arena, usage);
CBMResolvedCall resolved = {0};
resolved.caller_qn = caller_qn;
resolved.callee_qn = target_qn;
resolved.strategy = "lsp_callable_value_reference";
resolved.reason = usage.ref_name;
resolved.confidence = 0.99f;
resolved.kind = CBM_RESOLVED_CALL_REFERENCE;
resolved.site_start_byte = usage.site_start_byte;
resolved.site_end_byte = usage.site_end_byte;
cbm_resolvedcall_push(&result->resolved_calls, &result->arena, resolved);
probe->injected = true;
return;
}
}
TEST(parallel_exact_semantic_noncallable_target_stays_usage) {
static const char source[] = "const semanticValue = 1;\n"
"function consume(value: unknown): void {}\n"
"export function carrier(): void { consume(semanticValue); }\n";
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_exact_noncallable_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/app.ts", tmpdir);
if (th_write_file(path, source) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write exact non-callable fixture");
}
cbm_file_info_t file = {0};
file.path = path;
file.rel_path = (char *)"app.ts";
file.language = CBM_LANG_TYPESCRIPT;
exact_noncallable_reference_probe_t sequential_probe = {0};
exact_noncallable_reference_probe_t parallel_probe = {0};
cbm_gbuf_t *sequential = run_sequential_with_lsp_cross_and_mutator(
"cbm_exact_noncallable", tmpdir, &file, 1, inject_exact_noncallable_reference,
&sequential_probe, false);
cbm_gbuf_t *parallel = run_parallel_with_extract_opts_and_mutator(
"cbm_exact_noncallable", tmpdir, &file, 1, 1, NULL, inject_exact_noncallable_reference,
&parallel_probe, false);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const cbm_gbuf_node_t *sequential_target = find_unique_node_by_name_label_qn_suffix(
sequential, "semanticValue", "Variable", "app.semanticValue");
const cbm_gbuf_node_t *parallel_target = find_unique_node_by_name_label_qn_suffix(
parallel, "semanticValue", "Variable", "app.semanticValue");
const bool sequential_usage =
has_edge_from_callable_to_node(sequential, "app.carrier", sequential_target, "USAGE");
const bool parallel_usage =
has_edge_from_callable_to_node(parallel, "app.carrier", parallel_target, "USAGE");
const bool sequential_promoted =
has_edge_from_callable_to_node(sequential, "app.carrier", sequential_target,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(sequential, "app.carrier", sequential_target, "CALLS");
const bool parallel_promoted =
has_edge_from_callable_to_node(parallel, "app.carrier", parallel_target,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(parallel, "app.carrier", parallel_target, "CALLS");
cbm_gbuf_t *control[2] = {
run_sequential_with_lsp_cross_and_mutator("cbm_exact_noncallable", tmpdir, &file, 1, NULL,
NULL, false),
run_parallel_with_extract_opts_and_mutator("cbm_exact_noncallable", tmpdir, &file, 1, 1,
NULL, NULL, NULL, false)};
for (int i = 0; i < 2; i++) {
ASSERT_NOT_NULL(control[i]);
const cbm_gbuf_node_t *target = find_unique_node_by_name_label_qn_suffix(
control[i], "semanticValue", "Variable", "app.semanticValue");
ASSERT_NOT_NULL(target);
ASSERT_TRUE(has_edge_from_callable_to_node(control[i], "app.carrier", target, "USAGE"));
ASSERT_FALSE(
has_edge_from_callable_to_node(control[i], "app.carrier", target, "CALL_REFERENCE"));
cbm_gbuf_free(control[i]);
}
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
th_rmtree(tmpdir);
ASSERT_TRUE(sequential_probe.injected);
ASSERT_TRUE(parallel_probe.injected);
ASSERT_NOT_NULL(sequential_target);
ASSERT_NOT_NULL(parallel_target);
ASSERT_FALSE(sequential_usage);
ASSERT_FALSE(parallel_usage);
ASSERT_FALSE(sequential_promoted);
ASSERT_FALSE(parallel_promoted);
PASS();
}
/* A value argument whose exact target is a module Variable remains an ordinary
* USAGE even when the TS semantic pass marks the occurrence as reference-like.
* Conversely, a parameter or local declaration with the same spelling owns its
* lexical occurrence and must not fall through to the module Variable. Exercise
* both the sequential pass_usages path and the fused parallel resolver. */
TEST(parallel_typescript_module_value_usage_respects_lexical_shadows) {
static const char settings_source[] =
"export const config = { enabled: true };\n"
"function consumeConfig(value: { enabled: boolean }): void {}\n"
"export function moduleArgument(): void { consumeConfig(config); }\n";
static const char shadows_source[] =
"import { config } from './settings';\n"
"function consumeShadow(value: { enabled: boolean }): void {}\n"
"export function parameterShadow(config: { enabled: boolean }): void {\n"
" consumeShadow(config);\n"
"}\n"
"export function localShadow(): void {\n"
" const config = { enabled: false };\n"
" consumeShadow(config);\n"
"}\n";
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_ts_usage_scope_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char settings_path[512];
char shadows_path[512];
snprintf(settings_path, sizeof(settings_path), "%s/settings.ts", tmpdir);
snprintf(shadows_path, sizeof(shadows_path), "%s/shadows.ts", tmpdir);
if (th_write_file(settings_path, settings_source) != 0 ||
th_write_file(shadows_path, shadows_source) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write TypeScript usage-scope fixture");
}
cbm_file_info_t files[2] = {0};
files[0].path = settings_path;
files[0].rel_path = (char *)"settings.ts";
files[0].language = CBM_LANG_TYPESCRIPT;
files[1].path = shadows_path;
files[1].rel_path = (char *)"shadows.ts";
files[1].language = CBM_LANG_TYPESCRIPT;
cbm_gbuf_t *sequential = run_sequential("cbm_ts_usage_scope", tmpdir, files, 2);
cbm_gbuf_t *parallel = run_parallel("cbm_ts_usage_scope", tmpdir, files, 2, 2);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const cbm_gbuf_node_t *sequential_config = find_unique_node_by_name_label_qn_suffix(
sequential, "config", "Variable", "settings.config");
const cbm_gbuf_node_t *parallel_config =
find_unique_node_by_name_label_qn_suffix(parallel, "config", "Variable", "settings.config");
const bool sequential_target = sequential_config != NULL;
const bool parallel_target = parallel_config != NULL;
const bool sequential_callers =
find_unique_callable_node_by_tail(sequential, "settings.moduleArgument") &&
find_unique_callable_node_by_tail(sequential, "shadows.parameterShadow") &&
find_unique_callable_node_by_tail(sequential, "shadows.localShadow");
const bool parallel_callers =
find_unique_callable_node_by_tail(parallel, "settings.moduleArgument") &&
find_unique_callable_node_by_tail(parallel, "shadows.parameterShadow") &&
find_unique_callable_node_by_tail(parallel, "shadows.localShadow");
const bool sequential_module_usage = has_edge_from_callable_to_node(
sequential, "settings.moduleArgument", sequential_config, "USAGE");
const bool parallel_module_usage = has_edge_from_callable_to_node(
parallel, "settings.moduleArgument", parallel_config, "USAGE");
const bool sequential_module_reference =
has_edge_from_callable_to_node(sequential, "settings.moduleArgument", sequential_config,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(sequential, "settings.moduleArgument", sequential_config,
"CALLS");
const bool parallel_module_reference =
has_edge_from_callable_to_node(parallel, "settings.moduleArgument", parallel_config,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(parallel, "settings.moduleArgument", parallel_config,
"CALLS");
const bool sequential_parameter_leak =
has_edge_from_callable_to_node(sequential, "shadows.parameterShadow", sequential_config,
"USAGE") ||
has_edge_from_callable_to_node(sequential, "shadows.parameterShadow", sequential_config,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(sequential, "shadows.parameterShadow", sequential_config,
"CALLS");
const bool parallel_parameter_leak =
has_edge_from_callable_to_node(parallel, "shadows.parameterShadow", parallel_config,
"USAGE") ||
has_edge_from_callable_to_node(parallel, "shadows.parameterShadow", parallel_config,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(parallel, "shadows.parameterShadow", parallel_config,
"CALLS");
const bool sequential_local_leak =
has_edge_from_callable_to_node(sequential, "shadows.localShadow", sequential_config,
"USAGE") ||
has_edge_from_callable_to_node(sequential, "shadows.localShadow", sequential_config,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(sequential, "shadows.localShadow", sequential_config,
"CALLS");
const bool parallel_local_leak =
has_edge_from_callable_to_node(parallel, "shadows.localShadow", parallel_config, "USAGE") ||
has_edge_from_callable_to_node(parallel, "shadows.localShadow", parallel_config,
"CALL_REFERENCE") ||
has_edge_from_callable_to_node(parallel, "shadows.localShadow", parallel_config, "CALLS");
if (!sequential_target || !parallel_target || !sequential_callers || !parallel_callers ||
!sequential_module_usage || !parallel_module_usage || sequential_module_reference ||
parallel_module_reference || sequential_parameter_leak || parallel_parameter_leak ||
sequential_local_leak || parallel_local_leak) {
printf(" TS module-value diagnostic: target=%d/%d callers=%d/%d usage=%d/%d "
"reference=%d/%d parameter_leak=%d/%d local_leak=%d/%d\n",
sequential_target, parallel_target, sequential_callers, parallel_callers,
sequential_module_usage, parallel_module_usage, sequential_module_reference,
parallel_module_reference, sequential_parameter_leak, parallel_parameter_leak,
sequential_local_leak, parallel_local_leak);
}
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
th_rmtree(tmpdir);
ASSERT_TRUE(sequential_target);
ASSERT_TRUE(parallel_target);
ASSERT_TRUE(sequential_callers);
ASSERT_TRUE(parallel_callers);
ASSERT_TRUE(sequential_module_usage);
ASSERT_TRUE(parallel_module_usage);
ASSERT_FALSE(sequential_module_reference);
ASSERT_FALSE(parallel_module_reference);
ASSERT_FALSE(sequential_parameter_leak);
ASSERT_FALSE(parallel_parameter_leak);
ASSERT_FALSE(sequential_local_leak);
ASSERT_FALSE(parallel_local_leak);
PASS();
}
enum {
TS_EXACT_SELECTED_IMPORT = 0,
TS_EXACT_DECOY_IMPORT,
TS_EXACT_LOCAL_CLASS,
TS_EXACT_NAMESPACE_INNER_CALL,
TS_EXACT_NAMESPACE_CONSTRUCTOR_CALL,
TS_EXACT_EDGE_COUNT
};
typedef struct {
int edge_counts[2][TS_EXACT_EDGE_COUNT];
bool quality_ok[2];
RQ_Report quality[2]; /* sequential, parallel */
} ts_exact_parity_observation_t;
static ts_exact_parity_observation_t run_ts_exact_parity_fixture(CBMLanguage main_language,
const char *extension,
const char *project) {
ts_exact_parity_observation_t observation;
for (int route = 0; route < 2; route++) {
for (int edge = 0; edge < TS_EXACT_EDGE_COUNT; edge++) {
observation.edge_counts[route][edge] = -1;
}
}
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_ts_exact_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
return observation;
}
char selected_path[512];
char decoy_path[512];
char main_path[512];
char main_rel[64];
snprintf(selected_path, sizeof(selected_path), "%s/a/types.ts", tmpdir);
snprintf(decoy_path, sizeof(decoy_path), "%s/b/types.ts", tmpdir);
snprintf(main_path, sizeof(main_path), "%s/main.%s", tmpdir, extension);
snprintf(main_rel, sizeof(main_rel), "main.%s", extension);
static const char type_source[] = "export interface Config { enabled: boolean }\n";
static const char main_source[] =
"import type { Config } from './a/types';\n"
"export function choose(cfg: Config): boolean { return cfg.enabled; }\n"
"namespace App {\n"
" export namespace Utils {\n"
" export function clamp(): number { return 1; }\n"
" export function normalise(): number { return clamp(); }\n"
" }\n"
" export class Config {\n"
" constructor() { Utils.normalise(); }\n"
" }\n"
"}\n";
if (th_write_file(selected_path, type_source) != 0 ||
th_write_file(decoy_path, type_source) != 0 || th_write_file(main_path, main_source) != 0) {
th_rmtree(tmpdir);
return observation;
}
cbm_file_info_t files[3] = {0};
files[0].path = selected_path;
files[0].rel_path = (char *)"a/types.ts";
files[0].language = CBM_LANG_TYPESCRIPT;
files[1].path = decoy_path;
files[1].rel_path = (char *)"b/types.ts";
files[1].language = CBM_LANG_TYPESCRIPT;
files[2].path = main_path;
files[2].rel_path = main_rel;
files[2].language = main_language;
RQ_Source sources[] = {{.path = "a/types.ts", .text = type_source},
{.path = "b/types.ts", .text = type_source},
{.path = main_rel, .text = main_source}};
RQ_Expected expected[] = {
{.case_id = "imported-interface-annotation",
.site =
{.file = main_rel, .caller = "main.choose", .needle = "Config", .occurrence_index = 1},
.relation = "USES_TYPE",
.target = {.file = "a/types.ts",
.suffix = "a.types.Config",
.label = "Interface",
.declaration_line = 1,
.declaration_text = "export interface Config { enabled: boolean }"}},
{.case_id = "namespace-sibling-call",
.site = {.file = main_rel,
.caller = "main.App.Utils.normalise",
.needle = "clamp()",
.occurrence_index = 1},
.relation = "CALLS",
.target = {.file = main_rel,
.suffix = "main.App.Utils.clamp",
.label = "Function",
.declaration_line = 5,
.declaration_text = " export function clamp(): number { return 1; }"}},
{.case_id = "namespace-constructor-call",
.site = {.file = main_rel,
.caller = "main.App.Config.constructor",
.needle = "Utils.normalise()"},
.relation = "CALLS",
.target = {.file = main_rel,
.suffix = "main.App.Utils.normalise",
.label = "Function",
.declaration_line = 6,
.declaration_text =
" export function normalise(): number { return clamp(); }"},
.mode = RQ_KNOWN_GAP,
.gap_id = "typescript-namespace-member-call-proof",
.why = "Nested namespace member lookup is not source-certified.",
.tried = "Retained the constructor and explicit Utils receiver; no bare-name fallback is "
"admitted."}};
cbm_gbuf_t *graphs[2] = {
run_sequential_with_lsp_cross_and_mutator(project, tmpdir, files, 3, NULL, NULL, true),
run_parallel_with_extract_opts_and_mutator(project, tmpdir, files, 3, 2, NULL, NULL, NULL,
true),
};
for (int route = 0; route < 2; route++) {
cbm_gbuf_t *graph = graphs[route];
const cbm_gbuf_node_t *choose =
find_unique_node_by_name_label_qn_suffix(graph, "choose", "Function", "main.choose");
const cbm_gbuf_node_t *selected = find_unique_node_by_name_label_qn_suffix(
graph, "Config", "Interface", "a.types.Config");
const cbm_gbuf_node_t *decoy = find_unique_node_by_name_label_qn_suffix(
graph, "Config", "Interface", "b.types.Config");
const cbm_gbuf_node_t *local_class =
find_unique_node_by_name_label_qn_suffix(graph, "Config", "Class", "main.App.Config");
const cbm_gbuf_node_t *normalise = find_unique_node_by_name_label_qn_suffix(
graph, "normalise", "Function", "main.App.Utils.normalise");
const cbm_gbuf_node_t *clamp = find_unique_node_by_name_label_qn_suffix(
graph, "clamp", "Function", "main.App.Utils.clamp");
const cbm_gbuf_node_t *constructor = find_unique_node_by_name_label_qn_suffix(
graph, "constructor", "Method", "main.App.Config.constructor");
observation.quality_ok[route] = parallel_quality_check(
graph, project, route ? "ts-namespace-parallel" : "ts-namespace-sequential", sources, 3,
expected, 3, &observation.quality[route]);
observation.edge_counts[route][TS_EXACT_SELECTED_IMPORT] =
count_edges_between_nodes(graph, choose, selected, "USES_TYPE");
observation.edge_counts[route][TS_EXACT_DECOY_IMPORT] =
count_edges_between_nodes(graph, choose, decoy, "USES_TYPE");
observation.edge_counts[route][TS_EXACT_LOCAL_CLASS] =
count_edges_between_nodes(graph, choose, local_class, "USES_TYPE");
observation.edge_counts[route][TS_EXACT_NAMESPACE_INNER_CALL] =
count_edges_between_nodes(graph, normalise, clamp, "CALLS");
observation.edge_counts[route][TS_EXACT_NAMESPACE_CONSTRUCTOR_CALL] =
count_edges_between_nodes(graph, constructor, normalise, "CALLS");
}
cbm_gbuf_free(graphs[0]);
cbm_gbuf_free(graphs[1]);
th_rmtree(tmpdir);
return observation;
}
static void print_ts_exact_parity_diagnostic(const char *language,
const ts_exact_parity_observation_t *observation) {
printf(" %s exact parity: seq=[%d,%d,%d,%d,%d] par=[%d,%d,%d,%d,%d]\n", language,
observation->edge_counts[0][TS_EXACT_SELECTED_IMPORT],
observation->edge_counts[0][TS_EXACT_DECOY_IMPORT],
observation->edge_counts[0][TS_EXACT_LOCAL_CLASS],
observation->edge_counts[0][TS_EXACT_NAMESPACE_INNER_CALL],
observation->edge_counts[0][TS_EXACT_NAMESPACE_CONSTRUCTOR_CALL],
observation->edge_counts[1][TS_EXACT_SELECTED_IMPORT],
observation->edge_counts[1][TS_EXACT_DECOY_IMPORT],
observation->edge_counts[1][TS_EXACT_LOCAL_CLASS],
observation->edge_counts[1][TS_EXACT_NAMESPACE_INNER_CALL],
observation->edge_counts[1][TS_EXACT_NAMESPACE_CONSTRUCTOR_CALL]);
}
TEST(parallel_typescript_import_namespace_exact_parity) {
ts_exact_parity_observation_t observation =
run_ts_exact_parity_fixture(CBM_LANG_TYPESCRIPT, "ts", "cbm_ts_exact_parity");
print_ts_exact_parity_diagnostic("TypeScript", &observation);
for (int route = 0; route < 2; route++) {
ASSERT_TRUE(observation.quality_ok[route]);
ASSERT_EQ(observation.quality[route].genuine, 3);
ASSERT_EQ(observation.quality[route].missing, 1);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_SELECTED_IMPORT], 1);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_DECOY_IMPORT], 0);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_LOCAL_CLASS], 0);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_NAMESPACE_INNER_CALL], 1);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_NAMESPACE_CONSTRUCTOR_CALL], 0);
}
PASS();
}
TEST(parallel_tsx_import_namespace_exact_parity) {
ts_exact_parity_observation_t observation =
run_ts_exact_parity_fixture(CBM_LANG_TSX, "tsx", "cbm_tsx_exact_parity");
print_ts_exact_parity_diagnostic("TSX", &observation);
for (int route = 0; route < 2; route++) {
ASSERT_TRUE(observation.quality_ok[route]);
ASSERT_EQ(observation.quality[route].genuine, 3);
ASSERT_EQ(observation.quality[route].missing, 1);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_SELECTED_IMPORT], 1);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_DECOY_IMPORT], 0);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_LOCAL_CLASS], 0);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_NAMESPACE_INNER_CALL], 1);
ASSERT_EQ(observation.edge_counts[route][TS_EXACT_NAMESPACE_CONSTRUCTOR_CALL], 0);
}
PASS();
}
typedef struct {
bool carrier;
bool semantic;
bool exact_join;
} kotlin_implicit_probe_t;
static kotlin_implicit_probe_t probe_kotlin_implicit_site(const CBMFileResult *result,
const char *caller, const char *callee,
const char *target_qn,
const char *strategy) {
kotlin_implicit_probe_t probe = {0};
if (!result || !caller || !callee || !target_qn || !strategy) {
return probe;
}
for (int i = 0; i < result->calls.count; i++) {
const CBMCall *call = &result->calls.items[i];
const char *call_caller =
call->enclosing_func_qn ? strrchr(call->enclosing_func_qn, '.') : NULL;
call_caller = call_caller ? call_caller + 1 : call->enclosing_func_qn;
if (!call->requires_lsp_resolution || !call->callee_name || !call_caller ||
strcmp(call->callee_name, callee) != 0 || strcmp(call_caller, caller) != 0 ||
!cbm_pipeline_source_site_present(call->site_start_byte, call->site_end_byte)) {
continue;
}
probe.carrier = true;
}
for (int j = 0; j < result->resolved_calls.count; j++) {
const CBMResolvedCall *resolved = &result->resolved_calls.items[j];
const char *resolved_caller =
resolved->caller_qn ? strrchr(resolved->caller_qn, '.') : NULL;
resolved_caller = resolved_caller ? resolved_caller + 1 : resolved->caller_qn;
if (resolved->kind != CBM_RESOLVED_INVOCATION || !resolved_caller || !resolved->callee_qn ||
!resolved->strategy || strcmp(resolved_caller, caller) != 0 ||
strcmp(resolved->callee_qn, target_qn) != 0 ||
strcmp(resolved->strategy, strategy) != 0) {
continue;
}
probe.semantic = true;
for (int i = 0; i < result->calls.count; i++) {
const CBMCall *call = &result->calls.items[i];
const char *call_caller =
call->enclosing_func_qn ? strrchr(call->enclosing_func_qn, '.') : NULL;
call_caller = call_caller ? call_caller + 1 : call->enclosing_func_qn;
if (!call->requires_lsp_resolution || !call->callee_name || !call_caller ||
strcmp(call->callee_name, callee) != 0 || strcmp(call_caller, caller) != 0) {
continue;
}
if (cbm_pipeline_source_site_eq(call->site_start_byte, call->site_end_byte,
resolved->site_start_byte, resolved->site_end_byte)) {
probe.exact_join = true;
}
}
}
return probe;
}
/* #2120: two C# projects declare a class with the same simple name in
* namespaces that share a root (Contoso.Platform.Identity.UserIdentityInfo vs
* Contoso.Platform.BackOffice.Identity.UserIdentityInfo). Graph QNs are
* path-derived, so the namespace-prefix lookups never hit a project type and
* the short-name fallback scored both variants 0 against the file namespace:
* registration order picked the target, ignoring the caller's `using`
* directive. Each caller below can see exactly ONE variant (C# binding rules),
* so exactly one edge per caller is correct whatever the file order: plain
* `using` (both directions), enclosing-namespace proximity, and a
* fully-qualified name. The last case is a reference-assembly shape: one file
* declaring several namespaces, whose file-level namespace is only the FIRST
* one; the decoy is registered first, so a mislabeled namespace falls back to
* it. `partial` pieces of one type are separate registry entries, so a member
* declared on another piece must still bind to that piece. */
TEST(parallel_csharp_same_name_class_binds_by_namespace) {
static const char platform_src[] =
"namespace Contoso.Platform.Identity;\n"
"public class UserIdentityInfo {\n"
" public static UserIdentityInfo ParseFromClaim(string c)\n"
" => new UserIdentityInfo();\n"
"}\n";
static const char backoffice_src[] =
"namespace Contoso.Platform.BackOffice.Identity;\n"
"public class UserIdentityInfo {\n"
" public static UserIdentityInfo ParseFromClaim(string c)\n"
" => new UserIdentityInfo();\n"
"}\n";
static const char bo_handler_src[] =
"using Contoso.Platform.BackOffice.Identity;\n"
"namespace Contoso.Platform.BackOffice.CqrsHandlers;\n"
"public class Handler {\n"
" public string Handle(string claim) {\n"
" var info = UserIdentityInfo.ParseFromClaim(claim);\n"
" return info.ToString();\n"
" }\n"
"}\n";
static const char pf_handler_src[] =
"using Contoso.Platform.Identity;\n"
"namespace Contoso.Platform.Api;\n"
"public class PlatformHandler {\n"
" public string Handle(string claim) {\n"
" var info = UserIdentityInfo.ParseFromClaim(claim);\n"
" return info.ToString();\n"
" }\n"
"}\n";
static const char nested_ns_src[] =
"namespace Contoso.Platform.BackOffice.Identity.Claims\n"
"{\n"
" public class ClaimReader {\n"
" public string Read(string claim) {\n"
" var info = UserIdentityInfo.ParseFromClaim(claim);\n"
" return info.ToString();\n"
" }\n"
" }\n"
"}\n";
static const char fq_src[] = "namespace Contoso.Platform.Reports;\n"
"public class Report {\n"
" public string Build(string claim) {\n"
" var info = Contoso.Platform.BackOffice.Identity"
".UserIdentityInfo.ParseFromClaim(claim);\n"
" return info.ToString();\n"
" }\n"
"}\n";
static const char decoy_src[] = "namespace Contoso.Decoy;\n"
"public class Worker {\n"
" public void Start() { }\n"
" public void Beta() { }\n"
"}\n";
static const char multi_ns_src[] = "namespace Contoso.Win32 { public class SafeHandle { } }\n"
"namespace Contoso.Threading {\n"
" public class Worker { public void Start() { } }\n"
"}\n";
static const char runner_src[] = "using Contoso.Threading;\n"
"namespace Contoso.App;\n"
"public class Runner {\n"
" public void Run() {\n"
" var w = new Worker();\n"
" w.Start();\n"
" }\n"
"}\n";
static const char part_a_src[] = "namespace Contoso.Parts;\n"
"public partial class Thing { public void Alpha() { } }\n";
static const char part_b_src[] = "namespace Contoso.Parts;\n"
"public partial class Thing { public void Beta() { } }\n";
static const char part_user_src[] = "using Contoso.Parts;\n"
"namespace Contoso.App;\n"
"public class PartUser {\n"
" public void Use() {\n"
" var t = new Thing();\n"
" t.Alpha();\n"
" t.Beta();\n"
" }\n"
"}\n";
const char *project = "cbm_cs_same_name";
static const char *rels[] = {
"src/Decoy/Worker.cs",
"src/Contoso.Platform/Identity/UserIdentityInfo.cs",
"src/Contoso.Platform.BackOffice/Identity/UserIdentityInfo.cs",
"src/Contoso.Platform.BackOffice/CqrsHandlers/Handler.cs",
"src/Contoso.Platform/Api/PlatformHandler.cs",
"src/Contoso.Platform.BackOffice/Identity/Claims/ClaimReader.cs",
"src/Contoso.Platform/Reports/Report.cs",
"src/Ref/Threading.cs",
"src/App/Runner.cs",
"src/Parts/Thing.A.cs",
"src/Parts/Thing.B.cs",
"src/App/PartUser.cs",
};
const char *srcs[] = {decoy_src, platform_src, backoffice_src, bo_handler_src,
pf_handler_src, nested_ns_src, fq_src, multi_ns_src,
runner_src, part_a_src, part_b_src, part_user_src};
enum { N_FILES = 12, N_CHECKS = 13 };
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_cs_same_name_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char paths[N_FILES][512];
cbm_file_info_t files[N_FILES] = {0};
for (int i = 0; i < N_FILES; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/%s", tmpdir, rels[i]);
if (th_write_file(paths[i], srcs[i]) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write C# same-name fixture");
}
files[i].path = paths[i];
files[i].rel_path = (char *)rels[i];
files[i].language = CBM_LANG_CSHARP;
}
cbm_gbuf_t *graphs[2] = {run_sequential_with_lsp_cross(project, tmpdir, files, N_FILES), NULL};
g_harness_cs_registry = true;
graphs[1] = run_parallel(project, tmpdir, files, N_FILES, 2);
g_harness_cs_registry = false;
ASSERT_NOT_NULL(graphs[0]);
ASSERT_NOT_NULL(graphs[1]);
const char *pf = "cbm_cs_same_name.src.Contoso.Platform.Identity.UserIdentityInfo."
"UserIdentityInfo.ParseFromClaim";
const char *bo = "cbm_cs_same_name.src.Contoso.Platform.BackOffice.Identity."
"UserIdentityInfo.UserIdentityInfo.ParseFromClaim";
const char *ref_start = "cbm_cs_same_name.src.Ref.Threading.Worker.Start";
const char *decoy_start = "cbm_cs_same_name.src.Decoy.Worker.Worker.Start";
const char *part_alpha = "cbm_cs_same_name.src.Parts.Thing.A.Thing.Alpha";
const char *part_beta = "cbm_cs_same_name.src.Parts.Thing.B.Thing.Beta";
const char *decoy_beta = "cbm_cs_same_name.src.Decoy.Worker.Worker.Beta";
RQ_Source sources[N_FILES];
for (int i = 0; i < N_FILES; i++)
sources[i] = (RQ_Source){.path = rels[i], .text = srcs[i]};
const struct {
int source, target;
const char *caller, *needle, *suffix, *declaration;
int line;
} cases[] = {{3, 2, "Handler.Handle", "UserIdentityInfo.ParseFromClaim(claim)", bo,
" public static UserIdentityInfo ParseFromClaim(string c)", 3},
{4, 1, "PlatformHandler.Handle", "UserIdentityInfo.ParseFromClaim(claim)", pf,
" public static UserIdentityInfo ParseFromClaim(string c)", 3},
{5, 2, "ClaimReader.Read", "UserIdentityInfo.ParseFromClaim(claim)", bo,
" public static UserIdentityInfo ParseFromClaim(string c)", 3},
{6, 2, "Report.Build",
"Contoso.Platform.BackOffice.Identity.UserIdentityInfo.ParseFromClaim(claim)", bo,
" public static UserIdentityInfo ParseFromClaim(string c)", 3},
{8, 7, "Runner.Run", "w.Start()", ref_start,
" public class Worker { public void Start() { } }", 3},
{11, 9, "PartUser.Use", "t.Alpha()", part_alpha,
"public partial class Thing { public void Alpha() { } }", 2},
{11, 10, "PartUser.Use", "t.Beta()", part_beta,
"public partial class Thing { public void Beta() { } }", 2}};
enum { N_MEMBER_CALLS = 7, N_EXPECTED = 9 };
RQ_Expected expected[N_EXPECTED] = {0};
for (int i = 0; i < N_MEMBER_CALLS; i++)
expected[i] = (RQ_Expected){
.case_id = i == 6 ? "partial-beta" : cases[i].caller,
.site = {.file = rels[cases[i].source],
.caller = cases[i].caller,
.needle = cases[i].needle},
.relation = "CALLS",
.target = {.file = rels[cases[i].target],
.suffix = cases[i].suffix,
.label = "Method",
.declaration_line = cases[i].line,
.declaration_text = cases[i].declaration},
.mode = RQ_REQUIRED};
/* `using Contoso.Threading` and nothing nearer named Worker: the constructor
* call selects Contoso.Threading.Worker, the second namespace of Threading.cs,
* never the decoy Contoso.Decoy.Worker this file does not import. */
expected[N_MEMBER_CALLS] = (RQ_Expected){
.case_id = "Runner.Run-constructor",
.site = {.file = rels[8], .caller = "Runner.Run", .needle = "new Worker()"},
.relation = "CALLS",
.target = {.file = rels[7],
.suffix = "cbm_cs_same_name.src.Ref.Threading.Worker",
.label = "Class",
.declaration_line = 3,
.declaration_text = " public class Worker { public void Start() { } }"},
.mode = RQ_REQUIRED};
expected[N_MEMBER_CALLS + 1] = (RQ_Expected){
.case_id = "PartUser.Use-partial-constructor",
.site = {.file = rels[11], .caller = "PartUser.Use", .needle = "new Thing()"},
.relation = "CALLS",
.target = {.file = rels[9],
.suffix = "cbm_cs_same_name.src.Parts.Thing.A.Thing",
.label = "Class",
.declaration_line = 2,
.declaration_text = "public partial class Thing { public void Alpha() { } }"},
.mode = RQ_REQUIRED};
RQ_Report quality[2] = {0};
bool ok[2][N_CHECKS];
bool all = true;
for (int g = 0; g < 2; g++) {
cbm_gbuf_t *gb = graphs[g];
ASSERT(parallel_quality_check(gb, project,
g ? "csharp-members-parallel" : "csharp-members-sequential",
sources, N_FILES, expected, N_EXPECTED, &quality[g]));
ASSERT_EQ(quality[g].genuine, N_EXPECTED);
ASSERT_EQ(quality[g].found, N_EXPECTED);
ASSERT_EQ(quality[g].missing, 0);
ok[g][0] = has_edge_from_callable_to_qn(gb, "Handler.Handle", bo, "CALLS");
ok[g][1] = !has_edge_from_callable_to_qn(gb, "Handler.Handle", pf, "CALLS");
ok[g][2] = has_edge_from_callable_to_qn(gb, "PlatformHandler.Handle", pf, "CALLS");
ok[g][3] = !has_edge_from_callable_to_qn(gb, "PlatformHandler.Handle", bo, "CALLS");
ok[g][4] = has_edge_from_callable_to_qn(gb, "ClaimReader.Read", bo, "CALLS");
ok[g][5] = !has_edge_from_callable_to_qn(gb, "ClaimReader.Read", pf, "CALLS");
ok[g][6] = has_edge_from_callable_to_qn(gb, "Report.Build", bo, "CALLS");
ok[g][7] = !has_edge_from_callable_to_qn(gb, "Report.Build", pf, "CALLS");
ok[g][8] = has_edge_from_callable_to_qn(gb, "Runner.Run", ref_start, "CALLS");
ok[g][9] = !has_edge_from_callable_to_qn(gb, "Runner.Run", decoy_start, "CALLS");
ok[g][10] = has_edge_from_callable_to_qn(gb, "PartUser.Use", part_alpha, "CALLS");
ok[g][11] = has_edge_from_callable_to_qn(gb, "PartUser.Use", part_beta, "CALLS");
ok[g][12] = !has_edge_from_callable_to_qn(gb, "PartUser.Use", decoy_beta, "CALLS");
for (int k = 0; k < N_CHECKS; k++) {
all = all && ok[g][k];
}
}
if (!all) {
for (int g = 0; g < 2; g++) {
printf(" C# same-name diagnostic %s: using_bo=%d/%d using_pf=%d/%d "
"enclosing_ns=%d/%d fully_qualified=%d/%d multi_ns_file=%d/%d "
"partial=%d/%d/%d\n",
g == 0 ? "sequential" : "parallel", ok[g][0], ok[g][1], ok[g][2], ok[g][3],
ok[g][4], ok[g][5], ok[g][6], ok[g][7], ok[g][8], ok[g][9], ok[g][10], ok[g][11],
ok[g][12]);
}
}
cbm_gbuf_free(graphs[0]);
cbm_gbuf_free(graphs[1]);
th_rmtree(tmpdir);
ASSERT_TRUE(all);
PASS();
}
static const char PAR_C_TARGET[] = "int quality_target(void) { return 7; }\n";
static const char PAR_C_CALLER[] = "int quality_target(void);\n"
"int quality_call(void) { return quality_target(); }\n";
static const RQ_Source PAR_C_SOURCES[] = {{.path = "quality/Target.c", .text = PAR_C_TARGET},
{.path = "quality/Caller.c", .text = PAR_C_CALLER}};
static const RQ_Expected PAR_C_REQUIRED = {
.case_id = "required-c-cross-file-prototype",
.site = {.file = "quality/Caller.c",
.caller = "quality.Caller.quality_call",
.needle = "quality_target()"},
.relation = "CALLS",
.target = {.file = "quality/Target.c",
.suffix = "quality.Target.quality_target",
.label = "Function",
.declaration_line = 1,
.declaration_text = "int quality_target(void) { return 7; }"},
.mode = RQ_REQUIRED};
static bool parallel_quality_c_files(const char *root, cbm_file_info_t files[2],
char paths[2][512]) {
for (int i = 0; i < 2; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/%s", root, PAR_C_SOURCES[i].path);
if (th_write_file(paths[i], PAR_C_SOURCES[i].text) != 0)
return false;
files[i].path = paths[i];
files[i].rel_path = (char *)PAR_C_SOURCES[i].path;
files[i].language = CBM_LANG_C;
}
return true;
}
/* #2120 residual, reported on a real repo: step 8b binds only namespace-
* qualified TOP-LEVEL types. A qualified `A.B` whose qualifier A is a TYPE
* (nested-type constructor `new Command.Result(...)`, chained-constructor
* receiver `new Grain.Handler(dep).Handle(...)`) and a top-level type with NO
* namespace declaration (global namespace, e.g. a `[Inject]`-consumed service)
* both fell through to the short-name fallback, where the prefix score against
* a path-derived QN is 0 for every variant — registration order picked the
* target. Decoy files are registered FIRST so the old registry-order behavior
* binds every check to the decoy and the test fails. */
TEST(parallel_csharp_nested_type_and_global_namespace_binding) {
static const char decoy_src[] =
"namespace Acme.Layouts.Cqrs;\n"
"public class GrainCommand {\n"
" public class Handler {\n"
" public int Handle() => 9;\n"
" }\n"
" public class Result {\n"
" public Result(bool ok) { }\n"
" public int Code() => 9;\n"
" }\n"
"}\n";
static const char partner_src[] =
"namespace Acme.Partner.Cqrs;\n"
"public class GrainCommand {\n"
" public class Handler {\n"
" private readonly int _v;\n"
" public Handler(int v) { _v = v; }\n"
" public int Handle() => _v;\n"
" }\n"
" public class Result {\n"
" public Result(bool ok) { }\n"
" public int Code() => 1;\n"
" }\n"
"}\n";
static const char backoffice_src[] =
"namespace Acme.BackOffice.Cqrs;\n"
"public class GrainCommand {\n"
" public class Result {\n"
" public Result(bool ok) { }\n"
" public int Code() => 2;\n"
" }\n"
"}\n";
static const char nested_user_src[] =
"using Acme.Partner.Cqrs;\n"
"namespace Acme.App;\n"
"public class NestedUser {\n"
" public int Run() {\n"
" var r = new GrainCommand.Result(true);\n"
" return r.Code();\n"
" }\n"
"}\n";
static const char chained_user_src[] =
"using Acme.Partner.Cqrs;\n"
"namespace Acme.App;\n"
"public class ChainedUser {\n"
" public int Run() {\n"
" return new GrainCommand.Handler(3).Handle();\n"
" }\n"
"}\n";
static const char other_update_src[] =
"namespace Acme.Other.Web.Services;\n"
"public class UpdateService {\n"
" public string Check() => \"other\";\n"
"}\n";
/* No namespace declaration: the global namespace. */
static const char global_update_src[] =
"public class UpdateService {\n"
" public string Check() => \"global\";\n"
"}\n";
static const char boot_src[] =
"namespace Acme.App;\n"
"public class Boot {\n"
" public string Boot1() {\n"
" var u = new UpdateService();\n"
" return u.Check();\n"
" }\n"
"}\n";
/* The lookup-order probes: the SAME simple name is importable from
* Acme.Other.Web.Services AND declared in the global namespace. C# looks
* a simple name up namespace by namespace, innermost outward; at each one
* it checks that namespace's members, then the usings of the namespace
* declaration enclosing the reference. The global namespace comes last,
* and its "declaration" is the compilation unit, so:
* - a compilation-unit `using` (ImportUser) is consulted only AFTER the
* global namespace's members: the global UpdateService wins;
* - a `using` inside the namespace body (NsUsingUser) is consulted
* before any outer namespace: the imported UpdateService wins. */
static const char import_user_src[] =
"using Acme.Other.Web.Services;\n"
"namespace Acme.App;\n"
"public class ImportUser {\n"
" public string Run2() {\n"
" var u = new UpdateService();\n"
" return u.Check();\n"
" }\n"
"}\n";
static const char ns_using_user_src[] =
"namespace Acme.App {\n"
" using Acme.Other.Web.Services;\n"
" public class NsUsingUser {\n"
" public string Run3() {\n"
" var u = new UpdateService();\n"
" return u.Check();\n"
" }\n"
" }\n"
"}\n";
const char *project = "cbm_cs_nested";
static const char *rels[] = {
"src/Decoy/Layouts.cs",
"src/Decoy/OtherUpdate.cs",
"src/Partner/Cqrs/GrainCommand.cs",
"src/BackOffice/Cqrs/GrainCommand.cs",
"src/Globals/UpdateService.cs",
"src/App/NestedUser.cs",
"src/App/ChainedUser.cs",
"src/App/Boot.cs",
"src/App/ImportUser.cs",
"src/App/NsUsingUser.cs",
};
const char *srcs[] = {decoy_src, other_update_src, partner_src,
backoffice_src, global_update_src, nested_user_src,
chained_user_src, boot_src, import_user_src,
ns_using_user_src};
enum { N_FILES = 10, N_CHECKS = 12 };
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_cs_nested_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char paths[N_FILES][512];
cbm_file_info_t files[N_FILES] = {0};
for (int i = 0; i < N_FILES; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/%s", tmpdir, rels[i]);
if (th_write_file(paths[i], srcs[i]) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write C# nested-type fixture");
}
files[i].path = paths[i];
files[i].rel_path = (char *)rels[i];
files[i].language = CBM_LANG_CSHARP;
}
cbm_gbuf_t *graphs[2] = {run_sequential_with_lsp_cross(project, tmpdir, files, N_FILES), NULL};
g_harness_cs_registry = true;
graphs[1] = run_parallel(project, tmpdir, files, N_FILES, 2);
g_harness_cs_registry = false;
ASSERT_NOT_NULL(graphs[0]);
ASSERT_NOT_NULL(graphs[1]);
const char *pf_code = "cbm_cs_nested.src.Partner.Cqrs.GrainCommand.GrainCommand."
"Result.Code";
const char *pf_handle = "cbm_cs_nested.src.Partner.Cqrs.GrainCommand.GrainCommand."
"Handler.Handle";
const char *bo_code = "cbm_cs_nested.src.BackOffice.Cqrs.GrainCommand.GrainCommand."
"Result.Code";
const char *decoy_code = "cbm_cs_nested.src.Decoy.Layouts.GrainCommand.GrainCommand."
"Result.Code";
const char *decoy_handle = "cbm_cs_nested.src.Decoy.Layouts.GrainCommand.GrainCommand."
"Handler.Handle";
const char *global_check = "cbm_cs_nested.src.Globals.UpdateService.UpdateService.Check";
const char *other_check = "cbm_cs_nested.src.Decoy.OtherUpdate.UpdateService.Check";
bool ok[2][N_CHECKS];
bool all = true;
for (int g = 0; g < 2; g++) {
cbm_gbuf_t *gb = graphs[g];
ok[g][0] = has_edge_from_callable_to_qn(gb, "NestedUser.Run", pf_code, "CALLS");
ok[g][1] = !has_edge_from_callable_to_qn(gb, "NestedUser.Run", bo_code, "CALLS") &&
!has_edge_from_callable_to_qn(gb, "NestedUser.Run", decoy_code, "CALLS");
/* The ctor EDGE itself joins through the pipeline's textual carrier and
* is out of scope here (see PR notes); what must hold is that the
* nested type RESOLUTION is right, proven by the Code()/Handle() edges
* above, and that no ctor edge binds to a variant the caller cannot
* see: neither the BackOffice nor the registry-first decoy. */
ok[g][2] = !has_edge_from_callable_to_qn(
gb, "NestedUser.Run",
"cbm_cs_nested.src.BackOffice.Cqrs.GrainCommand.GrainCommand.Result",
"CALLS") &&
!has_edge_from_callable_to_qn(
gb, "NestedUser.Run",
"cbm_cs_nested.src.BackOffice.Cqrs.GrainCommand.GrainCommand."
"Result.Result",
"CALLS");
ok[g][3] = !has_edge_from_callable_to_qn(gb, "NestedUser.Run",
"cbm_cs_nested.src.Decoy.Layouts.GrainCommand."
"GrainCommand.Result.Result",
"CALLS");
ok[g][4] = has_edge_from_callable_to_qn(gb, "ChainedUser.Run", pf_handle, "CALLS");
ok[g][5] = !has_edge_from_callable_to_qn(gb, "ChainedUser.Run", decoy_handle, "CALLS");
ok[g][6] = has_edge_from_callable_to_qn(gb, "Boot.Boot1", global_check, "CALLS");
ok[g][7] = !has_edge_from_callable_to_qn(gb, "Boot.Boot1", other_check, "CALLS");
ok[g][8] = has_edge_from_callable_to_qn(gb, "ImportUser.Run2", global_check, "CALLS");
ok[g][9] = !has_edge_from_callable_to_qn(gb, "ImportUser.Run2", other_check, "CALLS");
ok[g][10] = has_edge_from_callable_to_qn(gb, "NsUsingUser.Run3", other_check, "CALLS");
ok[g][11] = !has_edge_from_callable_to_qn(gb, "NsUsingUser.Run3", global_check, "CALLS");
for (int k = 0; k < N_CHECKS; k++) {
/* The ctor-edge checks 2/3 gate the parallel path only. In the
* sequential path the `new GrainCommand.Result(true)` edge is
* still drawn by the textual ctor carrier, not by the resolved
* type (the out-of-scope carrier join above); on current main it
* lands on the BackOffice Result already with the nested-type
* resolution alone. The diagnostic below still prints both. */
if (g == 0 && (k == 2 || k == 3))
continue;
all = all && ok[g][k];
}
}
if (!all) {
for (int g = 0; g < 2; g++) {
printf(" C# nested/global diagnostic %s: nested_code=%d/%d nested_ctor=%d/%d "
"chained=%d/%d global_ns=%d/%d cu_using=%d/%d ns_using=%d/%d\n",
g == 0 ? "sequential" : "parallel", ok[g][0], ok[g][1], ok[g][2], ok[g][3],
ok[g][4], ok[g][5], ok[g][6], ok[g][7], ok[g][8], ok[g][9], ok[g][10],
ok[g][11]);
}
}
cbm_gbuf_free(graphs[0]);
cbm_gbuf_free(graphs[1]);
th_rmtree(tmpdir);
ASSERT_TRUE(all);
PASS();
}
/* A type extracted with no namespace is global only if its file parsed
* cleanly. The vendored grammar does not parse a `ref partial struct` with
* members (reference assemblies are full of them), and error recovery
* dissolves the namespace around it, so the static class after it surfaces
* with no namespace. Read as global, it outranked the compilation-unit
* `using` and took every `Gadget.Spin()` call from the real, visible type;
* on dotnet/runtime that moved 5,313 CALLS from implementation files into
* reference assemblies. The damaged file is registered FIRST so neither the
* old registry order nor the global rank can explain a pass. */
TEST(parallel_csharp_parse_damaged_namespace_is_not_global) {
static const char damaged_src[] = "namespace Acme.Lib\n"
"{\n"
" public ref partial struct Handler\n"
" {\n"
" public void Append(string value) { }\n"
" }\n"
" public static partial class Gadget {\n"
" public static int Spin() => 9;\n"
" }\n"
"}\n";
static const char real_src[] = "namespace Acme.Lib;\n"
"public static class Gadget {\n"
" public static int Spin() => 1;\n"
"}\n";
static const char user_src[] = "using Acme.Lib;\n"
"namespace Acme.App;\n"
"public class GadgetUser {\n"
" public int Run() {\n"
" return Gadget.Spin();\n"
" }\n"
"}\n";
const char *project = "cbm_cs_damaged";
static const char *rels[] = {"src/Ref/Lib.cs", "src/Lib/Gadget.cs", "src/App/GadgetUser.cs"};
const char *srcs[] = {damaged_src, real_src, user_src};
enum { N_FILES = 3 };
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_cs_damaged_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char paths[N_FILES][512];
cbm_file_info_t files[N_FILES] = {0};
for (int i = 0; i < N_FILES; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/%s", tmpdir, rels[i]);
if (th_write_file(paths[i], srcs[i]) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write C# damaged-namespace fixture");
}
files[i].path = paths[i];
files[i].rel_path = (char *)rels[i];
files[i].language = CBM_LANG_CSHARP;
}
cbm_gbuf_t *graphs[2] = {run_sequential_with_lsp_cross(project, tmpdir, files, N_FILES), NULL};
g_harness_cs_registry = true;
graphs[1] = run_parallel(project, tmpdir, files, N_FILES, 2);
g_harness_cs_registry = false;
ASSERT_NOT_NULL(graphs[0]);
ASSERT_NOT_NULL(graphs[1]);
const char *real_spin = "cbm_cs_damaged.src.Lib.Gadget.Gadget.Spin";
const char *damaged_spin = "cbm_cs_damaged.src.Ref.Lib.Gadget.Spin";
/* The parse view now reads `ref partial struct`, so neither file lost its namespace and
* both declare Acme.Lib.Gadget: a reference assembly beside its implementation. Nothing
* here says which one this caller compiles against, so neither is bound. Known gap: a
* project model could tell them apart. The defect above must stay gone either way. */
bool ok[2][2];
for (int g = 0; g < 2; g++) {
ok[g][0] = !has_edge_from_callable_to_qn(graphs[g], "GadgetUser.Run", real_spin, "CALLS");
ok[g][1] = !has_edge_from_callable_to_qn(graphs[g], "GadgetUser.Run", damaged_spin,
"CALLS");
}
bool all = ok[0][0] && ok[0][1] && ok[1][0] && ok[1][1];
if (!all) {
printf(" C# damaged-namespace diagnostic: sequential no_real=%d no_damaged=%d "
"parallel no_real=%d no_damaged=%d\n",
ok[0][0], ok[0][1], ok[1][0], ok[1][1]);
}
cbm_gbuf_free(graphs[0]);
cbm_gbuf_free(graphs[1]);
th_rmtree(tmpdir);
ASSERT_TRUE(all);
PASS();
}
/* External Kotlin protocol targets must not borrow a project method merely
* because the final Class.method segments agree. Raw assertions prove that the
* iterator/destructuring carriers and their stdlib semantic rows really exist;
* the graph negatives therefore cannot pass by dropping synthesis. The
* reference and the operator bind in both drivers; the alias call in this file
* remains quality debt, and the same call in a file without a class is required. */
TEST(parallel_kotlin_external_protocol_does_not_use_project_class_method_tail) {
static const char collision_source[] = "package collision\n"
"class IntArray {\n"
" fun iterator(): IntArray = this\n"
"}\n"
"class Pair {\n"
" operator fun component1(): Int = 0\n"
"}\n";
static const char main_source[] =
"package app\n"
"fun loopBuiltIn(values: kotlin.IntArray) {\n"
" for (value in values) { println(value) }\n"
"}\n"
"fun destructureBuiltIn(value: kotlin.Pair<Int, Int>) {\n"
" val (left, right) = value\n"
"}\n"
"fun projectHandler(): Int = 7\n"
"fun aliasCaller(): Int {\n"
" val callback = ::projectHandler\n"
" return callback()\n"
"}\n"
"class Box { operator fun plus(other: Box): Box = this }\n"
"fun operatorCaller(left: Box, right: Box): Box = left + right\n";
static const char alias_source[] =
"package quality\n"
"fun handler() {}\n"
"fun aliasControl() { val callback = ::handler; callback() }\n";
const RQ_Source sources[] = {{.path = "collision/Collisions.kt", .text = collision_source},
{.path = "app/Main.kt", .text = main_source},
{.path = "quality/Alias.kt", .text = alias_source}};
const RQ_Expected expected[] = {
{.case_id = "original-callable-alias-beside-class-member",
.site = {.file = "app/Main.kt",
.caller = "app.Main.aliasCaller",
.needle = "callback()",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Main.kt",
.suffix = "app.Main.projectHandler",
.label = "Function",
.declaration_line = 8,
.declaration_text = "fun projectHandler(): Int = 7"},
.mode = RQ_KNOWN_GAP,
.gap_id = "kotlin_callable_alias_beside_class_member",
.why = "The file parses completely (asserted below), but this alias has no proof once the "
"file also declares a class with a member: the same call binds with `class Box` "
"gone or empty, as the parse-complete quality/Alias.kt row shows.",
.tried = "Preserved the original generic/destructuring/loop fixture and raw carriers. "
"Varied the file: a loop or destructuring does not block the alias, a class "
"with one member function does. The call is left unbound in both drivers."},
{.case_id = "original-callable-reference",
.site = {.file = "app/Main.kt",
.caller = "app.Main.aliasCaller",
.needle = "projectHandler",
.occurrence_index = 1},
.relation = "CALL_REFERENCE",
.target = {.file = "app/Main.kt",
.suffix = "app.Main.projectHandler",
.label = "Function",
.declaration_line = 8,
.declaration_text = "fun projectHandler(): Int = 7"},
.mode = RQ_REQUIRED},
{.case_id = "binary-plus-quality",
.site = {.file = "app/Main.kt",
.caller = "app.Main.operatorCaller",
.needle = "left + right",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Main.kt",
.suffix = "app.Main.Box.plus",
.label = "Method",
.declaration_line = 13,
.declaration_text = "class Box { operator fun plus(other: Box): Box = this }"},
.mode = RQ_REQUIRED},
{.case_id = "required-clean-callable-alias",
.site = {.file = "quality/Alias.kt",
.caller = "quality.Alias.aliasControl",
.needle = "callback()"},
.relation = "CALLS",
.target = {.file = "quality/Alias.kt",
.suffix = "quality.Alias.handler",
.label = "Function",
.declaration_line = 2,
.declaration_text = "fun handler() {}"},
.mode = RQ_REQUIRED},
{.case_id = "required-clean-callable-reference",
.site = {.file = "quality/Alias.kt",
.caller = "quality.Alias.aliasControl",
.needle = "handler",
.occurrence_index = 1},
.relation = "CALL_REFERENCE",
.target = {.file = "quality/Alias.kt",
.suffix = "quality.Alias.handler",
.label = "Function",
.declaration_line = 2,
.declaration_text = "fun handler() {}"},
.mode = RQ_REQUIRED},
};
const char *project = "cbm_kt_target_policy";
CBMFileResult *raw = cbm_extract_file(main_source, (int)strlen(main_source), CBM_LANG_KOTLIN,
project, "app/Main.kt", 0, NULL, NULL);
ASSERT_NOT_NULL(raw);
ASSERT_FALSE(raw->has_error || raw->parse_incomplete);
kotlin_implicit_probe_t iterator = probe_kotlin_implicit_site(
raw, "loopBuiltIn", "iterator", "kotlin.IntArray.iterator", "lsp_kt_iterator");
kotlin_implicit_probe_t component = probe_kotlin_implicit_site(
raw, "destructureBuiltIn", "component1", "kotlin.Pair.component1", "lsp_kt_destructure");
cbm_free_result(raw);
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_kt_target_policy_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char collision_path[512];
char main_path[512];
snprintf(collision_path, sizeof(collision_path), "%s/collision/Collisions.kt", tmpdir);
snprintf(main_path, sizeof(main_path), "%s/app/Main.kt", tmpdir);
if (th_write_file(collision_path, collision_source) != 0 ||
th_write_file(main_path, main_source) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write Kotlin target-policy fixture");
}
char alias_path[512];
snprintf(alias_path, sizeof(alias_path), "%s/quality/Alias.kt", tmpdir);
ASSERT_EQ(th_write_file(alias_path, alias_source), 0);
cbm_file_info_t files[3] = {0};
files[0].path = collision_path;
files[0].rel_path = (char *)"collision/Collisions.kt";
files[0].language = CBM_LANG_KOTLIN;
files[1].path = main_path;
files[1].rel_path = (char *)"app/Main.kt";
files[1].language = CBM_LANG_KOTLIN;
files[2].path = alias_path;
files[2].rel_path = (char *)"quality/Alias.kt";
files[2].language = CBM_LANG_KOTLIN;
cbm_gbuf_t *sequential = run_sequential_with_lsp_cross(project, tmpdir, files, 3);
cbm_gbuf_t *parallel = run_parallel(project, tmpdir, files, 3, 2);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const cbm_gbuf_node_t *sequential_iterator = find_unique_node_by_name_label_qn_suffix(
sequential, "iterator", "Method", "Collisions.IntArray.iterator");
const cbm_gbuf_node_t *parallel_iterator = find_unique_node_by_name_label_qn_suffix(
parallel, "iterator", "Method", "Collisions.IntArray.iterator");
const cbm_gbuf_node_t *sequential_component = find_unique_node_by_name_label_qn_suffix(
sequential, "component1", "Method", "Collisions.Pair.component1");
const cbm_gbuf_node_t *parallel_component = find_unique_node_by_name_label_qn_suffix(
parallel, "component1", "Method", "Collisions.Pair.component1");
const bool sequential_nodes =
sequential_iterator && sequential_component &&
find_unique_callable_node_by_tail(sequential, "Main.loopBuiltIn") &&
find_unique_callable_node_by_tail(sequential, "Main.destructureBuiltIn");
const bool parallel_nodes =
parallel_iterator && parallel_component &&
find_unique_callable_node_by_tail(parallel, "Main.loopBuiltIn") &&
find_unique_callable_node_by_tail(parallel, "Main.destructureBuiltIn");
const bool sequential_alias = find_calls_edge_by_tails(sequential, "app.Main.aliasCaller",
"app.Main.projectHandler") != NULL;
const bool parallel_alias = find_calls_edge_by_tails(parallel, "app.Main.aliasCaller",
"app.Main.projectHandler") != NULL;
const bool sequential_operator = find_calls_edge_by_tails(sequential, "app.Main.operatorCaller",
"app.Main.Box.plus") != NULL;
const bool parallel_operator =
find_calls_edge_by_tails(parallel, "app.Main.operatorCaller", "app.Main.Box.plus") != NULL;
const bool sequential_wrong_iterator = has_edge_from_callable_to_node(
sequential, "Main.loopBuiltIn", sequential_iterator, "CALLS");
const bool parallel_wrong_iterator =
has_edge_from_callable_to_node(parallel, "Main.loopBuiltIn", parallel_iterator, "CALLS");
const bool sequential_wrong_component = has_edge_from_callable_to_node(
sequential, "Main.destructureBuiltIn", sequential_component, "CALLS");
const bool parallel_wrong_component = has_edge_from_callable_to_node(
parallel, "Main.destructureBuiltIn", parallel_component, "CALLS");
if (!iterator.carrier || !iterator.semantic || !iterator.exact_join || !component.carrier ||
!component.semantic || !component.exact_join || !sequential_nodes || !parallel_nodes ||
sequential_wrong_iterator || parallel_wrong_iterator || sequential_wrong_component ||
parallel_wrong_component) {
printf(" Kotlin target-policy diagnostic: iterator_raw=%d/%d/%d "
"component_raw=%d/%d/%d nodes=%d/%d alias=%d/%d operator=%d/%d "
"wrong_iterator=%d/%d wrong_component=%d/%d\n",
iterator.carrier, iterator.semantic, iterator.exact_join, component.carrier,
component.semantic, component.exact_join, sequential_nodes, parallel_nodes,
sequential_alias, parallel_alias, sequential_operator, parallel_operator,
sequential_wrong_iterator, parallel_wrong_iterator, sequential_wrong_component,
parallel_wrong_component);
}
RQ_Report quality[2] = {0};
ASSERT(parallel_quality_check(sequential, project, "kotlin-protocol-0", sources, 3, expected, 5,
&quality[0]));
ASSERT_EQ(quality[0].genuine, 5);
ASSERT_EQ(quality[0].found, 4);
ASSERT_EQ(quality[0].missing, 1);
ASSERT_EQ(quality[0].required_missing + quality[0].wrong, 0);
ASSERT(parallel_quality_check(parallel, project, "kotlin-protocol-1", sources, 3, expected, 5,
&quality[1]));
ASSERT_EQ(quality[1].genuine, 5);
ASSERT_EQ(quality[1].found, 4);
ASSERT_EQ(quality[1].missing, 1);
ASSERT_EQ(quality[1].required_missing + quality[1].wrong, 0);
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
th_rmtree(tmpdir);
ASSERT_TRUE(iterator.carrier);
ASSERT_TRUE(iterator.semantic);
ASSERT_TRUE(iterator.exact_join);
ASSERT_TRUE(component.carrier);
ASSERT_TRUE(component.semantic);
ASSERT_TRUE(component.exact_join);
ASSERT_TRUE(sequential_nodes);
ASSERT_TRUE(parallel_nodes);
ASSERT_FALSE(sequential_wrong_iterator);
ASSERT_FALSE(parallel_wrong_iterator);
ASSERT_FALSE(sequential_wrong_component);
ASSERT_FALSE(parallel_wrong_component);
PASS();
}
/* Kotlin operator carriers and semantic joins remain required independently
* of strict edge admission. All six genuine invocations bind to the operator
* their receiver's declared type selects; false type-test/index targets stay
* forbidden. A cross-file C control prevents empty graph parity. */
TEST(parallel_kotlin_nonbinary_operator_carriers_reach_graph) {
static const char definitions_source[] =
"package app\n"
"class UnaryBox { operator fun unaryMinus(): UnaryBox = this }\n"
"class Bag { operator fun contains(value: Int): Boolean = true }\n"
"class Slots { operator fun get(index: Int): Int = index }\n"
"class Counter {\n"
" operator fun inc(): Counter = this\n"
" operator fun dec(): Counter = this\n"
"}\n";
static const char callers_source[] = "package app\n"
"fun negate(value: UnaryBox): UnaryBox = -value\n"
"fun hasValue(bag: Bag): Boolean = 1 in bag\n"
"fun lacksValue(bag: Bag): Boolean = 1 !in bag\n"
"fun lookup(slots: Slots): Int = slots[0]\n"
"fun increment(value: Counter): Counter { var current = "
"value; current++; return current }\n"
"fun decrement(value: Counter): Counter { var current = "
"value; current--; return current }\n"
"fun isBag(value: Any): Boolean = value is Bag\n"
"fun assign(slots: Slots) { slots[0] = 1 }\n";
static const char source[] = "package app\n"
"class UnaryBox { operator fun unaryMinus(): UnaryBox = this }\n"
"class Bag { operator fun contains(value: Int): Boolean = true }\n"
"class Slots { operator fun get(index: Int): Int = index }\n"
"class Counter {\n"
" operator fun inc(): Counter = this\n"
" operator fun dec(): Counter = this\n"
"}\n"
"fun negate(value: UnaryBox): UnaryBox = -value\n"
"fun hasValue(bag: Bag): Boolean = 1 in bag\n"
"fun lacksValue(bag: Bag): Boolean = 1 !in bag\n"
"fun lookup(slots: Slots): Int = slots[0]\n"
"fun increment(value: Counter): Counter { var current = value; "
"current++; return current }\n"
"fun decrement(value: Counter): Counter { var current = value; "
"current--; return current }\n"
"fun isBag(value: Any): Boolean = value is Bag\n"
"fun assign(slots: Slots) { slots[0] = 1 }\n";
const RQ_Source sources[] = {{.path = "app/Operators.kt", .text = definitions_source},
{.path = "app/Main.kt", .text = callers_source},
PAR_C_SOURCES[0],
PAR_C_SOURCES[1]};
const RQ_Expected expected[] = {
{.case_id = "unary-minus",
.site = {.file = "app/Main.kt",
.caller = "app.Main.negate",
.needle = "-value",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Operators.kt",
.suffix = "app.Operators.UnaryBox.unaryMinus",
.label = "Method",
.declaration_line = 2,
.declaration_text =
"class UnaryBox { operator fun unaryMinus(): UnaryBox = this }"},
.mode = RQ_REQUIRED},
{.case_id = "membership",
.site = {.file = "app/Main.kt",
.caller = "app.Main.hasValue",
.needle = "1 in bag",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Operators.kt",
.suffix = "app.Operators.Bag.contains",
.label = "Method",
.declaration_line = 3,
.declaration_text =
"class Bag { operator fun contains(value: Int): Boolean = true }"},
.mode = RQ_REQUIRED},
{.case_id = "negated-membership",
.site = {.file = "app/Main.kt",
.caller = "app.Main.lacksValue",
.needle = "1 !in bag",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Operators.kt",
.suffix = "app.Operators.Bag.contains",
.label = "Method",
.declaration_line = 3,
.declaration_text =
"class Bag { operator fun contains(value: Int): Boolean = true }"},
.mode = RQ_REQUIRED},
{.case_id = "index-read",
.site = {.file = "app/Main.kt",
.caller = "app.Main.lookup",
.needle = "slots[0]",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Operators.kt",
.suffix = "app.Operators.Slots.get",
.label = "Method",
.declaration_line = 4,
.declaration_text =
"class Slots { operator fun get(index: Int): Int = index }"},
.mode = RQ_REQUIRED},
{.case_id = "increment",
.site = {.file = "app/Main.kt",
.caller = "app.Main.increment",
.needle = "current++",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Operators.kt",
.suffix = "app.Operators.Counter.inc",
.label = "Method",
.declaration_line = 6,
.declaration_text = " operator fun inc(): Counter = this"},
.mode = RQ_REQUIRED},
{.case_id = "decrement",
.site = {.file = "app/Main.kt",
.caller = "app.Main.decrement",
.needle = "current--",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "app/Operators.kt",
.suffix = "app.Operators.Counter.dec",
.label = "Method",
.declaration_line = 7,
.declaration_text = " operator fun dec(): Counter = this"},
.mode = RQ_REQUIRED},
PAR_C_REQUIRED,
};
const char *project = "cbm_kt_nonbinary_operators";
CBMFileResult *raw = cbm_extract_file(source, (int)strlen(source), CBM_LANG_KOTLIN, project,
"app/Main.kt", 0, NULL, NULL);
ASSERT_NOT_NULL(raw);
ASSERT_FALSE(raw->has_error || raw->parse_incomplete);
kotlin_implicit_probe_t unary = probe_kotlin_implicit_site(
raw, "negate", "unaryMinus", "app.UnaryBox.unaryMinus", "lsp_kt_operator");
kotlin_implicit_probe_t contains = probe_kotlin_implicit_site(
raw, "hasValue", "contains", "app.Bag.contains", "lsp_kt_operator");
kotlin_implicit_probe_t not_contains = probe_kotlin_implicit_site(
raw, "lacksValue", "contains", "app.Bag.contains", "lsp_kt_operator");
kotlin_implicit_probe_t index =
probe_kotlin_implicit_site(raw, "lookup", "get", "app.Slots.get", "lsp_kt_operator");
kotlin_implicit_probe_t increment =
probe_kotlin_implicit_site(raw, "increment", "inc", "app.Counter.inc", "lsp_kt_operator");
kotlin_implicit_probe_t decrement =
probe_kotlin_implicit_site(raw, "decrement", "dec", "app.Counter.dec", "lsp_kt_operator");
kotlin_implicit_probe_t is_negative =
probe_kotlin_implicit_site(raw, "isBag", "contains", "app.Bag.contains", "lsp_kt_operator");
kotlin_implicit_probe_t assignment_negative =
probe_kotlin_implicit_site(raw, "assign", "get", "app.Slots.get", "lsp_kt_operator");
cbm_free_result(raw);
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_kt_nonbinary_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char definitions_path[512];
char callers_path[512];
snprintf(definitions_path, sizeof(definitions_path), "%s/app/Operators.kt", tmpdir);
snprintf(callers_path, sizeof(callers_path), "%s/app/Main.kt", tmpdir);
if (th_write_file(definitions_path, definitions_source) != 0 ||
th_write_file(callers_path, callers_source) != 0) {
th_rmtree(tmpdir);
FAIL("failed to write Kotlin nonbinary-operator fixture");
}
cbm_file_info_t files[4] = {0};
files[0].path = definitions_path;
files[0].rel_path = (char *)"app/Operators.kt";
files[0].language = CBM_LANG_KOTLIN;
files[1].path = callers_path;
files[1].rel_path = (char *)"app/Main.kt";
files[1].language = CBM_LANG_KOTLIN;
char control_paths[2][512];
ASSERT(parallel_quality_c_files(tmpdir, files + 2, control_paths));
cbm_gbuf_t *sequential = run_sequential_with_lsp_cross(project, tmpdir, files, 4);
cbm_gbuf_t *parallel = run_parallel(project, tmpdir, files, 4, 2);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const bool sequential_unary =
find_calls_edge_by_tails(sequential, "app.Main.negate",
"app.Operators.UnaryBox.unaryMinus") != NULL;
const bool parallel_unary =
find_calls_edge_by_tails(parallel, "app.Main.negate",
"app.Operators.UnaryBox.unaryMinus") != NULL;
const bool sequential_contains = find_calls_edge_by_tails(sequential, "app.Main.hasValue",
"app.Operators.Bag.contains") != NULL;
const bool parallel_contains = find_calls_edge_by_tails(parallel, "app.Main.hasValue",
"app.Operators.Bag.contains") != NULL;
const bool sequential_not_contains =
find_calls_edge_by_tails(sequential, "app.Main.lacksValue", "app.Operators.Bag.contains") !=
NULL;
const bool parallel_not_contains =
find_calls_edge_by_tails(parallel, "app.Main.lacksValue", "app.Operators.Bag.contains") !=
NULL;
const bool sequential_index =
find_calls_edge_by_tails(sequential, "app.Main.lookup", "app.Operators.Slots.get") != NULL;
const bool parallel_index =
find_calls_edge_by_tails(parallel, "app.Main.lookup", "app.Operators.Slots.get") != NULL;
const bool sequential_increment = find_calls_edge_by_tails(sequential, "app.Main.increment",
"app.Operators.Counter.inc") != NULL;
const bool parallel_increment = find_calls_edge_by_tails(parallel, "app.Main.increment",
"app.Operators.Counter.inc") != NULL;
const bool sequential_decrement = find_calls_edge_by_tails(sequential, "app.Main.decrement",
"app.Operators.Counter.dec") != NULL;
const bool parallel_decrement = find_calls_edge_by_tails(parallel, "app.Main.decrement",
"app.Operators.Counter.dec") != NULL;
const bool sequential_false_contains =
find_calls_edge_by_tails(sequential, "Main.isBag", "app.Operators.Bag.contains") != NULL;
const bool parallel_false_contains =
find_calls_edge_by_tails(parallel, "Main.isBag", "app.Operators.Bag.contains") != NULL;
const bool sequential_false_get =
find_calls_edge_by_tails(sequential, "Main.assign", "app.Operators.Slots.get") != NULL;
const bool parallel_false_get =
find_calls_edge_by_tails(parallel, "Main.assign", "app.Operators.Slots.get") != NULL;
if (!unary.carrier || !unary.semantic || !unary.exact_join || !contains.carrier ||
!contains.semantic || !contains.exact_join || !not_contains.carrier ||
!not_contains.semantic || !not_contains.exact_join || !index.carrier || !index.semantic ||
!index.exact_join || !increment.carrier || !increment.semantic || !increment.exact_join ||
!decrement.carrier || !decrement.semantic || !decrement.exact_join || is_negative.carrier ||
is_negative.semantic || assignment_negative.carrier || sequential_false_contains ||
parallel_false_contains || sequential_false_get || parallel_false_get) {
printf(" Kotlin nonbinary diagnostic: unary=%d/%d/%d/%d/%d "
"contains=%d/%d/%d/%d/%d notin=%d/%d/%d/%d/%d "
"index=%d/%d/%d/%d/%d inc=%d/%d/%d/%d/%d dec=%d/%d/%d/%d/%d "
"negative=%d/%d/%d/%d/%d/%d\n",
unary.carrier, unary.semantic, unary.exact_join, sequential_unary, parallel_unary,
contains.carrier, contains.semantic, contains.exact_join, sequential_contains,
parallel_contains, not_contains.carrier, not_contains.semantic,
not_contains.exact_join, sequential_not_contains, parallel_not_contains,
index.carrier, index.semantic, index.exact_join, sequential_index, parallel_index,
increment.carrier, increment.semantic, increment.exact_join, sequential_increment,
parallel_increment, decrement.carrier, decrement.semantic, decrement.exact_join,
sequential_decrement, parallel_decrement, is_negative.carrier, is_negative.semantic,
assignment_negative.carrier, sequential_false_contains, parallel_false_contains,
sequential_false_get || parallel_false_get);
}
RQ_Report quality[2] = {0};
ASSERT(parallel_quality_check(sequential, project, "kotlin-nonbinary-0", sources, 4, expected,
7, &quality[0]));
ASSERT_EQ(quality[0].genuine, 7);
ASSERT_EQ(quality[0].found, 7);
ASSERT_EQ(quality[0].missing, 0);
ASSERT_EQ(quality[0].required_missing + quality[0].wrong, 0);
ASSERT(parallel_quality_check(parallel, project, "kotlin-nonbinary-1", sources, 4, expected, 7,
&quality[1]));
ASSERT_EQ(quality[1].genuine, 7);
ASSERT_EQ(quality[1].found, 7);
ASSERT_EQ(quality[1].missing, 0);
ASSERT_EQ(quality[1].required_missing + quality[1].wrong, 0);
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
th_rmtree(tmpdir);
ASSERT_TRUE(unary.carrier);
ASSERT_TRUE(unary.semantic);
ASSERT_TRUE(unary.exact_join);
ASSERT_TRUE(contains.carrier);
ASSERT_TRUE(contains.semantic);
ASSERT_TRUE(contains.exact_join);
ASSERT_TRUE(not_contains.carrier);
ASSERT_TRUE(not_contains.semantic);
ASSERT_TRUE(not_contains.exact_join);
ASSERT_TRUE(index.carrier);
ASSERT_TRUE(index.semantic);
ASSERT_TRUE(index.exact_join);
ASSERT_TRUE(increment.carrier);
ASSERT_TRUE(increment.semantic);
ASSERT_TRUE(increment.exact_join);
ASSERT_TRUE(decrement.carrier);
ASSERT_TRUE(decrement.semantic);
ASSERT_TRUE(decrement.exact_join);
ASSERT_FALSE(is_negative.carrier);
ASSERT_FALSE(is_negative.semantic);
ASSERT_FALSE(assignment_negative.carrier);
ASSERT_FALSE(sequential_false_contains);
ASSERT_FALSE(parallel_false_contains);
ASSERT_FALSE(sequential_false_get);
ASSERT_FALSE(parallel_false_get);
PASS();
}
/* Build the smallest real Cargo workspace that exercises the direct parallel
* resolve API. crate_a is a declared workspace member, while unlisted is a
* graph-visible decoy with the same helper name. That decoy is intentional:
* without manifest-aware routing, a bare-name match cannot accidentally make
* the test green. The optional crate_b-local helper pins the second failure
* mode where a confident in-file resolution used to suppress cross-LSP. */
static cbm_gbuf_t *run_issue56_parallel_workspace(bool local_decoy) {
static const char workspace_toml[] = "[workspace]\n"
"members = [\"crate_a\", \"crate_b\"]\n"
"resolver = \"2\"\n";
static const char crate_a_toml[] =
"[package]\nname = \"crate_a\"\nversion = \"0.1.0\"\nedition = \"2021\"\n";
static const char crate_b_toml[] =
"[package]\nname = \"crate_b\"\nversion = \"0.1.0\"\nedition = \"2021\"\n"
"\n[dependencies]\ncrate_a = { path = \"../crate_a\" }\n";
static const char crate_a_source[] = "pub fn helper() {}\n";
static const char unlisted_source[] = "pub fn helper() {}\n";
static const char caller_without_local[] = "fn run() { crate_a::helper(); }\n"
"fn main() { run(); }\n";
static const char caller_with_local[] = "fn helper() {}\n"
"fn run() { crate_a::helper(); }\n"
"fn main() { run(); }\n";
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_issue56_parallel_XXXXXX");
if (!cbm_mkdtemp(tmpdir))
return NULL;
char crate_a[512];
char crate_a_src[512];
char crate_b[512];
char crate_b_src[512];
char unlisted[512];
char unlisted_src[512];
snprintf(crate_a, sizeof(crate_a), "%s/crate_a", tmpdir);
snprintf(crate_a_src, sizeof(crate_a_src), "%s/src", crate_a);
snprintf(crate_b, sizeof(crate_b), "%s/crate_b", tmpdir);
snprintf(crate_b_src, sizeof(crate_b_src), "%s/src", crate_b);
snprintf(unlisted, sizeof(unlisted), "%s/unlisted", tmpdir);
snprintf(unlisted_src, sizeof(unlisted_src), "%s/src", unlisted);
if (cbm_mkdir(crate_a) != 0 || cbm_mkdir(crate_a_src) != 0 || cbm_mkdir(crate_b) != 0 ||
cbm_mkdir(crate_b_src) != 0 || cbm_mkdir(unlisted) != 0 || cbm_mkdir(unlisted_src) != 0) {
th_rmtree(tmpdir);
return NULL;
}
char root_manifest[512];
char crate_a_manifest[512];
char crate_b_manifest[512];
char crate_a_file[512];
char crate_b_file[512];
char unlisted_file[512];
snprintf(root_manifest, sizeof(root_manifest), "%s/Cargo.toml", tmpdir);
snprintf(crate_a_manifest, sizeof(crate_a_manifest), "%s/Cargo.toml", crate_a);
snprintf(crate_b_manifest, sizeof(crate_b_manifest), "%s/Cargo.toml", crate_b);
snprintf(crate_a_file, sizeof(crate_a_file), "%s/lib.rs", crate_a_src);
snprintf(crate_b_file, sizeof(crate_b_file), "%s/main.rs", crate_b_src);
snprintf(unlisted_file, sizeof(unlisted_file), "%s/lib.rs", unlisted_src);
if (th_write_file(root_manifest, workspace_toml) != 0 ||
th_write_file(crate_a_manifest, crate_a_toml) != 0 ||
th_write_file(crate_b_manifest, crate_b_toml) != 0 ||
th_write_file(crate_a_file, crate_a_source) != 0 ||
th_write_file(crate_b_file, local_decoy ? caller_with_local : caller_without_local) != 0 ||
th_write_file(unlisted_file, unlisted_source) != 0) {
th_rmtree(tmpdir);
return NULL;
}
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
if (cbm_discover(tmpdir, &opts, &files, &file_count) != 0 || file_count < 3) {
cbm_discover_free(files, file_count);
th_rmtree(tmpdir);
return NULL;
}
cbm_gbuf_t *gbuf =
run_parallel("cbm_issue56_parallel", tmpdir, files, file_count, 2 /* real workers */);
RQ_Source sources[] = {{.path = "crate_a/src/lib.rs", .text = crate_a_source},
{.path = "crate_b/src/main.rs",
.text = local_decoy ? caller_with_local : caller_without_local},
{.path = "unlisted/src/lib.rs", .text = unlisted_source}};
RQ_Expected expected[] = {
{.case_id = "manifest-selected-crate-call",
.site = {.file = "crate_b/src/main.rs",
.caller = "main.run",
.needle = "crate_a::helper()"},
.relation = "CALLS",
.target = {.file = "crate_a/src/lib.rs",
.suffix = "crate_a.src.lib.helper",
.label = "Function",
.declaration_line = 1,
.declaration_text = "pub fn helper() {}"},
.mode = RQ_REQUIRED,
.gap_id = "rust-cross-crate-call-proof",
.why = "Manifest routing alone does not supply a cross-crate declaration proof.",
.tried = "Retained the declared path dependency, unlisted crate decoy and optional local "
"shadow; exact local calls remain required."},
{.case_id = "local-call-control",
.site = {.file = "crate_b/src/main.rs",
.caller = "main.main",
.needle = "run()",
.occurrence_index = 1},
.relation = "CALLS",
.target = {.file = "crate_b/src/main.rs",
.suffix = "crate_b.src.main.run",
.label = "Function",
.declaration_line = local_decoy ? 2 : 1,
.declaration_text = "fn run() { crate_a::helper(); }"},
.mode = RQ_REQUIRED}};
RQ_Report quality = {0};
bool quality_ok = gbuf &&
parallel_quality_check(gbuf, "cbm_issue56_parallel",
local_decoy ? "rust-manifest-shadow" : "rust-manifest",
sources, 3, expected, 2, &quality) &&
quality.genuine == 2 && quality.found == 2 && quality.missing == 0;
if (!quality_ok) {
cbm_gbuf_free(gbuf);
gbuf = NULL;
}
cbm_discover_free(files, file_count);
th_rmtree(tmpdir);
return gbuf;
}
TEST(parallel_rust_cross_crate_worker_receives_workspace_manifest) {
cbm_gbuf_t *gbuf = run_issue56_parallel_workspace(false);
ASSERT_NOT_NULL(gbuf);
const cbm_gbuf_edge_t *correct =
find_call_edge_to_target_fragment(gbuf, "main.run", ".crate_a.");
const bool correct_found = correct != NULL;
const bool unlisted = callable_has_call_target_fragment(gbuf, "main.run", ".unlisted.");
const bool manifest_strategy =
correct && correct->properties_json && strstr(correct->properties_json, "lsp_cross_crate");
if (!correct || unlisted || manifest_strategy) {
printf(" issue56 manifest diagnostic: correct=%d unlisted=%d strategy=%d\n", correct_found,
unlisted, manifest_strategy);
}
cbm_gbuf_free(gbuf);
/* The manifest names the dependency, so the call has a proved target. It is not a
* hybrid LSP row: the shared resolver admitted it. */
ASSERT_TRUE(correct_found);
ASSERT_FALSE(unlisted);
ASSERT_FALSE(manifest_strategy);
PASS();
}
TEST(parallel_rust_cross_crate_manifest_beats_confident_local_resolution) {
cbm_gbuf_t *gbuf = run_issue56_parallel_workspace(true);
ASSERT_NOT_NULL(gbuf);
const cbm_gbuf_edge_t *correct =
find_call_edge_to_target_fragment(gbuf, "main.run", ".crate_a.");
const bool correct_found = correct != NULL;
const bool wrong_local = callable_has_call_target_fragment(gbuf, "main.run", ".crate_b.");
const bool manifest_strategy =
correct && correct->properties_json && strstr(correct->properties_json, "lsp_cross_crate");
if (!correct || wrong_local || manifest_strategy) {
printf(" issue56 local-shadow diagnostic: correct=%d wrong_local=%d strategy=%d\n",
correct_found, wrong_local, manifest_strategy);
}
cbm_gbuf_free(gbuf);
/* crate_a::helper() names the crate; the local helper of the same name is a decoy. */
ASSERT_TRUE(correct_found);
ASSERT_FALSE(wrong_local);
ASSERT_FALSE(manifest_strategy);
PASS();
}
/* A known external macro semantic owns its parser occurrence even when the
* external target is not materialized as a graph node. It must not fall back
* to a same-named local function in either pipeline. */
TEST(parallel_rust_known_macro_does_not_fallback_to_local_function) {
static const char source[] = "fn println() {}\nfn run() { println!(\"macro\"); }\n";
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_rust_macro_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/main.rs", tmpdir);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen main.rs failed");
}
fputs(source, file);
fclose(file);
CBMFileResult *extracted = cbm_extract_file(source, (int)strlen(source), CBM_LANG_RUST,
"cbm_rust_macro", "main.rs", 0, NULL, NULL);
ASSERT_NOT_NULL(extracted);
const char *site = strstr(source, "println!(\"macro\")");
const uint32_t start = site ? (uint32_t)(site - source) : 0;
const uint32_t end = start + (uint32_t)strlen("println!(\"macro\")");
bool semantic_owner = false;
bool exact_gated_carrier = false;
for (int i = 0; i < extracted->resolved_calls.count; i++) {
const CBMResolvedCall *resolved = &extracted->resolved_calls.items[i];
if (resolved->caller_qn && strstr(resolved->caller_qn, "run") && resolved->callee_qn &&
strcmp(resolved->callee_qn, "std.macros.println") == 0 && resolved->strategy &&
strcmp(resolved->strategy, "lsp_macro") == 0) {
semantic_owner = true;
}
}
for (int i = 0; i < extracted->calls.count; i++) {
const CBMCall *call = &extracted->calls.items[i];
if (call->enclosing_func_qn && strstr(call->enclosing_func_qn, "run") &&
call->callee_name && strcmp(call->callee_name, "println") == 0 &&
call->site_start_byte == start && call->site_end_byte == end &&
call->requires_lsp_resolution) {
exact_gated_carrier = true;
}
}
cbm_free_result(extracted);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"main.rs";
files[0].language = CBM_LANG_RUST;
cbm_gbuf_t *sequential = run_sequential("cbm_rust_macro", tmpdir, files, 1);
cbm_gbuf_t *parallel = run_parallel("cbm_rust_macro", tmpdir, files, 1, 1);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const bool sequential_nodes = find_unique_callable_node_by_tail(sequential, "main.run") &&
find_unique_callable_node_by_tail(sequential, "main.println");
const bool parallel_nodes = find_unique_callable_node_by_tail(parallel, "main.run") &&
find_unique_callable_node_by_tail(parallel, "main.println");
const bool sequential_wrong =
find_calls_edge_by_tails(sequential, "main.run", "main.println") != NULL;
const bool parallel_wrong =
find_calls_edge_by_tails(parallel, "main.run", "main.println") != NULL;
if (!semantic_owner || !exact_gated_carrier || !sequential_nodes || !parallel_nodes ||
sequential_wrong || parallel_wrong) {
printf(" Rust macro fallback diagnostic: semantic=%d gated_carrier=%d seq_nodes=%d "
"par_nodes=%d seq_wrong=%d par_wrong=%d\n",
semantic_owner, exact_gated_carrier, sequential_nodes, parallel_nodes,
sequential_wrong, parallel_wrong);
}
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(semantic_owner);
ASSERT_TRUE(exact_gated_carrier);
ASSERT_TRUE(sequential_nodes);
ASSERT_TRUE(parallel_nodes);
ASSERT_FALSE(sequential_wrong);
ASSERT_FALSE(parallel_wrong);
PASS();
}
/* Attribute syntax retains DECORATES. Resolving its external symbol remains
* counted quality debt; the synthetic annotation node cannot authorize USAGE
* or calls to implementation details knowable only by executing the macro. */
TEST(parallel_rust_proc_macros_are_decorates_and_usage_only) {
static const char source[] = "#[tokio::main]\nasync fn main() {}\n"
"#[tracing::instrument]\nfn work() {}\n";
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_rust_proc_macro_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char path[512];
snprintf(path, sizeof(path), "%s/main.rs", tmpdir);
FILE *file = cbm_fopen(path, "wb");
if (!file) {
rmdir(tmpdir);
FAIL("fopen main.rs failed");
}
fputs(source, file);
fclose(file);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = (char *)"main.rs";
files[0].language = CBM_LANG_RUST;
cbm_gbuf_t *sequential = run_sequential("cbm_rust_proc_macro", tmpdir, files, 1);
cbm_gbuf_t *parallel = run_parallel("cbm_rust_proc_macro", tmpdir, files, 1, 1);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const bool seq_tokio_decorates = has_edge_from_callable_to_qn(
sequential, "main.main", "<decorator:tokio::main>", "DECORATES");
const bool seq_tokio_usage =
has_edge_from_callable_to_qn(sequential, "main.main", "<decorator:tokio::main>", "USAGE");
const bool par_tokio_decorates =
has_edge_from_callable_to_qn(parallel, "main.main", "<decorator:tokio::main>", "DECORATES");
const bool par_tokio_usage =
has_edge_from_callable_to_qn(parallel, "main.main", "<decorator:tokio::main>", "USAGE");
const bool seq_tracing_decorates = has_edge_from_callable_to_qn(
sequential, "main.work", "<decorator:tracing::instrument>", "DECORATES");
const bool seq_tracing_usage = has_edge_from_callable_to_qn(
sequential, "main.work", "<decorator:tracing::instrument>", "USAGE");
const bool par_tracing_decorates = has_edge_from_callable_to_qn(
parallel, "main.work", "<decorator:tracing::instrument>", "DECORATES");
const bool par_tracing_usage = has_edge_from_callable_to_qn(
parallel, "main.work", "<decorator:tracing::instrument>", "USAGE");
const bool seq_fake = callable_has_call_target_fragment(sequential, "main.main", "Runtime") ||
callable_has_call_target_fragment(sequential, "main.work", "Span.enter");
const bool par_fake = callable_has_call_target_fragment(parallel, "main.main", "Runtime") ||
callable_has_call_target_fragment(parallel, "main.work", "Span.enter");
if (!seq_tokio_decorates || seq_tokio_usage || !par_tokio_decorates || par_tokio_usage ||
!seq_tracing_decorates || seq_tracing_usage || !par_tracing_decorates ||
par_tracing_usage || seq_fake || par_fake) {
printf(" Rust proc-macro policy diagnostic: tokio seq=%d/%d par=%d/%d "
"tracing seq=%d/%d par=%d/%d fake=%d/%d\n",
seq_tokio_decorates, seq_tokio_usage, par_tokio_decorates, par_tokio_usage,
seq_tracing_decorates, seq_tracing_usage, par_tracing_decorates, par_tracing_usage,
seq_fake, par_fake);
}
const struct {
const char *id, *caller, *spelling;
} obligations[] = {{"rust-tokio-main-attribute", "main.main", "tokio::main"},
{"rust-tracing-instrument-attribute", "main.work", "tracing::instrument"}};
const RQ_Forbidden unproved_usage[] = {
{.case_id = "rust-tokio-main-unproved-usage",
.site = {.file = "main.rs", .caller = "main.main", .needle = "tokio::main"},
.relation = "USAGE",
.any_target = true},
{.case_id = "rust-tracing-instrument-unproved-usage",
.site = {.file = "main.rs", .caller = "main.work", .needle = "tracing::instrument"},
.relation = "USAGE",
.any_target = true},
};
cbm_gbuf_t *graphs[] = {sequential, parallel};
RQ_Source input = {.path = "main.rs", .text = source};
for (int route = 0; route < 2; route++) {
/* These authored sites close the caller/USAGE domains and make the
* source snapshot and graph/proof checks non-vacuous. The oracle's
* external ontology has no proc-macro symbol, so genuine unresolved
* external references are counted separately below. */
RQ_Report projection = {0};
ASSERT(parallel_quality_check_with_forbidden(
graphs[route], "cbm_rust_proc_macro",
route ? "macro-projection-parallel" : "macro-projection-sequential", &input, 1, NULL, 0,
unproved_usage, sizeof(unproved_usage) / sizeof(unproved_usage[0]), &projection));
size_t row_count = 0, genuine = 0, missing = 0;
const cbm_resolve_record_t *rows = cbm_gbuf_resolutions(graphs[route], &row_count);
for (size_t i = 0; i < sizeof(obligations) / sizeof(obligations[0]); i++) {
const char *site = strstr(source, obligations[i].spelling);
ASSERT_NOT_NULL(site);
uint32_t start = (uint32_t)(site - source);
uint32_t end = start + (uint32_t)strlen(obligations[i].spelling);
bool emitted = false, recorded = false;
for (size_t row = 0; row < row_count; row++) {
const cbm_resolve_record_t *record = &rows[row];
if (record->file_path && !strcmp(record->file_path, "main.rs") && record->scope &&
rq_suffix(record->scope, NULL, obligations[i].caller) && record->relation &&
!strcmp(record->relation, "USAGE") && record->start_byte <= start &&
record->end_byte >= end) {
recorded = true;
emitted |= record->emitted;
}
}
genuine++;
missing += !emitted;
ASSERT_FALSE(emitted);
printf(" resolver quality gap=%s source=main.rs:%u-%u external=%s "
"reason=external_macro_identity_unproved recorded=%d "
"tried=preserved_literal_attribute_without_dependency_declaration\n",
obligations[i].id, start, end, obligations[i].spelling, recorded);
}
ASSERT_EQ(genuine, 2);
ASSERT_EQ(missing, 2);
}
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
unlink(path);
rmdir(tmpdir);
/* DECORATES records the literal attribute. Its synthetic annotation node
* is not a proved external symbol and cannot support a USAGE edge. */
ASSERT_TRUE(seq_tokio_decorates);
ASSERT_FALSE(seq_tokio_usage);
ASSERT_TRUE(par_tokio_decorates);
ASSERT_FALSE(par_tokio_usage);
ASSERT_TRUE(seq_tracing_decorates);
ASSERT_FALSE(seq_tracing_usage);
ASSERT_TRUE(par_tracing_decorates);
ASSERT_FALSE(par_tracing_usage);
ASSERT_FALSE(seq_fake);
ASSERT_FALSE(par_fake);
PASS();
}
static int assert_c_family_preprocessed_collision_graph(CBMLanguage language, const char *extension,
const char *project, const char *source,
const char *target_tail) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_c_origin_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char rel_path[64];
snprintf(rel_path, sizeof(rel_path), "main.%s", extension);
char path[512];
snprintf(path, sizeof(path), "%s/%s", tmpdir, rel_path);
FILE *file = cbm_fopen(path, "w");
if (!file) {
rmdir(tmpdir);
FAIL("fopen C-family fixture failed");
}
fputs(source, file);
fclose(file);
cbm_file_info_t files[1] = {0};
files[0].path = path;
files[0].rel_path = rel_path;
files[0].language = language;
cbm_gbuf_t *sequential = run_sequential(project, tmpdir, files, 1);
cbm_gbuf_t *parallel = run_parallel(project, tmpdir, files, 1, 1);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const cbm_gbuf_edge_t *sequential_edge =
find_calls_edge_by_tails(sequential, "main.occurrence_probe", target_tail);
const cbm_gbuf_edge_t *parallel_edge =
find_calls_edge_by_tails(parallel, "main.occurrence_probe", target_tail);
if (!sequential_edge || !parallel_edge) {
printf(" C-family origin diagnostic (%s): sequential=%s parallel=%s\n", extension,
sequential_edge ? "present" : "absent", parallel_edge ? "present" : "absent");
}
ASSERT_NOT_NULL(sequential_edge);
ASSERT_NOT_NULL(parallel_edge);
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
unlink(path);
rmdir(tmpdir);
return 0;
}
TEST(parallel_c_preprocessed_coordinate_collision_preserves_hidden_target) {
static const char source[] =
"static int alpha_target(void){return 1;}\n"
"static int bravo_target(void){return 2;}\n"
"int occurrence_probe(void){\n"
" int (*fp)(void) = alpha_target;\n"
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ fp ( );\n"
"#define HIDDEN_FP_CALL() (fp = bravo_target, fp())\n"
" return HIDDEN_FP_CALL();\n"
"}\n";
return assert_c_family_preprocessed_collision_graph(CBM_LANG_C, "c", "cbm_c_origin", source,
"main.bravo_target");
}
static int assert_cpp_like_preprocessed_collision_graph(CBMLanguage language, const char *extension,
const char *project) {
static const char source[] =
"struct Alpha { int render(){return 1;} };\n"
"struct Bravo { int render(){return 2;} };\n"
"int occurrence_probe(){\n"
" Alpha alpha; Bravo bravo;\n"
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ alpha . render ( );\n"
"#define HIDDEN_RENDER(x) x.render()\n"
" return HIDDEN_RENDER(bravo);\n"
"}\n";
return assert_c_family_preprocessed_collision_graph(language, extension, project, source,
"Bravo.render");
}
TEST(parallel_cpp_preprocessed_coordinate_collision_preserves_hidden_target) {
return assert_cpp_like_preprocessed_collision_graph(CBM_LANG_CPP, "cpp", "cbm_cpp_origin");
}
TEST(parallel_cuda_preprocessed_coordinate_collision_preserves_hidden_target) {
return assert_cpp_like_preprocessed_collision_graph(CBM_LANG_CUDA, "cu", "cbm_cuda_origin");
}
static const char PAR_JVM_JAVA[] = "package com.example;\n"
"\n"
"class JavaCaller {\n"
" String call(KotlinService kotlinService) {\n"
" return kotlinService.ping(new JavaService());\n"
" }\n"
"}\n"
"\n"
"class JavaService {\n"
" String pong() {\n"
" return \"pong\";\n"
" }\n"
"}\n";
static const char PAR_JVM_KOTLIN[] = "package com.example\n"
"\n"
"class KotlinService {\n"
" fun ping(javaService: JavaService): String {\n"
" return javaService.pong()\n"
" }\n"
"}\n";
static const char PAR_QUALITY_SERVICE[] = "package api\n"
"class Service {\n"
" fun keep(value: Int): Int = value\n"
"}\n";
static const char PAR_QUALITY_CALLER[] =
"package client\n"
"import api.Service\n"
"fun direct(value: Service, number: Int): Int = value.keep(number)\n";
static const RQ_Source PAR_JVM_SOURCES[] = {
{.path = "src/main/java/com/example/Example.java", .text = PAR_JVM_JAVA},
{.path = "src/main/kotlin/com/example/KotlinService.kt", .text = PAR_JVM_KOTLIN},
{.path = "src/main/kotlin/quality/Service.kt", .text = PAR_QUALITY_SERVICE},
{.path = "src/main/kotlin/quality/Control.kt", .text = PAR_QUALITY_CALLER},
};
static const RQ_Expected PAR_JVM_EXPECTED[] = {
{.case_id = "java-reference-argument",
.site = {.file = "src/main/java/com/example/Example.java",
.caller = "src.main.java.com.example.JavaCaller.call",
.needle = "kotlinService.ping(new JavaService())",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "src/main/kotlin/com/example/KotlinService.kt",
.suffix = "src.main.kotlin.com.example.KotlinService.KotlinService.ping",
.label = "Method",
.declaration_line = 4,
.declaration_text = " fun ping(javaService: JavaService): String {"},
.mode = RQ_REQUIRED},
{.case_id = "kotlin-java-package-access",
.site = {.file = "src/main/kotlin/com/example/KotlinService.kt",
.caller = "src.main.kotlin.com.example.KotlinService.KotlinService.ping",
.needle = "javaService.pong()",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "src/main/java/com/example/Example.java",
.suffix = "src.main.java.com.example.JavaService.pong",
.label = "Method",
.declaration_line = 10,
.declaration_text = " String pong() {"},
.mode = RQ_REQUIRED},
{.case_id = "java-implicit-constructor",
.site = {.file = "src/main/java/com/example/Example.java",
.caller = "src.main.java.com.example.JavaCaller.call",
.needle = "new JavaService()"},
.relation = "CALLS",
/* No constructor is declared, so the class stands for the implicit one, as it
* does for every Java and C# `new` (test_resolver_oo.c). */
.target = {.file = "src/main/java/com/example/Example.java",
.suffix = "src.main.java.com.example.JavaService",
.label = "Class",
.declaration_line = 9,
.declaration_text = "class JavaService {"},
.mode = RQ_REQUIRED},
{.case_id = "required-cross-file-scalar-control",
.site = {.file = "src/main/kotlin/quality/Control.kt",
.caller = "src.main.kotlin.quality.Control.direct",
.needle = "value.keep(number)",
.occurrence_index = 0},
.relation = "CALLS",
.target = {.file = "src/main/kotlin/quality/Service.kt",
.suffix = "src.main.kotlin.quality.Service.Service.keep",
.label = "Method",
.declaration_line = 3,
.declaration_text = " fun keep(value: Int): Int = value"}},
};
/* The receiver declaration and method exist only in the other file.
* Per-file extraction cannot prove this target; cross-file source proof
* must run, including when the caller bytes have to be reread. */
static bool parallel_quality_control_files(const char *root, cbm_file_info_t *files,
char paths[2][512]) {
for (int i = 0; i < 2; i++) {
const RQ_Source *source = &PAR_JVM_SOURCES[i + 2];
snprintf(paths[i], sizeof(paths[i]), "%s/%s", root, source->path);
if (th_write_file(paths[i], source->text) != 0)
return false;
files[i].path = paths[i];
files[i].rel_path = (char *)source->path;
files[i].language = CBM_LANG_KOTLIN;
}
return true;
}
TEST(parallel_java_kotlin_lsp_override_cross_file_emits_lsp_strategy_edges) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_jvm_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char jpath[512];
snprintf(jpath, sizeof(jpath), "%s/src/main/java/com/example/Example.java", tmpdir);
char jdir[512];
snprintf(jdir, sizeof(jdir), "%s/src/main/java/com/example", tmpdir);
cbm_mkdir_p(jdir, 0755);
FILE *jf = cbm_fopen(jpath, "wb");
if (!jf) {
FAIL("fopen example.java failed");
}
fputs(PAR_JVM_JAVA, jf);
fclose(jf);
char kpath[512];
snprintf(kpath, sizeof(kpath), "%s/src/main/kotlin/com/example/KotlinService.kt", tmpdir);
char kdir[512];
snprintf(kdir, sizeof(kdir), "%s/src/main/kotlin/com/example", tmpdir);
cbm_mkdir_p(kdir, 0755);
FILE *kf = cbm_fopen(kpath, "wb");
if (!kf) {
unlink(jpath);
rmdir(tmpdir);
FAIL("fopen example.kt failed");
}
fputs(PAR_JVM_KOTLIN, kf);
fclose(kf);
cbm_file_info_t files[4] = {0};
files[0].path = jpath;
files[0].rel_path = (char *)"src/main/java/com/example/Example.java";
files[0].language = CBM_LANG_JAVA;
files[1].path = kpath;
files[1].rel_path = (char *)"src/main/kotlin/com/example/KotlinService.kt";
files[1].language = CBM_LANG_KOTLIN;
char control_paths[2][512];
ASSERT(parallel_quality_control_files(tmpdir, files + 2, control_paths));
cbm_gbuf_t *gbuf = run_parallel("com", tmpdir, files, 4, 2);
ASSERT_NOT_NULL(gbuf);
RQ_Report quality = {0};
ASSERT(parallel_quality_check(gbuf, "com", "jvm-cross-file", PAR_JVM_SOURCES, 4,
PAR_JVM_EXPECTED, 4, &quality));
ASSERT_EQ(quality.genuine, 4);
ASSERT_EQ(quality.found, 4);
ASSERT_EQ(quality.missing, 0);
ASSERT_EQ(quality.required_missing + quality.wrong, 0);
cbm_gbuf_free(gbuf);
th_rmtree(tmpdir);
PASS();
}
/* #2053 contract for cbm_pipeline_rust_external_target: only a Rust row whose
* strategy names a registered target, and whose QN lies outside the project
* prefix, counts as external. The end-to-end probes live in test_pipeline.c
* (pipeline_rust_std_receiver_never_binds_project_method*). */
TEST(parallel_rust_external_target_contract) {
const char *proj = "proj";
/* External: registered std / seeded-crate targets. */
ASSERT_TRUE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_method_dispatch",
"std.path.Path.join", proj));
ASSERT_TRUE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_deref_dispatch",
"std.path.Path.join", proj));
ASSERT_TRUE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_constructor",
"core.sync.atomic.AtomicUsize.new", proj));
/* A prefix that is not a whole segment is still outside the project. */
ASSERT_TRUE(
cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_direct", "projx.helper", proj));
/* Project-prefixed targets keep the registry fallback. */
ASSERT_FALSE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_method_dispatch",
"proj.src.lib.EvidenceTier.join", proj));
/* Synthesized / non-registered strategies are not evidence. */
ASSERT_FALSE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_prelude_trait",
"std.path.PathBuf.clone", proj));
ASSERT_FALSE(
cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_unresolved", "root.join", proj));
/* Per-language: no other language is affected. */
ASSERT_FALSE(cbm_pipeline_rust_external_target(CBM_LANG_GO, "lsp_method_dispatch",
"std.path.Path.join", proj));
/* Defensive NULL/empty inputs. */
ASSERT_FALSE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, NULL, "std.x", proj));
ASSERT_FALSE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_direct", "std.x", NULL));
ASSERT_FALSE(cbm_pipeline_rust_external_target(CBM_LANG_RUST, "lsp_direct", "", proj));
PASS();
}
/* A class/method suffix never establishes declaration or caller identity.
* Even the legacy opt-in argument must not recover a coincidental JVM tail. */
TEST(parallel_lsp_tail_match_fallbacks_disabled_for_all_languages) {
for (int language = 0; language < CBM_LANG_COUNT; language++)
ASSERT_FALSE(cbm_pipeline_lsp_allow_tail_match((CBMLanguage)language));
/* Wrong-module coincidence: the resolved entry's caller shares only
* the "Service.handle" tail with the textual call's enclosing
* function, so the exact caller_qn pass misses and only the tail
* fallback could join them. */
CBMResolvedCall rc_item = {0};
rc_item.caller_qn = "com.example.pkg.Service.handle";
rc_item.callee_qn = "com.example.pkg.Helper.run";
rc_item.strategy = "lsp";
rc_item.confidence = 0.9f;
CBMResolvedCallArray arr = {0};
arr.items = &rc_item;
arr.count = 1;
arr.cap = 1;
CBMCall call = {0};
call.enclosing_func_qn = "proj.other_mod.Service.handle";
call.callee_name = "helper.run";
ASSERT_TRUE(cbm_pipeline_find_lsp_resolution(&arr, &call, false) == NULL);
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&arr, &call, true));
/* Equal byte coordinates and a caller leaf still do not connect a package
* declaration to a file declaration. This needs an explicit identity map. */
CBMResolvedCall top_level_rows[2] = {0};
top_level_rows[0].caller_qn = "app.operatorCaller";
top_level_rows[0].callee_qn = "app.Box.plus";
top_level_rows[0].strategy = "lsp_kt_operator";
top_level_rows[0].confidence = 0.9f;
top_level_rows[0].site_start_byte = 80;
top_level_rows[0].site_end_byte = 92;
top_level_rows[0].source_origin = CBM_SOURCE_ORIGIN_RAW;
CBMResolvedCallArray top_level_arr = {0};
top_level_arr.items = top_level_rows;
top_level_arr.count = 1;
top_level_arr.cap = 2;
CBMCall top_level_call = {0};
top_level_call.enclosing_func_qn = "proj.app.Main.operatorCaller";
top_level_call.callee_name = "plus";
top_level_call.site_start_byte = 80;
top_level_call.site_end_byte = 92;
top_level_call.source_origin = CBM_SOURCE_ORIGIN_RAW;
top_level_call.requires_lsp_resolution = true;
ASSERT_TRUE(cbm_pipeline_find_lsp_resolution(&top_level_arr, &top_level_call, false) == NULL);
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&top_level_arr, &top_level_call, true));
top_level_call.enclosing_func_qn = "proj.app.Main.differentCaller";
ASSERT_TRUE(cbm_pipeline_find_lsp_resolution(&top_level_arr, &top_level_call, true) == NULL);
top_level_call.enclosing_func_qn = "proj.app.Main.operatorCaller";
top_level_rows[1] = top_level_rows[0];
top_level_rows[1].callee_qn = "app.Other.plus";
top_level_arr.count = 2;
ASSERT_TRUE(cbm_pipeline_find_lsp_resolution(&top_level_arr, &top_level_call, true) == NULL);
/* Target-node fallback: callee_qn misses both as-is and
* project-prefixed; exactly one node coincidentally shares the
* "Helper.run" tail in an unrelated module. */
cbm_gbuf_t *tgbuf = cbm_gbuf_new("proj", "/tmp");
ASSERT_NOT_NULL(tgbuf);
int64_t nid = cbm_gbuf_upsert_node(tgbuf, "Method", "run", "proj.zeta.Helper.run",
"zeta/helper.py", 1, 3, NULL);
ASSERT_TRUE(nid != 0);
ASSERT_TRUE(cbm_pipeline_lsp_target_node(tgbuf, "proj", "com.other.Helper.run", false) == NULL);
const cbm_gbuf_node_t *jvm_hit =
cbm_pipeline_lsp_target_node(tgbuf, "proj", "com.other.Helper.run", true);
ASSERT_NULL(jvm_hit);
const cbm_gbuf_node_t *exact =
cbm_pipeline_lsp_target_node(tgbuf, "proj", "proj.zeta.Helper.run", true);
ASSERT_NOT_NULL(exact);
ASSERT_EQ(exact->id, nid);
/* A synthetic external protocol target has no source-level evidence for
* that Class.method-tail equivalence. Its strict lookup must therefore
* remain exact and fail closed on the same coincidence. */
ASSERT_TRUE(cbm_pipeline_lsp_target_node_strict(tgbuf, "proj", "com.other.Helper.run", true) ==
NULL);
cbm_gbuf_free(tgbuf);
PASS();
}
TEST(parallel_python_lsp_override_emits_lsp_strategy_edges) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_pylsp_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
/* Single-file scenario: pins the in-file LSP path where py_lsp
* registers Greeter from the file's own defs, types `g = Greeter()`
* as NAMED("…Greeter"), and resolves `g.hello()` to Greeter.hello
* via attribute lookup. callee_qn matches the gbuf QN directly. The
* cross-file equivalent is covered by
* parallel_python_lsp_override_cross_file_emits_lsp_strategy_edges,
* which exercises the project-prefix fallback in
* cbm_pipeline_lsp_target_node. */
char fpath0[512];
snprintf(fpath0, sizeof(fpath0), "%s/app.py", tmpdir);
FILE *f = fopen(fpath0, "w");
if (!f) {
FAIL("fopen app.py failed");
}
fprintf(f, "class Greeter:\n"
" def hello(self):\n"
" return 'hi'\n"
"\n"
"def main():\n"
" g = Greeter()\n"
" g.hello()\n");
fclose(f);
cbm_file_info_t files[1] = {0};
files[0].path = fpath0;
files[0].rel_path = (char *)"app.py";
files[0].language = CBM_LANG_PYTHON;
cbm_gbuf_t *gbuf = run_parallel("cbm_par_pylsp", tmpdir, files, 1, 1);
ASSERT_NOT_NULL(gbuf);
lsp_edge_count_ctx_t c = {0};
cbm_gbuf_foreach_edge(gbuf, count_lsp_call_edges, &c);
/* Sanity: extraction produced at least one call edge. */
ASSERT_GT(c.total_calls, 0);
/* The exact receiver target and public proof-backed provenance survive
* projection; legacy lsp_* labels no longer authorize graph edges. */
ASSERT_GT(c.proof_strategy_count, 0);
ASSERT_NOT_NULL(find_calls_edge_by_tails(gbuf, "app.main", "Greeter.hello"));
cbm_gbuf_free(gbuf);
unlink(fpath0);
rmdir(tmpdir);
PASS();
}
/* ── Go cross-package field-chain fixture (field_defs fold) ─────── */
/* Production's sequential driver seeds Folder nodes via pass_structure; the
* compact harness (run_sequential_with_lsp_cross_*) does not. Go imports
* resolve to Folder nodes, so a Go import-map fixture must seed File and
* Folder nodes itself — replicating the production shape. */
static cbm_gbuf_t *run_go_field_chain_sequential(const char *project, const char *repo_path,
cbm_file_info_t *files, int file_count) {
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, repo_path);
cbm_registry_t *reg = cbm_registry_new();
CBMFileResult **cache = (CBMFileResult **)calloc((size_t)file_count, sizeof(CBMFileResult *));
if (!gbuf || !reg || !cache) {
cbm_gbuf_free(gbuf);
cbm_registry_free(reg);
free(cache);
return NULL;
}
atomic_int cancelled;
atomic_init(&cancelled, 0);
cbm_pipeline_ctx_t ctx = {
.project_name = project,
.repo_path = repo_path,
.gbuf = gbuf,
.registry = reg,
.cancelled = &cancelled,
.result_cache = cache,
};
seed_test_file_nodes(gbuf, project, files, file_count);
char *svc_dir_qn = cbm_pipeline_fqn_folder(project, "svc");
if (svc_dir_qn) {
cbm_gbuf_upsert_node(gbuf, "Folder", "svc", svc_dir_qn, "svc", 0, 0, "{}");
free(svc_dir_qn);
}
cbm_init();
bool ok = cbm_pipeline_pass_definitions(&ctx, files, file_count) == 0 &&
cbm_shared_resolver_capture_sources(&ctx, files, file_count, cache);
if (ok) {
int cross_status = cbm_pipeline_pass_lsp_cross(&ctx, files, file_count, cache);
ok = cross_status != CBM_PIPELINE_ABORT_PRESERVE_DB &&
!atomic_load(&ctx.test_declarations_failed);
}
if (ok) {
ctx.shared_resolver = cbm_shared_resolver_build(&ctx, files, file_count, cache);
ok = ctx.shared_resolver != NULL;
}
if (ok)
ok = cbm_pipeline_pass_calls(&ctx, files, file_count) == 0 &&
cbm_pipeline_pass_usages(&ctx, files, file_count) == 0 &&
cbm_pipeline_pass_semantic(&ctx, files, file_count) == 0 &&
harness_resolve_occurrences(&ctx, files, file_count) &&
cbm_shared_resolver_project(gbuf);
/* CBM_GO_FIELD_DIAG dump. NOTE: resolved-call records may borrow QN
* strings across file results (cross-file resolution in pass_lsp_cross),
* so every result must stay alive while ANY is inspected. Print all
* first, free all second. */
if (getenv("CBM_GO_FIELD_DIAG")) {
for (int i = 0; i < file_count; i++) {
if (!cache[i]) {
continue;
}
const CBMFileResult *r = cache[i];
printf(" [diag] file %s defs=%d imports=%d calls=%d resolved=%d\n", files[i].rel_path,
r->defs.count, r->imports.count, r->calls.count, r->resolved_calls.count);
for (int j = 0; j < r->resolved_calls.count; j++) {
const CBMResolvedCall *rc = &r->resolved_calls.items[j];
printf(" [diag] rc caller=%s callee=%s strategy=%s conf=%.2f kind=%d "
"span=[%u,%u)\n",
rc->caller_qn ? rc->caller_qn : "?", rc->callee_qn ? rc->callee_qn : "?",
rc->strategy ? rc->strategy : "?", rc->confidence, (int)rc->kind,
(unsigned)rc->site_start_byte, (unsigned)rc->site_end_byte);
}
for (int j = 0; j < r->calls.count; j++) {
const CBMCall *c = &r->calls.items[j];
printf(" [diag] call callee=%s enclosing=%s span=[%u,%u) req=%d\n",
c->callee_name ? c->callee_name : "?",
c->enclosing_func_qn ? c->enclosing_func_qn : "?",
(unsigned)c->site_start_byte, (unsigned)c->site_end_byte,
(int)c->requires_lsp_resolution);
}
for (int j = 0; j < r->defs.count; j++) {
const CBMDefinition *d = &r->defs.items[j];
printf(" [diag] def label=%s qn=%s parent=%s ret=%s\n",
d->label ? d->label : "?", d->qualified_name ? d->qualified_name : "?",
d->parent_class ? d->parent_class : "?",
d->return_type ? d->return_type : "?");
}
}
}
if (!ok || atomic_load(&ctx.test_declarations_failed)) {
cbm_gbuf_free(gbuf);
gbuf = NULL;
}
cbm_shared_resolver_free(ctx.shared_resolver);
ctx.shared_resolver = NULL;
for (int i = 0; i < file_count; i++) {
cbm_free_result(cache[i]);
}
free(cache);
ctx.result_cache = NULL;
harness_ctx_free_tables(&ctx);
cbm_registry_free(reg);
if (ctx.seq_cross_arena_live) {
cbm_arena_destroy(&ctx.seq_cross_arena);
ctx.seq_cross_arena_live = false;
}
if (ctx.seq_cross_def_modules) {
for (int i = 0; i < ctx.seq_cross_def_module_count; i++) {
free(ctx.seq_cross_def_modules[i]);
}
free(ctx.seq_cross_def_modules);
ctx.seq_cross_def_modules = NULL;
}
return gbuf;
}
static const char PAR_GO_SERVICE[] = "package service\n"
"\n"
"import (\n"
" \"context\"\n"
" pb \"acme-sdk/apis/order\"\n"
")\n"
"\n"
"type OrderService struct{}\n"
"\n"
"func (s *OrderService) PlaceOrder(ctx context.Context, req "
"*pb.PlaceOrderReq) (*pb.PlaceOrderRsp, error) {\n"
" return nil, nil\n"
"}\n"
"\n"
"func (s *OrderService) ListOrders(ctx context.Context, req "
"*pb.ListOrdersReq) (*pb.ListOrdersRsp, error) {\n"
" return nil, nil\n"
"}\n";
static const char PAR_GO_HANDLER[] = "package handler\n"
"\n"
"import (\n"
" \"context\"\n"
"\n"
" pb \"acme-sdk/apis/order\"\n"
"\n"
" \"acme-order/internal/service\"\n"
")\n"
"\n"
"type OrderHandler struct {\n"
" pb.UnimplementedOrderServer\n"
" cartSvc *service.CartService\n"
" userSvc *service.UserService\n"
" orderSvc *service.OrderService\n"
" inventorySvc *service.InventoryService\n"
" paymentSvc *service.PaymentService\n"
" shipmentSvc *service.ShipmentService\n"
" couponSvc *service.CouponService\n"
" searchSvc *service.SearchService\n"
"}\n"
"\n"
"func (h *OrderHandler) ListOrders(ctx context.Context, req "
"*pb.ListOrdersReq) (*pb.ListOrdersRsp, error) {\n"
" return h.orderSvc.ListOrders(ctx, req)\n"
"}\n"
"\n"
"func (h *OrderHandler) PlaceOrder(ctx context.Context, req "
"*pb.PlaceOrderReq) (*pb.PlaceOrderRsp, error) {\n"
" return h.orderSvc.PlaceOrder(ctx, req)\n"
"}\n"
"\n"
"func (h *OrderHandler) UpdateCart(ctx context.Context, req "
"*pb.UpdateCartReq) (*pb.UpdateCartRsp, error) {\n"
" return h.cartSvc.UpdateCart(ctx, req)\n"
"}\n";
/* Preserve the original cross-package field-chain fixture as counted quality
* debt: its module root and external protobuf types are not certified. Both
* declared OrderService calls remain in the denominator, and an independent
* C prototype/definition call must resolve through this same cross harness. */
TEST(parallel_go_cross_package_field_chain_resolves) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_gofold_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char svc_path[512];
char app_path[512];
snprintf(svc_path, sizeof(svc_path), "%s/acme-order/internal/service/order_service.go", tmpdir);
snprintf(app_path, sizeof(app_path), "%s/acme-order/internal/handler/order_handler.go", tmpdir);
if (th_write_file(svc_path, PAR_GO_SERVICE) != 0 ||
th_write_file(app_path, PAR_GO_HANDLER) != 0) {
th_rmtree(tmpdir);
FAIL("write fixture failed");
}
cbm_file_info_t files[4] = {0};
files[0].path = svc_path;
files[0].rel_path = (char *)"acme-order/internal/service/order_service.go";
files[0].language = CBM_LANG_GO;
files[1].path = app_path;
files[1].rel_path = (char *)"acme-order/internal/handler/order_handler.go";
files[1].language = CBM_LANG_GO;
char control_paths[2][512];
ASSERT(parallel_quality_c_files(tmpdir, files + 2, control_paths));
const RQ_Source sources[] = {
{.path = "acme-order/internal/service/order_service.go", .text = PAR_GO_SERVICE},
{.path = "acme-order/internal/handler/order_handler.go", .text = PAR_GO_HANDLER},
PAR_C_SOURCES[0],
PAR_C_SOURCES[1]};
const RQ_Expected expected[] = {
{.case_id = "go-field-chain-ListOrders",
.site = {.file = "acme-order/internal/handler/order_handler.go",
.caller = "acme-order.internal.handler.OrderHandler.ListOrders",
.needle = "h.orderSvc.ListOrders(ctx, req)"},
.relation = "CALLS",
.target = {.file = "acme-order/internal/service/order_service.go",
.suffix = "acme-order.internal.service.OrderService.ListOrders",
.label = "Method",
.declaration_line = 14,
.declaration_text = "func (s *OrderService) ListOrders(ctx context.Context, "
"req *pb.ListOrdersReq) (*pb.ListOrdersRsp, error) {"},
.mode = RQ_KNOWN_GAP,
.gap_id = "go_field_chain_module_and_external_type_scope_unproven",
.why = "The fixture has no go.mod and its external pb types and several receiver fields "
"lack complete declarations.",
.tried = "Retained all original fields, imports, and both real OrderService calls. No "
"module root or external type identity is guessed; a separate cross-file C call "
"remains required."},
{.case_id = "go-field-chain-PlaceOrder",
.site = {.file = "acme-order/internal/handler/order_handler.go",
.caller = "acme-order.internal.handler.OrderHandler.PlaceOrder",
.needle = "h.orderSvc.PlaceOrder(ctx, req)"},
.relation = "CALLS",
.target = {.file = "acme-order/internal/service/order_service.go",
.suffix = "acme-order.internal.service.OrderService.PlaceOrder",
.label = "Method",
.declaration_line = 10,
.declaration_text = "func (s *OrderService) PlaceOrder(ctx context.Context, "
"req *pb.PlaceOrderReq) (*pb.PlaceOrderRsp, error) {"},
.mode = RQ_KNOWN_GAP,
.gap_id = "go_field_chain_module_and_external_type_scope_unproven",
.why = "The fixture has no go.mod and its external pb types and several receiver fields "
"lack complete declarations.",
.tried = "Retained all original fields, imports, and both real OrderService calls. No "
"module root or external type identity is guessed; a separate cross-file C call "
"remains required."},
PAR_C_REQUIRED};
/* UpdateCart has no declared target in these original sources and is
* outside this oracle's two explicitly covered handler CALLS domains. */
cbm_gbuf_t *gbuf = run_go_field_chain_sequential("go_field_fold", tmpdir, files, 4);
ASSERT_NOT_NULL(gbuf);
/* Since #1909, Go method QNs are receiver-qualified on both endpoints. */
const cbm_gbuf_edge_t *edge = find_call_edge_to_target_fragment(
gbuf, "OrderHandler.PlaceOrder", ".service.OrderService.PlaceOrder");
const bool found = edge != NULL;
const bool dispatch = edge && edge->properties_json &&
strstr(edge->properties_json, "\"strategy\":\"lsp_type_dispatch\"");
if (!found || !dispatch) {
printf(" go field chain diagnostic: found=%d dispatch=%d\n", found, dispatch);
if (edge && edge->properties_json) {
printf(" go field chain props: %s\n", edge->properties_json);
}
/* Dump all callable nodes and CALLS edges for cross-referencing */
{
const cbm_gbuf_node_t **nodes = NULL;
int ncount = 0;
if (cbm_gbuf_find_by_label(gbuf, "Function", &nodes, &ncount) == 0) {
for (int i = 0; i < ncount; i++) {
printf(" node Function: %s\n", nodes[i]->qualified_name);
}
}
if (cbm_gbuf_find_by_label(gbuf, "Method", &nodes, &ncount) == 0) {
for (int i = 0; i < ncount; i++) {
printf(" node Method: %s\n", nodes[i]->qualified_name);
}
}
cbm_gbuf_edge_visitor_fn edge_dump = NULL;
(void)edge_dump;
/* print every CALLS edge with endpoints */
const cbm_gbuf_edge_t **all = NULL;
int ecount = 0;
if (cbm_gbuf_find_edges_by_type(gbuf, "CALLS", &all, &ecount) == 0) {
for (int i = 0; i < ecount; i++) {
const cbm_gbuf_node_t *src =
all[i] ? cbm_gbuf_find_by_id(gbuf, all[i]->source_id) : NULL;
const cbm_gbuf_node_t *dst =
all[i] ? cbm_gbuf_find_by_id(gbuf, all[i]->target_id) : NULL;
printf(" CALLS edge: %s -> %s props=%s\n", src ? src->qualified_name : "?",
dst ? dst->qualified_name : "?",
all[i]->properties_json ? all[i]->properties_json : "{}");
}
}
}
}
RQ_Report quality = {0};
ASSERT(parallel_quality_check(gbuf, "go_field_fold", "go-field-chain-quality", sources, 4,
expected, 3, &quality));
ASSERT_EQ(quality.genuine, 3);
ASSERT_EQ(quality.found, 1);
ASSERT_EQ(quality.missing, 2);
cbm_gbuf_free(gbuf);
th_rmtree(tmpdir);
PASS();
}
/* Cross-file regression for the QN-mismatch bug: py_lsp's per-file mode
* emits resolved_calls.callee_qn as the raw import-module path (e.g.
* `greeter.Greeter` from `from greeter import Greeter`) rather than the
* project-qualified QN the gbuf stores (`<project>.greeter.Greeter`).
* Before cbm_pipeline_lsp_target_node added the project-prefix fallback,
* the LSP match succeeded (lsp_overrides counter incremented) but the
* downstream cbm_gbuf_find_by_qn lookup missed silently, dropping the
* edge. With the fallback in place, the cross-file `g.hello()` call is
* attributed to <project>.greeter.Greeter.hello with an lsp_* strategy.
*
* Two-file scenario: greeter.py defines Greeter; app.py imports it and
* calls hello() — same shape as the original failing reproduction. */
TEST(parallel_python_lsp_override_cross_file_emits_lsp_strategy_edges) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_pylsp_xf_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char gpath[512];
snprintf(gpath, sizeof(gpath), "%s/greeter.py", tmpdir);
FILE *gf = fopen(gpath, "w");
if (!gf) {
FAIL("fopen greeter.py failed");
}
fprintf(gf, "class Greeter:\n"
" def hello(self):\n"
" return 'hi'\n");
fclose(gf);
char apath[512];
snprintf(apath, sizeof(apath), "%s/app.py", tmpdir);
FILE *af = fopen(apath, "w");
if (!af) {
unlink(gpath);
rmdir(tmpdir);
FAIL("fopen app.py failed");
}
fprintf(af, "from greeter import Greeter\n"
"\n"
"def main():\n"
" g = Greeter()\n"
" g.hello()\n");
fclose(af);
cbm_file_info_t files[2] = {0};
files[0].path = gpath;
files[0].rel_path = (char *)"greeter.py";
files[0].language = CBM_LANG_PYTHON;
files[1].path = apath;
files[1].rel_path = (char *)"app.py";
files[1].language = CBM_LANG_PYTHON;
cbm_gbuf_t *gbuf = run_parallel("cbm_par_pylsp_xf", tmpdir, files, 2, 2);
ASSERT_NOT_NULL(gbuf);
lsp_edge_count_ctx_t c = {0};
cbm_gbuf_foreach_edge(gbuf, count_lsp_call_edges, &c);
ASSERT_GT(c.total_calls, 0);
/* Require the actual cross-file receiver target after strict projection. */
ASSERT_GT(c.proof_strategy_count, 0);
ASSERT_NOT_NULL(find_calls_edge_by_tails(gbuf, "app.main", "Greeter.hello"));
cbm_gbuf_free(gbuf);
unlink(apath);
unlink(gpath);
rmdir(tmpdir);
PASS();
}
/* Retention parity has one required source-dependent Kotlin cross-file
* control in every mode. The original Java/Kotlin pair remains byte-identical
* and contributes three explicit quality misses per mode: the two original
* method calls and nested implicit constructor need strict identities/proofs.
* Empty graphs cannot satisfy the required control or the projection oracle. */
typedef struct {
bool retain_expected, caller_seen, retention_matches, pre_cross_unproven;
} parallel_quality_retention_probe_t;
static void parallel_quality_probe_retention(CBMFileResult **cache, int count, void *ud) {
parallel_quality_retention_probe_t *probe = ud;
if (count != 4 || !cache[3])
return;
const CBMFileResult *caller = cache[3];
probe->caller_seen = true;
probe->retention_matches =
(caller->source != NULL && caller->source_len > 0) == probe->retain_expected;
probe->pre_cross_unproven = true;
const char *site = strstr(PAR_QUALITY_CALLER, "value.keep(number)");
uint32_t start = (uint32_t)(site - PAR_QUALITY_CALLER);
for (int i = 0; i < caller->resolved_calls.count; i++) {
const CBMResolvedCall *row = &caller->resolved_calls.items[i];
if (row->kind == CBM_RESOLVED_INVOCATION && row->site_start_byte == start &&
row->site_end_byte == start + strlen("value.keep(number)") && row->evidence.proof)
probe->pre_cross_unproven = false;
}
}
TEST(parallel_cross_file_reread_preserves_unretained_edges) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_xf_reread_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char jpath[512];
snprintf(jpath, sizeof(jpath), "%s/src/main/java/com/example/Example.java", tmpdir);
char jdir[512];
snprintf(jdir, sizeof(jdir), "%s/src/main/java/com/example", tmpdir);
cbm_mkdir_p(jdir, 0755);
FILE *jf = cbm_fopen(jpath, "wb");
if (!jf) {
FAIL("fopen Example.java failed");
}
fputs(PAR_JVM_JAVA, jf);
fclose(jf);
char kpath[512];
snprintf(kpath, sizeof(kpath), "%s/src/main/kotlin/com/example/KotlinService.kt", tmpdir);
char kdir[512];
snprintf(kdir, sizeof(kdir), "%s/src/main/kotlin/com/example", tmpdir);
cbm_mkdir_p(kdir, 0755);
FILE *kf = cbm_fopen(kpath, "wb");
if (!kf) {
FAIL("fopen KotlinService.kt failed");
}
fputs(PAR_JVM_KOTLIN, kf);
fclose(kf);
cbm_file_info_t files[4] = {0};
files[0].path = jpath;
files[0].rel_path = (char *)"src/main/java/com/example/Example.java";
files[0].language = CBM_LANG_JAVA;
files[1].path = kpath;
files[1].rel_path = (char *)"src/main/kotlin/com/example/KotlinService.kt";
files[1].language = CBM_LANG_KOTLIN;
char control_paths[2][512];
ASSERT(parallel_quality_control_files(tmpdir, files + 2, control_paths));
/* CONTROL (retained) + two drop scenarios that reach the cross-file edge
* only via the on-demand re-read: NO-RETAIN disables retention entirely;
* OVER-CAP sets a 1-byte per-file cap so every file is dropped by size. */
const cbm_parallel_extract_opts_t no_retain = {
.retain_sources = false,
.retain_sources_set = true,
};
const cbm_parallel_extract_opts_t over_cap = {
.retain_sources = true,
.retain_sources_set = true,
.retain_per_file_max_bytes = 1, /* 1 byte → every file dropped by the size cap */
};
const cbm_parallel_extract_opts_t *scenarios[3] = {NULL, &no_retain, &over_cap};
for (int s = 0; s < 3; s++) {
parallel_quality_retention_probe_t probe = {.retain_expected = s == 0};
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts_and_mutator(
"com", tmpdir, files, 4, 2, scenarios[s], parallel_quality_probe_retention, &probe,
false);
ASSERT_NOT_NULL(gbuf);
ASSERT(probe.caller_seen);
ASSERT(probe.retention_matches);
ASSERT(probe.pre_cross_unproven);
RQ_Report quality = {0};
const char *names[] = {"jvm-reread-retained", "jvm-reread-disabled", "jvm-reread-capped"};
ASSERT(parallel_quality_check(gbuf, "com", names[s], PAR_JVM_SOURCES, 4, PAR_JVM_EXPECTED,
4, &quality));
ASSERT_EQ(quality.genuine, 4);
ASSERT_EQ(quality.found, 4);
ASSERT_EQ(quality.missing, 0);
ASSERT_EQ(quality.required_missing + quality.wrong, 0);
cbm_gbuf_free(gbuf);
}
th_rmtree(tmpdir);
PASS();
}
/* issue #294: gRPC service-name extraction must (a) preserve the canonical
* proto service name (FooServiceClient → FooService, not Foo) and (b) only
* match real stub/client types — ordinary receiver vars must NOT produce
* phantom __grpc__ Routes. */
TEST(grpc_service_name_preserves_service_suffix_issue294) {
char svc[256];
char meth[256];
/* Generated client class keeps the "Service" part of the name. */
ASSERT_TRUE(extract_grpc_service_method("pb.NewFooServiceClient.GetBar", svc, sizeof(svc), meth,
sizeof(meth)));
ASSERT_STR_EQ(svc, "FooService");
ASSERT_STR_EQ(meth, "GetBar");
/* Java-style ...ServiceGrpc strips only "Grpc". */
ASSERT_TRUE(extract_grpc_service_method("CartServiceGrpc.getCart", svc, sizeof(svc), meth,
sizeof(meth)));
ASSERT_STR_EQ(svc, "CartService");
/* BlockingStub wins over Stub (longest-suffix-first). */
ASSERT_TRUE(extract_grpc_service_method("CartServiceBlockingStub.getCart", svc, sizeof(svc),
meth, sizeof(meth)));
ASSERT_STR_EQ(svc, "CartService");
PASS();
}
TEST(grpc_no_phantom_route_from_plain_var_issue294) {
char svc[256];
char meth[256];
/* Ordinary receiver vars carry no gRPC stub suffix → must NOT match,
* so no phantom __grpc__provider/... or __grpc__builder/... Route. */
ASSERT_FALSE(
extract_grpc_service_method("_provider.GetGroup", svc, sizeof(svc), meth, sizeof(meth)));
ASSERT_FALSE(extract_grpc_service_method("_builder.AddSomeService", svc, sizeof(svc), meth,
sizeof(meth)));
ASSERT_FALSE(extract_grpc_service_method("logger.Info", svc, sizeof(svc), meth, sizeof(meth)));
PASS();
}
/* ── Shared "::" normalization in cbm_pipeline_find_lsp_resolution (QA F3) ─
*
* The last-"::"-segment normalization in lsp_resolve.h widens matching for
* qualified static callees (Perl `Pkg::sub`, C++ `Ns::fn`, etc.) across ALL
* languages, not just Perl. These tests lock the intended behavior directly
* against cbm_pipeline_find_lsp_resolution: (1) a qualified static call still
* resolves to the right resolved entry, and (2) the theoretical
* mis-attribution edge case (two same-named subs from different namespaces) is
* bounded by caller-QN equality + the confidence floor. */
static CBMResolvedCall make_rc(const char *caller, const char *callee, float conf) {
CBMResolvedCall rc = {0};
memset(&rc, 0, sizeof(rc));
rc.caller_qn = caller;
rc.callee_qn = callee;
rc.strategy = "test";
rc.confidence = conf;
return rc;
}
static CBMCall make_call(const char *enclosing, const char *callee_name) {
CBMCall c;
memset(&c, 0, sizeof(c));
c.enclosing_func_qn = enclosing;
c.callee_name = callee_name;
return c;
}
TEST(lsp_bare_segment_skips_preprocessor_spacing) {
/* simplecpp serializes macro-expanded member access with spaces around the
* separator (for example `bravo . render`). The shared semantic join must
* still compare the identifier leaf, not the whitespace-prefixed suffix. */
ASSERT_STR_EQ(cbm_lsp_bare_segment("bravo . render"), "render");
ASSERT_STR_EQ(cbm_lsp_bare_segment("ptr -> run"), "run");
PASS();
}
TEST(lsp_resolve_qualified_static_call_normalizes_colons) {
/* A qualified static call `Pkg::sub` (callee_name keeps the package
* prefix) must still match a resolved entry whose callee_qn short-name is
* the bare `sub`. This is the cross-language "::"-normalization contract. */
CBMResolvedCall items[] = {
make_rc("proj.mod.caller", "proj.Pkg.sub", 0.9f),
};
CBMResolvedCallArray arr = {items, 1, 1};
CBMCall call = make_call("proj.mod.caller", "Pkg::sub");
const CBMResolvedCall *hit = cbm_pipeline_find_lsp_resolution(&arr, &call, false);
ASSERT(hit != NULL);
ASSERT(strcmp(hit->callee_qn, "proj.Pkg.sub") == 0);
/* A bare call (no "::") to the same short name resolves identically —
* normalization must not regress the common case. */
CBMCall bare = make_call("proj.mod.caller", "sub");
const CBMResolvedCall *bare_hit = cbm_pipeline_find_lsp_resolution(&arr, &bare, false);
ASSERT(bare_hit != NULL);
ASSERT(strcmp(bare_hit->callee_qn, "proj.Pkg.sub") == 0);
PASS();
}
TEST(lsp_resolve_distinct_exact_caller_targets_fail_closed) {
/* Two same-named targets from different namespaces at the same caller and
* legacy occurrence are semantic ambiguity, not a confidence contest.
* Choosing the higher score would fabricate one exact CALLS target. */
CBMResolvedCall items[] = {
make_rc("proj.mod.caller", "proj.A.foo", 0.7f),
make_rc("proj.mod.caller", "proj.B.foo", 0.9f),
/* Below the confidence floor: must be ignored entirely. */
make_rc("proj.mod.caller", "proj.C.foo", 0.3f),
/* Different caller: must never match regardless of short-name. */
make_rc("proj.mod.other", "proj.D.foo", 0.95f),
};
CBMResolvedCallArray arr = {items, 4, 4};
CBMCall call = make_call("proj.mod.caller", "B::foo");
const CBMResolvedCall *hit = cbm_pipeline_find_lsp_resolution(&arr, &call, false);
ASSERT_NULL(hit);
/* The cross-caller high-confidence entry only matches its own caller. */
CBMCall other = make_call("proj.mod.other", "D::foo");
const CBMResolvedCall *other_hit = cbm_pipeline_find_lsp_resolution(&arr, &other, false);
ASSERT(other_hit != NULL);
ASSERT(strcmp(other_hit->callee_qn, "proj.D.foo") == 0);
/* A caller with no qualifying entry resolves to nothing (no widening can
* manufacture an edge across callers). */
CBMCall absent = make_call("proj.mod.absent", "foo");
ASSERT(cbm_pipeline_find_lsp_resolution(&arr, &absent, false) == NULL);
PASS();
}
TEST(lsp_resolve_duplicate_exact_caller_rows_same_target_are_not_ambiguous) {
CBMCall call = make_call("proj.mod.caller", "receiver.render");
call.site_start_byte = 75;
call.site_end_byte = 91;
CBMResolvedCall low = make_rc("proj.mod.caller", "proj.Alpha.render", 0.75f);
low.site_start_byte = call.site_start_byte;
low.site_end_byte = call.site_end_byte;
CBMResolvedCall high = low;
high.confidence = 0.95f;
CBMResolvedCall items[] = {low, high};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT(cbm_pipeline_find_lsp_resolution(&arr, &call, false) == &items[1]);
PASS();
}
TEST(lsp_resolve_distinct_exact_caller_site_targets_are_ambiguous) {
CBMCall call = make_call("proj.mod.caller", "receiver.render");
call.site_start_byte = 92;
call.site_end_byte = 108;
call.requires_lsp_resolution = true;
CBMResolvedCall alpha = make_rc("proj.mod.caller", "proj.Alpha.render", 0.75f);
alpha.site_start_byte = call.site_start_byte;
alpha.site_end_byte = call.site_end_byte;
CBMResolvedCall beta = make_rc("proj.mod.caller", "proj.Beta.render", 0.99f);
beta.site_start_byte = call.site_start_byte;
beta.site_end_byte = call.site_end_byte;
CBMResolvedCall items[] = {alpha, beta};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&arr, &call, false));
PASS();
}
/* Per-file and project-wide passes may spell one exact non-JVM invocation
* target with and without the project prefix. Graph identity, not raw string
* identity, decides whether those occurrence-identical rows conflict. */
TEST(lsp_resolve_project_prefixed_duplicate_is_not_ambiguous) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t target_id = cbm_gbuf_upsert_node(gbuf, "Function", "handler", "proj.mod.Target.handler",
"target.py", 1, 1, "{}");
ASSERT_GT(target_id, 0);
CBMCall call = make_call("proj.mod.Caller.run", "handler");
call.site_start_byte = 110;
call.site_end_byte = 119;
CBMResolvedCall raw = make_rc("proj.mod.Caller.run", "mod.Target.handler", 0.75f);
raw.site_start_byte = call.site_start_byte;
raw.site_end_byte = call.site_end_byte;
CBMResolvedCall prefixed = make_rc("proj.mod.Caller.run", "proj.mod.Target.handler", 0.90f);
prefixed.site_start_byte = call.site_start_byte;
prefixed.site_end_byte = call.site_end_byte;
CBMResolvedCall items[] = {raw, prefixed};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit =
cbm_pipeline_find_lsp_resolution_in_graph(&arr, &call, false, gbuf, project);
cbm_gbuf_free(gbuf);
ASSERT(hit == &items[1]);
PASS();
}
TEST(lsp_resolve_project_prefix_spellings_stay_ambiguous_when_both_nodes_exist) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t raw_id = cbm_gbuf_upsert_node(gbuf, "Function", "handler", "mod.Target.handler",
"raw.py", 1, 1, "{}");
int64_t prefixed_id = cbm_gbuf_upsert_node(
gbuf, "Function", "handler", "proj.mod.Target.handler", "prefixed.py", 1, 1, "{}");
ASSERT_GT(raw_id, 0);
ASSERT_GT(prefixed_id, 0);
ASSERT(raw_id != prefixed_id);
CBMCall call = make_call("proj.mod.Caller.run", "handler");
call.site_start_byte = 120;
call.site_end_byte = 129;
CBMResolvedCall raw = make_rc("proj.mod.Caller.run", "mod.Target.handler", 0.75f);
raw.site_start_byte = call.site_start_byte;
raw.site_end_byte = call.site_end_byte;
CBMResolvedCall prefixed = make_rc("proj.mod.Caller.run", "proj.mod.Target.handler", 0.90f);
prefixed.site_start_byte = call.site_start_byte;
prefixed.site_end_byte = call.site_end_byte;
CBMResolvedCall items[] = {raw, prefixed};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit =
cbm_pipeline_find_lsp_resolution_in_graph(&arr, &call, false, gbuf, project);
cbm_gbuf_free(gbuf);
ASSERT_NULL(hit);
PASS();
}
TEST(lsp_resolve_jvm_unproved_package_alias_cannot_merge_targets) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t target_id =
cbm_gbuf_upsert_node(gbuf, "Method", "twice", "proj.Util.twice", "Util.java", 1, 1, "{}");
ASSERT_GT(target_id, 0);
CBMCall call = make_call("proj.Client.run", "twice");
call.site_start_byte = 120;
call.site_end_byte = 128;
CBMResolvedCall local = make_rc("proj.Client.run", "proj.Util.twice", 0.92f);
local.site_start_byte = call.site_start_byte;
local.site_end_byte = call.site_end_byte;
CBMResolvedCall package = make_rc("proj.Client.run", "demo.Util.twice", 0.92f);
package.site_start_byte = call.site_start_byte;
package.site_end_byte = call.site_end_byte;
CBMResolvedCall items[] = {local, package};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit =
cbm_pipeline_find_lsp_resolution_in_graph(&arr, &call, true, gbuf, project);
cbm_gbuf_free(gbuf);
/* The fixture supplies no mapping from demo.Util to this graph declaration.
* A shared tail cannot merge the two different semantic target identities. */
ASSERT_NULL(hit);
PASS();
}
TEST(lsp_resolve_jvm_two_tail_only_packages_stay_ambiguous) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t target_id = cbm_gbuf_upsert_node(gbuf, "Method", "run", "proj.generated.Service.run",
"Service.java", 1, 1, "{}");
ASSERT_GT(target_id, 0);
CBMCall call = make_call("proj.Client.call", "run");
call.site_start_byte = 130;
call.site_end_byte = 138;
CBMResolvedCall first = make_rc("proj.Client.call", "pkg1.Service.run", 0.80f);
first.site_start_byte = call.site_start_byte;
first.site_end_byte = call.site_end_byte;
CBMResolvedCall second = make_rc("proj.Client.call", "pkg2.Service.run", 0.90f);
second.site_start_byte = call.site_start_byte;
second.site_end_byte = call.site_end_byte;
CBMResolvedCall items[] = {first, second};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit =
cbm_pipeline_find_lsp_resolution_in_graph(&arr, &call, true, gbuf, project);
cbm_gbuf_free(gbuf);
ASSERT_NULL(hit);
PASS();
}
TEST(lsp_resolve_exact_site_beats_higher_confidence_legacy_record) {
CBMCall call = make_call("proj.mod.caller", "receiver.render");
call.site_start_byte = 100;
call.site_end_byte = 117;
CBMResolvedCall exact = make_rc("proj.mod.caller", "proj.Alpha.render", 0.80f);
exact.site_start_byte = call.site_start_byte;
exact.site_end_byte = call.site_end_byte;
/* A legacy resolver record has no occurrence span. Even with greater
* confidence it must not override the semantic record for this exact
* carrier occurrence. */
CBMResolvedCall legacy = make_rc("proj.mod.caller", "proj.Beta.render", 0.95f);
CBMResolvedCall items[] = {exact, legacy};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit = cbm_pipeline_find_lsp_resolution(&arr, &call, false);
ASSERT(hit == &items[0]);
ASSERT(strcmp(hit->callee_qn, "proj.Alpha.render") == 0);
PASS();
}
TEST(lsp_resolve_exact_site_beats_equal_confidence_legacy_record) {
CBMCall call = make_call("proj.mod.caller", "receiver.render");
call.site_start_byte = 200;
call.site_end_byte = 217;
/* Put the legacy record first to prove equal-confidence selection is not
* an array-order accident: occurrence exactness is the primary key. */
CBMResolvedCall legacy = make_rc("proj.mod.caller", "proj.Beta.render", 0.90f);
CBMResolvedCall exact = make_rc("proj.mod.caller", "proj.Alpha.render", 0.90f);
exact.site_start_byte = call.site_start_byte;
exact.site_end_byte = call.site_end_byte;
CBMResolvedCall items[] = {legacy, exact};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit = cbm_pipeline_find_lsp_resolution(&arr, &call, false);
ASSERT(hit == &items[1]);
ASSERT(strcmp(hit->callee_qn, "proj.Alpha.render") == 0);
PASS();
}
TEST(lsp_resolve_malformed_span_is_not_legacy_compatible) {
CBMCall call = make_call("proj.mod.caller", "receiver.render");
call.site_start_byte = 300;
call.site_end_byte = 317;
/* A reversed/partial span is corrupt occurrence metadata, not a legacy
* 0:0 record. It must be rejected even when it has greater confidence. */
CBMResolvedCall malformed = make_rc("proj.mod.caller", "proj.Wrong.render", 0.99f);
malformed.site_start_byte = 317;
malformed.site_end_byte = 300;
CBMResolvedCall legacy = make_rc("proj.mod.caller", "proj.Legacy.render", 0.75f);
CBMResolvedCall items[] = {malformed, legacy};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit = cbm_pipeline_find_lsp_resolution(&arr, &call, false);
ASSERT(hit == &items[1]);
PASS();
}
TEST(lsp_resolve_below_floor_exact_does_not_block_legacy) {
CBMCall call = make_call("proj.mod.caller", "receiver.render");
call.site_start_byte = 400;
call.site_end_byte = 417;
CBMResolvedCall below_floor = make_rc("proj.mod.caller", "proj.Exact.render", 0.55f);
below_floor.site_start_byte = call.site_start_byte;
below_floor.site_end_byte = call.site_end_byte;
CBMResolvedCall legacy = make_rc("proj.mod.caller", "proj.Legacy.render", 0.70f);
CBMResolvedCall items[] = {below_floor, legacy};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT(cbm_pipeline_find_lsp_resolution(&arr, &call, false) == &items[1]);
PASS();
}
static CBMCall make_tail_call(uint32_t start, uint32_t end) {
CBMCall call = make_call("proj.raw.Service.handle", "helper.run");
call.site_start_byte = start;
call.site_end_byte = end;
return call;
}
static CBMResolvedCall make_tail_rc(const char *callee, float confidence, uint32_t start,
uint32_t end) {
CBMResolvedCall resolved = make_rc("com.example.generated.Service.handle", callee, confidence);
resolved.site_start_byte = start;
resolved.site_end_byte = end;
return resolved;
}
TEST(lsp_tail_occurrence_cannot_replace_exact_caller_identity) {
CBMCall call = make_tail_call(500, 512);
CBMResolvedCall exact = make_tail_rc("com.example.Alpha.run", 0.80f, 500, 512);
CBMResolvedCall legacy = make_tail_rc("com.example.Beta.run", 0.99f, 0, 0);
CBMResolvedCall exact_first[] = {exact, legacy};
CBMResolvedCall legacy_first[] = {legacy, exact};
CBMResolvedCallArray first = {exact_first, 2, 2};
CBMResolvedCallArray second = {legacy_first, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&first, &call, true));
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&second, &call, true));
call.enclosing_func_qn = exact.caller_qn;
ASSERT(cbm_pipeline_find_lsp_resolution(&first, &call, true) == &exact_first[0]);
ASSERT(cbm_pipeline_find_lsp_resolution(&second, &call, true) == &legacy_first[1]);
PASS();
}
TEST(lsp_tail_duplicate_exact_rows_require_exact_caller) {
CBMCall call = make_tail_call(600, 612);
CBMResolvedCall low = make_tail_rc("com.example.Helper.run", 0.75f, 600, 612);
CBMResolvedCall high = make_tail_rc("com.example.Helper.run", 0.90f, 600, 612);
CBMResolvedCall items[] = {low, high};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&arr, &call, true));
call.enclosing_func_qn = low.caller_qn;
ASSERT(cbm_pipeline_find_lsp_resolution(&arr, &call, true) == &items[1]);
PASS();
}
TEST(lsp_tail_distinct_exact_targets_are_ambiguous) {
CBMCall call = make_tail_call(700, 712);
CBMResolvedCall alpha = make_tail_rc("com.example.Alpha.run", 0.75f, 700, 712);
CBMResolvedCall beta = make_tail_rc("com.example.Beta.run", 0.99f, 700, 712);
CBMResolvedCall items[] = {alpha, beta};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&arr, &call, true));
PASS();
}
TEST(lsp_tail_duplicate_legacy_rows_require_exact_caller) {
CBMCall call = make_tail_call(800, 812);
CBMResolvedCall low = make_tail_rc("com.example.Helper.run", 0.75f, 0, 0);
CBMResolvedCall high = make_tail_rc("com.example.Helper.run", 0.90f, 0, 0);
CBMResolvedCall items[] = {low, high};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&arr, &call, true));
call.enclosing_func_qn = low.caller_qn;
ASSERT(cbm_pipeline_find_lsp_resolution(&arr, &call, true) == &items[1]);
PASS();
}
TEST(lsp_tail_distinct_legacy_targets_are_ambiguous) {
CBMCall call = make_tail_call(900, 912);
CBMResolvedCall alpha = make_tail_rc("com.example.Alpha.run", 0.75f, 0, 0);
CBMResolvedCall beta = make_tail_rc("com.example.Beta.run", 0.99f, 0, 0);
CBMResolvedCall items[] = {alpha, beta};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_resolution(&arr, &call, true));
PASS();
}
static CBMUsage make_reference_usage(const char *caller, const char *name, uint32_t start,
uint32_t end) {
CBMUsage usage = {0};
usage.enclosing_func_qn = caller;
usage.ref_name = name;
usage.kind = CBM_USAGE_CALL_REFERENCE;
usage.site_start_byte = start;
usage.site_end_byte = end;
return usage;
}
static CBMResolvedCall make_reference_rc(const char *caller, const char *callee, float confidence,
uint32_t start, uint32_t end) {
CBMResolvedCall resolved = make_rc(caller, callee, confidence);
resolved.kind = CBM_RESOLVED_CALL_REFERENCE;
resolved.site_start_byte = start;
resolved.site_end_byte = end;
return resolved;
}
TEST(lsp_reference_duplicate_exact_rows_same_target_are_not_ambiguous) {
CBMUsage usage = make_reference_usage("proj.mod.Caller.run", "handler", 1000, 1007);
CBMResolvedCall low =
make_reference_rc("proj.mod.Caller.run", "proj.Target.handler", 0.75f, 1000, 1007);
CBMResolvedCall high =
make_reference_rc("proj.mod.Caller.run", "proj.Target.handler", 0.90f, 1000, 1007);
CBMResolvedCall items[] = {low, high};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT(cbm_pipeline_find_lsp_reference(&arr, &usage, false) == &items[1]);
PASS();
}
/* Per-file LSP rows may carry a package-shaped target while a cross-file row
* carries the same graph QN with the project prefix already attached. Those
* spellings identify one target and must not trip the distinct-target
* ambiguity guard. */
TEST(lsp_reference_project_prefixed_duplicate_is_not_ambiguous) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t target_id = cbm_gbuf_upsert_node(gbuf, "Function", "handler", "proj.mod.Target.handler",
"target.go", 1, 1, "{}");
ASSERT_GT(target_id, 0);
const cbm_gbuf_node_t *raw_target =
cbm_pipeline_lsp_target_node(gbuf, project, "mod.Target.handler", false);
const cbm_gbuf_node_t *prefixed_target =
cbm_pipeline_lsp_target_node(gbuf, project, "proj.mod.Target.handler", false);
ASSERT_NOT_NULL(raw_target);
ASSERT_NOT_NULL(prefixed_target);
ASSERT_EQ(raw_target->id, prefixed_target->id);
CBMUsage usage = make_reference_usage("proj.mod.Caller.run", "handler", 1050, 1057);
CBMResolvedCall raw =
make_reference_rc("proj.mod.Caller.run", "mod.Target.handler", 0.75f, 1050, 1057);
CBMResolvedCall prefixed =
make_reference_rc("proj.mod.Caller.run", "proj.mod.Target.handler", 0.90f, 1050, 1057);
CBMResolvedCall items[] = {raw, prefixed};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit =
cbm_pipeline_find_lsp_reference_in_graph(&arr, &usage, false, gbuf, project);
cbm_gbuf_free(gbuf);
ASSERT(hit == &items[1]);
PASS();
}
TEST(lsp_reference_project_prefix_spellings_stay_ambiguous_when_both_nodes_exist) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t raw_id = cbm_gbuf_upsert_node(gbuf, "Function", "handler", "mod.Target.handler",
"raw.go", 1, 1, "{}");
int64_t prefixed_id = cbm_gbuf_upsert_node(
gbuf, "Function", "handler", "proj.mod.Target.handler", "prefixed.go", 1, 1, "{}");
ASSERT_GT(raw_id, 0);
ASSERT_GT(prefixed_id, 0);
ASSERT(raw_id != prefixed_id);
CBMUsage usage = make_reference_usage("proj.mod.Caller.run", "handler", 1070, 1077);
CBMResolvedCall raw =
make_reference_rc("proj.mod.Caller.run", "mod.Target.handler", 0.75f, 1070, 1077);
CBMResolvedCall prefixed =
make_reference_rc("proj.mod.Caller.run", "proj.mod.Target.handler", 0.90f, 1070, 1077);
CBMResolvedCall items[] = {raw, prefixed};
CBMResolvedCallArray arr = {items, 2, 2};
const CBMResolvedCall *hit =
cbm_pipeline_find_lsp_reference_in_graph(&arr, &usage, false, gbuf, project);
cbm_gbuf_free(gbuf);
ASSERT_NULL(hit);
PASS();
}
/* Distinct semantic packages remain distinct even when an unrelated graph
* declaration has the same class/member suffix. No legacy flag reopens it. */
TEST(lsp_reference_jvm_distinct_packages_do_not_collapse_via_tail_node) {
const char *project = "proj";
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t materialized_id = cbm_gbuf_upsert_node(
gbuf, "Method", "handler", "proj.generated.Service.handler", "Service.kt", 1, 1, "{}");
ASSERT_GT(materialized_id, 0);
CBMUsage usage = make_reference_usage("proj.consumer.Caller.run", "handler", 1080, 1087);
CBMResolvedCall pkg1 =
make_reference_rc("proj.consumer.Caller.run", "pkg1.Service.handler", 0.75f, 1080, 1087);
CBMResolvedCall pkg2 =
make_reference_rc("proj.consumer.Caller.run", "pkg2.Service.handler", 0.90f, 1080, 1087);
CBMResolvedCall pkg1_first[] = {pkg1, pkg2};
CBMResolvedCall pkg2_first[] = {pkg2, pkg1};
CBMResolvedCallArray first = {pkg1_first, 2, 2};
CBMResolvedCallArray second = {pkg2_first, 2, 2};
const cbm_gbuf_node_t *pkg1_tail =
cbm_pipeline_lsp_target_node(gbuf, project, pkg1.callee_qn, true);
const cbm_gbuf_node_t *pkg2_tail =
cbm_pipeline_lsp_target_node(gbuf, project, pkg2.callee_qn, true);
const cbm_gbuf_node_t *exact =
cbm_pipeline_lsp_target_node(gbuf, project, "proj.generated.Service.handler", true);
bool exact_node = exact && exact->id == materialized_id;
bool no_tail_node = !pkg1_tail && !pkg2_tail;
const CBMResolvedCall *first_hit =
cbm_pipeline_find_lsp_reference_in_graph(&first, &usage, true, gbuf, project);
const CBMResolvedCall *second_hit =
cbm_pipeline_find_lsp_reference_in_graph(&second, &usage, true, gbuf, project);
cbm_gbuf_free(gbuf);
ASSERT_TRUE(exact_node);
ASSERT_TRUE(no_tail_node);
ASSERT_NULL(first_hit);
ASSERT_NULL(second_hit);
PASS();
}
TEST(lsp_reference_distinct_exact_targets_are_order_invariant) {
CBMUsage usage = make_reference_usage("proj.mod.Caller.run", "handler", 1100, 1107);
CBMResolvedCall alpha =
make_reference_rc("proj.mod.Caller.run", "proj.Alpha.handler", 0.90f, 1100, 1107);
CBMResolvedCall beta =
make_reference_rc("proj.mod.Caller.run", "proj.Beta.handler", 0.90f, 1100, 1107);
CBMResolvedCall alpha_first[] = {alpha, beta};
CBMResolvedCall beta_first[] = {beta, alpha};
CBMResolvedCallArray first = {alpha_first, 2, 2};
CBMResolvedCallArray second = {beta_first, 2, 2};
const CBMResolvedCall *first_hit = cbm_pipeline_find_lsp_reference(&first, &usage, false);
const CBMResolvedCall *second_hit = cbm_pipeline_find_lsp_reference(&second, &usage, false);
bool invariant = (!first_hit && !second_hit) ||
(first_hit && second_hit && first_hit->callee_qn && second_hit->callee_qn &&
strcmp(first_hit->callee_qn, second_hit->callee_qn) == 0);
ASSERT_TRUE(invariant);
PASS();
}
TEST(lsp_reference_duplicate_tail_rows_require_exact_caller) {
CBMUsage usage = make_reference_usage("proj.raw.Service.handle", "handler", 1200, 1207);
CBMResolvedCall low =
make_reference_rc("com.generated.Service.handle", "com.Target.handler", 0.75f, 1200, 1207);
CBMResolvedCall high =
make_reference_rc("com.generated.Service.handle", "com.Target.handler", 0.90f, 1200, 1207);
CBMResolvedCall items[] = {low, high};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_reference(&arr, &usage, true));
usage.enclosing_func_qn = low.caller_qn;
ASSERT(cbm_pipeline_find_lsp_reference(&arr, &usage, true) == &items[1]);
PASS();
}
TEST(lsp_reference_distinct_tail_targets_are_ambiguous) {
CBMUsage usage = make_reference_usage("proj.raw.Service.handle", "handler", 1300, 1307);
CBMResolvedCall alpha =
make_reference_rc("com.generated.Service.handle", "com.Alpha.handler", 0.75f, 1300, 1307);
CBMResolvedCall beta =
make_reference_rc("com.generated.Service.handle", "com.Beta.handler", 0.99f, 1300, 1307);
CBMResolvedCall items[] = {alpha, beta};
CBMResolvedCallArray arr = {items, 2, 2};
ASSERT_NULL(cbm_pipeline_find_lsp_reference(&arr, &usage, true));
PASS();
}
TEST(lsp_target_node_supports_long_prefixed_qualified_name) {
const char *project = "long_project";
enum { CALLEE_LEN = 1100 };
char *callee = malloc(CALLEE_LEN + 1U);
char *qualified = malloc(strlen(project) + 1U + CALLEE_LEN + 1U);
ASSERT_NOT_NULL(callee);
ASSERT_NOT_NULL(qualified);
memset(callee, 'n', CALLEE_LEN);
callee[CALLEE_LEN] = '\0';
snprintf(qualified, strlen(project) + 1U + CALLEE_LEN + 1U, "%s.%s", project, callee);
cbm_gbuf_t *gbuf = cbm_gbuf_new(project, "/tmp");
ASSERT_NOT_NULL(gbuf);
int64_t id =
cbm_gbuf_upsert_node(gbuf, "Function", "target", qualified, "target.c", 1, 1, "{}");
ASSERT_GT(id, 0);
const cbm_gbuf_node_t *found = cbm_pipeline_lsp_target_node(gbuf, project, callee, false);
if (!found) {
printf(" long target-QN diagnostic: project=%zu callee=%zu combined=%zu\n",
strlen(project), strlen(callee), strlen(qualified));
}
bool same_node = found && found->id == id;
cbm_gbuf_free(gbuf);
free(qualified);
free(callee);
ASSERT_TRUE(same_node);
PASS();
}
/* ── Suite Registration ──────────────────────────────────────────── */
/* ── PHP vendor-imported types never bind by name (#1186) ─────────── */
/* Foo.php imports `Request` from a vendor namespace no project file declares,
* so the import stays unresolved. The project has its own class Request
* (another namespace) and a TypeScript function sendAsync. Every edge from
* Foo.php that names the vendor Request -- its base class, calls on a
* Request-typed receiver, the `new`, the type references -- used to bind by
* name to those project symbols. Svc is imported from a declared project
* namespace and is the control that must keep resolving. */
static const struct {
const char *rel;
CBMLanguage lang;
const char *src;
} k_php_vendor_files[] = {
{"lib/Requests/Request.php", CBM_LANG_PHP,
"<?php\nnamespace App\\Http\\Requests;\n\nclass Request {\n"
" public function validated() { return 1; }\n"
" public static function makeIt() { return 2; }\n"
" public function get($k) { return $k; }\n}\n"},
{"lib/Services/Svc.php", CBM_LANG_PHP,
"<?php\nnamespace App\\Services;\n\nclass Svc {\n"
" public function go() { return 3; }\n"
" public static function build() { return 4; }\n}\n"},
{"web/api.ts", CBM_LANG_TYPESCRIPT, "export function sendAsync(x: number) { return x; }\n"},
{"lib/Integrations/Foo.php", CBM_LANG_PHP,
"<?php\nnamespace App\\Http\\Integrations;\n\n"
"use Saloon\\Http\\Request;\nuse App\\Services\\Svc;\n\n"
"class Foo extends Request {\n"
" public function run(Request $r, Svc $s) {\n"
" $r->validated();\n"
" Request::makeIt();\n"
" $made = new Request();\n"
" $s->go();\n"
" Svc::build();\n"
" $r->get('id');\n"
" return $r->sendAsync(1);\n"
" }\n}\n\n"
"class Bar extends Svc {\n}\n"},
{"lib/Integrations/Clients.php", CBM_LANG_PHP,
"<?php\nnamespace App\\Http\\Integrations;\n"
"use GuzzleHttp\\Client as GuzzleClient;\nuse Kafka\\Producer as KafkaProducer;\n"
"class Clients { function run() {\n"
" GuzzleClient::get('/api/vendor-health');\n"
" KafkaProducer::publish('vendor-orders', 'payload');\n}\n}\n"},
};
enum { PHP_VENDOR_FILE_COUNT = (int)(sizeof(k_php_vendor_files) / sizeof(k_php_vendor_files[0])) };
typedef struct {
const cbm_gbuf_t *gbuf;
const char *type;
const char *src_rel;
const char *tgt_rel;
int count;
int source_nodes;
bool query_failed;
} php_file_edge_count_t;
static void php_count_source_edges(const cbm_gbuf_node_t *source, void *ud) {
php_file_edge_count_t *c = ud;
if (!source->file_path || strcmp(source->file_path, c->src_rel) != 0)
return;
c->source_nodes++;
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(c->gbuf, source->id, c->type, &edges, &count) != 0) {
c->query_failed = true;
return;
}
for (int i = 0; i < count; i++) {
const cbm_gbuf_node_t *target = cbm_gbuf_find_by_id(c->gbuf, edges[i]->target_id);
if (!target) {
c->query_failed = true;
} else if (target->file_path && strcmp(target->file_path, c->tgt_rel) == 0) {
c->count++;
}
}
}
static int php_file_edges(const cbm_gbuf_t *gbuf, const char *type, const char *source,
const char *target) {
char *source_qn = cbm_pipeline_fqn_compute("cbm_par_phpvendor", source, "__file__");
char *target_qn = cbm_pipeline_fqn_compute("cbm_par_phpvendor", target, "__file__");
const cbm_gbuf_node_t *s = source_qn ? cbm_gbuf_find_by_qn(gbuf, source_qn) : NULL;
const cbm_gbuf_node_t *t = target_qn ? cbm_gbuf_find_by_qn(gbuf, target_qn) : NULL;
bool files_exist = s && t && s->label && t->label && strcmp(s->label, "File") == 0 &&
strcmp(t->label, "File") == 0;
free(source_qn);
free(target_qn);
if (!files_exist)
return -1;
php_file_edge_count_t c = {.gbuf = gbuf, .type = type, .src_rel = source, .tgt_rel = target};
cbm_gbuf_foreach_node(gbuf, php_count_source_edges, &c);
return c.query_failed || c.source_nodes == 0 ? -1 : c.count;
}
static bool php_vendor_extracted_sites(void) {
for (int file = 3; file < PHP_VENDOR_FILE_COUNT; file++) {
const char *source = k_php_vendor_files[file].src;
CBMFileResult *r =
cbm_extract_file(source, (int)strlen(source), CBM_LANG_PHP, "cbm_par_phpvendor",
k_php_vendor_files[file].rel, 0, NULL, NULL);
if (!r)
return false;
bool ok = r->defs.count > 0 && r->imports.count == 2;
const char *core[] = {"validated", "makeIt", "go", "build", "get", "sendAsync"};
const char *service[] = {"get", "publish"};
const char *const *wanted = file == 3 ? core : service;
int wanted_count = file == 3 ? 6 : 2;
for (int i = 0; i < wanted_count; i++) {
int count = 0;
for (int j = 0; j < r->calls.count; j++) {
const CBMCall *c = &r->calls.items[j];
if (!c->callee_name || !c->enclosing_func_qn ||
strcmp(cbm_pipeline_call_callee_leaf(c->callee_name), wanted[i]) != 0 ||
c->site_end_byte <= c->site_start_byte || c->site_end_byte > strlen(source))
continue;
if (file == 4) {
const char *arg = i == 0 ? "/api/vendor-health" : "vendor-orders";
const char *alias = i == 0 ? "GuzzleClient::" : "KafkaProducer::";
/* The extractor names a scoped call by its method alone
* (it keeps only a literal `Route::` scope, #952), so the
* aliased scope is proven on the occurrence text. */
if (!c->first_string_arg || strcmp(c->first_string_arg, arg) != 0 ||
strncmp(source + c->site_start_byte, alias, strlen(alias)) != 0)
continue;
}
count++;
}
ok = count == 1 && ok;
}
cbm_free_result(r);
if (!ok)
return false;
}
return true;
}
static bool php_vendor_service_edge(const cbm_gbuf_t *gb, const char *type, const char *name) {
const cbm_gbuf_node_t *source = find_unique_callable_node_by_tail(gb, "Clients.run");
if (!source)
return false;
const cbm_gbuf_edge_t **edges = NULL;
int count = 0;
if (cbm_gbuf_find_edges_by_source_type(gb, source->id, type, &edges, &count) != 0 || count != 1)
return false;
const cbm_gbuf_node_t *target = cbm_gbuf_find_by_id(gb, edges[0]->target_id);
return target && target->label && strcmp(target->label, "Route") == 0 &&
edges[0]->properties_json && strstr(edges[0]->properties_json, name);
}
static void php_vendor_edge_fp(const cbm_gbuf_edge_t *edge, void *ud) {
if (strcmp(edge->type, "CALLS") == 0 || strcmp(edge->type, "USAGE") == 0 ||
strcmp(edge->type, "INHERITS") == 0 || strcmp(edge->type, "IMPORTS") == 0 ||
strcmp(edge->type, "HTTP_CALLS") == 0 || strcmp(edge->type, "ASYNC_CALLS") == 0)
fp_visit_edge(edge, ud);
}
/* 1 when the graph holds no vendor-named edge and every control edge. */
static int php_vendor_graph_ok(const cbm_gbuf_t *gbuf, const char *driver) {
static const char *const foo = "lib/Integrations/Foo.php";
static const char *const req = "lib/Requests/Request.php";
static const char *const svc = "lib/Services/Svc.php";
struct {
const char *type;
const char *tgt;
int want_min;
int want_max;
} checks[] = {
{"INHERITS", req, 0, 0}, {"CALLS", req, 0, 0}, {"USAGE", req, 0, 0},
{"CALLS", "web/api.ts", 0, 0}, {"INHERITS", svc, 1, 1}, {"CALLS", svc, 2, 2},
{"CALLS", foo, 0, 0}, {"USAGE", svc, 1, 1 << 20},
};
int ok = 1;
for (size_t i = 0; i < sizeof(checks) / sizeof(checks[0]); i++) {
int got = php_file_edges(gbuf, checks[i].type, foo, checks[i].tgt);
if (got < checks[i].want_min || got > checks[i].want_max) {
printf(" [%s] %s Foo.php -> %s: got %d, want %d..%d\n", driver, checks[i].type,
checks[i].tgt, got, checks[i].want_min, checks[i].want_max);
ok = 0;
}
}
return ok && php_vendor_service_edge(gbuf, "HTTP_CALLS", "/api/vendor-health") &&
php_vendor_service_edge(gbuf, "ASYNC_CALLS", "vendor-orders");
}
TEST(parallel_php_vendor_types_never_bind_by_name_issue1186) {
ASSERT_TRUE(php_vendor_extracted_sites());
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_phpvendor_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("mkdtemp failed");
}
char paths[PHP_VENDOR_FILE_COUNT][512];
cbm_file_info_t files[PHP_VENDOR_FILE_COUNT];
memset(files, 0, sizeof(files));
for (int i = 0; i < PHP_VENDOR_FILE_COUNT; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/%s", tmpdir, k_php_vendor_files[i].rel);
char dir[512];
snprintf(dir, sizeof(dir), "%s", paths[i]);
*strrchr(dir, '/') = '\0';
cbm_mkdir_p(dir, 0755);
FILE *f = fopen(paths[i], "w");
if (!f) {
th_rmtree(tmpdir);
FAIL("fopen failed");
}
bool wrote = fputs(k_php_vendor_files[i].src, f) >= 0;
bool closed = fclose(f) == 0;
if (!wrote || !closed) {
th_rmtree(tmpdir);
FAIL("fixture write failed");
}
files[i].path = paths[i];
files[i].rel_path = (char *)k_php_vendor_files[i].rel;
files[i].language = k_php_vendor_files[i].lang;
}
cbm_gbuf_t *seq = run_sequential_with_lsp_cross_and_mutator(
"cbm_par_phpvendor", tmpdir, files, PHP_VENDOR_FILE_COUNT, NULL, NULL, true);
cbm_gbuf_t *par = run_parallel_with_extract_opts_and_mutator(
"cbm_par_phpvendor", tmpdir, files, PHP_VENDOR_FILE_COUNT, 2, NULL, NULL, NULL, true);
const size_t saved_budget = cbm_mem_budget();
const size_t budget = (size_t)1024 * 1024 * 1024;
cbm_mem_set_budget_for_tests(budget);
cbm_mem_set_charged_for_tests(budget - budget / 16);
g_harness_spill = true;
int cached = -1;
cbm_gbuf_t *spilled = run_parallel_with_extract_opts_and_mutator(
"cbm_par_phpvendor", tmpdir, files, PHP_VENDOR_FILE_COUNT, 2, NULL, count_cached_results,
&cached, true);
int64_t parked = g_harness_parked;
g_harness_spill = false;
cbm_mem_set_charged_for_tests(0);
cbm_mem_set_budget_for_tests(saved_budget);
int seq_ok = seq ? php_vendor_graph_ok(seq, "sequential") : 0;
int par_ok = par ? php_vendor_graph_ok(par, "parallel") : 0;
int spill_ok = spilled ? php_vendor_graph_ok(spilled, "spilled") : 0;
graph_fp_t seq_edges = {.gb = seq}, par_edges = {.gb = par};
graph_fp_t par_all = {0}, spill_all = {0};
if (seq)
cbm_gbuf_foreach_edge(seq, php_vendor_edge_fp, &seq_edges);
if (par) {
cbm_gbuf_foreach_edge(par, php_vendor_edge_fp, &par_edges);
par_all = graph_fingerprint(par);
}
if (spilled)
spill_all = graph_fingerprint(spilled);
bool edge_parity = seq_edges.count > 0 && seq_edges.count == par_edges.count &&
seq_edges.sum == par_edges.sum && seq_edges.xr == par_edges.xr;
bool spill_parity = par_all.count > 0 && par_all.count == spill_all.count &&
par_all.sum == spill_all.sum && par_all.xr == spill_all.xr;
cbm_gbuf_free(seq);
cbm_gbuf_free(par);
cbm_gbuf_free(spilled);
th_rmtree(tmpdir);
ASSERT_EQ(cached, 0);
ASSERT_EQ((int)parked, PHP_VENDOR_FILE_COUNT);
ASSERT_TRUE(seq_ok);
ASSERT_TRUE(par_ok);
ASSERT_TRUE(spill_ok);
ASSERT_TRUE(edge_parity);
ASSERT_TRUE(spill_parity);
PASS();
}
/* ── PHP LSP types receivers of imported project classes (#1186) ─── */
/* Foo.php calls methods on receivers typed with classes imported from OTHER
* project files: a promoted property (`$this->svc->go()`) and an aliased
* import (`R $r` -> `$r->find()`). The import map handed to the PHP LSP held
* the IMPORTS edge target -- the imported file's File node QN
* (`...Svc.php.__file__`) -- as the class, so no receiver ever typed and the
* calls fell to name-only strategies (the #1186 "useful calls at 0.38"). The
* decoy App\Other\Svc shares the short name and the method, so a name-only
* match cannot tell the two apart. Controls: the vendor-typed receiver
* (`Client $c`) must stay unresolved, and the same-file calls bind. */
static const struct {
const char *rel;
const char *src;
} k_php_xfile_files[] = {
{"lib/Services/Svc.php", "<?php\nnamespace App\\Services;\n\nclass Svc {\n"
" public function go() { return 1; }\n}\n"},
{"lib/Other/Svc.php", "<?php\nnamespace App\\Other;\n\nclass Svc {\n"
" public function go() { return 2; }\n}\n"},
{"lib/Repo/Repo.php", "<?php\nnamespace App\\Repo;\n\nclass Repo {\n"
" public function find() { return 3; }\n}\n"},
{"lib/Http/Foo.php",
"<?php\nnamespace App\\Http;\n\n"
"use App\\Services\\Svc;\nuse App\\Repo\\Repo as R;\nuse Vendor\\Pkg\\Client;\n\n"
"class Foo {\n"
" public function __construct(private Svc $svc) {}\n"
" public function run(R $r, Client $c) {\n"
" $this->svc->go();\n"
" $r->find();\n"
" $c->go();\n"
" $this->local();\n"
" (new Local())->ping();\n"
" }\n"
" private function local() { return 4; }\n}\n\n"
"class Local {\n"
" public function ping() { return 5; }\n}\n"},
};
enum { PHP_XFILE_FILE_COUNT = (int)(sizeof(k_php_xfile_files) / sizeof(k_php_xfile_files[0])) };
typedef struct {
const cbm_gbuf_t *gbuf;
const char *src_rel;
const char *tgt_rel;
const char *tgt_suffix;
const char *strategy_prefix;
int total;
int with_strategy;
} php_xfile_edge_count_t;
static void php_xfile_count_edge(const cbm_gbuf_edge_t *edge, void *ud) {
php_xfile_edge_count_t *c = ud;
if (!edge->type || strcmp(edge->type, "CALLS") != 0) {
return;
}
const cbm_gbuf_node_t *s = cbm_gbuf_find_by_id(c->gbuf, edge->source_id);
const cbm_gbuf_node_t *t = cbm_gbuf_find_by_id(c->gbuf, edge->target_id);
if (!s || !t || !s->file_path || !t->file_path || !t->qualified_name ||
strcmp(s->file_path, c->src_rel) != 0 || strcmp(t->file_path, c->tgt_rel) != 0) {
return;
}
size_t qlen = strlen(t->qualified_name);
size_t slen = strlen(c->tgt_suffix);
if (qlen < slen || strcmp(t->qualified_name + qlen - slen, c->tgt_suffix) != 0) {
return;
}
c->total++;
char needle[128];
snprintf(needle, sizeof(needle), "\"strategy\":\"%s", c->strategy_prefix);
if (edge->properties_json && strstr(edge->properties_json, needle)) {
c->with_strategy++;
}
}
/* One unresolved vendor import voids no other import: each project receiver
* binds to the class its own `use` names, and the decoy that shares a short
* name and a method gets nothing. Proof, not a strategy label, admits the edge,
* so no edge carries an LSP strategy. */
static int php_xfile_graph_ok(const cbm_gbuf_t *gbuf, const char *driver) {
static const char *const foo = "lib/Http/Foo.php";
struct {
const char *tgt;
const char *suffix;
int want_total;
int want_lsp;
} checks[] = {
{"lib/Services/Svc.php", ".Svc.go", 1, 0}, /* $this->svc->go(), not $c->go() */
{"lib/Other/Svc.php", ".Svc.go", 0, 0}, /* decoy: same short name + method */
{"lib/Repo/Repo.php", ".Repo.find", 1, 0}, /* aliased import */
{foo, ".Foo.local", 1, 0}, /* same-file control */
{foo, ".Local.ping", 1, 0}, /* same-file control */
};
int ok = 1;
for (size_t i = 0; i < sizeof(checks) / sizeof(checks[0]); i++) {
php_xfile_edge_count_t c = {gbuf, foo, checks[i].tgt, checks[i].suffix, "php_", 0, 0};
cbm_gbuf_foreach_edge(gbuf, php_xfile_count_edge, &c);
if (c.total != checks[i].want_total || c.with_strategy != checks[i].want_lsp) {
printf(" [%s] CALLS Foo.php -> %s%s: %d edge(s), %d via php_*; want %d, %d\n", driver,
checks[i].tgt, checks[i].suffix, c.total, c.with_strategy, checks[i].want_total,
checks[i].want_lsp);
ok = 0;
}
}
return ok;
}
/* Run the fixture on both venues. extra_use (nullable) is one more `use` line
* added to Foo.php. Returns 1 when both graphs pass, 0 when not, -1 on setup
* failure. */
static int php_xfile_run(const char *extra_use) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_par_phpxfile_XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
return -1;
}
char paths[PHP_XFILE_FILE_COUNT][512];
cbm_file_info_t files[PHP_XFILE_FILE_COUNT];
memset(files, 0, sizeof(files));
static const char foo_use[] = "use Vendor\\Pkg\\Client;\n";
for (int i = 0; i < PHP_XFILE_FILE_COUNT; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/%s", tmpdir, k_php_xfile_files[i].rel);
char dir[512];
snprintf(dir, sizeof(dir), "%s", paths[i]);
*strrchr(dir, '/') = '\0';
cbm_mkdir_p(dir, 0755);
char src[2048];
const char *s = k_php_xfile_files[i].src;
const char *at = extra_use ? strstr(s, foo_use) : NULL;
if (at) {
size_t head = (size_t)(at - s) + sizeof(foo_use) - 1;
snprintf(src, sizeof(src), "%.*s%s%s", (int)head, s, extra_use, s + head);
s = src;
}
if (th_write_file(paths[i], s) != 0) {
th_rmtree(tmpdir);
return -1;
}
files[i].path = paths[i];
files[i].rel_path = (char *)k_php_xfile_files[i].rel;
files[i].language = CBM_LANG_PHP;
}
cbm_gbuf_t *seq = run_sequential_with_lsp_cross_and_mutator(
"cbm_par_phpxfile", tmpdir, files, PHP_XFILE_FILE_COUNT, NULL, NULL, true);
cbm_gbuf_t *par = run_parallel_with_extract_opts_and_mutator(
"cbm_par_phpxfile", tmpdir, files, PHP_XFILE_FILE_COUNT, 2, NULL, NULL, NULL, true);
RQ_Source sources[PHP_XFILE_FILE_COUNT] = {0};
for (int i = 0; i < PHP_XFILE_FILE_COUNT; i++)
sources[i] = (RQ_Source){.path = k_php_xfile_files[i].rel,
.text = parallel_fixture_source(paths[i])};
const struct {
const char *id, *needle, *suffix, *decl;
int target, line;
} cases[] = {{"php-promoted-property", "$this->svc->go()", "lib.Services.Svc.Svc.go",
" public function go() { return 1; }", 0, 5},
{"php-imported-receiver", "$r->find()", "lib.Repo.Repo.Repo.find",
" public function find() { return 3; }", 2, 5},
{"php-local-private", "$this->local()", "lib.Http.Foo.Foo.local",
" private function local() { return 4; }", 3, 17},
{"php-constructed-receiver", "(new Local())->ping()", "lib.Http.Foo.Local.ping",
" public function ping() { return 5; }", 3, 21}};
RQ_Expected expected[4] = {0};
for (int i = 0; i < 4; i++)
expected[i] = (RQ_Expected){
.case_id = cases[i].id,
.site = {.file = "lib/Http/Foo.php",
.caller = "lib.Http.Foo.Foo.run",
.needle = cases[i].needle},
.relation = "CALLS",
.target = {.file = k_php_xfile_files[cases[i].target].rel,
.suffix = cases[i].suffix,
.label = "Method",
.declaration_line =
cases[i].line + (extra_use && cases[i].target == 3 ? 1 : 0),
.declaration_text = cases[i].decl},
.mode = RQ_REQUIRED};
/* The vendor receiver has no project declaration: Svc::go is its decoy. */
const RQ_Forbidden forbidden[] = {
{.case_id = "php-vendor-receiver-stays-unresolved",
.site = {.file = "lib/Http/Foo.php",
.caller = "lib.Http.Foo.Foo.run",
.needle = "$c->go()"},
.relation = "CALLS",
.any_target = true}};
RQ_Report quality[2] = {0};
bool quality_ok =
seq && par &&
parallel_quality_check_with_forbidden(seq, "cbm_par_phpxfile", "php-receiver-sequential",
sources, PHP_XFILE_FILE_COUNT, expected, 4, forbidden,
1, &quality[0]) &&
parallel_quality_check_with_forbidden(par, "cbm_par_phpxfile", "php-receiver-parallel",
sources, PHP_XFILE_FILE_COUNT, expected, 4, forbidden,
1, &quality[1]) &&
quality[0].genuine == 4 && quality[0].found == 4 && quality[0].missing == 0 &&
quality[1].genuine == 4 && quality[1].found == 4 && quality[1].missing == 0;
for (int i = 0; i < PHP_XFILE_FILE_COUNT; i++)
free((void *)sources[i].text);
int seq_ok = seq ? php_xfile_graph_ok(seq, "sequential") : 0;
int par_ok = par ? php_xfile_graph_ok(par, "parallel") : 0;
cbm_gbuf_free(seq);
cbm_gbuf_free(par);
th_rmtree(tmpdir);
return quality_ok && seq_ok && par_ok;
}
TEST(parallel_php_lsp_types_imported_project_receivers_issue1186) {
ASSERT_EQ(php_xfile_run(NULL), 1);
PASS();
}
/* The same file with one more import that no file in the project declares,
* the global class `use Exception;` written in most namespaced PHP files.
* The typed import evidence leaves such a file "unknown" as a whole (#2520),
* so the class-valued import mapping must not depend on that evidence. */
TEST(parallel_php_lsp_types_imported_project_receivers_unknown_bindings_issue1186) {
ASSERT_EQ(php_xfile_run("use Exception;\n"), 1);
PASS();
}
/* A fresh PHP external row replaces stale evidence for its exact occurrence,
* including an older external row, without clearing a real project invocation
* of the same method at the next occurrence. Test both old-row orders. */
TEST(parallel_php_external_row_replaces_only_its_exact_occurrence) {
const char *source = "<?php\nnamespace App;\nuse Vendor\\Http\\Request;\n"
"use App\\Project\\Request as LocalRequest;\n"
"class Caller { function run(Request $r, LocalRequest $p) {\n"
" $r->validated();\n $p->validated();\n}\n}\n";
const char *project_type = "p.lib.Request.Request";
CBMLSPDef defs[] = {
{.qualified_name = project_type,
.short_name = "Request",
.label = "Class",
.def_module_qn = "p.lib.Request",
.lang = CBM_LANG_PHP},
{.qualified_name = "p.lib.Request.Request.validated",
.short_name = "validated",
.label = "Method",
.receiver_type = project_type,
.def_module_qn = "p.lib.Request",
.lang = CBM_LANG_PHP},
};
for (int order = 0; order < 2; order++) {
CBMFileResult *r = cbm_extract_file(source, (int)strlen(source), CBM_LANG_PHP, "p",
"main.php", 0, NULL, NULL);
ASSERT_NOT_NULL(r);
ASSERT_EQ(r->imports.count, 2);
const CBMCall *sites[2] = {0};
int count = 0;
for (int i = 0; i < r->calls.count; i++) {
const CBMCall *call = &r->calls.items[i];
if (call->enclosing_func_qn && strstr(call->enclosing_func_qn, "Caller.run") &&
call->callee_name &&
strcmp(cbm_pipeline_call_callee_leaf(call->callee_name), "validated") == 0 &&
call->site_end_byte > call->site_start_byte) {
ASSERT_TRUE(count < 2);
sites[count++] = call;
}
}
ASSERT_EQ(count, 2);
if (sites[0]->site_start_byte > sites[1]->site_start_byte) {
const CBMCall *swap = sites[0];
sites[0] = sites[1];
sites[1] = swap;
}
ASSERT_TRUE(sites[0]->site_end_byte <= sites[1]->site_start_byte);
CBMResolvedCall stale = {0};
stale.caller_qn = sites[0]->enclosing_func_qn;
stale.callee_qn = "p.lib.Request.Request.validated";
stale.strategy = "test_stale_project";
stale.confidence = 0.99f;
stale.kind = CBM_RESOLVED_INVOCATION;
stale.site_start_byte = sites[0]->site_start_byte;
stale.site_end_byte = sites[0]->site_end_byte;
stale.source_origin = sites[0]->source_origin;
CBMResolvedCall old_external = stale;
old_external.callee_qn = "Old.Vendor.Request.validated";
old_external.strategy = "php_external_receiver";
CBMResolvedCall separate = stale;
separate.strategy = "test_separate_project";
separate.site_start_byte = sites[1]->site_start_byte;
separate.site_end_byte = sites[1]->site_end_byte;
separate.source_origin = sites[1]->source_origin;
r->resolved_calls.count = 0;
cbm_resolvedcall_push(&r->resolved_calls, &r->arena, order ? old_external : stale);
cbm_resolvedcall_push(&r->resolved_calls, &r->arena, order ? stale : old_external);
cbm_resolvedcall_push(&r->resolved_calls, &r->arena, separate);
const char *targets[2] = {0};
for (int i = 0; i < r->imports.count; i++)
if (strcmp(r->imports.items[i].local_name, "LocalRequest") == 0)
targets[i] = "p.lib.Request.__file__";
CBMPHPImportBindings bindings = {.imports = &r->imports, .targets = targets};
const char *keys[] = {"LocalRequest"};
const char *vals[] = {project_type};
cbm_pxc_run_one_with_php_bindings(CBM_LANG_PHP, r, source, (int)strlen(source), "p.main",
NULL, defs, 2, keys, vals, 1, &bindings);
int external_count = 0, project_count = 0;
for (int i = 0; i < r->resolved_calls.count; i++) {
const CBMResolvedCall *row = &r->resolved_calls.items[i];
if (!row->caller_qn || strcmp(row->caller_qn, sites[0]->enclosing_func_qn) != 0 ||
row->kind != CBM_RESOLVED_INVOCATION)
continue;
if (row->site_start_byte == sites[0]->site_start_byte &&
row->site_end_byte == sites[0]->site_end_byte &&
row->source_origin == sites[0]->source_origin) {
ASSERT_STR_EQ(row->callee_qn, "Vendor.Http.Request.validated");
ASSERT_STR_EQ(row->strategy, "php_external_receiver");
external_count++;
} else if (row->site_start_byte == sites[1]->site_start_byte &&
row->site_end_byte == sites[1]->site_end_byte &&
row->source_origin == sites[1]->source_origin) {
ASSERT_STR_EQ(row->callee_qn, "p.lib.Request.Request.validated");
project_count++;
}
}
ASSERT_EQ(external_count, 1);
ASSERT_EQ(project_count, 1);
const CBMResolvedCall *vendor =
cbm_pipeline_find_lsp_resolution(&r->resolved_calls, sites[0], false);
const CBMResolvedCall *project =
cbm_pipeline_find_lsp_resolution(&r->resolved_calls, sites[1], false);
ASSERT_NOT_NULL(vendor);
ASSERT_NOT_NULL(project);
ASSERT_STR_EQ(vendor->strategy, "php_external_receiver");
ASSERT_STR_EQ(project->callee_qn, "p.lib.Request.Request.validated");
cbm_free_result(r);
}
PASS();
}
SUITE(parallel) {
RUN_TEST(parallel_php_external_row_replaces_only_its_exact_occurrence);
RUN_TEST(parallel_php_vendor_types_never_bind_by_name_issue1186);
RUN_TEST(parallel_php_lsp_types_imported_project_receivers_issue1186);
RUN_TEST(parallel_php_lsp_types_imported_project_receivers_unknown_bindings_issue1186);
RUN_TEST(usage_semantic_reference_candidate_trusts_marked_producer);
RUN_TEST(lsp_bare_segment_skips_preprocessor_spacing);
RUN_TEST(lsp_resolve_qualified_static_call_normalizes_colons);
RUN_TEST(lsp_resolve_distinct_exact_caller_targets_fail_closed);
RUN_TEST(lsp_resolve_duplicate_exact_caller_rows_same_target_are_not_ambiguous);
RUN_TEST(lsp_resolve_distinct_exact_caller_site_targets_are_ambiguous);
RUN_TEST(lsp_resolve_project_prefixed_duplicate_is_not_ambiguous);
RUN_TEST(lsp_resolve_project_prefix_spellings_stay_ambiguous_when_both_nodes_exist);
RUN_TEST(lsp_resolve_jvm_unproved_package_alias_cannot_merge_targets);
RUN_TEST(lsp_resolve_jvm_two_tail_only_packages_stay_ambiguous);
RUN_TEST(lsp_resolve_exact_site_beats_higher_confidence_legacy_record);
RUN_TEST(lsp_resolve_exact_site_beats_equal_confidence_legacy_record);
RUN_TEST(lsp_resolve_malformed_span_is_not_legacy_compatible);
RUN_TEST(lsp_resolve_below_floor_exact_does_not_block_legacy);
RUN_TEST(lsp_tail_occurrence_cannot_replace_exact_caller_identity);
RUN_TEST(lsp_tail_duplicate_exact_rows_require_exact_caller);
RUN_TEST(lsp_tail_distinct_exact_targets_are_ambiguous);
RUN_TEST(lsp_tail_duplicate_legacy_rows_require_exact_caller);
RUN_TEST(lsp_tail_distinct_legacy_targets_are_ambiguous);
RUN_TEST(lsp_reference_duplicate_exact_rows_same_target_are_not_ambiguous);
RUN_TEST(lsp_reference_project_prefixed_duplicate_is_not_ambiguous);
RUN_TEST(lsp_reference_project_prefix_spellings_stay_ambiguous_when_both_nodes_exist);
RUN_TEST(lsp_reference_jvm_distinct_packages_do_not_collapse_via_tail_node);
RUN_TEST(lsp_reference_distinct_exact_targets_are_order_invariant);
RUN_TEST(lsp_reference_duplicate_tail_rows_require_exact_caller);
RUN_TEST(lsp_reference_distinct_tail_targets_are_ambiguous);
RUN_TEST(lsp_target_node_supports_long_prefixed_qualified_name);
RUN_TEST(grpc_service_name_preserves_service_suffix_issue294);
RUN_TEST(grpc_no_phantom_route_from_plain_var_issue294);
/* Graph buffer merge/shared-ID tests */
RUN_TEST(gbuf_shared_ids_unique);
RUN_TEST(gbuf_merge_nodes);
RUN_TEST(gbuf_merge_edges);
RUN_TEST(gbuf_merge_empty_src);
RUN_TEST(gbuf_merge_src_free_safe);
RUN_TEST(gbuf_next_id_accessors);
RUN_TEST(gbuf_raise_next_id_never_lowers);
RUN_TEST(parallel_full_index_keeps_edge_ids_unique);
RUN_TEST(parallel_closure_repair_syncs_ids_through_the_helper);
/* Parallel pipeline parity tests */
RUN_TEST(parallel_node_count);
RUN_TEST(parallel_python_lsp_override_emits_lsp_strategy_edges);
RUN_TEST(parallel_lsp_index_exact_site_beats_legacy_record_in_graph);
RUN_TEST(parallel_lsp_index_distinct_exact_targets_fail_closed_in_graph);
RUN_TEST(parallel_lsp_index_exact_ambiguity_does_not_fall_through_to_legacy);
RUN_TEST(parallel_lsp_long_exact_key_never_yields_legacy_target);
RUN_TEST(parallel_lsp_exact_index_handles_repeated_synthetic_occurrences_without_linear_scan);
#if defined(CBM_CALL_REFERENCE_LOOKUP_TEST_API) && CBM_CALL_REFERENCE_LOOKUP_TEST_API
RUN_TEST(parallel_call_reference_lookup_rows_grow_linearly);
#endif
RUN_TEST(parallel_tsx_local_component_shadow_has_no_false_call);
RUN_TEST(parallel_exact_semantic_noncallable_target_stays_usage);
RUN_TEST(parallel_typescript_module_value_usage_respects_lexical_shadows);
RUN_TEST(parallel_typescript_import_namespace_exact_parity);
RUN_TEST(parallel_tsx_import_namespace_exact_parity);
RUN_TEST(parallel_csharp_same_name_class_binds_by_namespace);
RUN_TEST(parallel_csharp_nested_type_and_global_namespace_binding);
RUN_TEST(parallel_csharp_parse_damaged_namespace_is_not_global);
RUN_TEST(parallel_kotlin_external_protocol_does_not_use_project_class_method_tail);
RUN_TEST(parallel_kotlin_nonbinary_operator_carriers_reach_graph);
RUN_TEST(parallel_rust_cross_crate_worker_receives_workspace_manifest);
RUN_TEST(parallel_rust_cross_crate_manifest_beats_confident_local_resolution);
RUN_TEST(parallel_rust_known_macro_does_not_fallback_to_local_function);
RUN_TEST(parallel_rust_proc_macros_are_decorates_and_usage_only);
RUN_TEST(parallel_c_preprocessed_coordinate_collision_preserves_hidden_target);
RUN_TEST(parallel_cpp_preprocessed_coordinate_collision_preserves_hidden_target);
RUN_TEST(parallel_cuda_preprocessed_coordinate_collision_preserves_hidden_target);
RUN_TEST(parallel_python_lsp_override_cross_file_emits_lsp_strategy_edges);
RUN_TEST(parallel_go_cross_package_field_chain_resolves);
RUN_TEST(parallel_cross_file_reread_preserves_unretained_edges);
RUN_TEST(parallel_java_kotlin_lsp_override_cross_file_emits_lsp_strategy_edges);
RUN_TEST(parallel_rust_external_target_contract);
RUN_TEST(parallel_lsp_tail_match_fallbacks_disabled_for_all_languages);
RUN_TEST(parallel_calls_parity);
RUN_TEST(parallel_defines_parity);
RUN_TEST(parallel_defines_method_parity);
RUN_TEST(parallel_imports_parity);
RUN_TEST(parallel_usage_parity);
RUN_TEST(parallel_inherits_parity);
RUN_TEST(parallel_implements_parity);
RUN_TEST(parallel_semantic_fixture_expected_counts);
RUN_TEST(parallel_total_edges);
RUN_TEST(parallel_spill_mode_builds_the_same_graph);
RUN_TEST(parallel_post_extract_projection_spills_before_resolve);
RUN_TEST(parallel_post_extract_projection_covers_the_measured_growth);
RUN_TEST(sequential_definitions_cache_drops_trees_and_compacts);
RUN_TEST(sequential_definitions_configured_failure_releases_work_arena);
RUN_TEST(parallel_empty_files);
RUN_TEST(parallel_args_json_no_overflow);
RUN_TEST(parallel_wide_definition_keeps_fp_sp_bt_f14);
/* Cleanup shared state */
parity_teardown();
}