Skip to content

Commit 9056fbc

Browse files
committed
Fix traceback: Nested attribute suggestions execute property code
1 parent c9a5d9a commit 9056fbc

File tree

4 files changed

+23
-16
lines changed

4 files changed

+23
-16
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ Improved error messages
378378
name, the error message will suggest accessing it via that inner attribute:
379379

380380
.. code-block:: python
381+
from dataclasses import dataclass
382+
from math import pi
381383
382384
@dataclass
383385
class Circle:

Lib/test/test_traceback.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4401,17 +4401,23 @@ def __init__(self):
44014401
def test_getattr_nested_with_property(self):
44024402
# Test that descriptors (including properties) are suggested in nested attributes
44034403
class Inner:
4404+
def __init__(self):
4405+
self.access_counter = 0
44044406
@property
44054407
def computed(self):
4408+
self.access_counter += 1
44064409
return 42
44074410

44084411
class Outer:
44094412
def __init__(self):
44104413
self.inner = Inner()
44114414

4412-
actual = self.get_suggestion(Outer(), 'computed')
4413-
# Descriptors should not be suggested to avoid executing arbitrary code
4415+
obj = Outer()
4416+
actual = self.get_suggestion(obj, 'computed')
4417+
# Descriptors should be suggested
44144418
self.assertIn("inner.computed", actual)
4419+
# Should not increment the access counter
4420+
self.assertEqual(obj.inner.access_counter, 0)
44154421

44164422
def test_getattr_nested_no_suggestion_for_deep_nesting(self):
44174423
# Test that deeply nested attributes (2+ levels) are not suggested

Lib/traceback.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io
1515
import importlib.util
1616
import pathlib
17+
import inspect
1718
import _colorize
1819

1920
from contextlib import suppress
@@ -1670,30 +1671,27 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
16701671
16711672
Returns the first nested attribute suggestion found, or None.
16721673
Limited to checking 20 attributes.
1673-
Only considers non-descriptor attributes to avoid executing arbitrary code.
16741674
Skips lazy imports to avoid triggering module loading.
16751675
"""
16761676
# Check for nested attributes (only one level deep)
16771677
attrs_to_check = [x for x in attrs if not x.startswith('_')][:20] # Limit number of attributes to check
16781678
for attr_name in attrs_to_check:
1679-
with suppress(Exception):
1680-
# Check if attr_name is a descriptor - if so, skip it
1681-
attr_from_class = getattr(type(obj), attr_name, None)
1682-
if attr_from_class is not None and hasattr(attr_from_class, '__get__'):
1683-
continue # Skip descriptors to avoid executing arbitrary code
1684-
1679+
with suppress(AttributeError):
1680+
attr_obj = inspect.getattr_static(obj, attr_name)
1681+
1682+
try:
1683+
inspect.getattr_static(attr_obj, '__get__')
1684+
continue # Descriptor, skip it as we can't access its contents safely
1685+
except AttributeError:
1686+
pass
1687+
16851688
# Skip lazy imports to avoid triggering module loading
16861689
if _is_lazy_import(obj, attr_name):
16871690
continue
16881691

1689-
# Safe to get the attribute since it's not a descriptor
1690-
attr_obj = getattr(obj, attr_name)
1691-
1692-
# Check if the nested attribute exists and is not a descriptor
1693-
nested_attr_from_class = getattr(type(attr_obj), wrong_name, None)
1692+
inspect.getattr_static(attr_obj, wrong_name)
16941693

1695-
if hasattr(attr_obj, wrong_name):
1696-
return f"{attr_name}.{wrong_name}"
1694+
return f"{attr_name}.{wrong_name}"
16971695

16981696
return None
16991697

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed nested properties being executed in error message suggestions

0 commit comments

Comments
 (0)