eden/.llms/rules/ACR_recursive_traversal.md
Severity: CRITICAL
fn foo(...) { ... foo(parent) ... } patterns on commit/changeset/path structuresRUST_MIN_STACK or thread stack size to accommodate deep recursionVec as worklist)max_depth parameter that is checkedBAD (recursive blame — matches S623056):
fn blame_file(ctx: &CoreContext, path: &Path, cs_id: ChangesetId) -> Result<BlameResult> {
let parent = get_parent(ctx, cs_id).await?;
if content_changed(ctx, path, cs_id, parent).await? {
let parent_blame = blame_file(ctx, path, parent).await?; // recurse!
merge_blame(parent_blame, cs_id)
} else {
blame_file(ctx, path, parent).await? // recurse without bound!
}
// For files with 10K+ revisions, this exhausts the stack → SIGSEGV
}
GOOD (iterative with explicit stack):
fn blame_file(ctx: &CoreContext, path: &Path, cs_id: ChangesetId) -> Result<BlameResult> {
let mut work_stack = vec![cs_id];
let mut blame = BlameResult::empty();
while let Some(current) = work_stack.pop() {
let parent = get_parent(ctx, current).await?;
if content_changed(ctx, path, current, parent).await? {
blame = merge_blame(blame, current);
}
if parent != ROOT {
work_stack.push(parent);
}
}
Ok(blame)
}
BAD (band-aid fix):
// "Fix" for stack overflow: just increase the stack size.
// This only delays the crash for slightly deeper histories.
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024) // 64MB stack
.spawn(move || blame_file(ctx, path, cs_id))
Convert recursive history/DAG traversal to iterative form using an explicit Vec-based worklist. If recursion is unavoidable, add an explicit depth limit with a clear error when exceeded. Never increase stack size as a fix for unbounded recursion — it's a time bomb. This applies to blame, annotate, log, diff across commits, and any operation proportional to repo history depth.