mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(extract): YAML walkers grow their stacks instead of capping content
walk_yaml_mapping (dotted key paths, 256 frames) and scan_yaml_for_infra_bindings (512 nodes) kept their pending nodes in fixed-size stacks and stopped pushing once full. A mapping with more than 256 nested sibling mappings lost the full key_path of every leaf past the cap: `k299.url` was only recorded as the shorter path that the walk of a nested block emits on its own. The infra Route pass decides on those paths. A `data:` payload block with 300 entries lost `data.svcN.webhook_url` for N >= 256, the payload deny never saw those values, and 44 of them were minted as infra Route nodes. Both walkers now start on the same-sized on-stack chunk and grow through cbm_walk_stack_reserve / cbm_walk_stack_release (#2375). If the stack cannot grow, the failure is logged as extract.walk_stack_alloc_failed and the walk stops, instead of truncating silently. Part of #2388. Signed-off-by: Gaurav Dubey <gauravdubey0107@gmail.com>
This commit is contained in:
1 parent
ff28841eed
commit
4b843c2791
3 files changed
+152
-12
No files matched your search
@@ -1663,27 +1663,39 @@ typedef struct {
|
||||
TSNode node;
|
||||
const char *prefix;
|
||||
} yaml_walk_frame_t;
|
||||
#define YAML_WALK_STACK_CAP CBM_SZ_256
|
||||
/* On-stack first chunk of the key-path walk; wider mappings spill to the heap
|
||||
* through cbm_walk_stack_reserve instead of dropping frames (#2388). */
|
||||
#define YAML_WALK_STACK_INLINE CBM_SZ_256
|
||||
|
||||
/* Push block_mapping children of a block_node/block_mapping value onto the walk stack. */
|
||||
static void push_yaml_block_children(TSNode val, const char *path, yaml_walk_frame_t *stack,
|
||||
int *top) {
|
||||
/* Push block_mapping children of a block_node/block_mapping value onto the walk
|
||||
* stack, growing it as needed. False when the stack could not grow (logged by
|
||||
* cbm_walk_stack_reserve as extract.walk_stack_alloc_failed). */
|
||||
static bool push_yaml_block_children(TSNode val, const char *path, yaml_walk_frame_t **stack,
|
||||
int *cap, int *top, const yaml_walk_frame_t *inline_buf) {
|
||||
uint32_t vnc = ts_node_named_child_count(val);
|
||||
for (int vi = (int)vnc - SKIP_ONE; vi >= 0 && *top < YAML_WALK_STACK_CAP; vi--) {
|
||||
if (!cbm_walk_stack_reserve((void **)stack, cap, *top + (int)vnc, sizeof(yaml_walk_frame_t),
|
||||
inline_buf, "yaml_key_paths")) {
|
||||
return false;
|
||||
}
|
||||
for (int vi = (int)vnc - SKIP_ONE; vi >= 0; vi--) {
|
||||
TSNode vc = ts_node_named_child(val, (uint32_t)vi);
|
||||
const char *vctype = ts_node_type(vc);
|
||||
if (strcmp(vctype, "block_mapping") == 0 || strcmp(vctype, "block_mapping_pair") == 0) {
|
||||
stack[(*top)++] = (yaml_walk_frame_t){vc, path};
|
||||
(*stack)[(*top)++] = (yaml_walk_frame_t){vc, path};
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void walk_yaml_mapping(CBMExtractCtx *ctx, TSNode root, const char *root_prefix) {
|
||||
yaml_walk_frame_t stack[YAML_WALK_STACK_CAP];
|
||||
yaml_walk_frame_t inline_stack[YAML_WALK_STACK_INLINE];
|
||||
yaml_walk_frame_t *stack = inline_stack;
|
||||
int cap = YAML_WALK_STACK_INLINE;
|
||||
int top = 0;
|
||||
stack[top++] = (yaml_walk_frame_t){root, root_prefix};
|
||||
bool grown = true;
|
||||
|
||||
while (top > 0) {
|
||||
while (grown && top > 0) {
|
||||
yaml_walk_frame_t frame = stack[--top];
|
||||
TSNode node = frame.node;
|
||||
const char *prefix = frame.prefix;
|
||||
@@ -1709,12 +1721,16 @@ static void walk_yaml_mapping(CBMExtractCtx *ctx, TSNode root, const char *root_
|
||||
}
|
||||
const char *vk = ts_node_type(val);
|
||||
if (strcmp(vk, "block_node") == 0 || strcmp(vk, "block_mapping") == 0) {
|
||||
push_yaml_block_children(val, path, stack, &top);
|
||||
grown = push_yaml_block_children(val, path, &stack, &cap, &top, inline_stack);
|
||||
if (!grown) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
emit_yaml_leaf_value(ctx, val, path);
|
||||
}
|
||||
}
|
||||
cbm_walk_stack_release(stack, inline_stack);
|
||||
}
|
||||
|
||||
/* ── Infrastructure binding extraction ─────────────────────────────
|
||||
@@ -1873,9 +1889,13 @@ static void scan_mapping_for_bindings(CBMExtractCtx *ctx, TSNode mapping) {
|
||||
emit_infra_bindings(ctx, sources, source_keys, n_sources, targets, n_targets);
|
||||
}
|
||||
|
||||
#define INFRA_SCAN_STACK_CAP CBM_SZ_512
|
||||
/* On-stack first chunk of the binding scan; wider subtrees spill to the heap
|
||||
* through cbm_walk_stack_reserve instead of dropping nodes (#2388). */
|
||||
#define INFRA_SCAN_STACK_INLINE CBM_SZ_512
|
||||
static void scan_yaml_for_infra_bindings(CBMExtractCtx *ctx, TSNode root) {
|
||||
TSNode stack[INFRA_SCAN_STACK_CAP];
|
||||
TSNode inline_stack[INFRA_SCAN_STACK_INLINE];
|
||||
TSNode *stack = inline_stack;
|
||||
int cap = INFRA_SCAN_STACK_INLINE;
|
||||
int top = 0;
|
||||
stack[top++] = root;
|
||||
while (top > 0) {
|
||||
@@ -1884,10 +1904,15 @@ static void scan_yaml_for_infra_bindings(CBMExtractCtx *ctx, TSNode root) {
|
||||
scan_mapping_for_bindings(ctx, node);
|
||||
}
|
||||
uint32_t nc = ts_node_named_child_count(node);
|
||||
for (int i = (int)nc - SKIP_ONE; i >= 0 && top < INFRA_SCAN_STACK_CAP; i--) {
|
||||
if (!cbm_walk_stack_reserve((void **)&stack, &cap, top + (int)nc, sizeof(TSNode),
|
||||
inline_stack, "yaml_infra_bindings")) {
|
||||
break;
|
||||
}
|
||||
for (int i = (int)nc - SKIP_ONE; i >= 0; i--) {
|
||||
stack[top++] = ts_node_named_child(node, (uint32_t)i);
|
||||
}
|
||||
}
|
||||
cbm_walk_stack_release(stack, inline_stack);
|
||||
}
|
||||
|
||||
/* ── HCL infrastructure binding extraction ───────────────────────────
|
||||
|
||||
@@ -7229,6 +7229,85 @@ TEST(body_tokens_wide_body_samples_from_the_start) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* The YAML walkers kept their pending mappings in fixed-size stacks (256 for
|
||||
* the dotted key-path walk, 512 for the infrastructure-binding scan) and
|
||||
* silently stopped pushing once full (#2388). A root mapping with more nested
|
||||
* sibling mappings than the old cap lost the full dotted key_path of every
|
||||
* leaf past it: `k299.url` was only ever recorded as the bare `url` that the
|
||||
* per-mapping walk of the nested block emits. */
|
||||
enum { WIDE_YAML_KEYS = 300, WIDE_YAML_ITEMS = 600 };
|
||||
|
||||
static int has_string_ref_path(CBMFileResult *r, const char *key_path, const char *value) {
|
||||
for (int i = 0; i < r->string_refs.count; i++) {
|
||||
const CBMStringRef *s = &r->string_refs.items[i];
|
||||
if (s->key_path && s->value && strcmp(s->key_path, key_path) == 0 &&
|
||||
strcmp(s->value, value) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(yaml_key_paths_wide_mapping_exceed_old_stack_cap) {
|
||||
size_t cap = (size_t)WIDE_YAML_KEYS * 48 + 64;
|
||||
char *src = malloc(cap);
|
||||
ASSERT_NOT_NULL(src);
|
||||
size_t pos = 0;
|
||||
for (int i = 0; i < WIDE_YAML_KEYS; i++) {
|
||||
pos += (size_t)snprintf(src + pos, cap - pos,
|
||||
"k%d:\n url: \"https://h%d.example.com/x\"\n", i, i);
|
||||
}
|
||||
CBMFileResult *r = extract(src, CBM_LANG_YAML, "t", "wide.yaml");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
for (int i = 0; i < WIDE_YAML_KEYS; i++) {
|
||||
char path[32];
|
||||
char value[48];
|
||||
snprintf(path, sizeof(path), "k%d.url", i);
|
||||
snprintf(value, sizeof(value), "https://h%d.example.com/x", i);
|
||||
ASSERT(has_string_ref_path(r, path, value));
|
||||
}
|
||||
cbm_free_result(r);
|
||||
free(src);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static int has_infra_source(CBMFileResult *r, const char *source, const char *url) {
|
||||
for (int i = 0; i < r->infra_bindings.count; i++) {
|
||||
const CBMInfraBinding *b = &r->infra_bindings.items[i];
|
||||
if (b->source_name && b->target_url && strcmp(b->source_name, source) == 0 &&
|
||||
strcmp(b->target_url, url) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(yaml_infra_bindings_wide_sequence_exceed_old_stack_cap) {
|
||||
size_t cap = (size_t)WIDE_YAML_ITEMS * 64 + 64;
|
||||
char *src = malloc(cap);
|
||||
ASSERT_NOT_NULL(src);
|
||||
size_t pos = 0;
|
||||
for (int i = 0; i < WIDE_YAML_ITEMS; i++) {
|
||||
pos += (size_t)snprintf(src + pos, cap - pos,
|
||||
"- topic: t%d\n push_endpoint: \"https://h%d.example.com/p\"\n", i,
|
||||
i);
|
||||
}
|
||||
CBMFileResult *r = extract(src, CBM_LANG_YAML, "t", "pubsub.yaml");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
for (int i = 0; i < WIDE_YAML_ITEMS; i++) {
|
||||
char source[16];
|
||||
char url[48];
|
||||
snprintf(source, sizeof(source), "t%d", i);
|
||||
snprintf(url, sizeof(url), "https://h%d.example.com/p", i);
|
||||
ASSERT(has_infra_source(r, source, url));
|
||||
}
|
||||
cbm_free_result(r);
|
||||
free(src);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* Perl call-graph noise (#459 follow-up)
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
@@ -13483,6 +13562,8 @@ SUITE(extraction) {
|
||||
RUN_TEST(complexity_wide_body_exceeds_old_stack_cap);
|
||||
RUN_TEST(count_branching_wide_body_exceeds_old_stack_cap);
|
||||
RUN_TEST(body_tokens_wide_body_samples_from_the_start);
|
||||
RUN_TEST(yaml_key_paths_wide_mapping_exceed_old_stack_cap);
|
||||
RUN_TEST(yaml_infra_bindings_wide_sequence_exceed_old_stack_cap);
|
||||
RUN_TEST(extract_c_ifdef_split_brace_fn_recovered_issue961);
|
||||
RUN_TEST(extract_cpp_preproc_signature_gap_issue946);
|
||||
RUN_TEST(extract_cpp_preproc_macro_generated_callable_skipped_issue949);
|
||||
|
||||
@@ -1553,6 +1553,39 @@ TEST(contract_edge_infra_routes_from_deploy_configs_still_minted) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #2388: the YAML key-path walk kept its pending mappings in a 256-frame stack
|
||||
* and stopped pushing once full. A `data:` payload block with more nested
|
||||
* entries than that lost the full `data.<entry>.webhook_url` path of every entry
|
||||
* past the cap; only the shorter per-mapping refs survived, the payload deny
|
||||
* never saw them, and each of those values minted an infra Route (44 of 300
|
||||
* here). Every payload value must stay unminted however wide the block is. */
|
||||
enum { WIDE_PAYLOAD_ENTRIES = 300 };
|
||||
|
||||
TEST(contract_edge_no_infra_routes_from_wide_payload_block_issue2388) {
|
||||
size_t cap = (size_t)WIDE_PAYLOAD_ENTRIES * 64 + 16;
|
||||
char *src = malloc(cap);
|
||||
ASSERT_NOT_NULL(src);
|
||||
size_t pos = (size_t)snprintf(src, cap, "data:\n");
|
||||
for (int i = 0; i < WIDE_PAYLOAD_ENTRIES; i++) {
|
||||
pos +=
|
||||
(size_t)snprintf(src + pos, cap - pos,
|
||||
" svc%d:\n webhook_url: https://h%d.payload.example/hook\n", i, i);
|
||||
}
|
||||
LangProj lp;
|
||||
LangFile f[] = {{"service.yaml", src}};
|
||||
cbm_store_t *store = lang_index_files(&lp, f, 1);
|
||||
ASSERT_NOT_NULL(store);
|
||||
int routes = count_infra_routes_matching(store, lp.project, "payload.example");
|
||||
if (routes != 0) {
|
||||
fprintf(stderr, " [2388] FAIL wide payload block minted %d infra routes (expected 0)\n",
|
||||
routes);
|
||||
}
|
||||
ASSERT_EQ(routes, 0);
|
||||
lang_cleanup(&lp, store);
|
||||
free(src);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A config URL becomes an infra Route only under a key that names an endpoint
|
||||
* this service exposes. Reference links are not: across 17 infra repositories
|
||||
* the infra Routes were changelog `pull_request` links (deckhouse 6,240),
|
||||
@@ -1945,6 +1978,7 @@ SUITE(lang_contract) {
|
||||
RUN_TEST(contract_edge_python_aliased_import_call_resolves_issue988);
|
||||
RUN_TEST(contract_edge_no_infra_routes_from_ci_configs_issue999);
|
||||
RUN_TEST(contract_edge_infra_routes_from_deploy_configs_still_minted);
|
||||
RUN_TEST(contract_edge_no_infra_routes_from_wide_payload_block_issue2388);
|
||||
RUN_TEST(contract_edge_no_infra_routes_from_reference_links);
|
||||
RUN_TEST(contract_edge_infra_routes_from_endpoint_key_spellings);
|
||||
RUN_TEST(contract_edge_commonjs_require_call_resolves_issue871);
|
||||
|
||||
Reference in new issue
Block a user