Skip to content

Commit d649d2b

Browse files
camsim99gemini-code-assist[bot]gmackall
authored
[Android] Update the CLI to reject passing engine configuration flags with a prebuilt binary in release mode (#190870)
> [!CAUTION] > This is a breaking change. Accompanying breaking change docs: flutter/website#13796. For security purposes*, modifies the Flutter CLI to throw a fatal error **on Android only** when `flutter run --release --use-application-binary` is invoked **in addition to** passing any other Android engine configuration flags. So, for example, the following release-mode invocations for a Flutter Android app will be valid with this PR: - `flutter run --release` - `flutter run --release --verbose --enable-hcpp --any-other-engine-config-flag` - `flutter run --release --use-aplication-binary` The following will not be valid: - `flutter run --release --use-application-binary --verbose --enable-hcpp --any-other-android-engine-config-flag` *See #190461 for details. This is part of that work. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Gray Mackall <[email protected]>
1 parent 3829a94 commit d649d2b

9 files changed

Lines changed: 790 additions & 199 deletions

File tree

‎packages/flutter_tools/lib/src/android/android_device.dart‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import '../protocol_discovery.dart';
2626
import '../vmservice.dart';
2727
import 'android_builder.dart';
2828
import 'android_console.dart';
29+
import 'android_engine_cli_flags.dart';
2930
import 'android_sdk.dart';
3031
import 'application_package.dart';
3132
import 'gradle_utils.dart' as gradle_utils;
@@ -566,8 +567,9 @@ class AndroidDevice extends Device {
566567
final releaseManifestEngineShellArgs = <String>[
567568
if (debuggingOptions.buildInfo.mode == BuildMode.release) ...<String>[
568569
...debuggingOptions.getAndroidLaunchArguments(),
569-
if (platformArgs['trace-startup'] as bool? ?? false) '--trace-startup',
570-
if (route != null) '--route=$route',
570+
if (platformArgs[AndroidEngineCliFlags.traceStartup] as bool? ?? false)
571+
'--${AndroidEngineCliFlags.traceStartup}',
572+
if (route != null) '--${AndroidEngineCliFlags.route}=$route',
571573
],
572574
];
573575

@@ -601,7 +603,7 @@ class AndroidDevice extends Device {
601603
return LaunchResult.failed();
602604
}
603605

604-
final bool traceStartup = platformArgs['trace-startup'] as bool? ?? false;
606+
final bool traceStartup = platformArgs[AndroidEngineCliFlags.traceStartup] as bool? ?? false;
605607
ProtocolDiscovery? vmServiceDiscovery;
606608

607609
if (debuggingOptions.debuggingEnabled) {
@@ -628,9 +630,11 @@ class AndroidDevice extends Device {
628630
'-a', 'android.intent.action.MAIN',
629631
'-c', 'android.intent.category.LAUNCHER',
630632
'-f', '0x20000000', // FLAG_ACTIVITY_SINGLE_TOP
631-
...debuggingOptions.getAndroidLaunchArgumentsAsIntentExtras(),
632-
if (traceStartup) ...<String>['--ez', 'trace-startup', 'true'],
633-
if (route != null) ...<String>['--es', 'route', route],
633+
if (debuggingOptions.buildInfo.mode != BuildMode.release) ...<String>[
634+
...debuggingOptions.getAndroidLaunchArgumentsAsIntentExtras(),
635+
if (traceStartup) ...<String>['--ez', AndroidEngineCliFlags.traceStartup, 'true'],
636+
if (route != null) ...<String>['--es', AndroidEngineCliFlags.route, route],
637+
],
634638
if (debuggingOptions.debuggingEnabled && userIdentifier != null) ...<String>[
635639
'--user',
636640
userIdentifier,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// TODO(camsim99): Migrate to a shared file with Dart/Kotlin/Java once constants are
6+
// defined cross-language; see https://github.com/flutter/flutter/pull/190236.
7+
/// The command line flags that are passed directly to the Android engine.
8+
abstract final class AndroidEngineCliFlags {
9+
// Flags that represent both the Flutter CLI option key and the Android engine shell argument.
10+
static const String traceStartup = 'trace-startup';
11+
static const String profileStartup = 'profile-startup';
12+
static const String route = 'route';
13+
static const String traceSkia = 'trace-skia';
14+
static const String traceAllowlist = 'trace-allowlist';
15+
static const String traceSkiaAllowlist = 'trace-skia-allowlist';
16+
static const String traceSystrace = 'trace-systrace';
17+
static const String traceToFile = 'trace-to-file';
18+
static const String enableDartProfiling = 'enable-dart-profiling';
19+
static const String enableSoftwareRendering = 'enable-software-rendering';
20+
static const String skiaDeterministicRendering = 'skia-deterministic-rendering';
21+
static const String endlessTraceBuffer = 'endless-trace-buffer';
22+
static const String profileMicrotasks = 'profile-microtasks';
23+
static const String purgePersistentCache = 'purge-persistent-cache';
24+
static const String enableImpeller = 'enable-impeller';
25+
static const String enableVulkanValidation = 'enable-vulkan-validation';
26+
static const String enableFlutterGpu = 'enable-flutter-gpu';
27+
static const String testFlag = 'test-flag';
28+
static const String startPaused = 'start-paused';
29+
static const String disableServiceAuthCodes = 'disable-service-auth-codes';
30+
static const String disableServiceOriginCheck = 'disable-service-origin-check';
31+
static const String dartFlags = 'dart-flags';
32+
static const String useTestFonts = 'use-test-fonts';
33+
34+
// Flags that represent only the Flutter CLI option key. These are mapped to a different Android engine shell argument.
35+
static const String enableHcpp = 'enable-hcpp';
36+
static const String verboseSystemLogs = 'verbose-system-logs';
37+
38+
// Flags that represent only the Android engine shell argument. These are mapped from a different Flutter CLI option key.
39+
static const String enableHcppAndSurfaceControl = 'enable-hcpp-and-surface-control';
40+
static const String verboseLogging = 'verbose-logging';
41+
42+
static const List<String> allFlags = <String>[
43+
route,
44+
traceStartup,
45+
profileStartup,
46+
traceSkia,
47+
traceAllowlist,
48+
traceSkiaAllowlist,
49+
traceSystrace,
50+
traceToFile,
51+
enableDartProfiling,
52+
enableSoftwareRendering,
53+
skiaDeterministicRendering,
54+
endlessTraceBuffer,
55+
profileMicrotasks,
56+
purgePersistentCache,
57+
enableImpeller,
58+
enableVulkanValidation,
59+
enableFlutterGpu,
60+
enableHcpp,
61+
testFlag,
62+
startPaused,
63+
disableServiceAuthCodes,
64+
disableServiceOriginCheck,
65+
dartFlags,
66+
useTestFonts,
67+
verboseSystemLogs,
68+
];
69+
}

‎packages/flutter_tools/lib/src/commands/drive.dart‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,12 @@ class DriveCommand extends RunCommandBase {
259259

260260
@override
261261
Future<void> validateCommand() async {
262+
// For Android prebuilt applications run in release mode, validate that engine configuration flags
263+
// are not passed.
264+
validatePrebuiltAndroidApplicationFlags();
265+
262266
final ToolContext(:FileSystem fs, :Logger logger) = _toolContext;
267+
263268
if (userIdentifier != null) {
264269
final Device? device = await findTargetDevice();
265270
if (device is! AndroidDevice) {

‎packages/flutter_tools/lib/src/commands/run.dart‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:unified_analytics/unified_analytics.dart' as analytics;
99
import 'package:vm_service/vm_service.dart';
1010

1111
import '../android/android_device.dart';
12+
import '../android/android_engine_cli_flags.dart';
1213
import '../android/android_workflow.dart' as android_workflow;
1314
import '../base/common.dart';
1415
import '../base/file_system.dart';
@@ -288,6 +289,33 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
288289
);
289290
return webDevServerConfig;
290291
}
292+
293+
@protected
294+
void validatePrebuiltAndroidApplicationFlags() {
295+
// First, verify the build mode.
296+
if (getBuildMode() != BuildMode.release) {
297+
return;
298+
}
299+
300+
// Then, verify an Android prebuilt application is being run.
301+
final String? applicationBinary =
302+
argParser.options.containsKey(FlutterOptions.kUseApplicationBinary)
303+
? stringArg(FlutterOptions.kUseApplicationBinary)
304+
: null;
305+
if (applicationBinary != null && applicationBinary.toLowerCase().endsWith('.apk')) {
306+
final Iterable<String> intentFlags = AndroidEngineCliFlags.allFlags.where(
307+
(String flag) => argParser.options.containsKey(flag) && argResults?.wasParsed(flag) == true,
308+
);
309+
310+
if (intentFlags.isNotEmpty) {
311+
throwToolExit(
312+
'Running a prebuilt APK with --${FlutterOptions.kUseApplicationBinary} in release mode with flags used to configure the Flutter Android engine '
313+
'(${intentFlags.map((String flag) => '--$flag').join(', ')}) is no longer supported. Define the required flags via the Android manifest instead. See '
314+
'https://docs.flutter.dev/release/breaking-changes/restrict-command-line-flags-prebuilt-android-release-binaries for more details.',
315+
);
316+
}
317+
}
318+
}
291319
}
292320

293321
class RunCommand extends RunCommandBase {
@@ -542,9 +570,11 @@ class RunCommand extends RunCommandBase {
542570

543571
@override
544572
Future<void> validateCommand() async {
545-
// When running with a prebuilt application, no command validation is
546-
// necessary.
547-
if (!runningWithPrebuiltApplication) {
573+
if (runningWithPrebuiltApplication) {
574+
// For Android prebuilt applications run in release mode, validate that engine configuration flags
575+
// are not passed.
576+
validatePrebuiltAndroidApplicationFlags();
577+
} else {
548578
await super.validateCommand();
549579
}
550580

‎packages/flutter_tools/lib/src/device.dart‎

Lines changed: 65 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66

77
import 'package:meta/meta.dart';
88

9+
import 'android/android_engine_cli_flags.dart';
910
import 'application_package.dart';
1011
import 'base/context.dart';
1112
import 'base/dds.dart';
@@ -1425,86 +1426,78 @@ class DebuggingOptions {
14251426
),
14261427
);
14271428

1429+
Map<String, Object?> _getAndroidEngineConfig() {
1430+
return <String, Object?>{
1431+
if (enableDartProfiling) AndroidEngineCliFlags.enableDartProfiling: true,
1432+
if (profileStartup) AndroidEngineCliFlags.profileStartup: true,
1433+
if (enableSoftwareRendering) AndroidEngineCliFlags.enableSoftwareRendering: true,
1434+
if (skiaDeterministicRendering) AndroidEngineCliFlags.skiaDeterministicRendering: true,
1435+
if (traceSkia) AndroidEngineCliFlags.traceSkia: true,
1436+
if (traceAllowlist != null) AndroidEngineCliFlags.traceAllowlist: traceAllowlist,
1437+
if (traceSkiaAllowlist != null) AndroidEngineCliFlags.traceSkiaAllowlist: traceSkiaAllowlist,
1438+
if (traceSystrace) AndroidEngineCliFlags.traceSystrace: true,
1439+
if (traceToFile != null) AndroidEngineCliFlags.traceToFile: traceToFile,
1440+
if (endlessTraceBuffer) AndroidEngineCliFlags.endlessTraceBuffer: true,
1441+
if (profileMicrotasks) AndroidEngineCliFlags.profileMicrotasks: true,
1442+
if (purgePersistentCache) AndroidEngineCliFlags.purgePersistentCache: true,
1443+
if (enableImpeller == ImpellerStatus.enabled) AndroidEngineCliFlags.enableImpeller: true,
1444+
if (enableImpeller == ImpellerStatus.disabled) AndroidEngineCliFlags.enableImpeller: false,
1445+
if (enableFlutterGpu) AndroidEngineCliFlags.enableFlutterGpu: true,
1446+
if (enableVulkanValidation) AndroidEngineCliFlags.enableVulkanValidation: true,
1447+
if (enableHcpp != null) AndroidEngineCliFlags.enableHcppAndSurfaceControl: enableHcpp,
1448+
if (testFlag) AndroidEngineCliFlags.testFlag: true,
1449+
if (debuggingEnabled) ...<String, Object?>{
1450+
// TODO(camsim99): Determine if we should even forward these to the Android embedding since Android
1451+
// appears unsupported. https://github.com/flutter/flutter/issues/191849
1452+
if (buildInfo.isDebug) 'enable-checked-mode': true,
1453+
if (buildInfo.isDebug) 'verify-entry-points': true,
1454+
if (startPaused) AndroidEngineCliFlags.startPaused: true,
1455+
if (disableServiceAuthCodes) AndroidEngineCliFlags.disableServiceAuthCodes: true,
1456+
if (disableServiceOriginCheck) AndroidEngineCliFlags.disableServiceOriginCheck: true,
1457+
if (dartFlags.isNotEmpty) AndroidEngineCliFlags.dartFlags: dartFlags,
1458+
if (useTestFonts) AndroidEngineCliFlags.useTestFonts: true,
1459+
if (verboseSystemLogs) AndroidEngineCliFlags.verboseLogging: true,
1460+
},
1461+
};
1462+
}
1463+
14281464
/// Retrieves Android engine shell arguments from the debugging options based on the
14291465
/// command line flags that are passed to the engine via the manifest.
14301466
Set<String> getAndroidLaunchArguments() {
1431-
return <String>{
1432-
if (enableDartProfiling) ...<String>['--enable-dart-profiling'],
1433-
if (profileStartup) ...<String>['--profile-startup'],
1434-
if (enableSoftwareRendering) ...<String>['--enable-software-rendering'],
1435-
if (skiaDeterministicRendering) ...<String>['--skia-deterministic-rendering'],
1436-
if (traceSkia) ...<String>['--trace-skia'],
1437-
if (traceAllowlist != null) ...<String>['--trace-allowlist=${traceAllowlist!}'],
1438-
if (traceSkiaAllowlist != null) ...<String>['--trace-skia-allowlist=${traceSkiaAllowlist!}'],
1439-
if (traceSystrace) ...<String>['--trace-systrace'],
1440-
if (traceToFile != null) ...<String>['--trace-to-file=${traceToFile!}'],
1441-
if (endlessTraceBuffer) ...<String>['--endless-trace-buffer'],
1442-
if (profileMicrotasks) ...<String>['--profile-microtasks'],
1443-
if (purgePersistentCache) ...<String>['--purge-persistent-cache'],
1444-
if (enableImpeller == ImpellerStatus.enabled) ...<String>['--enable-impeller=true'],
1445-
if (enableImpeller == ImpellerStatus.disabled) ...<String>['--enable-impeller=false'],
1446-
if (enableFlutterGpu) ...<String>['--enable-flutter-gpu'],
1447-
if (enableVulkanValidation) ...<String>['--enable-vulkan-validation'],
1448-
if (enableHcpp != null) ...<String>['--enable-hcpp-and-surface-control=$enableHcpp'],
1449-
if (testFlag) ...<String>['--test-flag'],
1450-
if (debuggingEnabled) ...<String>[
1451-
if (buildInfo.isDebug) ...<String>[
1452-
...<String>['--enable-checked-mode'],
1453-
...<String>['--verify-entry-points'],
1454-
],
1455-
if (startPaused) ...<String>['--start-paused'],
1456-
if (disableServiceAuthCodes) ...<String>['--disable-service-auth-codes'],
1457-
if (disableServiceOriginCheck) ...<String>['--disable-service-origin-check'],
1458-
if (dartFlags.isNotEmpty) ...<String>['--dart-flags=$dartFlags'],
1459-
if (useTestFonts) ...<String>['--use-test-fonts'],
1460-
if (verboseSystemLogs) ...<String>['--verbose-logging'],
1461-
],
1462-
};
1467+
final Map<String, Object?> configs = _getAndroidEngineConfig();
1468+
final args = <String>{};
1469+
for (final MapEntry<String, Object?> entry in configs.entries) {
1470+
final Object? value = entry.value;
1471+
if (entry.key == AndroidEngineCliFlags.enableImpeller ||
1472+
entry.key == AndroidEngineCliFlags.enableHcppAndSurfaceControl) {
1473+
args.add('--${entry.key}=$value');
1474+
} else if (value is bool) {
1475+
args.add(value ? '--${entry.key}' : '--${entry.key}=false');
1476+
} else if (value is String) {
1477+
args.add('--${entry.key}=$value');
1478+
} else {
1479+
assert(false, 'Unsupported engine config value type ${value.runtimeType} for ${entry.key}');
1480+
}
1481+
}
1482+
return args;
14631483
}
14641484

14651485
/// Retrieves Android engine shell arguments from the debugging options based on the
14661486
/// command line flags that are passed to the engine via the manifest as Intent extras.
14671487
List<String> getAndroidLaunchArgumentsAsIntentExtras() {
1468-
return <String>[
1469-
if (enableDartProfiling) ...<String>['--ez', 'enable-dart-profiling', 'true'],
1470-
if (profileStartup) ...<String>['--ez', 'profile-startup', 'true'],
1471-
if (enableSoftwareRendering) ...<String>['--ez', 'enable-software-rendering', 'true'],
1472-
if (skiaDeterministicRendering) ...<String>['--ez', 'skia-deterministic-rendering', 'true'],
1473-
if (traceSkia) ...<String>['--ez', 'trace-skia', 'true'],
1474-
if (traceAllowlist != null) ...<String>['--es', 'trace-allowlist', traceAllowlist!],
1475-
if (traceSkiaAllowlist != null) ...<String>[
1476-
'--es',
1477-
'trace-skia-allowlist',
1478-
traceSkiaAllowlist!,
1479-
],
1480-
if (traceSystrace) ...<String>['--ez', 'trace-systrace', 'true'],
1481-
if (traceToFile != null) ...<String>['--es', 'trace-to-file', traceToFile!],
1482-
if (endlessTraceBuffer) ...<String>['--ez', 'endless-trace-buffer', 'true'],
1483-
if (profileMicrotasks) ...<String>['--ez', 'profile-microtasks', 'true'],
1484-
if (purgePersistentCache) ...<String>['--ez', 'purge-persistent-cache', 'true'],
1485-
if (enableImpeller == ImpellerStatus.enabled) ...<String>['--ez', 'enable-impeller', 'true'],
1486-
if (enableImpeller == ImpellerStatus.disabled) ...<String>[
1487-
'--ez',
1488-
'enable-impeller',
1489-
'false',
1490-
],
1491-
if (enableFlutterGpu) ...<String>['--ez', 'enable-flutter-gpu', 'true'],
1492-
if (enableVulkanValidation) ...<String>['--ez', 'enable-vulkan-validation', 'true'],
1493-
if (enableHcpp != null) ...<String>['--ez', 'enable-hcpp-and-surface-control', '$enableHcpp'],
1494-
if (debuggingEnabled) ...<String>[
1495-
if (buildInfo.isDebug) ...<String>[
1496-
...<String>['--ez', 'enable-checked-mode', 'true'],
1497-
...<String>['--ez', 'verify-entry-points', 'true'],
1498-
],
1499-
if (startPaused) ...<String>['--ez', 'start-paused', 'true'],
1500-
if (disableServiceAuthCodes) ...<String>['--ez', 'disable-service-auth-codes', 'true'],
1501-
if (disableServiceOriginCheck) ...<String>['--ez', 'disable-service-origin-check', 'true'],
1502-
if (dartFlags.isNotEmpty) ...<String>['--es', 'dart-flags', dartFlags],
1503-
if (useTestFonts) ...<String>['--ez', 'use-test-fonts', 'true'],
1504-
if (verboseSystemLogs) ...<String>['--ez', 'verbose-logging', 'true'],
1505-
if (testFlag) ...<String>['--ez', 'test-flag', 'true'],
1506-
],
1507-
];
1488+
final Map<String, Object?> configs = _getAndroidEngineConfig();
1489+
final args = <String>[];
1490+
for (final MapEntry<String, Object?> entry in configs.entries) {
1491+
final Object? value = entry.value;
1492+
if (value is bool) {
1493+
args.addAll(<String>['--ez', entry.key, value.toString()]);
1494+
} else if (value is String) {
1495+
args.addAll(<String>['--es', entry.key, value]);
1496+
} else {
1497+
assert(false, 'Unsupported engine config value type ${value.runtimeType} for ${entry.key}');
1498+
}
1499+
}
1500+
return args;
15081501
}
15091502
}
15101503

0 commit comments

Comments
 (0)