diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6bbfeb64..480b660eb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,10 @@ void initState() { - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#090) - [diff](https://github.com/getsentry/sentry-native/compare/0.8.4...0.9.0) +### Enhancements + +- Only enable load debug image integration for obfuscated apps ([#2907](https://github.com/getsentry/sentry-dart/pull/2907)) + ## 9.0.0 Version 9.0.0 marks a major release of the Sentry Dart/Flutter SDKs containing breaking changes. diff --git a/dart/lib/src/load_dart_debug_images_integration.dart b/dart/lib/src/load_dart_debug_images_integration.dart index 8cba0f5456..b26aa05a83 100644 --- a/dart/lib/src/load_dart_debug_images_integration.dart +++ b/dart/lib/src/load_dart_debug_images_integration.dart @@ -14,20 +14,23 @@ import 'protocol/sentry_stack_trace.dart'; import 'sentry_options.dart'; class LoadDartDebugImagesIntegration extends Integration { - static const integrationName = 'LoadDartDebugImagesIntegration'; + static const integrationName = 'LoadDartDebugImages'; @override void call(Hub hub, SentryOptions options) { - if (options.enableDartSymbolication) { - options.addEventProcessor(LoadImageIntegrationEventProcessor(options)); + if (options.enableDartSymbolication && + options.runtimeChecker.isAppObfuscated() && + !options.platform.isWeb) { + options.addEventProcessor( + LoadDartDebugImagesIntegrationEventProcessor(options)); options.sdk.addIntegration(integrationName); } } } @internal -class LoadImageIntegrationEventProcessor implements EventProcessor { - LoadImageIntegrationEventProcessor(this._options); +class LoadDartDebugImagesIntegrationEventProcessor implements EventProcessor { + LoadDartDebugImagesIntegrationEventProcessor(this._options); final SentryOptions _options; diff --git a/dart/lib/src/runtime_checker.dart b/dart/lib/src/runtime_checker.dart index 4aee63c8fd..458b247c49 100644 --- a/dart/lib/src/runtime_checker.dart +++ b/dart/lib/src/runtime_checker.dart @@ -22,6 +22,15 @@ class RuntimeChecker { return const bool.fromEnvironment('dart.vm.profile', defaultValue: false); } + /// Check if the Dart code is obfuscated. + bool isAppObfuscated() { + // In non-obfuscated builds, this will return "RuntimeChecker" + // In obfuscated builds, this will return something like "a" or other short identifier + // Note: Flutter Web production builds will always be minified / "obfuscated". + final typeName = runtimeType.toString(); + return !typeName.contains('RuntimeChecker'); + } + final bool isRootZone; String get compileMode { diff --git a/dart/test/load_dart_debug_images_integration_test.dart b/dart/test/load_dart_debug_images_integration_test.dart index 11fdddfa5c..c81da91086 100644 --- a/dart/test/load_dart_debug_images_integration_test.dart +++ b/dart/test/load_dart_debug_images_integration_test.dart @@ -9,6 +9,7 @@ import 'package:sentry/src/platform/mock_platform.dart'; import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:test/test.dart'; +import 'mocks/mock_runtime_checker.dart'; import 'test_utils.dart'; void main() { @@ -26,6 +27,7 @@ void main() { setUp(() { fixture = Fixture(); fixture.options.platform = platform; + fixture.callIntegration(); }); test('adds itself to sdk.integrations', () { @@ -40,7 +42,7 @@ void main() { expect(fixture.options.eventProcessors.length, 1); expect( fixture.options.eventProcessors.first.runtimeType.toString(), - 'LoadImageIntegrationEventProcessor', + 'LoadDartDebugImagesIntegrationEventProcessor', ); }); @@ -191,8 +193,39 @@ isolate_dso_base: 10000000 }); } + test('does not add itself to sdk.integrations if app is not obfuscated', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: false); + fixture.callIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + false, + ); + }); + + test('does not add event processor to options if app is not obfuscated', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: false); + fixture.callIntegration(); + expect(fixture.options.eventProcessors.length, 0); + }); + + test('does not add itself to sdk.integrations if platform is web', () { + final fixture = Fixture() + ..options.runtimeChecker = MockRuntimeChecker(isObfuscated: true) + ..options.platform = MockPlatform(isWeb: true); + fixture.callIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadDartDebugImagesIntegration.integrationName), + false, + ); + }); + test('debug image is null on unsupported platforms', () async { final fixture = Fixture()..options.platform = MockPlatform.linux(); + fixture.callIntegration(); final event = fixture.newEvent(stackTrace: fixture.parse(''' *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** build_id: 'b680cb890f9e3c12a24b172d050dec73' @@ -205,10 +238,11 @@ isolate_dso_base: 40000000 } class Fixture { - final options = defaultTestOptions(); + final options = defaultTestOptions() + ..runtimeChecker = MockRuntimeChecker(isObfuscated: true); late final factory = SentryStackTraceFactory(options); - Fixture() { + void callIntegration() { final integration = LoadDartDebugImagesIntegration(); integration.call(Hub(options), options); } diff --git a/dart/test/mocks/mock_runtime_checker.dart b/dart/test/mocks/mock_runtime_checker.dart index c7ea74b730..3ce75c9b54 100644 --- a/dart/test/mocks/mock_runtime_checker.dart +++ b/dart/test/mocks/mock_runtime_checker.dart @@ -7,12 +7,14 @@ class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider { this.isDebug = false, this.isProfile = false, this.isRelease = false, + this.isObfuscated = false, bool isRootZone = true, }) : super(isRootZone: isRootZone); final bool isDebug; final bool isProfile; final bool isRelease; + final bool isObfuscated; @override bool isDebugMode() => isDebug; @@ -22,4 +24,7 @@ class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider { @override bool isReleaseMode() => isRelease; + + @override + bool isAppObfuscated() => isObfuscated; } diff --git a/flutter/lib/src/integrations/native_load_debug_images_integration.dart b/flutter/lib/src/integrations/native_load_debug_images_integration.dart index 4fe7210afd..e5115ab825 100644 --- a/flutter/lib/src/integrations/native_load_debug_images_integration.dart +++ b/flutter/lib/src/integrations/native_load_debug_images_integration.dart @@ -22,20 +22,25 @@ class LoadNativeDebugImagesIntegration @override void call(Hub hub, SentryFlutterOptions options) { - options.addEventProcessor( - _LoadImageListIntegrationEventProcessor(options, _native), - ); - options.sdk.addIntegration(integrationName); + // ignore: invalid_use_of_internal_member + if (options.runtimeChecker.isAppObfuscated()) { + options.addEventProcessor( + _LoadNativeDebugImagesIntegrationEventProcessor(options, _native), + ); + options.sdk.addIntegration(integrationName); + } } } -class _LoadImageListIntegrationEventProcessor implements EventProcessor { - _LoadImageListIntegrationEventProcessor(this._options, this._native); +class _LoadNativeDebugImagesIntegrationEventProcessor + implements EventProcessor { + _LoadNativeDebugImagesIntegrationEventProcessor(this._options, this._native); final SentryFlutterOptions _options; final SentryNativeBinding _native; - late final _dartProcessor = LoadImageIntegrationEventProcessor(_options); + late final _dartProcessor = + LoadDartDebugImagesIntegrationEventProcessor(_options); @override Future apply(SentryEvent event, Hint hint) async { diff --git a/flutter/test/integrations/load_native_debug_images_integration_test.dart b/flutter/test/integrations/load_native_debug_images_integration_test.dart index b0e1dfecd3..3b5861ad17 100644 --- a/flutter/test/integrations/load_native_debug_images_integration_test.dart +++ b/flutter/test/integrations/load_native_debug_images_integration_test.dart @@ -6,6 +6,7 @@ import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/integrations/native_load_debug_images_integration.dart'; +import '../mocks.dart'; import 'fixture.dart'; void main() { @@ -26,6 +27,7 @@ void main() { setUp(() async { fixture = IntegrationTestFixture(LoadNativeDebugImagesIntegration.new); + fixture.options.runtimeChecker = MockRuntimeChecker(isObfuscated: true); when(fixture.binding.loadDebugImages(any)) .thenAnswer((_) async => imageList.toList()); await fixture.registerIntegration(); @@ -73,8 +75,8 @@ void main() { test('Event processor adds image list to the event', () async { final ep = fixture.options.eventProcessors.first; - expect( - ep.runtimeType.toString(), "_LoadImageListIntegrationEventProcessor"); + expect(ep.runtimeType.toString(), + "_LoadNativeDebugImagesIntegrationEventProcessor"); SentryEvent? event = _getEvent(); event = await ep.apply(event, Hint()); @@ -104,6 +106,28 @@ void main() { verifyNever(fixture.binding.loadDebugImages(any)); }); }); + + test('does not add itself to sdk.integrations if app is not obfuscated', + () async { + final fixture = + IntegrationTestFixture(LoadNativeDebugImagesIntegration.new); + fixture.options.runtimeChecker = MockRuntimeChecker(); + await fixture.registerIntegration(); + expect( + fixture.options.sdk.integrations + .contains(LoadNativeDebugImagesIntegration.integrationName), + false, + ); + }); + + test('does not add event processor to options if app is not obfuscated', + () async { + final fixture = + IntegrationTestFixture(LoadNativeDebugImagesIntegration.new); + fixture.options.runtimeChecker = MockRuntimeChecker(); + await fixture.registerIntegration(); + expect(fixture.options.eventProcessors.length, 0); + }); } SentryEvent _getEvent() { diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 1c563123ae..122ee92fef 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -79,10 +79,12 @@ void main() {} class MockRuntimeChecker with NoSuchMethodProvider implements RuntimeChecker { MockRuntimeChecker({ this.buildMode = MockRuntimeCheckerBuildMode.debug, + this.isObfuscated = false, this.isRoot = true, }); final MockRuntimeCheckerBuildMode buildMode; + final bool isObfuscated; final bool isRoot; @override @@ -94,6 +96,9 @@ class MockRuntimeChecker with NoSuchMethodProvider implements RuntimeChecker { @override bool isReleaseMode() => buildMode == MockRuntimeCheckerBuildMode.release; + @override + bool isAppObfuscated() => isObfuscated; + @override bool get isRootZone => isRoot; }