|
97 | 97 | using ObjCRuntime; |
98 | 98 | #endif |
99 | 99 |
|
100 | | -#if ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_STACKFRAMEWITHADDRESS |
| 100 | +#if ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_STACKFRAMEWITHADDRESS || ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_RECORD_ERROR_USER_INFO |
101 | 101 | using Firebase.Crashlytics; |
| 102 | +using Foundation; |
102 | 103 | using ObjCRuntime; |
103 | 104 | #endif |
104 | 105 |
|
@@ -2527,6 +2528,88 @@ void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEven |
2527 | 2528 | } |
2528 | 2529 | #endif |
2529 | 2530 |
|
| 2531 | +#if ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_RECORD_ERROR_USER_INFO |
| 2532 | + static Task<string> VerifyCrashlyticsRecordErrorUserInfoAsync() |
| 2533 | + { |
| 2534 | + const string selector = "recordError:userInfo:"; |
| 2535 | + |
| 2536 | + var signature = typeof(Crashlytics).GetMethod( |
| 2537 | + nameof(Crashlytics.RecordError), |
| 2538 | + BindingFlags.Instance | BindingFlags.Public, |
| 2539 | + binder: null, |
| 2540 | + types: new[] { typeof(NSError), typeof(NSDictionary<NSString, NSObject>) }, |
| 2541 | + modifiers: null); |
| 2542 | + if (signature is null) |
| 2543 | + { |
| 2544 | + throw new InvalidOperationException( |
| 2545 | + $"Expected managed API '{nameof(Crashlytics.RecordError)}({typeof(NSError).FullName}, {typeof(NSDictionary<NSString, NSObject>).FullName})' was not found."); |
| 2546 | + } |
| 2547 | + |
| 2548 | + var crashlytics = Crashlytics.SharedInstance; |
| 2549 | + if (crashlytics is null) |
| 2550 | + { |
| 2551 | + throw new InvalidOperationException("Firebase.Crashlytics.Crashlytics.SharedInstance returned null after App.Configure()."); |
| 2552 | + } |
| 2553 | + |
| 2554 | + if (!crashlytics.RespondsToSelector(new Selector(selector))) |
| 2555 | + { |
| 2556 | + throw new InvalidOperationException($"Native FIRCrashlytics does not respond to expected selector '{selector}'."); |
| 2557 | + } |
| 2558 | + |
| 2559 | + using var domain = new NSString("codex.crashlytics.e2e"); |
| 2560 | + using var userInfoKey = new NSString("codex_context"); |
| 2561 | + using var userInfoValue = new NSString("record-error-user-info"); |
| 2562 | + using var error = new NSError(domain, -130, null); |
| 2563 | + using var userInfo = NSDictionary<NSString, NSObject>.FromObjectsAndKeys( |
| 2564 | + new NSObject[] { userInfoValue }, |
| 2565 | + new[] { userInfoKey }, |
| 2566 | + 1); |
| 2567 | + |
| 2568 | + NSException? marshaledException = null; |
| 2569 | + MarshalObjectiveCExceptionMode? marshaledExceptionMode = null; |
| 2570 | + |
| 2571 | + void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEventArgs args) |
| 2572 | + { |
| 2573 | + marshaledException ??= args.Exception; |
| 2574 | + marshaledExceptionMode ??= args.ExceptionMode; |
| 2575 | + } |
| 2576 | + |
| 2577 | + Runtime.MarshalObjectiveCException += OnMarshalObjectiveCException; |
| 2578 | + try |
| 2579 | + { |
| 2580 | + try |
| 2581 | + { |
| 2582 | + crashlytics.RecordError(error, userInfo); |
| 2583 | + } |
| 2584 | + catch (ObjCException ex) |
| 2585 | + { |
| 2586 | + throw new InvalidOperationException( |
| 2587 | + $"Selector '{selector}' should not throw after the missing binding is added, but observed {ex.GetType().FullName}. " + |
| 2588 | + $"NSError domain: {error.Domain}. UserInfo type: {userInfo.GetType().FullName}. " + |
| 2589 | + $"NSException.Name: {FormatDetail(marshaledException?.Name?.ToString())}. " + |
| 2590 | + $"NSException.Reason: {FormatDetail(marshaledException?.Reason)}. " + |
| 2591 | + $"Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}.", |
| 2592 | + ex); |
| 2593 | + } |
| 2594 | + |
| 2595 | + if (marshaledException is not null) |
| 2596 | + { |
| 2597 | + throw new InvalidOperationException( |
| 2598 | + $"Selector '{selector}' completed, but Runtime.MarshalObjectiveCException captured unexpected NSException.Name '{marshaledException.Name}'. " + |
| 2599 | + $"Reason: {FormatDetail(marshaledException.Reason)}. Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}."); |
| 2600 | + } |
| 2601 | + |
| 2602 | + return Task.FromResult( |
| 2603 | + $"Selector '{selector}' crossed the native boundary without ObjC exception. " + |
| 2604 | + $"NSError domain: {error.Domain}. UserInfo count: {userInfo.Count}."); |
| 2605 | + } |
| 2606 | + finally |
| 2607 | + { |
| 2608 | + Runtime.MarshalObjectiveCException -= OnMarshalObjectiveCException; |
| 2609 | + } |
| 2610 | + } |
| 2611 | +#endif |
| 2612 | + |
2530 | 2613 | static string FormatNSError(Foundation.NSError error) |
2531 | 2614 | { |
2532 | 2615 | return $"{error.Domain} ({error.Code}): {error.LocalizedDescription}"; |
|
0 commit comments