Skip to content

Commit 5837d04

Browse files
orenmnncoghlan
authored andcommitted
bpo-31588: Validate return value of __prepare__() methods (GH-3764)
Class execution requires that __prepare__() methods return a proper execution namespace. Check for that immediately after calling __prepare__(), rather than passing it through to the code execution machinery and potentially triggering SystemError (in debug builds) or a cryptic TypeError (in release builds). Patch by Oren Milman.
1 parent 236329e commit 5837d04

File tree

3 files changed

+31
-0
lines changed

3 files changed

+31
-0
lines changed

Lib/test/test_types.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,28 @@ def __prepare__(*args):
864864
self.assertIs(ns, expected_ns)
865865
self.assertEqual(len(kwds), 0)
866866

867+
def test_bad___prepare__(self):
868+
# __prepare__() must return a mapping.
869+
class BadMeta(type):
870+
@classmethod
871+
def __prepare__(*args):
872+
return None
873+
with self.assertRaisesRegex(TypeError,
874+
r'^BadMeta\.__prepare__\(\) must '
875+
r'return a mapping, not NoneType$'):
876+
class Foo(metaclass=BadMeta):
877+
pass
878+
# Also test the case in which the metaclass is not a type.
879+
class BadMeta:
880+
@classmethod
881+
def __prepare__(*args):
882+
return None
883+
with self.assertRaisesRegex(TypeError,
884+
r'^<metaclass>\.__prepare__\(\) must '
885+
r'return a mapping, not NoneType$'):
886+
class Bar(metaclass=BadMeta()):
887+
pass
888+
867889
def test_metaclass_derivation(self):
868890
# issue1294232: correct metaclass calculation
869891
new_calls = [] # to check the order of __new__ calls
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Raise a `TypeError` with a helpful error message when class creation fails
2+
due to a metaclass with a bad ``__prepare__()`` method. Patch by Oren Milman.

Python/bltinmodule.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ builtin___build_class__(PyObject *self, PyObject **args, Py_ssize_t nargs,
157157
Py_DECREF(bases);
158158
return NULL;
159159
}
160+
if (!PyMapping_Check(ns)) {
161+
PyErr_Format(PyExc_TypeError,
162+
"%.200s.__prepare__() must return a mapping, not %.200s",
163+
isclass ? ((PyTypeObject *)meta)->tp_name : "<metaclass>",
164+
Py_TYPE(ns)->tp_name);
165+
goto error;
166+
}
160167
cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
161168
NULL, 0, NULL, 0, NULL, 0, NULL,
162169
PyFunction_GET_CLOSURE(func));

0 commit comments

Comments
 (0)