Skip to content

Only enable load debug image integration for obfuscated apps #2907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: feat-web/debug-id
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
- [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.
Expand Down
13 changes: 8 additions & 5 deletions dart/lib/src/load_dart_debug_images_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@ import 'protocol/sentry_stack_trace.dart';
import 'sentry_options.dart';

class LoadDartDebugImagesIntegration extends Integration<SentryOptions> {
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;

Expand Down
9 changes: 9 additions & 0 deletions dart/lib/src/runtime_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
40 changes: 37 additions & 3 deletions dart/test/load_dart_debug_images_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -26,6 +27,7 @@ void main() {
setUp(() {
fixture = Fixture();
fixture.options.platform = platform;
fixture.callIntegration();
});

test('adds itself to sdk.integrations', () {
Expand All @@ -40,7 +42,7 @@ void main() {
expect(fixture.options.eventProcessors.length, 1);
expect(
fixture.options.eventProcessors.first.runtimeType.toString(),
'LoadImageIntegrationEventProcessor',
'LoadDartDebugImagesIntegrationEventProcessor',
);
});

Expand Down Expand Up @@ -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'
Expand All @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions dart/test/mocks/mock_runtime_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,4 +24,7 @@ class MockRuntimeChecker extends RuntimeChecker with NoSuchMethodProvider {

@override
bool isReleaseMode() => isRelease;

@override
bool isAppObfuscated() => isObfuscated;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<SentryEvent?> apply(SentryEvent event, Hint hint) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ Integration<SentryFlutterOptions> createLoadDebugImagesIntegration(

/// Loads the debug id injected by Sentry tooling e.g Sentry Dart Plugin
/// This is necessary for symbolication of minified js stacktraces via debug ids.
class LoadWebDebugImagesIntegration
extends Integration<SentryFlutterOptions> {
class LoadWebDebugImagesIntegration extends Integration<SentryFlutterOptions> {
final SentryNativeBinding _native;
static const integrationName = 'LoadWebDebugImages';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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();
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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() {
Expand Down
5 changes: 5 additions & 0 deletions flutter/test/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,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
Expand All @@ -88,6 +90,9 @@ class MockRuntimeChecker with NoSuchMethodProvider implements RuntimeChecker {
@override
bool isReleaseMode() => buildMode == MockRuntimeCheckerBuildMode.release;

@override
bool isAppObfuscated() => isObfuscated;

@override
bool get isRootZone => isRoot;
}
Expand Down
Loading