https://github.com/python/cpython/commit/f23a1837d7156c4c478528321a423eae2b31e4bf
commit: f23a1837d7156c4c478528321a423eae2b31e4bf
branch: main
author: Michael Droettboom <[email protected]>
committer: mdboom <[email protected]>
date: 2026-05-10T21:44:59-04:00
summary:
gh-112821: Fix rlcompleter failures on objects with descriptors (#149577)
* gh-112821: Fix rlcompleter failures on objects with descriptors
* Confirm no accesses
files:
A Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
M Lib/rlcompleter.py
M Lib/test/test_rlcompleter.py
diff --git a/Lib/rlcompleter.py b/Lib/rlcompleter.py
index e8cef29d00467f..6c6d9bb6b34244 100644
--- a/Lib/rlcompleter.py
+++ b/Lib/rlcompleter.py
@@ -179,14 +179,14 @@ def attr_matches(self, text):
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
diff --git a/Lib/test/test_rlcompleter.py b/Lib/test/test_rlcompleter.py
index a8914953ce9eb4..e6d727d417b298 100644
--- a/Lib/test/test_rlcompleter.py
+++ b/Lib/test/test_rlcompleter.py
@@ -1,6 +1,7 @@
import unittest
from unittest.mock import patch
import builtins
+import types
import rlcompleter
from test.support import MISSING_C_DOCSTRINGS
@@ -135,6 +136,57 @@ def bar(self):
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
diff --git
a/Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
b/Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
new file mode 100644
index 00000000000000..cfbcde81493e22
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
@@ -0,0 +1,4 @@
+In the REPL, autocompletion might run arbitrary code in the getter of a
+descriptor. If that getter raised an exception, autocompletion would fail to
+present any options for the entire object. Autocompletion now works as
+expected for these objects.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]