Merge remote-tracking branch 'origin/main' into fix/php-global-use-known-absent

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-10-19 02:47:12 +02:00
6 changed files with 341 additions and 76 deletions
+14
View File
@@ -1176,6 +1176,20 @@ static const char *compute_class_qn(CBMExtractCtx *ctx, TSNode node, const WalkS
if (!name || !name[0]) {
return NULL;
}
/* Rust: the impl's type text carries its generic arguments (`impl<T> Gen<T>`,
* `impl<'a> Parser<'a>`), which the def side strips before naming the type
* (extract_defs.c extract_rust_impl: `Buffer<T>` -> `Buffer`). Strip them the
* same way, or every method of a generic impl gets a scope QN no node carries
* and all its calls fall back to the File node. */
if (ctx->language == CBM_LANG_RUST && strcmp(ts_node_type(node), "impl_item") == 0) {
char *lt = strchr(name, '<');
if (lt == name) {
return NULL; /* `<T as Tr>`-style self type: no name to scope by */
}
if (lt) {
*lt = '\0';
}
}
/* Nested class: prefix with the enclosing class QN (Outer.Inner) so this
* scope QN matches the def-side class QN (extract_defs.c compute_class_qn /
+18
View File
@@ -306,6 +306,22 @@ static void rust_strip_turbofish(char *path) {
*write = '\0';
}
/* In place: `Gen<T>` -> `Gen`, `foo::Bar<'a, T>` -> `foo::Bar` (#763). An
* impl's self type keeps its generic arguments in the source text, but every
* def-side QN (a method's receiver, the extractor's call scope) strips them.
* Unstripped, the LSP's caller QN, `self`/`Self` dispatch and the return-type
* harvest never matched a generic impl's methods. Text starting with `<` (a
* qualified path) is left alone. Safe on NULL. */
static void rust_strip_impl_type_args(char *type_text) {
if (!type_text) {
return;
}
char *lt = strchr(type_text, '<');
if (lt && lt > type_text) {
*lt = '\0';
}
}
/* Look up a `use` alias and return its fully-qualified module path,
* or NULL if absent. The returned pointer aliases into the use map. */
static const char *rust_resolve_use(RustLSPContext *ctx, const char *local_name) {
@@ -5274,6 +5290,7 @@ static void rust_process_impl(RustLSPContext *ctx, TSNode impl_node) {
char *type_text = rust_node_text(ctx, type_node);
if (!type_text)
return;
rust_strip_impl_type_args(type_text);
/* Detect blanket impl: `impl<T: Trait> ForeignTrait for T { ... }`
* where type_text is a name that appears in the impl's type
@@ -6028,6 +6045,7 @@ void cbm_rust_build_local_registry(CBMArena *arena, CBMTypeRegistry *reg, CBMFil
if (ts_node_is_null(type_node) || ts_node_is_null(body))
continue;
char *type_name = cbm_node_text(arena, type_node, source);
rust_strip_impl_type_args(type_name);
if (!type_name || !type_name[0])
continue;
const char *type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name);
+66 -76
View File
@@ -677,12 +677,6 @@ static void finish_data_flow_props(char *props, size_t propsz, size_t pos,
}
}
typedef struct {
int64_t source_id;
const char *props;
const char *edge_type;
} caller_edge_ref_t;
static bool http_call_edge_has_valid_route(const cbm_gbuf_edge_t *edge) {
char url_buf[CBM_SZ_512];
const char *url = json_extract(edge->properties_json, "url_path", url_buf, sizeof(url_buf));
@@ -747,84 +741,82 @@ static int try_create_data_flow(cbm_gbuf_t *gb, int64_t caller_id, int64_t handl
return SKIP_ONE;
}
/* Collect extra handler IDs reachable via INFRA_MAPS chain from a route. */
static int collect_infra_handlers(cbm_gbuf_t *gb, int64_t route_id, int64_t *out, int max_out) {
const cbm_gbuf_edge_t **infra_edges = NULL;
int infra_count = 0;
cbm_gbuf_find_edges_by_source_type(gb, route_id, "INFRA_MAPS", &infra_edges, &infra_count);
/* One route's DATA_FLOWS context: its direct handlers (handler -[HANDLES]-> route) and its
* INFRA_MAPS hops (route -[INFRA_MAPS]-> endpoint, whose handlers are reached via the endpoint).
* The edge arrays are the graph buffer's own index arrays. Inserting DATA_FLOWS never appends to
* a HANDLES, INFRA_MAPS, HTTP_CALLS or ASYNC_CALLS array, so they stay valid while flows are added
* (only edge deletion rebuilds the indexes). */
typedef struct {
cbm_gbuf_t *gb;
const cbm_gbuf_node_t *route;
const cbm_gbuf_edge_t **handles;
int handles_count;
const cbm_gbuf_edge_t **infra;
int infra_count;
int flows;
int skipped;
} route_flows_t;
int n = 0;
for (int ie = 0; ie < infra_count; ie++) {
const cbm_gbuf_edge_t **ep_handles = NULL;
int ep_hcount = 0;
cbm_gbuf_find_edges_by_target_type(gb, infra_edges[ie]->target_id, "HANDLES", &ep_handles,
&ep_hcount);
for (int eh = 0; eh < ep_hcount && n < max_out; eh++) {
out[n++] = ep_handles[eh]->source_id;
}
static void route_flow_add(route_flows_t *rf, const cbm_gbuf_edge_t *caller, int64_t handler_id,
const char *edge_type, bool via_infra) {
int rc = try_create_data_flow(rf->gb, caller->source_id, handler_id, rf->route, edge_type,
caller->properties_json, via_infra);
if (rc > 0) {
rf->flows++;
} else if (rc < 0) {
rf->skipped++;
}
return n;
}
/* Collect caller edges (HTTP_CALLS + ASYNC_CALLS) targeting a route. */
static int collect_caller_edges(cbm_gbuf_t *gb, int64_t route_id, caller_edge_ref_t *out,
int max_out) {
int n = 0;
/* DATA_FLOWS from one caller edge to every handler of the route, direct and via infra. */
static void caller_data_flows(route_flows_t *rf, const cbm_gbuf_edge_t *caller,
const char *edge_type) {
for (int hi = 0; hi < rf->handles_count; hi++) {
route_flow_add(rf, caller, rf->handles[hi]->source_id, edge_type, false);
}
for (int ie = 0; ie < rf->infra_count; ie++) {
const cbm_gbuf_edge_t **ep_handles = NULL;
int ep_hcount = 0;
cbm_gbuf_find_edges_by_target_type(rf->gb, rf->infra[ie]->target_id, "HANDLES", &ep_handles,
&ep_hcount);
for (int eh = 0; eh < ep_hcount; eh++) {
route_flow_add(rf, caller, ep_handles[eh]->source_id, edge_type, true);
}
}
}
/* Create DATA_FLOWS between EVERY caller (HTTP_CALLS + ASYNC_CALLS) and EVERY handler of one
* route node.
*
* Callers were copied into a 64-slot buffer and infra handlers into a 32-slot one, each keeping the
* first entries in edge-insertion order -- i.e. in parallel worker interleaving. Which DATA_FLOWS
* existed therefore changed between two indexes of the same tree with the same binary
* (thingsboard, 2026-10-17), and every caller past the 64th lost its flow outright. The flow set
* is now the full caller x handler product. A (caller, handler) pair reached twice -- through two
* routes, or directly and via infra -- keeps the order-free property winner of
* cbm_gbuf_insert_edge. */
static void create_route_data_flows(cbm_gbuf_t *gb, const cbm_gbuf_node_t *route, int *flows,
int *skipped) {
route_flows_t rf = {.gb = gb, .route = route};
cbm_gbuf_find_edges_by_target_type(gb, route->id, "HANDLES", &rf.handles, &rf.handles_count);
cbm_gbuf_find_edges_by_source_type(gb, route->id, "INFRA_MAPS", &rf.infra, &rf.infra_count);
const cbm_gbuf_edge_t **http_edges = NULL;
int http_count = 0;
cbm_gbuf_find_edges_by_target_type(gb, route_id, "HTTP_CALLS", &http_edges, &http_count);
for (int i = 0; i < http_count && n < max_out; i++) {
if (!http_call_edge_has_valid_route(http_edges[i])) {
continue;
cbm_gbuf_find_edges_by_target_type(gb, route->id, "HTTP_CALLS", &http_edges, &http_count);
for (int i = 0; i < http_count; i++) {
if (http_call_edge_has_valid_route(http_edges[i])) {
caller_data_flows(&rf, http_edges[i], "HTTP_CALLS");
}
out[n].source_id = http_edges[i]->source_id;
out[n].props = http_edges[i]->properties_json;
out[n].edge_type = "HTTP_CALLS";
n++;
}
const cbm_gbuf_edge_t **async_edges = NULL;
int async_count = 0;
cbm_gbuf_find_edges_by_target_type(gb, route_id, "ASYNC_CALLS", &async_edges, &async_count);
for (int i = 0; i < async_count && n < max_out; i++) {
out[n].source_id = async_edges[i]->source_id;
out[n].props = async_edges[i]->properties_json;
out[n].edge_type = "ASYNC_CALLS";
n++;
}
return n;
}
/* Create DATA_FLOWS between callers and handlers for one route node. */
static void create_route_data_flows(cbm_gbuf_t *gb, const cbm_gbuf_node_t *route,
const caller_edge_ref_t *callers, int n_callers, int *flows,
int *skipped) {
const cbm_gbuf_edge_t **handles_edges = NULL;
int handles_count = 0;
cbm_gbuf_find_edges_by_target_type(gb, route->id, "HANDLES", &handles_edges, &handles_count);
int64_t extra_handlers[CBM_SZ_32];
int n_extra = collect_infra_handlers(gb, route->id, extra_handlers, CBM_SZ_32);
for (int ci = 0; ci < n_callers; ci++) {
for (int hi = 0; hi < handles_count; hi++) {
int rc = try_create_data_flow(gb, callers[ci].source_id, handles_edges[hi]->source_id,
route, callers[ci].edge_type, callers[ci].props, false);
if (rc > 0) {
(*flows)++;
} else if (rc < 0) {
(*skipped)++;
}
}
for (int xi = 0; xi < n_extra; xi++) {
int rc = try_create_data_flow(gb, callers[ci].source_id, extra_handlers[xi], route,
callers[ci].edge_type, callers[ci].props, true);
if (rc > 0) {
(*flows)++;
} else if (rc < 0) {
(*skipped)++;
}
}
cbm_gbuf_find_edges_by_target_type(gb, route->id, "ASYNC_CALLS", &async_edges, &async_count);
for (int i = 0; i < async_count; i++) {
caller_data_flows(&rf, async_edges[i], "ASYNC_CALLS");
}
*flows += rf.flows;
*skipped += rf.skipped;
}
/* Find the enclosing proto service for a function by file + line range. */
@@ -931,9 +923,7 @@ static void create_data_flows(cbm_gbuf_t *gb) {
int skipped = 0;
for (int ri = 0; ri < route_count; ri++) {
caller_edge_ref_t caller_edges[CBM_SZ_64];
int n_callers = collect_caller_edges(gb, routes[ri]->id, caller_edges, CBM_SZ_64);
create_route_data_flows(gb, routes[ri], caller_edges, n_callers, &flows, &skipped);
create_route_data_flows(gb, routes[ri], &flows, &skipped);
}
if (flows > 0 || skipped > 0) {
+58
View File
@@ -7178,6 +7178,63 @@ TEST(extract_perl_method_call_flags_is_method) {
PASS();
}
/* The def QN of `name` and the enclosing-function QN of the call `callee` inside
* it, from one extraction result. */
static void rust_def_and_scope(const CBMFileResult *r, const char *name, const char *callee,
const char **def_qn, const char **scope_qn) {
*def_qn = NULL;
*scope_qn = NULL;
for (int i = 0; i < r->defs.count; i++) {
const CBMDefinition *d = &r->defs.items[i];
if (d->name && d->qualified_name && strcmp(d->name, name) == 0) {
*def_qn = d->qualified_name;
}
}
for (int i = 0; i < r->calls.count; i++) {
const CBMCall *c = &r->calls.items[i];
if (c->callee_name && strcmp(c->callee_name, callee) == 0 && c->enclosing_func_qn &&
strstr(c->enclosing_func_qn, name)) {
*scope_qn = c->enclosing_func_qn;
}
}
}
/* F-R4: a method of a generic impl (`impl<T> Gen<T>`, `impl<'a> Life<'a>`) got a
* call scope that kept the generic arguments ("...Gen<T>.in_generic") while the
* def side strips them ("...Gen.in_generic"), so no node carried the scope and
* every call in the body fell back to the File node (base census: 84,331 Rust
* CALLS sourced from a File although inside a function, 96% of all File-sourced
* Rust CALLS). The def QN and the call scope must agree. */
TEST(extract_rust_generic_impl_call_scope_matches_def) {
CBMFileResult *r = extract("pub struct Plain;\n"
"impl Plain {\n"
" pub fn in_plain(&self) -> u32 { helper() }\n"
"}\n"
"pub struct Gen<T> { v: T }\n"
"impl<T> Gen<T> {\n"
" pub fn in_generic(&self) -> u32 { helper() }\n"
"}\n"
"pub struct Life<'a> { s: &'a str }\n"
"impl<'a> Life<'a> {\n"
" pub fn in_lifetime(&self) -> u32 { helper() }\n"
"}\n"
"pub fn helper() -> u32 { 1 }\n",
CBM_LANG_RUST, "t", "src/lib.rs");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
static const char *const methods[] = {"in_plain", "in_generic", "in_lifetime", NULL};
for (int m = 0; methods[m]; m++) {
const char *def_qn = NULL;
const char *scope_qn = NULL;
rust_def_and_scope(r, methods[m], "helper", &def_qn, &scope_qn);
ASSERT_NOT_NULL(def_qn);
ASSERT_NOT_NULL(scope_qn);
ASSERT_STR_EQ(scope_qn, def_qn);
}
cbm_free_result(r);
PASS();
}
/* Languages OUTSIDE the is_method flag set (only Perl and TS/JS/TSX set it) must
* be unaffected: a Go method call never sets is_method. */
TEST(extract_flag_exempt_method_call_not_flagged_is_method) {
@@ -12512,6 +12569,7 @@ SUITE(extraction) {
RUN_TEST(extract_perl_config_string_not_a_callee);
RUN_TEST(extract_perl_builtin_call_is_function_not_method);
RUN_TEST(extract_perl_method_call_flags_is_method);
RUN_TEST(extract_rust_generic_impl_call_scope_matches_def);
RUN_TEST(extract_flag_exempt_method_call_not_flagged_is_method);
RUN_TEST(extract_python_member_call_flags_is_method);
RUN_TEST(extract_python_bare_call_flags_locally_bound_callee);
+95
View File
@@ -385,6 +385,98 @@ TEST(infrascan_http_calls_join_matching_handler_route) {
PASS();
}
/* FD16: a route's DATA_FLOWS were built from the first 64 callers and the first 32 infra handlers
* in edge-insertion order (parallel worker interleaving), so the flow set changed between two
* indexes of the same tree and every caller past the 64th lost its flow. */
enum { FD16_CALLERS = 70, FD16_INFRA_HANDLERS = 40 };
static int64_t fd16_route_with_callers(cbm_gbuf_t *gb, int64_t *callers, bool reverse) {
int64_t route = cbm_gbuf_upsert_node(gb, "Route", "/api/orders", "__route__GET__/api/orders",
"", 0, 0, "{\"method\":\"GET\"}");
int64_t handler = cbm_gbuf_upsert_node(gb, "Function", "get_orders", "test.get_orders",
"server.py", 1, 3, "{}");
cbm_gbuf_insert_edge(gb, handler, route, "HANDLES", "{\"handler\":\"test.get_orders\"}");
for (int i = 0; i < FD16_CALLERS; i++) {
char name[32];
char qn[48];
snprintf(name, sizeof(name), "client_%02d", i);
snprintf(qn, sizeof(qn), "test.client_%02d", i);
callers[i] =
cbm_gbuf_upsert_node(gb, "Function", name, qn, "client.py", i + 1, i + 1, "{}");
}
for (int k = 0; k < FD16_CALLERS; k++) {
int i = reverse ? FD16_CALLERS - 1 - k : k;
cbm_gbuf_insert_edge(gb, callers[i], route, "HTTP_CALLS",
"{\"callee\":\"requests.get\",\"url_path\":\"/api/orders\","
"\"method\":\"GET\"}");
}
return handler;
}
TEST(infrascan_data_flows_link_every_caller_of_a_route) {
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_fd16_callers");
ASSERT_NOT_NULL(gb);
int64_t callers[FD16_CALLERS];
int64_t handler = fd16_route_with_callers(gb, callers, false);
cbm_pipeline_create_route_nodes(gb);
for (int i = 0; i < FD16_CALLERS; i++) {
ASSERT_TRUE(has_data_flow(gb, callers[i], handler));
}
cbm_gbuf_free(gb);
PASS();
}
TEST(infrascan_data_flows_do_not_depend_on_caller_insertion_order) {
cbm_gbuf_t *fwd = cbm_gbuf_new("test", "/tmp/cbm_infrascan_fd16_fwd");
cbm_gbuf_t *rev = cbm_gbuf_new("test", "/tmp/cbm_infrascan_fd16_rev");
ASSERT_NOT_NULL(fwd);
ASSERT_NOT_NULL(rev);
int64_t fwd_callers[FD16_CALLERS];
int64_t rev_callers[FD16_CALLERS];
int64_t fwd_handler = fd16_route_with_callers(fwd, fwd_callers, false);
int64_t rev_handler = fd16_route_with_callers(rev, rev_callers, true);
cbm_pipeline_create_route_nodes(fwd);
cbm_pipeline_create_route_nodes(rev);
for (int i = 0; i < FD16_CALLERS; i++) {
ASSERT_EQ(has_data_flow(fwd, fwd_callers[i], fwd_handler),
has_data_flow(rev, rev_callers[i], rev_handler));
}
cbm_gbuf_free(fwd);
cbm_gbuf_free(rev);
PASS();
}
TEST(infrascan_data_flows_reach_every_infra_handler) {
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_fd16_infra");
ASSERT_NOT_NULL(gb);
int64_t route = cbm_gbuf_upsert_node(gb, "Route", "/api/orders", "__route__GET__/api/orders",
"", 0, 0, "{\"method\":\"GET\"}");
int64_t endpoint = cbm_gbuf_upsert_node(gb, "Route", "/orders", "__route__GET__/orders", "", 0,
0, "{\"method\":\"GET\"}");
int64_t client =
cbm_gbuf_upsert_node(gb, "Function", "client", "test.client", "client.py", 1, 3, "{}");
cbm_gbuf_insert_edge(gb, route, endpoint, "INFRA_MAPS", "{}");
cbm_gbuf_insert_edge(gb, client, route, "HTTP_CALLS",
"{\"callee\":\"requests.get\",\"url_path\":\"/api/orders\","
"\"method\":\"GET\"}");
int64_t handlers[FD16_INFRA_HANDLERS];
for (int i = 0; i < FD16_INFRA_HANDLERS; i++) {
char name[32];
char qn[48];
snprintf(name, sizeof(name), "orders_%02d", i);
snprintf(qn, sizeof(qn), "test.orders_%02d", i);
handlers[i] =
cbm_gbuf_upsert_node(gb, "Function", name, qn, "server.py", i + 1, i + 1, "{}");
cbm_gbuf_insert_edge(gb, handlers[i], endpoint, "HANDLES", "{}");
}
cbm_pipeline_create_route_nodes(gb);
for (int i = 0; i < FD16_INFRA_HANDLERS; i++) {
ASSERT_TRUE(has_data_flow(gb, client, handlers[i]));
}
cbm_gbuf_free(gb);
PASS();
}
SUITE(infrascan) {
RUN_TEST(infrascan_http_route_literal_guard_rejects_filesystem_paths);
RUN_TEST(infrascan_http_route_literal_guard_rejects_document_extensions);
@@ -393,4 +485,7 @@ SUITE(infrascan) {
RUN_TEST(infrascan_service_pattern_match_keeps_real_library_qns);
RUN_TEST(infrascan_route_nodes_skip_bad_http_url_paths);
RUN_TEST(infrascan_http_calls_join_matching_handler_route);
RUN_TEST(infrascan_data_flows_link_every_caller_of_a_route);
RUN_TEST(infrascan_data_flows_do_not_depend_on_caller_insertion_order);
RUN_TEST(infrascan_data_flows_reach_every_infra_handler);
}
+90
View File
@@ -9042,6 +9042,94 @@ TEST(pipeline_rust_assoc_call_never_binds_free_namesake_parallel) {
return rust_assoc_call_free_namesake_case(52);
}
/* F-R4 (#763, Rust): calls inside a method of a generic impl (`impl<T> Gen<T>`,
* `impl<'a> Life<'a>`) were sourced from the File node: the call scope kept the
* generic arguments the def side strips. Each method must be the source of its
* own call. pad_files > 0 forces the parallel resolver. */
static int rust_generic_impl_attribution_case(int pad_files) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_rust_gimpl_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
FAIL("tmpdir");
}
write_temp_file(tmp, "Cargo.toml", "[package]\nname = \"fx\"\nversion = \"0.1.0\"\n");
write_temp_file(
tmp, "src/lib.rs",
"pub struct Gen<T> { v: T }\n"
"impl<T> Gen<T> {\n"
" pub fn in_generic(&self) -> u32 { helper_a() + self.other_g() + Self::assoc_g() }\n"
" pub fn other_g(&self) -> u32 { 3 }\n"
" pub fn assoc_g() -> u32 { 4 }\n"
"}\n"
"pub struct Life<'a> { s: &'a str }\n"
"impl<'a> Life<'a> {\n"
" pub fn in_lifetime(&self) -> u32 { helper_b() + self.other_l() }\n"
" pub fn other_l(&self) -> u32 { 5 }\n"
"}\n"
"pub fn helper_a() -> u32 { 1 }\n"
"pub fn helper_b() -> u32 { 2 }\n");
for (int i = 0; i < pad_files; i++) {
char name[64];
char body[128];
snprintf(name, sizeof(name), "src/filler%d.rs", i);
snprintf(body, sizeof(body), "pub fn filler%d() -> u32 {\n %d\n}\n", i, i);
write_temp_file(tmp, name, body);
}
char *saved = NULL;
if (pad_files > 0) {
char *old_workers = getenv("CBM_WORKERS");
saved = old_workers ? strdup(old_workers) : NULL;
cbm_setenv("CBM_WORKERS", "4", 1);
}
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/rust_gimpl.db", tmp);
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
ASSERT_EQ(cbm_pipeline_run(p), 0);
const char *project = cbm_pipeline_project_name(p);
cbm_store_t *s = cbm_store_open_path(db_path);
ASSERT_NOT_NULL(s);
ASSERT_TRUE(cross_file_call_exists(s, project, "in_generic", "helper_a"));
ASSERT_TRUE(cross_file_call_exists(s, project, "in_lifetime", "helper_b"));
/* ... and not from the file. */
ASSERT_FALSE(cross_file_call_exists(s, project, "lib.rs", "helper_a"));
ASSERT_FALSE(cross_file_call_exists(s, project, "lib.rs", "helper_b"));
/* The LSP side names the impl's self type without its arguments too: its
* rows join these callers (lsp_*, not a registry fallback), and `self.` /
* `Self::` dispatch inside a generic impl resolves (Self::assoc_g had no
* edge at all). */
ASSERT_TRUE(
cross_file_call_has_strategy(s, project, "in_generic", "helper_a", "\"strategy\":\"lsp_"));
ASSERT_TRUE(
cross_file_call_has_strategy(s, project, "in_generic", "other_g", "\"strategy\":\"lsp_"));
ASSERT_TRUE(cross_file_call_exists(s, project, "in_generic", "assoc_g"));
ASSERT_TRUE(
cross_file_call_has_strategy(s, project, "in_lifetime", "other_l", "\"strategy\":\"lsp_"));
cbm_store_close(s);
cbm_pipeline_free(p);
if (pad_files > 0) {
if (saved) {
cbm_setenv("CBM_WORKERS", saved, 1);
free(saved);
} else {
cbm_unsetenv("CBM_WORKERS");
}
}
th_rmtree(tmp);
PASS();
}
TEST(pipeline_rust_generic_impl_method_sources_its_calls) {
return rust_generic_impl_attribution_case(0);
}
TEST(pipeline_rust_generic_impl_method_sources_its_calls_parallel) {
return rust_generic_impl_attribution_case(52);
}
TEST(pipeline_go_rw_usage_never_cross_into_c_parallel) {
/* Parallel twin of the test above: USAGE and WRITES/READS each have a
* second, independent resolver in pass_parallel.c (resolve_file_usages /
@@ -24748,6 +24836,8 @@ SUITE(pipeline) {
RUN_TEST(pipeline_rust_call_never_binds_another_language_parallel);
RUN_TEST(pipeline_rust_assoc_call_never_binds_free_namesake);
RUN_TEST(pipeline_rust_assoc_call_never_binds_free_namesake_parallel);
RUN_TEST(pipeline_rust_generic_impl_method_sources_its_calls);
RUN_TEST(pipeline_rust_generic_impl_method_sources_its_calls_parallel);
RUN_TEST(pipeline_rust_std_receiver_never_binds_project_method);
RUN_TEST(pipeline_rust_std_receiver_never_binds_project_method_parallel);
RUN_TEST(pipeline_go_bare_ref_never_binds_field);