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 'xcode_debug.dart';
|
||||||
import 'xcodeproj.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 {
|
class IOSDevices extends PollingDeviceDiscovery {
|
||||||
IOSDevices({
|
IOSDevices({
|
||||||
required Platform platform,
|
required Platform platform,
|
||||||
@ -594,6 +612,7 @@ class IOSDevice extends Device {
|
|||||||
debuggingOptions: debuggingOptions,
|
debuggingOptions: debuggingOptions,
|
||||||
packageId: packageId,
|
packageId: packageId,
|
||||||
vmServiceDiscovery: vmServiceDiscovery,
|
vmServiceDiscovery: vmServiceDiscovery,
|
||||||
|
package: package,
|
||||||
);
|
);
|
||||||
} else if (isWirelesslyConnected) {
|
} else if (isWirelesslyConnected) {
|
||||||
// Wait for the Dart VM url to be discovered via logs (from `ios-deploy`)
|
// 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 String packageId,
|
||||||
required DebuggingOptions debuggingOptions,
|
required DebuggingOptions debuggingOptions,
|
||||||
ProtocolDiscovery? vmServiceDiscovery,
|
ProtocolDiscovery? vmServiceDiscovery,
|
||||||
|
IOSApp? package,
|
||||||
}) async {
|
}) async {
|
||||||
Timer? maxWaitForCI;
|
Timer? maxWaitForCI;
|
||||||
final Completer<Uri?> cancelCompleter = Completer<Uri?>();
|
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(
|
final Future<Uri?> vmUrlFromMDns = MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch(
|
||||||
packageId,
|
packageId,
|
||||||
this,
|
this,
|
||||||
@ -771,9 +796,40 @@ class IOSDevice extends Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
maxWaitForCI?.cancel();
|
maxWaitForCI?.cancel();
|
||||||
|
await errorListener?.cancel();
|
||||||
return localUri;
|
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({
|
ProtocolDiscovery _setupDebuggerAndVmServiceDiscovery({
|
||||||
required IOSApp package,
|
required IOSApp package,
|
||||||
required Directory bundle,
|
required Directory bundle,
|
||||||
|
@ -1101,6 +1101,75 @@ void main() {
|
|||||||
MDnsVmServiceDiscovery: () => FakeMDnsVmServiceDiscovery(returnsNull: true),
|
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