You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
test_deep_nesting from test_plistlib triggers an exhausted stack protection in a debug build under WASI #111803
changed the title [-]`test_deep_nesting` from `test_plistlib` triggers stack overflow protection in a debug build under WASI[/-][+]`test_deep_nesting` from `test_plistlib` triggers an exhausted stack protection in a debug build under WASI[/+]on Nov 8, 2023
The test appear to test a deeply nested list, but I don't know why the test itself ignores recursion errors. Maybe @serhiy-storchaka recalls why added that part of the test.
The test is efffectively a no-op in main, both values of N result in a RecusionError on my laptop (--enable-framework --with-pydebug)
This diff still tests fairly deep nesting without hitting RecursionError:
diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py
index f47982907d..5650a43d95 100644
--- a/Lib/test/test_plistlib.py+++ b/Lib/test/test_plistlib.py@@ -971,18 +971,15 @@ def test_cycles(self):
self.assertIs(b['x'], b)
def test_deep_nesting(self):
- for N in [300, 100000]:+ for N in [50, 100]:+ with self.subTest(N=N):
chunks = [b'\xa1' + (i + 1).to_bytes(4, 'big') for i in range(N)]
- try:- result = self.decode(*chunks, b'\x54seed', offset_size=4, ref_size=4)- except RecursionError:- pass- else:- for i in range(N):- self.assertIsInstance(result, list)- self.assertEqual(len(result), 1)- result = result[0]- self.assertEqual(result, 'seed')+ result = self.decode(*chunks, b'\x54seed', offset_size=4, ref_size=4)+ for i in range(N):+ self.assertIsInstance(result, list)+ self.assertEqual(len(result), 1)+ result = result[0]+ self.assertEqual(result, 'seed')
def test_large_timestamp(self):
# Issue #26709: 32-bit timestamp out of range
On my laptop the max recursion that doesn't hit the recursion limit is about 160, keeping the max at 100 in the test keeps us far away from the actual limit. A value of 100 should still be a lot larger than the nesting seen in real plist files, although I don't know if decoding keyed archives (a secondary use case for some users) can run into this.
I'd be in favour of adjusting the test. If there are users that run into the recursion limit with a real-world use-case we can always try to to improve the algorithm, but let's keep the code as-is for now.
The maximum depth was 320 in 3.10, 321 in 3.11, 484 in 3.12, and only 160 in 3.13. Or 1/3 of sys.getrecursionlimit() in 3.10 and 3.11, 1/2 in 3.12 and 1/6 in 3.13. A large regression, especially in comparison with 3.12.
RecursionError for the lower limit is a sign of some general Python regression. Something related to generators or generator expressions.
The stack overflow was because of recursive calls of list.extend() which consume the C stack which is very limited on WASI. #114024 gets rid of list.extend(). All recursion is now in pure Python and no longer depends on the size of the C stack. It is only limited by sys.getrecursionlimit() which can be increased unlimitedly for this task. The test works even for N=968.
It is possible to implement the binary Plist loader in completely non-recursive way without relying on the fact that the pure Python recursion does not consume the C stack (CPython implementation detail), but it will be more complex and perhaps more slow for common cases.
I created #114413 about an unexpected recursion limitation.
But there is still an issue that a debug build under WASI crashes before reaching the recursion limit. Perhaps the sys.getrecursionlimit() value should be significantly reduced on this platform?
@brettcannon, could you please test examples from #114413 on Python 3.11, 3.12 and main and find the minimal depth of crashing in these versions?
But there is still an issue that a debug build under WASI crashes before reaching the recursion limit. Perhaps the sys.getrecursionlimit() value should be significantly reduced on this platform?
The problem is if you reduce it to make one test happy, it has a cascading effect where other tests start to fail. I tried that and then suddenly I think test_json began failing.
@brettcannon, could you please test examples from #114413 on Python 3.11, 3.12 and main and find the minimal depth of crashing in these versions?
I can at least for main, but I'm not concerned for the other versions since the test suite won't pass anyway since all the fixes so far have been in main.
But there is still an issue that a debug build under WASI crashes before reaching the recursion limit. Perhaps the sys.getrecursionlimit() value should be significantly reduced on this platform?
The problem is if you reduce it to make one test happy, it has a cascading effect where other tests start to fail. I tried that and then suddenly I think test_json began failing.
If I drop it down to 150 then two tests fail.
test_pickle
ERROR: test_load_global_specialization_failure_keeps_oparg (__main__.RebindBuiltinsTests.test_load_global_specialization_failure_keeps_oparg)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Lib/test/test_dynamic.py", line 146, in test_load_global_specialization_failure_keeps_oparg
sum_func = eval(code, MyGlobals())
~~~~^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded during compilation
test_dynamic
ERROR: test_unpickler (__main__.SizeofTests.test_unpickler)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Lib/test/test_pickle.py", line 405, in test_unpickler
check_unpickler(recurse(100), 128, 140)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/Lib/test/test_pickle.py", line 385, in check_unpickler
dump = pickle.dumps(data)
~~~~~~~~~~~~^^^^^^
RecursionError: maximum recursion depth exceeded while pickling an object
Setting it to 175 and the stack depth check in #114413 triggers stack depth exhaustion in the WASI host.
Activity
brettcannon commentedon Nov 8, 2023
[-]`test_deep_nesting` from `test_plistlib` triggers stack overflow protection in a debug build under WASI[/-][+]`test_deep_nesting` from `test_plistlib` triggers an exhausted stack protection in a debug build under WASI[/+]brettcannon commentedon Jan 12, 2024
@ned-deily @ronaldoussoren what is
test_deep_nesting()supposed to measure? A really nested file, or triggeringRecursionError?ronaldoussoren commentedon Jan 13, 2024
The test was introduced in 34637a0 as part of this issue: https://bugs.python.org/issue42103
The test appear to test a deeply nested list, but I don't know why the test itself ignores recursion errors. Maybe @serhiy-storchaka recalls why added that part of the test.
The test is efffectively a no-op in main, both values of N result in a RecusionError on my laptop (
--enable-framework --with-pydebug)This diff still tests fairly deep nesting without hitting RecursionError:
On my laptop the max recursion that doesn't hit the recursion limit is about 160, keeping the max at 100 in the test keeps us far away from the actual limit. A value of 100 should still be a lot larger than the nesting seen in real plist files, although I don't know if decoding keyed archives (a secondary use case for some users) can run into this.
I'd be in favour of adjusting the test. If there are users that run into the recursion limit with a real-world use-case we can always try to to improve the algorithm, but let's keep the code as-is for now.
ronaldoussoren commentedon Jan 13, 2024
The linked PR contains the inlined diff from my previous message.
serhiy-storchaka commentedon Jan 13, 2024
It is a regression. It works for N=300 in 3.10-3.12 and only hits the recursion limit in 3.13.
serhiy-storchaka commentedon Jan 13, 2024
The maximum depth was 320 in 3.10, 321 in 3.11, 484 in 3.12, and only 160 in 3.13. Or 1/3 of
sys.getrecursionlimit()in 3.10 and 3.11, 1/2 in 3.12 and 1/6 in 3.13. A large regression, especially in comparison with 3.12.See also https://discuss.python.org/t/recursion-limits/33869.
pythongh-111803: Support loading more deeply nested lists in binary p…
serhiy-storchaka commentedon Jan 13, 2024
RecursionError for the lower limit is a sign of some general Python regression. Something related to generators or generator expressions.
The stack overflow was because of recursive calls of
list.extend()which consume the C stack which is very limited on WASI. #114024 gets rid oflist.extend(). All recursion is now in pure Python and no longer depends on the size of the C stack. It is only limited bysys.getrecursionlimit()which can be increased unlimitedly for this task. The test works even for N=968.It is possible to implement the binary Plist loader in completely non-recursive way without relying on the fact that the pure Python recursion does not consume the C stack (CPython implementation detail), but it will be more complex and perhaps more slow for common cases.
ronaldoussoren commentedon Jan 13, 2024
@serhiy-storchaka, your fix is much better than just reducing the limit in the test case. Thanks for looking into this.
8 remaining items
serhiy-storchaka commentedon Jan 22, 2024
I created #114413 about an unexpected recursion limitation.
But there is still an issue that a debug build under WASI crashes before reaching the recursion limit. Perhaps the
sys.getrecursionlimit()value should be significantly reduced on this platform?@brettcannon, could you please test examples from #114413 on Python 3.11, 3.12 and main and find the minimal depth of crashing in these versions?
pythongh-111803: Support loading more deeply nested lists in binary p…
pythongh-111803: Make test_deep_nesting from test_plistlib more strict (
[3.12] gh-111803: Make test_deep_nesting from test_plistlib more stri…
[3.11] gh-111803: Make test_deep_nesting from test_plistlib more stri…
brettcannon commentedon Jan 22, 2024
The problem is if you reduce it to make one test happy, it has a cascading effect where other tests start to fail. I tried that and then suddenly I think
test_jsonbegan failing.I can at least for
main, but I'm not concerned for the other versions since the test suite won't pass anyway since all the fixes so far have been inmain.brettcannon commentedon Jan 22, 2024
If I drop it down to 150 then two tests fail.
test_pickletest_dynamicSetting it to 175 and the stack depth check in #114413 triggers stack depth exhaustion in the WASI host.
brettcannon commentedon Jan 23, 2024
I opened #114456 for changing the recursion limit.
pythongh-111803: Support loading more deeply nested lists in binary p…
pythongh-111803: Make test_deep_nesting from test_plistlib more strict (
dawnwages commentedon Feb 15, 2024
What's left to be done on this one?
brettcannon commentedon Feb 21, 2024
I don't think anything, so I'm going to close it. Thanks for the ping!
pythongh-111803: Support loading more deeply nested lists in binary p…
pythongh-111803: Make test_deep_nesting from test_plistlib more strict (