From 78e86db74650f97636ab0ec6663699daa1af2c6f Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 7 Jul 2026 20:21:01 +0800 Subject: [PATCH] fix(codegen): guard against infinite recursion on cyclic helper bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _collect_security_ta_binding_stacks recursed into an Identifier's helper binding (the `isinstance(expr_node, Identifier)` + _security_lookup_helper_binding path) WITHOUT the `resolving` cycle guard that the mutable/global/func paths in the same function already carry. A cyclic helper binding — an identifier bound to an expression that transitively references the same binding — recursed forever (hungpixi-macd-enhanced-mtf: RecursionError, transpile crash, even at limit 300000). Guard that path too, keyed by the bound node's id so acyclic bindings are completely unaffected (the guard never fires → byte-identical emission; the corpus + all other strategies are unchanged). Turns a transpiler crash into a terminating traversal. Co-Authored-By: Claude Opus 4.8 (1M context) --- pineforge_codegen/codegen/security.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pineforge_codegen/codegen/security.py b/pineforge_codegen/codegen/security.py index 114e5cf..ad162f1 100644 --- a/pineforge_codegen/codegen/security.py +++ b/pineforge_codegen/codegen/security.py @@ -1173,6 +1173,16 @@ def _collect_security_ta_binding_stacks( if bound is not None: if isinstance(bound, str): return collected + # Guard against a cyclic helper binding: an identifier bound to + # an expression that transitively references the same binding + # (e.g. mutually-referential helper params) would recurse here + # forever — the mutable/global/func paths below already carry a + # `resolving` guard, this path did not. Keyed by the bound node + # so acyclic bindings are unaffected (byte-identical emission). + bind_key = f"bind:{id(bound)}" + if bind_key in resolving: + return collected + resolving.add(bind_key) self._collect_security_ta_binding_stacks( bound, resolving, @@ -1181,6 +1191,7 @@ def _collect_security_ta_binding_stacks( inline_ta_indices, inline_helper, ) + resolving.discard(bind_key) return collected mutable_info = self._global_mutable_infos.get(expr_node.name)