Skip to content

Commit 79f313a

Browse files
authored
Fix tab completion for subclasses of trusted classes (ipython#15020)
## Fixes ipython#14988 ## Solution Added logic to check the MRO for base classes from trusted modules. Now trusts subclasses if: - Any parent class comes from a trusted module - The `__getattr__` method is inherited (not overridden)
2 parents 3c53444 + 4345e36 commit 79f313a

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

IPython/core/guarded_eval.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,18 @@ def _has_original_dunder_external(
179179
member_method = getattr(member_type, method_name, None)
180180
if member_method == method:
181181
return True
182+
if isinstance(member_type, ModuleType):
183+
method = getattr(value_type, method_name, None)
184+
for base_class in value_type.__mro__[1:]:
185+
base_module = getmodule(base_class)
186+
if base_module and (
187+
base_module.__name__ == member_type.__name__
188+
or base_module.__name__.startswith(member_type.__name__ + ".")
189+
):
190+
# Check if the method comes from this trusted base class
191+
base_method = getattr(base_class, method_name, None)
192+
if base_method is not None and base_method == method:
193+
return True
182194
except (AttributeError, KeyError):
183195
return False
184196

tests/test_completer.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,56 @@ def __getattr__(self, attr):
15231523
_, matches = complete(line_buffer="unsafe_list_factory.example.")
15241524
self.assertNotIn(".append", matches)
15251525

1526+
def test_completion_allow_subclass_of_trusted_module(self):
1527+
factory_code = textwrap.dedent(
1528+
"""
1529+
class ListFactory:
1530+
def __getattr__(self, attr):
1531+
return []
1532+
"""
1533+
)
1534+
trusted_lib = types.ModuleType("my.trusted.lib")
1535+
sys.modules["my.trusted.lib"] = trusted_lib
1536+
exec(factory_code, trusted_lib.__dict__)
1537+
1538+
ip = get_ipython()
1539+
# Create a subclass in __main__ (untrusted namespace)
1540+
subclass_code = textwrap.dedent(
1541+
"""
1542+
class SubclassFactory(trusted_lib.ListFactory):
1543+
pass
1544+
"""
1545+
)
1546+
ip.user_ns["trusted_lib"] = trusted_lib
1547+
exec(subclass_code, ip.user_ns)
1548+
ip.user_ns["subclass_factory"] = ip.user_ns["SubclassFactory"]()
1549+
complete = ip.Completer.complete
1550+
with (
1551+
evaluation_policy("limited", allowed_getattr_external={"my.trusted.lib"}),
1552+
jedi_status(False),
1553+
):
1554+
_, matches = complete(line_buffer="subclass_factory.example.")
1555+
self.assertIn(".append", matches)
1556+
1557+
# Test that overriding __getattr__ in subclass in untrusted namespace prevents completion
1558+
overriding_subclass_code = textwrap.dedent(
1559+
"""
1560+
class OverridingSubclass(trusted_lib.ListFactory):
1561+
def __getattr__(self, attr):
1562+
return {}
1563+
"""
1564+
)
1565+
exec(overriding_subclass_code, ip.user_ns)
1566+
ip.user_ns["overriding_factory"] = ip.user_ns["OverridingSubclass"]()
1567+
1568+
with (
1569+
evaluation_policy("limited", allowed_getattr_external={"my.trusted.lib"}),
1570+
jedi_status(False),
1571+
):
1572+
_, matches = complete(line_buffer="overriding_factory.example.")
1573+
self.assertNotIn(".append", matches)
1574+
self.assertNotIn(".keys", matches)
1575+
15261576
def test_policy_warnings(self):
15271577
with self.assertWarns(
15281578
UserWarning,

0 commit comments

Comments
 (0)