Skip to content

fix(pdf-outline): use iterative walk to avoid RecursionError on deep outlines#3855

Open
Ys876 wants to merge 1 commit into
docling-project:mainfrom
Ys876:fix/pdf-outline-recursion-limit
Open

fix(pdf-outline): use iterative walk to avoid RecursionError on deep outlines#3855
Ys876 wants to merge 1 commit into
docling-project:mainfrom
Ys876:fix/pdf-outline-recursion-limit

Conversation

@Ys876

@Ys876 Ys876 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Fixes #1743. PDFs whose bookmark/outline tree is nested deeper than
Python's default recursion limit (1000 levels) crashed with
RecursionError: maximum recursion depth exceeded during conversion.
This happens for real on large technical or legal documents with many
nested heading levels, and can happen more severely with malformed
PDFs.

Root cause

extract_outline_from_docling_parse() in docling/utils/pdf_outline.py
walked the outline tree with a plain recursive function, one Python
call per nesting level, with no depth bound.

Fix

Replace the recursive walk with an iterative pre-order depth-first
traversal using an explicit stack -- a standard technique for removing
Python's call-stack limit from tree traversals.

Testing

  • Verified the fix is output-equivalent to the original recursive
    implementation across 200 randomly generated tree shapes (varying
    size and branching), plus explicit edge cases: empty tree, flat
    sibling list, a 500-deep chain (shallow enough for the original to
    survive, for direct side-by-side comparison), and title-whitespace
    handling (empty, whitespace-only, needs-stripping).
  • Verified the fix handles depths the original could never survive:
    tested successfully to 100,000 nested levels.
  • Added tests/test_pdf_outline.py (previously zero coverage for this
    module in isolation) with 5 tests, including a regression test that
    fails with RecursionError against the unmodified code and passes
    against the fix.
  • Ran the existing tests/test_heading_hierarchy_bookmarks.py suite
    (13 tests, including two against real sample/generated PDFs) --
    all pass unmodified, zero regression.
  • ruff format / ruff check both report the changed files already
    clean.

…outlines

extract_outline_from_docling_parse() walked a PDF's bookmark/outline tree
recursively, one Python function call per nesting level, with no depth
limit. PDFs whose outline is nested deeper than Python's default
recursion limit (1000) -- which happens for real, large technical or
legal documents with many nested heading levels, and can happen more
severely in malformed PDFs -- crashed with RecursionError.

Replace the recursive walk with an iterative one using an explicit
stack. Verified output-equivalent to the original across 200 randomized
tree shapes plus explicit edge cases (empty tree, flat list, whitespace
titles), and verified the fix handles trees far deeper than the
original could ever survive (tested to 100,000 levels).

Added tests/test_pdf_outline.py, which had no prior coverage for this
module in isolation -- the existing heading-hierarchy tests only use
shallow real sample PDFs and could never have caught a deep-recursion
bug.

Fixes docling-project#1743

Signed-off-by: Yana shah <shah1115@purdue.edu>
@github-actions

Copy link
Copy Markdown
Contributor

DCO Check Passed

Thanks @Ys876, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error while parsing : maximum recursion depth exceeded

2 participants