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-32932: More revealing error message when non-str objects in __all__ #5848

Merged
merged 11 commits into from
Mar 24, 2018

Conversation

zhangyangyu
Copy link
Member

@zhangyangyu zhangyangyu commented Feb 24, 2018

Python/ceval.c Outdated
if (skip_leading_underscores && PyUnicode_Check(name)) {
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"Item in __all__ must be str, not %.100s",
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't be better to include the module name as in Python version?

If skip_leading_underscores is true, the source of names is __dict__, not __all__.

Copy link
Member Author

@zhangyangyu zhangyangyu Feb 24, 2018

Choose a reason for hiding this comment

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

Of course it would be, but I hesitate. Is v always a module and I can use PyModule_GetName then? Or using __name__? __name__ could be altered.

@zhangyangyu zhangyangyu requested a review from a team February 24, 2018 16:27
Python/ceval.c Outdated
break;
}
PyErr_Format(PyExc_TypeError,
"%s in %U.%s must be str, not %.100s",
Copy link
Member

Choose a reason for hiding this comment

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

%U will fail if modname is not a string.

@@ -111,6 +111,27 @@ def test_from_import_missing_attr_path_is_canonical(self):
self.assertIn(cm.exception.name, {'posixpath', 'ntpath'})
self.assertIsNotNone(cm.exception)

def test_from_import_star_invalid_type(self):
TESTFNnoat = TESTFN[1:]
source = TESTFNnoat + os.extsep + "py"
Copy link
Member

Choose a reason for hiding this comment

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

There are no guaranties that TESTFN without the first character is a valid identifier or that it doesn't conflict with the name of a standard module. It would be safer to use TESTFN as the name of a directory name where put the Python file with predefined name (or different names for different cases). Or even use test.support.temp_dir().

sys.path.insert(0, os.curdir)
try:
with open(source, "w") as f:
f.write("__all__ = [1]")
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps None or a bytes object are more realistic examples.

f.write("__all__ = [1]")
with self.assertRaisesRegex(
TypeError, f"{TESTFNnoat}.__all__ must be str"):
exec(f"from {TESTFNnoat} import *")
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't be safer to pass locals and globals dicts? And you can check that the import doesn't have effect on them.

exec(f"from {TESTFNnoat} import *")
unload(TESTFNnoat)
with open(source, "w") as f:
f.write("import sys\nsys.modules[__name__].__dict__[1] = 1")
Copy link
Member

Choose a reason for hiding this comment

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

Whouldn't be easier to use just globals()?

def test_from_import_star_invalid_type(self):
TESTFNnoat = TESTFN[1:]
source = TESTFNnoat + os.extsep + "py"
sys.path.insert(0, os.curdir)
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps there is a special purposed helper in test.support or test.test_importlib.util for temporary modifying sys.path and safely clean up.

@@ -111,6 +111,26 @@ def test_from_import_missing_attr_path_is_canonical(self):
self.assertIn(cm.exception.name, {'posixpath', 'ntpath'})
self.assertIsNotNone(cm.exception)

def test_from_import_star_invalid_type(self):
with _ready_to_import() as (name, path):
Copy link
Member

Choose a reason for hiding this comment

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

Oh, there was a ready helper in this file! Nice.

Python/ceval.c Outdated
break;
}
PyErr_Format(PyExc_TypeError,
"%s in %S.%s must be str, not %.100s",
Copy link
Member

Choose a reason for hiding this comment

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

%S can emit a warning or fail if modname is bytes. :(

Copy link
Member Author

@zhangyangyu zhangyangyu Feb 27, 2018

Choose a reason for hiding this comment

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

Hmm, it's right. We could limit __name__ to str here. It's just another check. But the warning or failure won't cause the program to crash and they are hints for the programmer there is something not good in your program: do you really want __name__ a bytes object? Otherwise to avoid the warning or failure, any place using PyObject_Str should come after a comparison like !PyBytes_Check. _handle_fromlist could fail either.

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 this is a consenting adults thing where if you go so far as to set an object's name to a bytes object then you're already asking for trouble.

Copy link
Member

Choose a reason for hiding this comment

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

Isn't this true also for non-str objects in __all__?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but one is more possible than another. Anyway, adding a check here isn't too difficult so I'm fine asking for a guard.

f.write("__all__ = [b'invalid_type']")
globals = {}
with self.assertRaisesRegex(
TypeError, f"{name}.__all__ must be str"
Copy link
Member

Choose a reason for hiding this comment

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

The string is a regular expression, . should be escaped. And since name is just "spam", it is safe to substitute it. But in general case you could need to escape all metacharacters in it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. I'll change it.

@zhangyangyu
Copy link
Member Author

Okay, adding a check is not hard, so let's add it.

Python/ceval.c Outdated
}
else {
PyErr_Format(PyExc_TypeError,
"%s in %S.%s must be str, not %.100s",
Copy link
Member

Choose a reason for hiding this comment

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

%U can be used instead of %S.

Python/ceval.c Outdated
}
if (!PyUnicode_Check(modname)) {
PyErr_Format(PyExc_TypeError,
"__name__ must be a string, not %.100s",
Copy link
Member

Choose a reason for hiding this comment

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

This message can be unclear. What if add "module" before "__name__"?

Python/ceval.c Outdated
@@ -4859,12 +4859,12 @@ import_all_from(PyObject *locals, PyObject *v)
}
if (!PyUnicode_Check(modname)) {
PyErr_Format(PyExc_TypeError,
"__name__ must be a string, not %.100s",
"module.__name__ must be a string, not %.100s",
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't be better to replace . with a space?

Copy link
Member Author

Choose a reason for hiding this comment

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

why? I don't get the point.

Copy link
Member

Choose a reason for hiding this comment

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

Because "module" is not a name of any object.

But it was just a question. I don't know what is better in English. If . looks better to you, keep it.

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 either is fine in practice, but leaving . out would technically be more accurate (since we're using "module" as an ordinary English word, rather than as a variable name)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks!

@zhangyangyu zhangyangyu merged commit d8b291a into python:master Mar 24, 2018
@zhangyangyu zhangyangyu deleted the issue32932 branch March 24, 2018 10:39
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.

6 participants