]> granicus.if.org Git - python/commitdiff
[3.6] bpo-31588: Validate return value of __prepare__() methods (GH-3790)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 27 Sep 2017 16:21:33 +0000 (09:21 -0700)
committerNick Coghlan <ncoghlan@gmail.com>
Wed, 27 Sep 2017 16:21:33 +0000 (02:21 +1000)
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.
(cherry picked from commit 5837d0418f47933b2e3c139bdee8a79c248a943c)

Lib/test/test_types.py
Misc/NEWS.d/next/Core and Builtins/2017-09-26-13-03-16.bpo-31588.wT9Iy7.rst [new file with mode: 0644]
Python/bltinmodule.c

index 382ca03e5ad2b8deb843b504d5ad55b0e9c9ec6e..d79429faf118f6b949fd344ebc73f0e396e79ab8 100644 (file)
@@ -846,6 +846,28 @@ class ClassCreationTests(unittest.TestCase):
         self.assertIs(ns, expected_ns)
         self.assertEqual(len(kwds), 0)
 
+    def test_bad___prepare__(self):
+        # __prepare__() must return a mapping.
+        class BadMeta(type):
+            @classmethod
+            def __prepare__(*args):
+                return None
+        with self.assertRaisesRegex(TypeError,
+                                    r'^BadMeta\.__prepare__\(\) must '
+                                    r'return a mapping, not NoneType$'):
+            class Foo(metaclass=BadMeta):
+                pass
+        # Also test the case in which the metaclass is not a type.
+        class BadMeta:
+            @classmethod
+            def __prepare__(*args):
+                return None
+        with self.assertRaisesRegex(TypeError,
+                                    r'^<metaclass>\.__prepare__\(\) must '
+                                    r'return a mapping, not NoneType$'):
+            class Bar(metaclass=BadMeta()):
+                pass
+
     def test_metaclass_derivation(self):
         # issue1294232: correct metaclass calculation
         new_calls = []  # to check the order of __new__ calls
diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-09-26-13-03-16.bpo-31588.wT9Iy7.rst b/Misc/NEWS.d/next/Core and Builtins/2017-09-26-13-03-16.bpo-31588.wT9Iy7.rst
new file mode 100644 (file)
index 0000000..44a7aba
--- /dev/null
@@ -0,0 +1,2 @@
+Raise a `TypeError` with a helpful error message when class creation fails
+due to a metaclass with a bad ``__prepare__()`` method. Patch by Oren Milman.
index 911e2ba79ef7304a23b613299e03ec6057c2ff00..1960a095b8d3ff9ae8d90d794e94d4a09bf3b259 100644 (file)
@@ -167,6 +167,13 @@ builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
         Py_DECREF(bases);
         return NULL;
     }
+    if (!PyMapping_Check(ns)) {
+        PyErr_Format(PyExc_TypeError,
+                     "%.200s.__prepare__() must return a mapping, not %.200s",
+                     isclass ? ((PyTypeObject *)meta)->tp_name : "<metaclass>",
+                     Py_TYPE(ns)->tp_name);
+        goto error;
+    }
     cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
                              NULL, 0, NULL, 0, NULL, 0, NULL,
                              PyFunction_GET_CLOSURE(func));