Skip to content

Commit 2762c6c

Browse files
gh-121637: Syntax error for optimized-away incorrect await (#121656)
Co-authored-by: Bénédikt Tran <[email protected]>
1 parent 69f2dc5 commit 2762c6c

File tree

5 files changed

+107
-55
lines changed

5 files changed

+107
-55
lines changed

Doc/whatsnew/3.14.rst

+5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ New Features
7575
Other Language Changes
7676
======================
7777

78+
* Incorrect usage of :keyword:`await` and asynchronous comprehensions
79+
is now detected even if the code is optimized away by the :option:`-O`
80+
command line option. For example, ``python -O -c 'assert await 1'``
81+
now produces a :exc:`SyntaxError`. (Contributed by Jelle Zijlstra in :gh:`121637`.)
82+
7883
* Added class methods :meth:`float.from_number` and :meth:`complex.from_number`
7984
to convert a number to :class:`float` or :class:`complex` type correspondingly.
8085
They raise an error if the argument is a string.

Lib/test/test_builtin.py

+45-25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import random
1717
import re
1818
import sys
19+
import textwrap
1920
import traceback
2021
import types
2122
import typing
@@ -412,7 +413,7 @@ def test_compile_top_level_await_no_coro(self):
412413
"socket.accept is broken"
413414
)
414415
def test_compile_top_level_await(self):
415-
"""Test whether code some top level await can be compiled.
416+
"""Test whether code with top level await can be compiled.
416417
417418
Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag
418419
set, and make sure the generated code object has the CO_COROUTINE flag
@@ -426,6 +427,7 @@ async def arange(n):
426427
yield i
427428

