Skip to content

Commit e285232

Browse files
[3.13] gh-130851: Don't crash when deduping unusual code constants (GH-130853) (#130880)
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. (cherry picked from commit 2905690) Co-authored-by: Sam Gross <[email protected]>
1 parent 39f7b06 commit e285232

File tree

3 files changed

+32
-6
lines changed

3 files changed

+32
-6
lines changed

Lib/test/test_code.py

+17
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,23 @@ def func2():
596596

597597
self.assertTrue(globals["func1"]() is globals["func2"]())
598598

599+
@cpython_only
600+
def test_unusual_constants(self):
601+
# gh-130851: Code objects constructed with constants that are not
602+
# types generated by the bytecode compiler should not crash the
603+
# interpreter.
604+
class Unhashable:
605+
def __hash__(self):
606+
raise TypeError("unhashable type")
607+
608+
class MyInt(int):
609+
pass
610+
611+
code = compile("a = 1", "<string>", "exec")
612+
code = code.replace(co_consts=(1, Unhashable(), MyInt(1), MyInt(1)))
613+
self.assertIsInstance(code.co_consts[1], Unhashable)
614+
self.assertEqual(code.co_consts[2], code.co_consts[3])
615+
599616

600617
class CodeWeakRefTest(unittest.TestCase):
601618

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -2527,6 +2527,7 @@ intern_one_constant(PyObject *op)
25272527
_Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(consts, op);
25282528
if (entry == NULL) {
25292529
if (_Py_hashtable_set(consts, op, op) != 0) {
2530+
PyErr_NoMemory();
25302531
return NULL;
25312532
}
25322533

@@ -2548,7 +2549,8 @@ intern_one_constant(PyObject *op)
25482549
}
25492550

25502551
static int
2551-
compare_constants(const void *key1, const void *key2) {
2552+
compare_constants(const void *key1, const void *key2)
2553+
{
25522554
PyObject *op1 = (PyObject *)key1;
25532555
PyObject *op2 = (PyObject *)key2;
25542556
if (op1 == op2) {
@@ -2608,8 +2610,8 @@ compare_constants(const void *key1, const void *key2) {
26082610
Py_complex c2 = ((PyComplexObject *)op2)->cval;
26092611
return memcmp(&c1, &c2, sizeof(Py_complex)) == 0;
26102612
}
2611-
_Py_FatalErrorFormat("unexpected type in compare_constants: %s",
2612-
Py_TYPE(op1)->tp_name);
2613+
// gh-130851: Treat instances of unexpected types as distinct if they are
2614+
// not the same object.
26132615
return 0;
26142616
}
26152617

@@ -2629,9 +2631,13 @@ hash_const(const void *key)
26292631
}
26302632
Py_hash_t h = PyObject_Hash(op);
26312633
if (h == -1) {
2632-
// This should never happen: all the constants we support have
2633-
// infallible hash functions.
2634-
Py_FatalError("code: hash failed");
2634+
// gh-130851: Other than slice objects, every constant that the
2635+
// bytecode compiler generates is hashable. However, users can
2636+
// provide their own constants, when constructing code objects via
2637+
// types.CodeType(). If the user-provided constant is unhashable, we
2638+
// use the memory address of the object as a fallback hash value.
2639+
PyErr_Clear();
2640+
return (Py_uhash_t)(uintptr_t)key;
26352641
}
26362642
return (Py_uhash_t)h;
26372643
}

0 commit comments

Comments
 (0)