From 2ff8495f72ff2f69e4a78727f8a97d6dd8821ea5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 7 Aug 2026 12:39:47 +0300 Subject: [PATCH] gh-155319: Fix the source line of a warning issued with module_globals warn_explicit() computed the source line from the loader of the module whose globals were passed as module_globals, but did not pass it to WarningMessage. It also raised IndexError if lineno was out of the range of the module source. --- Lib/test/test_warnings/__init__.py | 31 +++++++++++++++++++ ...08-07-02-20-00.gh-issue-155319.srcline.rst | 4 +++ Python/_warnings.c | 10 ++++-- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index bf1bcf8e6ed5d9a..cde6076dd2f44b7 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1661,6 +1661,37 @@ def test_RuntimeError(self): _version=version) +class WarnExplicitSourceTests(BaseTest): + # gh-155319: the source line is taken from the loader of the module + # whose globals are passed as module_globals, if the file which name + # is passed as filename cannot be read. + + def warn_explicit(self, lineno): + with support.captured_stderr() as stderr: + with self.module.catch_warnings(): + self.module.simplefilter("always") + self.module.warn_explicit( + 'eggs', UserWarning, 'nonexistent', lineno, + module_globals=warning_tests.__dict__) + return stderr.getvalue() + + def test_source_line(self): + source = warning_tests.__loader__.get_source(warning_tests.__name__) + expected = source.splitlines()[0].strip() + self.assertEqual(self.warn_explicit(1), + f'nonexistent:1: UserWarning: eggs\n {expected}\n') + + def test_source_line_out_of_range(self): + self.assertEqual(self.warn_explicit(1000), + 'nonexistent:1000: UserWarning: eggs\n') + +class CWarnExplicitSourceTests(WarnExplicitSourceTests, unittest.TestCase): + module = c_warnings + +class PyWarnExplicitSourceTests(WarnExplicitSourceTests, unittest.TestCase): + module = py_warnings + + class BootstrapTest(unittest.TestCase): def test_issue_8766(self): diff --git a/Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst b/Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst new file mode 100644 index 000000000000000..34a7ff83a73c0b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst @@ -0,0 +1,4 @@ +:func:`warnings.warn_explicit` now displays the source line taken from the +loader of the module whose globals are passed as *module_globals*. It also +no longer raises :exc:`IndexError` if *lineno* is out of the range of the +module source. diff --git a/Python/_warnings.c b/Python/_warnings.c index 4f6de50efa14a8e..992c3e50f2a9f67 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -747,7 +747,8 @@ call_show_warning(PyThreadState *tstate, PyObject *category, } msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category, - filename, lineno_obj, Py_None, Py_None, + filename, lineno_obj, Py_None, + sourceline ? sourceline : Py_None, source ? source : Py_None, module, NULL); Py_DECREF(warnmsg_cls); @@ -1234,8 +1235,11 @@ get_source_line(PyInterpreterState *interp, PyObject *module_globals, int lineno } /* Get the source line. */ - source_line = PyList_GetItem(source_list, lineno-1); - Py_XINCREF(source_line); + if (lineno < 1 || lineno > PyList_GET_SIZE(source_list)) { + Py_DECREF(source_list); + return NULL; + } + source_line = Py_NewRef(PyList_GET_ITEM(source_list, lineno-1)); Py_DECREF(source_list); return source_line; }