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
+1239 -513

No files matched your search

+22 -13
View File
@@ -87,7 +87,7 @@ cellvars: ()
freevars: () freevars: ()
nlocals: 0 nlocals: 0
flags: 67108867 flags: 67108867
consts: ("'doc string'", 'None') consts: ("'doc string'",)
>>> def keywordonly_args(a,b,*,k1): >>> def keywordonly_args(a,b,*,k1):
... return a,b,k1 ... return a,b,k1
@@ -161,7 +161,7 @@ cellvars: ()
freevars: () freevars: ()
nlocals: 3 nlocals: 3
flags: 67108995 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): >>> def no_docstring(x, y, z):
... return x + "hello" + y + z + "world" ... return x + "hello" + y + z + "world"
@@ -423,14 +423,6 @@ class CodeTest(unittest.TestCase):
new_code = code = func.__code__.replace(co_linetable=b'') new_code = code = func.__code__.replace(co_linetable=b'')
self.assertEqual(list(new_code.co_lines()), []) 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') @unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing')
def test_returns_only_none(self): def test_returns_only_none(self):
value = True value = True
@@ -540,7 +532,7 @@ class CodeTest(unittest.TestCase):
], ],
[ [
("PUSH_EXC_INFO", None), ("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 ("STORE_NAME", "e"), # XX: we know the location for this
("DELETE_NAME", "e"), ("DELETE_NAME", "e"),
("RERAISE", 1), ("RERAISE", 1),
@@ -1165,6 +1157,22 @@ class CodeTest(unittest.TestCase):
with self.assertRaises(Exception): with self.assertRaises(Exception):
_testinternalcapi.verify_stateless_code(func) _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): def isinterned(s):
return s is sys.intern(('_' + s + '_')[1:-1]) return s is sys.intern(('_' + s + '_')[1:-1])
@@ -1438,6 +1446,7 @@ class CodeLocationTest(unittest.TestCase):
co_code=bytes( co_code=bytes(
[ [
dis.opmap["RESUME"], 0, dis.opmap["RESUME"], 0,
dis.opmap["CACHE"], 0,
dis.opmap["LOAD_COMMON_CONSTANT"], 0, dis.opmap["LOAD_COMMON_CONSTANT"], 0,
dis.opmap["RAISE_VARARGS"], 1, dis.opmap["RAISE_VARARGS"], 1,
] ]
@@ -1446,7 +1455,7 @@ class CodeLocationTest(unittest.TestCase):
[ [
(1 << 7) (1 << 7)
| (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3) | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
| (3 - 1), | (4 - 1),
0, 0,
] ]
), ),
@@ -1454,7 +1463,7 @@ class CodeLocationTest(unittest.TestCase):
self.assertRaises(AssertionError, f) self.assertRaises(AssertionError, f)
self.assertEqual( self.assertEqual(
list(f.__code__.co_positions()), list(f.__code__.co_positions()),
3 * [(42, 42, None, None)], 4 * [(42, 42, None, None)],
) )
@cpython_only @cpython_only
+50 -9
View File
@@ -724,8 +724,7 @@ class TestSpecifics(unittest.TestCase):
@support.cpython_only @support.cpython_only
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
@support.run_with_limited_c_stack( @support.skip_if_huge_c_stack(100_000 if sys.platform == "android" else 500_000)
100_000 if sys.platform == "android" else 500_000)
@support.skip_emscripten_stack_overflow() @support.skip_emscripten_stack_overflow()
def test_compiler_recursion_limit(self): def test_compiler_recursion_limit(self):
# Compiler frames are small # Compiler frames are small
@@ -841,8 +840,10 @@ class TestSpecifics(unittest.TestCase):
else: else:
return "unused" return "unused"
self.assertEqual(f.__code__.co_consts, if f.__doc__ is None:
(f.__doc__, "used")) self.assertEqual(f.__code__.co_consts, (True, "used"))
else:
self.assertEqual(f.__code__.co_consts, (f.__doc__, "used"))
@support.cpython_only @support.cpython_only
def test_remove_unused_consts_no_docstring(self): def test_remove_unused_consts_no_docstring(self):
@@ -887,6 +888,10 @@ class TestSpecifics(unittest.TestCase):
def f1(): def f1():
"docstring" "docstring"
return 42 return 42
if f1.__doc__ is None:
self.assertEqual(f1.__code__.co_consts, (42,))
else:
self.assertEqual(f1.__code__.co_consts, (f1.__doc__,)) self.assertEqual(f1.__code__.co_consts, (f1.__doc__,))
# This is a regression test for a CPython specific peephole optimizer # This is a regression test for a CPython specific peephole optimizer
@@ -1294,7 +1299,7 @@ class TestSpecifics(unittest.TestCase):
x x
in in
y) y)
genexp_lines = [0, 4, 2, 0, 4] genexp_lines = [4, 0, 4, 2, 0, 4]
genexp_code = return_genexp.__code__.co_consts[0] genexp_code = return_genexp.__code__.co_consts[0]
code_lines = self.get_code_lines(genexp_code) code_lines = self.get_code_lines(genexp_code)
@@ -1629,6 +1634,17 @@ class TestSpecifics(unittest.TestCase):
def f(): def f():
a if (1 if b else c) else d 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): def test_global_declaration_in_except_used_in_else(self):
# See gh-111123 # See gh-111123
code = textwrap.dedent("""\ code = textwrap.dedent("""\
@@ -1733,6 +1749,30 @@ class TestSpecifics(unittest.TestCase):
self.assertEqual(wm.category, SyntaxWarning) self.assertEqual(wm.category, SyntaxWarning)
self.assertIn("\"is\" with 'int' literal", str(wm.message)) 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', [ @support.subTests('src', [
textwrap.dedent(""" textwrap.dedent("""
def f(): def f():
@@ -2419,8 +2459,8 @@ class TestSourcePositions(unittest.TestCase):
for i, pos in enumerate(positions): for i, pos in enumerate(positions):
with self.subTest(i=i, pos=pos): with self.subTest(i=i, pos=pos):
start_line, end_line, start_col, end_col = pos start_line, end_line, start_col, end_col = pos
if i == 0 and start_col == end_col == 0: if i <= 1:
# ignore the RESUME in the beginning # ignore the RESUME and CACHE in the beginning
continue continue
self.assertEqual(start_line, 1) self.assertEqual(start_line, 1)
self.assertEqual(end_line, 1) self.assertEqual(end_line, 1)
@@ -2446,12 +2486,13 @@ class TestSourcePositions(unittest.TestCase):
start_line, end_line, _, _ = instr.positions start_line, end_line, _, _ = instr.positions
self.assertEqual(start_line, end_line) 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. # three for the no-exception __exit__ call, and one for the return.
# They should all have the locations of the context manager ('xyz'). # They should all have the locations of the context manager ('xyz').
load_none = [instr for instr in dis.get_instructions(f) if 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 return_value = [instr for instr in dis.get_instructions(f) if
instr.opname == 'RETURN_VALUE'] instr.opname == 'RETURN_VALUE']
+328 -288
View File
File diff suppressed because it is too large. Load diff
+15 -17
View File
@@ -357,26 +357,25 @@ class ModifyUnderlyingIterableTest(unittest.TestCase):
yield x yield x
return gen(range(10)) return gen(range(10))
def process_tests(self, get_generator, is_expr): def process_tests(self, get_generator, changes_iterable):
err_iterator = "'.*' object is not an iterator" if changes_iterable:
err_iterable = "'.*' object is not iterable"
for obj in self.iterables: for obj in self.iterables:
g_obj = get_generator(obj) g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj, obj=obj): with self.subTest(g_obj=g_obj, obj=obj):
if is_expr:
self.assertRaisesRegex(TypeError, err_iterator, list, g_obj)
else:
self.assertListEqual(list(g_obj), list(obj)) self.assertListEqual(list(g_obj), list(obj))
g_iter = get_generator(iter(obj)) g_iter = get_generator(iter(obj))
with self.subTest(g_iter=g_iter, obj=obj): with self.subTest(g_iter=g_iter, obj=obj):
self.assertListEqual(list(g_iter), list(obj)) self.assertListEqual(list(g_iter), list(obj))
err_regex = "'.*' object is not iterable"
for obj in self.non_iterables: for obj in self.non_iterables:
g_obj = get_generator(obj) g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj): with self.subTest(g_obj=g_obj):
err = err_iterator if is_expr else err_iterable if changes_iterable:
self.assertRaisesRegex(TypeError, err, list, g_obj) self.assertRaisesRegex(TypeError, err_regex, list, g_obj)
else:
next(g_obj)
def test_modify_f_locals(self): def test_modify_f_locals(self):
def modify_f_locals(g, local, obj): def modify_f_locals(g, local, obj):
@@ -389,22 +388,22 @@ class ModifyUnderlyingIterableTest(unittest.TestCase):
def get_generator_genfunc(obj): def get_generator_genfunc(obj):
return modify_f_locals(self.genfunc(), 'it', obj) return modify_f_locals(self.genfunc(), 'it', obj)
self.process_tests(get_generator_genexpr, True) self.process_tests(get_generator_genexpr, False)
self.process_tests(get_generator_genfunc, False) self.process_tests(get_generator_genfunc, True)
def test_new_gen_from_gi_code(self): def test_new_gen_from_gi_code(self):
def new_gen_from_gi_code(g, obj): def new_gen_from_gi_code(g, obj):
generator_func = types.FunctionType(g.gi_code, {}) generator_func = types.FunctionType(g.gi_code, {})
return generator_func(obj) return generator_func(obj)
def get_generator_genexpr(obj): for obj in self.non_iterables:
return new_gen_from_gi_code(self.genexpr(), obj) with self.assertRaises(TypeError):
new_gen_from_gi_code(self.genexpr(), obj)
def get_generator_genfunc(obj): def get_generator_genfunc(obj):
return new_gen_from_gi_code(self.genfunc(), obj) return new_gen_from_gi_code(self.genfunc(), obj)
self.process_tests(get_generator_genexpr, True) self.process_tests(get_generator_genfunc, True)
self.process_tests(get_generator_genfunc, False)
class ExceptionTest(unittest.TestCase): class ExceptionTest(unittest.TestCase):
@@ -1367,7 +1366,7 @@ From the Iterators list, about the types of these things.
>>> type(i) >>> type(i)
<class 'generator'> <class 'generator'>
>>> [s for s in dir(i) if not s.startswith('_')] >>> [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 >>> from test.support import HAVE_DOCSTRINGS
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') >>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
Implement next(self). Implement next(self).
@@ -2878,8 +2877,7 @@ __test__ = {"tut": tutorial_tests,
} }
def load_tests(loader, tests, pattern): def load_tests(loader, tests, pattern):
from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON tests.addTest(doctest.DocTestSuite())
tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # TODO: RUSTPYTHON
return tests 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 unittest
import test.support import test.support
from test.support import requires_specialization_ft, script_helper from test.support import import_helper, requires_specialization, script_helper
try:
import _testcapi
except ImportError:
_testcapi = None
try:
import _testinternalcapi
except ImportError:
_testinternalcapi = None
PAIR = (0,1) PAIR = (0,1)
@@ -202,13 +193,10 @@ INSTRUMENTED_EVENTS = [
(E.BRANCH, "branch"), (E.BRANCH, "branch"),
] ]
EXCEPT_EVENTS = [ SIMPLE_EVENTS = INSTRUMENTED_EVENTS + [
(E.RAISE, "raise"), (E.RAISE, "raise"),
(E.PY_UNWIND, "unwind"),
(E.EXCEPTION_HANDLED, "exception_handled"), (E.EXCEPTION_HANDLED, "exception_handled"),
] (E.PY_UNWIND, "unwind"),
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
(E.C_RAISE, "c_raise"), (E.C_RAISE, "c_raise"),
(E.C_RETURN, "c_return"), (E.C_RETURN, "c_return"),
] ]
@@ -744,18 +732,6 @@ class TestDisable(MonitoringTestBase, unittest.TestCase):
sys.monitoring.register_callback(TEST_TOOL, event, None) 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: class ExceptionRecorder:
@@ -879,7 +855,6 @@ class ExceptionMonitoringTest(CheckEvents):
self.check_events(func1, [("raise", KeyError)]) self.check_events(func1, [("raise", KeyError)])
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
def test_implicit_stop_iteration(self): def test_implicit_stop_iteration(self):
"""Generators are documented as raising a StopIteration """Generators are documented as raising a StopIteration
when they terminate. when they terminate.
@@ -889,6 +864,8 @@ class ExceptionMonitoringTest(CheckEvents):
This test checks that both paths record an equivalent event. This test checks that both paths record an equivalent event.
""" """
_testinternalcapi = import_helper.import_module("_testinternalcapi")
def gen(): def gen():
yield 1 yield 1
return 2 return 2
@@ -1054,9 +1031,10 @@ class ExceptionMonitoringTest(CheckEvents):
) )
self.assertEqual(events[0], ("throw", IndexError)) self.assertEqual(events[0], ("throw", IndexError))
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi") @requires_specialization
@requires_specialization_ft
def test_no_unwind_for_shim_frame(self): def test_no_unwind_for_shim_frame(self):
_testinternalcapi = import_helper.import_module("_testinternalcapi")
class ValueErrorRaiser: class ValueErrorRaiser:
def __init__(self): def __init__(self):
raise ValueError() raise ValueError()
@@ -1222,19 +1200,20 @@ class TestLineAndInstructionEvents(CheckEvents):
line3 = 3 line3 = 3
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10), ("line", "get_events", 10),
('line', 'func1', 1), ("line", "func1", 1),
('instruction', 'func1', 2), ("instruction", "func1", 4),
('instruction', 'func1', 4), ("instruction", "func1", 6),
('line', 'func1', 2), ("line", "func1", 2),
('instruction', 'func1', 6), ("instruction", "func1", 8),
('instruction', 'func1', 8), ("instruction", "func1", 10),
('line', 'func1', 3), ("line", "func1", 3),
('instruction', 'func1', 10), ("instruction", "func1", 12),
('instruction', 'func1', 12), ("instruction", "func1", 14),
('instruction', 'func1', 14), ("instruction", "func1", 16),
('instruction', 'func1', 16), ("instruction", "func1", 18),
('line', 'get_events', 11)]) ("line", "get_events", 11),
])
def test_c_call(self): def test_c_call(self):
@@ -1244,22 +1223,23 @@ class TestLineAndInstructionEvents(CheckEvents):
line3 = 3 line3 = 3
self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10), ("line", "get_events", 10),
('line', 'func2', 1), ("line", "func2", 1),
('instruction', 'func2', 2), ("instruction", "func2", 4),
('instruction', 'func2', 4), ("instruction", "func2", 6),
('line', 'func2', 2), ("line", "func2", 2),
('instruction', 'func2', 6), ("instruction", "func2", 8),
('instruction', 'func2', 8), ("instruction", "func2", 10),
('instruction', 'func2', 28), ("instruction", "func2", 30),
('instruction', 'func2', 30), ("instruction", "func2", 32),
('instruction', 'func2', 38), ("instruction", "func2", 40),
('line', 'func2', 3), ("line", "func2", 3),
('instruction', 'func2', 40), ("instruction", "func2", 42),
('instruction', 'func2', 42), ("instruction", "func2", 44),
('instruction', 'func2', 44), ("instruction", "func2", 46),
('instruction', 'func2', 46), ("instruction", "func2", 48),
('line', 'get_events', 11)]) ("line", "get_events", 11),
])
def test_try_except(self): def test_try_except(self):
@@ -1272,28 +1252,29 @@ class TestLineAndInstructionEvents(CheckEvents):
line = 6 line = 6
self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10), ("line", "get_events", 10),
('line', 'func3', 1), ("line", "func3", 1),
('instruction', 'func3', 2), ("instruction", "func3", 4),
('line', 'func3', 2), ("line", "func3", 2),
('instruction', 'func3', 4), ("instruction", "func3", 6),
('instruction', 'func3', 6), ("instruction", "func3", 8),
('line', 'func3', 3), ("line", "func3", 3),
('instruction', 'func3', 8), ("instruction", "func3", 10),
('instruction', 'func3', 18), ("instruction", "func3", 20),
('instruction', 'func3', 20), ("instruction", "func3", 22),
('line', 'func3', 4), ("line", "func3", 4),
('instruction', 'func3', 22), ("instruction", "func3", 24),
('line', 'func3', 5), ("line", "func3", 5),
('instruction', 'func3', 24), ("instruction", "func3", 26),
('instruction', 'func3', 26), ("instruction", "func3", 28),
('instruction', 'func3', 28), ("instruction", "func3", 30),
('line', 'func3', 6), ("line", "func3", 6),
('instruction', 'func3', 30), ("instruction", "func3", 32),
('instruction', 'func3', 32), ("instruction", "func3", 34),
('instruction', 'func3', 34), ("instruction", "func3", 36),
('instruction', 'func3', 36), ("instruction", "func3", 38),
('line', 'get_events', 11)]) ("line", "get_events", 11),
])
def test_with_restart(self): def test_with_restart(self):
def func1(): def func1():
@@ -1304,16 +1285,16 @@ class TestLineAndInstructionEvents(CheckEvents):
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10), ('line', 'get_events', 10),
('line', 'func1', 1), ('line', 'func1', 1),
('instruction', 'func1', 2),
('instruction', 'func1', 4), ('instruction', 'func1', 4),
('line', 'func1', 2),
('instruction', 'func1', 6), ('instruction', 'func1', 6),
('line', 'func1', 2),
('instruction', 'func1', 8), ('instruction', 'func1', 8),
('line', 'func1', 3),
('instruction', 'func1', 10), ('instruction', 'func1', 10),
('line', 'func1', 3),
('instruction', 'func1', 12), ('instruction', 'func1', 12),
('instruction', 'func1', 14), ('instruction', 'func1', 14),
('instruction', 'func1', 16), ('instruction', 'func1', 16),
('instruction', 'func1', 18),
('line', 'get_events', 11)]) ('line', 'get_events', 11)])
sys.monitoring.restart_events() sys.monitoring.restart_events()
@@ -1321,16 +1302,16 @@ class TestLineAndInstructionEvents(CheckEvents):
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
('line', 'get_events', 10), ('line', 'get_events', 10),
('line', 'func1', 1), ('line', 'func1', 1),
('instruction', 'func1', 2),
('instruction', 'func1', 4), ('instruction', 'func1', 4),
('line', 'func1', 2),
('instruction', 'func1', 6), ('instruction', 'func1', 6),
('line', 'func1', 2),
('instruction', 'func1', 8), ('instruction', 'func1', 8),
('line', 'func1', 3),
('instruction', 'func1', 10), ('instruction', 'func1', 10),
('line', 'func1', 3),
('instruction', 'func1', 12), ('instruction', 'func1', 12),
('instruction', 'func1', 14), ('instruction', 'func1', 14),
('instruction', 'func1', 16), ('instruction', 'func1', 16),
('instruction', 'func1', 18),
('line', 'get_events', 11)]) ('line', 'get_events', 11)])
def test_turn_off_only_instruction(self): def test_turn_off_only_instruction(self):
@@ -1378,10 +1359,10 @@ class TestInstallIncrementally(MonitoringTestBase, unittest.TestCase):
line1 = 1 line1 = 1
MUST_INCLUDE_LI = [ MUST_INCLUDE_LI = [
('instruction', 'func1', 2),
('line', 'func1', 2),
('instruction', 'func1', 4), ('instruction', 'func1', 4),
('instruction', 'func1', 6)] ('line', 'func1', 2),
('instruction', 'func1', 6),
('instruction', 'func1', 8)]
def test_line_then_instruction(self): def test_line_then_instruction(self):
recorders = [ LineRecorder, InstructionRecorder ] recorders = [ LineRecorder, InstructionRecorder ]
@@ -1398,11 +1379,11 @@ class TestInstallIncrementally(MonitoringTestBase, unittest.TestCase):
len(()) len(())
MUST_INCLUDE_CI = [ MUST_INCLUDE_CI = [
('instruction', 'func2', 2), ('instruction', 'func2', 4),
('call', 'func2', sys.monitoring.MISSING), ('call', 'func2', sys.monitoring.MISSING),
('call', 'len', ()), ('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)]) ('line', 'func3', 6)])
def test_set_non_local_event(self): def test_set_non_local_event(self):
# C_RETURN/C_RAISE are ancillary (derived) events — not settable as local
with self.assertRaises(ValueError): 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): def line_from_offset(code, offset):
for start, end, line in code.co_lines(): for start, end, line in code.co_lines():
@@ -1617,11 +1924,11 @@ class TestBranchAndJumpEvents(CheckEvents):
('branch right', 'whilefunc', 1, 3)]) ('branch right', 'whilefunc', 1, 3)])
self.check_events(func, recorders = BRANCH_OFFSET_RECORDERS, expected = [ self.check_events(func, recorders = BRANCH_OFFSET_RECORDERS, expected = [
('branch left', 'func', 28, 32), ('branch left', 'func', 32, 36),
('branch right', 'func', 44, 58), ('branch right', 'func', 48, 62),
('branch left', 'func', 28, 32), ('branch left', 'func', 32, 36),
('branch left', 'func', 44, 50), ('branch left', 'func', 48, 54),
('branch right', 'func', 28, 70)]) ('branch right', 'func', 32, 74)])
def test_except_star(self): def test_except_star(self):
@@ -1648,8 +1955,8 @@ class TestBranchAndJumpEvents(CheckEvents):
('branch', 'func', 4, 4), ('branch', 'func', 4, 4),
('line', 'func', 5), ('line', 'func', 5),
('line', 'meth', 1), ('line', 'meth', 1),
('jump', 'func', 5, '[offset=120]'), ('jump', 'func', 5, '[offset=122]'),
('branch', 'func', '[offset=124]', '[offset=130]'), ('branch', 'func', '[offset=126]', '[offset=132]'),
('line', 'get_events', 11)]) ('line', 'get_events', 11)])
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [ self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
@@ -1663,8 +1970,8 @@ class TestBranchAndJumpEvents(CheckEvents):
('line', 'func', 5), ('line', 'func', 5),
('line', 'meth', 1), ('line', 'meth', 1),
('return', 'meth', None), ('return', 'meth', None),
('jump', 'func', 5, '[offset=120]'), ('jump', 'func', 5, '[offset=122]'),
('branch', 'func', '[offset=124]', '[offset=130]'), ('branch', 'func', '[offset=126]', '[offset=132]'),
('return', 'func', None), ('return', 'func', None),
('line', 'get_events', 11)]) ('line', 'get_events', 11)])
@@ -1676,8 +1983,8 @@ class TestBranchAndJumpEvents(CheckEvents):
n += 1 n += 1
return None return None
in_loop = ('branch left', 'foo', 10, 16) in_loop = ('branch left', 'foo', 12, 18)
exit_loop = ('branch right', 'foo', 10, 40) exit_loop = ('branch right', 'foo', 12, 42)
self.check_events(foo, recorders = BRANCH_OFFSET_RECORDERS, expected = [ self.check_events(foo, recorders = BRANCH_OFFSET_RECORDERS, expected = [
in_loop, in_loop,
in_loop, in_loop,
@@ -2150,8 +2457,8 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
finally: finally:
sys.monitoring.set_events(TEST_TOOL, 0) sys.monitoring.set_events(TEST_TOOL, 0)
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
def test_108390(self): def test_108390(self):
_testinternalcapi = import_helper.import_module("_testinternalcapi")
class Foo: class Foo:
def __init__(self, set_event): def __init__(self, set_event):
@@ -2209,6 +2516,21 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
sys.monitoring.restart_events() sys.monitoring.restart_events()
sys.monitoring.set_events(0, 0) 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): class TestOptimizer(MonitoringTestBase, unittest.TestCase):
@@ -2227,8 +2549,9 @@ class TestOptimizer(MonitoringTestBase, unittest.TestCase):
class TestTier2Optimizer(CheckEvents): class TestTier2Optimizer(CheckEvents):
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
def test_monitoring_already_opimized_loop(self): def test_monitoring_already_opimized_loop(self):
_testinternalcapi = import_helper.import_module("_testinternalcapi")
def test_func(recorder): def test_func(recorder):
set_events = sys.monitoring.set_events set_events = sys.monitoring.set_events
line = E.LINE line = E.LINE
@@ -2262,23 +2585,24 @@ class TestMonitoringAtShutdown(unittest.TestCase):
script_helper.run_test_script(script) script_helper.run_test_script(script)
@unittest.skipUnless(_testcapi, "requires _testcapi")
class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase): class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
class Scope: class Scope:
def __init__(self, *args): def __init__(self, *args):
self._testcapi = import_helper.import_module("_testcapi")
self.args = args self.args = args
def __enter__(self): def __enter__(self):
_testcapi.monitoring_enter_scope(*self.args) self._testcapi.monitoring_enter_scope(*self.args)
def __exit__(self, *args): def __exit__(self, *args):
_testcapi.monitoring_exit_scope() self._testcapi.monitoring_exit_scope()
def setUp(self): def setUp(self):
super(TestCApiEventGeneration, self).setUp() super(TestCApiEventGeneration, self).setUp()
capi = _testcapi self._testcapi = import_helper.import_module("_testcapi")
capi = self._testcapi
self.codelike = capi.CodeLike(2) self.codelike = capi.CodeLike(2)
@@ -2347,7 +2671,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
def test_fire_event(self): def test_fire_event(self):
for expected, event, function, *args in self.cases: for expected, event, function, *args in self.cases:
offset = 0 offset = 0
self.codelike = _testcapi.CodeLike(1) self.codelike = self._testcapi.CodeLike(1)
with self.subTest(function.__name__): with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args) args_ = (self.codelike, offset) + tuple(args)
self.check_event_count(event, function, args_, expected) self.check_event_count(event, function, args_, expected)
@@ -2358,7 +2682,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
continue continue
assert args and isinstance(args[-1], BaseException) assert args and isinstance(args[-1], BaseException)
offset = 0 offset = 0
self.codelike = _testcapi.CodeLike(1) self.codelike = self._testcapi.CodeLike(1)
with self.subTest(function.__name__): with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,) args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,)
evt = int(math.log2(event)) evt = int(math.log2(event))
@@ -2368,7 +2692,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
def test_fire_event_failing_callback(self): def test_fire_event_failing_callback(self):
for expected, event, function, *args in self.cases: for expected, event, function, *args in self.cases:
offset = 0 offset = 0
self.codelike = _testcapi.CodeLike(1) self.codelike = self._testcapi.CodeLike(1)
with self.subTest(function.__name__): with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args) args_ = (self.codelike, offset) + tuple(args)
exc = OSError(42) exc = OSError(42)
@@ -2418,12 +2742,14 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
def test_disable_event(self): def test_disable_event(self):
for expected, event, function, *args in self.cases: for expected, event, function, *args in self.cases:
offset = 0 offset = 0
self.codelike = _testcapi.CodeLike(2) self.codelike = self._testcapi.CodeLike(2)
with self.subTest(function.__name__): with self.subTest(function.__name__):
args_ = (self.codelike, 0) + tuple(args) args_ = (self.codelike, 0) + tuple(args)
self.check_disable(event, function, args_, expected) self.check_disable(event, function, args_, expected)
def test_enter_scope_two_events(self): def test_enter_scope_two_events(self):
_testcapi = self._testcapi
try: try:
yield_counter = CounterWithDisable() yield_counter = CounterWithDisable()
unwind_counter = CounterWithDisable() unwind_counter = CounterWithDisable()
+312 -60
View File
@@ -13,7 +13,7 @@ except ImportError:
from test import support from test import support
from test.support.bytecode_helper import ( from test.support.bytecode_helper import (
BytecodeTestCase, CfgOptimizationTestCase, CompilationStepTestCase) BytecodeTestCase, CfgOptimizationTestCase)
def compile_pattern_with_fast_locals(pattern): def compile_pattern_with_fast_locals(pattern):
@@ -106,7 +106,7 @@ class TestTranforms(BytecodeTestCase):
self.check_lnotab(code) self.check_lnotab(code)
def test_global_as_constant(self): 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(): def f():
x = None x = None
x = None x = None
@@ -121,7 +121,7 @@ class TestTranforms(BytecodeTestCase):
for func, elem in ((f, None), (g, True), (h, False)): for func, elem in ((f, None), (g, True), (h, False)):
with self.subTest(func=func): with self.subTest(func=func):
self.assertNotInBytecode(func, 'LOAD_GLOBAL') self.assertNotInBytecode(func, 'LOAD_GLOBAL')
self.assertInBytecode(func, 'LOAD_CONST', elem) self.assertInBytecode(func, 'LOAD_COMMON_CONSTANT', elem)
self.check_lnotab(func) self.check_lnotab(func)
def f(): def f():
@@ -129,7 +129,7 @@ class TestTranforms(BytecodeTestCase):
return None return None
self.assertNotInBytecode(f, 'LOAD_GLOBAL') self.assertNotInBytecode(f, 'LOAD_GLOBAL')
self.assertInBytecode(f, 'LOAD_CONST', None) self.assertInBytecode(f, 'LOAD_COMMON_CONSTANT', None)
self.check_lnotab(f) self.check_lnotab(f)
def test_while_one(self): def test_while_one(self):
@@ -146,12 +146,13 @@ class TestTranforms(BytecodeTestCase):
def test_pack_unpack(self): def test_pack_unpack(self):
for line, elem in ( for line, elem in (
('a, = a,', 'LOAD_CONST',), ('a, = a,', None),
('a, b = a, b', 'SWAP',), ('a, b = a, b', 'SWAP',),
('a, b, c = a, b, c', 'SWAP',), ('a, b, c = a, b, c', 'SWAP',),
): ):
with self.subTest(line=line): with self.subTest(line=line):
code = compile(line,'','single') code = compile(line,'','single')
if elem is not None:
self.assertInBytecode(code, elem) self.assertInBytecode(code, elem)
self.assertNotInBytecode(code, 'BUILD_TUPLE') self.assertNotInBytecode(code, 'BUILD_TUPLE')
self.assertNotInBytecode(code, 'UNPACK_SEQUENCE') self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
@@ -174,10 +175,10 @@ class TestTranforms(BytecodeTestCase):
# Long tuples should be folded too. # Long tuples should be folded too.
code = compile(repr(tuple(range(10000))),'','single') code = compile(repr(tuple(range(10000))),'','single')
self.assertNotInBytecode(code, 'BUILD_TUPLE') 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) load_consts = [instr for instr in dis.get_instructions(code)
if instr.opname == 'LOAD_CONST'] if instr.opname == 'LOAD_CONST']
self.assertEqual(len(load_consts), 2) self.assertEqual(len(load_consts), 1)
self.check_lnotab(code) self.check_lnotab(code)
# Bug 1053819: Tuple of constants misidentified when presented with: # 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), ('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
('-(1.0-1.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), ('-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), ('---""', 'UNARY_NEGATIVE', None, False, None, None),
('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2), ('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2),
('~~~""', 'UNARY_INVERT', None, False, None, None), ('~~~""', '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 ('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), ('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1),
('---x', 'UNARY_NEGATIVE', None, False, None, None), ('---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', 'NB_ADD', True, 'LOAD_SMALL_INT', 3),
('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6), ('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6),
('1 + ""', 'NB_ADD', False, None, None), ('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 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4),
('1 - ""', 'NB_SUBTRACT', False, None, None), ('1 - ""', 'NB_SUBTRACT', False, None, None),
('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4), ('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4),
@@ -734,23 +735,27 @@ class TestTranforms(BytecodeTestCase):
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
'not all arguments converted during string formatting'): 'not all arguments converted during string formatting'):
eval("'%s' % (x, y)", {'x': 1, 'y': 2}) 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}) eval("'%s%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(ValueError, 'incomplete format'): with self.assertRaisesRegex(ValueError, 'stray % at position 4'):
eval("'%s%%%' % (x,)", {'x': 1234}) eval("'%s%%%' % (x,)", {'x': 1234})
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'): 'not enough arguments for format string'):
eval("'%s%z' % (x,)", {'x': 1234}) 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}) eval("'%s%z' % (x, 5)", {'x': 1234})
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
'format argument 1: %d requires a real number, not str'): 'format argument 1: %d requires a real number, not str'):
eval("'%d' % (x,)", {'x': '1234'}) 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}) 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'}) 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'}) eval("'%f' % (x,)", {'x': '1234'})
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
'not enough arguments for format string'): 'not enough arguments for format string'):
@@ -1118,6 +1123,13 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
self.assertInBytecode(f, "LOAD_FAST_BORROW") self.assertInBytecode(f, "LOAD_FAST_BORROW")
self.assertNotInBytecode(f, "LOAD_FAST_CHECK") 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): class DirectCfgOptimizerTests(CfgOptimizationTestCase):
@@ -1507,7 +1519,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0), ('LOAD_SMALL_INT', 1, 0),
('LOAD_SMALL_INT', 2, 0), ('LOAD_SMALL_INT', 2, 0),
('BUILD_LIST', 2, 0), ('BUILD_LIST', 2, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1520,7 +1532,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
] ]
after = [ after = [
('LOAD_CONST', 1, 0), ('LOAD_CONST', 1, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1528,7 +1540,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end, end,
('END_FOR', None, 0), ('END_FOR', None, 0),
('POP_ITER', None, 0), ('POP_ITER', None, 0),
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)]) 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_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0), ('LOAD_NAME', 0, 0),
('BUILD_LIST', 2, 0), ('BUILD_LIST', 2, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1553,7 +1565,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_SMALL_INT', 1, 0), ('LOAD_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0), ('LOAD_NAME', 0, 0),
('BUILD_TUPLE', 2, 0), ('BUILD_TUPLE', 2, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1561,7 +1573,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end, end,
('END_FOR', None, 0), ('END_FOR', None, 0),
('POP_ITER', None, 0), ('POP_ITER', None, 0),
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None]) 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', 1, 0),
('LOAD_SMALL_INT', 2, 0), ('LOAD_SMALL_INT', 2, 0),
('BUILD_SET', 2, 0), ('BUILD_SET', 2, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1585,7 +1597,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
] ]
after = [ after = [
('LOAD_CONST', 1, 0), ('LOAD_CONST', 1, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1593,7 +1605,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end, end,
('END_FOR', None, 0), ('END_FOR', None, 0),
('POP_ITER', None, 0), ('POP_ITER', None, 0),
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})]) 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_SMALL_INT', 1, 0),
('LOAD_NAME', 0, 0), ('LOAD_NAME', 0, 0),
('BUILD_SET', 2, 0), ('BUILD_SET', 2, 0),
('GET_ITER', None, 0), ('GET_ITER', 0, 0),
start := self.Label(), start := self.Label(),
('FOR_ITER', end := self.Label(), 0), ('FOR_ITER', end := self.Label(), 0),
('STORE_FAST', 0, 0), ('STORE_FAST', 0, 0),
@@ -1615,7 +1627,22 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 0, 0), ('LOAD_CONST', 0, 0),
('RETURN_VALUE', None, 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): def test_optimize_literal_list_contains(self):
# x in [1, 2] ==> x in (1, 2) # x in [1, 2] ==> x in (1, 2)
@@ -1634,7 +1661,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 1, 0), ('LOAD_CONST', 1, 0),
('CONTAINS_OP', 0, 0), ('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0), ('POP_TOP', None, 0),
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)]) self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
@@ -1657,7 +1684,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('BUILD_TUPLE', 2, 0), ('BUILD_TUPLE', 2, 0),
('CONTAINS_OP', 0, 0), ('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0), ('POP_TOP', None, 0),
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None]) self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
@@ -1679,7 +1706,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('LOAD_CONST', 1, 0), ('LOAD_CONST', 1, 0),
('CONTAINS_OP', 0, 0), ('CONTAINS_OP', 0, 0),
('POP_TOP', None, 0), ('POP_TOP', None, 0),
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 7, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})]) 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), ('LOAD_CONST', 0, 0),
('RETURN_VALUE', None, 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): def test_optimize_unary_not(self):
# test folding # test folding
@@ -1707,10 +1744,10 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
after = [ after = [
('LOAD_CONST', 1, 0), ('LOAD_COMMON_CONSTANT', 10, 0),
('RETURN_VALUE', None, 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 # test cancel out
before = [ before = [
@@ -1758,7 +1795,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
after = [ after = [
('LOAD_CONST', 0, 0), ('LOAD_COMMON_CONSTANT', 9, 0),
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
@@ -1774,10 +1811,10 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
('RETURN_VALUE', None, 0), ('RETURN_VALUE', None, 0),
] ]
after = [ after = [
('LOAD_CONST', 1, 0), ('LOAD_COMMON_CONSTANT', 10, 0),
('RETURN_VALUE', None, 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) # test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point)
before = [ before = [
@@ -2393,7 +2430,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
("LOAD_FAST", 1, 4), ("LOAD_FAST", 1, 4),
("LIST_EXTEND", 1, 5), ("LIST_EXTEND", 1, 5),
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6), ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
("GET_ITER", None, 7), ("GET_ITER", 0, 7),
top := self.Label(), top := self.Label(),
("FOR_ITER", end := self.Label(), 8), ("FOR_ITER", end := self.Label(), 8),
("STORE_FAST", 2, 9), ("STORE_FAST", 2, 9),
@@ -2411,7 +2448,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
("LOAD_FAST_BORROW", 1, 4), ("LOAD_FAST_BORROW", 1, 4),
("LIST_EXTEND", 1, 5), ("LIST_EXTEND", 1, 5),
("NOP", None, 6), # ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6), ("NOP", None, 6), # ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
("GET_ITER", None, 7), ("GET_ITER", 0, 7),
top := self.Label(), top := self.Label(),
("FOR_ITER", end := self.Label(), 8), ("FOR_ITER", end := self.Label(), 8),
("STORE_FAST", 2, 9), ("STORE_FAST", 2, 9),
@@ -2419,7 +2456,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
end, end,
("END_FOR", None, 11), ("END_FOR", None, 11),
("POP_TOP", None, 12), ("POP_TOP", None, 12),
("LOAD_CONST", 0, 13), ("LOAD_COMMON_CONSTANT", 7, 13),
("RETURN_VALUE", None, 14), ("RETURN_VALUE", None, 14),
] ]
self.cfg_optimization_test(insts, expected_insts, consts=[None]) 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(b, [3, 2, 1, 0])
self.assertEqual(items, []) 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): class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
def make_bb(self, insts): def make_bb(self, insts):
@@ -2443,7 +2642,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
maxconst = max(maxconst, arg) maxconst = max(maxconst, arg)
consts = [None for _ in range(maxconst + 1)] consts = [None for _ in range(maxconst + 1)]
return insts + [ return insts + [
("LOAD_CONST", 0, last_loc + 1), ("LOAD_COMMON_CONSTANT", 7, last_loc + 1),
("RETURN_VALUE", None, last_loc + 2), ("RETURN_VALUE", None, last_loc + 2),
], consts ], consts
@@ -2474,7 +2673,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
] ]
expected = [ expected = [
("LOAD_FAST_BORROW", 0, 1), ("LOAD_FAST_BORROW", 0, 1),
("LOAD_CONST", 1, 2), ("LOAD_COMMON_CONSTANT", 7, 2),
("SWAP", 2, 3), ("SWAP", 2, 3),
("POP_TOP", None, 4), ("POP_TOP", None, 4),
] ]
@@ -2512,7 +2711,13 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("STORE_FAST", 0, 3), ("STORE_FAST", 0, 3),
("POP_TOP", None, 4), ("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 = [ insts = [
("LOAD_FAST", 0, 1), ("LOAD_FAST", 0, 1),
@@ -2521,7 +2726,14 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
("POP_TOP", None, 5), ("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 = [ insts = [
("LOAD_FAST", 0, 1), ("LOAD_FAST", 0, 1),
@@ -2542,7 +2754,12 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_CONST", 0, 3), ("LOAD_CONST", 0, 3),
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), ("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): def test_consume_no_inputs(self):
insts = [ insts = [
@@ -2587,7 +2804,19 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_CONST", 0, 7), ("LOAD_CONST", 0, 7),
("RETURN_VALUE", None, 8), ("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): def test_load_attr(self):
insts = [ insts = [
@@ -2656,10 +2885,10 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_FAST", 0, 1), ("LOAD_FAST", 0, 1),
("LOAD_FAST_BORROW", 1, 2), ("LOAD_FAST_BORROW", 1, 2),
("SEND", end := self.Label(), 3), ("SEND", end := self.Label(), 3),
("LOAD_CONST", 0, 4), ("LOAD_COMMON_CONSTANT", 7, 4),
("RETURN_VALUE", None, 5), ("RETURN_VALUE", None, 5),
end, end,
("LOAD_CONST", 0, 6), ("LOAD_COMMON_CONSTANT", 7, 6),
("RETURN_VALUE", None, 7) ("RETURN_VALUE", None, 7)
] ]
self.cfg_optimization_test(insts, expected, consts=[None]) self.cfg_optimization_test(insts, expected, consts=[None])
@@ -2697,7 +2926,15 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("LOAD_CONST", 0, 5), ("LOAD_CONST", 0, 5),
("RETURN_VALUE", None, 6) ("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 = [ insts = [
("LOAD_CONST", 0, 1), ("LOAD_CONST", 0, 1),
@@ -2706,7 +2943,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
("RETURN_VALUE", None, 4) ("RETURN_VALUE", None, 4)
] ]
expected = [ expected = [
("LOAD_CONST", 0, 1), ("LOAD_COMMON_CONSTANT", 7, 1),
("LOAD_FAST_BORROW", 0, 2), ("LOAD_FAST_BORROW", 0, 2),
("SET_FUNCTION_ATTRIBUTE", 2, 3), ("SET_FUNCTION_ATTRIBUTE", 2, 3),
("RETURN_VALUE", None, 4) ("RETURN_VALUE", None, 4)
@@ -2714,22 +2951,37 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
self.cfg_optimization_test(insts, expected, consts=[None]) self.cfg_optimization_test(insts, expected, consts=[None])
def test_get_yield_from_iter(self): def test_get_yield_from_iter(self):
# GET_YIELD_FROM_ITER may leave its operand on the stack
insts = [ insts = [
("LOAD_FAST", 0, 1), ("LOAD_FAST", 0, 1),
("GET_YIELD_FROM_ITER", None, 2), ("GET_ITER", 1, 2),
("LOAD_CONST", 0, 3), ("PUSH_NULL", None, 3),
("LOAD_CONST", 0, 4),
send := self.Label(), send := self.Label(),
("SEND", end := self.Label(), 5), ("SEND", end := self.Label(), 6),
("YIELD_VALUE", 1, 6), ("YIELD_VALUE", 1, 7),
("RESUME", 2, 7), ("RESUME", 2, 8),
("JUMP", send, 8), ("JUMP", send, 9),
end, end,
("END_SEND", None, 9), ("END_SEND", None, 10),
("LOAD_CONST", 0, 10), ("LOAD_CONST", 0, 11),
("RETURN_VALUE", None, 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): def test_push_exc_info(self):
insts = [ insts = [
+11 -11
View File
@@ -8,7 +8,7 @@ use crate::{
class::PyClassImpl, class::PyClassImpl,
frame::{FrameObject, FrameOwner}, frame::{FrameObject, FrameOwner},
function::PySetterValue, function::PySetterValue,
types::{Destructor, Representable}, types::Representable,
}; };
use core::sync::atomic::Ordering::Relaxed; use core::sync::atomic::Ordering::Relaxed;
use num_traits::Zero; use num_traits::Zero;
@@ -515,19 +515,19 @@ impl Py<FrameObject> {
); );
match owner { match owner {
FrameOwner::Generator => { FrameOwner::Generator => {
// FRAME_SUSPENDED (lasti > 0) cannot be cleared. FRAME_CREATED // RETURN_GENERATOR executes the creation preamble, so lasti
// and finished frames go through the owner finalizer. // no longer distinguishes a created frame from a suspended one.
if self.lasti() != 0 {
return Err(vm.new_runtime_error("cannot clear a suspended frame"));
}
if let Some(owner) = self.iframe().generator.to_owned() { if let Some(owner) = self.iframe().generator.to_owned() {
if let Some(coro) = owner.downcast_ref::<PyCoroutine>() { let coro = if let Some(coro) = owner.downcast_ref::<PyCoroutine>() {
let _ = PyCoroutine::del(coro, vm); coro.as_coro()
} else if let Some(async_gen) = owner.downcast_ref::<PyAsyncGen>() { } 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>() { } 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(()); return Ok(());
} }
+13
View File
@@ -122,6 +122,19 @@ impl Py<PyGenerator> {
self.inner.running() 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] #[pygetset]
fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> { fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
self.inner.code() 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 /// Whether the frame has run at all, i.e. is stopped at a `yield` rather
/// than at its start. /// than at its start.
pub(crate) fn started(&self) -> bool { pub(crate) fn started(&self) -> bool {