Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2762c6c

Browse filesBrowse files
gh-121637: Syntax error for optimized-away incorrect await (#121656)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 69f2dc5 commit 2762c6c
Copy full SHA for 2762c6c

File tree

Expand file treeCollapse file tree

5 files changed

+107
-55
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+107
-55
lines changed

‎Doc/whatsnew/3.14.rst

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.14.rst
+5Lines changed: 5 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Lib/test/test_builtin.py
+45-25Lines changed: 45 additions & 25 deletions
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

+4Lines changed: 4 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Python/compile.c
+14-23Lines changed: 14 additions & 23 deletions
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

Copy file name to clipboardExpand all lines: Python/symtable.c
+39-7Lines changed: 39 additions & 7 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.