diff --git a/Lib/inspect.py b/Lib/inspect.py index 3317f58f475..e9c2dbd5c88 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 facf040d3cd..b194b27aba3 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -3557,6 +3557,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): @@ -3612,6 +3625,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.d/next/Library/2017-09-27-08-11-38.bpo-25532.ey4Yez.rst b/Misc/NEWS.d/next/Library/2017-09-27-08-11-38.bpo-25532.ey4Yez.rst new file mode 100644 index 00000000000..8146dcdc6d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-09-27-08-11-38.bpo-25532.ey4Yez.rst @@ -0,0 +1,3 @@ +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.