From 3ead775d0bfbdd538794e92bbeee452b50d49007 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 19 Oct 2026 02:11:45 +0200 Subject: [PATCH] fix(python): skip annotation-only imports without a new raw free The memory-core linter counts raw allocator calls per file and fails when a file grows. The TYPE_CHECKING change freed the copied local name of an annotation-only import with a new raw free in two import-map builders. build_import_map now asks the predicate on the name's span in the edge properties before copying it (cbm_imports_local_span_binds_runtime_name, which cbm_imports_local_binds_runtime_name now wraps), and the cross-LSP builder routes an annotation-only name through its existing no-value path. Same behaviour, no new raw allocator site. Signed-off-by: Martin Vogel --- internal/cbm/cbm.c | 13 ++++++++++--- internal/cbm/cbm.h | 4 ++++ src/pipeline/pass_calls.c | 28 ++++++++++++++++++---------- src/pipeline/pass_lsp_cross.c | 10 ++++------ 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index a69bd1b8a..23b871dbc 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -402,14 +402,16 @@ void cbm_imports_push(CBMImportArray *arr, CBMArena *a, CBMImport imp) { arr->items[arr->count++] = imp; } -bool cbm_imports_local_binds_runtime_name(const CBMImportArray *imports, const char *local_name) { - if (!imports || !local_name) { +bool cbm_imports_local_span_binds_runtime_name(const CBMImportArray *imports, const char *name, + size_t len) { + if (!imports || !name) { return true; } bool seen = false; for (int i = 0; i < imports->count; i++) { const CBMImport *imp = &imports->items[i]; - if (!imp->local_name || strcmp(imp->local_name, local_name) != 0) { + if (!imp->local_name || strncmp(imp->local_name, name, len) != 0 || + imp->local_name[len] != '\0') { continue; } if (cbm_import_binds_runtime_name(imp)) { @@ -420,6 +422,11 @@ bool cbm_imports_local_binds_runtime_name(const CBMImportArray *imports, const c return !seen; } +bool cbm_imports_local_binds_runtime_name(const CBMImportArray *imports, const char *local_name) { + return !local_name || + cbm_imports_local_span_binds_runtime_name(imports, local_name, strlen(local_name)); +} + void cbm_usages_push(CBMUsageArray *arr, CBMArena *a, CBMUsage usage) { GROW_ARRAY(arr, a); arr->items[arr->count++] = usage; diff --git a/internal/cbm/cbm.h b/internal/cbm/cbm.h index bb9799a15..504bda4fc 100644 --- a/internal/cbm/cbm.h +++ b/internal/cbm/cbm.h @@ -1321,6 +1321,10 @@ void cbm_imports_push(CBMImportArray *arr, CBMArena *a, CBMImport imp); * annotation-only (cbm_import_binds_runtime_name), so also true when no import * names it: the rule for the builders that key IMPORTS edges by local name. */ bool cbm_imports_local_binds_runtime_name(const CBMImportArray *imports, const char *local_name); +/* The same for the `len` bytes at `name` (not NUL-terminated): lets a caller + * decide before it copies the name. */ +bool cbm_imports_local_span_binds_runtime_name(const CBMImportArray *imports, const char *name, + size_t len); void cbm_usages_push(CBMUsageArray *arr, CBMArena *a, CBMUsage usage); void cbm_throws_push(CBMThrowArray *arr, CBMArena *a, CBMThrow thr); void cbm_rw_push(CBMRWArray *arr, CBMArena *a, CBMReadWrite rw); diff --git a/src/pipeline/pass_calls.c b/src/pipeline/pass_calls.c index 9d60ee5c9..fbbdabbe7 100644 --- a/src/pipeline/pass_calls.c +++ b/src/pipeline/pass_calls.c @@ -95,21 +95,24 @@ static const char *itoa_log(int val) { /* Build per-file import map from cached extraction result or graph buffer edges. * Returns parallel arrays of (local_name, module_qn) pairs. Caller frees. */ -/* Parse "local_name":"value" from JSON properties string. Returns strdup'd key or NULL. */ -static char *extract_local_name_from_json(const char *props_json) { +/* The "local_name":"value" of a JSON properties string, as a span of it; + * false when absent or empty. */ +static bool local_name_span_from_json(const char *props_json, const char **out, size_t *len) { if (!props_json) { - return NULL; + return false; } const char *start = strstr(props_json, "\"local_name\":\""); if (!start) { - return NULL; + return false; } start += strlen("\"local_name\":\""); const char *end = strchr(start, '"'); if (!end || end <= start) { - return NULL; + return false; } - return cbm_strndup(start, end - start); + *out = start; + *len = (size_t)(end - start); + return true; } /* The graph node whose QN is cbm_pipeline_fqn_compute(project, rel_path, name), @@ -193,11 +196,16 @@ static int build_import_map(cbm_pipeline_ctx_t *ctx, const char *rel_path, if (!target) { continue; } - char *key = extract_local_name_from_json(e->properties_json); - if (key && !cbm_imports_local_binds_runtime_name(result ? &result->imports : NULL, key)) { - free(key); /* annotation-only import (#1277): no CALLS binding */ - key = NULL; + const char *name = NULL; + size_t name_len = 0; + /* An annotation-only import (#1277) binds no CALLS name: skipped before + * the key is copied. */ + if (!local_name_span_from_json(e->properties_json, &name, &name_len) || + !cbm_imports_local_span_binds_runtime_name(result ? &result->imports : NULL, name, + name_len)) { + continue; } + char *key = cbm_strndup(name, name_len); if (key) { keys[count] = key; vals[count] = target->qualified_name; diff --git a/src/pipeline/pass_lsp_cross.c b/src/pipeline/pass_lsp_cross.c index b1fa5f264..4f8ab8280 100644 --- a/src/pipeline/pass_lsp_cross.c +++ b/src/pipeline/pass_lsp_cross.c @@ -1147,12 +1147,10 @@ int cbm_pxc_build_import_map_ex(const cbm_gbuf_t *gbuf, const char *project_name continue; memcpy(local, start, n); local[n] = '\0'; - if (!include_type_only && - !cbm_imports_local_binds_runtime_name(result ? &result->imports : NULL, local)) { - free(local); /* annotation-only import (#1277): no CALLS binding */ - continue; - } - char *value = pxc_import_value_qn(lang, result, local, target); + /* An annotation-only import (#1277) binds no CALLS name: it gets no value. */ + bool binds = include_type_only || + cbm_imports_local_binds_runtime_name(result ? &result->imports : NULL, local); + char *value = binds ? pxc_import_value_qn(lang, result, local, target) : NULL; if (!value) { free(local); continue;