if (word[:n] == attr and
not (noprefix and word[:n+1] == noprefix)):
match = "%s.%s" % (expr, word)
- if isinstance(getattr(type(thisobject), word, None),
- property):
- # bpo-44752: thisobject.word is a method decorated by
- # `@property`. What follows applies a postfix if
- # thisobject.word is callable, but know we know that
- # this is not callable (because it is a property).
- # Also, getattr(thisobject, word) will evaluate the
- # property method, which is not desirable.
+
+ class_attr = getattr(type(thisobject), word, None)
+ if isinstance(
+ class_attr,
+ (property, types.GetSetDescriptorType, types.MemberDescriptorType)
+ ) or (hasattr(class_attr, '__get__') and not callable(class_attr)):
+ # Avoid evaluating descriptors, which could run
+ # arbitrary code or raise exceptions.
matches.append(match)
continue
import unittest
from unittest.mock import patch
import builtins
+import types
import rlcompleter
from test.support import MISSING_C_DOCSTRINGS
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
self.assertFalse(f.property_called)
+ def test_released_memoryview_completion_works(self):
+ mv = memoryview(b"abc")
+ mv.release()
+
+ self.assertIsInstance(type(mv).shape, types.GetSetDescriptorType)
+ self.assertIsInstance(type(mv).strides, types.GetSetDescriptorType)
+
+ completer = rlcompleter.Completer(dict(mv=mv))
+ matches = completer.attr_matches('mv.')
+
+ # These are getset descriptors on memoryview and should be completed
+ # without evaluating the released-memoryview getters.
+ self.assertIn('mv.shape', matches)
+ self.assertIn('mv.strides', matches)
+
+ def test_member_descriptor_not_evaluated(self):
+ class Foo:
+ __slots__ = ("boom",)
+ boom_accesses = 0
+
+ def __getattribute__(self, name):
+ if name == "boom":
+ type(self).boom_accesses += 1
+ raise RuntimeError("boom access should be skipped")
+ return super().__getattribute__(name)
+
+ self.assertIsInstance(Foo.boom, types.MemberDescriptorType)
+
+ completer = rlcompleter.Completer(dict(f=Foo()))
+ matches = completer.attr_matches('f.')
+ self.assertIn('f.boom', matches)
+ self.assertEqual(Foo.boom_accesses, 0)
+
+ def test_raising_descriptor_completion_works(self):
+ class ExplodingDescriptor:
+ def __init__(self):
+ self.instance_get_calls = 0
+
+ def __get__(self, obj, owner):
+ if obj is None:
+ return self
+ self.instance_get_calls += 1
+ raise RuntimeError("descriptor getter exploded")
+
+ class Foo:
+ boom = ExplodingDescriptor()
+
+ completer = rlcompleter.Completer(dict(f=Foo()))
+ matches = completer.attr_matches('f.')
+ self.assertIn('f.boom', matches)
+ self.assertEqual(Foo.boom.instance_get_calls, 0)
def test_uncreated_attr(self):
# Attributes like properties and slots should be completed even when