Files
codebase-memory-mcp/tests/test_parallel.c
T
Martin Vogel 2910e2842f feat(lsp): resolve cross-file base classes for the Python/TS cross-LSP
`CBMDefinition.base_classes` carries the SOURCE SPELLING of each base
("Base", "django.db.Model"): extraction strips keywords and generic
arguments, but it cannot know where the name is declared. The Python and
TS cross-file registrars, however, consume `CBMLSPDef.embedded_types` as
fully-qualified names — py_lookup_attribute and ts_lookup_member feed each
entry straight into cbm_registry_lookup_type.

An unqualified spelling therefore matched nothing declared in ANOTHER
file. `class Child(Base)` in child.py never saw Base in base.py, so a
call to an inherited method through a typed receiver found no member and
fell through to the weak textual cascade, where the receiver-aware guard
(#592/#606) correctly kills it. The member-lookup walk over
embedded_types was already there in both languages; only the names it
walked were unusable across files.

cbm_pxc_collect_all_defs now resolves each base spelling to a project QN
using exactly the inputs pass_semantic uses to draw its INHERITS edge:
the project registry, the declaring module, and the file's import map.
Two properties follow. The LSP's inheritance view is the same relation
the graph records, so the two cannot diverge. And the binding is import-
or same-module-backed rather than a short-name guess, so the CALLS edge
it enables is a supported fact that the weak-member guard keeps.

Weak registry strategies (suffix_match / unique_name / field_type_hint /
fuzzy) are rejected for bases via an explicit drop-list: a base bound
because some project type happens to share its name is precisely the
fabricated relation #606 removed, and inheritance multiplies it — every
inherited member of the wrong base would become a callable target. An
unresolved base keeps its raw spelling and the behaviour that predates
this change, so stdlib and third-party bases are unaffected.

Scoped to the languages whose registrars read embedded_types as QNs
(Python, JS/TS/TSX). Go, JVM, C#, C++ and Rust already qualify their own
embedded types and keep the raw join.

Cost is O(defs) hash lookups plus ONE import map per file: no per-call-
site hierarchy walk, no registry scan, no per-file registry rebuild. The
complexity guard stays linear (nodes 1.93, edges 2.07, per-file defs
2.00 against a files x corpus coupling of ~4).

Tests:
- ts/S6 flips to its own documented condition: was `calls == 0` as a
  tripwire recording the gap, now asserts calls >= 1 AND an lsp_ts_*
  strategy. Measured: strategy=lsp_ts_method, confidence 0.95.
- python/S6b is new and covers what python/S6 structurally cannot: S6's
  `def run(c)` parameter is un-annotated, so no receiver type exists to
  inherit through and any binding there is a guess about a name (it
  resolves via unique_name at 0.75). S6b supplies the receiver three ways
  — annotated parameter, `self` in the subclass, constructor result — and
  asserts INHERITS >= 1 plus an lsp_* strategy. Revert-check: all three
  fall back to unique_name at 0.75 without this change, lsp_method at
  0.90 with it.
- Both rows assert the STRATEGY, not just the edge count: a short-name
  guess and a resolved inherited member produce the same count in a
  two-file fixture, and only the strategy tells them apart.
- Corrects python/S6's stale comment citing a base_classes extraction
  bug; extraction_inheritance is green for Python and TS, and
  base_classes now holds clean names.

macOS full suite 7690 passed / 0 failed / 8 skipped (140 suites);
make lint-ci clean.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-29 17:30:00 +02:00

4129 lines
176 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/lsp_resolve.h"
#include "pipeline/worker_pool.h"
#include "graph_buffer/graph_buffer.h"
#include "discover/discover.h"
#include "foundation/platform.h"
#include "foundation/log.h"
#include "cbm.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);
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;
}
}
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();
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,
};
cbm_init();
cbm_pipeline_pass_definitions(&ctx, files, file_count);
cbm_pipeline_pass_calls(&ctx, files, file_count);
cbm_pipeline_pass_usages(&ctx, files, file_count);
cbm_pipeline_pass_semantic(&ctx, files, file_count);
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();
cbm_pipeline_pass_definitions(&ctx, files, file_count);
cbm_pipeline_pass_lsp_cross(&ctx, files, file_count, cache);
if (mutator) {
mutator(cache, file_count, mutator_ud);
}
cbm_pipeline_pass_calls(&ctx, files, file_count);
cbm_pipeline_pass_usages(&ctx, files, file_count);
cbm_pipeline_pass_semantic(&ctx, files, file_count);
for (int i = 0; i < file_count; i++) {
cbm_free_result(cache[i]);
}
free(cache);
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 ───────────────── */
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();
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,
};
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);
CBMFileResult **result_cache = calloc((size_t)file_count, sizeof(CBMFileResult *));
cbm_init();
if (extract_opts) {
cbm_parallel_extract_ex(&ctx, files, file_count, result_cache, &shared_ids, worker_count,
extract_opts);
} else {
cbm_parallel_extract(&ctx, files, file_count, result_cache, &shared_ids, worker_count);
}
cbm_gbuf_set_next_id(gbuf, atomic_load(&shared_ids));
if (mutator) {
mutator(result_cache, file_count, mutator_ud);
}
cbm_build_registry_from_cache(&ctx, files, file_count, result_cache);
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;
CBMLSPDef *all_defs =
def_modules ? cbm_pxc_collect_all_defs(&ctx, result_cache, files, file_count,
ctx.project_name, def_modules, &def_count, NULL)
: NULL;
CBMModuleDefIndex *module_def_index =
all_defs ? cbm_pxc_build_module_def_index(all_defs, def_count) : NULL;
cbm_parallel_resolve(&ctx, files, file_count, result_cache, &shared_ids, worker_count, all_defs,
def_count, def_modules, module_def_index,
NULL /* cross_registries — tests use per-file path */);
cbm_gbuf_set_next_id(gbuf, atomic_load(&shared_ids));
cbm_pxc_free_module_def_index(module_def_index);
free(all_defs);
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);
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();
}
/* ── 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();
}
/* ── 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). This indexes a fixture whose
* single call carries enough long args to drive pos past 2 KB; under the
* ASan test build a regression aborts here. */
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++) {
/* 100-char string literal per arg; 60 args => args JSON well past the
* 2 KB props buffer, forcing the pre-fix overshoot. */
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);
ASSERT_GT(cbm_gbuf_edge_count(gbuf), 0);
cbm_gbuf_free(gbuf);
cbm_discover_free(files, file_count);
th_rmtree(dir);
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();
}
/* ── Parallel-pipeline LSP-override regression ────────────────────── */
/* Pin the wiring fix that unified pass_calls.c (sequential) and
* pass_parallel.c (parallel) on cbm_pipeline_find_lsp_resolution +
* CBM_LSP_CONFIDENCE_FLOOR (lsp_resolve.h). Before the unification, the
* parallel path carried its own lsp_override_resolution_pp at floor 0.5
* while the sequential path used find_lsp_resolution at floor 0.6, so a
* project produced different CALLS edge attributions depending on which
* pipeline mode kicked in. This test indexes a small Python repo via
* the parallel pipeline and asserts at least one resulting CALLS edge
* carries an "lsp_*" strategy — proof the parallel path consults
* result->resolved_calls and emits LSP-attributed edges. */
typedef struct {
int lsp_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\":\"lsp")) {
c->lsp_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");
}
ASSERT_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 != 0 || 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);
/* alpha_edges is a SIDE EFFECT of this test, not its subject. The subject is
* beta_edges == 0 and legacy_edges == 0 below: an ambiguous exact LSP match
* must not fall through to the lower-ranked legacy semantic row. Both are
* unchanged, as are all four probe assertions above.
*
* It used to be 1 because this harness DELIBERATELY injects an ambiguous
* exact match and forces the shared matcher to fail closed, so
* `value.render()` drops past the LSP into the registry with THREE
* same-named candidates (Alpha/Beta/Legacy.render) and bound by
* suffix_match — MEASURED: strategy=suffix_match, cands=3, conf=0.5500.
* That is a 1-in-3 guess that happened to land on Alpha: exactly the weak
* member-call class the Python guard now suppresses (#1276), and the same
* accepted trade recorded on python/S6 in test_lsp_resolution_probe.c.
*
* NOT over-suppression: sibling tests in this suite index the same
* `value.render()` source and resolve it via lsp_method (cands=1,
* conf=0.9000), a strategy the guard keeps — they still assert 1. Only the
* sabotaged-LSP path degrades to a weak textual guess.
* Flips back to 1 once py_lsp_cross resolves the receiver on this path. */
ASSERT_EQ(alpha_edges, 0);
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\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);
ASSERT_NOT_NULL(find_calls_edge_by_tails(gbuf, "app.combine", "Number.__add__"));
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;
/* Replace incidental extractor usages with a deterministic one-to-one set of
* exact callable references. Every target is a real extracted Function node,
* so the fused resolver must traverse its ordinary usage-materialization path
* and emit 128 distinct CALL_REFERENCE edges. */
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 scale_caller():\n return None\n", file);
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();
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 != CALL_REFERENCE_SCALE_OCCURRENCES ||
exact_target_edges != CALL_REFERENCE_SCALE_OCCURRENCES || 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(gbuf);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(probe.injected);
ASSERT_EQ(probe.reference_count, CALL_REFERENCE_SCALE_OCCURRENCES);
ASSERT_EQ(lookup_rc, 0);
ASSERT_EQ(edge_count, CALL_REFERENCE_SCALE_OCCURRENCES);
ASSERT_EQ(exact_target_edges, 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);
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);
}
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. A resulting USAGE
* therefore proves that the exact semantic row was joined and then
* classified by the materialized target's non-callable label. */
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_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_TRUE(sequential_usage);
ASSERT_TRUE(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]; /* 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;
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.edge_counts[route][TS_EXACT_SELECTED_IMPORT] =
count_edges_between_nodes(graph, choose, selected, "USAGE");
observation.edge_counts[route][TS_EXACT_DECOY_IMPORT] =
count_edges_between_nodes(graph, choose, decoy, "USAGE");
observation.edge_counts[route][TS_EXACT_LOCAL_CLASS] =
count_edges_between_nodes(graph, choose, local_class, "USAGE");
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_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], 1);
}
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_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], 1);
}
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;
}
/* 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. Local alias
* and operator calls are positive controls for the narrowed target policy. */
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";
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");
}
cbm_file_info_t files[2] = {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;
cbm_gbuf_t *sequential = run_sequential_with_lsp_cross(project, tmpdir, files, 2);
cbm_gbuf_t *parallel = run_parallel(project, tmpdir, files, 2, 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, "Main.aliasCaller", "Main.projectHandler") != NULL;
const bool parallel_alias =
find_calls_edge_by_tails(parallel, "Main.aliasCaller", "Main.projectHandler") != NULL;
const bool sequential_operator =
find_calls_edge_by_tails(sequential, "Main.operatorCaller", "Box.plus") != NULL;
const bool parallel_operator =
find_calls_edge_by_tails(parallel, "Main.operatorCaller", "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_alias || !parallel_alias || !sequential_operator || !parallel_operator ||
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);
}
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_TRUE(sequential_alias);
ASSERT_TRUE(parallel_alias);
ASSERT_TRUE(sequential_operator);
ASSERT_TRUE(parallel_operator);
ASSERT_FALSE(sequential_wrong_iterator);
ASSERT_FALSE(parallel_wrong_iterator);
ASSERT_FALSE(sequential_wrong_component);
ASSERT_FALSE(parallel_wrong_component);
PASS();
}
/* Kotlin's unary, membership, and index conventions are real invocations just
* like binary `plus`: the LSP already proves the exact operator method at the
* exact expression span. The syntax extractor must provide a matching guarded
* CBMCall carrier so both graph paths can materialize CALLS without a textual
* guess. This test separately proves carrier, semantic row, exact join, and
* graph edge for each syntax family. */
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 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[2] = {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;
cbm_gbuf_t *sequential = run_sequential_with_lsp_cross(project, tmpdir, files, 2);
cbm_gbuf_t *parallel = run_parallel(project, tmpdir, files, 2, 2);
ASSERT_NOT_NULL(sequential);
ASSERT_NOT_NULL(parallel);
const bool sequential_unary =
find_calls_edge_by_tails(sequential, "Main.negate", "UnaryBox.unaryMinus") != NULL;
const bool parallel_unary =
find_calls_edge_by_tails(parallel, "Main.negate", "UnaryBox.unaryMinus") != NULL;
const bool sequential_contains =
find_calls_edge_by_tails(sequential, "Main.hasValue", "Bag.contains") != NULL;
const bool parallel_contains =
find_calls_edge_by_tails(parallel, "Main.hasValue", "Bag.contains") != NULL;
const bool sequential_not_contains =
find_calls_edge_by_tails(sequential, "Main.lacksValue", "Bag.contains") != NULL;
const bool parallel_not_contains =
find_calls_edge_by_tails(parallel, "Main.lacksValue", "Bag.contains") != NULL;
const bool sequential_index =
find_calls_edge_by_tails(sequential, "Main.lookup", "Slots.get") != NULL;
const bool parallel_index =
find_calls_edge_by_tails(parallel, "Main.lookup", "Slots.get") != NULL;
const bool sequential_increment =
find_calls_edge_by_tails(sequential, "Main.increment", "Counter.inc") != NULL;
const bool parallel_increment =
find_calls_edge_by_tails(parallel, "Main.increment", "Counter.inc") != NULL;
const bool sequential_decrement =
find_calls_edge_by_tails(sequential, "Main.decrement", "Counter.dec") != NULL;
const bool parallel_decrement =
find_calls_edge_by_tails(parallel, "Main.decrement", "Counter.dec") != NULL;
const bool sequential_false_contains =
find_calls_edge_by_tails(sequential, "Main.isBag", "Bag.contains") != NULL;
const bool parallel_false_contains =
find_calls_edge_by_tails(parallel, "Main.isBag", "Bag.contains") != NULL;
const bool sequential_false_get =
find_calls_edge_by_tails(sequential, "Main.assign", "Slots.get") != NULL;
const bool parallel_false_get =
find_calls_edge_by_tails(parallel, "Main.assign", "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_unary ||
!parallel_unary || !sequential_contains || !parallel_contains || !sequential_not_contains ||
!parallel_not_contains || !sequential_index || !parallel_index || !sequential_increment ||
!parallel_increment || !sequential_decrement || !parallel_decrement ||
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);
}
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_TRUE(sequential_unary);
ASSERT_TRUE(parallel_unary);
ASSERT_TRUE(sequential_contains);
ASSERT_TRUE(parallel_contains);
ASSERT_TRUE(sequential_not_contains);
ASSERT_TRUE(parallel_not_contains);
ASSERT_TRUE(sequential_index);
ASSERT_TRUE(parallel_index);
ASSERT_TRUE(sequential_increment);
ASSERT_TRUE(parallel_increment);
ASSERT_TRUE(sequential_decrement);
ASSERT_TRUE(parallel_decrement);
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 */);
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);
ASSERT_TRUE(correct_found);
ASSERT_FALSE(unlisted);
ASSERT_TRUE(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);
ASSERT_TRUE(correct_found);
ASSERT_FALSE(wrong_local);
ASSERT_TRUE(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 proc-macros are source-level decorator references. Both pipelines
* must represent them as DECORATES + USAGE and must not fabricate calls to
* implementation details that are 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, "w");
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);
}
cbm_gbuf_free(sequential);
cbm_gbuf_free(parallel);
unlink(path);
rmdir(tmpdir);
ASSERT_TRUE(seq_tokio_decorates);
ASSERT_TRUE(seq_tokio_usage);
ASSERT_TRUE(par_tokio_decorates);
ASSERT_TRUE(par_tokio_usage);
ASSERT_TRUE(seq_tracing_decorates);
ASSERT_TRUE(seq_tracing_usage);
ASSERT_TRUE(par_tracing_decorates);
ASSERT_TRUE(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");
}
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, "w");
if (!jf) {
FAIL("fopen example.java failed");
}
fprintf(jf, "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");
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, "w");
if (!kf) {
unlink(jpath);
rmdir(tmpdir);
FAIL("fopen example.kt failed");
}
fprintf(kf, "package com.example\n"
"\n"
"class KotlinService {\n"
" fun ping(javaService: JavaService): String {\n"
" return javaService.pong()\n"
" }\n"
"}\n");
fclose(kf);
cbm_file_info_t files[2] = {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;
cbm_gbuf_t *gbuf = run_parallel("com", tmpdir, files, 2, 2);
ASSERT_NOT_NULL(gbuf);
const cbm_gbuf_edge_t *java_to_kotlin =
find_calls_edge_by_tails(gbuf, "JavaCaller.call", "KotlinService.ping");
const cbm_gbuf_edge_t *kotlin_to_java =
find_calls_edge_by_tails(gbuf, "KotlinService.ping", "JavaService.pong");
ASSERT_NOT_NULL(java_to_kotlin);
ASSERT_NOT_NULL(kotlin_to_java);
ASSERT_NOT_NULL(java_to_kotlin->properties_json);
ASSERT_NOT_NULL(kotlin_to_java->properties_json);
ASSERT_NOT_NULL(strstr(java_to_kotlin->properties_json, "\"strategy\":\"lsp"));
ASSERT_NOT_NULL(strstr(kotlin_to_java->properties_json, "\"strategy\":\"lsp"));
ASSERT_TRUE(strstr(java_to_kotlin->properties_json, "\"strategy\":\"callee_suffix\"") == NULL);
ASSERT_TRUE(strstr(kotlin_to_java->properties_json, "\"strategy\":\"callee_suffix\"") == NULL);
cbm_gbuf_free(gbuf);
unlink(kpath);
unlink(jpath);
rmdir(tmpdir);
PASS();
}
/* Gate guard for the JVM-only unique-tail fallbacks (lsp_resolve.h).
*
* The tail fallbacks join LSP overrides across QN drift by unique
* "Class.method" leaf. That is only sound where class-per-file package
* semantics hold (Java/Kotlin); in any other language a single
* wrong-module coincidence would fabricate a CALLS edge, so
* cbm_pipeline_lsp_allow_tail_match must keep the fallbacks OFF there.
*
* NOTE: a natural end-to-end non-JVM coincidence fixture is impractical:
* reaching the fallbacks requires the LSP and the textual extraction to
* disagree on QN prefixes, which path-derived single-root languages do
* not produce in a small fixture (that drift is precisely the JVM
* mixed-source-root symptom the fallback exists for). So this test
* exercises the gated branches directly: the SAME wrong-module
* coincidence must resolve with the gate open (JVM) and must NOT with
* the gate closed. If the gate were removed — fallbacks made
* unconditional again — the gate-closed assertions below would fail. */
TEST(parallel_lsp_tail_match_fallbacks_gated_to_jvm) {
/* Policy: exactly the JVM languages. */
ASSERT_TRUE(cbm_pipeline_lsp_allow_tail_match(CBM_LANG_JAVA));
ASSERT_TRUE(cbm_pipeline_lsp_allow_tail_match(CBM_LANG_KOTLIN));
ASSERT_TRUE(!cbm_pipeline_lsp_allow_tail_match(CBM_LANG_PYTHON));
ASSERT_TRUE(!cbm_pipeline_lsp_allow_tail_match(CBM_LANG_GO));
ASSERT_TRUE(!cbm_pipeline_lsp_allow_tail_match(CBM_LANG_TYPESCRIPT));
ASSERT_TRUE(!cbm_pipeline_lsp_allow_tail_match(CBM_LANG_CPP));
/* 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_TRUE(cbm_pipeline_find_lsp_resolution(&arr, &call, true) == &rc_item);
/* Kotlin top-level functions have one more legitimate QN drift: the graph
* owner is file-shaped (`...Main.operatorCaller`) while the semantic owner
* is package-shaped (`app.operatorCaller`). An exact source occurrence and
* equal caller leaf may reconcile that JVM-only drift; the non-JVM policy
* remains closed, a different caller leaf remains closed, and distinct
* semantic targets at the same occurrence remain ambiguous. */
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_TRUE(cbm_pipeline_find_lsp_resolution(&top_level_arr, &top_level_call, true) ==
&top_level_rows[0]);
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_NOT_NULL(jvm_hit);
ASSERT_TRUE(strcmp(jvm_hit->qualified_name, "proj.zeta.Helper.run") == 0);
/* 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 parallel pipeline must surface at least one LSP-attributed
* CALLS edge. This proves the unified cbm_pipeline_find_lsp_resolution
* (shared with pass_calls.c at floor 0.6) is actually consulted in
* the parallel pipeline, and that the resulting edge is emitted with
* the LSP strategy intact rather than overwritten by the registry
* fallback. */
ASSERT_GT(c.lsp_strategy_count, 0);
cbm_gbuf_free(gbuf);
unlink(fpath0);
rmdir(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);
/* The cross-file LSP override must produce at least one lsp_*
* CALLS edge. Without the project-prefix fallback in
* cbm_pipeline_lsp_target_node this assertion would fail because the
* raw module-path callee_qn doesn't match the project-qualified
* gbuf node QN. */
ASSERT_GT(c.lsp_strategy_count, 0);
cbm_gbuf_free(gbuf);
unlink(apath);
unlink(gpath);
rmdir(tmpdir);
PASS();
}
/* RED/GREEN A — the graph-quality guarantee behind the low-RAM retention cap.
*
* The fused cross-file LSP step re-parses each file's source to resolve calls
* whose receiver type lives in ANOTHER file (the per-file pass cannot). When
* the retention cap drops a file's source, that resolution MUST still happen
* via a bounded on-demand re-read; otherwise the cross-file CALLS edge is LOST.
*
* Fixture: a Java<->Kotlin pair with genuinely cross-language calls that only
* the cross-file LSP resolves — JavaCaller.call -> KotlinService.ping (Java ->
* Kotlin) and KotlinService.ping -> JavaService.pong (Kotlin -> Java). These
* carry the "lsp" strategy and do NOT exist without the cross-file source,
* unlike same-file or import-local Python calls which the per-file pass already
* resolves (so counting lsp edges on those cannot detect the fallback).
*
* Three scenarios asserted GREEN with the re-read fallback in place:
* 1. CONTROL — default retention: both cross-file edges present. Proves the
* fixture genuinely produces them (non-vacuity guard).
* 2. NO-RETAIN — retain_sources=false: nothing retained -> edges survive only
* via the re-read fallback.
* 3. OVER-CAP — per-file cap = 1 byte: every file dropped by the SIZE cap ->
* edges survive only via the re-read fallback.
* On main (no fallback) scenarios 2 and 3 LOSE both edges = RED; scenario 1
* stays present = the non-vacuity control. */
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 = fopen(jpath, "w");
if (!jf) {
FAIL("fopen Example.java failed");
}
fprintf(jf, "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");
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 = fopen(kpath, "w");
if (!kf) {
FAIL("fopen KotlinService.kt failed");
}
fprintf(kf, "package com.example\n"
"\n"
"class KotlinService {\n"
" fun ping(javaService: JavaService): String {\n"
" return javaService.pong()\n"
" }\n"
"}\n");
fclose(kf);
cbm_file_info_t files[2] = {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;
/* 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++) {
cbm_gbuf_t *gbuf = run_parallel_with_extract_opts("com", tmpdir, files, 2, 2, scenarios[s]);
ASSERT_NOT_NULL(gbuf);
const cbm_gbuf_edge_t *java_to_kotlin =
find_calls_edge_by_tails(gbuf, "JavaCaller.call", "KotlinService.ping");
const cbm_gbuf_edge_t *kotlin_to_java =
find_calls_edge_by_tails(gbuf, "KotlinService.ping", "JavaService.pong");
/* Both cross-file (Java↔Kotlin) CALLS edges must be present in EVERY
* scenario. In the drop scenarios (s=1,2) the caller's source is NOT
* retained, so these edges exist ONLY because resolve_worker re-reads
* the source on demand. Without that fallback (main) they are LOST. */
ASSERT_NOT_NULL(java_to_kotlin);
ASSERT_NOT_NULL(kotlin_to_java);
/* And they must come from the source-dependent cross-file LSP, not a
* source-free suffix heuristic — proving the re-read actually ran. */
ASSERT_NOT_NULL(java_to_kotlin->properties_json);
ASSERT_NOT_NULL(strstr(java_to_kotlin->properties_json, "\"strategy\":\"lsp"));
ASSERT_NOT_NULL(kotlin_to_java->properties_json);
ASSERT_NOT_NULL(strstr(kotlin_to_java->properties_json, "\"strategy\":\"lsp"));
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_package_alias_of_materialized_target_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, "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);
ASSERT_NOT_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_exact_site_beats_legacy_regardless_of_order) {
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(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_same_target_are_not_ambiguous) {
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(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_same_target_are_not_ambiguous) {
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(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();
}
/* A JVM tail lookup may recover one materialized graph node when package- and
* path-shaped QNs drift, but it must not erase semantic disagreement between
* two resolver rows. `pkg1.Service.handler` and `pkg2.Service.handler` are
* distinct source targets even when each independently falls back to the same
* unique `Service.handler` graph node. Collapsing them would let row order or
* confidence fabricate an exact CALL_REFERENCE instead of failing closed. */
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);
bool same_tail_node = pkg1_tail && pkg2_tail && pkg1_tail->id == materialized_id &&
pkg2_tail->id == materialized_id;
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(same_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_same_target_are_not_ambiguous) {
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(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 ──────────────────────────────────────────── */
SUITE(parallel) {
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_package_alias_of_materialized_target_is_not_ambiguous);
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_exact_site_beats_legacy_regardless_of_order);
RUN_TEST(lsp_tail_duplicate_exact_rows_same_target_are_not_ambiguous);
RUN_TEST(lsp_tail_distinct_exact_targets_are_ambiguous);
RUN_TEST(lsp_tail_duplicate_legacy_rows_same_target_are_not_ambiguous);
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_same_target_are_not_ambiguous);
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);
/* 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_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_cross_file_reread_preserves_unretained_edges);
RUN_TEST(parallel_java_kotlin_lsp_override_cross_file_emits_lsp_strategy_edges);
RUN_TEST(parallel_lsp_tail_match_fallbacks_gated_to_jvm);
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_empty_files);
RUN_TEST(parallel_args_json_no_overflow);
/* Cleanup shared state */
parity_teardown();
}