Unverified Commit 08d079f6 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] wire up complete support for Dart obfuscation (#50509)

parent baa4b783
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
Future<void> main() async {
await task(() async {
try {
bool foundProjectName = false;
await runProjectTest((FlutterProject flutterProject) async {
section('APK content for task assembleRelease with --obfuscate');
await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[
'apk',
'--target-platform=android-arm',
'--obfuscate',
'--split-debug-info=foo/',
]);
});
final String outputDirectory = path.join(
flutterProject.rootPath,
'build/app/outputs/apk/release/app-release.apk',
);
final Iterable<String> apkFiles = await getFilesInApk(outputDirectory);
checkCollectionContains<String>(<String>[
...flutterAssets,
...baseApkFiles,
'lib/armeabi-v7a/libapp.so',
], apkFiles);
// Verify that an identifier from the Dart project code is not present
// in the compiled binary.
await inDirectory(flutterProject.rootPath, () async {
await exec('unzip', <String>[outputDirectory]);
final String response = await eval(
'grep',
<String>[flutterProject.name, 'lib/armeabi-v7a/libapp.so'],
canFail: true,
);
if (response.trim().contains('matches')) {
foundProjectName = true;
}
});
});
if (foundProjectName) {
return TaskResult.failure('Found project name in obfuscated dart library');
}
return TaskResult.success(null);
} on TaskResult catch (taskResult) {
return taskResult;
} catch (e) {
return TaskResult.failure(e.toString());
}
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
Future<void> main() async {
await task(() async {
try {
bool foundProjectName = false;
await runProjectTest((FlutterProject flutterProject) async {
section('iOS Framework content with --obfuscate');
await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[
'ios',
'--release',
'--obfuscate',
'--split-debug-info=foo/',
]);
});
final String outputFramework = path.join(
flutterProject.rootPath,
'build/ios/iphoneos/Runner.app/Frameworks/App.framework/App',
);
if (!File(outputFramework).existsSync()) {
fail('Failed to produce expected output at $outputFramework');
}
// Verify that an identifier from the Dart project code is not present
// in the compiled binary.
await inDirectory(flutterProject.rootPath, () async {
final String response = await eval(
'grep',
<String>[flutterProject.name, outputFramework],
canFail: true,
);
if (response.trim().contains('matches')) {
foundProjectName = true;
}
});
});
if (foundProjectName) {
return TaskResult.failure('Found project name in obfuscated dart library');
}
return TaskResult.success(null);
} on TaskResult catch (taskResult) {
return taskResult;
} catch (e) {
return TaskResult.failure(e.toString());
}
});
}
...@@ -319,6 +319,13 @@ tasks: ...@@ -319,6 +319,13 @@ tasks:
stage: devicelab stage: devicelab
required_agent_capabilities: ["mac/android"] required_agent_capabilities: ["mac/android"]
android_obfuscate_test:
description: >
Builds an obfuscated APK and verifies a dart identifier cannot be found
stage: devicelab
flaky: true
required_agent_capabilities: ["linux/android"]
complex_layout_semantics_perf: complex_layout_semantics_perf:
description: > description: >
Measures duration of building the initial semantics tree. Measures duration of building the initial semantics tree.
...@@ -390,6 +397,13 @@ tasks: ...@@ -390,6 +397,13 @@ tasks:
# iOS on-device tests # iOS on-device tests
ios_obfuscate_test:
description: >
Builds an obfuscated APK and verifies a dart identifier cannot be found
stage: devicelab
flaky: true
required_agent_capabilities: ["mac/ios"]
tiles_scroll_perf_ios__timeline_summary: tiles_scroll_perf_ios__timeline_summary:
description: > description: >
Measures the runtime performance of the tiles tab in the Complex Layout sample app on iPhone 6. Measures the runtime performance of the tiles tab in the Complex Layout sample app on iPhone 6.
......
...@@ -67,6 +67,11 @@ if [[ -n "$TREE_SHAKE_ICONS" ]]; then ...@@ -67,6 +67,11 @@ if [[ -n "$TREE_SHAKE_ICONS" ]]; then
icon_tree_shaker_flag="true" icon_tree_shaker_flag="true"
fi fi
dart_obfuscation_flag="false"
if [[ -n "$DART_OBFUSCATION" ]]; then
dart_obfuscation_flag="true"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \ ${verbose_flag} \
${flutter_engine_flag} \ ${flutter_engine_flag} \
...@@ -76,6 +81,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \ ...@@ -76,6 +81,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
-dTargetFile="${target_path}" \ -dTargetFile="${target_path}" \
-dBuildMode="${build_mode}" \ -dBuildMode="${build_mode}" \
-dTreeShakeIcons="${icon_tree_shaker_flag}" \ -dTreeShakeIcons="${icon_tree_shaker_flag}" \
-dDartObfuscation="${dart_obfuscation_flag}" \
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \ -dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
--build-inputs="${build_inputs_path}" \ --build-inputs="${build_inputs_path}" \
--build-outputs="${build_outputs_path}" \ --build-outputs="${build_outputs_path}" \
......
...@@ -166,6 +166,11 @@ BuildApp() { ...@@ -166,6 +166,11 @@ BuildApp() {
icon_tree_shaker_flag="true" icon_tree_shaker_flag="true"
fi fi
dart_obfuscation_flag="false"
if [[ -n "$DART_OBFUSCATION" ]]; then
dart_obfuscation_flag="true"
fi
RunCommand "${FLUTTER_ROOT}/bin/flutter" \ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${verbose_flag} \ ${verbose_flag} \
${flutter_engine_flag} \ ${flutter_engine_flag} \
...@@ -179,6 +184,7 @@ BuildApp() { ...@@ -179,6 +184,7 @@ BuildApp() {
-dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \ -dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \
-dTreeShakeIcons="${icon_tree_shaker_flag}" \ -dTreeShakeIcons="${icon_tree_shaker_flag}" \
-dTrackWidgetCreation="${track_widget_creation_flag}" \ -dTrackWidgetCreation="${track_widget_creation_flag}" \
-dDartObfuscation="${dart_obfuscation_flag}" \
-dEnableBitcode="${bitcode_flag}" \ -dEnableBitcode="${bitcode_flag}" \
"${build_mode}_ios_bundle_flutter_assets" "${build_mode}_ios_bundle_flutter_assets"
......
...@@ -607,6 +607,10 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -607,6 +607,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('split-debug-info')) { if (project.hasProperty('split-debug-info')) {
splitDebugInfoValue = project.property('split-debug-info') splitDebugInfoValue = project.property('split-debug-info')
} }
Boolean dartObfuscationValue = false
if (project.hasProperty('dart-obfuscation')) {
dartObfuscationValue = project.property('dart-obfuscation').toBoolean();
}
Boolean treeShakeIconsOptionsValue = false Boolean treeShakeIconsOptionsValue = false
if (project.hasProperty('tree-shake-icons')) { if (project.hasProperty('tree-shake-icons')) {
treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean() treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean()
...@@ -647,6 +651,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -647,6 +651,7 @@ class FlutterPlugin implements Plugin<Project> {
extraGenSnapshotOptions extraGenSnapshotOptionsValue extraGenSnapshotOptions extraGenSnapshotOptionsValue
splitDebugInfo splitDebugInfoValue splitDebugInfo splitDebugInfoValue
treeShakeIcons treeShakeIconsOptionsValue treeShakeIcons treeShakeIconsOptionsValue
dartObfuscation dartObfuscationValue
doLast { doLast {
project.exec { project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
...@@ -792,6 +797,8 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -792,6 +797,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String splitDebugInfo String splitDebugInfo
@Optional @Input @Optional @Input
Boolean treeShakeIcons Boolean treeShakeIcons
@Optional @Input
Boolean dartObfuscation
@OutputFiles @OutputFiles
FileCollection getDependenciesFiles() { FileCollection getDependenciesFiles() {
...@@ -854,6 +861,9 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -854,6 +861,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (treeShakeIcons == true) { if (treeShakeIcons == true) {
args "-dTreeShakeIcons=true" args "-dTreeShakeIcons=true"
} }
if (dartObfuscation == true) {
args "-dDartObfuscation=true"
}
if (extraGenSnapshotOptions != null) { if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
} }
......
...@@ -344,6 +344,9 @@ Future<void> buildGradleApp({ ...@@ -344,6 +344,9 @@ Future<void> buildGradleApp({
if (androidBuildInfo.buildInfo.treeShakeIcons) { if (androidBuildInfo.buildInfo.treeShakeIcons) {
command.add('-Ptree-shake-icons=true'); command.add('-Ptree-shake-icons=true');
} }
if (androidBuildInfo.buildInfo.dartObfuscation) {
command.add('-Pdart-obfuscation=true');
}
command.add(assembleTask); command.add(assembleTask);
GradleHandledError detectedGradleError; GradleHandledError detectedGradleError;
......
...@@ -93,6 +93,7 @@ class AotBuilder { ...@@ -93,6 +93,7 @@ class AotBuilder {
bitcode: bitcode, bitcode: bitcode,
quiet: quiet, quiet: quiet,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
).then<int>((int buildExitCode) { ).then<int>((int buildExitCode) {
return buildExitCode; return buildExitCode;
}); });
...@@ -130,6 +131,7 @@ class AotBuilder { ...@@ -130,6 +131,7 @@ class AotBuilder {
extraGenSnapshotOptions: extraGenSnapshotOptions, extraGenSnapshotOptions: extraGenSnapshotOptions,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
if (snapshotExitCode != 0) { if (snapshotExitCode != 0) {
status?.cancel(); status?.cancel();
......
...@@ -42,6 +42,16 @@ class GenSnapshot { ...@@ -42,6 +42,16 @@ class GenSnapshot {
Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode); Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode);
} }
/// Ignored warning messages from gen_snapshot.
static const Set<String> kIgnoredWarnings = <String>{
// --strip on elf snapshot.
'Warning: Generating ELF library without DWARF debugging information.',
// --strip on ios-assembly snapshot.
'Warning: Generating assembly code without DWARF debugging information.',
// A fun two-part message with spaces for obfuscation.
'Warning: This VM has been configured to obfuscate symbol information which violates the Dart standard.',
' See dartbug.com/30524 for more information.',
};
Future<int> run({ Future<int> run({
@required SnapshotType snapshotType, @required SnapshotType snapshotType,
DarwinArch darwinArch, DarwinArch darwinArch,
...@@ -59,18 +69,9 @@ class GenSnapshot { ...@@ -59,18 +69,9 @@ class GenSnapshot {
snapshotterPath += '_' + getNameForDarwinArch(darwinArch); snapshotterPath += '_' + getNameForDarwinArch(darwinArch);
} }
StringConverter outputFilter;
if (additionalArgs.contains('--strip')) {
// Filter out gen_snapshot's warning message about stripping debug symbols
// from ELF library snapshots.
const String kStripWarning = 'Warning: Generating ELF library without DWARF debugging information.';
const String kAssemblyStripWarning = 'Warning: Generating assembly code without DWARF debugging information.';
outputFilter = (String line) => line != kStripWarning && line != kAssemblyStripWarning ? line : null;
}
return processUtils.stream( return processUtils.stream(
<String>[snapshotterPath, ...args], <String>[snapshotterPath, ...args],
mapFunction: outputFilter, mapFunction: (String line) => kIgnoredWarnings.contains(line) ? null : line,
); );
} }
} }
...@@ -94,6 +95,7 @@ class AOTSnapshotter { ...@@ -94,6 +95,7 @@ class AOTSnapshotter {
List<String> extraGenSnapshotOptions = const <String>[], List<String> extraGenSnapshotOptions = const <String>[],
@required bool bitcode, @required bool bitcode,
@required String splitDebugInfo, @required String splitDebugInfo,
@required bool dartObfuscation,
bool quiet = false, bool quiet = false,
}) async { }) async {
if (bitcode && platform != TargetPlatform.ios) { if (bitcode && platform != TargetPlatform.ios) {
...@@ -147,7 +149,8 @@ class AOTSnapshotter { ...@@ -147,7 +149,8 @@ class AOTSnapshotter {
// multiple debug files. // multiple debug files.
final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch); final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
final String debugFilename = 'app.$archName.symbols'; final String debugFilename = 'app.$archName.symbols';
if (splitDebugInfo?.isNotEmpty ?? false) { final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
if (shouldSplitDebugInfo) {
globals.fs.directory(splitDebugInfo) globals.fs.directory(splitDebugInfo)
.createSync(recursive: true); .createSync(recursive: true);
} }
...@@ -157,10 +160,12 @@ class AOTSnapshotter { ...@@ -157,10 +160,12 @@ class AOTSnapshotter {
// Faster async/await // Faster async/await
'--no-causal-async-stacks', '--no-causal-async-stacks',
'--lazy-async-stacks', '--lazy-async-stacks',
if (splitDebugInfo?.isNotEmpty ?? false) ...<String>[ if (shouldSplitDebugInfo) ...<String>[
'--dwarf-stack-traces', '--dwarf-stack-traces',
'--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}' '--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}'
] ],
if (dartObfuscation)
'--obfuscate',
]); ]);
genSnapshotArgs.add(mainPath); genSnapshotArgs.add(mainPath);
......
...@@ -22,6 +22,7 @@ class BuildInfo { ...@@ -22,6 +22,7 @@ class BuildInfo {
this.buildNumber, this.buildNumber,
this.buildName, this.buildName,
this.splitDebugInfoPath, this.splitDebugInfoPath,
this.dartObfuscation = false,
@required this.treeShakeIcons, @required this.treeShakeIcons,
}); });
...@@ -68,6 +69,9 @@ class BuildInfo { ...@@ -68,6 +69,9 @@ class BuildInfo {
/// executable. /// executable.
final String splitDebugInfoPath; final String splitDebugInfoPath;
/// Whether to apply dart source code obfuscation.
final bool dartObfuscation;
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault); static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault); static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
......
...@@ -222,6 +222,7 @@ class AndroidAot extends AotElfBase { ...@@ -222,6 +222,7 @@ class AndroidAot extends AotElfBase {
final List<String> extraGenSnapshotOptions = environment.defines[kExtraGenSnapshotOptions]?.split(',') final List<String> extraGenSnapshotOptions = environment.defines[kExtraGenSnapshotOptions]?.split(',')
?? const <String>[]; ?? const <String>[];
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final int snapshotExitCode = await snapshotter.build( final int snapshotExitCode = await snapshotter.build(
platform: targetPlatform, platform: targetPlatform,
buildMode: buildMode, buildMode: buildMode,
...@@ -231,6 +232,7 @@ class AndroidAot extends AotElfBase { ...@@ -231,6 +232,7 @@ class AndroidAot extends AotElfBase {
bitcode: false, bitcode: false,
extraGenSnapshotOptions: extraGenSnapshotOptions, extraGenSnapshotOptions: extraGenSnapshotOptions,
splitDebugInfo: splitDebugInfo, splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation,
); );
if (snapshotExitCode != 0) { if (snapshotExitCode != 0) {
throw Exception('AOT snapshotter exited with code $snapshotExitCode'); throw Exception('AOT snapshotter exited with code $snapshotExitCode');
......
...@@ -66,6 +66,9 @@ const String kDartDefines = 'DartDefines'; ...@@ -66,6 +66,9 @@ const String kDartDefines = 'DartDefines';
/// The other supported value is armv7, the 32-bit iOS architecture. /// The other supported value is armv7, the 32-bit iOS architecture.
const String kIosArchs = 'IosArchs'; const String kIosArchs = 'IosArchs';
/// Whether to enable Dart obfuscation and where to save the symbol map.
const String kDartObfuscation = 'DartObfuscation';
/// Copies the pre-built flutter bundle. /// Copies the pre-built flutter bundle.
// This is a one-off rule for implementing build bundle in terms of assemble. // This is a one-off rule for implementing build bundle in terms of assemble.
class CopyFlutterBundle extends Target { class CopyFlutterBundle extends Target {
...@@ -270,7 +273,8 @@ abstract class AotElfBase extends Target { ...@@ -270,7 +273,8 @@ abstract class AotElfBase extends Target {
?? const <String>[]; ?? const <String>[];
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
final String saveDebuggingInformation = environment.defines[kSplitDebugInfo]; final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final int snapshotExitCode = await snapshotter.build( final int snapshotExitCode = await snapshotter.build(
platform: targetPlatform, platform: targetPlatform,
buildMode: buildMode, buildMode: buildMode,
...@@ -279,7 +283,8 @@ abstract class AotElfBase extends Target { ...@@ -279,7 +283,8 @@ abstract class AotElfBase extends Target {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
extraGenSnapshotOptions: extraGenSnapshotOptions, extraGenSnapshotOptions: extraGenSnapshotOptions,
splitDebugInfo: saveDebuggingInformation splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation,
); );
if (snapshotExitCode != 0) { if (snapshotExitCode != 0) {
throw Exception('AOT snapshotter exited with code $snapshotExitCode'); throw Exception('AOT snapshotter exited with code $snapshotExitCode');
......
...@@ -40,6 +40,7 @@ abstract class AotAssemblyBase extends Target { ...@@ -40,6 +40,7 @@ abstract class AotAssemblyBase extends Target {
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final List<DarwinArch> iosArchs = environment.defines[kIosArchs] final List<DarwinArch> iosArchs = environment.defines[kIosArchs]
?.split(' ') ?.split(' ')
?.map(getIOSArchForName) ?.map(getIOSArchForName)
...@@ -63,6 +64,7 @@ abstract class AotAssemblyBase extends Target { ...@@ -63,6 +64,7 @@ abstract class AotAssemblyBase extends Target {
bitcode: bitcode, bitcode: bitcode,
quiet: true, quiet: true,
splitDebugInfo: splitDebugInfo, splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation,
)); ));
} }
final List<int> results = await Future.wait(pending); final List<int> results = await Future.wait(pending);
......
...@@ -201,6 +201,7 @@ class CompileMacOSFramework extends Target { ...@@ -201,6 +201,7 @@ class CompileMacOSFramework extends Target {
throw Exception('precompiled macOS framework only supported in release/profile builds.'); throw Exception('precompiled macOS framework only supported in release/profile builds.');
} }
final String splitDebugInfo = environment.defines[kSplitDebugInfo]; final String splitDebugInfo = environment.defines[kSplitDebugInfo];
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final int result = await AOTSnapshotter(reportTimings: false).build( final int result = await AOTSnapshotter(reportTimings: false).build(
bitcode: false, bitcode: false,
buildMode: buildMode, buildMode: buildMode,
...@@ -210,6 +211,7 @@ class CompileMacOSFramework extends Target { ...@@ -210,6 +211,7 @@ class CompileMacOSFramework extends Target {
darwinArch: DarwinArch.x86_64, darwinArch: DarwinArch.x86_64,
packagesPath: environment.projectDir.childFile('.packages').path, packagesPath: environment.projectDir.childFile('.packages').path,
splitDebugInfo: splitDebugInfo, splitDebugInfo: splitDebugInfo,
dartObfuscation: dartObfuscation,
); );
if (result != 0) { if (result != 0) {
throw Exception('gen shapshot failed.'); throw Exception('gen shapshot failed.');
......
...@@ -27,6 +27,7 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -27,6 +27,7 @@ class BuildApkCommand extends BuildSubCommand {
usesBuildNameOption(); usesBuildNameOption();
addShrinkingFlag(); addShrinkingFlag();
addSplitDebugInfoOption(); addSplitDebugInfoOption();
addDartObfuscationOption();
argParser argParser
..addFlag('split-per-abi', ..addFlag('split-per-abi',
negatable: false, negatable: false,
......
...@@ -24,6 +24,8 @@ class BuildAppBundleCommand extends BuildSubCommand { ...@@ -24,6 +24,8 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesBuildNumberOption(); usesBuildNumberOption();
usesBuildNameOption(); usesBuildNameOption();
addShrinkingFlag(); addShrinkingFlag();
addSplitDebugInfoOption();
addDartObfuscationOption();
argParser argParser
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
......
...@@ -26,6 +26,7 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -26,6 +26,7 @@ class BuildIOSCommand extends BuildSubCommand {
usesPubOption(); usesPubOption();
usesBuildNumberOption(); usesBuildNumberOption();
usesBuildNameOption(); usesBuildNameOption();
addDartObfuscationOption();
argParser argParser
..addFlag('simulator', ..addFlag('simulator',
help: 'Build for the iOS simulator instead of the device.', help: 'Build for the iOS simulator instead of the device.',
......
...@@ -49,6 +49,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -49,6 +49,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
usesFlavorOption(); usesFlavorOption();
usesPubOption(); usesPubOption();
usesDartDefines(); usesDartDefines();
addSplitDebugInfoOption();
addDartObfuscationOption();
argParser argParser
..addFlag('debug', ..addFlag('debug',
negatable: true, negatable: true,
......
...@@ -21,6 +21,7 @@ class BuildMacosCommand extends BuildSubCommand { ...@@ -21,6 +21,7 @@ class BuildMacosCommand extends BuildSubCommand {
addSplitDebugInfoOption(); addSplitDebugInfoOption();
usesTargetOption(); usesTargetOption();
addBuildModeFlags(); addBuildModeFlags();
addDartObfuscationOption();
} }
@override @override
......
...@@ -172,6 +172,11 @@ List<String> _xcodeBuildSettingsLines({ ...@@ -172,6 +172,11 @@ List<String> _xcodeBuildSettingsLines({
xcodeBuildSettings.add('SPLIT_DEBUG_INFO=${buildInfo.splitDebugInfoPath}'); xcodeBuildSettings.add('SPLIT_DEBUG_INFO=${buildInfo.splitDebugInfoPath}');
} }
// This is an optional path to obfuscate and output a mapping.
if (buildInfo.dartObfuscation) {
xcodeBuildSettings.add('DART_OBFUSCATION=true');
}
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH. // The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}'); xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}');
......
...@@ -108,6 +108,7 @@ class FlutterOptions { ...@@ -108,6 +108,7 @@ class FlutterOptions {
static const String kFileSystemRoot = 'filesystem-root'; static const String kFileSystemRoot = 'filesystem-root';
static const String kFileSystemScheme = 'filesystem-scheme'; static const String kFileSystemScheme = 'filesystem-scheme';
static const String kSplitDebugInfoOption = 'split-debug-info'; static const String kSplitDebugInfoOption = 'split-debug-info';
static const String kDartObfuscationOption = 'obfuscate';
} }
abstract class FlutterCommand extends Command<void> { abstract class FlutterCommand extends Command<void> {
...@@ -393,6 +394,18 @@ abstract class FlutterCommand extends Command<void> { ...@@ -393,6 +394,18 @@ abstract class FlutterCommand extends Command<void> {
); );
} }
void addDartObfuscationOption() {
argParser.addFlag(FlutterOptions.kDartObfuscationOption,
help: 'In a release build, this flag removes identifiers and replaces them '
'with randomized values for the purposes of source code obfuscation. This '
'flag must always be combined with "--split-debug-info" option, the '
'mapping between the values and the original identifiers is stored in the '
'symbol map created in the specified directory. For an app built with this '
'flag, the \'flutter symbolize\' command with the right program '
'symbol file is required to obtain a human readable stack trace.',
);
}
void addTreeShakeIconsFlag() { void addTreeShakeIconsFlag() {
argParser.addFlag('tree-shake-icons', argParser.addFlag('tree-shake-icons',
negatable: true, negatable: true,
...@@ -483,6 +496,10 @@ abstract class FlutterCommand extends Command<void> { ...@@ -483,6 +496,10 @@ abstract class FlutterCommand extends Command<void> {
); );
} }
/// Compute the [BuildInfo] for the current flutter command.
///
/// Throws a [ToolExit] if the current set of options is not compatible with
/// eachother.
BuildInfo getBuildInfo() { BuildInfo getBuildInfo() {
final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') && final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') &&
boolArg('track-widget-creation'); boolArg('track-widget-creation');
...@@ -507,6 +524,20 @@ abstract class FlutterCommand extends Command<void> { ...@@ -507,6 +524,20 @@ abstract class FlutterCommand extends Command<void> {
} }
} }
final bool dartObfuscation = argParser.options.containsKey(FlutterOptions.kDartObfuscationOption)
&& boolArg(FlutterOptions.kDartObfuscationOption);
final String splitDebugInfoPath = argParser.options.containsKey(FlutterOptions.kSplitDebugInfoOption)
? stringArg(FlutterOptions.kSplitDebugInfoOption)
: null;
if (dartObfuscation && (splitDebugInfoPath == null || splitDebugInfoPath.isEmpty)) {
throwToolExit(
'"--${FlutterOptions.kDartObfuscationOption}" can only be used in '
'combination with "--${FlutterOptions.kSplitDebugInfoOption}"',
);
}
return BuildInfo(getBuildMode(), return BuildInfo(getBuildMode(),
argParser.options.containsKey('flavor') argParser.options.containsKey('flavor')
? stringArg('flavor') ? stringArg('flavor')
...@@ -526,12 +557,11 @@ abstract class FlutterCommand extends Command<void> { ...@@ -526,12 +557,11 @@ abstract class FlutterCommand extends Command<void> {
buildName: argParser.options.containsKey('build-name') buildName: argParser.options.containsKey('build-name')
? stringArg('build-name') ? stringArg('build-name')
: null, : null,
splitDebugInfoPath: argParser.options.containsKey(FlutterOptions.kSplitDebugInfoOption)
? stringArg(FlutterOptions.kSplitDebugInfoOption)
: null,
treeShakeIcons: argParser.options.containsKey('tree-shake-icons') treeShakeIcons: argParser.options.containsKey('tree-shake-icons')
? boolArg('tree-shake-icons') ? boolArg('tree-shake-icons')
: kIconTreeShakerEnabledDefault, : kIconTreeShakerEnabledDefault,
splitDebugInfoPath: splitDebugInfoPath,
dartObfuscation: dartObfuscation,
); );
} }
......
...@@ -36,5 +36,8 @@ lib/generated_plugin_registrant.dart ...@@ -36,5 +36,8 @@ lib/generated_plugin_registrant.dart
# Symbolication related # Symbolication related
app.*.symbols app.*.symbols
# Obfuscation related
app.*.map.json
# Exceptions to above rules. # Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
...@@ -43,3 +43,6 @@ build/ ...@@ -43,3 +43,6 @@ build/
# Symbolication related # Symbolication related
app.*.symbols app.*.symbols
# Obfuscation related
app.*.map.json
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import '../../src/common.dart';
import '../../src/context.dart';
void main() {
testUsingContext('obfuscate requires split-debug-info', () {
final FakeBuildCommand command = FakeBuildCommand();
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
expect(() => commandRunner.run(<String>[
'build',
'--obfuscate',
]), throwsA(isA<ToolExit>()));
});
}
class FakeBuildCommand extends FlutterCommand {
FakeBuildCommand() {
addSplitDebugInfoOption();
addDartObfuscationOption();
}
@override
String get description => throw UnimplementedError();
@override
String get name => 'build';
@override
Future<FlutterCommandResult> runCommand() async {
getBuildInfo();
return FlutterCommandResult.success();
}
}
...@@ -267,6 +267,7 @@ void main() { ...@@ -267,6 +267,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
), isNot(equals(0))); ), isNot(equals(0)));
}, overrides: contextOverrides); }, overrides: contextOverrides);
...@@ -280,6 +281,7 @@ void main() { ...@@ -280,6 +281,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
), isNot(0)); ), isNot(0));
}, overrides: contextOverrides); }, overrides: contextOverrides);
...@@ -293,6 +295,7 @@ void main() { ...@@ -293,6 +295,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
), isNot(0)); ), isNot(0));
}, overrides: contextOverrides); }, overrides: contextOverrides);
...@@ -320,6 +323,7 @@ void main() { ...@@ -320,6 +323,7 @@ void main() {
darwinArch: DarwinArch.armv7, darwinArch: DarwinArch.armv7,
bitcode: true, bitcode: true,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -381,6 +385,7 @@ void main() { ...@@ -381,6 +385,7 @@ void main() {
darwinArch: DarwinArch.armv7, darwinArch: DarwinArch.armv7,
bitcode: true, bitcode: true,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -441,6 +446,7 @@ void main() { ...@@ -441,6 +446,7 @@ void main() {
darwinArch: DarwinArch.armv7, darwinArch: DarwinArch.armv7,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -469,7 +475,7 @@ void main() { ...@@ -469,7 +475,7 @@ void main() {
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true); expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
}, overrides: contextOverrides); }, overrides: contextOverrides);
testUsingContext('builds iOS armv7 profile AOT snapshot with dwarf stack traces', () async { testUsingContext('builds iOS armv7 profile AOT snapshot with dwarStackTraces', () async {
globals.fs.file('main.dill').writeAsStringSync('binary magic'); globals.fs.file('main.dill').writeAsStringSync('binary magic');
final String outputPath = globals.fs.path.join('build', 'foo'); final String outputPath = globals.fs.path.join('build', 'foo');
...@@ -494,6 +500,7 @@ void main() { ...@@ -494,6 +500,7 @@ void main() {
darwinArch: DarwinArch.armv7, darwinArch: DarwinArch.armv7,
bitcode: false, bitcode: false,
splitDebugInfo: 'foo', splitDebugInfo: 'foo',
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -524,6 +531,60 @@ void main() { ...@@ -524,6 +531,60 @@ void main() {
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true); expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
}, overrides: contextOverrides); }, overrides: contextOverrides);
testUsingContext('builds iOS armv7 profile AOT snapshot with obfuscate', () async {
globals.fs.file('main.dill').writeAsStringSync('binary magic');
final String outputPath = globals.fs.path.join('build', 'foo');
globals.fs.directory(outputPath).createSync(recursive: true);
final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
genSnapshot.outputs = <String, String>{
assembly: 'blah blah\n.section __DWARF\nblah blah\n',
};
final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
final int genSnapshotExitCode = await snapshotter.build(
platform: TargetPlatform.ios,
buildMode: BuildMode.profile,
mainPath: 'main.dill',
packagesPath: '.packages',
outputPath: outputPath,
darwinArch: DarwinArch.armv7,
bitcode: false,
splitDebugInfo: null,
dartObfuscation: true,
);
expect(genSnapshotExitCode, 0);
expect(genSnapshot.callCount, 1);
expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
expect(genSnapshot.snapshotType.mode, BuildMode.profile);
expect(genSnapshot.additionalArgs, <String>[
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=$assembly',
'--strip',
'--no-sim-use-hardfp',
'--no-use-integer-division',
'--no-causal-async-stacks',
'--lazy-async-stacks',
'--obfuscate',
'main.dill',
]);
verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode'))));
verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode'))));
verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1);
verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1);
final File assemblyFile = globals.fs.file(assembly);
expect(assemblyFile.existsSync(), true);
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
}, overrides: contextOverrides);
testUsingContext('builds iOS arm64 profile AOT snapshot', () async { testUsingContext('builds iOS arm64 profile AOT snapshot', () async {
globals.fs.file('main.dill').writeAsStringSync('binary magic'); globals.fs.file('main.dill').writeAsStringSync('binary magic');
...@@ -547,6 +608,7 @@ void main() { ...@@ -547,6 +608,7 @@ void main() {
darwinArch: DarwinArch.arm64, darwinArch: DarwinArch.arm64,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -587,6 +649,7 @@ void main() { ...@@ -587,6 +649,7 @@ void main() {
darwinArch: DarwinArch.armv7, darwinArch: DarwinArch.armv7,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -629,6 +692,7 @@ void main() { ...@@ -629,6 +692,7 @@ void main() {
darwinArch: DarwinArch.arm64, darwinArch: DarwinArch.arm64,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -660,6 +724,7 @@ void main() { ...@@ -660,6 +724,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -694,6 +759,7 @@ void main() { ...@@ -694,6 +759,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: 'foo', splitDebugInfo: 'foo',
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -715,6 +781,41 @@ void main() { ...@@ -715,6 +781,41 @@ void main() {
]); ]);
}, overrides: contextOverrides); }, overrides: contextOverrides);
testUsingContext('builds shared library for android-arm with obfuscate', () async {
globals.fs.file('main.dill').writeAsStringSync('binary magic');
final String outputPath = globals.fs.path.join('build', 'foo');
globals.fs.directory(outputPath).createSync(recursive: true);
final int genSnapshotExitCode = await snapshotter.build(
platform: TargetPlatform.android_arm,
buildMode: BuildMode.release,
mainPath: 'main.dill',
packagesPath: '.packages',
outputPath: outputPath,
bitcode: false,
splitDebugInfo: null,
dartObfuscation: true,
);
expect(genSnapshotExitCode, 0);
expect(genSnapshot.callCount, 1);
expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
expect(genSnapshot.snapshotType.mode, BuildMode.release);
expect(genSnapshot.additionalArgs, <String>[
'--deterministic',
'--snapshot_kind=app-aot-elf',
'--elf=build/foo/app.so',
'--strip',
'--no-sim-use-hardfp',
'--no-use-integer-division',
'--no-causal-async-stacks',
'--lazy-async-stacks',
'--obfuscate',
'main.dill',
]);
}, overrides: contextOverrides);
testUsingContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async { testUsingContext('builds shared library for android-arm without dwarf stack traces due to empty string', () async {
globals.fs.file('main.dill').writeAsStringSync('binary magic'); globals.fs.file('main.dill').writeAsStringSync('binary magic');
...@@ -729,6 +830,7 @@ void main() { ...@@ -729,6 +830,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: '', splitDebugInfo: '',
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -762,6 +864,7 @@ void main() { ...@@ -762,6 +864,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
...@@ -801,6 +904,7 @@ void main() { ...@@ -801,6 +904,7 @@ void main() {
outputPath: outputPath, outputPath: outputPath,
bitcode: false, bitcode: false,
splitDebugInfo: null, splitDebugInfo: null,
dartObfuscation: false,
); );
expect(genSnapshotExitCode, 0); expect(genSnapshotExitCode, 0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment