Skip to content
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

[3.12] gh-104614: Make Sure ob_type is Always Set Correctly by PyType_Ready() (gh-105122) #105211

Merged
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
38 changes: 37 additions & 1 deletion Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Run the _testcapi module tests (tests for the Python/C API): by defn,
# these are all functions _testcapi exports whose name begins with 'test_'.

from collections import OrderedDict
import _thread
from collections import OrderedDict
import contextlib
import importlib.machinery
import importlib.util
import os
Expand Down Expand Up @@ -1626,6 +1627,41 @@ def test_tp_mro_is_set(self):
self.assertIsNot(mro, None)


class TestStaticTypes(unittest.TestCase):

_has_run = False

@classmethod
def setUpClass(cls):
# The tests here don't play nice with our approach to refleak
# detection, so we bail out in that case.
if cls._has_run:
raise unittest.SkipTest('these tests do not support re-running')
cls._has_run = True

@contextlib.contextmanager
def basic_static_type(self, *args):
cls = _testcapi.get_basic_static_type(*args)
yield cls

def test_pytype_ready_always_sets_tp_type(self):
# The point of this test is to prevent something like
# https://github.com/python/cpython/issues/104614
# from happening again.

# First check when tp_base/tp_bases is *not* set before PyType_Ready().
with self.basic_static_type() as cls:
self.assertIs(cls.__base__, object);
self.assertEqual(cls.__bases__, (object,));
self.assertIs(type(cls), type(object));

# Then check when we *do* set tp_base/tp_bases first.
with self.basic_static_type(object) as cls:
self.assertIs(cls.__base__, object);
self.assertEqual(cls.__bases__, (object,));
self.assertIs(type(cls), type(object));


class TestThreadState(unittest.TestCase):

@threading_helper.reap_threads
Expand Down
45 changes: 45 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2627,6 +2627,50 @@ type_get_tp_mro(PyObject *self, PyObject *type)
}


/* We only use 2 in test_capi/test_misc.py. */
#define NUM_BASIC_STATIC_TYPES 2
static PyTypeObject BasicStaticTypes[NUM_BASIC_STATIC_TYPES] = {
#define INIT_BASIC_STATIC_TYPE \
{ \
PyVarObject_HEAD_INIT(NULL, 0) \
.tp_name = "BasicStaticType", \
.tp_basicsize = sizeof(PyObject), \
}
INIT_BASIC_STATIC_TYPE,
INIT_BASIC_STATIC_TYPE,
#undef INIT_BASIC_STATIC_TYPE
};
static int num_basic_static_types_used = 0;

static PyObject *
get_basic_static_type(PyObject *self, PyObject *args)
{
PyObject *base = NULL;
if (!PyArg_ParseTuple(args, "|O", &base)) {
return NULL;
}
assert(base == NULL || PyType_Check(base));

if(num_basic_static_types_used >= NUM_BASIC_STATIC_TYPES) {
PyErr_SetString(PyExc_RuntimeError, "no more available basic static types");
return NULL;
}
PyTypeObject *cls = &BasicStaticTypes[num_basic_static_types_used++];

if (base != NULL) {
cls->tp_base = (PyTypeObject *)Py_NewRef(base);
cls->tp_bases = Py_BuildValue("(O)", base);
if (cls->tp_bases == NULL) {
return NULL;
}
}
if (PyType_Ready(cls) < 0) {
return NULL;
}
return (PyObject *)cls;
}


