Commit ce6a0704 authored by Zackery Spytz's avatar Zackery Spytz Committed by Serhiy Storchaka

bpo-34880: Add the LOAD_ASSERTION_ERROR opcode. (GH-15073)

Fix assert statement misbehavior if AssertionError is shadowed.
parent 8371799e
......@@ -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
......
......@@ -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`.)
......@@ -53,6 +53,7 @@ extern "C" {
#define LOAD_BUILD_CLASS 71
#define YIELD_FROM 72
#define GET_AWAITABLE 73
#define LOAD_ASSERTION_ERROR 74
#define INPLACE_LSHIFT 75
#define INPLACE_RSHIFT 76
#define INPLACE_AND 77
......
......@@ -271,6 +271,7 @@ _code_type = type(_write_atomic.__code__)
# 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
......@@ -279,7 +280,7 @@ _code_type = type(_write_atomic.__code__)
# 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__'
......
......@@ -109,7 +109,7 @@ def_op('PRINT_EXPR', 70)
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)
......
......@@ -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
......
......@@ -1285,6 +1285,22 @@ class ExceptionTests(unittest.TestCase):
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):
......
The :keyword:`assert` statement now works properly if the
:exc:`AssertionError` exception is being shadowed.
Patch by Zackery Spytz.
......@@ -2242,6 +2242,13 @@ main_loop:
}
}
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__);
......
......@@ -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;
}
......@@ -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)
{
......@@ -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);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -73,7 +73,7 @@ static void *opcode_targets[256] = {
&&TARGET_LOAD_BUILD_CLASS,
&&TARGET_YIELD_FROM,
&&TARGET_GET_AWAITABLE,
&&_unknown_opcode,
&&TARGET_LOAD_ASSERTION_ERROR,
&&TARGET_INPLACE_LSHIFT,
&&TARGET_INPLACE_RSHIFT,
&&TARGET_INPLACE_AND,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment