From cdd3b26ef108a7f432044978d1bcff563f8bce8b Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:54:22 -0800 Subject: [PATCH 1/3] Avoid some attribute accesses in const_factory --- astroid/nodes/node_classes.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 47b7a9fd0..ebfd5ffe8 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -5514,6 +5514,7 @@ def _create_basic_elements( """Create a list of nodes to function as the elements of a new node.""" elements: list[NodeNG] = [] for element in value: + # NOTE: avoid accessing any attributes of element in the loop. element_node = const_factory(element) element_node.parent = node elements.append(element_node) @@ -5526,6 +5527,7 @@ def _create_dict_items( """Create a list of node pairs to function as the items of a new dict node.""" elements: list[tuple[SuccessfulInferenceResult, SuccessfulInferenceResult]] = [] for key, value in values.items(): + # NOTE: avoid accessing any attributes of both key and value in the loop. key_node = const_factory(key) key_node.parent = node value_node = const_factory(value) @@ -5536,18 +5538,23 @@ def _create_dict_items( def const_factory(value: Any) -> ConstFactoryResult: """Return an astroid node for a python value.""" - assert not isinstance(value, NodeNG) + # NOTE: avoid accessing any attributes of value until it is known that value + # is of a const type, to avoid possibly triggering code for a live object. + # Accesses include value.__class__ and isinstance(value, ...), but not type(value). + # See: https://github.com/pylint-dev/astroid/issues/2686 + value_type = type(value) + assert not issubclass(value_type, NodeNG) # This only handles instances of the CONST types. Any # subclasses get inferred as EmptyNode. # TODO: See if we should revisit these with the normal builder. - if value.__class__ not in CONST_CLS: + if value_type not in CONST_CLS: node = EmptyNode() node.object = value return node instance: List | Set | Tuple | Dict - initializer_cls = CONST_CLS[value.__class__] + initializer_cls = CONST_CLS[value_type] if issubclass(initializer_cls, (List, Set, Tuple)): instance = initializer_cls( lineno=None, From 55b11efff97e563faa1c2831c595c8138c04d533 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 16 Feb 2025 14:16:19 -0800 Subject: [PATCH 2/3] Update changelog for live objects fix --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 3b88dd19a..d7d8da1b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -42,6 +42,10 @@ Release date: TBA Closes pylint-dev/pylint#10112 +* Fix crash when `sys.modules` contains lazy loader objects during checking. + + Closes #2686 + Closes pylint-dev/pylint#8589 What's New in astroid 3.3.8? From 965a2130c4d16bc807b058c92f2d2a36c0d442cf Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:26:19 -0800 Subject: [PATCH 3/3] Add test for collection element __getattribute__ --- tests/test_raw_building.py | 9 +++++++++ .../data/fake_module_with_collection_getattribute.py | 11 +++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/testdata/python3/data/fake_module_with_collection_getattribute.py diff --git a/tests/test_raw_building.py b/tests/test_raw_building.py index 2d18e8a19..58b17b27c 100644 --- a/tests/test_raw_building.py +++ b/tests/test_raw_building.py @@ -22,6 +22,7 @@ import pytest import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr +import tests.testdata.python3.data.fake_module_with_collection_getattribute as fm_collection import tests.testdata.python3.data.fake_module_with_warnings as fm from astroid.builder import AstroidBuilder from astroid.const import IS_PYPY, PY312_PLUS @@ -121,6 +122,14 @@ def test_module_object_with_broken_getattr(self) -> None: # This should not raise an exception AstroidBuilder(AstroidManager()).inspect_build(fm_getattr, "test") + def test_module_collection_with_object_getattribute(self) -> None: + # Tests https://github.com/pylint-dev/astroid/issues/2686 + # When astroid live inspection of module's collection raises + # error when element __getattribute__ causes collection to change size. + + # This should not raise an exception + AstroidBuilder(AstroidManager()).inspect_build(fm_collection, "test") + @pytest.mark.skipif( "posix" not in sys.builtin_module_names, reason="Platform doesn't support posix" diff --git a/tests/testdata/python3/data/fake_module_with_collection_getattribute.py b/tests/testdata/python3/data/fake_module_with_collection_getattribute.py new file mode 100644 index 000000000..e6ab45089 --- /dev/null +++ b/tests/testdata/python3/data/fake_module_with_collection_getattribute.py @@ -0,0 +1,11 @@ +class Changer: + def __getattribute__(self, name): + list_collection.append(self) + set_collection.add(self) + dict_collection[self] = self + return object.__getattribute__(self, name) + + +list_collection = [Changer()] +set_collection = {Changer()} +dict_collection = {Changer(): Changer()}