Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,25 @@ def test_code_hash_uses_bytecode(self):
self.assertNotEqual(c, c1)
self.assertNotEqual(hash(c), hash(c1))

def test_code_equality_uses_consts(self):
c = compile("0.0", "example.py", "eval")
self.assertEqual(c.co_consts, (0.0,))
self.assertEqual(c, c.replace(co_consts=(0.0,)))
for consts in [(-0.0,), (0,), (False,), ((0.0,),), (0.0, None)]:
with self.subTest(consts=consts):
self.assertNotEqual(c, c.replace(co_consts=consts))

def test_code_equality_nested(self):
# gh-160788: this took time exponential in the nesting depth.
depth = 30
source = "".join(" " * i + f"class C{i}:\n" for i in range(depth))
indent = " " * depth
code1 = compile(source + indent + "pass\n", "example.py", "exec")
code2 = compile(source + indent + "pass\n", "example.py", "exec")
code3 = compile(source + indent + "x = 1\n", "example.py", "exec")
self.assertEqual(code1, code2)
self.assertNotEqual(code1, code3)

@cpython_only
def test_code_equal_with_instrumentation(self):
""" GH-109052
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Comparing :ref:`code objects <code-objects>` with ``==`` no longer takes
time exponential in the nesting depth of their classes and functions.
32 changes: 19 additions & 13 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2500,7 +2500,7 @@ code_richcompare(PyObject *self, PyObject *other, int op)
{
PyCodeObject *co, *cp;
int eq;
PyObject *consts1, *consts2;
PyObject *key1, *key2;
PyObject *res;

if ((op != Py_EQ && op != Py_NE) ||
Expand Down Expand Up @@ -2537,19 +2537,25 @@ code_richcompare(PyObject *self, PyObject *other, int op)
i += _PyOpcode_Caches[co_instr.op.code];
}

/* compare constants */
consts1 = _PyCode_ConstantKey(co->co_consts);
if (!consts1)
return NULL;
consts2 = _PyCode_ConstantKey(cp->co_consts);
if (!consts2) {
Py_DECREF(consts1);
return NULL;
/* compare constants one by one: the key of the whole tuple also holds
the tuple itself, so comparing it would compare nested code objects
twice, which takes exponential time in the nesting depth */
eq = PyTuple_GET_SIZE(co->co_consts) == PyTuple_GET_SIZE(cp->co_consts);
if (!eq) goto unequal;
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(co->co_consts); i++) {
key1 = _PyCode_ConstantKey(PyTuple_GET_ITEM(co->co_consts, i));
if (!key1)
return NULL;
key2 = _PyCode_ConstantKey(PyTuple_GET_ITEM(cp->co_consts, i));
if (!key2) {
Py_DECREF(key1);
return NULL;
}
eq = PyObject_RichCompareBool(key1, key2, Py_EQ);
Py_DECREF(key1);
Py_DECREF(key2);
if (eq <= 0) goto unequal;
}
eq = PyObject_RichCompareBool(consts1, consts2, Py_EQ);
Py_DECREF(consts1);
Py_DECREF(consts2);
if (eq <= 0) goto unequal;

eq = PyObject_RichCompareBool(co->co_names, cp->co_names, Py_EQ);
if (eq <= 0) goto unequal;
Expand Down
Loading