-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
bpo-31588: Prevent raising a SystemError in class creation in case of a metaclass with a bad __prepare__() method #3764
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
Conversation
Lib/test/test_types.py
Outdated
| # __prepare__() must return a mapping. | ||
| class BadMeta(type): | ||
| def __prepare__(*args): | ||
| pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it would look better if add explicit return None. __prepare__() must return a value.
| if (!PyMapping_Check(ns)) { | ||
| PyErr_Format(PyExc_TypeError, | ||
| "%.200s.__prepare__() must return a mapping, not %.200s", | ||
| isclass ? ((PyTypeObject *)meta)->tp_name : "<metaclass>", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this code is as it is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that printing the name of the problematic metaclass would be helpful to the user.
Now ISTM that I should have checked the __name__ attribute.
Do you think I should add that? Or should we just not include the name of the metaclass in the error message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use ((PyTypeObject *)meta)->tp_name in all cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, if meta is not a type object, then ((PyTypeObject *)meta)->tp_name might contain arbitrary data, according to the type of meta, which might cause a crash (as indeed happens when running my second test).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what circumstances meta is not a type object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is an example:
class BadMetaclass:
def __prepare__(*args):
pass
class Bar(metaclass=BadMetaclass()):
pass
I don't know about usecases for that (though I am certainly not a metaclasses expert), but IIUC, the C code explicitly handles such cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use meta->ob_type->tp_name in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When metaclass isn't a type, it can be an arbitrary callable, so I think it's reasonable to just hardcode in <metaclass> for that case.
| @@ -0,0 +1,2 @@ | |||
| Prevent raising a SystemError in class creation in case of a metaclass with | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear what is the new behavior. Is a class created successfully?
Document that a TypeError is now raised instead of a SystemError.
| if (!PyMapping_Check(ns)) { | ||
| PyErr_Format(PyExc_TypeError, | ||
| "%.200s.__prepare__() must return a mapping, not %.200s", | ||
| isclass ? ((PyTypeObject *)meta)->tp_name : "<metaclass>", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use meta->ob_type->tp_name in this case?
| def test_bad___prepare__(self): | ||
| # __prepare__() must return a mapping. | ||
| class BadMeta(type): | ||
| def __prepare__(*args): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__prepare__ is called as a class method here. Decorate it with classmethod.
ncoghlan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual patch looks good to me, but I think the test case needs some refinement to make sure we're actually getting the desired behaviour.
Lib/test/test_types.py
Outdated
| class BadMeta(type): | ||
| def __prepare__(*args): | ||
| return None | ||
| with self.assertRaises(TypeError): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use self.assertRaisesRegex to check we're getting the error message we expect rather than a different TypeError ( like "NoneType is not subscriptable")
| @@ -0,0 +1,2 @@ | |||
| Raise a `TypeError` instead of `SystemError` in case class creation failed due | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't reproduce the reported SystemError myself - I get an unhelpfully cryptic error message, but it's a TypeError instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got the same message you got when I compiled in release mode. this is because checks that are in debug mode only, as I mentioned in https://bugs.python.org/issue31588#msg303122.
So without the patch we have a TypeError in release mode and a SystemError in debug mode.
I would change the phrasing of the NEWS.d item accordingly.
| if (!PyMapping_Check(ns)) { | ||
| PyErr_Format(PyExc_TypeError, | ||
| "%.200s.__prepare__() must return a mapping, not %.200s", | ||
| isclass ? ((PyTypeObject *)meta)->tp_name : "<metaclass>", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When metaclass isn't a type, it can be an arbitrary callable, so I think it's reasonable to just hardcode in <metaclass> for that case.
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
| @@ -0,0 +1,2 @@ | |||
| Raise a `TypeError` with a helpful error message in case class creation failed | |||
| due to a metaclass with a bad ``__prepare__()`` method. Patch by Oren Milman. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I be more specific here?
i.e. it could be:
... due to a metaclass whose __prepare__() method returned a non-mapping object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your current wording is fine.
|
I didn't expect the Spanish Inquisition! |
|
Nobody expects the Spanish Inquisition! @ncoghlan: please review the changes made to this pull request. |
| @@ -0,0 +1,2 @@ | |||
| Raise a `TypeError` with a helpful error message in case class creation failed | |||
| due to a metaclass with a bad ``__prepare__()`` method. Patch by Oren Milman. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your current wording is fine.
|
GH-3790 is a backport of this pull request to the 3.6 branch. |
…onGH-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. (cherry picked from commit 5837d04)
bltinmodule.c: add a check whether__prepare__()returned a mapping.test_types.py: add tests to verify that theSystemErroris no more.https://bugs.python.org/issue31588