3
3
from collections .abc import Mapping
4
4
from typing import Any
5
5
6
+ import orjson
7
+
8
+ from sentry .attachments import CachedAttachment , attachment_cache
9
+ from sentry .ingest .consumer .processors import CACHE_TIMEOUT
6
10
from sentry .lang .java .utils import get_jvm_images , get_proguard_images
7
11
from sentry .lang .native .error import SymbolicationFailed , write_error
8
12
from sentry .lang .native .symbolicator import Symbolicator
11
15
from sentry .models .release import Release
12
16
from sentry .stacktraces .processing import find_stacktraces_in_data
13
17
from sentry .utils import metrics
18
+ from sentry .utils .cache import cache_key_for_event
14
19
from sentry .utils .safe import get_path
15
20
16
21
logger = logging .getLogger (__name__ )
@@ -139,6 +144,76 @@ def _get_release_package(project: Project, release_name: str | None) -> str | No
139
144
return release .package if release else None
140
145
141
146
147
+ def _get_window_class_names (attachments : list [CachedAttachment ]) -> list [str ]:
148
+ """Returns the class names of all windows in all view hierarchies
149
+ contained in `attachments`."""
150
+
151
+ class_names = []
152
+ windows_to_deobfuscate = []
153
+
154
+ for attachment in attachments :
155
+ if attachment .type == "event.view_hierarchy" :
156
+ view_hierarchy = orjson .loads (attachment_cache .get_data (attachment ))
157
+ windows_to_deobfuscate .extend (view_hierarchy .get ("windows" ))
158
+
159
+ while windows_to_deobfuscate :
160
+ window = windows_to_deobfuscate .pop ()
161
+ if window .get ("type" ) is not None :
162
+ class_names .append (window ["type" ])
163
+ if children := window .get ("children" ):
164
+ windows_to_deobfuscate .extend (children )
165
+
166
+ return class_names
167
+
168
+
169
+ def _deobfuscate_view_hierarchy (view_hierarchy : Any , class_names : dict [str , str ]) -> None :
170
+ """Deobfuscates a view hierarchy in-place.
171
+
172
+ The `class_names` dict is used to resolve obfuscated to deobfuscated names. If
173
+ an obfuscated class name isn't present in `class_names`, it is left unchanged."""
174
+
175
+ windows_to_deobfuscate = [* view_hierarchy .get ("windows" )]
176
+
177
+ while windows_to_deobfuscate :
178
+ window = windows_to_deobfuscate .pop ()
179
+ if (
180
+ window .get ("type" ) is not None
181
+ and (mapped_type := class_names .get (window ["type" ])) is not None
182
+ ):
183
+ window ["type" ] = mapped_type
184
+ if children := window .get ("children" ):
185
+ windows_to_deobfuscate .extend (children )
186
+
187
+
188
+ def _deobfuscate_view_hierarchies (
189
+ attachments : list [CachedAttachment ], class_names : dict [str , str ]
190
+ ) -> list [CachedAttachment ]:
191
+ """Deobfuscates all view hierarchies contained in `attachments`, returning a new list of attachments.
192
+
193
+ Non-view-hierarchy attachments are unchanged.
194
+ """
195
+ new_attachments = []
196
+ for attachment in attachments :
197
+ if attachment .type == "event.view_hierarchy" :
198
+ view_hierarchy = orjson .loads (attachment_cache .get_data (attachment ))
199
+ _deobfuscate_view_hierarchy (view_hierarchy , class_names )
200
+ # Reupload to cache as a unchunked data
201
+ new_attachments .append (
202
+ CachedAttachment (
203
+ type = attachment .type ,
204
+ id = attachment .id ,
205
+ name = attachment .name ,
206
+ content_type = attachment .content_type ,
207
+ data = orjson .dumps (view_hierarchy ),
208
+ chunks = None ,
209
+ )
210
+ )
211
+ else :
212
+ new_attachments .append (attachment )
213
+
214
+ return new_attachments
215
+
216
+
142
217
def map_symbolicator_process_jvm_errors (
143
218
errors : list [dict [str , Any ]] | None ,
144
219
) -> list [dict [str , Any ]]:
@@ -195,10 +270,17 @@ def process_jvm_stacktraces(symbolicator: Symbolicator, data: Any) -> Any:
195
270
]
196
271
197
272
processable_exceptions = _get_exceptions_for_symbolication (data )
273
+ cache_key = cache_key_for_event (data )
274
+ attachments = [* attachment_cache .get (cache_key )]
275
+ window_class_names = _get_window_class_names (attachments )
198
276
199
277
metrics .incr ("proguard.symbolicator.events" )
200
278
201
- if not any (stacktrace ["frames" ] for stacktrace in stacktraces ) and not processable_exceptions :
279
+ if (
280
+ not any (stacktrace ["frames" ] for stacktrace in stacktraces )
281
+ and not processable_exceptions
282
+ and not window_class_names
283
+ ):
202
284
metrics .incr ("proguard.symbolicator.events.skipped" )
203
285
return
204
286
@@ -211,6 +293,7 @@ def process_jvm_stacktraces(symbolicator: Symbolicator, data: Any) -> Any:
211
293
stacktraces = stacktraces ,
212
294
modules = modules ,
213
295
release_package = release_package ,
296
+ classes = window_class_names ,
214
297
)
215
298
216
299
if not _handle_response_status (data , response ):
@@ -248,4 +331,8 @@ def process_jvm_stacktraces(symbolicator: Symbolicator, data: Any) -> Any:
248
331
raw_exc ["module" ] = exc ["module" ]
249
332
raw_exc ["type" ] = exc ["type" ]
250
333
334
+ classes = response .get ("classes" )
335
+ new_attachments = _deobfuscate_view_hierarchies (attachments , classes )
336
+ attachment_cache .set (cache_key , attachments = new_attachments , timeout = CACHE_TIMEOUT )
337
+
251
338
return data
0 commit comments