Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 190 additions & 18 deletions internal/cbm/cbm.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "lsp/java_lsp.h"
#include "lsp/kotlin_lsp.h"
#include "lsp/rust_lsp.h"
#include "lsp/lsp_work.h"
#include "preprocessor.h"
#include "sql_values.h" // #1735: literal INSERT rows kept out of the SQL parse
#include "foundation/compat.h"
Expand Down Expand Up @@ -243,6 +244,11 @@ void cbm_typeassign_push(CBMTypeAssignArray *arr, CBMArena *a, CBMTypeAssign ta)
arr->items[arr->count++] = ta;
}

void cbm_fieldtype_push(CBMFieldTypeArray *arr, CBMArena *a, CBMFieldType ft) {
GROW_ARRAY(arr, a);
arr->items[arr->count++] = ft;
}

void cbm_stringref_push(CBMStringRefArray *arr, CBMArena *a, CBMStringRef sr) {
GROW_ARRAY(arr, a);
arr->items[arr->count++] = sr;
Expand Down Expand Up @@ -2141,6 +2147,183 @@ static bool cbm_sql_values_exclusion_on(const char *rel_path) {
return true;
}

/* ── Innermost enclosing Function/Method per call (#1527) ──────────────
* The call-context metrics attribute each call to the Function/Method def with
* the SMALLEST line span containing the call's line, ties to the lowest def
* index. Scanning every def for every call made that O(calls x defs) per file
* -- quadratic in file size, the largest single cost on a module with
* thousands of methods. The sweep below gives the identical answer in
* O((calls + defs) log defs): calls in line order, candidates entering a
* min-heap keyed (span, index) once their start line is reached and leaving it
* lazily once their end line has passed -- lines only grow, so a def that has
* ended never contains a later call. The heap top is then the smallest
* (span, index) among the defs that contain the line. */
static bool enclosing_is_callable(const CBMDefinition *d) {
return d->name && d->label &&
(strcmp(d->label, "Function") == 0 || strcmp(d->label, "Method") == 0);
}

/* The reference answer for one line (and the path when the sweep's arrays
* cannot be allocated). */
static int cbm_enclosing_callable_scan(const CBMFileResult *result, int line) {
int best = -1;
int best_span = -1;
for (int di = 0; di < result->defs.count; di++) {
const CBMDefinition *d = &result->defs.items[di];
CBM_LSP_WORK(1);
if (!enclosing_is_callable(d)) {
continue;
}
if ((int)d->start_line <= line && line <= (int)d->end_line) {
int span = (int)d->end_line - (int)d->start_line;
if (best < 0 || span < best_span) {
best_span = span;
best = di;
}
}
}
return best;
}

static CBM_TLS const CBMFileResult *tl_enclosing_result;

static int enclosing_def_by_start(const void *a, const void *b) {
int ia = *(const int *)a;
int ib = *(const int *)b;
uint32_t la = tl_enclosing_result->defs.items[ia].start_line;
uint32_t lb = tl_enclosing_result->defs.items[ib].start_line;
return la < lb ? -1 : la > lb ? 1 : ia - ib;
}

static int enclosing_call_by_line(const void *a, const void *b) {
int ia = *(const int *)a;
int ib = *(const int *)b;
int la = tl_enclosing_result->calls.items[ia].start_line;
int lb = tl_enclosing_result->calls.items[ib].start_line;
return la < lb ? -1 : la > lb ? 1 : ia - ib;
}

/* Heap order: smaller span first, then smaller def index. */
static bool enclosing_heap_less(const CBMFileResult *r, int a, int b) {
const CBMDefinition *da = &r->defs.items[a];
const CBMDefinition *db = &r->defs.items[b];
int sa = (int)da->end_line - (int)da->start_line;
int sb = (int)db->end_line - (int)db->start_line;
return sa != sb ? sa < sb : a < b;
}

static void enclosing_heap_push(const CBMFileResult *r, int *heap, int *n, int di) {
int i = (*n)++;
heap[i] = di;
while (i > 0) {
int parent = (i - 1) / 2;
CBM_LSP_WORK(1);
if (!enclosing_heap_less(r, heap[i], heap[parent])) {
break;
}
int t = heap[i];
heap[i] = heap[parent];
heap[parent] = t;
i = parent;
}
}

static void enclosing_heap_pop(const CBMFileResult *r, int *heap, int *n) {
heap[0] = heap[--(*n)];
int i = 0;
for (;;) {
int l = (2 * i) + 1;
int m = i;
CBM_LSP_WORK(1);
if (l < *n && enclosing_heap_less(r, heap[l], heap[m])) {
m = l;
}
if (l + 1 < *n && enclosing_heap_less(r, heap[l + 1], heap[m])) {
m = l + 1;
}
if (m == i) {
break;
}
int t = heap[i];
heap[i] = heap[m];
heap[m] = t;
i = m;
}
}

/* enclosing[ci] = innermost Function/Method def index for call ci, or -1.
* Returns NULL when the working arrays cannot be allocated; the caller then
* asks cbm_enclosing_callable_scan per call (same answer, old cost). */
static int *cbm_enclosing_callables(const CBMFileResult *result, int call_count) {
int def_count = result->defs.count;
if (call_count <= 0) {
return NULL;
}
size_t call_bytes = (size_t)call_count * sizeof(int);
size_t def_bytes = (size_t)(def_count > 0 ? def_count : 1) * sizeof(int);
int *enclosing = cbm_alloc(CBM_MEM_CLASS_OTHER, call_bytes);
int *call_order = cbm_alloc(CBM_MEM_CLASS_OTHER, call_bytes);
int *cands = cbm_alloc(CBM_MEM_CLASS_OTHER, def_bytes);
int *heap = cbm_alloc(CBM_MEM_CLASS_OTHER, def_bytes);
if (!enclosing || !call_order || !cands || !heap) {
cbm_free(CBM_MEM_CLASS_OTHER, enclosing);
cbm_free(CBM_MEM_CLASS_OTHER, call_order);
cbm_free(CBM_MEM_CLASS_OTHER, cands);
cbm_free(CBM_MEM_CLASS_OTHER, heap);
return NULL;
}
int ncand = 0;
for (int di = 0; di < def_count; di++) {
if (enclosing_is_callable(&result->defs.items[di])) {
cands[ncand++] = di;
}
}
for (int ci = 0; ci < call_count; ci++) {
call_order[ci] = ci;
enclosing[ci] = -1;
}
tl_enclosing_result = result;
qsort(cands, (size_t)ncand, sizeof(int), enclosing_def_by_start);
qsort(call_order, (size_t)call_count, sizeof(int), enclosing_call_by_line);
tl_enclosing_result = NULL;

int heap_n = 0;
int next = 0;
for (int k = 0; k < call_count; k++) {
int ci = call_order[k];
int line = result->calls.items[ci].start_line;
while (next < ncand && (int)result->defs.items[cands[next]].start_line <= line) {
enclosing_heap_push(result, heap, &heap_n, cands[next++]);
}
while (heap_n > 0 && (int)result->defs.items[heap[0]].end_line < line) {
enclosing_heap_pop(result, heap, &heap_n);
}
enclosing[ci] = heap_n > 0 ? heap[0] : -1;
}
cbm_free(CBM_MEM_CLASS_OTHER, call_order);
cbm_free(CBM_MEM_CLASS_OTHER, cands);
cbm_free(CBM_MEM_CLASS_OTHER, heap);
return enclosing;
}

#ifdef CBM_ENABLE_TEST_SEAMS
int cbm_test_enclosing_sweep_mismatches(const CBMFileResult *result) {
int *enclosing = cbm_enclosing_callables(result, result->calls.count);
if (!enclosing) {
return result->calls.count > 0 ? -1 : 0;
}
int mismatches = 0;
for (int ci = 0; ci < result->calls.count; ci++) {
int line = result->calls.items[ci].start_line;
if (line > 0 && enclosing[ci] != cbm_enclosing_callable_scan(result, line)) {
mismatches++;
}
}
cbm_free(CBM_MEM_CLASS_OTHER, enclosing);
return mismatches;
}
#endif

static CBMFileResult *extract_file_ex_body(const char *source, int source_len, CBMLanguage language,
const char *project, const char *rel_path,
int64_t timeout_micros, const char **extra_defines,
Expand Down Expand Up @@ -2504,8 +2687,10 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
// Also run LSP on expanded source for additional type-resolved
// calls (language is already C/C++/CUDA — checked in enclosing
// block). Runs in every mode.
cbm_run_c_lsp(a, result, expanded, expanded_len, pp_root,
language != CBM_LANG_C, CBM_SOURCE_ORIGIN_PREPROCESSED);
if (!result->lsp_skipped) {
cbm_run_c_lsp(a, result, expanded, expanded_len, pp_root,
language != CBM_LANG_C, CBM_SOURCE_ORIGIN_PREPROCESSED);
}

/* All C-LSP emitters stamp origin directly so rewrite-time
* comparisons are already safe. Keep this boundary sweep as
Expand Down Expand Up @@ -2828,28 +3013,14 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
d->param_count = pc;
}

int *enclosing = cbm_enclosing_callables(result, orig_calls_count);
for (int ci = 0; ci < orig_calls_count; ci++) {
const CBMCall *c = &result->calls.items[ci];
if (!c->callee_name || c->start_line <= 0) {
continue;
}
// Innermost enclosing Function/Method def by line range (smallest span).
int best = -1;
int best_span = -1;
for (int di = 0; di < def_count; di++) {
const CBMDefinition *d = &result->defs.items[di];
if (!d->name || !d->label ||
(strcmp(d->label, "Function") != 0 && strcmp(d->label, "Method") != 0)) {
continue;
}
if ((int)d->start_line <= c->start_line && c->start_line <= (int)d->end_line) {
int span = (int)d->end_line - (int)d->start_line;
if (best < 0 || span < best_span) {
best_span = span;
best = di;
}
}
}
int best = enclosing ? enclosing[ci] : cbm_enclosing_callable_scan(result, c->start_line);
if (best < 0) {
continue;
}
Expand Down Expand Up @@ -2894,6 +3065,7 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
}
free(has_self);
free(has_guarded);
cbm_free(CBM_MEM_CLASS_OTHER, enclosing);

uint64_t t2 = now_ns();

Expand Down
40 changes: 40 additions & 0 deletions internal/cbm/cbm.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,17 @@ typedef struct {
CBMChannelDirection direction;
} CBMChannel;

/* Python: one annotated instance field of a class -- `x: T` or `x: T = v` in
* the class body, `self.x: T = v` in __init__, or `self.x = p` where `p` is an
* annotated __init__ parameter. Not a graph node: it only carries the field's
* declared type to the cross-file LSP, so `obj.x.m()` on a class imported
* from another file can be typed (#1277). */
typedef struct {
const char *class_qn; // QN of the owning class
const char *field_name; // attribute name
const char *type_text; // raw annotation text, resolved later per file
} CBMFieldType;

// Rust: impl Trait for Struct
typedef struct {
const char *trait_name; // trait name (raw text)
Expand Down Expand Up @@ -439,6 +450,15 @@ typedef struct {
int cap;
} CBMResolvedCallArray;

/* Refinement status: existing parser/no-op exits are separate from these
* explicit evaluator failures. The caller retains prior completed output. */
typedef enum {
CBM_LSP_COMPLETE = 0,
CBM_LSP_MEMO_FAILED,
CBM_LSP_DEPTH_EXCEEDED,
CBM_LSP_SCOPE_FAILED,
} CBMLSPStatus;

// Growable arrays used during extraction.
typedef struct {
CBMDefinition *items;
Expand Down Expand Up @@ -518,6 +538,12 @@ typedef struct {
int cap;
} CBMChannelArray;

typedef struct {
CBMFieldType *items;
int count;
int cap;
} CBMFieldTypeArray;

// Full extraction result for one file.
typedef struct CBMFileResult {
CBMArena arena; // owns local memory; composites may also retain child arenas below
Expand All @@ -536,6 +562,7 @@ typedef struct CBMFileResult {
CBMStringRefArray string_refs; // URL/config string literals from AST
CBMInfraBindingArray infra_bindings; // topic→URL pairs from IaC configs
CBMChannelArray channels; // Socket.IO / EventEmitter pub/sub participation
CBMFieldTypeArray field_types; // Python: annotated instance fields (#1277)

const char *module_qn; // module qualified name
const char *namespace_name; // declared namespace/package (Java/Kotlin/C#/PHP), NULL if none
Expand Down Expand Up @@ -892,9 +919,17 @@ void cbm_rw_push(CBMRWArray *arr, CBMArena *a, CBMReadWrite rw);
void cbm_typerefs_push(CBMTypeRefArray *arr, CBMArena *a, CBMTypeRef tr);
void cbm_envaccess_push(CBMEnvAccessArray *arr, CBMArena *a, CBMEnvAccess ea);
void cbm_typeassign_push(CBMTypeAssignArray *arr, CBMArena *a, CBMTypeAssign ta);
void cbm_fieldtype_push(CBMFieldTypeArray *arr, CBMArena *a, CBMFieldType ft);
void cbm_stringref_push(CBMStringRefArray *arr, CBMArena *a, CBMStringRef sr);
void cbm_infrabinding_push(CBMInfraBindingArray *arr, CBMArena *a, CBMInfraBinding ib);
void cbm_impltrait_push(CBMImplTraitArray *arr, CBMArena *a, CBMImplTrait it);

#ifdef CBM_ENABLE_TEST_SEAMS
/* #1527 seam: how many of result's calls get a different innermost enclosing
* Function/Method from the call-context sweep than from the reference
* per-call scan (0 = identical), or -1 when the sweep could not allocate. */
int cbm_test_enclosing_sweep_mismatches(const CBMFileResult *result);
#endif
void cbm_resolvedcall_push(CBMResolvedCallArray *arr, CBMArena *a, CBMResolvedCall rc);
void cbm_channels_push(CBMChannelArray *arr, CBMArena *a, CBMChannel ch);

Expand Down Expand Up @@ -948,4 +983,9 @@ bool cbm_label_is_relation(const char *label);
// `label` may be NULL (returns false). Defined in helpers.c.
bool cbm_label_is_registry_symbol(const char *label);

/* Python symbol scope only: callers must establish the language explicitly.
* A trailing .__init__ is omitted when a nonempty prefix precedes it; raw
* Module/file identity is unchanged. NULL returns zero. */
size_t cbm_fqn_symbol_scope_len(const char *module_qn);

#endif // CBM_H
Loading
Loading