Skip to content

bpo-34880: assert statement misbehavior if AssertionError is shadowed #15073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,14 @@ iterations of the loop.
from the block stack.


.. opcode:: LOAD_ASSERTION_ERROR

Pushes :exc:`AssertionError` onto the stack. Used by the :keyword:`assert`
statement.

.. versionadded:: 3.9


.. opcode:: LOAD_BUILD_CLASS

Pushes :func:`builtins.__build_class__` onto the stack. It is later called
Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,12 @@ Changes in the Python API

* The :mod:`venv` activation scripts no longer special-case when
``__VENV_PROMPT__`` is set to ``""``.


CPython bytecode changes
------------------------

* The :opcode:`LOAD_ASSERTION_ERROR` opcode was added for handling the
:keyword:`assert` statement. Previously, the assert statement would not work
correctly if the :exc:`AssertionError` exception was being shadowed.
(Contributed by Zackery Spytz in :issue:`34880`.)
1 change: 1 addition & 0 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.8b2 3412 (Swap the position of positional args and positional
# only args in ast.arguments #37593)
# Python 3.8b4 3413 (Fix "break" and "continue" in "finally" #37830)
# Python 3.9a0 3420 (add LOAD_ASSERTION_ERROR #34880)
#
# MAGIC must change whenever the bytecode emitted by the compiler may no
# longer be understood by older implementations of the eval loop (usually
Expand All @@ -279,7 +280,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3413).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3420).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

_PYCACHE = '__pycache__'
Expand Down
2 changes: 1 addition & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def jabs_op(name, op):
def_op('LOAD_BUILD_CLASS', 71)
def_op('YIELD_FROM', 72)
def_op('GET_AWAITABLE', 73)

def_op('LOAD_ASSERTION_ERROR', 74)
def_op('INPLACE_LSHIFT', 75)
def_op('INPLACE_RSHIFT', 76)
def_op('INPLACE_AND', 77)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def bug1333982(x=[]):
dis_bug1333982 = """\
%3d 0 LOAD_CONST 1 (0)
2 POP_JUMP_IF_TRUE 26
4 LOAD_GLOBAL 0 (AssertionError)
4 LOAD_ASSERTION_ERROR
6 LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>)
8 LOAD_CONST 3 ('bug1333982.<locals>.<listcomp>')
10 MAKE_FUNCTION 0
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,22 @@ def g():
next(i)
next(i)

@unittest.skipUnless(__debug__, "Won't work if __debug__ is False")
def test_assert_shadowing(self):
# Shadowing AssertionError would cause the assert statement to
# misbehave.
global AssertionError
AssertionError = TypeError
try:
assert False, 'hello'
except BaseException as e:
del AssertionError
self.assertIsInstance(e, AssertionError)
self.assertEqual(str(e), 'hello')
else:
del AssertionError
self.fail('Expected exception')


class ImportErrorTests(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :keyword:`assert` statement now works properly if the
:exc:`AssertionError` exception is being shadowed.
Patch by Zackery Spytz.
7 changes: 7 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,13 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
}
}

case TARGET(LOAD_ASSERTION_ERROR): {
PyObject *value = PyExc_AssertionError;
Py_INCREF(value);
PUSH(value);
FAST_DISPATCH();
}

case TARGET(LOAD_BUILD_CLASS): {
_Py_IDENTIFIER(__build_class__);

Expand Down
10 changes: 3 additions & 7 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,8 @@ stack_effect(int opcode, int oparg, int jump)
return (oparg & FVS_MASK) == FVS_HAVE_SPEC ? -1 : 0;
case LOAD_METHOD:
return 1;
case LOAD_ASSERTION_ERROR:
return 1;
default:
return PY_INVALID_STACK_EFFECT;
}
Expand Down Expand Up @@ -3253,16 +3255,10 @@ compiler_from_import(struct compiler *c, stmt_ty s)
static int
compiler_assert(struct compiler *c, stmt_ty s)
{
static PyObject *assertion_error = NULL;
basicblock *end;

if (c->c_optimize)
return 1;
if (assertion_error == NULL) {
assertion_error = PyUnicode_InternFromString("AssertionError");
if (assertion_error == NULL)
return 0;
}
if (s->v.Assert.test->kind == Tuple_kind &&
asdl_seq_LEN(s->v.Assert.test->v.Tuple.elts) > 0)
{
Expand All @@ -3277,7 +3273,7 @@ compiler_assert(struct compiler *c, stmt_ty s)
return 0;
if (!compiler_jump_if(c, s->v.Assert.test, end, 1))
return 0;
ADDOP_O(c, LOAD_GLOBAL, assertion_error, names);
ADDOP(c, LOAD_ASSERTION_ERROR);
if (s->v.Assert.msg) {
VISIT(c, expr, s->v.Assert.msg);
ADDOP_I(c, CALL_FUNCTION, 1);
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy