Skip to content

Commit 234be58

Browse files
Fix RuntimeError caused by analyzing live objects with __getattribute__ or descriptors (#2687) (#2688)
Avoid some attribute accesses in const_factory (cherry picked from commit 71dc6b2) Co-authored-by: aatle <168398276+aatle@users.noreply.github.com>
1 parent 6aeafd5 commit 234be58

File tree

4 files changed

+35
-3
lines changed

4 files changed

+35
-3
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ What's New in astroid 3.3.9?
1414
Release date: TBA
1515

1616

17+
* Fix crash when `sys.modules` contains lazy loader objects during checking.
18+
19+
Closes #2686
20+
Closes pylint-dev/pylint#8589
21+
1722

1823
What's New in astroid 3.3.8?
1924
============================

astroid/nodes/node_classes.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5488,6 +5488,7 @@ def _create_basic_elements(
54885488
"""Create a list of nodes to function as the elements of a new node."""
54895489
elements: list[NodeNG] = []
54905490
for element in value:
5491+
# NOTE: avoid accessing any attributes of element in the loop.
54915492
element_node = const_factory(element)
54925493
element_node.parent = node
54935494
elements.append(element_node)
@@ -5500,6 +5501,7 @@ def _create_dict_items(
55005501
"""Create a list of node pairs to function as the items of a new dict node."""
55015502
elements: list[tuple[SuccessfulInferenceResult, SuccessfulInferenceResult]] = []
55025503
for key, value in values.items():
5504+
# NOTE: avoid accessing any attributes of both key and value in the loop.
55035505
key_node = const_factory(key)
55045506
key_node.parent = node
55055507
value_node = const_factory(value)
@@ -5510,18 +5512,23 @@ def _create_dict_items(
55105512

55115513
def const_factory(value: Any) -> ConstFactoryResult:
55125514
"""Return an astroid node for a python value."""
5513-
assert not isinstance(value, NodeNG)
5515+
# NOTE: avoid accessing any attributes of value until it is known that value
5516+
# is of a const type, to avoid possibly triggering code for a live object.
5517+
# Accesses include value.__class__ and isinstance(value, ...), but not type(value).
5518+
# See: https://github.com/pylint-dev/astroid/issues/2686
5519+
value_type = type(value)
5520+
assert not issubclass(value_type, NodeNG)
55145521

55155522
# This only handles instances of the CONST types. Any
55165523
# subclasses get inferred as EmptyNode.
55175524
# TODO: See if we should revisit these with the normal builder.
5518-
if value.__class__ not in CONST_CLS:
5525+
if value_type not in CONST_CLS:
55195526
node = EmptyNode()
55205527
node.object = value
55215528
return node
55225529

55235530
instance: List | Set | Tuple | Dict
5524-
initializer_cls = CONST_CLS[value.__class__]
5531+
initializer_cls = CONST_CLS[value_type]
55255532
if issubclass(initializer_cls, (List, Set, Tuple)):
55265533
instance = initializer_cls(
55275534
lineno=None,

tests/test_raw_building.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import pytest
2323

2424
import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr
25+
import tests.testdata.python3.data.fake_module_with_collection_getattribute as fm_collection
2526
import tests.testdata.python3.data.fake_module_with_warnings as fm
2627
from astroid.builder import AstroidBuilder
2728
from astroid.const import IS_PYPY, PY312_PLUS
@@ -118,6 +119,14 @@ def test_module_object_with_broken_getattr(self) -> None:
118119
# This should not raise an exception
119120
AstroidBuilder().inspect_build(fm_getattr, "test")
120121

122+
def test_module_collection_with_object_getattribute(self) -> None:
123+
# Tests https://github.com/pylint-dev/astroid/issues/2686
124+
# When astroid live inspection of module's collection raises
125+
# error when element __getattribute__ causes collection to change size.
126+
127+
# This should not raise an exception
128+
AstroidBuilder().inspect_build(fm_collection, "test")
129+
121130

122131
@pytest.mark.skipif(
123132
"posix" not in sys.builtin_module_names, reason="Platform doesn't support posix"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Changer:
2+
def __getattribute__(self, name):
3+
list_collection.append(self)
4+
set_collection.add(self)
5+
dict_collection[self] = self
6+
return object.__getattribute__(self, name)
7+
8+
9+
list_collection = [Changer()]
10+
set_collection = {Changer()}
11+
dict_collection = {Changer(): Changer()}

0 commit comments

Comments
 (0)