gh-121637: Syntax error for optimized-away incorrect await (#121656)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
Jelle Zijlstra 2024-07-22 14:12:43 -07:00 committed by GitHub
parent 69f2dc5c06
commit 2762c6cc5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 104 additions and 52 deletions

View file

@ -75,6 +75,11 @@ New Features
Other Language Changes Other Language Changes
====================== ======================
* Incorrect usage of :keyword:`await` and asynchronous comprehensions
is now detected even if the code is optimized away by the :option:`-O`
command line option. For example, ``python -O -c 'assert await 1'``
now produces a :exc:`SyntaxError`. (Contributed by Jelle Zijlstra in :gh:`121637`.)
* Added class methods :meth:`float.from_number` and :meth:`complex.from_number` * Added class methods :meth:`float.from_number` and :meth:`complex.from_number`
to convert a number to :class:`float` or :class:`complex` type correspondingly. to convert a number to :class:`float` or :class:`complex` type correspondingly.
They raise an error if the argument is a string. They raise an error if the argument is a string.

View file

@ -16,6 +16,7 @@ import platform
import random import random
import re import re
import sys import sys
import textwrap
import traceback import traceback
import types import types
import typing import typing
@ -412,7 +413,7 @@ class BuiltinTest(unittest.TestCase):
"socket.accept is broken" "socket.accept is broken"
) )
def test_compile_top_level_await(self): def test_compile_top_level_await(self):
"""Test whether code some top level await can be compiled. """Test whether code with top level await can be compiled.
Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag
set, and make sure the generated code object has the CO_COROUTINE flag set, and make sure the generated code object has the CO_COROUTINE flag
@ -426,6 +427,7 @@ class BuiltinTest(unittest.TestCase):
yield i yield i
modes = ('single', 'exec') modes = ('single', 'exec')
optimizations = (-1, 0, 1, 2)
code_samples = [ code_samples = [
'''a = await asyncio.sleep(0, result=1)''', '''a = await asyncio.sleep(0, result=1)''',
'''async for i in arange(1): '''async for i in arange(1):
@ -438,34 +440,52 @@ class BuiltinTest(unittest.TestCase):
'''a = [x async for x in arange(2) async for x in arange(2)][1]''', '''a = [x async for x in arange(2) async for x in arange(2)][1]''',
'''a = [x async for x in (x async for x in arange(5))][1]''', '''a = [x async for x in (x async for x in arange(5))][1]''',
'''a, = [1 for x in {x async for x in arange(1)}]''', '''a, = [1 for x in {x async for x in arange(1)}]''',
'''a = [await asyncio.sleep(0, x) async for x in arange(2)][1]''' '''a = [await asyncio.sleep(0, x) async for x in arange(2)][1]''',
# gh-121637: Make sure we correctly handle the case where the
# async code is optimized away
'''assert not await asyncio.sleep(0); a = 1''',
'''assert [x async for x in arange(1)]; a = 1''',
'''assert {x async for x in arange(1)}; a = 1''',
'''assert {x: x async for x in arange(1)}; a = 1''',
'''
if (a := 1) and __debug__:
async with asyncio.Lock() as l:
pass
''',
'''
if (a := 1) and __debug__:
async for x in arange(2):
pass
''',
] ]
policy = maybe_get_event_loop_policy() policy = maybe_get_event_loop_policy()
try: try:
for mode, code_sample in product(modes, code_samples): for mode, code_sample, optimize in product(modes, code_samples, optimizations):
source = dedent(code_sample) with self.subTest(mode=mode, code_sample=code_sample, optimize=optimize):
with self.assertRaises( source = dedent(code_sample)
SyntaxError, msg=f"source={source} mode={mode}"): with self.assertRaises(
compile(source, '?', mode) SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode, optimize=optimize)
co = compile(source, co = compile(source,
'?', '?',
mode, mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
optimize=optimize)
self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE, self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
msg=f"source={source} mode={mode}") msg=f"source={source} mode={mode}")
# test we can create and advance a function type # test we can create and advance a function type
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange} globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
async_f = FunctionType(co, globals_) async_f = FunctionType(co, globals_)
asyncio.run(async_f()) asyncio.run(async_f())
self.assertEqual(globals_['a'], 1) self.assertEqual(globals_['a'], 1)
# test we can await-eval, # test we can await-eval,
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange} globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
asyncio.run(eval(co, globals_)) asyncio.run(eval(co, globals_))
self.assertEqual(globals_['a'], 1) self.assertEqual(globals_['a'], 1)
finally: finally:
asyncio.set_event_loop_policy(policy) asyncio.set_event_loop_policy(policy)

View file

@ -0,0 +1,4 @@
Previously, incorrect usage of :keyword:`await` or asynchronous
comprehensions in code removed by the :option:`-O` option was not flagged by
the Python compiler. Now, such code raises :exc:`SyntaxError`. Patch by
Jelle Zijlstra.

