Skip to content

Commit

Permalink
PoC for checking both __loader__ and __spec__.loader
Browse files Browse the repository at this point in the history
In _warnings.c, in the C equivalent of warnings.warn_explicit(), if the module
globals are given (and not None), the warning will attempt to get the source
line for the issued warning.  To do this, it needs the module's loader.

Previously, it would only look up `__loader__` in the module globals.  In
python/cpython#86298 we want to defer to the
`__spec__.loader` if available.

The first step on this journey is to check that `loader` == `__spec__.loader`
and issue another warning if it is not.  This commit does that.

Since this is a PoC, only manual testing for now.

```python
import warnings

import bar

warnings.warn_explicit(
    'warning!',
    RuntimeWarning,
    'bar.py', 2,
    module='bar knee',
    module_globals=bar.__dict__,
    )
```

```python
import sys
import os
import pathlib

```

Then running this: `./python.exe -Wdefault /tmp/foo.py`

Produces:

```
bar.py:2: RuntimeWarning: warning!
  import os
```

Uncomment the `__loader__ = ` line in `bar.py` and try it again:

```
sys:1: ImportWarning: Module bar; __loader__ != __spec__.loader (<_frozen_importlib_external.SourceFileLoader object at 0x109f7dfa0> != PosixPath('.'))
bar.py:2: RuntimeWarning: warning!
  import os
```
  • Loading branch information
warsaw committed Oct 3, 2022
1 parent 873a2f2 commit 37b581a
Showing 1 changed file with 41 additions and 7 deletions.
48 changes: 41 additions & 7 deletions Python/_warnings.c
Original file line number Diff line number Diff line change
Expand Up @@ -977,25 +977,59 @@ warnings_warn_impl(PyObject *module, PyObject *message, PyObject *category,
static PyObject *
get_source_line(PyInterpreterState *interp, PyObject *module_globals, int lineno)
{
PyObject *loader;
PyObject *loader, *spec_loader;
PyObject *spec;
PyObject *module_name;
PyObject *get_source;
PyObject *source;
PyObject *source_list;
PyObject *source_line;

/* Check/get the requisite pieces needed for the loader. */
module_name = _PyDict_GetItemWithError(module_globals, &_Py_ID(__name__));
if (!module_name) {
return NULL;
}
Py_INCREF(module_name);

/* Check/get the requisite pieces needed for the loader. Get both the
__spec__.loader and __loader__ attributes and warn if they are not the
same.
*/
spec = _PyDict_GetItemWithError(module_globals, &_Py_ID(__spec__));
if (spec == NULL) {
Py_DECREF(module_name);
return NULL;
}
Py_INCREF(spec);

spec_loader = PyObject_GetAttrString(spec, "loader");
Py_DECREF(spec);
if (spec_loader == NULL) {
Py_DECREF(module_name);
return NULL;
}

loader = _PyDict_GetItemWithError(module_globals, &_Py_ID(__loader__));
if (loader == NULL) {
Py_DECREF(spec_loader);
Py_DECREF(module_name);
return NULL;
}
Py_INCREF(loader);
module_name = _PyDict_GetItemWithError(module_globals, &_Py_ID(__name__));
if (!module_name) {
Py_DECREF(loader);
return NULL;

if (spec_loader != loader) {
int error = PyErr_WarnFormat(
PyExc_ImportWarning, 2,
"Module %U; __loader__ != __spec__.loader (%R != %R)",
module_name, spec_loader, loader);
if (error < 0) {
Py_DECREF(module_name);
Py_DECREF(spec_loader);
Py_DECREF(loader);
return NULL;
}
}
Py_INCREF(module_name);
Py_DECREF(spec_loader);

/* Make sure the loader implements the optional get_source() method. */
(void)_PyObject_LookupAttr(loader, &_Py_ID(get_source), &get_source);
Expand Down

0 comments on commit 37b581a

Please sign in to comment.