Repository navigation
Compiling recursive Python ASTs crash the interpreter #55314
Copy link
Copy link
Closed
Labels
3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = None
closed_at = <Date 2021-06-08.17:42:44.965>
created_at = <Date 2011-02-03.05:02:39.444>
labels = ['interpreter-core', '3.10', '3.9', 'type-crash', '3.11']
title = 'Compiling recursive Python ASTs crash the interpreter'
updated_at = <Date 2021-06-08.17:42:44.964>
user = 'https://github.com/benjaminp'bugs.python.org fields:
activity = <Date 2021-06-08.17:42:44.964>
actor = 'BTaskaya'
assignee = 'none'
closed = True
closed_date = <Date 2021-06-08.17:42:44.965>
closer = 'BTaskaya'
components = ['Interpreter Core']
creation = <Date 2011-02-03.05:02:39.444>
creator = 'benjamin.peterson'
dependencies = []
files = []
hgrepos = []
issue_num = 11105
keywords = ['patch']
message_count = 27.0
messages = ['127783', '127797', '127798', '127799', '127800', '127801', '127816', '155978', '155986', '358952', '359122', '373078', '373084', '395040', '395046', '395050', '395172', '395173', '395174', '395177', '395189', '395190', '395341', '395342', '395343', '395344', '395345']
nosy_count = 12.0
nosy_names = ['terry.reedy', 'gregory.p.smith', 'belopolsky', 'benjamin.peterson', 'jkloth', 'meador.inge', 'lukasz.langa', 'eric.snow', 'pablogsal', 'miss-islington', 'BTaskaya', 'kj']
pr_nums = ['20594', '26521', '26522', '26550', '26554', '26578', '26604', '26605', '26607']
priority = None
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'crash'
url = 'https://bugs.python.org/issue11105'
versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Activity
benjaminp commentedon Feb 3, 2011
You don't want to know why I was thinking about this...
abalkin commentedon Feb 3, 2011
Looks like a stack overflow caused by an infinite recursion. I am not sure if it is possible to add cycle detection code without sacrificing performance or setting some arbitrary limits.
I wonder: Why ast nodes need to be mutable?
benjaminp commentedon Feb 3, 2011
2011/2/3 Alexander Belopolsky <report@bugs.python.org>:
Yes, it's definitely low priority. It's probably easier to crash the
interpreter by producing differently malformed ast anyway.
So people can change them.
abalkin commentedon Feb 3, 2011
On Thu, Feb 3, 2011 at 12:08 PM, Benjamin Peterson
<report@bugs.python.org> wrote:
..
Well, they are hashable, so this needs to be done carefully. Is this
necessary for AST-based optimizations? Does Python actually change
AST after it has been created? Note that for some optimizations it
may be more appropriate to build a new tree rather than mutate the old
one. Depending on the algorithm, you may or may not need to change
the nodes after they have been created in the process.
benjaminp commentedon Feb 3, 2011
2011/2/3 Alexander Belopolsky <report@bugs.python.org>: > > Alexander Belopolsky <belopolsky@users.sourceforge.net> added the comment: > > On Thu, Feb 3, 2011 at 12:08 PM, Benjamin Peterson > <report@bugs.python.org> wrote: > .. >>> I wonder: Why ast nodes need to be mutable? >> >> So people can change them. > > Well, they are hashable, so this needs to be done carefully. Is this > necessary for AST-based optimizations? Does Python actually change > AST after it has been created? Note that for some optimizations it > may be more appropriate to build a new tree rather than mutate the old > one. Depending on the algorithm, you may or may not need to change > the nodes after they have been created in the process.Other people are, though. The hash is by identity anyway.
benjaminp commentedon Feb 3, 2011
2011/2/3 Alexander Belopolsky <report@bugs.python.org>: > > Alexander Belopolsky <belopolsky@users.sourceforge.net> added the comment: > > On Thu, Feb 3, 2011 at 12:08 PM, Benjamin Peterson > <report@bugs.python.org> wrote: > .. >>> I wonder: Why ast nodes need to be mutable? >> >> So people can change them. > > Well, they are hashable, so this needs to be done carefully. Is this > necessary for AST-based optimizations? Does Python actually change > AST after it has been created? Note that for some optimizations it > may be more appropriate to build a new tree rather than mutate the old > one. Depending on the algorithm, you may or may not need to change > the nodes after they have been created in the process.Other people are, though. The hash is by identity anyway.
birkenfeld commentedon Feb 3, 2011
Alex: If the node attributes were not mutable, it would be extremely awkward, not to say inefficient, to mutate an already existing AST as returned by ast.parse().
The AST objects in the _ast module aren't what Python works with internally, anyway. When calling ast.parse(), the AST is converted to Python objects (these are defined in Python-ast.c), and compile()ing such an object converts them back to the internal tree representation. This conversion is where recursions would need to be handled.
gpshead commentedon Mar 16, 2012
i haven't confirmed if it is this exact bug but I believe a coworker just ran into something similar. he wrote code to use the ast to remove docstrings from code before passing it to compile() (as that saves a noticable amount of memory). in the case the ast for code like:
Removing the docstring and passing such a thing to compile triggers a problem. A workaround was to add a pass in such cases:
if (node.body and isinstance(node.body[0], ast.Expr) and
isinstance(node.body[0].value, ast.Str)):
docstring = node.body.pop(0)
if len(node.body) == 0:
# An empty body will sometimes provoke a segfault when you call
# compile on the code object.
node.body.append(ast.Pass(lineno=docstring.lineno,
col_offset=docstring.col_offset))
regardless, it'd be better if compile() *never* crashed on strange input.
benjaminp commentedon Mar 16, 2012
Have him try on 3.3. This should be less of an issue there where there is an AST validator. It doesn't fix this bug, but it does fix most accidental AST construction bugs.
isidentical commentedon Dec 28, 2019
pppery commentedon Dec 31, 2019
What about indirect cycles like below:
(I tested, this also crashes)
terryjreedy commentedon Jul 6, 2020
With 3.9 on Windows, using Benjamin's example, I do not get the Windows equivalent of a seg fault. However, execution stops at compile with no exception, including SystemExit.
These examples amount to limited fuzz testing of compile(). I think it should raise something like "SyntaxError: recursive ast" or even 'bad ast' if malformed non-recursive asts have the same issue.
11 remaining items
pablogsal commentedon Jun 5, 2021
Batuhan, can you take a look?
isidentical commentedon Jun 5, 2021
Yes.
isidentical commentedon Jun 5, 2021
I don't think that we should make a global change for this case, AFAIK some of the core parts of the interpreter maintain their own recursion checks with different handling of windows limits. E.g;
cpython/Python/marshal.c
Lines 25 to 40 in fa106a6
We might need to end up with the same motion and do the handling by ourselves. Wdyt @pablogsal @kj?
isidentical commentedon Jun 6, 2021
After playing with it for a couple hours and without much success of creating a test environment (only using buildbots), I decided not to introduce hard limits. Even though they make the original tests to pass, they don't solve the problem overall and also more important part is that the 'hard limits' might cause regressions for people who do compile(<AST>) calls.
For normal windows builds (as @kj noted) something might work in the current revision and we might just break it with introducing hard limits. Since the trees are tend to get really branchy, I don't think it is a good idea.
I'm open to any proposals/plans
Extra: In the worst case that we can't come up with something (the AST converter functions are really long 2000+ LoC C functions so it is possible that there might be stuff that eats a lot of space on the stack), we can either
a) revert => not a good option, this is not a regression on the python itself. It is a fix for other os's and windows release builds
b) always skip the test on windows => we can do that but it might be counterintuitive for the future
c) use a really low recursion limit for the test_recursion_* for windows => I'm open to fallback to this if nothing comes up.
we might need to revert this though as is it is not a regression. It used to crash with the same exact error, just outside of the test suite, and now since it works for linux/macos/others + windows for release builds I wonder whether can just skip the test on windows and keep it as is in the worst scenario).
pablogsal commentedon Jun 6, 2021
Well, is not that the test is flaky technically, this means that the feature doesn't work on Windows (non release builds). So the reasoning has to be why we want/need to not support this on Windows. Otherwise we need to customize the limit on debug builds.
isidentical commentedon Jun 8, 2021
New changeset e58d762 by Batuhan Taskaya in branch 'main':
bpo-11105: reduce the recursion limit for tests (GH-26550)
e58d762
isidentical commentedon Jun 8, 2021
New changeset 8004c45 by Batuhan Taskaya in branch 'main':
bpo-11105: document the new test.support.infinite_recursion context manager (GH-26604)
8004c45
isidentical commentedon Jun 8, 2021
New changeset bd6f0d3 by Batuhan Taskaya in branch '3.10':
[3.10] bpo-11105: reduce the recursion limit for tests. (GH-26607)
bd6f0d3
isidentical commentedon Jun 8, 2021
New changeset 87f5022 by Batuhan Taskaya in branch '3.9':
[3.9] bpo-11105: reduce the recursion limit for tests. (GH-26605)
87f5022
isidentical commentedon Jun 8, 2021
The issue has been solved, all buildbots should now pass. Will continue to monitor the situation. Thanks for the report @kj!