diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index ca903d302bdd31..91cfca9f4b6226 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -56,6 +56,7 @@ "run_with_tz", "PGO", "missing_compiler_executable", "ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST", "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", + "repeat_cpython_adaptative", ] @@ -2118,3 +2119,27 @@ def clear_ignored_deprecations(*tokens: object) -> None: if warnings.filters != new_filters: warnings.filters[:] = new_filters warnings._filters_mutated() + + +def repeat_cpython_adaptative(f): + """Runs a test multiple times. First with PEP 659 adaptive opcodes, then + with specialized opcodes once the opcodes turn "hot". This catches bugs + related to specialized opcodes. + + See bpo-46465 for an example bug affecting only specialized opcodes, + due to a missing CHECK_EVAL_BREAKER. + """ + # _co_warmedup is only available in CPython + if check_impl_detail(cpython=True) and hasattr(f.__code__, "_co_warmedup"): + @functools.wraps(f) + def wrapper(self): + done = False + while not done: + is_warmedup = f.__code__._co_warmedup + if is_warmedup: + done = True + with self.subTest(cpython_is_warmedup=is_warmedup): + f(self) + return wrapper + else: + return f diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py index eebd2b610ce11f..195466a2ec9a47 100644 --- a/Lib/unittest/test/test_break.py +++ b/Lib/unittest/test/test_break.py @@ -6,6 +6,7 @@ import weakref import unittest +from test.support import repeat_cpython_adaptative @unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") @@ -23,7 +24,9 @@ def tearDown(self): unittest.signals._results = weakref.WeakKeyDictionary() unittest.signals._interrupt_handler = None - + # Tests both adaptive and specialized opcodes for proper + # CHECK_EVAL_BREAKER(). See bpo-46465 for an example bug. + @repeat_cpython_adaptative def testInstallHandler(self): default_handler = signal.getsignal(signal.SIGINT) unittest.installHandler() @@ -121,7 +124,9 @@ def test(result): self.assertTrue(result2.shouldStop) self.assertFalse(result3.shouldStop) - + # Tests both adaptive and specialized opcodes for proper + # CHECK_EVAL_BREAKER(). See bpo-46465 for an example bug. + @repeat_cpython_adaptative def testHandlerReplacedButCalled(self): # Can't use skipIf decorator because the signal handler may have # been changed after defining this method. diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-01-23-21-01-28.bpo-46465.z25Vbj.rst b/Misc/NEWS.d/next/Core and Builtins/2022-01-23-21-01-28.bpo-46465.z25Vbj.rst new file mode 100644 index 00000000000000..d0313cb78351f8 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-01-23-21-01-28.bpo-46465.z25Vbj.rst @@ -0,0 +1,2 @@ +Properly check for eval loop breaker in specialized ``CALL`` opcodes. Patch +by Victor Stinner and Ken Jin. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index a413b183be8edc..863ddd908a9d77 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1542,6 +1542,17 @@ code_getfreevars(PyCodeObject *code, void *closure) return _PyCode_GetFreevars(code); } +static PyObject * +code_iswarmedup(PyCodeObject *code, void *closure) +{ + if (code->co_quickened != NULL) { + Py_RETURN_TRUE; + } + else { + Py_RETURN_FALSE; + } +} + static PyGetSetDef code_getsetlist[] = { {"co_lnotab", (getter)code_getlnotab, NULL, NULL}, // The following old names are kept for backward compatibility. @@ -1549,6 +1560,7 @@ static PyGetSetDef code_getsetlist[] = { {"co_varnames", (getter)code_getvarnames, NULL, NULL}, {"co_cellvars", (getter)code_getcellvars, NULL, NULL}, {"co_freevars", (getter)code_getfreevars, NULL, NULL}, + {"_co_warmedup", (getter)code_iswarmedup, NULL, NULL}, {0} }; diff --git a/Python/ceval.c b/Python/ceval.c index 9aaddd99edacf7..6299212ae79406 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4752,6 +4752,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr if (res == NULL) { goto error; } + CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -4783,6 +4784,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr if (res == NULL) { goto error; } + CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -4822,6 +4824,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr */ goto error; } + CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -4881,6 +4884,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr if (res == NULL) { goto error; } + CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -4939,6 +4943,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr if (res == NULL) { goto error; } + CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -4972,6 +4977,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr if (res == NULL) { goto error; } + CHECK_EVAL_BREAKER(); DISPATCH(); }