From f3cd815c77ff4d690762b53aed85c1f77e3df73c Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Mon, 17 Nov 2025 18:23:00 +0900 Subject: [PATCH] feat(parsercontrol): add shallow recursion guards to ParserControlBox (if/loop/break/continue/block) --- .../parser/stmt/parser_control_box.hako | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/lang/src/compiler/parser/stmt/parser_control_box.hako b/lang/src/compiler/parser/stmt/parser_control_box.hako index 91fca714..3e5242d6 100644 --- a/lang/src/compiler/parser/stmt/parser_control_box.hako +++ b/lang/src/compiler/parser/stmt/parser_control_box.hako @@ -7,6 +7,16 @@ using sh_core as StringHelpers // Required: using chain resolution not implemen static box ParserControlBox { // Parse: if (cond) { ... } else { ... } parse_if(src, i, stmt_start, ctx) { + // Shallow recursion guard for Stage‑B / selfhost: protect parse_if from + // accidental self-recursion via malformed input or control flow bugs. + { + local depth = env.get("HAKO_STAGEB_IF_DEPTH") + if depth != null && ("" + depth) != "0" { + print("[stageb/recursion] ParserControlBox.parse_if recursion detected") + return "{\"type\":\"If\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"then\":[]}" + } + env.set("HAKO_STAGEB_IF_DEPTH", "1") + } local j = i + 2 // skip "if" j = ctx.skip_ws(src, j) local paren = 0 @@ -42,6 +52,7 @@ static box ParserControlBox { } ctx.gpos_set(j) + env.set("HAKO_STAGEB_IF_DEPTH", "0") if else_json == null { return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + "}" } else { @@ -52,6 +63,16 @@ static box ParserControlBox { // Parse: loop(cond) { ... } // Dev tracing: set HAKO_PARSER_TRACE_LOOP=1 to enable debug prints parse_loop(src, i, stmt_start, ctx) { + // Shallow recursion guard for parse_loop + { + local depth = env.get("HAKO_STAGEB_LOOP_DEPTH") + if depth != null && ("" + depth) != "0" { + print("[stageb/recursion] ParserControlBox.parse_loop recursion detected") + return "{\"type\":\"Loop\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"body\":[]}" + } + env.set("HAKO_STAGEB_LOOP_DEPTH", "1") + } + local trace = 0 // dev-only (disabled in Stage-B runtime: no env API here) local j = i + 4 // skip "loop" j = ctx.skip_ws(src, j) @@ -133,11 +154,20 @@ static box ParserControlBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + env.set("HAKO_STAGEB_LOOP_DEPTH", "0") return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}" } // Parse: break → {type:"Break"} (Stage-3) or no-op parse_break(src, i, stmt_start, ctx) { + { + local depth = env.get("HAKO_STAGEB_BREAK_DEPTH") + if depth != null && ("" + depth) != "0" { + print("[stageb/recursion] ParserControlBox.parse_break recursion detected") + return "{\"type\":\"Break\"}" + } + env.set("HAKO_STAGEB_BREAK_DEPTH", "1") + } local j = i + 5 // skip "break" if ctx.stage3_enabled() == 1 { @@ -153,11 +183,20 @@ static box ParserControlBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + env.set("HAKO_STAGEB_BREAK_DEPTH", "0") return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}" } // Parse: continue → {type:"Continue"} (Stage-3) or no-op parse_continue(src, i, stmt_start, ctx) { + { + local depth = env.get("HAKO_STAGEB_CONTINUE_DEPTH") + if depth != null && ("" + depth) != "0" { + print("[stageb/recursion] ParserControlBox.parse_continue recursion detected") + return "{\"type\":\"Continue\"}" + } + env.set("HAKO_STAGEB_CONTINUE_DEPTH", "1") + } local j = i + 8 // skip "continue" if ctx.stage3_enabled() == 1 { @@ -173,11 +212,20 @@ static box ParserControlBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + env.set("HAKO_STAGEB_CONTINUE_DEPTH", "0") return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}" } // Parse: { stmt1; stmt2; ... } parse_block(src, i, ctx) { + { + local depth = env.get("HAKO_STAGEB_BLOCK_DEPTH") + if depth != null && ("" + depth) != "0" { + print("[stageb/recursion] ParserControlBox.parse_block recursion detected") + return "[]@" + ctx.i2s(i) + } + env.set("HAKO_STAGEB_BLOCK_DEPTH", "1") + } local j = ctx.skip_ws(src, i) if src.substring(j, j+1) != "{" { return "[]@" + ctx.i2s(j) } j = j + 1 @@ -231,6 +279,7 @@ static box ParserControlBox { } body = body + "]" + env.set("HAKO_STAGEB_BLOCK_DEPTH", "0") return body + "@" + ctx.i2s(j) } }