You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On iOS, a user's managed BeforeSend callback is invoked for native crashes, and on that path SentryEvent.Exception is always null. A callback that dereferences Exception — entirely correct on every other path — throws a NullReferenceException for native crashes only.
This needs investigating rather than an obvious fix, hence a separate issue. Found while reviewing #5610, which changes what happens after the callback throws but does not introduce the null.
Why the callback runs at all
We deliberately hook the Cocoa SDK's own BeforeSend so that managed event processors and the user's BeforeSend also apply to native events — otherwise a native crash would bypass both:
A signal or Mach-exception crash (SIGSEGV, EXC_BAD_ACCESS, …) carries no NSError, so Error is null and Exception stays null. The native crash detail is still present on the event — it's in SentryExceptions from the deserialized wire-format payload — it just never reaches the Exception property.
The path
flowchart TD
A["Native crash<br/><i>SIGSEGV, no NSError</i>"] --> B["Crash report written to disk<br/><i>process is dead</i>"]
B --> C["Next app launch<br/><i>Cocoa SDK reads the report</i>"]
C --> D["ToSentryEvent()<br/><i>Exception stays null</i>"]
D --> E["User BeforeSend runs<br/><i>e.Exception.Message</i>"]
E --> F["NullReferenceException"]
F --> G["Before #5610<br/><i>crash still reported,<br/>breadcrumb attached</i>"]
F --> H["After #5610<br/><i>crash report dropped</i>"]
Loading
Why it's easy to miss
Exception is populated on every managed path, so if (e.Exception.Message.Contains("token")) is correct everywhere an author would test it, and throws only here. Because it throws only here, managed errors keep arriving normally and just the native crashes are affected — the symptom is "we stopped getting iOS crash reports", with nothing pointing at BeforeSend.
#5610 aligns BeforeSend failures with the callback error isolation spec (#5535): the item is dropped rather than sent with the exception stapled on as a breadcrumb. That is the right behaviour in general, and on this path it turns a degraded crash report into no crash report. It is logged at error level and counted in client reports, so it isn't silent, but neither is visible unless you go looking.
So #5610 changes the consequence; this issue is about the cause. ProcessOnBeforeSend gained test coverage for the throwing-callback case in #5610 (test/Sentry.Tests/Platforms/iOS/SentrySdkTests.cs).
Worth deciding
Should Exception be populated for native crashes — e.g. synthesised from SentryExceptions — or is null correct because there is no managed exception object?
If null is correct, is it documented anywhere a user writing BeforeSend would see it? SetBeforeSend's XML docs don't mention that the callback also runs for native events.
Does the same gap exist on the Android bridge, where Native.EnableBeforeSend routes native events through the managed callback too?
Summary
On iOS, a user's managed
BeforeSendcallback is invoked for native crashes, and on that pathSentryEvent.Exceptionis alwaysnull. A callback that dereferencesException— entirely correct on every other path — throws aNullReferenceExceptionfor native crashes only.This needs investigating rather than an obvious fix, hence a separate issue. Found while reviewing #5610, which changes what happens after the callback throws but does not introduce the null.
Why the callback runs at all
We deliberately hook the Cocoa SDK's own
BeforeSendso that managed event processors and the user'sBeforeSendalso apply to native events — otherwise a native crash would bypass both:sentry-dotnet/src/Sentry/Platforms/Cocoa/SentrySdk.cs
Line 101 in 85cc2bf
ProcessOnBeforeSendconverts the native event into a managedSentryEvent, runs managed processors, then callsSentryEventHelper.DoBeforeSend.Why
Exceptionis nullThe conversion populates
Exceptiononly from anNSError:sentry-dotnet/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs
Line 308 in 85cc2bf
A signal or Mach-exception crash (
SIGSEGV,EXC_BAD_ACCESS, …) carries noNSError, soErroris null andExceptionstays null. The native crash detail is still present on the event — it's inSentryExceptionsfrom the deserialized wire-format payload — it just never reaches theExceptionproperty.The path
flowchart TD A["Native crash<br/><i>SIGSEGV, no NSError</i>"] --> B["Crash report written to disk<br/><i>process is dead</i>"] B --> C["Next app launch<br/><i>Cocoa SDK reads the report</i>"] C --> D["ToSentryEvent()<br/><i>Exception stays null</i>"] D --> E["User BeforeSend runs<br/><i>e.Exception.Message</i>"] E --> F["NullReferenceException"] F --> G["Before #5610<br/><i>crash still reported,<br/>breadcrumb attached</i>"] F --> H["After #5610<br/><i>crash report dropped</i>"]Why it's easy to miss
Exceptionis populated on every managed path, soif (e.Exception.Message.Contains("token"))is correct everywhere an author would test it, and throws only here. Because it throws only here, managed errors keep arriving normally and just the native crashes are affected — the symptom is "we stopped getting iOS crash reports", with nothing pointing atBeforeSend.Relationship to #5610
#5610 aligns
BeforeSendfailures with the callback error isolation spec (#5535): the item is dropped rather than sent with the exception stapled on as a breadcrumb. That is the right behaviour in general, and on this path it turns a degraded crash report into no crash report. It is logged at error level and counted in client reports, so it isn't silent, but neither is visible unless you go looking.So #5610 changes the consequence; this issue is about the cause.
ProcessOnBeforeSendgained test coverage for the throwing-callback case in #5610 (test/Sentry.Tests/Platforms/iOS/SentrySdkTests.cs).Worth deciding
Exceptionbe populated for native crashes — e.g. synthesised fromSentryExceptions— or is null correct because there is no managed exception object?BeforeSendwould see it?SetBeforeSend's XML docs don't mention that the callback also runs for native events.Native.EnableBeforeSendroutes native events through the managed callback too?