mirror of https://github.com/python/cpython
gh-110805: Allow the repl to show source code and complete tracebacks (#110775)
This commit is contained in:
parent
898f531996
commit
e1d8c65e1d
|
@ -233,6 +233,7 @@ struct _is {
|
||||||
|
|
||||||
/* the initial PyInterpreterState.threads.head */
|
/* the initial PyInterpreterState.threads.head */
|
||||||
PyThreadState _initial_thread;
|
PyThreadState _initial_thread;
|
||||||
|
Py_ssize_t _interactive_src_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,17 @@ extern struct _mod* _PyParser_ASTFromFile(
|
||||||
PyCompilerFlags *flags,
|
PyCompilerFlags *flags,
|
||||||
int *errcode,
|
int *errcode,
|
||||||
PyArena *arena);
|
PyArena *arena);
|
||||||
|
extern struct _mod* _PyParser_InteractiveASTFromFile(
|
||||||
|
FILE *fp,
|
||||||
|
PyObject *filename_ob,
|
||||||
|
const char *enc,
|
||||||
|
int mode,
|
||||||
|
const char *ps1,
|
||||||
|
const char *ps2,
|
||||||
|
PyCompilerFlags *flags,
|
||||||
|
int *errcode,
|
||||||
|
PyObject **interactive_src,
|
||||||
|
PyArena *arena);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,3 +180,10 @@ def lazycache(filename, module_globals):
|
||||||
cache[filename] = (get_lines,)
|
cache[filename] = (get_lines,)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _register_code(code, string, name):
|
||||||
|
cache[code] = (
|
||||||
|
len(string),
|
||||||
|
None,
|
||||||
|
[line + '\n' for line in string.splitlines()],
|
||||||
|
name)
|
||||||
|
|
|
@ -203,6 +203,8 @@ class CmdLineTest(unittest.TestCase):
|
||||||
stderr = p.stderr if separate_stderr else p.stdout
|
stderr = p.stderr if separate_stderr else p.stdout
|
||||||
self.assertIn(b'Traceback ', stderr.readline())
|
self.assertIn(b'Traceback ', stderr.readline())
|
||||||
self.assertIn(b'File "<stdin>"', stderr.readline())
|
self.assertIn(b'File "<stdin>"', stderr.readline())
|
||||||
|
self.assertIn(b'1/0', stderr.readline())
|
||||||
|
self.assertIn(b' ~^~', stderr.readline())
|
||||||
self.assertIn(b'ZeroDivisionError', stderr.readline())
|
self.assertIn(b'ZeroDivisionError', stderr.readline())
|
||||||
|
|
||||||
def test_repl_stdout_flush(self):
|
def test_repl_stdout_flush(self):
|
||||||
|
|
|
@ -131,6 +131,68 @@ class TestInteractiveInterpreter(unittest.TestCase):
|
||||||
self.assertEqual(process.returncode, 0)
|
self.assertEqual(process.returncode, 0)
|
||||||
self.assertIn('before close', output)
|
self.assertIn('before close', output)
|
||||||
|
|
||||||
|
def test_interactive_traceback_reporting(self):
|
||||||
|
user_input = "1 / 0 / 3 / 4"
|
||||||
|
p = spawn_repl()
|
||||||
|
p.stdin.write(user_input)
|
||||||
|
output = kill_python(p)
|
||||||
|
self.assertEqual(p.returncode, 0)
|
||||||
|
|
||||||
|
traceback_lines = output.splitlines()[-6:-1]
|
||||||
|
expected_lines = [
|
||||||
|
"Traceback (most recent call last):",
|
||||||
|
" File \"<stdin>\", line 1, in <module>",
|
||||||
|
" 1 / 0 / 3 / 4",
|
||||||
|
" ~~^~~",
|
||||||
|
"ZeroDivisionError: division by zero",
|
||||||
|
]
|
||||||
|
self.assertEqual(traceback_lines, expected_lines)
|
||||||
|
|
||||||
|
def test_interactive_traceback_reporting_multiple_input(self):
|
||||||
|
user_input1 = dedent("""
|
||||||
|
def foo(x):
|
||||||
|
1 / x
|
||||||
|
|
||||||
|
""")
|
||||||
|
p = spawn_repl()
|
||||||
|
p.stdin.write(user_input1)
|
||||||
|
user_input2 = "foo(0)"
|
||||||
|
p.stdin.write(user_input2)
|
||||||
|
output = kill_python(p)
|
||||||
|
self.assertEqual(p.returncode, 0)
|
||||||
|
|
||||||
|
traceback_lines = output.splitlines()[-7:-1]
|
||||||
|
expected_lines = [
|
||||||
|
' File "<stdin>", line 1, in <module>',
|
||||||
|
' foo(0)',
|
||||||
|
' File "<stdin>", line 2, in foo',
|
||||||
|
' 1 / x',
|
||||||
|
' ~~^~~',
|
||||||
|
'ZeroDivisionError: division by zero'
|
||||||
|
]
|
||||||
|
self.assertEqual(traceback_lines, expected_lines)
|
||||||
|
|
||||||
|
def test_interactive_source_is_in_linecache(self):
|
||||||
|
user_input = dedent("""
|
||||||
|
def foo(x):
|
||||||
|
return x + 1
|
||||||
|
|
||||||
|
def bar(x):
|
||||||
|
return foo(x) + 2
|
||||||
|
""")
|
||||||
|
p = spawn_repl()
|
||||||
|
p.stdin.write(user_input)
|
||||||
|
user_input2 = dedent("""
|
||||||
|
import linecache
|
||||||
|
print(linecache.cache['<python-input-1>'])
|
||||||
|
""")
|
||||||
|
p.stdin.write(user_input2)
|
||||||
|
output = kill_python(p)
|
||||||
|
self.assertEqual(p.returncode, 0)
|
||||||
|
expected = "(30, None, [\'def foo(x):\\n\', \' return x + 1\\n\', \'\\n\'], \'<stdin>\')"
|
||||||
|
self.assertIn(expected, output, expected)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
|
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -434,7 +434,6 @@ class StackSummary(list):
|
||||||
co = f.f_code
|
co = f.f_code
|
||||||
filename = co.co_filename
|
filename = co.co_filename
|
||||||
name = co.co_name
|
name = co.co_name
|
||||||
|
|
||||||
fnames.add(filename)
|
fnames.add(filename)
|
||||||
linecache.lazycache(filename, f.f_globals)
|
linecache.lazycache(filename, f.f_globals)
|
||||||
# Must defer line lookups until we have called checkcache.
|
# Must defer line lookups until we have called checkcache.
|
||||||
|
@ -447,6 +446,7 @@ class StackSummary(list):
|
||||||
end_lineno=end_lineno, colno=colno, end_colno=end_colno))
|
end_lineno=end_lineno, colno=colno, end_colno=end_colno))
|
||||||
for filename in fnames:
|
for filename in fnames:
|
||||||
linecache.checkcache(filename)
|
linecache.checkcache(filename)
|
||||||
|
|
||||||
# If immediate lookup was desired, trigger lookups now.
|
# If immediate lookup was desired, trigger lookups now.
|
||||||
if lookup_lines:
|
if lookup_lines:
|
||||||
for f in result:
|
for f in result:
|
||||||
|
@ -479,8 +479,12 @@ 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 = []
|
||||||
row.append(' File "{}", line {}, in {}\n'.format(
|
if frame_summary.filename.startswith("<python-input"):
|
||||||
frame_summary.filename, frame_summary.lineno, frame_summary.name))
|
row.append(' File "<stdin>", line {}, in {}\n'.format(
|
||||||
|
frame_summary.lineno, frame_summary.name))
|
||||||
|
else:
|
||||||
|
row.append(' File "{}", line {}, in {}\n'.format(
|
||||||
|
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 @@
|
||||||
|
Allow the repl to show source code and complete tracebacks. Patch by Pablo
|
||||||
|
Galindo
|
|
@ -23,5 +23,18 @@ _PyParser_ASTFromFile(FILE *fp, PyObject *filename_ob, const char *enc,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return _PyPegen_run_parser_from_file_pointer(fp, mode, filename_ob, enc, ps1, ps2,
|
return _PyPegen_run_parser_from_file_pointer(fp, mode, filename_ob, enc, ps1, ps2,
|
||||||
flags, errcode, arena);
|
flags, errcode, NULL, arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod_ty
|
||||||
|
_PyParser_InteractiveASTFromFile(FILE *fp, PyObject *filename_ob, const char *enc,
|
||||||
|
int mode, const char *ps1, const char* ps2,
|
||||||
|
PyCompilerFlags *flags, int *errcode,
|
||||||
|
PyObject **interactive_src, PyArena *arena)
|
||||||
|
{
|
||||||
|
if (PySys_Audit("compile", "OO", Py_None, filename_ob) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return _PyPegen_run_parser_from_file_pointer(fp, mode, filename_ob, enc, ps1, ps2,
|
||||||
|
flags, errcode, interactive_src, arena);
|
||||||
|
}
|
|
@ -878,7 +878,8 @@ _PyPegen_run_parser(Parser *p)
|
||||||
mod_ty
|
mod_ty
|
||||||
_PyPegen_run_parser_from_file_pointer(FILE *fp, int start_rule, PyObject *filename_ob,
|
_PyPegen_run_parser_from_file_pointer(FILE *fp, int start_rule, PyObject *filename_ob,
|
||||||
const char *enc, const char *ps1, const char *ps2,
|
const char *enc, const char *ps1, const char *ps2,
|
||||||
PyCompilerFlags *flags, int *errcode, PyArena *arena)
|
PyCompilerFlags *flags, int *errcode,
|
||||||
|
PyObject **interactive_src, PyArena *arena)
|
||||||
{
|
{
|
||||||
struct tok_state *tok = _PyTokenizer_FromFile(fp, enc, ps1, ps2);
|
struct tok_state *tok = _PyTokenizer_FromFile(fp, enc, ps1, ps2);
|
||||||
if (tok == NULL) {
|
if (tok == NULL) {
|
||||||
|
@ -908,6 +909,15 @@ _PyPegen_run_parser_from_file_pointer(FILE *fp, int start_rule, PyObject *filena
|
||||||
result = _PyPegen_run_parser(p);
|
result = _PyPegen_run_parser(p);
|
||||||
_PyPegen_Parser_Free(p);
|
_PyPegen_Parser_Free(p);
|
||||||
|
|
||||||
|
if (tok->fp_interactive && tok->interactive_src_start && result && interactive_src != NULL) {
|
||||||
|
*interactive_src = PyUnicode_FromString(tok->interactive_src_start);
|
||||||
|
if (!interactive_src || _PyArena_AddPyObject(arena, *interactive_src) < 0) {
|
||||||
|
Py_XDECREF(interactive_src);
|
||||||
|
result = NULL;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
error:
|
error:
|
||||||
_PyTokenizer_Free(tok);
|
_PyTokenizer_Free(tok);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -350,7 +350,8 @@ void *_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args, asdl_comprehensi
|
||||||
Parser *_PyPegen_Parser_New(struct tok_state *, int, int, int, int *, PyArena *);
|
Parser *_PyPegen_Parser_New(struct tok_state *, int, int, int, int *, PyArena *);
|
||||||
void _PyPegen_Parser_Free(Parser *);
|
void _PyPegen_Parser_Free(Parser *);
|
||||||
mod_ty _PyPegen_run_parser_from_file_pointer(FILE *, int, PyObject *, const char *,
|
mod_ty _PyPegen_run_parser_from_file_pointer(FILE *, int, PyObject *, const char *,
|
||||||
const char *, const char *, PyCompilerFlags *, int *, PyArena *);
|
const char *, const char *, PyCompilerFlags *, int *, PyObject **,
|
||||||
|
PyArena *);
|
||||||
void *_PyPegen_run_parser(Parser *);
|
void *_PyPegen_run_parser(Parser *);
|
||||||
mod_ty _PyPegen_run_parser_from_string(const char *, int, PyObject *, PyCompilerFlags *, PyArena *);
|
mod_ty _PyPegen_run_parser_from_string(const char *, int, PyObject *, PyCompilerFlags *, PyArena *);
|
||||||
asdl_stmt_seq *_PyPegen_interactive_exit(Parser *);
|
asdl_stmt_seq *_PyPegen_interactive_exit(Parser *);
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
/* 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 *);
|
PyCompilerFlags *, PyArena *, PyObject*);
|
||||||
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 *);
|
||||||
|
@ -178,7 +178,8 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename, PyCompilerFlags *flag
|
||||||
// Call _PyParser_ASTFromFile() with sys.stdin.encoding, sys.ps1 and sys.ps2
|
// Call _PyParser_ASTFromFile() with sys.stdin.encoding, sys.ps1 and sys.ps2
|
||||||
static int
|
static int
|
||||||
pyrun_one_parse_ast(FILE *fp, PyObject *filename,
|
pyrun_one_parse_ast(FILE *fp, PyObject *filename,
|
||||||
PyCompilerFlags *flags, PyArena *arena, mod_ty *pmod)
|
PyCompilerFlags *flags, PyArena *arena,
|
||||||
|
mod_ty *pmod, PyObject** interactive_src)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
|
||||||
|
@ -236,9 +237,9 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename,
|
||||||
}
|
}
|
||||||
|
|
||||||
int errcode = 0;
|
int errcode = 0;
|
||||||
*pmod = _PyParser_ASTFromFile(fp, filename, encoding,
|
*pmod = _PyParser_InteractiveASTFromFile(fp, filename, encoding,
|
||||||
Py_single_input, ps1, ps2,
|
Py_single_input, ps1, ps2,
|
||||||
flags, &errcode, arena);
|
flags, &errcode, interactive_src, arena);
|
||||||
Py_XDECREF(ps1_obj);
|
Py_XDECREF(ps1_obj);
|
||||||
Py_XDECREF(ps2_obj);
|
Py_XDECREF(ps2_obj);
|
||||||
Py_XDECREF(encoding_obj);
|
Py_XDECREF(encoding_obj);
|
||||||
|
@ -266,7 +267,8 @@ PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod_ty mod;
|
mod_ty mod;
|
||||||
int parse_res = pyrun_one_parse_ast(fp, filename, flags, arena, &mod);
|
PyObject *interactive_src;
|
||||||
|
int parse_res = pyrun_one_parse_ast(fp, filename, flags, arena, &mod, &interactive_src);
|
||||||
if (parse_res != 0) {
|
if (parse_res != 0) {
|
||||||
_PyArena_Free(arena);
|
_PyArena_Free(arena);
|
||||||
return parse_res;
|
return parse_res;
|
||||||
|
@ -279,7 +281,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);
|
PyObject *res = run_mod(mod, filename, main_dict, main_dict, flags, arena, interactive_src);
|
||||||
_PyArena_Free(arena);
|
_PyArena_Free(arena);
|
||||||
Py_DECREF(main_module);
|
Py_DECREF(main_module);
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
|
@ -1149,7 +1151,7 @@ PyRun_StringFlags(const char *str, int start, PyObject *globals,
|
||||||
str, &_Py_STR(anon_string), start, flags, arena);
|
str, &_Py_STR(anon_string), start, flags, arena);
|
||||||
|
|
||||||
if (mod != NULL)
|
if (mod != NULL)
|
||||||
ret = run_mod(mod, &_Py_STR(anon_string), globals, locals, flags, arena);
|
ret = run_mod(mod, &_Py_STR(anon_string), globals, locals, flags, arena, NULL);
|
||||||
_PyArena_Free(arena);
|
_PyArena_Free(arena);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1174,7 +1176,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);
|
ret = run_mod(mod, filename, globals, locals, flags, arena, NULL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ret = NULL;
|
ret = NULL;
|
||||||
|
@ -1262,12 +1264,70 @@ 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)
|
PyCompilerFlags *flags, PyArena *arena, PyObject* interactive_src)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
PyCodeObject *co = _PyAST_Compile(mod, filename, flags, -1, arena);
|
PyObject* interactive_filename = filename;
|
||||||
if (co == NULL)
|
if (interactive_src) {
|
||||||
|
PyInterpreterState *interp = tstate->interp;
|
||||||
|
interactive_filename = PyUnicode_FromFormat(
|
||||||
|
"<python-input-%d>", interp->_interactive_src_count++
|
||||||
|
);
|
||||||
|
if (interactive_filename == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyCodeObject *co = _PyAST_Compile(mod, interactive_filename, flags, -1, arena);
|
||||||
|
if (co == NULL) {
|
||||||
|
Py_DECREF(interactive_filename);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interactive_src) {
|
||||||
|
PyObject *linecache_module = PyImport_ImportModule("linecache");
|
||||||
|
|
||||||
|
if (linecache_module == NULL) {
|
||||||
|
Py_DECREF(co);
|
||||||
|
Py_DECREF(interactive_filename);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *print_tb_func = PyObject_GetAttrString(linecache_module, "_register_code");
|
||||||
|
|
||||||
|
if (print_tb_func == NULL) {
|
||||||
|
Py_DECREF(co);
|
||||||
|
Py_DECREF(interactive_filename);
|
||||||
|
Py_DECREF(linecache_module);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyCallable_Check(print_tb_func)) {
|
||||||
|
Py_DECREF(co);
|
||||||
|
Py_DECREF(interactive_filename);
|
||||||
|
Py_DECREF(linecache_module);
|
||||||
|
Py_DECREF(print_tb_func);
|
||||||
|
PyErr_SetString(PyExc_ValueError, "linecache._register_code is not callable");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* result = PyObject_CallFunction(
|
||||||
|
print_tb_func, "OOO",
|
||||||
|
interactive_filename,
|
||||||
|
interactive_src,
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
|
||||||
|
Py_DECREF(interactive_filename);
|
||||||
|
|
||||||
|
Py_DECREF(linecache_module);
|
||||||
|
Py_XDECREF(print_tb_func);
|
||||||
|
Py_XDECREF(result);
|
||||||
|
if (!result) {
|
||||||
|
Py_DECREF(co);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_PySys_Audit(tstate, "exec", "O", co) < 0) {
|
if (_PySys_Audit(tstate, "exec", "O", co) < 0) {
|
||||||
Py_DECREF(co);
|
Py_DECREF(co);
|
||||||
|
|
Loading…
Reference in New Issue