Skip to content

Commit 02d0934

Browse files
authored
fix(profiling): Use type() instead when extracting frames (#3716)
When extract frame names, we should avoid accessing the `__class__` attribute as it can be overwritten in the class implementation. In this particular instance, the `SimpleLazyObject` class in django wraps `__class__` so when it is accessed, it can cause the underlying lazy object to be evaluation unexpectedly. To avoid this, use the `type()` builtin function which does cannot be overwritten and will return the correct class. Note that this does not work with old style classes but since dropping python 2 support, we only need to consider new style classes.
1 parent bf40090 commit 02d0934

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

sentry_sdk/profiler/utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def get_frame_name(frame):
8989
and co_varnames[0] == "self"
9090
and "self" in frame.f_locals
9191
):
92-
for cls in frame.f_locals["self"].__class__.__mro__:
92+
for cls in type(frame.f_locals["self"]).__mro__:
9393
if name in cls.__dict__:
9494
return "{}.{}".format(cls.__name__, name)
9595
except (AttributeError, ValueError):

tests/integrations/django/test_basic.py

+48
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import inspect
12
import json
23
import os
34
import re
5+
import sys
46
import pytest
57
from functools import partial
68
from unittest.mock import patch
@@ -12,6 +14,7 @@
1214
from django.core.management import execute_from_command_line
1315
from django.db.utils import OperationalError, ProgrammingError, DataError
1416
from django.http.request import RawPostDataException
17+
from django.utils.functional import SimpleLazyObject
1518

1619
try:
1720
from django.urls import reverse
@@ -29,6 +32,7 @@
2932
)
3033
from sentry_sdk.integrations.django.signals_handlers import _get_receiver_name
3134
from sentry_sdk.integrations.executing import ExecutingIntegration
35+
from sentry_sdk.profiler.utils import get_frame_name
3236
from sentry_sdk.tracing import Span
3337
from tests.conftest import unpack_werkzeug_response
3438
from tests.integrations.django.myapp.wsgi import application
@@ -1295,3 +1299,47 @@ def test_ensures_no_spotlight_middleware_when_no_spotlight(
12951299
added = frozenset(settings.MIDDLEWARE) ^ original_middleware
12961300

12971301
assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added
1302+
1303+
1304+
def test_get_frame_name_when_in_lazy_object():
1305+
allowed_to_init = False
1306+
1307+
class SimpleLazyObjectWrapper(SimpleLazyObject):
1308+
def unproxied_method(self):
1309+
"""
1310+
For testing purposes. We inject a method on the SimpleLazyObject
1311+
class so if python is executing this method, we should get
1312+
this class instead of the wrapped class and avoid evaluating
1313+
the wrapped object too early.
1314+
"""
1315+
return inspect.currentframe()
1316+
1317+
class GetFrame:
1318+
def __init__(self):
1319+
assert allowed_to_init, "GetFrame not permitted to initialize yet"
1320+
1321+
def proxied_method(self):
1322+
"""
1323+
For testing purposes. We add an proxied method on the instance
1324+
class so if python is executing this method, we should get
1325+
this class instead of the wrapper class.
1326+
"""
1327+
return inspect.currentframe()
1328+
1329+
instance = SimpleLazyObjectWrapper(lambda: GetFrame())
1330+
1331+
assert get_frame_name(instance.unproxied_method()) == (
1332+
"SimpleLazyObjectWrapper.unproxied_method"
1333+
if sys.version_info < (3, 11)
1334+
else "test_get_frame_name_when_in_lazy_object.<locals>.SimpleLazyObjectWrapper.unproxied_method"
1335+
)
1336+
1337+
# Now that we're about to access an instance method on the wrapped class,
1338+
# we should permit initializing it
1339+
allowed_to_init = True
1340+
1341+
assert get_frame_name(instance.proxied_method()) == (
1342+
"GetFrame.proxied_method"
1343+
if sys.version_info < (3, 11)
1344+
else "test_get_frame_name_when_in_lazy_object.<locals>.GetFrame.proxied_method"
1345+
)

0 commit comments

Comments
 (0)