Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2026-06-10 - Avoiding Eager Allocation in Recursive AST Generation
**Learning:** Python's `ast.iter_child_nodes` produces a generator. When writing recursive AST visitors, eagerly materializing this generator with `list(ast.iter_child_nodes(node))` just to pass it to the recursive function creates significant memory allocation and garbage collection overhead. Furthermore, replacing `yield from` with explicit stack/list manipulation to "flatten" traversal often backfires, as it usually introduces intermediate slice (`[::-1]`) overhead to preserve tree visitation order, destroying the benefits of flattening.
**Action:** Always accept `Iterable[ast.AST]` in recursive AST visitors and pass `ast.iter_child_nodes(node)` directly. Trust Python's generator delegation (`yield from` or just iterating) over manual list materialization.
12 changes: 5 additions & 7 deletions src/wardline/scanner/taint/variable_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from wardline.core.taints import _PROVENANCE_CLASH, TRUST_RANK, TaintState, combine

if TYPE_CHECKING:
from collections.abc import Iterator
from collections.abc import Iterable, Iterator

# Serialisation sinks — calls that cross the representation boundary. Their
# output sheds validation provenance (raw bytes/str), so → UNKNOWN_RAW. This is
Expand Down Expand Up @@ -1691,7 +1691,7 @@ def compute_return_callee(


def _assignment_callee(
nodes: list[ast.AST],
nodes: Iterable[ast.AST],
name: str,
worst: TaintState,
function_taint: TaintState,
Expand Down Expand Up @@ -1724,9 +1724,7 @@ def _assignment_callee(
and _resolve_expr(node.value, function_taint, taint_map, var_taints) == worst
):
result = callee
nested = _assignment_callee(
list(ast.iter_child_nodes(node)), name, worst, function_taint, taint_map, var_taints
)
nested = _assignment_callee(ast.iter_child_nodes(node), name, worst, function_taint, taint_map, var_taints)
if nested is not None:
result = nested
return result
Expand All @@ -1743,7 +1741,7 @@ def _return_callee(node: ast.expr) -> str | None:


def _collect_return_paths(
nodes: list[ast.AST],
nodes: Iterable[ast.AST],
function_taint: TaintState,
taint_map: dict[str, TaintState],
var_taints: dict[str, TaintState],
Expand Down Expand Up @@ -1771,4 +1769,4 @@ def _collect_return_paths(
if isinstance(node, (ast.Return, ast.Yield, ast.YieldFrom)) and node.value is not None:
taint = _resolve_expr(node.value, function_taint, taint_map, var_taints)
out.append((taint, _return_callee(node.value), node.value))
_collect_return_paths(list(ast.iter_child_nodes(node)), function_taint, taint_map, var_taints, out)
_collect_return_paths(ast.iter_child_nodes(node), function_taint, taint_map, var_taints, out)
Loading