Skip to content

bpo-34284: Nonsensical exception message when calling __new__ on non-instantiable objects #8576

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

Closed
wants to merge 5 commits into from

Conversation

VadimPushtaev
Copy link
Contributor

@VadimPushtaev VadimPushtaev commented Jul 30, 2018

@pppery
Copy link

pppery commented Jul 30, 2018

Please don't combine patches for two issues in one PR

@VadimPushtaev
Copy link
Contributor Author

VadimPushtaev commented Jul 30, 2018

Please don't combine patches for two issues in one PR

That was unintentional. Fixed.

@@ -5885,12 +5885,24 @@ tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds)
staticbase = staticbase->tp_base;
/* If staticbase is NULL now, it is a really weird type.
In the spirit of backwards compatibility (?), just shut up. */
char extra[10] = "normal";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be dead code

@pppery
Copy link

pppery commented Jul 30, 2018

Also, I disagree with this method of fixing it, as I explained on the BPO issue

object.__new__(type(sys.flags))
message = 'object.__new__(sys.flags) is not safe'
self.assertEqual(str(cm.exception), message)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for tuple.__new__(type(sys.flags))?

Copy link
Contributor Author

@VadimPushtaev VadimPushtaev Aug 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's easy, but I'm still not sure what is the right behavior here. I need time to read through bpo-31506 to comprehend the problem.

@pppery
Copy link

pppery commented Aug 5, 2018

Discussion on the BPO issue seems to be suggesting a different method of fixing

@pppery
Copy link

pppery commented Aug 8, 2018

I think that this patch makes the "if(type->tp_new == null)" check in type_call unnecessary

@pppery
Copy link

pppery commented Aug 8, 2018

This also seems like it won't fix the bug for type(sys.flags), which has its own problem

@VadimPushtaev
Copy link
Contributor Author

Thanks for your comments.

This also seems like it won't fix the bug for type(sys.flags), which has its own problem

Exactly, there is more than one reason why tp_new can be null. That's why we can't remove if(type->tp_new == null) yet.

I know that there is still work to do. I need to write some tests, check all places where tp_new is compared to null (there are numerous of them), repair sys.flags (and similar objects).

Now I just want to now that I'm following the right path.

@@ -0,0 +1 @@
NullNew for old extension types instead of just null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This NEWS entry could be worded clearer

@VadimPushtaev
Copy link
Contributor Author

More changes:

  • News reworded.
  • sys.flags is fixed, as well as other tuples of sys
  • Bunch of necessary tests

@VadimPushtaev
Copy link
Contributor Author

I'm also not sure what to do with all that tp_new == NULL. We can't guarantee there are no more types that are written the same way as sys.flags. Can we just fix what is initially reported in the issue and leave the rest alone?

Copy link

@pppery pppery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug I initially reported would continue to appear for all types whose tp_new == NULL, which is why I suggested removing checks for that case: better to fail louder then let a cryptic error-handling bug go unnoticed. (Although I don't expect such a change to happen) Yes, this change could just happen, but that will still leave the potential for buggy types and make the code (in my opinion -- I expect to be disagreed with here) less clean. Also, the test you added testing sys.flags should probably also test the other types you fixed and live in test_sys

@VadimPushtaev
Copy link
Contributor Author

Also, the test you added testing sys.flags should probably also test the other types you fixed and live in test_sys

Less ambitious tests for this were already there, so it makes perfect sense. Fixed.

@VadimPushtaev
Copy link
Contributor Author

_curses_panel.panel aka curses.panel.panel is fixed as well.

@VadimPushtaev
Copy link
Contributor Author

_tkinter fixed.

@VadimPushtaev
Copy link
Contributor Author

Now we can remove that tp_new == NULL check. It can be done fairly simple:

--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -912,12 +912,10 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
 {
     PyObject *obj;

-    if (type->tp_new == NULL) {
-        PyErr_Format(PyExc_TypeError,
-                     "cannot create '%.100s' instances",
-                     type->tp_name);
-        return NULL;
-    }
+    /* tp_new cannot be NULL,
+       if you want to prevent class instantiation
+       use PyType_NullNew as tp_new instead */
+    assert(type->tp_new != NULL);

The Python test suite still passes with that change.

However, in that case, we should update docs as well. The current behavior is documented at least here:

What about custom user types? Is it OK to break C API between major releases? Sholdn't we depreceate it first?

I need advice.

Copy link

@pppery pppery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the title of this PR should probably be changed

@VadimPushtaev VadimPushtaev changed the title bpo-34284: Nonsensical exception message when calling __new__ on some sys objects bpo-34284: Nonsensical exception message when calling __new__ on objects with tp_new == NULL Sep 5, 2018
@VadimPushtaev VadimPushtaev changed the title bpo-34284: Nonsensical exception message when calling __new__ on objects with tp_new == NULL bpo-34284: Nonsensical exception message when calling __new__ on non-instantiable objects Sep 6, 2018
Copy link
Contributor

@asvetlov asvetlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds a new public PyType_NullNew API.
I doubt if we need it, the problem can be solved by with static private functions

@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 have made the requested changes; please review again. 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.

@VadimPushtaev
Copy link
Contributor Author

@asvetlov, thanks for the review.

First, I'm still looking for an answer for #8576 (comment). Should we allow type->tp_new == NULL?

Second, if we don't expose PyType_NullNew as a public function, we don't allow third-party libraries to make classes with the undefined __new__. Is it OK?

@asvetlov
Copy link
Contributor

asvetlov commented Dec 5, 2018

Allowing tp_new == NULL is also a big change.
I don't say the change is not acceptable but we need a strong motivation for introducing it.

From my understanding all you need is:

static PyObject *
_curses_panel_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyErr_string(PyExc_TypeError,
                 "'curses.panel.panel() is forbidden, please use curses.panel.new_panel(win)");
    return NULL;
}

The error text can be discussed and tuned but you've got my idea.

@pppery
Copy link

pppery commented Dec 5, 2018

@asvetlov tp_new == NULL is what is currently allowed. This patch is proposing not allowing it

Why should trying to instantiate _curses_panel.panel produce a different error message than trying to instantiate any other currently tp_new == NULL type, like generator for example?

Not providing a PyType_NullNew function would have the effect of requiring each module that wants to create a heaptype that can't be instantiated by calling it to duplicate its code, a task that seems to have no benefit.

@asvetlov
Copy link
Contributor

asvetlov commented Dec 5, 2018

Sorry, I have to re-read the thread to get restore the PR context first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants