Skip to content
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

bpo-29546: Improve from-import error message with location #103

Merged
merged 3 commits into from
Feb 22, 2017

Conversation

Carreau
Copy link
Contributor

@Carreau Carreau commented Feb 15, 2017

Add location information like canonical module name where identifier
cannot be found and file location if available.

First iteration of this was gh-91


Testless for now, I can add tests once the exact format has been agreed upon.

Python/ceval.c Outdated
@@ -5022,12 +5022,23 @@ import_from(PyObject *v, PyObject *name)
return x;
error:
pkgpath = PyModule_GetFilenameObject(v);
if (name == NULL){
Copy link
Member

Choose a reason for hiding this comment

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

Missing a space between ) and {.

Copy link
Member

@warsaw warsaw left a comment

Choose a reason for hiding this comment

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

Yep, that's exactly the format I was thinking! Great work.

@warsaw
Copy link
Member

warsaw commented Feb 15, 2017

Python 3.7.0a0 (default, Feb 15 2017, 14:30:57) 
[GCC 6.3.0 20161229] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from os import floop
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'floop' from 'os' (/home/barry/projects/python/cpython/Lib/os.py)
>>> import snarp
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'snarp'
>>> 
>>> from _opcode import fip
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'fip' from '_opcode' (/home/barry/projects/python/cpython/build/lib.linux-x86_64-3.7/_opcode.cpython-37m-x86_64-linux-gnu.so)
>>> 

@Carreau
Copy link
Contributor Author

Carreau commented Feb 15, 2017

Yep, that's exactly the format I was thinking! Great work.

Good, and thanks. I'll cleanup and add tests then !

@brettcannon brettcannon added the type-feature A feature request or enhancement label Feb 15, 2017
@Carreau
Copy link
Contributor Author

Carreau commented Feb 16, 2017

Added tests, Misc/NEWS, What's new.

The only thing I'm unsure is what the module's __file__ extensions is on windows. I'm guessing I can relax the regex in the test. I've assumed .dll.

It's also a bit ugly if a module has neither name not a file:

"cannot import name 'does_not_exist' from '<unknown module name>' (unknown location)"

But that seem to be rare from how it's currently tested:

class AlwaysAttributeError:
            def __getattr__(self, _):
                raise AttributeError

module_name = 'test_from_import_AttributeError'
self.addCleanup(unload, module_name)
sys.modules[module_name] = AlwaysAttributeError()

@warsaw
Copy link
Member

warsaw commented Feb 16, 2017 via email

Copy link
Member

@warsaw warsaw left a comment

Choose a reason for hiding this comment

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

Please review the potential reference counting bugs.

Python/ceval.c Outdated
pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
} else {
pkgname_or_unknown = pkgname;
}
Copy link
Member

Choose a reason for hiding this comment

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

I think there are some reference counting bugs lurking here.

  • It's possible the code jumps to error: after pkgname is decref'd. I think that decref should be deferred until just before the good-path return.
  • pkgname_or_unknown will be a new reference if pkgname is NULL, or it will steal the pkgname reference if it's not. In either case, the error stanza should decref pkgname_or_unknown, which will take care of ensuring the object gets decref for any goto error path.
  • pkgpath doesn't get decref'd. I think you should Py_XDECREF it before the return NULL just in case pkgpath is itself NULL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the detailed explanation, I still need to get used to do manual ref counting.
I've push changes. Let me know if I got things rights.

Copy link
Member

Choose a reason for hiding this comment

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

@Carreau Yes, I think the refcounting is right now. It can sometimes be difficult to trace all the various exit paths to make sure that everything gets decref'd and incref'd properly, but as best I can tell, you've got it right now. Thanks!

The only other thing is that it's possible for the PyUnicode_FromString("<unknown module name>") to fail and return NULL. Probably the only realistic cause of that would be a memory error (not sure if an encoding error could do it given that this is an ascii C string). If you want to be pedanditc, it would be useful to check for that error condition and do something more reasonable in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you want to be pedanditc, it would be useful to check for that error condition and do something more reasonable in that case

I'm unsure what "more reasonable" is, I check and failed with the original error message in that case, but I don't see how if allocating PyUnicode_FromString("<unknown module name>") fails we can do something sensible.

Copy link
Member

Choose a reason for hiding this comment

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

Probably the best you can do is to just decref any objects that may still have a reference count, and then immediately return NULL. The PyUnicode_FromString failure would have already set an exception, so that's the one you'll see. Something like:

1 file changed, 4 insertions(+)
Python/ceval.c | 4 ++++

