Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c4bf632

Browse files
committed
Improve async detection mechanism with blacklist
Because the async repl works by wrapping any code that raises SyntaxError in an async function and trying to execute it again, cell bodies that are invalid at the top level but valid in functions and methods (e.g. return and yield statements) currently allow executing invalid code. This patch blacklists return and yield statements outside of a function or method to restore the proper SyntaxError behavior.
1 parent 8d38b3c commit c4bf632

1 file changed

Lines changed: 40 additions & 2 deletions

File tree

IPython/core/async_helpers.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,40 @@ async def __wrapper__():
8080
return res
8181

8282

83+
class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
84+
"""
85+
Find syntax errors that would be an error in an async repl, but because
86+
the implementation involves wrapping the repl in an async function, it
87+
is erroneously allowed (e.g. yield or return at the top level)
88+
"""
89+
def generic_visit(self, node):
90+
func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
91+
invalid_types = (ast.Return, ast.Yield, ast.YieldFrom)
92+
93+
if isinstance(node, func_types):
94+
return # Don't recurse into functions
95+
elif isinstance(node, invalid_types):
96+
raise SyntaxError()
97+
else:
98+
super().generic_visit(node)
99+
100+
101+
def _async_parse_cell(cell: str) -> ast.AST:
102+
"""
103+
This is a compatibility shim for pre-3.7 when async outside of a function
104+
is a syntax error at the parse stage.
105+
106+
It will return an abstract syntax tree parsed as if async and await outside
107+
of a function were not a syntax error.
108+
"""
109+
if sys.version_info < (3, 7):
110+
# Prior to 3.7 you need to asyncify before parse
111+
wrapped_parse_tree = ast.parse(_asyncify(cell))
112+
return wrapped_parse_tree.body[0].body[0]
113+
else:
114+
return ast.parse(cell)
115+
116+
83117
def _should_be_async(cell: str) -> bool:
84118
"""Detect if a block of code need to be wrapped in an `async def`
85119
@@ -99,8 +133,12 @@ def _should_be_async(cell: str) -> bool:
99133
return False
100134
except SyntaxError:
101135
try:
102-
ast.parse(_asyncify(cell))
103-
# TODO verify ast has not "top level" return or yield.
136+
parse_tree = _async_parse_cell(cell)
137+
138+
# Raise a SyntaxError if there are top-level return or yields
139+
v = _AsyncSyntaxErrorVisitor()
140+
v.visit(parse_tree)
141+
104142
except SyntaxError:
105143
return False
106144
return True

0 commit comments

Comments
 (0)