Align canonical compiler fixtures and generator frame state with final 3.15

Migrate only CI-red canonical test modules and their required syntax-warning fixture through update_lib from CPython d220409a. Preserve generated migration marker output. Clear created generator frames using actual owner state, publish closed state before warning callbacks, and expose the final read-only gi_state contract.

Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
dot committed 2026-10-12 22:58:46 +09:00
1 parent a2dacaf4bd
commit 476bb74f3c
10 files changed
+1270 -544

No files matched your search

+22 -13
View File
@@ -87,7 +87,7 @@ cellvars: ()
freevars: ()
nlocals: 0
flags: 67108867
consts: ("'doc string'", 'None')
consts: ("'doc string'",)
>>> def keywordonly_args(a,b,*,k1):
... return a,b,k1
@@ -161,7 +161,7 @@ cellvars: ()
freevars: ()
nlocals: 3
flags: 67108995
consts: ("'This is a docstring from async function'", 'None')
consts: ("'This is a docstring from async function'",)
>>> def no_docstring(x, y, z):
... return x + "hello" + y + z + "world"
@@ -423,14 +423,6 @@ class CodeTest(unittest.TestCase):
new_code = code = func.__code__.replace(co_linetable=b'')
self.assertEqual(list(new_code.co_lines()), [])
@unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12)
def test_co_lnotab_is_deprecated(self): # TODO: remove in 3.14
def func():
pass
with self.assertWarns(DeprecationWarning):
func.__code__.co_lnotab
@unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing')
def test_returns_only_none(self):
value = True
@@ -540,7 +532,7 @@ class CodeTest(unittest.TestCase):
],
[
("PUSH_EXC_INFO", None),
("LOAD_CONST", None), # artificial 'None'
("LOAD_COMMON_CONSTANT", None), # artificial 'None'
("STORE_NAME", "e"), # XX: we know the location for this
("DELETE_NAME", "e"),
("RERAISE", 1),
@@ -1165,6 +1157,22 @@ class CodeTest(unittest.TestCase):
with self.assertRaises(Exception):
_testinternalcapi.verify_stateless_code(func)
def test_code_richcompare_raise_exception(self):
class BadStr(str):
def __eq__(self, _):
raise RuntimeError("Poison!")
__hash__ = str.__hash__
c1 = compile("pass", "test", "exec")
c2 = c1.replace(co_name=BadStr("poison"))
c3 = compile("pass", "poison", "exec")
with self.assertRaises(RuntimeError):
c2 == c3
@unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12)
def test_co_lnotab_is_deprecated(self):
return super().test_co_lnotab_is_deprecated()
def isinterned(s):
return s is sys.intern(('_' + s + '_')[1:-1])
@@ -1438,6 +1446,7 @@ class CodeLocationTest(unittest.TestCase):
co_code=bytes(
[
dis.opmap["RESUME"], 0,
dis.opmap["CACHE"], 0,
dis.opmap["LOAD_COMMON_CONSTANT"], 0,
dis.opmap["RAISE_VARARGS"], 1,
]
@@ -1446,7 +1455,7 @@ class CodeLocationTest(unittest.TestCase):
[
(1 << 7)
| (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
| (3 - 1),
| (4 - 1),
0,
]
),
@@ -1454,7 +1463,7 @@ class CodeLocationTest(unittest.TestCase):
self.assertRaises(AssertionError, f)
self.assertEqual(
list(f.__code__.co_positions()),
3 * [(42, 42, None, None)],
4 * [(42, 42, None, None)],
)
@cpython_only
+51 -10
View File
@@ -724,8 +724,7 @@ class TestSpecifics(unittest.TestCase):
@support.cpython_only
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
@support.run_with_limited_c_stack(
100_000 if sys.platform == "android" else 500_000)
@support.skip_if_huge_c_stack(100_000 if sys.platform == "android" else 500_000)
@support.skip_emscripten_stack_overflow()
def test_compiler_recursion_limit(self):
# Compiler frames are small
@@ -841,8 +840,10 @@ class TestSpecifics(unittest.TestCase):
else:
return "unused"
self.assertEqual(f.__code__.co_consts,
(f.__doc__, "used"))
if f.__doc__ is None:
self.assertEqual(f.__code__.co_consts, (True, "used"))
else:
self.assertEqual(f.__code__.co_consts, (f.__doc__, "used"))
@support.cpython_only
def test_remove_unused_consts_no_docstring(self):
@@ -887,7 +888,11 @@ class TestSpecifics(unittest.TestCase):
def f1():
"docstring"
return 42
self.assertEqual(f1.__code__.co_consts, (f1.__doc__,))
if f1.__doc__ is None:
self.assertEqual(f1.__code__.co_consts, (42,))
else:
self.assertEqual(f1.__code__.co_consts, (f1.__doc__,))
# This is a regression test for a CPython specific peephole optimizer
# implementation bug present in a few releases. It's assertion verifies
@@ -1294,7 +1299,7 @@ class TestSpecifics(unittest.TestCase):
x
in
y)
genexp_lines = [0, 4, 2, 0, 4]
genexp_lines = [4, 0, 4, 2, 0, 4]
genexp_code = return_genexp.__code__.co_consts[0]
code_lines = self.get_code_lines(genexp_code)
@@ -1629,6 +1634,17 @@ class TestSpecifics(unittest.TestCase):
def f():
a if (1 if b else c) else d
def test_lineno_propagation_empty_blocks(self):
# Smoke test. See gh-138714.
def f():
while name:
try:
break
except:
pass
else:
1 if 1 else 1
def test_global_declaration_in_except_used_in_else(self):
# See gh-111123
code = textwrap.dedent("""\
@@ -1733,6 +1749,30 @@ class TestSpecifics(unittest.TestCase):
self.assertEqual(wm.category, SyntaxWarning)
self.assertIn("\"is\" with 'int' literal", str(wm.message))
def test_filter_syntax_warnings_by_module(self):
filename = support.findfile('test_import/data/syntax_warnings.py')
with open(filename, 'rb') as f:
source = f.read()
module_re = r'test\.test_import\.data\.syntax_warnings\z'
with warnings.catch_warnings(record=True) as wlog:
warnings.simplefilter('error')
warnings.filterwarnings('always', module=module_re)
compile(source, filename, 'exec')
self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
for wm in wlog:
self.assertEqual(wm.filename, filename)
self.assertIs(wm.category, SyntaxWarning)
with warnings.catch_warnings(record=True) as wlog:
warnings.simplefilter('error')
warnings.filterwarnings('always', module=r'package\.module\z')
warnings.filterwarnings('error', module=module_re)
compile(source, filename, 'exec', module='package.module')
self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
for wm in wlog:
self.assertEqual(wm.filename, filename)
self.assertIs(wm.category, SyntaxWarning)
@support.subTests('src', [
textwrap.dedent("""
def f():
@@ -2419,8 +2459,8 @@ class TestSourcePositions(unittest.TestCase):
for i, pos in enumerate(positions):
with self.subTest(i=i, pos=pos):
start_line, end_line, start_col, end_col = pos
if i == 0 and start_col == end_col == 0:
# ignore the RESUME in the beginning
if i <= 1:
# ignore the RESUME and CACHE in the beginning
continue
self.assertEqual(start_line, 1)
self.assertEqual(end_line, 1)
@@ -2446,12 +2486,13 @@ class TestSourcePositions(unittest.TestCase):
start_line, end_line, _, _ = instr.positions
self.assertEqual(start_line, end_line)
# Expect four `LOAD_CONST None` instructions:
# Expect four `None`-loading instructions:
# three for the no-exception __exit__ call, and one for the return.
# They should all have the locations of the context manager ('xyz').
load_none = [instr for instr in dis.get_instructions(f) if
instr.opname == 'LOAD_CONST' and instr.argval is None]
instr.opname in ('LOAD_CONST', 'LOAD_COMMON_CONSTANT')
and instr.argval is None]
return_value = [instr for instr in dis.get_instructions(f) if
instr.opname == 'RETURN_VALUE']
+350 -310
View File
File diff suppressed because it is too large. Load diff
+22 -24
View File
@@ -300,7 +300,7 @@ class GeneratorTest(unittest.TestCase):
def __iter__(self):
return Iterator()
self.assertEqual([1, 2], list(i for i in C()))
self.assertEqual([1,2], list(i for i in C()))
def test_close_clears_frame(self):
# gh-142766: Test that closing a generator clears its frame
@@ -357,26 +357,25 @@ class ModifyUnderlyingIterableTest(unittest.TestCase):
yield x
return gen(range(10))
def process_tests(self, get_generator, is_expr):
err_iterator = "'.*' object is not an iterator"
err_iterable = "'.*' object is not iterable"
for obj in self.iterables:
g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj, obj=obj):
if is_expr:
self.assertRaisesRegex(TypeError, err_iterator, list, g_obj)
else:
def process_tests(self, get_generator, changes_iterable):
if changes_iterable:
for obj in self.iterables:
g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj, obj=obj):
self.assertListEqual(list(g_obj), list(obj))
g_iter = get_generator(iter(obj))
with self.subTest(g_iter=g_iter, obj=obj):
self.assertListEqual(list(g_iter), list(obj))
g_iter = get_generator(iter(obj))
with self.subTest(g_iter=g_iter, obj=obj):
self.assertListEqual(list(g_iter), list(obj))
err_regex = "'.*' object is not iterable"
for obj in self.non_iterables:
g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj):
err = err_iterator if is_expr else err_iterable
self.assertRaisesRegex(TypeError, err, list, g_obj)
if changes_iterable:
self.assertRaisesRegex(TypeError, err_regex, list, g_obj)
else:
next(g_obj)
def test_modify_f_locals(self):
def modify_f_locals(g, local, obj):
@@ -389,22 +388,22 @@ class ModifyUnderlyingIterableTest(unittest.TestCase):
def get_generator_genfunc(obj):
return modify_f_locals(self.genfunc(), 'it', obj)
self.process_tests(get_generator_genexpr, True)
self.process_tests(get_generator_genfunc, False)
self.process_tests(get_generator_genexpr, False)
self.process_tests(get_generator_genfunc, True)
def test_new_gen_from_gi_code(self):
def new_gen_from_gi_code(g, obj):
generator_func = types.FunctionType(g.gi_code, {})
return generator_func(obj)
def get_generator_genexpr(obj):
return new_gen_from_gi_code(self.genexpr(), obj)
for obj in self.non_iterables:
with self.assertRaises(TypeError):
new_gen_from_gi_code(self.genexpr(), obj)
def get_generator_genfunc(obj):
return new_gen_from_gi_code(self.genfunc(), obj)
self.process_tests(get_generator_genexpr, True)
self.process_tests(get_generator_genfunc, False)
self.process_tests(get_generator_genfunc, True)
class ExceptionTest(unittest.TestCase):
@@ -1367,7 +1366,7 @@ From the Iterators list, about the types of these things.
>>> type(i)
<class 'generator'>
>>> [s for s in dir(i) if not s.startswith('_')]
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw']
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_state', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw']
>>> from test.support import HAVE_DOCSTRINGS
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
Implement next(self).
@@ -2878,8 +2877,7 @@ __test__ = {"tut": tutorial_tests,
}
def load_tests(loader, tests, pattern):
from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON
tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # TODO: RUSTPYTHON
tests.addTest(doctest.DocTestSuite())
return tests
+21
View File
@@ -0,0 +1,21 @@
# Syntax warnings emitted in different parts of the Python compiler.
# Parser/lexer/lexer.c
x = 1or 0 # line 4
# Parser/tokenizer/helpers.c
'\z' # line 7
# Parser/string_parser.c
'\400' # line 10
# _PyCompile_Warn() in Python/codegen.c
assert(x, 'message') # line 13
x is 1 # line 14
# _PyErr_EmitSyntaxWarning() in Python/ast_preprocess.c
def f():
try:
pass
finally:
return 42 # line 21
+441 -115
View File
@@ -12,16 +12,7 @@ import types
import unittest
import test.support
from test.support import requires_specialization_ft, script_helper
try:
import _testcapi
except ImportError:
_testcapi = None
try:
import _testinternalcapi
except ImportError:
_testinternalcapi = None
from test.support import import_helper, requires_specialization, script_helper
PAIR = (0,1)
@@ -202,13 +193,10 @@ INSTRUMENTED_EVENTS = [
(E.BRANCH, "branch"),
]
EXCEPT_EVENTS = [
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + [
(E.RAISE, "raise"),
(E.PY_UNWIND, "unwind"),
(E.EXCEPTION_HANDLED, "exception_handled"),
]
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
(E.PY_UNWIND, "unwind"),
(E.C_RAISE, "c_raise"),
(E.C_RETURN, "c_return"),
]
@@ -744,18 +732,6 @@ class TestDisable(MonitoringTestBase, unittest.TestCase):
sys.monitoring.register_callback(TEST_TOOL, event, None)
def test_disable_illegal_events(self):
for event, name in EXCEPT_EVENTS:
try:
counter = CounterWithDisable()
counter.disable = True
sys.monitoring.register_callback(TEST_TOOL, event, counter)
sys.monitoring.set_events(TEST_TOOL, event)
with self.assertRaises(ValueError):
self.raise_handle_reraise()
finally:
sys.monitoring.set_events(TEST_TOOL, 0)
sys.monitoring.register_callback(TEST_TOOL, event, None)
class ExceptionRecorder:
@@ -879,7 +855,6 @@ class ExceptionMonitoringTest(CheckEvents):
self.check_events(func1, [("raise", KeyError)])
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
def test_implicit_stop_iteration(self):
"""Generators are documented as raising a StopIteration
when they terminate.
@@ -889,6 +864,8 @@ class ExceptionMonitoringTest(CheckEvents):
This test checks that both paths record an equivalent event.
"""
_testinternalcapi = import_helper.import_module("_testinternalcapi")
def gen():
yield 1
return 2
@@ -1054,9 +1031,10 @@ class ExceptionMonitoringTest(CheckEvents):
)
self.assertEqual(events[0], ("throw", IndexError))
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
@requires_specialization_ft
@requires_specialization
def test_no_unwind_for_shim_frame(self):
_testinternalcapi = import_helper.import_module("_testinternalcapi")
class ValueErrorRaiser:
def __init__(self):
raise ValueError()
@@ -1222,19 +1200,20 @@ class TestLineAndInstructionEvents(CheckEvents):
line3 = 3
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10),
('line', 'func1', 1),
('instruction', 'func1', 2),
('instruction', 'func1', 4),
('line', 'func1', 2),
('instruction', 'func1', 6),
('instruction', 'func1', 8),
('line', 'func1', 3),
('instruction', 'func1', 10),
('instruction', 'func1', 12),
('instruction', 'func1', 14),
('instruction', 'func1', 16),
('line', 'get_events', 11)])
("line", "get_events", 10),
("line", "func1", 1),
("instruction", "func1", 4),
("instruction", "func1", 6),
("line", "func1", 2),
("instruction", "func1", 8),
("instruction", "func1", 10),
("line", "func1", 3),
("instruction", "func1", 12),
("instruction", "func1", 14),
("instruction", "func1", 16),
("instruction", "func1", 18),
("line", "get_events", 11),
])
def test_c_call(self):
@@ -1244,22 +1223,23 @@ class TestLineAndInstructionEvents(CheckEvents):
line3 = 3
self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10),
('line', 'func2', 1),
('instruction', 'func2', 2),
('instruction', 'func2', 4),
('line', 'func2', 2),
('instruction', 'func2', 6),
('instruction', 'func2', 8),
('instruction', 'func2', 28),
('instruction', 'func2', 30),
('instruction', 'func2', 38),
('line', 'func2', 3),
('instruction', 'func2', 40),
('instruction', 'func2', 42),
('instruction', 'func2', 44),
('instruction', 'func2', 46),
('line', 'get_events', 11)])
("line", "get_events", 10),
("line", "func2", 1),
("instruction", "func2", 4),
("instruction", "func2", 6),
("line", "func2", 2),
("instruction", "func2", 8),
("instruction", "func2", 10),
("instruction", "func2", 30),
("instruction", "func2", 32),
("instruction", "func2", 40),
("line", "func2", 3),
("instruction", "func2", 42),
("instruction", "func2", 44),
("instruction", "func2", 46),
("instruction", "func2", 48),
("line", "get_events", 11),
])
def test_try_except(self):
@@ -1272,28 +1252,29 @@ class TestLineAndInstructionEvents(CheckEvents):
line = 6
self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10),
('line', 'func3', 1),
('instruction', 'func3', 2),
('line', 'func3', 2),
('instruction', 'func3', 4),
('instruction', 'func3', 6),
('line', 'func3', 3),
('instruction', 'func3', 8),
('instruction', 'func3', 18),
('instruction', 'func3', 20),
('line', 'func3', 4),
('instruction', 'func3', 22),
('line', 'func3', 5),
('instruction', 'func3', 24),
('instruction', 'func3', 26),
('instruction', 'func3', 28),
('line', 'func3', 6),
('instruction', 'func3', 30),
('instruction', 'func3', 32),
('instruction', 'func3', 34),
('instruction', 'func3', 36),
('line', 'get_events', 11)])
("line", "get_events", 10),
("line", "func3", 1),
("instruction", "func3", 4),
("line", "func3", 2),
("instruction", "func3", 6),
("instruction", "func3", 8),
("line", "func3", 3),
("instruction", "func3", 10),
("instruction", "func3", 20),
("instruction", "func3", 22),
("line", "func3", 4),
("instruction", "func3", 24),
("line", "func3", 5),
("instruction", "func3", 26),
("instruction", "func3", 28),
("instruction", "func3", 30),
("line", "func3", 6),
("instruction", "func3", 32),
("instruction", "func3", 34),
("instruction", "func3", 36),
("instruction", "func3", 38),
("line", "get_events", 11),
])
def test_with_restart(self):
def func1():
@@ -1304,16 +1285,16 @@ class TestLineAndInstructionEvents(CheckEvents):
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10),
('line', 'func1', 1),
('instruction', 'func1', 2),
('instruction', 'func1', 4),
('line', 'func1', 2),
('instruction', 'func1', 6),
('line', 'func1', 2),
('instruction', 'func1', 8),
('line', 'func1', 3),
('instruction', 'func1', 10),
('line', 'func1', 3),
('instruction', 'func1', 12),
('instruction', 'func1', 14),
('instruction', 'func1', 16),
('instruction', 'func1', 18),
('line', 'get_events', 11)])
sys.monitoring.restart_events()
@@ -1321,16 +1302,16 @@ class TestLineAndInstructionEvents(CheckEvents):
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10),
('line', 'func1', 1),
('instruction', 'func1', 2),
('instruction', 'func1', 4),
('line', 'func1', 2),
('instruction', 'func1', 6),
('line', 'func1', 2),
('instruction', 'func1', 8),
('line', 'func1', 3),
('instruction', 'func1', 10),
('line', 'func1', 3),
('instruction', 'func1', 12),
('instruction', 'func1', 14),
('instruction', 'func1', 16),
('instruction', 'func1', 18),
('line', 'get_events', 11)])
def test_turn_off_only_instruction(self):
@@ -1378,10 +1359,10 @@ class TestInstallIncrementally(MonitoringTestBase, unittest.TestCase):
line1 = 1
MUST_INCLUDE_LI = [
('instruction', 'func1', 2),
('line', 'func1', 2),
('instruction', 'func1', 4),
('instruction', 'func1', 6)]
('line', 'func1', 2),
('instruction', 'func1', 6),
('instruction', 'func1', 8)]
def test_line_then_instruction(self):
recorders = [ LineRecorder, InstructionRecorder ]
@@ -1398,11 +1379,11 @@ class TestInstallIncrementally(MonitoringTestBase, unittest.TestCase):
len(())
MUST_INCLUDE_CI = [
('instruction', 'func2', 2),
('instruction', 'func2', 4),
('call', 'func2', sys.monitoring.MISSING),
('call', 'len', ()),
('instruction', 'func2', 12),
('instruction', 'func2', 14)]
('instruction', 'func2', 14),
('instruction', 'func2', 16)]
@@ -1486,8 +1467,334 @@ class TestLocalEvents(MonitoringTestBase, unittest.TestCase):
('line', 'func3', 6)])
def test_set_non_local_event(self):
# C_RETURN/C_RAISE are ancillary (derived) events — not settable as local
with self.assertRaises(ValueError):
sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.RAISE)
sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.C_RETURN)
def test_local_reraise(self):
"""RERAISE fires as a local event only for the instrumented code object."""
def foo():
try:
raise RuntimeError("test")
except RuntimeError:
raise
def bar():
try:
raise RuntimeError("test")
except RuntimeError:
raise
events = set()
def callback(code, offset, exc):
events.add(code.co_name)
try:
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
try:
foo()
except RuntimeError:
pass
try:
bar() # should NOT trigger the callback
except RuntimeError:
pass
self.assertEqual(events, {'foo'})
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
def test_local_reraise_disable(self):
"""Returning DISABLE from a RERAISE callback disables it for that code object."""
call_count = 0
def foo():
try:
raise RuntimeError("test")
except RuntimeError:
raise
def callback(code, offset, exc):
nonlocal call_count
call_count += 1
return sys.monitoring.DISABLE
try:
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
try:
foo()
except RuntimeError:
pass
self.assertEqual(call_count, 1)
try:
foo()
except RuntimeError:
pass
self.assertEqual(call_count, 1) # not fired again — disabled
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
def test_local_py_throw(self):
"""PY_THROW fires as a local event only for the instrumented code object."""
def gen_foo():
yield 1
yield 2
def gen_bar():
yield 1
yield 2
events = []
def callback(code, offset, exc):
events.append(code.co_name)
try:
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW)
g = gen_foo()
next(g)
try:
g.throw(RuntimeError("test"))
except RuntimeError:
pass
h = gen_bar()
next(h)
try:
h.throw(RuntimeError("test")) # should NOT trigger the callback
except RuntimeError:
pass
self.assertEqual(events, ['gen_foo'])
finally:
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
def test_local_py_throw_disable(self):
"""Returning DISABLE from a PY_THROW callback disables it for that code object."""
call_count = 0
def gen_foo():
yield 1
yield 2
def callback(code, offset, exc):
nonlocal call_count
call_count += 1
return sys.monitoring.DISABLE
try:
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW)
g = gen_foo()
next(g)
try:
g.throw(RuntimeError("test"))
except RuntimeError:
pass
self.assertEqual(call_count, 1)
g2 = gen_foo()
next(g2)
try:
g2.throw(RuntimeError("test"))
except RuntimeError:
pass
self.assertEqual(call_count, 1) # not fired again — disabled
finally:
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
def test_local_raise(self):
"""RAISE fires as a local event only for the instrumented code object."""
def foo():
try:
raise RuntimeError("test")
except RuntimeError:
pass
def bar():
try:
raise RuntimeError("test")
except RuntimeError:
pass
events = []
def callback(code, offset, exc):
events.append(code.co_name)
try:
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
foo()
bar() # should NOT trigger the callback
self.assertEqual(events, ['foo'])
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
def test_local_raise_disable(self):
"""Returning DISABLE from a RAISE callback disables it for that code object."""
call_count = 0
def foo():
try:
raise RuntimeError("test")
except RuntimeError:
pass
def callback(code, offset, exc):
nonlocal call_count
call_count += 1
return sys.monitoring.DISABLE
try:
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
foo()
self.assertEqual(call_count, 1)
foo()
self.assertEqual(call_count, 1) # not fired again — disabled
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
def test_local_exception_handled(self):
"""EXCEPTION_HANDLED fires as a local event only for the instrumented code object."""
def foo():
try:
raise RuntimeError("test")
except RuntimeError:
pass
def bar():
try:
raise RuntimeError("test")
except RuntimeError:
pass
events = []
def callback(code, offset, exc):
events.append(code.co_name)
try:
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED)
foo()
bar() # should NOT trigger the callback
self.assertEqual(events, ['foo'])
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None)
def test_local_exception_handled_disable(self):
"""Returning DISABLE from an EXCEPTION_HANDLED callback disables it for that code object."""
call_count = 0
def foo():
try:
raise RuntimeError("test")
except RuntimeError:
pass
def callback(code, offset, exc):
nonlocal call_count
call_count += 1
return sys.monitoring.DISABLE
try:
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED)
foo()
self.assertEqual(call_count, 1)
foo()
self.assertEqual(call_count, 1) # not fired again — disabled
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None)
def test_local_py_unwind(self):
"""PY_UNWIND fires as a local event only for the instrumented code object."""
def foo():
raise RuntimeError("test")
def bar():
raise RuntimeError("test")
events = []
def callback(code, offset, exc):
events.append(code.co_name)
try:
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND)
try:
foo()
except RuntimeError:
pass
try:
bar() # should NOT trigger the callback
except RuntimeError:
pass
self.assertEqual(events, ['foo'])
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
def test_local_py_unwind_disable(self):
"""Returning DISABLE from a PY_UNWIND callback disables it for that code object."""
call_count = 0
def foo():
raise RuntimeError("test")
def callback(code, offset, exc):
nonlocal call_count
call_count += 1
return sys.monitoring.DISABLE
try:
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND)
try:
foo()
except RuntimeError:
pass
self.assertEqual(call_count, 1) # fired once
try:
foo()
except RuntimeError:
pass
self.assertEqual(call_count, 1) # not fired again — disabled by DISABLE return
finally:
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
def line_from_offset(code, offset):
for start, end, line in code.co_lines():
@@ -1617,11 +1924,11 @@ class TestBranchAndJumpEvents(CheckEvents):
('branch right', 'whilefunc', 1, 3)])
self.check_events(func, recorders = BRANCH_OFFSET_RECORDERS, expected = [
('branch left', 'func', 28, 32),
('branch right', 'func', 44, 58),
('branch left', 'func', 28, 32),
('branch left', 'func', 44, 50),
('branch right', 'func', 28, 70)])
('branch left', 'func', 32, 36),
('branch right', 'func', 48, 62),
('branch left', 'func', 32, 36),
('branch left', 'func', 48, 54),
('branch right', 'func', 32, 74)])
def test_except_star(self):
@@ -1648,8 +1955,8 @@ class TestBranchAndJumpEvents(CheckEvents):
('branch', 'func', 4, 4),
('line', 'func', 5),
('line', 'meth', 1),
('jump', 'func', 5, '[offset=120]'),
('branch', 'func', '[offset=124]', '[offset=130]'),
('jump', 'func', 5, '[offset=122]'),
('branch', 'func', '[offset=126]', '[offset=132]'),
('line', 'get_events', 11)])
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
@@ -1663,8 +1970,8 @@ class TestBranchAndJumpEvents(CheckEvents):
('line', 'func', 5),
('line', 'meth', 1),
('return', 'meth', None),
('jump', 'func', 5, '[offset=120]'),
('branch', 'func', '[offset=124]', '[offset=130]'),
('jump', 'func', 5, '[offset=122]'),
('branch', 'func', '[offset=126]', '[offset=132]'),
('return', 'func', None),
('line', 'get_events', 11)])
@@ -1676,8 +1983,8 @@ class TestBranchAndJumpEvents(CheckEvents):
n += 1
return None
in_loop = ('branch left', 'foo', 10, 16)
exit_loop = ('branch right', 'foo', 10, 40)
in_loop = ('branch left', 'foo', 12, 18)
exit_loop = ('branch right', 'foo', 12, 42)
self.check_events(foo, recorders = BRANCH_OFFSET_RECORDERS, expected = [
in_loop,
in_loop,
@@ -2150,8 +2457,8 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
finally:
sys.monitoring.set_events(TEST_TOOL, 0)
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
def test_108390(self):
_testinternalcapi = import_helper.import_module("_testinternalcapi")
class Foo:
def __init__(self, set_event):
@@ -2209,6 +2516,21 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
sys.monitoring.restart_events()
sys.monitoring.set_events(0, 0)
def test_134879(self):
# gh-134789
# Specialized FOR_ITER not incrementing index
def foo():
t = 0
for i in [1,2,3,4]:
t += i
self.assertEqual(t, 10)
sys.monitoring.use_tool_id(0, "test")
self.addCleanup(sys.monitoring.free_tool_id, 0)
sys.monitoring.set_local_events(0, foo.__code__, E.BRANCH_LEFT | E.BRANCH_RIGHT)
foo()
sys.monitoring.set_local_events(0, foo.__code__, 0)
class TestOptimizer(MonitoringTestBase, unittest.TestCase):
@@ -2227,8 +2549,9 @@ class TestOptimizer(MonitoringTestBase, unittest.TestCase):
class TestTier2Optimizer(CheckEvents):
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
def test_monitoring_already_opimized_loop(self):
_testinternalcapi = import_helper.import_module("_testinternalcapi")
def test_func(recorder):
set_events = sys.monitoring.set_events
line = E.LINE
@@ -2262,23 +2585,24 @@ class TestMonitoringAtShutdown(unittest.TestCase):
script_helper.run_test_script(script)
@unittest.skipUnless(_testcapi, "requires _testcapi")
class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
class Scope:
def __init__(self, *args):
self._testcapi = import_helper.import_module("_testcapi")
self.args = args
def __enter__(self):
_testcapi.monitoring_enter_scope(*self.args)
self._testcapi.monitoring_enter_scope(*self.args)
def __exit__(self, *args):
_testcapi.monitoring_exit_scope()
self._testcapi.monitoring_exit_scope()
def setUp(self):
super(TestCApiEventGeneration, self).setUp()
capi = _testcapi
self._testcapi = import_helper.import_module("_testcapi")
capi = self._testcapi
self.codelike = capi.CodeLike(2)
@@ -2347,7 +2671,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
def test_fire_event(self):
for expected, event, function, *args in self.cases:
offset = 0
self.codelike = _testcapi.CodeLike(1)
self.codelike = self._testcapi.CodeLike(1)
with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args)
self.check_event_count(event, function, args_, expected)
@@ -2358,7 +2682,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
continue
assert args and isinstance(args[-1], BaseException)
offset = 0
self.codelike = _testcapi.CodeLike(1)
self.codelike = self._testcapi.CodeLike(1)
with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,)
evt = int(math.log2(event))
@@ -2368,7 +2692,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
def test_fire_event_failing_callback(self):
for expected, event, function, *args in self.cases:
offset = 0
self.codelike = _testcapi.CodeLike(1)
self.codelike = self._testcapi.CodeLike(1)
with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args)
exc = OSError(42)
@@ -2418,12 +2742,14 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
def test_disable_event(self):
for expected, event, function, *args in self.cases:
offset = 0
self.codelike = _testcapi.CodeLike(2)
self.codelike = self._testcapi.CodeLike(2)
with self.subTest(function.__name__):
args_ = (self.codelike, 0) + tuple(args)
self.check_disable(event, function, args_, expected)
def test_enter_scope_two_events(self):
_testcapi = self._testcapi
try:
yield_counter = CounterWithDisable()
unwind_counter = CounterWithDisable()
+313 -61
View File
@@ -13,7 +13,7 @@ except ImportError:
from test import support
from test.support.bytecode_helper import (
BytecodeTestCase, CfgOptimizationTestCase, CompilationStepTestCase)
BytecodeTestCase, CfgOptimizationTestCase)
def compile_pattern_with_fast_locals(pattern):
@@ -106,7 +106,7 @@ class TestTranforms(BytecodeTestCase):
self.check_lnotab(code)
def test_global_as_constant(self):
# LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False
# LOAD_GLOBAL None/True/False --> LOAD_COMMON_CONSTANT None/True/False
def f():
x = None
x = None
@@ -121,7 +121,7 @@ class TestTranforms(BytecodeTestCase):
for func, elem in ((f, None), (g, True), (h, False)):
with self.subTest(func=func):
self.assertNotInBytecode(func, 'LOAD_GLOBAL')
self.assertInBytecode(func, 'LOAD_CONST', elem)
self.assertInBytecode(func, 'LOAD_COMMON_CONSTANT', elem)
self.check_lnotab(func)
def f():
@@ -129,7 +129,7 @@ class TestTranforms(BytecodeTestCase):
return None
self.assertNotInBytecode(f, 'LOAD_GLOBAL')
self.assertInBytecode(f, 'LOAD_CONST', None)
self.assertInBytecode(f, 'LOAD_COMMON_CONSTANT', None)
self.check_lnotab(f)
def test_while_one(self):
@@ -146,13 +146,14 @@ class TestTranforms(BytecodeTestCase):
def test_pack_unpack(self):
for line, elem in (
('a, = a,', 'LOAD_CONST',),
('a, = a,', None),
('a, b = a, b', 'SWAP',),
('a, b, c = a, b, c', 'SWAP',),
):
with self.subTest(line=line):
code = compile(line,'','single')
self.assertInBytecode(code, elem)
if elem is not None:
self.assertInBytecode(code, elem)
self.assertNotInBytecode(code, 'BUILD_TUPLE')
self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
self.check_lnotab(code)
@@ -174,10 +175,10 @@ class TestTranforms(BytecodeTestCase):
# Long tuples should be folded too.
code = compile(repr(tuple(range(10000))),'','single')
self.assertNotInBytecode(code, 'BUILD_TUPLE')
# One LOAD_CONST for the tuple, one for the None return value
# One LOAD_CONST for the tuple; None return value uses LOAD_COMMON_CONSTANT
load_consts = [instr for instr in dis.get_instructions(code)
if instr.opname == 'LOAD_CONST']
self.assertEqual(len(load_consts), 2)
self.assertEqual(len(load_consts), 1)
self.check_lnotab(code)
# Bug 1053819: Tuple of constants misidentified when presented with:
@@ -283,11 +284,11 @@ class TestTranforms(BytecodeTestCase):
('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
('-(1.0-1.0)', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
('-0.5', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.5),
('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -1),
('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_COMMON_CONSTANT', -1),
('---""', 'UNARY_NEGATIVE', None, False, None, None),
('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2),
('~~~""', 'UNARY_INVERT', None, False, None, None),
('not not True', 'UNARY_NOT', None, True, 'LOAD_CONST', True),
('not not True', 'UNARY_NOT', None, True, 'LOAD_COMMON_CONSTANT', True),
('not not x', 'UNARY_NOT', None, True, 'LOAD_NAME', 'x'), # this should be optimized regardless of constant or not
('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1),
('---x', 'UNARY_NEGATIVE', None, False, None, None),
@@ -326,7 +327,7 @@ class TestTranforms(BytecodeTestCase):
('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3),
('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6),
('1 + ""', 'NB_ADD', False, None, None),
('1 - 2', 'NB_SUBTRACT', True, 'LOAD_CONST', -1),
('1 - 2', 'NB_SUBTRACT', True, 'LOAD_COMMON_CONSTANT', -1),
('1 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4),
('1 - ""', 'NB_SUBTRACT', False, None, None),
('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4),
@@ -734,23 +735,27 @@ class TestTranforms(BytecodeTestCase):
with self.assertRaisesRegex(TypeError,
'not all arguments converted during string formatting'):
eval("'%s' % (x, y)", {'x': 1, 'y': 2})
with self.assertRaisesRegex(ValueError, 'incomplete format'):
with self.assertRaisesRegex(ValueError, 'stray % at position 2'):
eval("'%s%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(ValueError, 'incomplete format'):
with self.assertRaisesRegex(ValueError, 'stray % at position 4'):
eval("'%s%%%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'):
eval("'%s%z' % (x,)", {'x': 1234})
with self.assertRaisesRegex(ValueError, 'unsupported format character'):
with self.assertRaisesRegex(ValueError,
'unsupported format %z at position 2'):
eval("'%s%z' % (x, 5)", {'x': 1234})
with self.assertRaisesRegex(TypeError,
'format argument 1: %d requires a real number, not str'):
eval("'%d' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %x requires an integer, not float'):
eval("'%x' % (x,)", {'x': 1234.56})
with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %x requires an integer, not str'):
eval("'%x' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
with self.assertRaisesRegex(TypeError,
'format argument 1: %f requires a real number, not str'):
eval("'%f' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'):
@@ -1118,6 +1123,13 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
self.assertInBytecode(f, "LOAD_FAST_BORROW")
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
def test_import_from_doesnt_clobber_load_fast_borrow(self):
def f(self):
if x: pass
self.x
from shutil import ExecError
print(ExecError)
self.assertInBytecode(f, "LOAD_FAST_BORROW", "self")
class DirectCfgOptimizerTests(CfgOptimizationTestCase):
@@ -1507,7 +1519,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0),
('LOAD_SMALL_INT', 2, 0),
('BUILD_LIST', 2, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1520,7 +1532,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
]
after = [
('LOAD_CONST', 1, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1528,7 +1540,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end,
('END_FOR', None, 0),
('POP_ITER', None, 0),
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
@@ -1538,7 +1550,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0),
('BUILD_LIST', 2, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1553,7 +1565,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0),
('BUILD_TUPLE', 2, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1561,7 +1573,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end,
('END_FOR', None, 0),
('POP_ITER', None, 0),
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
@@ -1572,7 +1584,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0),
('LOAD_SMALL_INT', 2, 0),
('BUILD_SET', 2, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1585,7 +1597,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
]
after = [
('LOAD_CONST', 1, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1593,7 +1605,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end,
('END_FOR', None, 0),
('POP_ITER', None, 0),
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
@@ -1604,7 +1616,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0),
('BUILD_SET', 2, 0),
('GET_ITER', None, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
@@ -1615,7 +1627,22 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 0, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None])
expected = [
('LOAD_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0),
('BUILD_SET', 2, 0),
('GET_ITER', 0, 0),
start := self.Label(),
('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0),
('JUMP', start, 0),
end,
('END_FOR', None, 0),
('POP_ITER', None, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None])
def test_optimize_literal_list_contains(self):
# x in [1, 2] ==> x in (1, 2)
@@ -1634,7 +1661,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 1, 0),
('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0),
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
@@ -1657,7 +1684,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('BUILD_TUPLE', 2, 0),
('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0),
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
@@ -1679,7 +1706,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 1, 0),
('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0),
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
@@ -1696,7 +1723,17 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 0, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None])
expected = [
('LOAD_NAME', 0, 0),
('LOAD_SMALL_INT', 1, 0),
('LOAD_NAME', 1, 0),
('BUILD_SET', 2, 0),
('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0),
('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None])
def test_optimize_unary_not(self):
# test folding
@@ -1707,10 +1744,10 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('RETURN_VALUE', None, 0),
]
after = [
('LOAD_CONST', 1, 0),
('LOAD_COMMON_CONSTANT', 10, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False])
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
# test cancel out
before = [
@@ -1758,7 +1795,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('RETURN_VALUE', None, 0),
]
after = [
('LOAD_CONST', 0, 0),
('LOAD_COMMON_CONSTANT', 9, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
@@ -1774,10 +1811,10 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('RETURN_VALUE', None, 0),
]
after = [
('LOAD_CONST', 1, 0),
('LOAD_COMMON_CONSTANT', 10, 0),
('RETURN_VALUE', None, 0),
]
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False])
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
# test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point)
before = [
@@ -2393,7 +2430,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
("LOAD_FAST", 1, 4),
("LIST_EXTEND", 1, 5),
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
("GET_ITER", None, 7),
("GET_ITER", 0, 7),
top := self.Label(),
("FOR_ITER", end := self.Label(), 8),
("STORE_FAST", 2, 9),
@@ -2411,7 +2448,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
("LOAD_FAST_BORROW", 1, 4),
("LIST_EXTEND", 1, 5),
("NOP", None, 6), # ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
("GET_ITER", None, 7),
("GET_ITER", 0, 7),
top := self.Label(),
("FOR_ITER", end := self.Label(), 8),
("STORE_FAST", 2, 9),
@@ -2419,7 +2456,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end,
("END_FOR", None, 11),
("POP_TOP", None, 12),
("LOAD_CONST", 0, 13),
("LOAD_COMMON_CONSTANT", 7, 13),
("RETURN_VALUE", None, 14),
]
self.cfg_optimization_test(insts, expected_insts, consts=[None])
@@ -2433,6 +2470,168 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
self.assertEqual(b, [3, 2, 1, 0])
self.assertEqual(items, [])
def test_fold_constant_big_list_for_iter(self):
# for x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple
consts = 35
before = (
[("BUILD_LIST", 0, 1)] +
[("LOAD_CONST", 0, 2), ("LIST_APPEND", 1, 3)] * consts +
[("GET_ITER", 0, 4),
top := self.Label(),
("FOR_ITER", end := self.Label(), 5),
("STORE_FAST", 0, 6),
("JUMP", top, 7),
end,
("END_FOR", None, 8),
("POP_ITER", None, 9),
("LOAD_CONST", 0, 10),
("RETURN_VALUE", None, 11)]
)
after = [
("LOAD_CONST", 1, 3),
("GET_ITER", 0, 4),
top := self.Label(),
("FOR_ITER", end := self.Label(), 5),
("STORE_FAST", 0, 6),
("JUMP", top, 7),
end,
("END_FOR", None, 8),
("POP_ITER", None, 9),
("LOAD_CONST", 0, 10),
("RETURN_VALUE", None, 11),
]
result_const = tuple(["test"] * consts)
self.cfg_optimization_test(before, after, consts=["test"],
expected_consts=["test", result_const])
def test_fold_constant_big_set_for_iter(self):
# for x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset
before = [
("BUILD_SET", 0, 1),
("LOAD_SMALL_INT", 1, 2), ("SET_ADD", 1, 3),
("LOAD_SMALL_INT", 2, 4), ("SET_ADD", 1, 5),
("LOAD_SMALL_INT", 3, 6), ("SET_ADD", 1, 7),
("GET_ITER", 0, 8),
top := self.Label(),
("FOR_ITER", end := self.Label(), 9),
("STORE_FAST", 0, 10),
("JUMP", top, 11),
end,
("END_FOR", None, 12),
("POP_ITER", None, 13),
("LOAD_CONST", 0, 14),
("RETURN_VALUE", None, 15),
]
after = [
("LOAD_CONST", 1, 7),
("GET_ITER", 0, 8),
top := self.Label(),
("FOR_ITER", end := self.Label(), 9),
("STORE_FAST", 0, 10),
("JUMP", top, 11),
end,
("END_FOR", None, 12),
("POP_ITER", None, 13),
("LOAD_CONST", 0, 14),
("RETURN_VALUE", None, 15),
]
self.cfg_optimization_test(before, after, consts=["test"],
expected_consts=["test", frozenset({1, 2, 3})])
def test_fold_constant_list_to_tuple_for_iter(self):
INTRINSIC_LIST_TO_TUPLE = 6
before = [
("BUILD_LIST", 0, 1),
("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3),
("LOAD_SMALL_INT", 2, 4), ("LIST_APPEND", 1, 5),
("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7),
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 8),
("GET_ITER", 0, 9),
top := self.Label(),
("FOR_ITER", end := self.Label(), 10),
("STORE_FAST", 0, 11),
("JUMP", top, 12),
end,
("END_FOR", None, 13),
("POP_ITER", None, 14),
("LOAD_CONST", 0, 15),
("RETURN_VALUE", None, 16),
]
after = [
("LOAD_CONST", 1, 8),
("GET_ITER", 0, 9),
top := self.Label(),
("FOR_ITER", end := self.Label(), 10),
("STORE_FAST", 0, 11),
("JUMP", top, 12),
end,
("END_FOR", None, 13),
("POP_ITER", None, 14),
("LOAD_CONST", 0, 15),
("RETURN_VALUE", None, 16),
]
self.cfg_optimization_test(before, after, consts=["test"],
expected_consts=["test", (1, 2, 3)])
def test_fold_constant_big_list_contains_op(self):
# x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple
before = [
("LOAD_FAST", 0, 1),
("BUILD_LIST", 0, 2),
("LOAD_SMALL_INT", 1, 3), ("LIST_APPEND", 1, 4),
("LOAD_SMALL_INT", 2, 5), ("LIST_APPEND", 1, 6),
("LOAD_SMALL_INT", 3, 7), ("LIST_APPEND", 1, 8),
("CONTAINS_OP", 0, 9),
("RETURN_VALUE", None, 10),
]
after = [
("LOAD_FAST_BORROW", 0, 1),
("LOAD_CONST", 1, 8),
("CONTAINS_OP", 0, 9),
("RETURN_VALUE", None, 10),
]
self.cfg_optimization_test(before, after, consts=[None],
expected_consts=[None, (1, 2, 3)])
def test_fold_constant_big_set_contains_op(self):
# x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset
before = [
("LOAD_FAST", 0, 1),
("BUILD_SET", 0, 2),
("LOAD_SMALL_INT", 1, 3), ("SET_ADD", 1, 4),
("LOAD_SMALL_INT", 2, 5), ("SET_ADD", 1, 6),
("LOAD_SMALL_INT", 3, 7), ("SET_ADD", 1, 8),
("CONTAINS_OP", 0, 9),
("RETURN_VALUE", None, 10),
]
after = [
("LOAD_FAST_BORROW", 0, 1),
("LOAD_CONST", 1, 8),
("CONTAINS_OP", 0, 9),
("RETURN_VALUE", None, 10),
]
self.cfg_optimization_test(before, after, consts=[None],
expected_consts=[None, frozenset({1, 2, 3})])
def test_no_fold_big_list_for_iter_with_non_const(self):
same = [
("BUILD_LIST", 0, 1),
("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3),
("LOAD_FAST_BORROW", 0, 4), ("LIST_APPEND", 1, 5),
("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7),
("GET_ITER", 0, 8),
top := self.Label(),
("FOR_ITER", end := self.Label(), 9),
("STORE_FAST", 1, 10),
("JUMP", top, 11),
end,
("END_FOR", None, 12),
("POP_ITER", None, 13),
("LOAD_CONST", 0, 14),
("RETURN_VALUE", None, 15),
]
self.cfg_optimization_test(same, same, consts=["test"])
class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
def make_bb(self, insts):
@@ -2443,7 +2642,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
maxconst = max(maxconst, arg)
consts = [None for _ in range(maxconst + 1)]
return insts + [
("LOAD_CONST", 0, last_loc + 1),
("LOAD_COMMON_CONSTANT", 7, last_loc + 1),
("RETURN_VALUE", None, last_loc + 2),
], consts
@@ -2474,7 +2673,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
]
expected = [
("LOAD_FAST_BORROW", 0, 1),
("LOAD_CONST", 1, 2),
("LOAD_COMMON_CONSTANT", 7, 2),
("SWAP", 2, 3),
("POP_TOP", None, 4),
]
@@ -2512,7 +2711,13 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("STORE_FAST", 0, 3),
("POP_TOP", None, 4),
]
self.check(insts, insts)
expected = [
("LOAD_FAST", 0, 1),
("LOAD_COMMON_CONSTANT", 7, 2),
("STORE_FAST", 0, 3),
("POP_TOP", None, 4),
]
self.check(insts, expected)
insts = [
("LOAD_FAST", 0, 1),
@@ -2521,7 +2726,14 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
("POP_TOP", None, 5),
]
self.check(insts, insts)
expected = [
("LOAD_FAST", 0, 1),
("LOAD_COMMON_CONSTANT", 7, 2),
("LOAD_COMMON_CONSTANT", 7, 3),
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
("POP_TOP", None, 5),
]
self.check(insts, expected)
insts = [
("LOAD_FAST", 0, 1),
@@ -2542,7 +2754,12 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_CONST", 0, 3),
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
]
self.check(insts, insts)
expected = [
("LOAD_FAST", 0, 1),
("LOAD_COMMON_CONSTANT", 7, 3),
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
]
self.check(insts, expected)
def test_consume_no_inputs(self):
insts = [
@@ -2587,7 +2804,19 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_CONST", 0, 7),
("RETURN_VALUE", None, 8),
]
self.cfg_optimization_test(insts, insts, consts=[None])
expected = [
("LOAD_FAST", 0, 1),
top := self.Label(),
("FOR_ITER", end := self.Label(), 2),
("STORE_FAST", 2, 3),
("JUMP", top, 4),
end,
("END_FOR", None, 5),
("POP_TOP", None, 6),
("LOAD_COMMON_CONSTANT", 7, 7),
("RETURN_VALUE", None, 8),
]
self.cfg_optimization_test(insts, expected, consts=[None])
def test_load_attr(self):
insts = [
@@ -2656,10 +2885,10 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_FAST", 0, 1),
("LOAD_FAST_BORROW", 1, 2),
("SEND", end := self.Label(), 3),
("LOAD_CONST", 0, 4),
("LOAD_COMMON_CONSTANT", 7, 4),
("RETURN_VALUE", None, 5),
end,
("LOAD_CONST", 0, 6),
("LOAD_COMMON_CONSTANT", 7, 6),
("RETURN_VALUE", None, 7)
]
self.cfg_optimization_test(insts, expected, consts=[None])
@@ -2697,7 +2926,15 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_CONST", 0, 5),
("RETURN_VALUE", None, 6)
]
self.cfg_optimization_test(insts, insts, consts=[None])
expected = [
("LOAD_COMMON_CONSTANT", 7, 1),
("LOAD_FAST", 0, 2),
("SET_FUNCTION_ATTRIBUTE", 2, 3),
("STORE_FAST", 1, 4),
("LOAD_COMMON_CONSTANT", 7, 5),
("RETURN_VALUE", None, 6)
]
self.cfg_optimization_test(insts, expected, consts=[None])
insts = [
("LOAD_CONST", 0, 1),
@@ -2706,7 +2943,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("RETURN_VALUE", None, 4)
]
expected = [
("LOAD_CONST", 0, 1),
("LOAD_COMMON_CONSTANT", 7, 1),
("LOAD_FAST_BORROW", 0, 2),
("SET_FUNCTION_ATTRIBUTE", 2, 3),
("RETURN_VALUE", None, 4)
@@ -2714,22 +2951,37 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
self.cfg_optimization_test(insts, expected, consts=[None])
def test_get_yield_from_iter(self):
# GET_YIELD_FROM_ITER may leave its operand on the stack
insts = [
("LOAD_FAST", 0, 1),
("GET_YIELD_FROM_ITER", None, 2),
("LOAD_CONST", 0, 3),
("GET_ITER", 1, 2),
("PUSH_NULL", None, 3),
("LOAD_CONST", 0, 4),
send := self.Label(),
("SEND", end := self.Label(), 5),
("YIELD_VALUE", 1, 6),
("RESUME", 2, 7),
("JUMP", send, 8),
("SEND", end := self.Label(), 6),
("YIELD_VALUE", 1, 7),
("RESUME", 2, 8),
("JUMP", send, 9),
end,
("END_SEND", None, 9),
("LOAD_CONST", 0, 10),
("RETURN_VALUE", None, 11),
("END_SEND", None, 10),
("LOAD_CONST", 0, 11),
("RETURN_VALUE", None, 12),
]
self.cfg_optimization_test(insts, insts, consts=[None])
expected = [
("LOAD_FAST", 0, 1),
("GET_ITER", 1, 2),
("PUSH_NULL", None, 3),
("LOAD_COMMON_CONSTANT", 7, 4),
send := self.Label(),
("SEND", end := self.Label(), 6),
("YIELD_VALUE", 1, 7),
("RESUME", 2, 8),
("JUMP", send, 9),
end,
("END_SEND", None, 10),
("LOAD_COMMON_CONSTANT", 7, 11),
("RETURN_VALUE", None, 12),
]
self.cfg_optimization_test(insts, expected, consts=[None])
def test_push_exc_info(self):
insts = [
+11 -11
View File
@@ -8,7 +8,7 @@ use crate::{
class::PyClassImpl,
frame::{FrameObject, FrameOwner},
function::PySetterValue,
types::{Destructor, Representable},
types::Representable,
};
use core::sync::atomic::Ordering::Relaxed;
use num_traits::Zero;
@@ -515,19 +515,19 @@ impl Py<FrameObject> {
);
match owner {
FrameOwner::Generator => {
// FRAME_SUSPENDED (lasti > 0) cannot be cleared. FRAME_CREATED
// and finished frames go through the owner finalizer.
if self.lasti() != 0 {
return Err(vm.new_runtime_error("cannot clear a suspended frame"));
}
// RETURN_GENERATOR executes the creation preamble, so lasti
// no longer distinguishes a created frame from a suspended one.
if let Some(owner) = self.iframe().generator.to_owned() {
if let Some(coro) = owner.downcast_ref::<PyCoroutine>() {
let _ = PyCoroutine::del(coro, vm);
let coro = if let Some(coro) = owner.downcast_ref::<PyCoroutine>() {
coro.as_coro()
} else if let Some(async_gen) = owner.downcast_ref::<PyAsyncGen>() {
let _ = PyAsyncGen::del(async_gen, vm);
async_gen.as_coro()
} else if let Some(generator) = owner.downcast_ref::<PyGenerator>() {
let _ = PyGenerator::del(generator, vm);
}
generator.as_coro()
} else {
unreachable!("generator-owned frame has no coroutine owner")
};
coro.clear_frame(&owner, vm)?;
}
return Ok(());
}
+13
View File
@@ -122,6 +122,19 @@ impl Py<PyGenerator> {
self.inner.running()
}
#[pygetset]
fn gi_state(&self) -> &'static str {
if self.inner.closed() {
"GEN_CLOSED"
} else if self.inner.running() {
"GEN_RUNNING"
} else if self.inner.started() {
"GEN_SUSPENDED"
} else {
"GEN_CREATED"
}
}
#[pygetset]
fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
self.inner.code()
+26
View File
@@ -495,6 +495,32 @@ impl Coro {
}
}
/// Clear a created generator frame without resuming its body.
pub(crate) fn clear_frame(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
if self.closed.load() {
return Ok(());
}
if self.running.compare_exchange(false, true).is_err() {
return Err(vm.new_runtime_error("cannot clear an executing frame"));
}
let claim = RunningGuard(self);
if self.closed.load() {
return Ok(());
}
if self.started() {
return Err(vm.new_runtime_error("cannot clear a suspended frame"));
}
self.closed.store(true);
// Closed owners cannot resume. Publish that state and release the
// running claim before a warning hook can observe or clear us again.
drop(claim);
if jen.class().is(vm.ctx.types.coroutine_type) {
crate::warn::warn_unawaited_coroutine(jen, &self.qualname(), vm);
}
self.clear_except_code();
Ok(())
}
/// Whether the frame has run at all, i.e. is stopped at a `yield` rather
/// than at its start.
pub(crate) fn started(&self) -> bool {