bpo-25532: Protect against infinite loops in inspect.unwrap() (#1717) · python/cpython@f9169ce (original) (raw)
3 files changed
lines changed
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -505,13 +505,16 @@ def _is_wrapper(f): | ||
505 | 505 | def _is_wrapper(f): |
506 | 506 | return hasattr(f, '__wrapped__') and not stop(f) |
507 | 507 | f = func # remember the original func for error reporting |
508 | -memo = {id(f)} # Memoise by id to tolerate non-hashable objects | |
508 | +# Memoise by id to tolerate non-hashable objects, but store objects to | |
509 | +# ensure they aren't destroyed, which would allow their IDs to be reused. | |
510 | +memo = {id(f): f} | |
511 | +recursion_limit = sys.getrecursionlimit() | |
509 | 512 | while _is_wrapper(func): |
510 | 513 | func = func.__wrapped__ |
511 | 514 | id_func = id(func) |
512 | -if id_func in memo: | |
515 | +if (id_func in memo) or (len(memo) >= recursion_limit): | |
513 | 516 | raise ValueError('wrapper loop when unwrapping {!r}'.format(f)) |
514 | -memo.add(id_func) | |
517 | +memo[id_func] = func | |
515 | 518 | return func |
516 | 519 | |
517 | 520 | # -------------------------------------------------- source code extraction |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -3554,6 +3554,19 @@ def test_builtins_have_signatures(self): | ||
3554 | 3554 | self.assertIsNone(obj.__text_signature__) |
3555 | 3555 | |
3556 | 3556 | |
3557 | +class NTimesUnwrappable: | |
3558 | +def __init__(self, n): | |
3559 | +self.n = n | |
3560 | +self._next = None | |
3561 | + | |
3562 | +@property | |
3563 | +def __wrapped__(self): | |
3564 | +if self.n <= 0: | |
3565 | +raise Exception("Unwrapped too many times") | |
3566 | +if self._next is None: | |
3567 | +self._next = NTimesUnwrappable(self.n - 1) | |
3568 | +return self._next | |
3569 | + | |
3557 | 3570 | class TestUnwrap(unittest.TestCase): |
3558 | 3571 | |
3559 | 3572 | def test_unwrap_one(self): |
@@ -3609,6 +3622,11 @@ class C: | ||
3609 | 3622 | __wrapped__ = func |
3610 | 3623 | self.assertIsNone(inspect.unwrap(C())) |
3611 | 3624 | |
3625 | +def test_recursion_limit(self): | |
3626 | +obj = NTimesUnwrappable(sys.getrecursionlimit() + 1) | |
3627 | +with self.assertRaisesRegex(ValueError, 'wrapper loop'): | |
3628 | +inspect.unwrap(obj) | |
3629 | + | |
3612 | 3630 | class TestMain(unittest.TestCase): |
3613 | 3631 | def test_only_source(self): |
3614 | 3632 | module = importlib.import_module('unittest') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -995,6 +995,10 @@ Library | ||
995 | 995 | - Issue #29581: ABCMeta.__new__ now accepts ``**kwargs``, allowing abstract base |
996 | 996 | classes to use keyword parameters in __init_subclass__. Patch by Nate Soares. |
997 | 997 | |
998 | +- Issue #25532: inspect.unwrap() will now only try to unwrap an object | |
999 | + sys.getrecursionlimit() times, to protect against objects which create a new | |
1000 | + object on every attribute access. | |
1001 | + | |
998 | 1002 | Windows |
999 | 1003 | ------- |
1000 | 1004 |