mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Follow-up to the review of #2091, on top of the contributor's change. The three ts_lsp.c walks (infer_return_type_from_body, rebuild_signatures_from_ast, convert_signature_type_params) now keep their first 256 slots on the C stack, as the old fixed arrays did, and grow on the heap through cbm_walk_stack_reserve / cbm_walk_stack_release (#2375) instead of cutting TSNodeStack storage from ctx->arena: - Scratch lifetime: ctx->arena in the per-file pass is the result arena, kept until the result cache is freed, and the return walk runs once per untyped top-level function. The heap chunk is now released when each walk ends. - No silent drop: when the stack cannot grow, cbm_walk_stack_reserve logs extract.walk_stack_alloc_failed and the walk stops. The return walk then answers "unknown"; the signature walks leave the declarations not yet reached as extract_defs registered them. - Traversal order: children are filled in cursor order (still one O(N) pass) and popped last-child first, exactly as main's walks did, so of several returns the later one is still tried first. tslsp_return_walk_order_prefers_later_return pins it; it fails under a first-child-first walk. The TSNodeStack arena initializer in extract_node_stack.h is no longer needed and is dropped, so that header is unchanged from main. The two stack-cap tests now fail instead of passing when their buffer allocation fails. Co-authored-by: Joshua Richter <jrichter5781@gmail.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
107 lines
4.5 KiB
C
107 lines
4.5 KiB
C
/*
|
|
* extract_node_stack.h — Growable TSNode stack for AST traversal.
|
|
*
|
|
* Replaces fixed-size TSNode stack[] arrays that silently drop AST subtrees
|
|
* when the stack overflows (GitHub issue #199).
|
|
*
|
|
* Traversal stacks are scratch: nothing in a CBMFileResult ever points into
|
|
* one. They are therefore cut from ctx->scratch, which the enclosing
|
|
* cbm_extract_file_ex call owns and destroys on the way out, and never from
|
|
* ctx->arena, which the pipeline holds for every file until the whole result
|
|
* cache is freed (#1997).
|
|
*
|
|
* Growth abandons the old buffer in that scratch arena, which is free because
|
|
* the arena dies with the file. The initial capacities below are unchanged, but
|
|
* they are no longer what a small file costs: cbm_extract_file_ex hands every
|
|
* call a lazy scratch arena, whose first block is taken by the first stack
|
|
* built, so a call that builds no stack allocates nothing.
|
|
*/
|
|
#ifndef CBM_EXTRACT_NODE_STACK_H
|
|
#define CBM_EXTRACT_NODE_STACK_H
|
|
|
|
#include "cbm.h" /* CBMExtractCtx: a stack draws from ctx->scratch */
|
|
#include "arena.h"
|
|
#include "tree_sitter/api.h"
|
|
#include <string.h> /* memcpy */
|
|
|
|
typedef struct {
|
|
TSNode *items;
|
|
int count;
|
|
int cap;
|
|
/* The arena every allocation for this stack comes from, recorded once by
|
|
* ts_nstack_init so push() cannot be handed a different one. It is
|
|
* ctx->scratch, or ctx->arena as the fallback when the context has none. */
|
|
CBMArena *scratch;
|
|
} TSNodeStack;
|
|
|
|
/* Initialize a stack with the given initial capacity, allocated from the
|
|
* context's traversal scratch. Taking the context rather than an arena is
|
|
* deliberate: it makes handing over ctx->arena, or a local alias of it, a type
|
|
* error rather than a retention bug nobody notices. A context built without a
|
|
* scratch falls back to ctx->arena, which is the behaviour that shipped before
|
|
* #1997, so no caller ever gets a NULL arena and silently loses nodes. */
|
|
static inline void ts_nstack_init(TSNodeStack *s, const CBMExtractCtx *ctx, int initial_cap) {
|
|
CBMArena *arena = ctx->scratch ? ctx->scratch : ctx->arena;
|
|
s->scratch = arena;
|
|
s->items = (TSNode *)cbm_arena_alloc(arena, (size_t)initial_cap * sizeof(TSNode));
|
|
s->count = 0;
|
|
s->cap = s->items ? initial_cap : 0;
|
|
}
|
|
|
|
/* Push a node onto the stack, growing 2x if needed. */
|
|
static inline void ts_nstack_push(TSNodeStack *s, TSNode node) {
|
|
if (s->count >= s->cap) {
|
|
int new_cap = s->cap ? s->cap * 2 : 512;
|
|
TSNode *new_items = (TSNode *)cbm_arena_alloc(s->scratch, (size_t)new_cap * sizeof(TSNode));
|
|
if (!new_items)
|
|
return; /* OOM: best-effort, stop growing */
|
|
if (s->items && s->count > 0) {
|
|
memcpy(new_items, s->items, (size_t)s->count * sizeof(TSNode));
|
|
}
|
|
/* Old s->items is abandoned in the arena — freed on arena_destroy. */
|
|
s->items = new_items;
|
|
s->cap = new_cap;
|
|
}
|
|
s->items[s->count++] = node;
|
|
}
|
|
|
|
/* Pop a node from the stack. Caller must check s->count > 0. */
|
|
static inline TSNode ts_nstack_pop(TSNodeStack *s) {
|
|
return s->items[--s->count];
|
|
}
|
|
|
|
/*
|
|
* Push all children of `node` so they POP in forward (source) order — a drop-in
|
|
* replacement for the common idiom:
|
|
* for (int i = (int)count - 1; i >= 0; i--) ts_nstack_push(s, ts_node_child(node, i));
|
|
*
|
|
* That idiom calls ts_node_child(node, i) once per index, and ts_node_child is
|
|
* O(i) in tree-sitter (it walks the child iterator from the first child each
|
|
* time). Over a node with N children that is O(N^2) — catastrophic on a program
|
|
* root holding hundreds of thousands of top-level nodes (e.g. fixture/generated
|
|
* files). This helper enumerates children in a single O(N) cursor pass, then
|
|
* reverses the just-pushed segment so pop order is identical to the old idiom.
|
|
*/
|
|
static inline void ts_nstack_push_children(TSNodeStack *s, TSNode node) {
|
|
int base = s->count;
|
|
/* The thread's reusable cursor (cbm_thread_cursor): the walk completes
|
|
* before anything else on this thread can ask for it. */
|
|
TSTreeCursor *cursor = cbm_thread_cursor(node);
|
|
if (ts_tree_cursor_goto_first_child(cursor)) {
|
|
do {
|
|
ts_nstack_push(s, ts_tree_cursor_current_node(cursor));
|
|
} while (ts_tree_cursor_goto_next_sibling(cursor));
|
|
}
|
|
/* Reverse [base, count) so the first child pops first (forward order). */
|
|
int lo = base, hi = s->count - 1;
|
|
while (lo < hi) {
|
|
TSNode tmp = s->items[lo];
|
|
s->items[lo] = s->items[hi];
|
|
s->items[hi] = tmp;
|
|
lo++;
|
|
hi--;
|
|
}
|
|
}
|
|
|
|
#endif /* CBM_EXTRACT_NODE_STACK_H */
|