8000 gh-135801: Fix inaccurate module info for SyntaxWarnings during AST parsing by heliang666s · Pull Request #135829 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-135801: Fix inaccurate module info for SyntaxWarnings during AST parsing #135829

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Include/internal/pycore_pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ extern PyObject* _PyErr_NoMemory(PyThreadState *tstate);

extern int _PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset);
extern int _PyErr_EmitSyntaxWarningFromCompiler(PyObject *msg, PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset);
extern void _PyErr_RaiseSyntaxError(PyObject *msg, PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset);

Expand Down
40 changes: 31 additions & 9 deletions Lib/test/test_warnings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from test.support import import_helper
from test.support import os_helper
from test.support import warnings_helper
from test.support import script_helper
from _pyrepl.completing_reader import stripcolor as strip_ansi
from test.support import force_not_colorized
from test.support.script_helper import assert_python_ok, assert_python_failure

Expand Down Expand Up @@ -771,15 +773,35 @@ def test_improper_input(self):
self.assertRaises(UserWarning, self.module.warn, 'convert to error')

def test_import_from_module(self):
with self.module.catch_warnings():
self.module._setoption('ignore::Warning')
with self.assertRaises(self.module._OptionError):
self.module._setoption('ignore::TestWarning')
with self.assertRaises(self.module._OptionError):
self.module._setoption('ignore::test.test_warnings.bogus')
self.module._setoption('error::test.test_warnings.TestWarning')
with self.assertRaises(TestWarning):
self.module.warn('test warning', TestWarning)
with os_helper.temp_dir() as script_dir:
script = script_helper.make_script(script_dir,
'test_warnings_importer',
'import test.test_warnings.data.import_warning')
rc, out, err = assert_python_ok(script)
self.assertNotIn(b'UserWarning', err)

def test_syntax_warning_for_compiler(self):
# Test that SyntaxWarning from the compiler has a proper module name,
# not a guessed one like 'sys'. gh-135801
code = textwrap.dedent("""\
class A:
def func(self):
return self.var is 2
""")
# The name of the script is 'test_sw'
with os_helper.temp_dir() as script_dir:
script_name = script_helper.make_script(script_dir, 'test_sw', code)
# We want to check that the warning filter for 'test_sw' module works.
rc, out, err = assert_python_failure("-W", "error::SyntaxWarning:test_sw",
script_name)
self.assertEqual(rc, 1)
self.assertEqual(out, b'')
# Check that we got a SyntaxError.
err = err.decode()
err = strip_ansi(err)
self.assertIn("""SyntaxError: "is" with 'int' literal. Did you mean "=="?""", err)
# Check that the filename in the traceback is correct.
self.assertIn(os.path.basename(script_name), err)


class CWCmdLineTests(WCmdLineTests, unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix warning deduplication regression for SyntaxWarnings with pseudo-filenames (e.g., <stdin>, <string>).
69 changes: 48 additions & 21 deletions Python/_warnings.c
Original file line number Diff line number Diff line change
Expand Up @@ -616,33 +616,60 @@ already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key,
static PyObject *
normalize_module(PyObject *filename)
{
PyObject *module;
int kind;
const void *data;
Py_ssize_t len;

len = PyUnicode_GetLength(filename);
if (len < 0)
PyObject *module_name = NULL;
PyObject *os_path = NULL;
PyObject *basename = NULL;
PyObject *splitext = NULL;
PyObject *root = NULL;

os_path = PyImport_ImportModule("os.path");
if (os_path == NULL) {
return NULL;
}

if (len == 0)
return PyUnicode_FromString("<unknown>");
basename = PyObject_CallMethod(os_path, "basename", "O", filename);
if (basename == NULL) {
goto cleanup;
}

kind = PyUnicode_KIND(filename);
data = PyUnicode_DATA(filename);
splitext = PyObject_CallMethod(os_path, "splitext", "O", basename);
if (splitext == NULL) {
goto cleanup;
}

/* if filename.endswith(".py"): */
if (len >= 3 &&
PyUnicode_READ(kind, data, len-3) == '.' &&
PyUnicode_READ(kind, data, len-2) == 'p' &&
PyUnicode_READ(kind, data, len-1) == 'y')
{
module = PyUnicode_Substring(filename, 0, len-3);
root = PyTuple_GetItem(splitext, 0);
if (root == NULL) {
goto cleanup;
}
else {
module = Py_NewRef(filename);

if (PyUnicode_CompareWithASCIIString(root, "__init__") == 0) {
PyObject *dirname = PyObject_CallMethod(os_path, "dirname", "O", filename);
if (dirname == NULL) {
goto cleanup;
}
module_name = PyObject_CallMethod(os_path, "basename", "O", dirname);
Py_DECREF(dirname);
} else {
module_name = Py_NewRef(root);
}

cleanup:
Py_XDECREF(os_path);
Py_XDECREF(basename);
Py_XDECREF(splitext);

if (module_name == NULL) {
// Fallback or error occurred
PyErr_Clear();
return PyUnicode_FromString("<unknown>");
}
return module;

if (PyUnicode_GetLength(module_name) == 0) {
Py_DECREF(module_name);
return PyUnicode_FromString("<unknown>");
}

return module_name;
}

static int
Expand Down
4 changes: 2 additions & 2 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1210,8 +1210,8 @@ _PyCompile_Warn(compiler *c, location loc, const char *format, ...)
if (msg == NULL) {
return ERROR;
}
int ret = _PyErr_EmitSyntaxWarning(msg, c->c_filename, loc.lineno, loc.col_offset + 1,
loc.end_lineno, loc.end_col_offset + 1);
int ret = _PyErr_EmitSyntaxWarningFromCompiler(msg, c->c_filename, loc.lineno, loc.col_offset + 1,
loc.end_lineno, loc.end_col_offset + 1);
Py_DECREF(msg);
return ret;
}
Expand Down
34 changes: 33 additions & 1 deletion Python/errors.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/* Error handling */

#include "Python.h"
Expand Down Expand Up @@ -1952,6 +1951,39 @@ _PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_
return 0;
}

/* Emits a SyntaxWarning and returns 0 on success.
If a SyntaxWarning is raised as error, replaces it with a SyntaxError
and returns -1.
This version is for the compiler, and derives the module from the filename.
*/
int
_PyErr_EmitSyntaxWarningFromCompiler(PyObject *msg, PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset)
{
Copy link
Member

Choose a reason for hiding this comment

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

_PyErr_EmitSyntaxWarning is called from compile.c and ast_preprocess.c. Arguably, these are both parts of "the compiler". Why do we need to introduce a new function for this rather than fix _PyErr_EmitSyntaxWarning?

Py_ssize_t len = PyUnicode_GET_LENGTH(filename);
if (len > 1 &&
PyUnicode_READ_CHAR(filename, 0) == '<' &&
PyUnicode_READ_CHAR(filename, len - 1) == '>')
{
return _PyErr_EmitSyntaxWarning(msg, filename, lineno, col_offset,
end_lineno, end_col_offset);
}

if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg,
filename, lineno, NULL, NULL) < 0)
{
if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
/* Replace the SyntaxWarning exception with a SyntaxError
to get a more accurate error report */
PyErr_Clear();
_PyErr_RaiseSyntaxError(msg, filename, lineno, col_offset,
end_lineno, end_col_offset);
}
return -1;
}
return 0;
}

/* Attempt to load the line of text that the exception refers to. If it
fails, it will return NULL but will not set an exception.
45DD Expand Down
Loading
0