Intercept error when iOS 18.4 crashes with JIT mode and give guided error (#164072)
Adds listener to device logs during launch (before Dart VM is found) and check if iOS 18.4+ JIT crash log and give guided error message: ``` ════════════════════════════════════════════════════════════════════════════════ A change to iOS has caused a temporary break in Flutter's debug mode on physical devices. See https://github.com/flutter/flutter/issues/163984 for details. In the meantime, we recommend these temporary workarounds: * When developing with a physical device, use one running iOS 18.3 or lower. * Use a simulator for development rather than a physical device. * If you must use a device updated to iOS 18.4+, use Flutter's release or profile mode via --release or --profile flags. ════════════════════════════════════════════════════════════════════════════════ ``` Fixes https://github.com/flutter/flutter/issues/164011. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [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]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [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
This commit is contained in:
parent
cca82ed93b
commit
aa113bd69c
@ -39,6 +39,24 @@ import 'xcode_build_settings.dart';
|
||||
import 'xcode_debug.dart';
|
||||
import 'xcodeproj.dart';
|
||||
|
||||
const String kJITCrashFailureMessage =
|
||||
'Crash occurred when compiling unknown function in unoptimized JIT mode in unknown pass';
|
||||
|
||||
@visibleForTesting
|
||||
String jITCrashFailureInstructions(String deviceVersion) => '''
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
A change to iOS has caused a temporary break in Flutter's debug mode on
|
||||
physical devices.
|
||||
See https://github.com/flutter/flutter/issues/163984 for details.
|
||||
|
||||
In the meantime, we recommend these temporary workarounds:
|
||||
|
||||
* When developing with a physical device, use one running iOS 18.3 or lower.
|
||||
* Use a simulator for development rather than a physical device.
|
||||
* If you must use a device updated to $deviceVersion, use Flutter's release or
|
||||
profile mode via --release or --profile flags.
|
||||
════════════════════════════════════════════════════════════════════════════════''';
|
||||
|
||||
class IOSDevices extends PollingDeviceDiscovery {
|
||||
IOSDevices({
|
||||
required Platform platform,
|
||||
@ -594,6 +612,7 @@ class IOSDevice extends Device {
|
||||
debuggingOptions: debuggingOptions,
|
||||
packageId: packageId,
|
||||
vmServiceDiscovery: vmServiceDiscovery,
|
||||
package: package,
|
||||
);
|
||||
} else if (isWirelesslyConnected) {
|
||||
// Wait for the Dart VM url to be discovered via logs (from `ios-deploy`)
|
||||
@ -702,6 +721,7 @@ class IOSDevice extends Device {
|
||||
required String packageId,
|
||||
required DebuggingOptions debuggingOptions,
|
||||
ProtocolDiscovery? vmServiceDiscovery,
|
||||
IOSApp? package,
|
||||
}) async {
|
||||
Timer? maxWaitForCI;
|
||||
final Completer<Uri?> cancelCompleter = Completer<Uri?>();
|
||||
@ -743,6 +763,11 @@ class IOSDevice extends Device {
|
||||
});
|
||||
}
|
||||
|
||||
final StreamSubscription<String>? errorListener = await _interceptErrorsFromLogs(
|
||||
package,
|
||||
debuggingOptions: debuggingOptions,
|
||||
);
|
||||
|
||||
final Future<Uri?> vmUrlFromMDns = MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch(
|
||||
packageId,
|
||||
this,
|
||||
@ -771,9 +796,40 @@ class IOSDevice extends Device {
|
||||
}
|
||||
}
|
||||
maxWaitForCI?.cancel();
|
||||
await errorListener?.cancel();
|
||||
return localUri;
|
||||
}
|
||||
|
||||
/// Listen to device logs for crash on iOS 18.4+ due to JIT restriction. If
|
||||
/// found, give guided error and throw tool exit. Returns null and does not
|
||||
/// listen if device is less than iOS 18.4.
|
||||
Future<StreamSubscription<String>?> _interceptErrorsFromLogs(
|
||||
IOSApp? package, {
|
||||
required DebuggingOptions debuggingOptions,
|
||||
}) async {
|
||||
// Currently only checking for kJITCrashFailureMessage, which only should
|
||||
// be checked on iOS 18.4+.
|
||||
if (sdkVersion == null || sdkVersion! < Version(18, 4, null)) {
|
||||
return null;
|
||||
}
|
||||
final DeviceLogReader deviceLogReader = getLogReader(
|
||||
app: package,
|
||||
usingCISystem: debuggingOptions.usingCISystem,
|
||||
);
|
||||
|
||||
final Stream<String> logStream = deviceLogReader.logLines;
|
||||
|
||||
final String deviceSdkVersion = await sdkNameAndVersion;
|
||||
|
||||
final StreamSubscription<String> errorListener = logStream.listen((String line) {
|
||||
if (line.contains(kJITCrashFailureMessage)) {
|
||||
throwToolExit(jITCrashFailureInstructions(deviceSdkVersion));
|
||||
}
|
||||
});
|
||||
|
||||
return errorListener;
|
||||
}
|
||||
|
||||
ProtocolDiscovery _setupDebuggerAndVmServiceDiscovery({
|
||||
required IOSApp package,
|
||||
required Directory bundle,
|
||||
|
@ -1101,6 +1101,75 @@ void main() {
|
||||
MDnsVmServiceDiscovery: () => FakeMDnsVmServiceDiscovery(returnsNull: true),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'IOSDevice.startApp prints guided message when iOS 18.4 crashes due to JIT',
|
||||
() async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final FakeProcessManager processManager = FakeProcessManager.empty();
|
||||
|
||||
final Directory temporaryXcodeProjectDirectory = fileSystem.systemTempDirectory
|
||||
.childDirectory('flutter_empty_xcode.rand0');
|
||||
final Directory bundleLocation = fileSystem.currentDirectory;
|
||||
final IOSDevice device = setUpIOSDevice(
|
||||
sdkVersion: '18.4',
|
||||
processManager: processManager,
|
||||
fileSystem: fileSystem,
|
||||
isCoreDevice: true,
|
||||
coreDeviceControl: FakeIOSCoreDeviceControl(),
|
||||
xcodeDebug: FakeXcodeDebug(
|
||||
expectedProject: XcodeDebugProject(
|
||||
scheme: 'Runner',
|
||||
xcodeWorkspace: temporaryXcodeProjectDirectory.childDirectory('Runner.xcworkspace'),
|
||||
xcodeProject: temporaryXcodeProjectDirectory.childDirectory('Runner.xcodeproj'),
|
||||
hostAppProjectName: 'Runner',
|
||||
),
|
||||
expectedDeviceId: '123',
|
||||
expectedLaunchArguments: <String>['--enable-dart-profiling'],
|
||||
expectedBundlePath: bundleLocation.path,
|
||||
),
|
||||
);
|
||||
final IOSApp iosApp = PrebuiltIOSApp(
|
||||
projectBundleId: 'app',
|
||||
bundleName: 'Runner',
|
||||
uncompressedBundle: bundleLocation,
|
||||
applicationPackage: bundleLocation,
|
||||
);
|
||||
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
|
||||
|
||||
device.portForwarder = const NoOpDevicePortForwarder();
|
||||
device.setLogReader(iosApp, deviceLogReader);
|
||||
|
||||
// Start writing messages to the log reader.
|
||||
Timer.run(() {
|
||||
deviceLogReader.addLine(kJITCrashFailureMessage);
|
||||
});
|
||||
|
||||
final Completer<void> completer = Completer<void>();
|
||||
// device.startApp() asynchronously calls throwToolExit, so we
|
||||
// catch it in a zone.
|
||||
unawaited(
|
||||
runZoned<Future<void>?>(
|
||||
() {
|
||||
unawaited(
|
||||
device.startApp(
|
||||
iosApp,
|
||||
prebuiltApplication: true,
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
platformArgs: <String, dynamic>{},
|
||||
),
|
||||
);
|
||||
return null;
|
||||
},
|
||||
onError: (Object error, StackTrace stack) {
|
||||
expect(error.toString(), contains(jITCrashFailureInstructions('iOS 18.4')));
|
||||
completer.complete();
|
||||
},
|
||||
),
|
||||
);
|
||||
await completer.future;
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user