Skip to content

Commit c7e5bba

Browse files
authoredAug 1, 2022
pythonGH-95150: Use position and exception tables for code hashing and equality (pythonGH-95509)
1 parent a95e60d commit c7e5bba

File tree

5 files changed

+68
-5
lines changed

5 files changed

+68
-5
lines changed
 

‎Lib/test/test_code.py

+21
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,27 @@ def func():
428428
self.assertIsNone(line)
429429
self.assertEqual(end_line, new_code.co_firstlineno + 1)
430430

431+
def test_code_equality(self):
432+
def f():
433+
try:
434+
a()
435+
except:
436+
b()
437+
else:
438+
c()
439+
finally:
440+
d()
441+
code_a = f.__code__
442+
code_b = code_a.replace(co_linetable=b"")
443+
code_c = code_a.replace(co_exceptiontable=b"")
444+
code_d = code_b.replace(co_exceptiontable=b"")
445+
self.assertNotEqual(code_a, code_b)
446+
self.assertNotEqual(code_a, code_c)
447+
self.assertNotEqual(code_a, code_d)
448+
self.assertNotEqual(code_b, code_c)
449+
self.assertNotEqual(code_b, code_d)
450+
self.assertNotEqual(code_c, code_d)
451+
431452

432453
def isinterned(s):
433454
return s is sys.intern(('_' + s + '_')[1:-1])

‎Lib/test/test_compile.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ def check_same_constant(const):
615615
exec(code, ns)
616616
f1 = ns['f1']
617617
f2 = ns['f2']
618-
self.assertIs(f1.__code__, f2.__code__)
618+
self.assertIs(f1.__code__.co_consts, f2.__code__.co_consts)
619619
self.check_constant(f1, const)
620620
self.assertEqual(repr(f1()), repr(const))
621621

@@ -628,7 +628,7 @@ def check_same_constant(const):
628628
# Note: "lambda: ..." emits "LOAD_CONST Ellipsis",
629629
# whereas "lambda: Ellipsis" emits "LOAD_GLOBAL Ellipsis"
630630
f1, f2 = lambda: ..., lambda: ...
631-
self.assertIs(f1.__code__, f2.__code__)
631+
self.assertIs(f1.__code__.co_consts, f2.__code__.co_consts)
632632
self.check_constant(f1, Ellipsis)
633633
self.assertEqual(repr(f1()), repr(Ellipsis))
634634

@@ -643,7 +643,7 @@ def check_same_constant(const):
643643
# {0} is converted to a constant frozenset({0}) by the peephole
644644
# optimizer
645645
f1, f2 = lambda x: x in {0}, lambda x: x in {0}
646-
self.assertIs(f1.__code__, f2.__code__)
646+
self.assertIs(f1.__code__.co_consts, f2.__code__.co_consts)
647647
self.check_constant(f1, frozenset({0}))
648648
self.assertTrue(f1(0))
649649

@@ -1302,6 +1302,27 @@ def f():
13021302
self.assertIsNotNone(end_column)
13031303
self.assertLessEqual((line, column), (end_line, end_column))
13041304

1305+
@support.cpython_only
1306+
def test_column_offset_deduplication(self):
1307+
# GH-95150: Code with different column offsets shouldn't be merged!
1308+
for source in [
1309+
"lambda: a",
1310+
"(a for b in c)",
1311+
"[a for b in c]",
1312+
"{a for b in c}",
1313+
"{a: b for c in d}",
1314+
]:
1315+
with self.subTest(source):
1316+
code = compile(f"{source}, {source}", "<test>", "eval")
1317+
self.assertEqual(len(code.co_consts), 2)
1318+
self.assertIsInstance(code.co_consts[0], types.CodeType)
1319+
self.assertIsInstance(code.co_consts[1], types.CodeType)
1320+
self.assertNotEqual(code.co_consts[0], code.co_consts[1])
1321+
self.assertNotEqual(
1322+
list(code.co_consts[0].co_positions()),
1323+
list(code.co_consts[1].co_positions()),
1324+
)
1325+
13051326

13061327
class TestExpressionStackSize(unittest.TestCase):
13071328
# These tests check that the computed stack size for a code object

‎Lib/test/test_syntax.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2012,7 +2012,8 @@ def fib(n):
20122012
a, b = 0, 1
20132013
"""
20142014
try:
2015-
self.assertEqual(compile(s1, '<string>', 'exec'), compile(s2, '<string>', 'exec'))
2015+
compile(s1, '<string>', 'exec')
2016+
compile(s2, '<string>', 'exec')
20162017
except SyntaxError:
20172018
self.fail("Indented statement over multiple lines is valid")
20182019

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Update code object hashing and equality to consider all debugging and
2+
exception handling tables. This fixes an issue where certain non-identical
3+
code objects could be "deduplicated" during compilation.

‎Objects/codeobject.c

+18-1
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,15 @@ code_richcompare(PyObject *self, PyObject *other, int op)
16951695
eq = PyObject_RichCompareBool(co->co_localsplusnames,
16961696
cp->co_localsplusnames, Py_EQ);
16971697
if (eq <= 0) goto unequal;
1698+
eq = PyObject_RichCompareBool(co->co_linetable, cp->co_linetable, Py_EQ);
1699+
if (eq <= 0) {
1700+
goto unequal;
1701+
}
1702+
eq = PyObject_RichCompareBool(co->co_exceptiontable,
1703+
cp->co_exceptiontable, Py_EQ);
1704+
if (eq <= 0) {
1705+
goto unequal;
1706+
}
16981707

16991708
if (op == Py_EQ)
17001709
res = Py_True;
@@ -1727,7 +1736,15 @@ code_hash(PyCodeObject *co)
17271736
if (h2 == -1) return -1;
17281737
h3 = PyObject_Hash(co->co_localsplusnames);
17291738
if (h3 == -1) return -1;
1730-
h = h0 ^ h1 ^ h2 ^ h3 ^
1739+
Py_hash_t h4 = PyObject_Hash(co->co_linetable);
1740+
if (h4 == -1) {
1741+
return -1;
1742+
}
1743+
Py_hash_t h5 = PyObject_Hash(co->co_exceptiontable);
1744+
if (h5 == -1) {
1745+
return -1;
1746+
}
1747+
h = h0 ^ h1 ^ h2 ^ h3 ^ h4 ^ h5 ^
17311748
co->co_argcount ^ co->co_posonlyargcount ^ co->co_kwonlyargcount ^
17321749
co->co_flags;
17331750
if (h == -1) h = -2;

0 commit comments

Comments
 (0)
Please sign in to comment.