diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 69c1ee0690d269..34110dbcace7e1 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -665,6 +665,23 @@ def func2(): self.assertTrue(globals["func1"]() is globals["func2"]()) + @cpython_only + def test_unusual_constants(self): + # gh-130851: Code objects constructed with constants that are not + # types generated by the bytecode compiler should not crash the + # interpreter. + class Unhashable: + def __hash__(self): + raise TypeError("unhashable type") + + class MyInt(int): + pass + + code = compile("a = 1", "", "exec") + code = code.replace(co_consts=(1, Unhashable(), MyInt(1), MyInt(1))) + self.assertIsInstance(code.co_consts[1], Unhashable) + self.assertEqual(code.co_consts[2], code.co_consts[3]) + class CodeWeakRefTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-04-20-33-28.gh-issue-130851.MT9j7n.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-04-20-33-28.gh-issue-130851.MT9j7n.rst new file mode 100644 index 00000000000000..49472fabb246ab --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-04-20-33-28.gh-issue-130851.MT9j7n.rst @@ -0,0 +1,3 @@ +Fix a crash in the :term:`free threading` build when constructing a +:class:`code` object with :attr:`~codeobject.co_consts` that contains instances +of types that are not otherwise generated by the bytecode compiler. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 833c4d10ba8318..5d23a7a1e808c4 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -2587,6 +2587,7 @@ intern_one_constant(PyObject *op) _Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(consts, op); if (entry == NULL) { if (_Py_hashtable_set(consts, op, op) != 0) { + PyErr_NoMemory(); return NULL; } @@ -2608,7 +2609,8 @@ intern_one_constant(PyObject *op) } static int -compare_constants(const void *key1, const void *key2) { +compare_constants(const void *key1, const void *key2) +{ PyObject *op1 = (PyObject *)key1; PyObject *op2 = (PyObject *)key2; if (op1 == op2) { @@ -2668,8 +2670,8 @@ compare_constants(const void *key1, const void *key2) { Py_complex c2 = ((PyComplexObject *)op2)->cval; return memcmp(&c1, &c2, sizeof(Py_complex)) == 0; } - _Py_FatalErrorFormat("unexpected type in compare_constants: %s", - Py_TYPE(op1)->tp_name); + // gh-130851: Treat instances of unexpected types as distinct if they are + // not the same object. return 0; } @@ -2689,9 +2691,13 @@ hash_const(const void *key) } Py_hash_t h = PyObject_Hash(op); if (h == -1) { - // This should never happen: all the constants we support have - // infallible hash functions. - Py_FatalError("code: hash failed"); + // gh-130851: Other than slice objects, every constant that the + // bytecode compiler generates is hashable. However, users can + // provide their own constants, when constructing code objects via + // types.CodeType(). If the user-provided constant is unhashable, we + // use the memory address of the object as a fallback hash value. + PyErr_Clear(); + return (Py_uhash_t)(uintptr_t)key; } return (Py_uhash_t)h; }