Skip to content

Commit d4273ae

Browse files
committed
fix(pipeline): bound unresolved call evidence
Signed-off-by: Pcristin <[email protected]>
1 parent c35d779 commit d4273ae

18 files changed

Lines changed: 1629 additions & 474 deletions

‎internal/cbm/cbm.h‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,6 @@ typedef struct {
332332
// (self.compiler.apply_converters()). An object the
333333
// class owns, not a parameter: read by the weak-member
334334
// guard's unique-name exemption. Default false.
335-
/* Ephemeral coverage evidence, set only while resolving this file. Capture
336-
* it before releasing a spilled result; never retain registry pointers. */
337-
bool coverage_calls_emitted;
338-
const char *coverage_candidate_qn;
339335
} CBMCall;
340336

341337
// What an import statement names, when the syntax says so. Only PHP's

‎src/foundation/mem_core.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ static mem_class_stats_t g_classes[CBM_MEM_CLASS_COUNT];
6767

6868
static const char *const g_class_names[CBM_MEM_CLASS_COUNT] = {
6969
"other", "gbuf_node", "gbuf_edge", "gbuf_string", "gbuf_index", "extract", "arena",
70-
"ts_tree", "semantic", "dump", "store", "hash_table", "dyn_array",
70+
"ts_tree", "semantic", "dump", "store", "hash_table", "dyn_array", "resolve",
7171
};
7272

