diff --git a/Lib/inspect.py b/Lib/inspect.py index 9c072eb0747..9a843d6420e 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -505,13 +505,16 @@ def unwrap(func, *, stop=None): def _is_wrapper(f): return hasattr(f, '__wrapped__') and not stop(f) f = func # remember the original func for error reporting - memo = {id(f)} # Memoise by id to tolerate non-hashable objects + # Memoise by id to tolerate non-hashable objects, but store objects to + # ensure they aren't destroyed, which would allow their IDs to be reused. + memo = {id(f): f} + recursion_limit = sys.getrecursionlimit() while _is_wrapper(func): func = func.__wrapped__ id_func = id(func) - if id_func in memo: + if (id_func in memo) or (len(memo) >= recursion_limit): raise ValueError('wrapper loop when unwrapping {!r}'.format(f)) - memo.add(id_func) + memo[id_func] = func return func # -------------------------------------------------- source code extraction diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index c55efd69425..350d5dbd776 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -3554,6 +3554,19 @@ class TestSignatureDefinitions(unittest.TestCase): self.assertIsNone(obj.__text_signature__) +class NTimesUnwrappable: + def __init__(self, n): + self.n = n + self._next = None + + @property + def __wrapped__(self): + if self.n <= 0: + raise Exception("Unwrapped too many times") + if self._next is None: + self._next = NTimesUnwrappable(self.n - 1) + return self._next + class TestUnwrap(unittest.TestCase): def test_unwrap_one(self): @@ -3609,6 +3622,11 @@ class TestUnwrap(unittest.TestCase): __wrapped__ = func self.assertIsNone(inspect.unwrap(C())) + def test_recursion_limit(self): + obj = NTimesUnwrappable(sys.getrecursionlimit() + 1) + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(obj) + class TestMain(unittest.TestCase): def test_only_source(self): module = importlib.import_module('unittest') diff --git a/Misc/NEWS b/Misc/NEWS index 5b41dcf61df..0e32d480b23 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -995,6 +995,10 @@ Library - Issue #29581: ABCMeta.__new__ now accepts ``**kwargs``, allowing abstract base classes to use keyword parameters in __init_subclass__. Patch by Nate Soares. +- Issue #25532: inspect.unwrap() will now only try to unwrap an object + sys.getrecursionlimit() times, to protect against objects which create a new + object on every attribute access. + Windows -------