Skip to content

Commit bb85c26

Browse files
Fix cache pollution from mutable reference (#3887)
- Removes manual overrides of copy behavior and leaves it up to the caller. - E.g. a future use case may require a non-deepcopy. If we override copy they would have to remove the dunder copy, update every implementation which relies copy, before finally creating their own copy implementation. - Deepcopies the flag buffer. - Though we do not cache mutable references yet we may soon and so this foot gun should be removed from possibility. - Removes "copy" test coverage from `test_lru_cache.py`. We're no longer assuming copy usage and leave it up to the caller. - The existing test in `tests/test_scope.py` covers the cache pollution case [originally mentioned here](#3852). - The mutable cache pollution case is not covered because we do not currently cache mutable objects. In general a generic class should assume as few implementation details as possible. If we leave the existing copy method someone may assume copy semantics and rely on it in a way that is inappropriate. Closes: #3886 Co-authored-by: Anton Pirker <[email protected]>
1 parent c3516db commit bb85c26

File tree

4 files changed

+2
-71
lines changed

4 files changed

+2
-71
lines changed

sentry_sdk/_lru_cache.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,6 @@ def __init__(self, max_size):
1717
self.hits = self.misses = 0
1818
self.full = False
1919

20-
def __copy__(self):
21-
# type: () -> LRUCache
22-
new = LRUCache(max_size=self.max_size)
23-
new.hits = self.hits
24-
new.misses = self.misses
25-
new.full = self.full
26-
new._data = self._data.copy()
27-
return new
28-
2920
def set(self, key, value):
3021
# type: (Any, Any) -> None
3122
current = self._data.pop(key, _SENTINEL)

sentry_sdk/flag_utils.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from copy import copy
21
from typing import TYPE_CHECKING
32

43
import sentry_sdk
@@ -25,12 +24,6 @@ def clear(self):
2524
# type: () -> None
2625
self.buffer = LRUCache(self.capacity)
2726

28-
def __copy__(self):
29-
# type: () -> FlagBuffer
30-
buffer = FlagBuffer(capacity=self.capacity)
31-
buffer.buffer = copy(self.buffer)
32-
return buffer
33-
3427
def get(self):
3528
# type: () -> list[FlagData]
3629
return [{"flag": key, "result": value} for key, value in self.buffer.get_all()]

sentry_sdk/scope.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import sys
33
import warnings
4-
from copy import copy
4+
from copy import copy, deepcopy
55
from collections import deque
66
from contextlib import contextmanager
77
from enum import Enum
@@ -252,7 +252,7 @@ def __copy__(self):
252252

253253
rv._last_event_id = self._last_event_id
254254

255-
rv._flags = copy(self._flags)
255+
rv._flags = deepcopy(self._flags)
256256

257257
return rv
258258

tests/test_lru_cache.py

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import pytest
2-
from copy import copy, deepcopy
32

43
from sentry_sdk._lru_cache import LRUCache
54

@@ -59,55 +58,3 @@ def test_cache_get_all():
5958
assert cache.get_all() == [(1, 1), (2, 2), (3, 3)]
6059
cache.get(1)
6160
assert cache.get_all() == [(2, 2), (3, 3), (1, 1)]
62-
63-
64-
def test_cache_copy():
65-
cache = LRUCache(3)
66-
cache.set(0, 0)
67-
cache.set(1, 1)
68-
69-
copied = copy(cache)
70-
cache.set(2, 2)
71-
cache.set(3, 3)
72-
assert copied.get_all() == [(0, 0), (1, 1)]
73-
assert cache.get_all() == [(1, 1), (2, 2), (3, 3)]
74-
75-
copied = copy(cache)
76-
cache.get(1)
77-
assert copied.get_all() == [(1, 1), (2, 2), (3, 3)]
78-
assert cache.get_all() == [(2, 2), (3, 3), (1, 1)]
79-
80-
81-
def test_cache_deepcopy():
82-
cache = LRUCache(3)
83-
cache.set(0, 0)
84-
cache.set(1, 1)
85-
86-
copied = deepcopy(cache)
87-
cache.set(2, 2)
88-
cache.set(3, 3)
89-
assert copied.get_all() == [(0, 0), (1, 1)]
90-
assert cache.get_all() == [(1, 1), (2, 2), (3, 3)]
91-
92-
copied = deepcopy(cache)
93-
cache.get(1)
94-
assert copied.get_all() == [(1, 1), (2, 2), (3, 3)]
95-
assert cache.get_all() == [(2, 2), (3, 3), (1, 1)]
96-
97-
98-
def test_cache_pollution():
99-
cache1 = LRUCache(max_size=2)
100-
cache1.set(1, True)
101-
cache2 = copy(cache1)
102-
cache2.set(1, False)
103-
assert cache1.get(1) is True
104-
assert cache2.get(1) is False
105-
106-
107-
def test_cache_pollution_deepcopy():
108-
cache1 = LRUCache(max_size=2)
109-
cache1.set(1, True)
110-
cache2 = deepcopy(cache1)
111-
cache2.set(1, False)
112-
assert cache1.get(1) is True
113-
assert cache2.get(1) is False

0 commit comments

Comments
 (0)