7373
const char *cbm_mem_class_name(cbm_mem_class_t cls) {

‎src/foundation/mem_core.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ typedef enum {
6666
CBM_MEM_CLASS_STORE, /* SQLite (bound mem methods) + store batches and row buffers */
6767
CBM_MEM_CLASS_HASH_TABLE, /* CBMHashTable buckets/entries not claimed by an owner class */
6868
CBM_MEM_CLASS_DYN_ARRAY, /* CBM_DYN_ARRAY item storage (every cbm_da_* user) */
69+
CBM_MEM_CLASS_RESOLVE, /* call resolution evidence and coverage capture */
6970
CBM_MEM_CLASS_COUNT
7071
} cbm_mem_class_t;
7172

‎src/mcp/mcp.c‎

Lines changed: 102 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6594,6 +6594,43 @@ static void coverage_add_row_json(yyjson_mut_doc *doc, yyjson_mut_val *array,
65946594
yyjson_mut_arr_add_val(array, item);
65956595
}
65966596

6597+
enum { COVERAGE_EXCLUDED_PREFIX_LENGTH = 11 };
6598+
6599+
enum {
6600+
COVERAGE_STATUS_PARSE,
6601+
COVERAGE_STATUS_EXCLUDED,
6602+
COVERAGE_STATUS_SKIPPED,
6603+
COVERAGE_STATUS_UNRESOLVED,
6604+
COVERAGE_STATUS_PASSES
6605+
};
6606+
6607+
static const char *coverage_row_status(const char *kind, int pass) {
6608+
kind = kind ? kind : "";
6609+
if (pass == COVERAGE_STATUS_PARSE) {
6610+
if (strcmp(kind, "parse_unusable") == 0) {
6611+
return "unusable";
6612+
}
6613+
if (strcmp(kind, "parse_partial") == 0) {
6614+
return "partial";
6615+
}
6616+
}
6617+
if (pass == COVERAGE_STATUS_EXCLUDED &&
6618+
strncmp(kind, "not_indexed", COVERAGE_EXCLUDED_PREFIX_LENGTH) == 0) {
6619+
return "excluded";
6620+
}
6621+
if (pass == COVERAGE_STATUS_SKIPPED && kind[0] && strcmp(kind, "unresolved_calls") != 0) {
6622+
return "skipped";
6623+
}
6624+
if (pass == COVERAGE_STATUS_UNRESOLVED && strcmp(kind, "unresolved_calls") == 0) {
6625+
return "unresolved_calls";
6626+
}
6627+
return NULL;
6628+
}
6629+
6630+
static bool coverage_unresolved_complete(bool have_meta, const cbm_coverage_meta_t *meta) {
6631+
return have_meta && meta->unresolved_calls_complete;
6632+
}
6633+
65976634
static const char *coverage_status(const cbm_coverage_row_t *rows, int count,
65986635
const char *requested_path, const char *recording_status,
65996636
bool generation_matches, bool lookup_ok,
@@ -6608,29 +6645,14 @@ static const char *coverage_status(const cbm_coverage_row_t *rows, int count,
66086645
break;
66096646
}
66106647
}
6611-
for (int pass = 0; pass < 3; pass++) {
6648+
for (int pass = 0; pass < COVERAGE_STATUS_PASSES; pass++) {
66126649
for (int i = 0; i < count; i++) {
66136650
if (exact && (!rows[i].rel_path || strcmp(rows[i].rel_path, requested_path) != 0)) {
66146651
continue;
66156652
}
6616-
const char *kind = rows[i].kind ? rows[i].kind : "";
6617-
/* "parse_unusable" must be named here. Without its own case it
6618-
* falls through to the catch-all below and reports "skipped",
6619-
* which is wrong in the way that matters: the file WAS indexed. */
6620-
if (pass == 0 && strcmp(kind, "parse_unusable") == 0) {
6621-
return "unusable";
6622-
}
6623-
if (pass == 0 && strcmp(kind, "parse_partial") == 0) {
6624-
return "partial";
6625-
}
6626-
if (pass == 0 && strcmp(kind, "unresolved_calls") == 0) {
6627-
return "unresolved_calls";
6628-
}
6629-
if (pass == 1 && strncmp(kind, "not_indexed", 11) == 0) {
6630-
return "excluded";
6631-
}
6632-
if (pass == 2 && kind[0]) {
6633-
return "skipped";
6653+
const char *status = coverage_row_status(rows[i].kind, pass);
6654+
if (status) {
6655+
return status;
66346656
}
66356657
}
66366658
}
@@ -6736,6 +6758,8 @@ static char *handle_check_index_coverage(cbm_mcp_server_t *srv, const char *args
67366758
have_meta ? meta.ignored_files_total : 0);
67376759
yyjson_mut_obj_add_bool(doc, meta_obj, "hash_records_complete",
67386760
have_meta && meta.hash_records_complete);
6761+
yyjson_mut_obj_add_bool(doc, meta_obj, "unresolved_calls_complete",
6762+
coverage_unresolved_complete(have_meta, &meta));
67396763
yyjson_mut_obj_add_int(doc, meta_obj, "coverage_version",
67406764
have_meta ? meta.coverage_version : 0);
67416765
yyjson_mut_obj_add_bool(doc, meta_obj, "generation_matches", generation_matches);
@@ -9265,7 +9289,6 @@ static void trace_coverage_match_site(trace_coverage_match_t *match, const char
92659289
yyjson_val *site) {
92669290
const char *caller = yyjson_get_str(yyjson_obj_get(site, "caller"));
92679291
const char *leaf = yyjson_get_str(yyjson_obj_get(site, "leaf"));
9268-
const char *candidate = yyjson_get_str(yyjson_obj_get(site, "candidate"));
92699292
const cbm_node_t *caller_node =
92709293
match->callers && caller ? cbm_ht_get(match->callers, caller) : NULL;
92719294
int line = yyjson_get_int(yyjson_obj_get(site, "line"));
@@ -9277,25 +9300,22 @@ static void trace_coverage_match_site(trace_coverage_match_t *match, const char
92779300
trace_coverage_may_target_project(match, leaf)) {
92789301
match->unresolved_out = true;
92799302
}
9280-
/* A short-name collision is not evidence that the resolver considered
9281-
* this traced target. */
9282-
if (match->targets && candidate && cbm_ht_has(match->targets, candidate)) {
9283-
match->unresolved_in = true;
9284-
}
92859303
}
92869304

9287-
static void trace_coverage_match_row(trace_coverage_match_t *match, const cbm_coverage_row_t *row,
9288-
const trace_coverage_scope_t *scope) {
9305+
static void trace_coverage_match_row(trace_coverage_match_t *match, const cbm_coverage_row_t *row) {
92899306
yyjson_doc *detail = yyjson_read(row->detail, strlen(row->detail), 0);
92909307
yyjson_val *sites = detail ? yyjson_doc_get_root(detail) : NULL;
92919308
if (!yyjson_is_arr(sites)) {
9292-
match->unresolved_out = scope->do_outbound;
9293-
match->unresolved_in = scope->do_inbound;
9309+
match->unresolved_out = true;
92949310
} else {
92959311
size_t idx;
92969312
size_t max;
92979313
yyjson_val *site;
92989314
yyjson_arr_foreach(sites, idx, max, site) {
9315+
if (!yyjson_is_obj(site) || yyjson_get_bool(yyjson_obj_get(site, "truncated"))) {
9316+
match->unresolved_out = true;
9317+
break;
9318+
}
92999319
trace_coverage_match_site(match, row->rel_path, site);
93009320
}
93019321
}
@@ -9304,32 +9324,69 @@ static void trace_coverage_match_row(trace_coverage_match_t *match, const cbm_co
93049324
}
93059325
}
93069326

9307-
static bool trace_coverage_scan_rows(cbm_store_t *store, const char *project,
9308-
trace_coverage_match_t *match,
9309-
const trace_coverage_scope_t *scope) {
9327+
typedef struct {
9328+
trace_coverage_match_t *match;
9329+
CBMHashTable *files;
9330+
bool ok;
9331+
} trace_coverage_query_t;
9332+
9333+
static void trace_coverage_query_file(const char *key, void *value, void *userdata) {
9334+
(void)key;
9335+
const cbm_node_t *caller = value;
9336+
trace_coverage_query_t *query = userdata;
9337+
const char *path = caller->file_path;
9338+
if (!query->ok || query->match->unresolved_out || !path || cbm_ht_has(query->files, path)) {
9339+
return;
9340+
}
9341+
cbm_ht_set(query->files, path, value);
9342+
if (!cbm_ht_has(query->files, path)) {
9343+
query->ok = false;
9344+
return;
9345+
}
93109346
cbm_coverage_row_t *rows = NULL;
9311-
int row_count = 0;
9312-
if (cbm_store_coverage_get_unresolved_calls(store, project, &rows, &row_count) !=
9313-
CBM_STORE_OK) {
9314-
return false;
9347+
int count = 0;
9348+
query->ok = cbm_store_coverage_get_unresolved_path(query->match->store, query->match->project,
9349+
path, &rows, &count) == CBM_STORE_OK;
9350+
for (int i = 0; query->ok && i < count; i++) {
9351+
trace_coverage_match_row(query->match, &rows[i]);
93159352
}
9316-
for (int i = 0; i < row_count; i++) {
9317-
if ((!scope->do_outbound || match->unresolved_out) &&
9318-
(!scope->do_inbound || match->unresolved_in)) {
9319-
break;
9353+
cbm_store_free_coverage(rows, count);
9354+
}
9355+
9356+
static void trace_coverage_query_candidate(const char *key, void *value, void *userdata) {
9357+
(void)value;
9358+
trace_coverage_query_t *query = userdata;
9359+
if (!query->ok || query->match->unresolved_in) {
9360+
return;
9361+
}
9362+
query->ok =
9363+
cbm_store_coverage_has_unresolved_candidate(query->match->store, query->match->project, key,
9364+
&query->match->unresolved_in) == CBM_STORE_OK;
9365+
}
9366+
9367+
static bool trace_coverage_query_evidence(trace_coverage_match_t *match,
9368+
const trace_coverage_scope_t *scope) {
9369+
trace_coverage_query_t query = {.match = match, .ok = true};
9370+
if (scope->do_outbound) {
9371+
query.files = cbm_ht_create(0);
9372+
if (!query.files) {
9373+
return false;
93209374
}
9321-
trace_coverage_match_row(match, &rows[i], scope);
9375+
cbm_ht_foreach(match->callers, trace_coverage_query_file, &query);
9376+
cbm_ht_free(query.files);
93229377
}
9323-
cbm_store_free_coverage(rows, row_count);
9324-
return true;
9378+
if (scope->do_inbound) {
9379+
cbm_ht_foreach(match->targets, trace_coverage_query_candidate, &query);
9380+
}
9381+
return query.ok;
93259382
}
93269383

93279384
static bool trace_coverage_meta_available(cbm_store_t *store, const char *project) {
93289385
cbm_coverage_meta_t meta = {0};
93299386
int rc = cbm_store_coverage_meta_get(store, project, &meta);
93309387
bool available = rc == CBM_STORE_OK &&
93319388
meta.coverage_version >= CBM_UNRESOLVED_CALL_COVERAGE_VERSION &&
9332-
meta.recording_status && strcmp(meta.recording_status, "unavailable") != 0;
9389+
meta.unresolved_calls_complete;
93339390
cbm_store_coverage_meta_clear(&meta);
93349391
return available;
93359392
}
@@ -9347,7 +9404,7 @@ static void trace_call_coverage(cbm_store_t *store, const char *project,
93479404
bool ok = trace_coverage_meta_available(store, project) &&
93489405
(!scope->do_outbound || match.callers) && (!scope->do_inbound || match.targets) &&
93499406
trace_coverage_build_sets(&match, scope) &&
9350-
trace_coverage_scan_rows(store, project, &match, scope);
9407+
trace_coverage_query_evidence(&match, scope);
93519408
*unresolved_out = ok ? match.unresolved_out : scope->do_outbound;
93529409
*unresolved_in = ok ? match.unresolved_in : scope->do_inbound;
93539410
cbm_ht_free(match.callers);

0 commit comments

Comments
 (0)