mirror of
https://github.com/python/cpython.git
synced 2025-10-06 23:21:06 +00:00
gh-67224: Show source lines in tracebacks when using the -c option when running Python (#111200)
This commit is contained in:
parent
3f84a19e62
commit
90a1b2859f
13 changed files with 104 additions and 36 deletions
|
@ -114,6 +114,9 @@ extern int _Py_LegacyLocaleDetected(int warn);
|
||||||
// Export for 'readline' shared extension
|
// Export for 'readline' shared extension
|
||||||
PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category);
|
PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category);
|
||||||
|
|
||||||
|
// Export for special main.c string compiling with source tracebacks
|
||||||
|
int _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5,10 +5,8 @@ is not found, it will look down the module search path for a file by
|
||||||
that name.
|
that name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import functools
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import tokenize
|
|
||||||
|
|
||||||
__all__ = ["getline", "clearcache", "checkcache", "lazycache"]
|
__all__ = ["getline", "clearcache", "checkcache", "lazycache"]
|
||||||
|
|
||||||
|
@ -82,6 +80,8 @@ def updatecache(filename, module_globals=None):
|
||||||
If something's wrong, print a message, discard the cache entry,
|
If something's wrong, print a message, discard the cache entry,
|
||||||
and return an empty list."""
|
and return an empty list."""
|
||||||
|
|
||||||
|
import tokenize
|
||||||
|
|
||||||
if filename in cache:
|
if filename in cache:
|
||||||
if len(cache[filename]) != 1:
|
if len(cache[filename]) != 1:
|
||||||
cache.pop(filename, None)
|
cache.pop(filename, None)
|
||||||
|
@ -176,11 +176,13 @@ def lazycache(filename, module_globals):
|
||||||
get_source = getattr(loader, 'get_source', None)
|
get_source = getattr(loader, 'get_source', None)
|
||||||
|
|
||||||
if name and get_source:
|
if name and get_source:
|
||||||
get_lines = functools.partial(get_source, name)
|
def get_lines(name=name, *args, **kwargs):
|
||||||
|
return get_source(name, *args, **kwargs)
|
||||||
cache[filename] = (get_lines,)
|
cache[filename] = (get_lines,)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _register_code(code, string, name):
|
def _register_code(code, string, name):
|
||||||
cache[code] = (
|
cache[code] = (
|
||||||
len(string),
|
len(string),
|
||||||
|
|
|
@ -684,6 +684,16 @@ class CmdLineTest(unittest.TestCase):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_source_lines_are_shown_when_running_source(self):
|
||||||
|
_, _, stderr = assert_python_failure("-c", "1/0")
|
||||||
|
expected_lines = [
|
||||||
|
b'Traceback (most recent call last):',
|
||||||
|
b' File "<string>", line 1, in <module>',
|
||||||
|
b' 1/0',
|
||||||
|
b' ~^~',
|
||||||
|
b'ZeroDivisionError: division by zero']
|
||||||
|
self.assertEqual(stderr.splitlines(), expected_lines)
|
||||||
|
|
||||||
def test_syntaxerror_does_not_crash(self):
|
def test_syntaxerror_does_not_crash(self):
|
||||||
script = "nonlocal x\n"
|
script = "nonlocal x\n"
|
||||||
with os_helper.temp_dir() as script_dir:
|
with os_helper.temp_dir() as script_dir:
|
||||||
|
|
|
@ -4396,11 +4396,11 @@ class MiscIOTest(unittest.TestCase):
|
||||||
''')
|
''')
|
||||||
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
|
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
|
||||||
warnings = proc.err.splitlines()
|
warnings = proc.err.splitlines()
|
||||||
self.assertEqual(len(warnings), 2)
|
self.assertEqual(len(warnings), 4)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
warnings[0].startswith(b"<string>:5: EncodingWarning: "))
|
warnings[0].startswith(b"<string>:5: EncodingWarning: "))
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
warnings[1].startswith(b"<string>:8: EncodingWarning: "))
|
warnings[2].startswith(b"<string>:8: EncodingWarning: "))
|
||||||
|
|
||||||
def test_text_encoding(self):
|
def test_text_encoding(self):
|
||||||
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
|
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
|
||||||
|
|
|
@ -184,7 +184,7 @@ class TestInteractiveInterpreter(unittest.TestCase):
|
||||||
p.stdin.write(user_input)
|
p.stdin.write(user_input)
|
||||||
user_input2 = dedent("""
|
user_input2 = dedent("""
|
||||||
import linecache
|
import linecache
|
||||||
print(linecache.cache['<python-input-1>'])
|
print(linecache.cache['<stdin>-1'])
|
||||||
""")
|
""")
|
||||||
p.stdin.write(user_input2)
|
p.stdin.write(user_input2)
|
||||||
output = kill_python(p)
|
output = kill_python(p)
|
||||||
|
|
|
@ -1769,9 +1769,9 @@ class RunFuncTestCase(BaseTestCase):
|
||||||
cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
|
cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
|
||||||
capture_output=True)
|
capture_output=True)
|
||||||
lines = cp.stderr.splitlines()
|
lines = cp.stderr.splitlines()
|
||||||
self.assertEqual(len(lines), 2, lines)
|
self.assertEqual(len(lines), 4, lines)
|
||||||
self.assertTrue(lines[0].startswith(b"<string>:2: EncodingWarning: "))
|
self.assertTrue(lines[0].startswith(b"<string>:2: EncodingWarning: "))
|
||||||
self.assertTrue(lines[1].startswith(b"<string>:3: EncodingWarning: "))
|
self.assertTrue(lines[2].startswith(b"<string>:3: EncodingWarning: "))
|
||||||
|
|
||||||
|
|
||||||
def _get_test_grp_name():
|
def _get_test_grp_name():
|
||||||
|
|
|
@ -1114,14 +1114,18 @@ class SysModuleTest(unittest.TestCase):
|
||||||
traceback = [
|
traceback = [
|
||||||
b'Traceback (most recent call last):',
|
b'Traceback (most recent call last):',
|
||||||
b' File "<string>", line 8, in <module>',
|
b' File "<string>", line 8, in <module>',
|
||||||
|
b' f2()',
|
||||||
b' File "<string>", line 6, in f2',
|
b' File "<string>", line 6, in f2',
|
||||||
|
b' f1()',
|
||||||
b' File "<string>", line 4, in f1',
|
b' File "<string>", line 4, in f1',
|
||||||
|
b' 1 / 0',
|
||||||
|
b' ~~^~~',
|
||||||
b'ZeroDivisionError: division by zero'
|
b'ZeroDivisionError: division by zero'
|
||||||
]
|
]
|
||||||
check(10, traceback)
|
check(10, traceback)
|
||||||
check(3, traceback)
|
check(3, traceback)
|
||||||
check(2, traceback[:1] + traceback[2:])
|
check(2, traceback[:1] + traceback[3:])
|
||||||
check(1, traceback[:1] + traceback[3:])
|
check(1, traceback[:1] + traceback[5:])
|
||||||
check(0, [traceback[-1]])
|
check(0, [traceback[-1]])
|
||||||
check(-1, [traceback[-1]])
|
check(-1, [traceback[-1]])
|
||||||
check(1<<1000, traceback)
|
check(1<<1000, traceback)
|
||||||
|
|
|
@ -313,6 +313,8 @@ class TracebackCases(unittest.TestCase):
|
||||||
rc, stdout, stderr = assert_python_ok('-c', code)
|
rc, stdout, stderr = assert_python_ok('-c', code)
|
||||||
expected = [b'Traceback (most recent call last):',
|
expected = [b'Traceback (most recent call last):',
|
||||||
b' File "<string>", line 8, in __init__',
|
b' File "<string>", line 8, in __init__',
|
||||||
|
b' x = 1 / 0',
|
||||||
|
b' ^^^^^',
|
||||||
b'ZeroDivisionError: division by zero']
|
b'ZeroDivisionError: division by zero']
|
||||||
self.assertEqual(stderr.splitlines(), expected)
|
self.assertEqual(stderr.splitlines(), expected)
|
||||||
|
|
||||||
|
|
|
@ -1233,6 +1233,10 @@ class EnvironmentVariableTests(BaseTest):
|
||||||
self.assertEqual(stderr.splitlines(),
|
self.assertEqual(stderr.splitlines(),
|
||||||
[b"Traceback (most recent call last):",
|
[b"Traceback (most recent call last):",
|
||||||
b" File \"<string>\", line 1, in <module>",
|
b" File \"<string>\", line 1, in <module>",
|
||||||
|
b' import sys, warnings; sys.stdout.write(str(sys.warnoptions)); warnings.w'
|
||||||
|
b"arn('Message', DeprecationWarning)",
|
||||||
|
b' ^^^^^^^^^^'
|
||||||
|
b'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
||||||
b"DeprecationWarning: Message"])
|
b"DeprecationWarning: Message"])
|
||||||
|
|
||||||
def test_default_filter_configuration(self):
|
def test_default_filter_configuration(self):
|
||||||
|
|
|
@ -476,12 +476,11 @@ class StackSummary(list):
|
||||||
gets called for every frame to be printed in the stack summary.
|
gets called for every frame to be printed in the stack summary.
|
||||||
"""
|
"""
|
||||||
row = []
|
row = []
|
||||||
if frame_summary.filename.startswith("<python-input"):
|
filename = frame_summary.filename
|
||||||
row.append(' File "<stdin>", line {}, in {}\n'.format(
|
if frame_summary.filename.startswith("<stdin>-"):
|
||||||
frame_summary.lineno, frame_summary.name))
|
filename = "<stdin>"
|
||||||
else:
|
row.append(' File "{}", line {}, in {}\n'.format(
|
||||||
row.append(' File "{}", line {}, in {}\n'.format(
|
filename, frame_summary.lineno, frame_summary.name))
|
||||||
frame_summary.filename, frame_summary.lineno, frame_summary.name))
|
|
||||||
if frame_summary.line:
|
if frame_summary.line:
|
||||||
stripped_line = frame_summary.line.strip()
|
stripped_line = frame_summary.line.strip()
|
||||||
row.append(' {}\n'.format(stripped_line))
|
row.append(' {}\n'.format(stripped_line))
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Show source lines in tracebacks when using the ``-c`` option when running
|
||||||
|
Python. Patch by Pablo Galindo
|
|
@ -249,7 +249,7 @@ pymain_run_command(wchar_t *command)
|
||||||
|
|
||||||
PyCompilerFlags cf = _PyCompilerFlags_INIT;
|
PyCompilerFlags cf = _PyCompilerFlags_INIT;
|
||||||
cf.cf_flags |= PyCF_IGNORE_COOKIE;
|
cf.cf_flags |= PyCF_IGNORE_COOKIE;
|
||||||
ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), &cf);
|
ret = _PyRun_SimpleStringFlagsWithName(PyBytes_AsString(bytes), "<string>", &cf);
|
||||||
Py_DECREF(bytes);
|
Py_DECREF(bytes);
|
||||||
return (ret != 0);
|
return (ret != 0);
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,17 @@
|
||||||
/* Forward */
|
/* Forward */
|
||||||
static void flush_io(void);
|
static void flush_io(void);
|
||||||
static PyObject *run_mod(mod_ty, PyObject *, PyObject *, PyObject *,
|
static PyObject *run_mod(mod_ty, PyObject *, PyObject *, PyObject *,
|
||||||
PyCompilerFlags *, PyArena *, PyObject*);
|
PyCompilerFlags *, PyArena *, PyObject*, int);
|
||||||
static PyObject *run_pyc_file(FILE *, PyObject *, PyObject *,
|
static PyObject *run_pyc_file(FILE *, PyObject *, PyObject *,
|
||||||
PyCompilerFlags *);
|
PyCompilerFlags *);
|
||||||
static int PyRun_InteractiveOneObjectEx(FILE *, PyObject *, PyCompilerFlags *);
|
static int PyRun_InteractiveOneObjectEx(FILE *, PyObject *, PyCompilerFlags *);
|
||||||
static PyObject* pyrun_file(FILE *fp, PyObject *filename, int start,
|
static PyObject* pyrun_file(FILE *fp, PyObject *filename, int start,
|
||||||
PyObject *globals, PyObject *locals, int closeit,
|
PyObject *globals, PyObject *locals, int closeit,
|
||||||
PyCompilerFlags *flags);
|
PyCompilerFlags *flags);
|
||||||
|
static PyObject *
|
||||||
|
_PyRun_StringFlagsWithName(const char *str, PyObject* name, int start,
|
||||||
|
PyObject *globals, PyObject *locals, PyCompilerFlags *flags,
|
||||||
|
int generate_new_source);
|
||||||
|
|
||||||
int
|
int
|
||||||
_PyRun_AnyFileObject(FILE *fp, PyObject *filename, int closeit,
|
_PyRun_AnyFileObject(FILE *fp, PyObject *filename, int closeit,
|
||||||
|
@ -281,7 +284,7 @@ PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
|
||||||
}
|
}
|
||||||
PyObject *main_dict = PyModule_GetDict(main_module); // borrowed ref
|
PyObject *main_dict = PyModule_GetDict(main_module); // borrowed ref
|
||||||
|
|
||||||
PyObject *res = run_mod(mod, filename, main_dict, main_dict, flags, arena, interactive_src);
|
PyObject *res = run_mod(mod, filename, main_dict, main_dict, flags, arena, interactive_src, 1);
|
||||||
_PyArena_Free(arena);
|
_PyArena_Free(arena);
|
||||||
Py_DECREF(main_module);
|
Py_DECREF(main_module);
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
|
@ -499,16 +502,25 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
|
_PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags) {
|
||||||
{
|
|
||||||
PyObject *main_module = PyImport_AddModuleRef("__main__");
|
PyObject *main_module = PyImport_AddModuleRef("__main__");
|
||||||
if (main_module == NULL) {
|
if (main_module == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
PyObject *dict = PyModule_GetDict(main_module); // borrowed ref
|
PyObject *dict = PyModule_GetDict(main_module); // borrowed ref
|
||||||
|
|
||||||
PyObject *res = PyRun_StringFlags(command, Py_file_input,
|
PyObject *res = NULL;
|
||||||
dict, dict, flags);
|
if (name == NULL) {
|
||||||
|
res = PyRun_StringFlags(command, Py_file_input, dict, dict, flags);
|
||||||
|
} else {
|
||||||
|
PyObject* the_name = PyUnicode_FromString(name);
|
||||||
|
if (!the_name) {
|
||||||
|
PyErr_Print();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
res = _PyRun_StringFlagsWithName(command, the_name, Py_file_input, dict, dict, flags, 0);
|
||||||
|
Py_DECREF(the_name);
|
||||||
|
}
|
||||||
Py_DECREF(main_module);
|
Py_DECREF(main_module);
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
|
@ -519,6 +531,12 @@ PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
|
||||||
|
{
|
||||||
|
return _PyRun_SimpleStringFlagsWithName(command, NULL, flags);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
_Py_HandleSystemExit(int *exitcode_p)
|
_Py_HandleSystemExit(int *exitcode_p)
|
||||||
{
|
{
|
||||||
|
@ -1131,9 +1149,10 @@ void PyErr_DisplayException(PyObject *exc)
|
||||||
PyErr_Display(NULL, exc, NULL);
|
PyErr_Display(NULL, exc, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
static PyObject *
|
||||||
PyRun_StringFlags(const char *str, int start, PyObject *globals,
|
_PyRun_StringFlagsWithName(const char *str, PyObject* name, int start,
|
||||||
PyObject *locals, PyCompilerFlags *flags)
|
PyObject *globals, PyObject *locals, PyCompilerFlags *flags,
|
||||||
|
int generate_new_source)
|
||||||
{
|
{
|
||||||
PyObject *ret = NULL;
|
PyObject *ret = NULL;
|
||||||
mod_ty mod;
|
mod_ty mod;
|
||||||
|
@ -1143,17 +1162,36 @@ PyRun_StringFlags(const char *str, int start, PyObject *globals,
|
||||||
if (arena == NULL)
|
if (arena == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
PyObject* source = NULL;
|
||||||
_Py_DECLARE_STR(anon_string, "<string>");
|
_Py_DECLARE_STR(anon_string, "<string>");
|
||||||
mod = _PyParser_ASTFromString(
|
|
||||||
str, &_Py_STR(anon_string), start, flags, arena);
|
|
||||||
|
|
||||||
if (mod != NULL)
|
if (name) {
|
||||||
ret = run_mod(mod, &_Py_STR(anon_string), globals, locals, flags, arena, NULL);
|
source = PyUnicode_FromString(str);
|
||||||
|
if (!source) {
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = &_Py_STR(anon_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod = _PyParser_ASTFromString(str, name, start, flags, arena);
|
||||||
|
|
||||||
|
if (mod != NULL) {
|
||||||
|
ret = run_mod(mod, name, globals, locals, flags, arena, source, generate_new_source);
|
||||||
|
}
|
||||||
|
Py_XDECREF(source);
|
||||||
_PyArena_Free(arena);
|
_PyArena_Free(arena);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
PyRun_StringFlags(const char *str, int start, PyObject *globals,
|
||||||
|
PyObject *locals, PyCompilerFlags *flags) {
|
||||||
|
|
||||||
|
return _PyRun_StringFlagsWithName(str, NULL, start, globals, locals, flags, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals,
|
pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals,
|
||||||
PyObject *locals, int closeit, PyCompilerFlags *flags)
|
PyObject *locals, int closeit, PyCompilerFlags *flags)
|
||||||
|
@ -1173,7 +1211,7 @@ pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals,
|
||||||
|
|
||||||
PyObject *ret;
|
PyObject *ret;
|
||||||
if (mod != NULL) {
|
if (mod != NULL) {
|
||||||
ret = run_mod(mod, filename, globals, locals, flags, arena, NULL);
|
ret = run_mod(mod, filename, globals, locals, flags, arena, NULL, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ret = NULL;
|
ret = NULL;
|
||||||
|
@ -1261,15 +1299,19 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
|
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
|
||||||
PyCompilerFlags *flags, PyArena *arena, PyObject* interactive_src)
|
PyCompilerFlags *flags, PyArena *arena, PyObject* interactive_src,
|
||||||
|
int generate_new_source)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
PyObject* interactive_filename = filename;
|
PyObject* interactive_filename = filename;
|
||||||
if (interactive_src) {
|
if (interactive_src) {
|
||||||
PyInterpreterState *interp = tstate->interp;
|
PyInterpreterState *interp = tstate->interp;
|
||||||
interactive_filename = PyUnicode_FromFormat(
|
if (generate_new_source) {
|
||||||
"<python-input-%d>", interp->_interactive_src_count++
|
interactive_filename = PyUnicode_FromFormat(
|
||||||
);
|
"%U-%d", filename, interp->_interactive_src_count++);
|
||||||
|
} else {
|
||||||
|
Py_INCREF(interactive_filename);
|
||||||
|
}
|
||||||
if (interactive_filename == NULL) {
|
if (interactive_filename == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue