Skip to content

Commit aada3ae

Browse files
authored
[stable] [flutter_tools] Fix impellerc Windows Unicode crash and improve diagnostics (#191276)
This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: #190233 ### Impact Description: On Windows, the Impeller shader compiler (`impellerc`) crashes when project directories or include paths contain non-ASCII/Unicode characters (such as localized usernames or paths). Additionally, `flutter_tools` obscured `impellerc`'s stdout and stderr output during `ShaderCompilerException`. This was identified as a top-10 crash signature in Flutter stable release 3.44.8. ### Changelog Description: [flutter/190233] Fix impellerc crash on Windows when paths contain Unicode characters, and improve shader compiler error diagnostics. ### Workaround: Move the Flutter project to a directory path containing exclusively ASCII characters. ### Risk: ### Test Coverage: ### Validation Steps: 1. On Windows, create a Flutter project in a directory path containing Unicode characters (e.g. `C:\Users\тест\flutter_project`). 2. Run `flutter build windows` or `flutter run` with Impeller enabled. 3. Confirm shader compilation succeeds without crashing.
1 parent 0f0c037 commit aada3ae

3 files changed

Lines changed: 285 additions & 7 deletions

File tree

‎engine/src/flutter/impeller/compiler/switches.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ Switches::Switches(const fml::CommandLine& command_line)
239239
}
240240

241241
auto dir = std::make_shared<fml::UniqueFD>(fml::OpenDirectoryReadOnly(
242-
*working_directory, include_dir_absolute.string().c_str()));
242+
*working_directory, Utf8FromPath(include_dir_absolute).c_str()));
243243
if (!dir || !dir->is_valid()) {
244244
continue;
245245
}