// Test PyThreadState C API
static PyObject *
test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
Expand Down Expand Up @@ -3384,6 +3428,7 @@ static PyMethodDef TestMethods[] = {
{"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
{"type_get_tp_bases", type_get_tp_bases, METH_O},
{"type_get_tp_mro", type_get_tp_mro, METH_O},
{"get_basic_static_type", get_basic_static_type, METH_VARARGS, NULL},
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"frame_getlocals", frame_getlocals, METH_O, NULL},
{"frame_getglobals", frame_getglobals, METH_O, NULL},
Expand Down
60 changes: 44 additions & 16 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,14 @@ clear_tp_bases(PyTypeObject *self)
{
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
if (_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
if (self->tp_bases != NULL
&& PyTuple_GET_SIZE(self->tp_bases) > 0)
{
assert(_Py_IsImmortal(self->tp_bases));
_Py_ClearImmortal(self->tp_bases);
if (self->tp_bases != NULL) {
if (PyTuple_GET_SIZE(self->tp_bases) == 0) {
Py_CLEAR(self->tp_bases);
}
else {
assert(_Py_IsImmortal(self->tp_bases));
_Py_ClearImmortal(self->tp_bases);
}
}
}
return;
Expand Down Expand Up @@ -352,11 +355,14 @@ clear_tp_mro(PyTypeObject *self)
{
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
if (_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
if (self->tp_mro != NULL
&& PyTuple_GET_SIZE(self->tp_mro) > 0)
{
assert(_Py_IsImmortal(self->tp_mro));
_Py_ClearImmortal(self->tp_mro);
if (self->tp_mro != NULL) {
if (PyTuple_GET_SIZE(self->tp_mro) == 0) {
Py_CLEAR(self->tp_mro);
}
else {
assert(_Py_IsImmortal(self->tp_mro));
_Py_ClearImmortal(self->tp_mro);
}
}
}
return;
Expand Down Expand Up @@ -6996,12 +7002,8 @@ type_ready_pre_checks(PyTypeObject *type)


static int
type_ready_set_bases(PyTypeObject *type)
type_ready_set_base(PyTypeObject *type)
{
if (lookup_tp_bases(type) != NULL) {
return 0;
}

/* Initialize tp_base (defaults to BaseObject unless that's us) */
PyTypeObject *base = type->tp_base;
if (base == NULL && type != &PyBaseObject_Type) {
Expand All @@ -7025,18 +7027,38 @@ type_ready_set_bases(PyTypeObject *type)
}
}

return 0;
}

static int
type_ready_set_type(PyTypeObject *type)
{
/* Initialize ob_type if NULL. This means extensions that want to be
compilable separately on Windows can call PyType_Ready() instead of
initializing the ob_type field of their type objects. */
/* The test for base != NULL is really unnecessary, since base is only
NULL when type is &PyBaseObject_Type, and we know its ob_type is
not NULL (it's initialized to &PyType_Type). But coverity doesn't
know that. */
PyTypeObject *base = type->tp_base;
if (Py_IS_TYPE(type, NULL) && base != NULL) {
Py_SET_TYPE(type, Py_TYPE(base));
}

/* Initialize tp_bases */
return 0;
}

static int
type_ready_set_bases(PyTypeObject *type)
{
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
if (!_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
assert(lookup_tp_bases(type) != NULL);
return 0;
}
assert(lookup_tp_bases(type) == NULL);
}

PyObject *bases = lookup_tp_bases(type);
if (bases == NULL) {
PyTypeObject *base = type->tp_base;
Expand Down Expand Up @@ -7446,6 +7468,12 @@ type_ready(PyTypeObject *type, int rerunbuiltin)
if (type_ready_set_dict(type) < 0) {
goto error;
}
if (type_ready_set_base(type) < 0) {
goto error;
}
if (type_ready_set_type(type) < 0) {
goto error;
}
if (type_ready_set_bases(type) < 0) {
goto error;
}
Expand Down
2 changes: 2 additions & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ Modules/_testcapi/watchers.c - num_code_object_destroyed_events -
Modules/_testcapi/watchers.c - pyfunc_watchers -
Modules/_testcapi/watchers.c - func_watcher_ids -
Modules/_testcapi/watchers.c - func_watcher_callbacks -
Modules/_testcapimodule.c - BasicStaticTypes -
Modules/_testcapimodule.c - num_basic_static_types_used -
Modules/_testcapimodule.c - ContainerNoGC_members -
Modules/_testcapimodule.c - ContainerNoGC_type -
Modules/_testcapimodule.c - FmData -
Expand Down