View file

@ -5675,14 +5675,16 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
PyCodeObject *co = NULL; PyCodeObject *co = NULL;
inlined_comprehension_state inline_state = {NULL, NULL, NULL, NO_LABEL, NO_LABEL}; inlined_comprehension_state inline_state = {NULL, NULL, NULL, NO_LABEL, NO_LABEL};
comprehension_ty outermost; comprehension_ty outermost;
#ifndef NDEBUG
int scope_type = c->u->u_scope_type; int scope_type = c->u->u_scope_type;
int is_top_level_await = IS_TOP_LEVEL_AWAIT(c); int is_top_level_await = IS_TOP_LEVEL_AWAIT(c);
#endif
PySTEntryObject *entry = _PySymtable_Lookup(SYMTABLE(c), (void *)e); PySTEntryObject *entry = _PySymtable_Lookup(SYMTABLE(c), (void *)e);
if (entry == NULL) { if (entry == NULL) {
goto error; goto error;
} }
int is_inlined = entry->ste_comp_inlined; int is_inlined = entry->ste_comp_inlined;
int is_async_generator = entry->ste_coroutine; int is_async_comprehension = entry->ste_coroutine;
location loc = LOC(e); location loc = LOC(e);
@ -5697,22 +5699,17 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
} }
else { else {
if (compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION, if (compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION,
(void *)e, e->lineno, NULL) < 0) (void *)e, e->lineno, NULL) < 0) {
{
goto error; goto error;
} }
} }
Py_CLEAR(entry); Py_CLEAR(entry);
if (is_async_generator && type != COMP_GENEXP && assert (!is_async_comprehension ||
scope_type != COMPILER_SCOPE_ASYNC_FUNCTION && type == COMP_GENEXP ||
scope_type != COMPILER_SCOPE_COMPREHENSION && scope_type == COMPILER_SCOPE_ASYNC_FUNCTION ||
!is_top_level_await) scope_type == COMPILER_SCOPE_COMPREHENSION ||
{ is_top_level_await);
compiler_error(c, loc, "asynchronous comprehension outside of "
"an asynchronous function");
goto error_in_scope;
}
if (type != COMP_GENEXP) { if (type != COMP_GENEXP) {
int op; int op;
@ -5777,7 +5774,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
ADDOP_I(c, loc, CALL, 0); ADDOP_I(c, loc, CALL, 0);
if (is_async_generator && type != COMP_GENEXP) { if (is_async_comprehension && type != COMP_GENEXP) {
ADDOP_I(c, loc, GET_AWAITABLE, 0); ADDOP_I(c, loc, GET_AWAITABLE, 0);
ADDOP_LOAD_CONST(c, loc, Py_None); ADDOP_LOAD_CONST(c, loc, Py_None);
ADD_YIELD_FROM(c, loc, 1); ADD_YIELD_FROM(c, loc, 1);
@ -6138,16 +6135,10 @@ compiler_visit_expr(struct compiler *c, expr_ty e)
ADD_YIELD_FROM(c, loc, 0); ADD_YIELD_FROM(c, loc, 0);
break; break;
case Await_kind: case Await_kind:
if (!IS_TOP_LEVEL_AWAIT(c)){ assert(IS_TOP_LEVEL_AWAIT(c) || (_PyST_IsFunctionLike(SYMTABLE_ENTRY(c)) && (
if (!_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) { c->u->u_scope_type == COMPILER_SCOPE_ASYNC_FUNCTION ||
return compiler_error(c, loc, "'await' outside function"); c->u->u_scope_type == COMPILER_SCOPE_COMPREHENSION
} )));
if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
c->u->u_scope_type != COMPILER_SCOPE_COMPREHENSION) {
return compiler_error(c, loc, "'await' outside async function");
}
}
VISIT(c, expr, e->v.Await.value); VISIT(c, expr, e->v.Await.value);
ADDOP_I(c, loc, GET_AWAITABLE, 0); ADDOP_I(c, loc, GET_AWAITABLE, 0);

View file

@ -70,10 +70,10 @@
#define DUPLICATE_TYPE_PARAM \ #define DUPLICATE_TYPE_PARAM \
"duplicate type parameter '%U'" "duplicate type parameter '%U'"
#define ASYNC_WITH_OUTISDE_ASYNC_FUNC \ #define ASYNC_WITH_OUTSIDE_ASYNC_FUNC \
"'async with' outside async function" "'async with' outside async function"
#define ASYNC_FOR_OUTISDE_ASYNC_FUNC \ #define ASYNC_FOR_OUTSIDE_ASYNC_FUNC \
"'async for' outside async function" "'async for' outside async function"
#define LOCATION(x) SRC_LOCATION_FROM_AST(x) #define LOCATION(x) SRC_LOCATION_FROM_AST(x)
@ -82,6 +82,8 @@
PyErr_RangedSyntaxLocationObject((FNAME), \ PyErr_RangedSyntaxLocationObject((FNAME), \
(L).lineno, (L).col_offset + 1, (L).end_lineno, (L).end_col_offset + 1) (L).lineno, (L).col_offset + 1, (L).end_lineno, (L).end_col_offset + 1)
#define IS_ASYNC_DEF(st) ((st)->st_cur->ste_type == FunctionBlock && (st)->st_cur->ste_coroutine)
static PySTEntryObject * static PySTEntryObject *
ste_new(struct symtable *st, identifier name, _Py_block_ty block, ste_new(struct symtable *st, identifier name, _Py_block_ty block,
void *key, _Py_SourceLocation loc) void *key, _Py_SourceLocation loc)
@ -1660,12 +1662,18 @@ check_import_from(struct symtable *st, stmt_ty s)
return 1; return 1;
} }
static bool
allows_top_level_await(struct symtable *st)
{
return (st->st_future->ff_features & PyCF_ALLOW_TOP_LEVEL_AWAIT) &&
st->st_cur->ste_type == ModuleBlock;
}
static void static void
maybe_set_ste_coroutine_for_module(struct symtable *st, stmt_ty s) maybe_set_ste_coroutine_for_module(struct symtable *st, stmt_ty s)
{ {
if ((st->st_future->ff_features & PyCF_ALLOW_TOP_LEVEL_AWAIT) && if (allows_top_level_await(st)) {
(st->st_cur->ste_type == ModuleBlock))
{
st->st_cur->ste_coroutine = 1; st->st_cur->ste_coroutine = 1;
} }
} }
@ -2054,7 +2062,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
} }
case AsyncWith_kind: case AsyncWith_kind:
maybe_set_ste_coroutine_for_module(st, s); maybe_set_ste_coroutine_for_module(st, s);
if (!symtable_raise_if_not_coroutine(st, ASYNC_WITH_OUTISDE_ASYNC_FUNC, LOCATION(s))) { if (!symtable_raise_if_not_coroutine(st, ASYNC_WITH_OUTSIDE_ASYNC_FUNC, LOCATION(s))) {
VISIT_QUIT(st, 0); VISIT_QUIT(st, 0);
} }
VISIT_SEQ(st, withitem, s->v.AsyncWith.items); VISIT_SEQ(st, withitem, s->v.AsyncWith.items);
@ -2062,7 +2070,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
break; break;
case AsyncFor_kind: case AsyncFor_kind:
maybe_set_ste_coroutine_for_module(st, s); maybe_set_ste_coroutine_for_module(st, s);
if (!symtable_raise_if_not_coroutine(st, ASYNC_FOR_OUTISDE_ASYNC_FUNC, LOCATION(s))) { if (!symtable_raise_if_not_coroutine(st, ASYNC_FOR_OUTSIDE_ASYNC_FUNC, LOCATION(s))) {
VISIT_QUIT(st, 0); VISIT_QUIT(st, 0);
} }
VISIT(st, expr, s->v.AsyncFor.target); VISIT(st, expr, s->v.AsyncFor.target);
@ -2279,6 +2287,20 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
if (!symtable_raise_if_annotation_block(st, "await expression", e)) { if (!symtable_raise_if_annotation_block(st, "await expression", e)) {
VISIT_QUIT(st, 0); VISIT_QUIT(st, 0);
} }
if (!allows_top_level_await(st)) {
if (!_PyST_IsFunctionLike(st->st_cur)) {
PyErr_SetString(PyExc_SyntaxError,
"'await' outside function");
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
VISIT_QUIT(st, 0);
}
if (!IS_ASYNC_DEF(st) && st->st_cur->ste_comprehension == NoComprehension) {
PyErr_SetString(PyExc_SyntaxError,
"'await' outside async function");
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
VISIT_QUIT(st, 0);
}
}
VISIT(st, expr, e->v.Await.value); VISIT(st, expr, e->v.Await.value);
st->st_cur->ste_coroutine = 1; st->st_cur->ste_coroutine = 1;
break; break;
@ -2798,6 +2820,16 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
if (!symtable_exit_block(st)) { if (!symtable_exit_block(st)) {
return 0; return 0;
} }
if (is_async &&
!IS_ASYNC_DEF(st) &&
st->st_cur->ste_comprehension == NoComprehension &&
!allows_top_level_await(st))
{
PyErr_SetString(PyExc_SyntaxError, "asynchronous comprehension outside of "
"an asynchronous function");
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
return 0;
}
if (is_async) { if (is_async) {
st->st_cur->ste_coroutine = 1; st->st_cur->ste_coroutine = 1;
} }