Skip to content
Open
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
72 changes: 72 additions & 0 deletions tests/test_recursion_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Test for recursion depth fix in tomlkit parser.
Verifies that deeply nested input raises ParseError instead of RecursionError.
"""
import sys
sys.path.insert(0, ".") # Use local patched version if available

import tomlkit
from tomlkit.exceptions import TOMLKitError

def test_deeply_nested_arrays():
"""Deeply nested arrays should raise ParseError, not RecursionError."""
payload = "x = " + "[" * 500 + "1" + "]" * 500
try:
tomlkit.parse(payload)
print("[FAIL] No exception raised for 500-deep nested arrays")
except RecursionError:
print("[FAIL] RecursionError — fix not applied")
except TOMLKitError as e:
print(f"[PASS] ParseError raised: {e}")
except Exception as e:
print(f"[????] Unexpected: {type(e).__name__}: {e}")

def test_deeply_nested_inline_tables():
"""Deeply nested inline tables should raise ParseError, not RecursionError."""
payload = "x = " + "{a = " * 200 + "1" + "}" * 200
try:
tomlkit.parse(payload)
print("[FAIL] No exception raised for 200-deep nested inline tables")
except RecursionError:
print("[FAIL] RecursionError — fix not applied")
except TOMLKitError as e:
print(f"[PASS] ParseError raised: {e}")
except Exception as e:
print(f"[????] Unexpected: {type(e).__name__}: {e}")

def test_normal_nesting_still_works():
"""Reasonable nesting depth should still parse fine."""
# 10 levels deep — well within limit
payload = "x = " + "[" * 10 + "1" + "]" * 10
try:
doc = tomlkit.parse(payload)
print(f"[PASS] 10-deep arrays parse OK")
except Exception as e:
print(f"[FAIL] Normal nesting broken: {e}")

# 5 levels inline tables
payload2 = 'x = {a = {b = {c = {d = {e = 1}}}}}'
try:
doc2 = tomlkit.parse(payload2)
print(f"[PASS] 5-deep inline tables parse OK")
except Exception as e:
print(f"[FAIL] Normal inline tables broken: {e}")

def test_mixed_nesting():
"""Mixed arrays and inline tables at depth."""
payload = "x = " + "[{a = " * 50 + "1" + "}]" * 50
try:
tomlkit.parse(payload)
print("[PASS] 50-deep mixed nesting parsed (within default limit)")
except RecursionError:
print("[FAIL] RecursionError on mixed nesting")
except TOMLKitError as e:
print(f"[PASS] ParseError on mixed nesting: {e}")

if __name__ == "__main__":
print("=== tomlkit recursion depth fix tests ===\n")
test_normal_nesting_still_works()
test_deeply_nested_arrays()
test_deeply_nested_inline_tables()
test_mixed_nesting()
print("\nDone")
22 changes: 20 additions & 2 deletions tomlkit/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,16 @@ class Parser:
Parser for TOML documents.
"""

def __init__(self, string: str | bytes) -> None:
# Default maximum nesting depth for arrays/inline tables
DEFAULT_MAX_NESTING_DEPTH = 100

def __init__(self, string: str | bytes, max_nesting_depth: int | None = None) -> None:
# Input to parse
self._src = Source(decode(string))

self._aot_stack: list[Key] = []
self._nesting_depth = 0
self._max_nesting_depth = max_nesting_depth if max_nesting_depth is not None else self.DEFAULT_MAX_NESTING_DEPTH

@property
def _state(self) -> _StateHandler:
Expand Down Expand Up @@ -582,6 +587,11 @@ def _parse_bool(self, style: BoolType) -> Bool:
def _parse_array(self) -> Array:
# Consume opening bracket, EOF here is an issue (middle of array)
self.inc(exception=UnexpectedEofError)
self._nesting_depth += 1
if self._nesting_depth > self._max_nesting_depth:
raise self.parse_error(
InternalParserError, "Maximum nesting depth exceeded"
)

elems: list[Item] = []
prev_value = None
Expand Down Expand Up @@ -637,15 +647,22 @@ def _parse_array(self) -> Array:
try:
res = Array(elems, Trivia())
except ValueError:
pass
self._nesting_depth -= 1
raise
else:
self._nesting_depth -= 1
return res

raise self.parse_error(ParseError, "Failed to parse array")

def _parse_inline_table(self) -> InlineTable:
# consume opening bracket, EOF here is an issue (middle of array)
self.inc(exception=UnexpectedEofError)
self._nesting_depth += 1
if self._nesting_depth > self._max_nesting_depth:
raise self.parse_error(
InternalParserError, "Maximum nesting depth exceeded"
)

elems = Container(True)
expect_key = True
Expand Down Expand Up @@ -685,6 +702,7 @@ def _parse_inline_table(self) -> InlineTable:
self.inc(exception=UnexpectedEofError)
expect_key = True

self._nesting_depth -= 1
return InlineTable(elems, Trivia())

def _parse_number(self, raw: str, trivia: Trivia) -> Item | None:
Expand Down
Loading