modified   Python/ceval.c
@@ -5024,6 +5024,10 @@ import_from(PyObject *v, PyObject *name)
     pkgpath = PyModule_GetFilenameObject(v);
     if (pkgname == NULL) {
         pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
+        if (pkgname_or_unknown == NULL) {
+            Py_XDECREF(pkgpath);
+            return NULL;
+        }
     } else {
         pkgname_or_unknown = pkgname;
     }

Copy link
Member

@warsaw warsaw left a comment

Choose a reason for hiding this comment

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

Semicolons to end lines.

Please check for PyUnicode_FromString() returning NULL.

Python/ceval.c Outdated
}

Py_XDECREF(pkgname_or_unknown)
Py_XDECREF(pkgpath)
Copy link
Member

Choose a reason for hiding this comment

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

Oh whoops, these lines need to end with semicolons.

@Carreau
Copy link
Contributor Author

Carreau commented Feb 20, 2017

semicolon added

Copy link
Member

@warsaw warsaw left a comment

Choose a reason for hiding this comment

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

Two more small comments, but I think this is really close!

Python/ceval.c Outdated
pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
} else {
pkgname_or_unknown = pkgname;
}
Copy link
Member

Choose a reason for hiding this comment

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

Probably the best you can do is to just decref any objects that may still have a reference count, and then immediately return NULL. The PyUnicode_FromString failure would have already set an exception, so that's the one you'll see. Something like:

1 file changed, 4 insertions(+)
Python/ceval.c | 4 ++++

modified   Python/ceval.c
@@ -5024,6 +5024,10 @@ import_from(PyObject *v, PyObject *name)
     pkgpath = PyModule_GetFilenameObject(v);
     if (pkgname == NULL) {
         pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
+        if (pkgname_or_unknown == NULL) {
+            Py_XDECREF(pkgpath);
+            return NULL;
+        }
     } else {
         pkgname_or_unknown = pkgname;
     }

Python/ceval.c Outdated

if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) {
if (pkgname_or_unknown == NULL){
Copy link
Member

Choose a reason for hiding this comment

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

You need a space between the paren and open brace.

Add location information like canonical module name where identifier
cannot be found and file location if available.

First iteration of this was pythongh-91
from Barry Warsaw:

- It's possible the code jumps to error: after pkgname is decref'd. I
  think that decref should be deferred until just before the good-path
  return.

- pkgname_or_unknown will be a new reference if pkgname is NULL, or it
  will steal the pkgname reference if it's not. In either case, the error
  stanza should decref pkgname_or_unknown, which will take care of
  ensuring the object gets decref for any goto error path.

- pkgpath doesn't get decref'd. I think you should Py_XDECREF it before
  the return NULL just in case pkgpath is itself NULL.
@Carreau
Copy link
Contributor Author

Carreau commented Feb 22, 2017

Comments should be addressed. Thanks !

@warsaw
Copy link
Member

warsaw commented Feb 22, 2017

@Carreau Thanks, this LGTM now. I'm pushing the big green button.

@warsaw warsaw merged commit 1bc1564 into python:master Feb 22, 2017
@Carreau Carreau deleted the import-messages branch February 22, 2017 15:50
@Carreau
Copy link
Contributor Author

Carreau commented Feb 22, 2017

Thanks !

akruis pushed a commit to akruis/cpython that referenced this pull request Sep 9, 2017
Fix an invalid assertion during interpreter shutdown and add
a test case for this problem.

https://bitbucket.org/stackless-dev/stackless/issues/103
(grafted from a2f660260c93e911fde4969dac79c9eb6cc03e72)
akruis pushed a commit to akruis/cpython that referenced this pull request Sep 9, 2017
akruis pushed a commit to akruis/cpython that referenced this pull request Sep 9, 2017
akruis pushed a commit to akruis/cpython that referenced this pull request Sep 9, 2017
The reworked C-function slp_kill_tasks_with_stacks() didn't work correctly
for deeply nested tasklets. Therefore I had to rework it again. This version
first kills a tasklet and only then clears the tstate of its cstacks.
I added several new test cases and reorganised them a bit.

https://bitbucket.org/stackless-dev/stackless/issues/103
(grafted from 130a60b8d3719e06807acd7e9b63c07bd5435c32)
akruis pushed a commit to akruis/cpython that referenced this pull request Sep 9, 2017
akruis pushed a commit to akruis/cpython that referenced this pull request Sep 9, 2017
jaraco pushed a commit that referenced this pull request Dec 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants