8000 traceback._compute_suggestion_error crashes on `__getattr__` raising · Issue #129605 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

traceback._compute_suggestion_error crashes on __getattr__ raising #129605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jcea opened this issue Feb 3, 2025 · 5 comments
Open

traceback._compute_suggestion_error crashes on __getattr__ raising #129605

jcea opened this issue Feb 3, 2025 · 5 comments
Assignees
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@jcea
Copy link
Member
jcea commented Feb 3, 2025

Bug report

Bug description:

This is a regression introduced in 3.13 because the new pyrepl. 3.14 is also affected.

Type this in an interactive 3.13/3.14 session:

>>> class a:
...     def __getattr__(self, x):
...         print(x)
...         print(qq)
...         
>>> b=a()
>>> b.pepe
pepe
qq
qq
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
  File "<python-input-3>", line 4, in __getattr__
NameError: name 'qq' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/local/lib/python3.13/_pyrepl/__main__.py", line 6, in <module>
    __pyrepl_interactive_console()
  File "/usr/local/lib/python3.13/_pyrepl/main.py", line 59, in interactive_console
    run_multiline_interactive_console(console)
  File "/usr/local/lib/python3.13/_pyrepl/simple_interact.py", line 160, in run_multiline_interactive_console
    more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg]
  File "/usr/local/lib/python3.13/code.py", line 314, in push
    more = self.runsource(source, filename, symbol=_symbol)
  File "/usr/local/lib/python3.13/_pyrepl/console.py", line 211, in runsource
    self.runcode(code)
  File "/usr/local/lib/python3.13/code.py", line 96, in runcode
    self.showtraceback()
  File "/usr/local/lib/python3.13/code.py", line 129, in showtraceback
    self._showtraceback(typ, value, tb.tb_next, '')
  File "/usr/local/lib/python3.13/code.py", line 145, in _showtraceback
    self._excepthook(typ, value, tb)
  File "/usr/local/lib/python3.13/_pyrepl/console.py", line 169, in _excepthook
    lines = traceback.format_exception(
  File "/usr/local/lib/python3.13/traceback.py", line 154, in format_exception
    te = TracebackException(type(value), value, tb, limit=limit, compact=True)
  File "/usr/local/lib/python3.13/traceback.py", line 1090, in __init__
    suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
  File "/usr/local/lib/python3.13/traceback.py", line 1516, in _compute_suggestion_error
    if hasattr(self, wrong_name):
  File "<python-input-3>", line 4, in __getattr__
NameError: name 'qq' is not defined

And the interactive python session is terminated.

Under previous Python releases, with no pyrepl, the right exception is raised and the repl keeps going:

Python 3.9.21 (main, Dec  9 2024, 19:43:35) 
[GCC 10.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class a:
...   def __getattr__(self, x):
...     print(x)
...     print(qq)
... 
>>> b=a()
>>> b.pepe
pepe
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getattr__
NameError: name 'qq' is not defined
>>> 

Notice that this problem raises SPECIFICALLY if __getattr__ raises a NameError exception. Other exceptions, like division by zero, don't break pyrepl.

CPython versions tested on:

3.13, 3.12, 3.11, 3.10, 3.9, 3.14

Operating systems tested on:

Linux

Linked PRs

@jcea jcea added topic-repl Related to the interactive shell type-bug An unexpected behavior, bug, or error labels Feb 3, 2025
@skirpichev
Copy link
Member

A quick fix (I'm not sure it's a right way):

diff --git a/Lib/traceback.py b/Lib/traceback.py
index 31c73efcef..abcc099c29 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -1520,8 +1520,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
         # has the wrong name as attribute
         if 'self' in frame.f_locals:
             self = frame.f_locals['self']
-            if hasattr(self, wrong_name):
-                return f"self.{wrong_name}"
+            try:
+                if hasattr(self, wrong_name):
+                    return f"self.{wrong_name}"
+            except NameError:
+                pass
 
     try:
         import _suggestions
>>> b.pepe
pepe
qq
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    b.pepe
  File "<python-input-0>", line 4, in __getattr__
    print(qq)
          ^^
NameError: name 'qq' is not defined

@jcea
Copy link
Member Author
jcea commented Feb 3, 2025

Cool, but "qq" should not be searched in the class. Notice that __getattr__ is called twice, first for "pepe" and another one, incorrectly, for "qq".

@skirpichev
Copy link
Member

That might be helpful for suggestions, see 99e2e60.

@lopuhin
Copy link
lopuhin commented Feb 25, 2025

Another instance of the same error:

import traceback


class C:
    @property
    def foo(self):
        return foo

try:
    C().foo
except Exception:
    print(traceback.format_exc())

gives (at least on Python 3.12):

% python t.py
Traceback (most recent call last):
  File "/Users/konstantin/t.py", line 10, in <module>
    C().foo
  File "/Users/konstantin/t.py", line 7, in foo
    return foo
           ^^^
NameError: name 'foo' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/konstantin/t.py", line 12, in <module>
    print(traceback.format_exc())
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 184, in format_exc
    return "".join(format_exception(sys.exception(), limit=limit, chain=chain))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 139, in format_exception
    te = TracebackException(type(value), value, tb, limit=limit, compact=True)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 767, in __init__
    suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/traceback.py", line 1100, in _compute_suggestion_error
    if hasattr(self, wrong_name):
       ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/konstantin/t.py", line 7, in foo
    return foo
           ^^^
NameError: name 'foo' is not defined

so the property gets re-executed. In other cases this can also lead to an infinite chain of exceptions.

@picnixz picnixz added the stdlib Python modules in the Lib dir label Feb 25, 2025
@ambv ambv changed the title Crashing "pyrepl" with "__getattr__" traceback._compute_suggestion_error crashes on __getattr__ raising Mar 20, 2025
@ambv ambv removed the topic-repl Related to the interactive shell label Mar 20, 2025
@kak-0
Copy link
kak-0 commented Apr 14, 2025

I encountered this bug when trying to log such an exception using logging.

The following code demonstrates the issue (using ValueError for demonstration purposes, even though the real issue I had was a NameError due to forgetting to write self.).

import logging

class Foo:
    @property
    def x(self):
        raise ValueError("x")

    def y(self):
        return x

logger = logging.getLogger(__name__)

try:
    Foo().y()
except Exception as e:
    logger.exception("y failed") # raises!

print("done") # not reached!

logger.exception prints "--- Logging error ---" and then calls traceback.print_exception which fails to print the traceback. The exception raised by hasattr is propagated by logger.exception, preventing the program from continuing!

I think this should be considered a bug in logging: logger.exception should probably silently drop any exceptions raised while trying to handle logging errors instead of propagating the exceptions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

7 participants
0