Skip to content

[3.13] gh-130851: Don't crash when deduping unusual code constants (GH-130853) #130880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 5, 2025
Merged
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
17 changes: 17 additions & 0 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,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", "<string>", "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):

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 12 additions & 6 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2527,6 +2527,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;
}

Expand All @@ -2548,7 +2549,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) {
Expand Down Expand Up @@ -2608,8 +2610,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;
}

Expand All @@ -2629,9 +2631,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;
}
Expand Down
Loading