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:
Gaurav Dubey committed 2026-10-22 01:44:27 +00:00
1 parent ff28841eed
commit 4b843c2791
3 files changed
+152 -12

No files matched your search

+37 -12
View File
@@ -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 ───────────────────────────
+81
View File
@@ -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);
+34
View File
@@ -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);