428429
modes = ('single', 'exec')
430+
optimizations = (-1, 0, 1, 2)
429431
code_samples = [
430432
'''a = await asyncio.sleep(0, result=1)''',
431433
'''async for i in arange(1):
@@ -438,34 +440,52 @@ async def arange(n):
438440
'''a = [x async for x in arange(2) async for x in arange(2)][1]''',
439441
'''a = [x async for x in (x async for x in arange(5))][1]''',
440442
'''a, = [1 for x in {x async for x in arange(1)}]''',
441-
'''a = [await asyncio.sleep(0, x) async for x in arange(2)][1]'''
443+
'''a = [await asyncio.sleep(0, x) async for x in arange(2)][1]''',
444+
# gh-121637: Make sure we correctly handle the case where the
445+
# async code is optimized away
446+
'''assert not await asyncio.sleep(0); a = 1''',
447+
'''assert [x async for x in arange(1)]; a = 1''',
448+
'''assert {x async for x in arange(1)}; a = 1''',
449+
'''assert {x: x async for x in arange(1)}; a = 1''',
450+
'''
451+
if (a := 1) and __debug__:
452+
async with asyncio.Lock() as l:
453+
pass
454+
''',
455+
'''
456+
if (a := 1) and __debug__:
457+
async for x in arange(2):
458+
pass
459+
''',
442460
]
443461
policy = maybe_get_event_loop_policy()
444462
try:
445-
for mode, code_sample in product(modes, code_samples):
446-
source = dedent(code_sample)
447-
with self.assertRaises(
448-
SyntaxError, msg=f"source={source} mode={mode}"):
449-
compile(source, '?', mode)
450-
451-
co = compile(source,
452-
'?',
453-
mode,
454-
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
455-
456-
self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
457-
msg=f"source={source} mode={mode}")
463+
for mode, code_sample, optimize in product(modes, code_samples, optimizations):
464+
with self.subTest(mode=mode, code_sample=code_sample, optimize=optimize):
465+
source = dedent(code_sample)
466+
with self.assertRaises(
467+
SyntaxError, msg=f"source={source} mode={mode}"):
468+
compile(source, '?', mode, optimize=optimize)
458469

459-
# test we can create and advance a function type
460-
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
461-
async_f = FunctionType(co, globals_)
462-
asyncio.run(async_f())
463-
self.assertEqual(globals_['a'], 1)
464-
465-
# test we can await-eval,
466-
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
467-
asyncio.run(eval(co, globals_))
468-
self.assertEqual(globals_['a'], 1)
470+
co = compile(source,
471+
'?',
472+
mode,
473+
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
474+
optimize=optimize)
475+
476+
self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
477+
msg=f"source={source} mode={mode}")
478+
479+
# test we can create and advance a function type
480+
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
481+
async_f = FunctionType(co, globals_)
482+
asyncio.run(async_f())
483+
self.assertEqual(globals_['a'], 1)
484+
485+
# test we can await-eval,
486+
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
487+
asyncio.run(eval(co, globals_))
488+
self.assertEqual(globals_['a'], 1)
469489
finally:
470490
asyncio.set_event_loop_policy(policy)
471491

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

Python/compile.c

+14-23
Original file line numberDiff line numberDiff line change
@@ -5675,14 +5675,16 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
56755675
PyCodeObject *co = NULL;
56765676
inlined_comprehension_state inline_state = {NULL, NULL, NULL, NO_LABEL, NO_LABEL};
56775677
comprehension_ty outermost;
5678+
#ifndef NDEBUG
56785679
int scope_type = c->u->u_scope_type;
56795680
int is_top_level_await = IS_TOP_LEVEL_AWAIT(c);
5681+
#endif
56805682
PySTEntryObject *entry = _PySymtable_Lookup(SYMTABLE(c), (void *)e);
56815683
if (entry == NULL) {
56825684
goto error;
56835685
}
56845686
int is_inlined = entry->ste_comp_inlined;
5685-
int is_async_generator = entry->ste_coroutine;
5687+
int is_async_comprehension = entry->ste_coroutine;
56865688

56875689
location loc = LOC(e);
56885690

@@ -5697,22 +5699,17 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
56975699
}
56985700
else {
56995701
if (compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION,
5700-
(void *)e, e->lineno, NULL) < 0)
5701-
{
5702+
(void *)e, e->lineno, NULL) < 0) {
57025703
goto error;
57035704
}
57045705
}
57055706
Py_CLEAR(entry);
57065707

5707-
if (is_async_generator && type != COMP_GENEXP &&
5708-
scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
5709-
scope_type != COMPILER_SCOPE_COMPREHENSION &&
5710-
!is_top_level_await)
5711-
{
5712-
compiler_error(c, loc, "asynchronous comprehension outside of "
5713-
"an asynchronous function");
5714-
goto error_in_scope;
5715-
}
5708+
assert (!is_async_comprehension ||
5709+
type == COMP_GENEXP ||
5710+
scope_type == COMPILER_SCOPE_ASYNC_FUNCTION ||
5711+
scope_type == COMPILER_SCOPE_COMPREHENSION ||
5712+
is_top_level_await);
57165713

57175714
if (type != COMP_GENEXP) {
57185715
int op;
@@ -5777,7 +5774,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
57775774

57785775
ADDOP_I(c, loc, CALL, 0);
57795776

5780-
if (is_async_generator && type != COMP_GENEXP) {
5777+
if (is_async_comprehension && type != COMP_GENEXP) {
57815778
ADDOP_I(c, loc, GET_AWAITABLE, 0);
57825779
ADDOP_LOAD_CONST(c, loc, Py_None);
57835780
ADD_YIELD_FROM(c, loc, 1);
@@ -6138,16 +6135,10 @@ compiler_visit_expr(struct compiler *c, expr_ty e)
61386135
ADD_YIELD_FROM(c, loc, 0);
61396136
break;
61406137
case Await_kind:
6141-
if (!IS_TOP_LEVEL_AWAIT(c)){
6142-
if (!_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) {
6143-
return compiler_error(c, loc, "'await' outside function");
6144-
}
6145-
6146-
if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
6147-
c->u->u_scope_type != COMPILER_SCOPE_COMPREHENSION) {
6148-
return compiler_error(c, loc, "'await' outside async function");
6149-
}
6150-
}
6138+
assert(IS_TOP_LEVEL_AWAIT(c) || (_PyST_IsFunctionLike(SYMTABLE_ENTRY(c)) && (
6139+
c->u->u_scope_type == COMPILER_SCOPE_ASYNC_FUNCTION ||
6140+
c->u->u_scope_type == COMPILER_SCOPE_COMPREHENSION
6141+
)));
61516142

61526143
VISIT(c, expr, e->v.Await.value);
61536144
ADDOP_I(c, loc, GET_AWAITABLE, 0);

Python/symtable.c

+39-7
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@
7070
#define DUPLICATE_TYPE_PARAM \
7171
"duplicate type parameter '%U'"
7272

73-
#define ASYNC_WITH_OUTISDE_ASYNC_FUNC \
73+
#define ASYNC_WITH_OUTSIDE_ASYNC_FUNC \
7474
"'async with' outside async function"
7575

76-
#define ASYNC_FOR_OUTISDE_ASYNC_FUNC \
76+
#define ASYNC_FOR_OUTSIDE_ASYNC_FUNC \
7777
"'async for' outside async function"
7878

7979
#define LOCATION(x) SRC_LOCATION_FROM_AST(x)
@@ -82,6 +82,8 @@
8282
PyErr_RangedSyntaxLocationObject((FNAME), \
8383
(L).lineno, (L).col_offset + 1, (L).end_lineno, (L).end_col_offset + 1)
8484

85+
#define IS_ASYNC_DEF(st) ((st)->st_cur->ste_type == FunctionBlock && (st)->st_cur->ste_coroutine)
86+
8587
static PySTEntryObject *
8688
ste_new(struct symtable *st, identifier name, _Py_block_ty block,
8789
void *key, _Py_SourceLocation loc)
@@ -1660,12 +1662,18 @@ check_import_from(struct symtable *st, stmt_ty s)
16601662
return 1;
16611663
}
16621664

1665+
static bool
1666+
allows_top_level_await(struct symtable *st)
1667+
{
1668+
return (st->st_future->ff_features & PyCF_ALLOW_TOP_LEVEL_AWAIT) &&
1669+
st->st_cur->ste_type == ModuleBlock;
1670+
}
1671+
1672+
16631673
static void
16641674
maybe_set_ste_coroutine_for_module(struct symtable *st, stmt_ty s)
16651675
{
1666-
if ((st->st_future->ff_features & PyCF_ALLOW_TOP_LEVEL_AWAIT) &&
1667-
(st->st_cur->ste_type == ModuleBlock))
1668-
{
1676+
if (allows_top_level_await(st)) {
16691677
st->st_cur->ste_coroutine = 1;
16701678
}
16711679
}
@@ -2054,15 +2062,15 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
20542062
}
20552063
case AsyncWith_kind:
20562064
maybe_set_ste_coroutine_for_module(st, s);
2057-
if (!symtable_raise_if_not_coroutine(st, ASYNC_WITH_OUTISDE_ASYNC_FUNC, LOCATION(s))) {
2065+
if (!symtable_raise_if_not_coroutine(st, ASYNC_WITH_OUTSIDE_ASYNC_FUNC, LOCATION(s))) {
20582066
VISIT_QUIT(st, 0);
20592067
}
20602068
VISIT_SEQ(st, withitem, s->v.AsyncWith.items);
20612069
VISIT_SEQ(st, stmt, s->v.AsyncWith.body);
20622070
break;
20632071
case AsyncFor_kind:
20642072
maybe_set_ste_coroutine_for_module(st, s);
2065-
if (!symtable_raise_if_not_coroutine(st, ASYNC_FOR_OUTISDE_ASYNC_FUNC, LOCATION(s))) {
2073+
if (!symtable_raise_if_not_coroutine(st, ASYNC_FOR_OUTSIDE_ASYNC_FUNC, LOCATION(s))) {
20662074
VISIT_QUIT(st, 0);
20672075
}
20682076
VISIT(st, expr, s->v.AsyncFor.target);
@@ -2279,6 +2287,20 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
22792287
if (!symtable_raise_if_annotation_block(st, "await expression", e)) {
22802288
VISIT_QUIT(st, 0);
22812289
}
2290+
if (!allows_top_level_await(st)) {
2291+
if (!_PyST_IsFunctionLike(st->st_cur)) {
2292+
PyErr_SetString(PyExc_SyntaxError,
2293+
"'await' outside function");
2294+
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
2295+
VISIT_QUIT(st, 0);
2296+
}
2297+
if (!IS_ASYNC_DEF(st) && st->st_cur->ste_comprehension == NoComprehension) {
2298+
PyErr_SetString(PyExc_SyntaxError,
2299+
"'await' outside async function");
2300+
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
2301+
VISIT_QUIT(st, 0);
2302+
}
2303+
}
22822304
VISIT(st, expr, e->v.Await.value);
22832305
st->st_cur->ste_coroutine = 1;
22842306
break;
@@ -2798,6 +2820,16 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
27982820
if (!symtable_exit_block(st)) {
27992821
return 0;
28002822
}
2823+
if (is_async &&
2824+
!IS_ASYNC_DEF(st) &&
2825+
st->st_cur->ste_comprehension == NoComprehension &&
2826+
!allows_top_level_await(st))
2827+
{
2828+
PyErr_SetString(PyExc_SyntaxError, "asynchronous comprehension outside of "
2829+
"an asynchronous function");
2830+
SET_ERROR_LOCATION(st->st_filename, LOCATION(e));
2831+
return 0;
2832+
}
28012833
if (is_async) {
28022834
st->st_cur->ste_coroutine = 1;
28032835
}

0 commit comments

Comments
 (0)