‎packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart‎

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ class ShaderCompiler {
265265
final List<String> shaderTargets = _shaderTargetsFromTargetPlatform(targetPlatform);
266266
final List<String> cmd = makeImpellercCommand(shaderTargets);
267267
_logger.printTrace('impellerc command: $cmd');
268-
final ProcessResult result = await _runCommand(cmd);
268+
ProcessResult result = await _runCommand(cmd);
269269
if (result.exitCode != 0) {
270270
// Maybe retry impellerc command without --sksl.
271271
if (!(shaderTargets.length > 1 && shaderTargets.contains('--sksl'))) {
@@ -286,6 +286,7 @@ class ShaderCompiler {
286286
if (retryResult.exitCode != 0) {
287287
// Retry failed.
288288
_logger.printError('impellerc failure: ${retryResult.stderr}');
289+
result = retryResult;
289290
failure = true;
290291
} else {
291292
// Retry succeeded. Don't fail, but log a warning message and the sksl
@@ -302,9 +303,15 @@ class ShaderCompiler {
302303

303304
if (failure) {
304305
if (fatal) {
306+
final String? hint = _getHelpfulHint(result, input, outputPath, impellerc.path);
307+
final message =
308+
'Shader compilation of "${input.path}" to "$outputPath" '
309+
'failed with exit code ${result.exitCode}.'
310+
'${hint != null ? '\n$hint' : ''}';
305311
throw ShaderCompilerException._(
306-
'Shader compilation of "${input.path}" to "$outputPath" '
307-
'failed with exit code ${result.exitCode}.',
312+
message,
313+
stdout: result.stdout as String?,
314+
stderr: result.stderr as String?,
308315
);
309316
}
310317
return false;
@@ -320,9 +327,46 @@ class ShaderCompiler {
320327
return true;
321328
}
322329

330+
String? _getHelpfulHint(
331+
ProcessResult result,
332+
File input,
333+
String outputPath,
334+
String impellercPath,
335+
) {
336+
final int exitCode = result.exitCode;
337+
if (_platform.isMacOS && exitCode == -9) {
338+
return 'The shader compiler (impellerc) may have been blocked by macOS Gatekeeper or run out of memory (OOM).\n'
339+
'To resolve Gatekeeper issues, try running:\n'
340+
' xattr -d com.apple.quarantine "$impellercPath"';
341+
}
342+
343+
final bool isWindowsAbort = _platform.isWindows && exitCode == 3;
344+
final bool isPosixAbort = (_platform.isMacOS || _platform.isLinux) && exitCode == -6;
345+
346+
if (isWindowsAbort || isPosixAbort) {
347+
var hint = 'The shader compiler (impellerc) aborted during compilation.';
348+
if (_platform.isWindows) {
349+
final bool hasNonAscii =
350+
RegExp(r'[^\x00-\x7F]').hasMatch(input.path) ||
351+
RegExp(r'[^\x00-\x7F]').hasMatch(outputPath);
352+
if (hasNonAscii) {
353+
hint +=
354+
'\nWarning: The path contains non-ASCII characters, which is known to cause crashes on Windows.\n'
355+
'Try moving your project to a path containing only ASCII characters.';
356+
}
357+
}
358+
return hint;
359+
}
360+
return null;
361+
}
362+
323363
Future<ProcessResult> _runCommand(List<String> command) async {
324364
try {
325-
return await _processManager.run(command, stderrEncoding: utf8);
365+
return await _processManager.run(
366+
command,
367+
stdoutEncoding: utf8AllowMalformed,
368+
stderrEncoding: utf8AllowMalformed,
369+
);
326370
} on ProcessException catch (e) {
327371
if (_isBlockedBySecurityPolicy(e)) {
328372
throw _SecurityPolicyBlockException(e);
@@ -363,10 +407,26 @@ class _SecurityPolicyBlockException implements Exception {
363407
}
364408

365409
class ShaderCompilerException implements Exception {
366-
ShaderCompilerException._(this.message);
410+
ShaderCompilerException._(this.message, {this.stdout, this.stderr});
367411

368412
final String message;
413+
final String? stdout;
414+
final String? stderr;
369415

370416
@override
371-
String toString() => 'ShaderCompilerException: $message\n\n';
417+
String toString() {
418+
final buffer = StringBuffer();
419+
buffer.write('ShaderCompilerException: $message\n');
420+
final String? stdout = this.stdout;
421+
if (stdout != null && stdout.trim().isNotEmpty) {
422+
buffer.writeln('Stdout:');
423+
buffer.writeln(stdout.trim());
424+
}
425+
final String? stderr = this.stderr;
426+
if (stderr != null && stderr.trim().isNotEmpty) {
427+
buffer.writeln('Stderr:');
428+
buffer.writeln(stderr.trim());
429+
}
430+
return buffer.toString();
431+
}
372432
}

‎packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart‎

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ void main() {
244244
'"/output/shaders/my_shader.frag" failed with exit code 1.',
245245
),
246246
);
247+
expect(e.stdout, 'impellerc stdout');
248+
expect(e.stderr, 'impellerc stderr');
247249
}
248250

249251
expect(fileSystem.file(outputPath).existsSync(), false);
@@ -1034,4 +1036,220 @@ void main() {
10341036
expect(headerLine.allMatches(logger.errorText).length, 2);
10351037
});
10361038
});
1039+
1040+
group('ShaderCompiler hints and diagnostics', () {
1041+
Future<void> expectShaderCompilerException({
1042+
required ShaderCompiler shaderCompiler,
1043+
required String inputPath,
1044+
required String outputPath,
1045+
required List<Matcher> matchers,
1046+
}) async {
1047+
await expectLater(
1048+
shaderCompiler.compileShader(
1049+
input: fileSystem.file(inputPath),
1050+
outputPath: outputPath,
1051+
targetPlatform: TargetPlatform.web_javascript,
1052+
),
1053+
throwsA(
1054+
isA<ShaderCompilerException>().having(
1055+
(ShaderCompilerException e) => e.toString(),
1056+
'toString()',
1057+
allOf(matchers),
1058+
),
1059+
),
1060+
);
1061+
}
1062+
1063+
testWithoutContext('macOS and exit code -9 adds Gatekeeper/OOM hint', () async {
1064+
final processManager = FakeProcessManager.list(<FakeCommand>[
1065+
FakeCommand(
1066+
command: <String>[
1067+
impellerc,
1068+
'--sksl',
1069+
'--iplr',
1070+
'--json',
1071+
'--sl=$outputPath',
1072+
'--spirv=$outputSpirvPath',
1073+
'--input=$notFragPath',
1074+
'--input-type=frag',
1075+
'--include=$fragDir',
1076+
'--include=$shaderLibDir',
1077+
],
1078+
exitCode: -9,
1079+
),
1080+
]);
1081+
final shaderCompiler = ShaderCompiler(
1082+
processManager: processManager,
1083+
logger: logger,
1084+
fileSystem: fileSystem,
1085+
artifacts: artifacts,
1086+
platform: FakePlatform(operatingSystem: 'macos'),
1087+
);
1088+
1089+
await expectShaderCompilerException(
1090+
shaderCompiler: shaderCompiler,
1091+
inputPath: notFragPath,
1092+
outputPath: outputPath,
1093+
matchers: <Matcher>[
1094+
contains('blocked by macOS Gatekeeper or run out of memory (OOM)'),
1095+
contains('xattr -d com.apple.quarantine'),
1096+
],
1097+
);
1098+
});
1099+
1100+
testWithoutContext('macOS and exit code -6 adds abort hint', () async {
1101+
final processManager = FakeProcessManager.list(<FakeCommand>[
1102+
FakeCommand(
1103+
command: <String>[
1104+
impellerc,
1105+
'--sksl',
1106+
'--iplr',
1107+
'--json',
1108+
'--sl=$outputPath',
1109+
'--spirv=$outputSpirvPath',
1110+
'--input=$notFragPath',
1111+
'--input-type=frag',
1112+
'--include=$fragDir',
1113+
'--include=$shaderLibDir',
1114+
],
1115+
exitCode: -6,
1116+
),
1117+
]);
1118+
final shaderCompiler = ShaderCompiler(
1119+
processManager: processManager,
1120+
logger: logger,
1121+
fileSystem: fileSystem,
1122+
artifacts: artifacts,
1123+
platform: FakePlatform(operatingSystem: 'macos'),
1124+
);
1125+
1126+
await expectShaderCompilerException(
1127+
shaderCompiler: shaderCompiler,
1128+
inputPath: notFragPath,
1129+
outputPath: outputPath,
1130+
matchers: <Matcher>[
1131+
contains('The shader compiler (impellerc) aborted during compilation.'),
1132+
],
1133+
);
1134+
});
1135+
1136+
testWithoutContext('Linux and exit code -6 adds abort hint', () async {
1137+
final processManager = FakeProcessManager.list(<FakeCommand>[
1138+
FakeCommand(
1139+
command: <String>[
1140+
impellerc,
1141+
'--sksl',
1142+
'--iplr',
1143+
'--json',
1144+
'--sl=$outputPath',
1145+
'--spirv=$outputSpirvPath',
1146+
'--input=$notFragPath',
1147+
'--input-type=frag',
1148+
'--include=$fragDir',
1149+
'--include=$shaderLibDir',
1150+
],
1151+
exitCode: -6,
1152+
),
1153+
]);
1154+
final shaderCompiler = ShaderCompiler(
1155+
processManager: processManager,
1156+
logger: logger,
1157+
fileSystem: fileSystem,
1158+
artifacts: artifacts,
1159+
platform: FakePlatform(),
1160+
);
1161+
1162+
await expectShaderCompilerException(
1163+
shaderCompiler: shaderCompiler,
1164+
inputPath: notFragPath,
1165+
outputPath: outputPath,
1166+
matchers: <Matcher>[
1167+
contains('The shader compiler (impellerc) aborted during compilation.'),
1168+
],
1169+
);
1170+
});
1171+
1172+
testWithoutContext('Windows and exit code 3 adds abort hint', () async {
1173+
final processManager = FakeProcessManager.list(<FakeCommand>[
1174+
FakeCommand(
1175+
command: <String>[
1176+
impellerc,
1177+
'--sksl',
1178+
'--iplr',
1179+
'--json',
1180+
'--sl=$outputPath',
1181+
'--spirv=$outputSpirvPath',
1182+
'--input=$notFragPath',
1183+
'--input-type=frag',
1184+
'--include=$fragDir',
1185+
'--include=$shaderLibDir',
1186+
],
1187+
exitCode: 3,
1188+
),
1189+
]);
1190+
final shaderCompiler = ShaderCompiler(
1191+
processManager: processManager,
1192+
logger: logger,
1193+
fileSystem: fileSystem,
1194+
artifacts: artifacts,
1195+
platform: FakePlatform(operatingSystem: 'windows'),
1196+
);
1197+
1198+
await expectShaderCompilerException(
1199+
shaderCompiler: shaderCompiler,
1200+
inputPath: notFragPath,
1201+
outputPath: outputPath,
1202+
matchers: <Matcher>[
1203+
contains('The shader compiler (impellerc) aborted during compilation.'),
1204+
isNot(contains('Warning: The path contains non-ASCII characters')),
1205+
],
1206+
);
1207+
});
1208+
1209+
testWithoutContext(
1210+
'Windows and exit code 3 with Unicode path adds Unicode path warning '
1211+
'(regression test for https://github.com/flutter/flutter/issues/190233)',
1212+
() async {
1213+
const unicodeFragPath = '/shaders/my_shåder.frag';
1214+
const unicodeOutputPath = '/output/shaders/my_shåder.frag';
1215+
const unicodeOutputSpirvPath = '/output/shaders/my_shåder.frag.spirv';
1216+
fileSystem.file(unicodeFragPath).createSync(recursive: true);
1217+
1218+
final processManager = FakeProcessManager.list(<FakeCommand>[
1219+
FakeCommand(
1220+
command: <String>[
1221+
impellerc,
1222+
'--sksl',
1223+
'--iplr',
1224+
'--json',
1225+
'--sl=$unicodeOutputPath',
1226+
'--spirv=$unicodeOutputSpirvPath',
1227+
'--input=$unicodeFragPath',
1228+
'--input-type=frag',
1229+
'--include=$fragDir',
1230+
'--include=$shaderLibDir',
1231+
],
1232+
exitCode: 3,
1233+
),
1234+
]);
1235+
final shaderCompiler = ShaderCompiler(
1236+
processManager: processManager,
1237+
logger: logger,
1238+
fileSystem: fileSystem,
1239+
artifacts: artifacts,
1240+
platform: FakePlatform(operatingSystem: 'windows'),
1241+
);
1242+
1243+
await expectShaderCompilerException(
1244+
shaderCompiler: shaderCompiler,
1245+
inputPath: unicodeFragPath,
1246+
outputPath: unicodeOutputPath,
1247+
matchers: <Matcher>[
1248+
contains('The shader compiler (impellerc) aborted during compilation.'),
1249+
contains('Warning: The path contains non-ASCII characters'),
1250+
],
1251+
);
1252+
},
1253+
);
1254+
});
10371255
}

0 commit comments

Comments
 (0)