From 184b61596fb4661a5ccd3a3f8f3ee033efb2c3d9 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 4 Mar 2025 20:27:50 +0000 Subject: [PATCH] gh-130851: Don't crash when deduping unusual code constants. 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. --- Lib/test/test_code.py | 17 +++++++++++++++++ ...5-03-04-20-33-28.gh-issue-130851.MT9j7n.rst | 3 +++ Objects/codeobject.c | 18 ++++++++++++------ 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-03-04-20-33-28.gh-issue-130851.MT9j7n.rst 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; }