Skip to content

Native engine segfaults on deeply nested ASTs (unbounded recursion in walk_node) #481

@carlos-alm

Description

@carlos-alm

Summary

The native Rust engine (crates/codegraph-core/) crashes with a segfault when parsing files with deeply nested syntax structures. The walk_node function in all language extractors recurses over the full AST without any depth limit, causing stack overflow that terminates the Node.js process.

Workaround: Use --engine wasm to bypass the native addon entirely.

Root Cause

Every language extractor's walk_node function follows this pattern:

fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    match node.kind() { /* ... */ }
    
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i) {
            walk_node(&child, source, symbols);  // RECURSIVE — NO DEPTH LIMIT
        }
    }
}

Each recursive call consumes ~256+ bytes of stack. When AST depth exceeds the thread stack limit (~1MB on Windows, ~8MB on Linux), Rust panics and the napi-rs binding propagates this as a native crash (segfault).

Affected Components

Rank Component File Depth Limit
CRITICAL walk_node (all extractors) src/extractors/*.rs None
CRITICAL walk_ast_nodes (JS) src/extractors/javascript.rs:411 None
CRITICAL walk_ast_nodes_with_config src/extractors/helpers.rs:208 None
HIGH scan_import_names (JS) src/extractors/javascript.rs:1115 None
HIGH extract_implements_from_node (JS) src/extractors/javascript.rs:765 None
MODERATE walk_children (complexity) src/complexity.rs:381 Semantic only
MODERATE process_if (CFG) src/cfg.rs:443 None
OK visit (dataflow) src/dataflow.rs:854 200 (has protection)

Note: The dataflow module (dataflow.rs) already implements MAX_VISIT_DEPTH = 200 — this pattern should be adopted everywhere.

Affected Extractors

  • javascript.rs (line 19) — also has second recursive walker at line 411
  • go.rs (line 19)
  • python.rs (line 19)
  • java.rs (line 35)
  • csharp.rs (line 36)
  • ruby.rs (line 35)
  • php.rs (line 35)
  • rust_lang.rs (line 32)

Trigger Conditions

Files with AST depth exceeding ~30-40 levels (platform-dependent), caused by:

  • Deeply nested function definitions or callbacks
  • Nested object/array literals
  • Nested template strings with expressions
  • Deeply chained method calls
  • Heavily indented control flow (if/else, try/catch)

Proposed Fix

  1. Add a MAX_WALK_DEPTH constant (e.g., 200, matching dataflow's existing MAX_VISIT_DEPTH)
  2. Thread a depth: usize parameter through all recursive walk_node, walk_ast_nodes, and helper functions
  3. Early-return when depth is exceeded — log a warning but don't crash
  4. Alternatively, convert to iterative traversal using an explicit stack (Vec<Node>) to eliminate stack overflow risk entirely
  5. Add std::panic::catch_unwind around the napi-rs entry point as a safety net

Option 4 (iterative) is the most robust long-term solution. Option 1-3 is a simpler short-term fix.

Reproduction

Observed during a Titan GAUNTLET audit session (Batch 3 — Extractors). The native engine segfaulted while running codegraph commands against its own codebase. Switching to --engine wasm worked around the issue.

Labels

bug, native-engine, safety

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions