Open devdanzin opened 1 month ago
In my opinion, we should figure out that issue first. This is probably something that should not happen - it's kind of equivalent to a comment only file generates an exception, that's just impossible.
In my opinion, we should figure out that issue first. This is probably something that should not happen - it's kind of equivalent to a comment only file generates an exception, that's just impossible.
Indeed, ISTM that due to the linecache bug we try to parse "lines" consisting of only a comment then access the tree's body first node. So fixing that issue will fix this specific failure.
Maybe it would still be interesting to make the code more robust against exceptions in _should_show_carets
?
Looks like there are two bugs:
Lib/_pyrepl/__main__.py
(this file is used for sources)tree.body
can be empty in other cases as well, so why not guard it?diff --git Lib/traceback.py Lib/traceback.py
index 6ee1a50ca68..eba6f03a3c6 100644
--- Lib/traceback.py
+++ Lib/traceback.py
@@ -698,6 +698,8 @@ def _should_show_carets(self, start_offset, end_offset, all_lines, anchors):
with suppress(SyntaxError, ImportError):
import ast
tree = ast.parse('\n'.join(all_lines))
+ if not tree.body:
+ return False
statement = tree.body[0]
value = None
def _spawns_full_line(value):
After:
» ./python.exe
Python 3.14.0a0 (heads/main-dirty:3de092b82f5, Jul 20 2024, 09:23:52) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> exec(compile("tuple()[0]", "s", "exec"))
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
exec(compile("tuple()[0]", "s", "exec"))
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "s", line 1, in <module>
# Important: don't add things to this module, as they will end up in the REPL's
IndexError: tuple index out of range
I've fixed the second issue and added a test for that one. I can move to fixing the first issue as well.
2. But, `tree.body` can be empty in other cases as well, so why not guard it?
Exactly. Another way to get an empty tree.body
is to run:
exec(compile("tuple()[0]", "x.py", "exec"))
Where you have a x.py file that consists of:
# Just a comment.
Yet another way to get an empty tree.body
:
exec(compile("print(2**100000)", "s", "exec"))
EDIT: never mind, I had reverted the fix.
Errr... ok. I'll amend the NEWS entry in the PR since it only talks about comments.
EDIT: Actually, I think you can only have an empty AST body if you are either an empty string or if you are a comment.
In StackSummary.format_frame_summary
, StackSummary._should_show_carets
is called without error suppression (like is done for _extract_caret_anchors_from_line_segment
). This causes any errors in that method to exit the interpreter. I suggest we also ignore errors from _should_show_carets
here:
This time, an error in parsing a too deeply nested structure exits the interpreter:
>>> with open("lambda.txt", "w") as n:
... n.write("lambda: lambda: " * 1000 + "None")
...
16004
>>> exec(compile("str(2**100000)", "lambda.txt", "exec"))
Traceback (most recent call last):
Exception ignored in the internal traceback machinery:
Traceback (most recent call last):
File "/home/danzin/projects/cpython/Lib/traceback.py", line 139, in _print_exception_bltin
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 130, in print_exception
te.print(file=file, chain=chain, colorize=colorize)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 1449, in print
for line in self.format(chain=chain, colorize=colorize):
File "/home/danzin/projects/cpython/Lib/traceback.py", line 1385, in format
yield from _ctx.emit(exc.stack.format(colorize=colorize))
File "/home/danzin/projects/cpython/Lib/traceback.py", line 748, in format
formatted_frame = self.format_frame_summary(frame_summary, colorize=colorize)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 583, in format_frame_summary
show_carets = self._should_show_carets(start_offset, end_offset, all_lines, anchors)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 701, in _should_show_carets
tree = ast.parse('\n'.join(all_lines))
File "/home/danzin/projects/cpython/Lib/ast.py", line 53, in parse
return compile(source, filename, mode, flags,
RecursionError: maximum recursion depth exceeded during compilation
Traceback (most recent call last):
File "/home/danzin/projects/cpython/Lib/code.py", line 91, in runcode
exec(code, self.locals)
File "<python-input-1>", line 1, in <module>
File "lambda.txt", line 1, in <module>
lambda: lambda: lambda: lambda: lambda: [...] lambda: None
ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/danzin/projects/cpython/Lib/runpy.py", line 198, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/home/danzin/projects/cpython/Lib/runpy.py", line 88, in _run_code
exec(code, run_globals)
File "/home/danzin/projects/cpython/Lib/_pyrepl/__main__.py", line 6, in <module>
__pyrepl_interactive_console()
File "/home/danzin/projects/cpython/Lib/_pyrepl/main.py", line 59, in interactive_console
run_multiline_interactive_console(console)
File "/home/danzin/projects/cpython/Lib/_pyrepl/simple_interact.py", line 156, in run_multiline_interactive_console
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
File "/home/danzin/projects/cpython/Lib/code.py", line 303, in push
more = self.runsource(source, filename, symbol=_symbol)
File "/home/danzin/projects/cpython/Lib/_pyrepl/console.py", line 200, in runsource
self.runcode(code)
File "/home/danzin/projects/cpython/Lib/code.py", line 95, in runcode
self.showtraceback()
File "/home/danzin/projects/cpython/Lib/_pyrepl/console.py", line 168, in showtraceback
super().showtraceback(colorize=self.can_colorize)
File "/home/danzin/projects/cpython/Lib/code.py", line 147, in showtraceback
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next, colorize=colorize)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 155, in format_exception
return list(te.format(chain=chain, colorize=colorize))
File "/home/danzin/projects/cpython/Lib/traceback.py", line 1385, in format
yield from _ctx.emit(exc.stack.format(colorize=colorize))
File "/home/danzin/projects/cpython/Lib/traceback.py", line 748, in format
formatted_frame = self.format_frame_summary(frame_summary, colorize=colorize)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 583, in format_frame_summary
show_carets = self._should_show_carets(start_offset, end_offset, all_lines, anchors)
File "/home/danzin/projects/cpython/Lib/traceback.py", line 701, in _should_show_carets
tree = ast.parse('\n'.join(all_lines))
File "/home/danzin/projects/cpython/Lib/ast.py", line 53, in parse
return compile(source, filename, mode, flags,
RecursionError: maximum recursion depth exceeded during compilation
If an even deeper nested structure is used, MemoryError
is raised, but somehow doesn't exit the interpreter.
Doesn't seem to affect 3.13. Should a new issue be created for this one?
Bug report
Bug description:
Running the code sample from #122071 in 3.13.0b4 or main exits the interpreter due to
traceback.StackSummary._should_show_carets
raising an exception:We can either protect the
_should_show_carets
call withsuppress(Exception)
here: https://github.com/python/cpython/blob/2762c6cc5e4c1c0d630568db5fbba7a3a71a507c/Lib/traceback.py#L581-L583Or guard against
tree.body
being empty here: https://github.com/python/cpython/blob/2762c6cc5e4c1c0d630568db5fbba7a3a71a507c/Lib/traceback.py#L700-L701(or both?)
Should I submit a PR with one of the fixes above? Any preferences?
CPython versions tested on:
3.13, CPython main branch
Operating systems tested on:
Windows, Linux
Linked PRs