From 2f3032404e2f0222ebdacab29be0cc43ef1a38ea Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Sun, 10 Aug 2025 19:53:24 -0400 Subject: [PATCH 1/6] attempt to fix issue with incorrect reporting of source code from PYTHON_BASIC_REPL --- Python/pythonrun.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 8f1c78bf831863..21336867915f45 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1365,6 +1365,25 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py return PyEval_EvalCode((PyObject*)co, globals, locals); } +PyObject * +get_interactive_filename(PyObject *filename, Py_ssize_t count){ + PyObject *result; + Py_ssize_t len = PyUnicode_GET_LENGTH(filename); + + if (len >= 2 + && PyUnicode_ReadChar(filename, 0) == '<' + && PyUnicode_ReadChar(filename, len - 1) == '>') { + PyObject *middle = PyUnicode_Substring(filename, 1, len-1); + result = PyUnicode_FromFormat("<%U-%d>", middle, count); + Py_DECREF(middle); + } else { + result = PyUnicode_FromFormat( + "%U-%d", filename, count); + } + return result; + +} + static PyObject * run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, PyArena *arena, PyObject* interactive_src, @@ -1375,8 +1394,8 @@ run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals, if (interactive_src) { PyInterpreterState *interp = tstate->interp; if (generate_new_source) { - interactive_filename = PyUnicode_FromFormat( - "%U-%d", filename, interp->_interactive_src_count++); + interactive_filename = get_interactive_filename( + filename, interp->_interactive_src_count++); } else { Py_INCREF(interactive_filename); } From e93e161654e5ab97ae099429accb3832d053ee4e Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Sun, 10 Aug 2025 20:11:31 -0400 Subject: [PATCH 2/6] update existing test cases for new repl filenames --- Lib/test/test_cmd_line_script.py | 2 +- Lib/test/test_repl.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 784c45aa96f8a7..4fa7fa99019a38 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -203,7 +203,7 @@ def check_repl_stderr_flush(self, separate_stderr=False): p.stdin.flush() stderr = p.stderr if separate_stderr else p.stdout self.assertIn(b'Traceback ', stderr.readline()) - self.assertIn(b'File ""', stderr.readline()) + self.assertIn(b'File ""', stderr.readline()) self.assertIn(b'1/0', stderr.readline()) self.assertIn(b' ~^~', stderr.readline()) self.assertIn(b'ZeroDivisionError', stderr.readline()) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index f4a4634fc62f8a..2094b0db64152a 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -156,7 +156,7 @@ def test_interactive_traceback_reporting(self): traceback_lines = output.splitlines()[-6:-1] expected_lines = [ "Traceback (most recent call last):", - " File \"\", line 1, in ", + " File \"\", line 1, in ", " 1 / 0 / 3 / 4", " ~~^~~", "ZeroDivisionError: division by zero", @@ -178,10 +178,10 @@ def foo(x): traceback_lines = output.splitlines()[-8:-1] expected_lines = [ - ' File "", line 1, in ', + ' File "", line 1, in ', ' foo(0)', ' ~~~^^^', - ' File "", line 2, in foo', + ' File "", line 2, in foo', ' 1 / x', ' ~~^~~', 'ZeroDivisionError: division by zero' From 3d46e8feed3dc97ae2ef1f87488179d5c6a3343f Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Sun, 10 Aug 2025 21:00:28 -0400 Subject: [PATCH 3/6] add tests for error reporting involving PYTHONSTARTUP --- Lib/test/test_repl.py | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 2094b0db64152a..215ebbd01b5d7f 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -188,6 +188,68 @@ def foo(x): ] self.assertEqual(traceback_lines, expected_lines) + def test_pythonstartup_error_reporting(self): + # errors based on https://github.com/python/cpython/issues/137576 + + def make_repl(env): + return subprocess.Popen( + [os.path.join(os.path.dirname(sys.executable), ''), "-i"], + executable=sys.executable, + text=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + + # case 1: error in user input, but PYTHONSTARTUP is fine + with os_helper.temp_dir() as tmpdir: + script = os.path.join(tmpdir, "pythonstartup.py") + with open(script, "w") as f: + f.write("print('from pythonstartup')" + os.linesep) + + env = os.environ.copy() + env['PYTHONSTARTUP'] = script + env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") + p = make_repl(env) + p.stdin.write("1/0") + output = kill_python(p) + expected = dedent(""" + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + """) + self.assertIn("from pythonstartup", output) + self.assertIn(expected, output) + + # case 2: error in PYTHONSTARTUP triggered by user input + with os_helper.temp_dir() as tmpdir: + script = os.path.join(tmpdir, "pythonstartup.py") + with open(script, "w") as f: + f.write("def foo():\n 1/0\n") + + env = os.environ.copy() + env['PYTHONSTARTUP'] = script + env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") + p = make_repl(env) + p.stdin.write('foo()') + output = kill_python(p) + expected = dedent(""" + Traceback (most recent call last): + File "", line 1, in + foo() + ~~~^^ + File "%s", line 2, in foo + 1/0 + ~^~ + ZeroDivisionError: division by zero + """) % script + self.assertIn(expected, output) + + + def test_runsource_show_syntax_error_location(self): user_input = dedent("""def f(x, x): ... """) From 29cff2699a9d34038ce7fed431f7204ee72b281f Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Sun, 10 Aug 2025 21:34:27 -0400 Subject: [PATCH 4/6] add news entry --- .../2025-08-10-21-34-12.gh-issue-137576.0ZicS-.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-08-10-21-34-12.gh-issue-137576.0ZicS-.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-10-21-34-12.gh-issue-137576.0ZicS-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-10-21-34-12.gh-issue-137576.0ZicS-.rst new file mode 100644 index 00000000000000..6593e0554ba04d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-10-21-34-12.gh-issue-137576.0ZicS-.rst @@ -0,0 +1,2 @@ +Fix for incorrect source code being shown in tracebacks from the Basic REPL +when ``PYTHONSTARTUP`` is given. Patch by Adam Hartz. From c2fce155d8f867c51ade33c715cdc2c15e768694 Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Sun, 10 Aug 2025 21:37:50 -0400 Subject: [PATCH 5/6] don't export get_interactive_filename function --- Python/pythonrun.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 21336867915f45..5d388f4aa7fadc 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1365,7 +1365,7 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py return PyEval_EvalCode((PyObject*)co, globals, locals); } -PyObject * +static PyObject * get_interactive_filename(PyObject *filename, Py_ssize_t count){ PyObject *result; Py_ssize_t len = PyUnicode_GET_LENGTH(filename); From 752aea744b055d875c401ffca9ddf5c10c7e63c5 Mon Sep 17 00:00:00 2001 From: adam j hartz Date: Sun, 10 Aug 2025 22:02:48 -0400 Subject: [PATCH 6/6] revert some changes to error reporting to keep in tracebacks instead of --- Lib/test/test_cmd_line_script.py | 2 +- Lib/test/test_repl.py | 10 +++++----- Lib/traceback.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 4fa7fa99019a38..784c45aa96f8a7 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -203,7 +203,7 @@ def check_repl_stderr_flush(self, separate_stderr=False): p.stdin.flush() stderr = p.stderr if separate_stderr else p.stdout self.assertIn(b'Traceback ', stderr.readline()) - self.assertIn(b'File ""', stderr.readline()) + self.assertIn(b'File ""', stderr.readline()) self.assertIn(b'1/0', stderr.readline()) self.assertIn(b' ~^~', stderr.readline()) self.assertIn(b'ZeroDivisionError', stderr.readline()) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 215ebbd01b5d7f..54e69277282c30 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -156,7 +156,7 @@ def test_interactive_traceback_reporting(self): traceback_lines = output.splitlines()[-6:-1] expected_lines = [ "Traceback (most recent call last):", - " File \"\", line 1, in ", + " File \"\", line 1, in ", " 1 / 0 / 3 / 4", " ~~^~~", "ZeroDivisionError: division by zero", @@ -178,10 +178,10 @@ def foo(x): traceback_lines = output.splitlines()[-8:-1] expected_lines = [ - ' File "", line 1, in ', + ' File "", line 1, in ', ' foo(0)', ' ~~~^^^', - ' File "", line 2, in foo', + ' File "", line 2, in foo', ' 1 / x', ' ~~^~~', 'ZeroDivisionError: division by zero' @@ -216,7 +216,7 @@ def make_repl(env): output = kill_python(p) expected = dedent(""" Traceback (most recent call last): - File "", line 1, in + File "", line 1, in 1/0 ~^~ ZeroDivisionError: division by zero @@ -238,7 +238,7 @@ def make_repl(env): output = kill_python(p) expected = dedent(""" Traceback (most recent call last): - File "", line 1, in + File "", line 1, in foo() ~~~^^ File "%s", line 2, in foo diff --git a/Lib/traceback.py b/Lib/traceback.py index 318ec13cf91121..1fe295add3a6dd 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -541,7 +541,7 @@ def format_frame_summary(self, frame_summary, **kwargs): colorize = kwargs.get("colorize", False) row = [] filename = frame_summary.filename - if frame_summary.filename.startswith("-"): + if frame_summary.filename.startswith("'): filename = "" if colorize: theme = _colorize.get_theme(force_color=True).traceback