Skip to content

Commit 2905690

Browse files
authored
gh-130851: Don't crash when deduping unusual code constants (#130853)
The bytecode compiler only generates a few different types of constants, like str, int, tuple, slices, etc. Users can construct code objects with various unusual constants, including ones that are not hashable or not even constant. The free threaded build previously crashed with a fatal error when confronted with these constants. Instead, treat distinct objects of otherwise unhandled types as not equal for the purposes of deduplication.
1 parent 78d50e9 commit 2905690

File tree

3 files changed

+32
-6
lines changed

3 files changed

+32
-6
lines changed

Lib/test/test_code.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,23 @@ def func2():
665665

666666
self.assertTrue(globals["func1"]() is globals["func2"]())
667667

668+
@cpython_only
669+
def test_unusual_constants(self):
670+
# gh-130851: Code objects constructed with constants that are not
671+
# types generated by the bytecode compiler should not crash the
672+
# interpreter.
673+
class Unhashable:
674+
def __hash__(self):
675+
raise TypeError("unhashable type")
676+
677+
class MyInt(int):
678+
pass
679+
680+
code = compile("a = 1", "<string>", "exec")
681+
code = code.replace(co_consts=(1, Unhashable(), MyInt(1), MyInt(1)))
682+
self.assertIsInstance(code.co_consts[1], Unhashable)
683+
self.assertEqual(code.co_consts[2], code.co_consts[3])
684+
668685

669686
class CodeWeakRefTest(unittest.TestCase):
670687

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in the :term:`free threading` build when constructing a
2+
:class:`code` object with :attr:`~codeobject.co_consts` that contains instances
3+
of types that are not otherwise generated by the bytecode compiler.

Objects/codeobject.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2587,6 +2587,7 @@ intern_one_constant(PyObject *op)
25872587
_Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(consts, op);
25882588
if (entry == NULL) {
25892589
if (_Py_hashtable_set(consts, op, op) != 0) {
2590+
PyErr_NoMemory();
25902591
return NULL;
25912592
}
25922593

@@ -2608,7 +2609,8 @@ intern_one_constant(PyObject *op)
26082609
}
26092610

26102611
static int
2611-
compare_constants(const void *key1, const void *key2) {
2612+
compare_constants(const void *key1, const void *key2)
2613+
{
26122614
PyObject *op1 = (PyObject *)key1;
26132615
PyObject *op2 = (PyObject *)key2;
26142616
if (op1 == op2) {
@@ -2668,8 +2670,8 @@ compare_constants(const void *key1, const void *key2) {
26682670
Py_complex c2 = ((PyComplexObject *)op2)->cval;
26692671
return memcmp(&c1, &c2, sizeof(Py_complex)) == 0;
26702672
}
2671-
_Py_FatalErrorFormat("unexpected type in compare_constants: %s",
2672-
Py_TYPE(op1)->tp_name);
2673+
// gh-130851: Treat instances of unexpected types as distinct if they are
2674+
// not the same object.
26732675
return 0;
26742676
}
26752677

@@ -2689,9 +2691,13 @@ hash_const(const void *key)
26892691
}
26902692
Py_hash_t h = PyObject_Hash(op);
26912693
if (h == -1) {
2692-
// This should never happen: all the constants we support have
2693-
// infallible hash functions.
2694-
Py_FatalError("code: hash failed");
2694+
// gh-130851: Other than slice objects, every constant that the
2695+
// bytecode compiler generates is hashable. However, users can
2696+
// provide their own constants, when constructing code objects via
2697+
// types.CodeType(). If the user-provided constant is unhashable, we
2698+
// use the memory address of the object as a fallback hash value.
2699+
PyErr_Clear();
2700+
return (Py_uhash_t)(uintptr_t)key;
26952701
}
26962702
return (Py_uhash_t)h;
26972703
}

0 commit comments

Comments
 (0)