8000 self-protect against broken Mapping implementations (#306) · pythonthings/sentry-python@8122005 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8122005

Browse files
crepererumuntitaker
authored andcommitted
self-protect against broken Mapping implementations (getsentry#306)
1 parent 7a1ba2f commit 8122005

File tree

2 files changed

+77
-33
lines changed

2 files changed

+77
-33
lines changed

sentry_sdk/utils.py

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -349,39 +349,43 @@ def safe_repr(value):
349349

350350

351351
def object_to_json(obj, remaining_depth=4, memo=None):
352-
if memo is None:
353-
memo = Memo()
354-
if memo.memoize(obj):
355-
return CYCLE_MARKER
352+
with capture_internal_exceptions():
353+
if memo is None:
354+
memo = Memo()
355+
if memo.memoize(obj):
356+
return CYCLE_MARKER
356357

357-
try:
358-
if remaining_depth > 0:
359-
hints = {"memo": memo, "remaining_depth": remaining_depth}
360-
for processor in global_repr_processors:
361-
with capture_internal_exceptions():
362-
result = processor(obj, hints)
363-
if result is not NotImplemented:
364-
return result
365-
366-
if isinstance(obj, (list, tuple)):
367-
# It is not safe to iterate over another sequence types as this may raise errors or
368-
# bring undesired side-effects (e.g. Django querysets are executed during iteration)
369-
return [
370-
object_to_json(x, remaining_depth=remaining_depth - 1, memo=memo)
371-
for x in obj
372-
]
373-
374-
if isinstance(obj, Mapping):
375-
return {
376-
safe_str(k): object_to_json(
377-
v, remaining_depth=remaining_depth - 1, memo=memo
378-
)
379-
for k, v in list(obj.items())
380-
}
381-
382-
return safe_repr(obj)
383-
finally:
384-
memo.unmemoize(obj)
358+
try:
359+
if remaining_depth > 0:
360+
hints = {"memo": memo, "remaining_depth": remaining_depth}
361+
for processor in global_repr_processors:
362+
with capture_internal_exceptions():
363+
result = processor(obj, hints)
364+
if result is not NotImplemented:
365+
return result
366+
367+
if isinstance(obj, (list, tuple)):
368+
# It is not safe to iterate over another sequence types as this may raise errors or
369+
# bring undesired side-effects (e.g. Django querysets are executed during iteration)
370+
return [
371+
object_to_json(
372+
x, remaining_depth=remaining_depth - 1, memo=memo
373+
)
374+
for x in obj
375+
]
376+
377+
if isinstance(obj, Mapping):
378+
return {
379+
safe_str(k): object_to_json(
380+
v, remaining_depth=remaining_depth - 1, memo=memo
381+
)
382+
for k, v in list(obj.items())
383+
}
384+
385+
return safe_repr(obj)
386+
finally:
387+
memo.unmemoize(obj)
388+
return u"<broken repr>"
385389

386390

387391
def extract_locals(frame):

tests/test_client.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@
1818
)
1919
from sentry_sdk.hub import HubMeta
2020
from sentry_sdk.transport import Transport
21-
from sentry_sdk._compat import reraise, text_type
21+
from sentry_sdk._compat import reraise, text_type, PY2
2222
from sentry_sdk.utils import HAS_CHAINED_EXCEPTIONS
2323

24+
if PY2:
25+
# Importing ABCs from collections is deprecated, and will stop working in 3.8
26+
# https://github.com/python/cpython/blob/master/Lib/collections/__init__.py#L49
27+
from collections import Mapping
28+
else:
29+
# New in 3.3
30+
# https://docs.python.org/3/library/collections.abc.html
31+
from collections.abc import Mapping
32+
2433

2534
class EventCaptured(Exception):
2635
pass
@@ -404,3 +413,34 @@ def test_chained_exceptions(sentry_init, capture_events):
404413
event, = events
405414

406415
assert len(event["exception"]["values"]) == 2
416+
417+
418+
@pytest.mark.tests_internal_exceptions
419+
def test_broken_mapping(sentry_init, capture_events):
420+
sentry_init()
421+
events = capture_events()
422+
423+
class C(Mapping):
424+
def broken(self, *args, **kwargs):
425+
raise Exception("broken")
426+
427+
__getitem__ = broken
428+
__setitem__ = broken
429+
__delitem__ = broken
430+
__iter__ = broken
431+
__len__ = broken
432+
433+
def __repr__(self):
434+
return "broken"
435+
436+
try:
437+
a = C() # noqa
438+
1 / 0
439+
except Exception:
440+
capture_exception()
441+
442+
event, = events
443+
assert (
444+
event["exception"]["values"][0]["stacktrace"]["frames"][0]["vars"]["a"]
445+
== "<broken repr>"
446+
)

0 commit comments

Comments
 (0)
0