Skip to content

Conversation

@orenmn
Copy link
Contributor

@orenmn orenmn commented Sep 26, 2017

  • in bltinmodule.c: add a check whether __prepare__() returned a mapping.
  • in test_types.py: add tests to verify that the SystemError is no more.

https://bugs.python.org/issue31588

# __prepare__() must return a mapping.
class BadMeta(type):
def __prepare__(*args):
pass
Copy link
Member

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>",
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

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?

Copy link
Contributor Author

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).

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor

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
Copy link
Member

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>",
Copy link
Member

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):
Copy link
Member

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.

Copy link
Contributor

@ncoghlan ncoghlan left a 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.

class BadMeta(type):
def __prepare__(*args):
return None
with self.assertRaises(TypeError):
Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor Author

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>",
Copy link
Contributor

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.

@bedevere-bot
Copy link

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 I didn't expect the Spanish Inquisition!. I will then notify any core developers who have left a review that you're ready for them to take another look at 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.
Copy link
Contributor Author

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.

Copy link
Contributor

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.

@orenmn
Copy link
Contributor Author

orenmn commented Sep 27, 2017

I didn't expect the Spanish Inquisition!

@bedevere-bot
Copy link

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.
Copy link
Contributor

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.

@ncoghlan ncoghlan merged commit 5837d04 into python:master Sep 27, 2017
@miss-islington
Copy link
Contributor

Thanks @orenmn for the PR, and @ncoghlan for merging it 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@bedevere-bot
Copy link

GH-3790 is a backport of this pull request to the 3.6 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 27, 2017
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants