[flutter_tool] Hide unsafe std{out,err} operations (#49561)
diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart
index 55a785c..e9d6996 100644
--- a/packages/flutter_tools/lib/runner.dart
+++ b/packages/flutter_tools/lib/runner.dart
@@ -111,12 +111,12 @@
}
} else {
// We've crashed; emit a log report.
- safeStdioWrite(stderr, '\n');
+ globals.stdio.stderrWrite('\n');
if (!reportCrashes) {
// Print the stack trace on the bots - don't write a crash report.
- safeStdioWrite(stderr, '$error\n');
- safeStdioWrite(stderr, '$stackTrace\n');
+ globals.stdio.stderrWrite('$error\n');
+ globals.stdio.stderrWrite('$stackTrace\n');
return _exit(1);
}
@@ -137,8 +137,7 @@
return _exit(1);
} catch (error) {
- safeStdioWrite(
- stderr,
+ globals.stdio.stderrWrite(
'Unable to generate crash report due to secondary error: $error\n'
'please let us know at https://github.com/flutter/flutter/issues.\n',
);
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index f8e437a..6045fa4 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -323,19 +323,34 @@
// The real stdin will never finish streaming. Pipe until the child process
// finishes.
- unawaited(process.stdin.addStream(stdin));
+ unawaited(process.stdin.addStream(globals.stdio.stdin)
+ // If the process exits unexpectedly with an error, that will be
+ // handled by the caller.
+ .catchError((dynamic err, StackTrace stack) {
+ globals.printTrace('Echoing stdin to the licenses subprocess failed:');
+ globals.printTrace('$err\n$stack');
+ }
+ ));
+
// Wait for stdout and stderr to be fully processed, because process.exitCode
// may complete first.
- await waitGroup<void>(<Future<void>>[
- stdout.addStream(process.stdout),
- stderr.addStream(process.stderr),
- ]);
+ try {
+ await waitGroup<void>(<Future<void>>[
+ globals.stdio.addStdoutStream(process.stdout),
+ globals.stdio.addStderrStream(process.stderr),
+ ]);
+ } catch (err, stack) {
+ globals.printTrace('Echoing stdout or stderr from the license subprocess failed:');
+ globals.printTrace('$err\n$stack');
+ }
final int exitCode = await process.exitCode;
return exitCode == 0;
} on ProcessException catch (e) {
throwToolExit(userMessages.androidCannotRunSdkManager(
- androidSdk.sdkManagerPath, e.toString()));
+ androidSdk.sdkManagerPath,
+ e.toString(),
+ ));
return false;
}
}
diff --git a/packages/flutter_tools/lib/src/base/io.dart b/packages/flutter_tools/lib/src/base/io.dart
index 7dc0194..b915d52 100644
--- a/packages/flutter_tools/lib/src/base/io.dart
+++ b/packages/flutter_tools/lib/src/base/io.dart
@@ -205,11 +205,17 @@
}
}
+/// A class that wraps stdout, stderr, and stdin, and exposes the allowed
+/// operations.
class Stdio {
const Stdio();
Stream<List<int>> get stdin => io.stdin;
+
+ @visibleForTesting
io.Stdout get stdout => io.stdout;
+
+ @visibleForTesting
io.IOSink get stderr => io.stderr;
bool get hasTerminal => io.stdout.hasTerminal;
@@ -246,27 +252,41 @@
int get terminalColumns => hasTerminal ? io.stdout.terminalColumns : null;
int get terminalLines => hasTerminal ? io.stdout.terminalLines : null;
bool get supportsAnsiEscapes => hasTerminal && io.stdout.supportsAnsiEscapes;
-}
-io.Stdout get stdout => globals.stdio.stdout;
-Stream<List<int>> get stdin => globals.stdio.stdin;
-io.IOSink get stderr => globals.stdio.stderr;
-bool get stdinHasTerminal => globals.stdio.stdinHasTerminal;
+ /// Writes [message] to [stderr], falling back on [fallback] if the write
+ /// throws any exception. The default fallback calls [print] on [message].
+ void stderrWrite(
+ String message, {
+ void Function(String, dynamic, StackTrace) fallback,
+ }) => _stdioWrite(stderr, message, fallback: fallback);
-/// Writes [message] to [sink], falling back on [fallback] if the write
-/// throws any exception. The default fallback calls [print] on [message].
-void safeStdioWrite(io.IOSink sink, String message, {
- void Function(String, dynamic, StackTrace) fallback,
-}) {
- try {
- sink.write(message);
- } catch (err, stack) {
- if (fallback == null) {
- print(message);
- } else {
- fallback(message, err, stack);
+ /// Writes [message] to [stdout], falling back on [fallback] if the write
+ /// throws any exception. The default fallback calls [print] on [message].
+ void stdoutWrite(
+ String message, {
+ void Function(String, dynamic, StackTrace) fallback,
+ }) => _stdioWrite(stdout, message, fallback: fallback);
+
+ // Helper for safeStderrWrite and safeStdoutWrite.
+ void _stdioWrite(io.IOSink sink, String message, {
+ void Function(String, dynamic, StackTrace) fallback,
+ }) {
+ try {
+ sink.write(message);
+ } catch (err, stack) {
+ if (fallback == null) {
+ print(message);
+ } else {
+ fallback(message, err, stack);
+ }
}
}
+
+ /// Adds [stream] to [stdout].
+ Future<void> addStdoutStream(Stream<List<int>> stream) => stdout.addStream(stream);
+
+ /// Adds [srtream] to [stderr].
+ Future<void> addStderrStream(Stream<List<int>> stream) => stderr.addStream(stream);
}
// TODO(zra): Move pid and writePidFile into `ProcessInfo`.
diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart
index e407dfd..11060db 100644
--- a/packages/flutter_tools/lib/src/base/logger.dart
+++ b/packages/flutter_tools/lib/src/base/logger.dart
@@ -9,7 +9,7 @@
import '../base/context.dart';
import '../globals.dart' as globals;
-import 'io.dart' hide stderr, stdin, stdout;
+import 'io.dart';
import 'terminal.dart' show AnsiTerminal, TerminalColor, OutputPreferences;
import 'utils.dart';
@@ -268,14 +268,10 @@
}
@protected
- void writeToStdOut(String message) {
- safeStdioWrite(_stdio.stdout, message);
- }
+ void writeToStdOut(String message) => _stdio.stdoutWrite(message);
@protected
- void writeToStdErr(String message) {
- safeStdioWrite(_stdio.stderr, message);
- }
+ void writeToStdErr(String message) => _stdio.stderrWrite(message);
@override
void printTrace(String message) { }
@@ -363,7 +359,7 @@
final String windowsMessage = message
.replaceAll('✗', 'X')
.replaceAll('✓', '√');
- safeStdioWrite(_stdio.stdout, windowsMessage);
+ _stdio.stdoutWrite(windowsMessage);
}
}
@@ -794,9 +790,7 @@
super.start();
}
- void _writeToStdOut(String message) {
- safeStdioWrite(_stdio.stdout, message);
- }
+ void _writeToStdOut(String message) => _stdio.stdoutWrite(message);
void _printMessage() {
assert(!_messageShowingOnCurrentLine);
@@ -903,9 +897,7 @@
_startSpinner();
}
- void _writeToStdOut(String message) {
- safeStdioWrite(_stdio.stdout, message);
- }
+ void _writeToStdOut(String message) => _stdio.stdoutWrite(message);
void _startSpinner() {
_writeToStdOut(_clear); // for _callback to backspace over
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index cd6b7c3..a9e43eb 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -143,7 +143,7 @@
final dynamic id = request['id'];
if (id == null) {
- safeStdioWrite(stderr, 'no id for request: $request\n');
+ globals.stdio.stderrWrite('no id for request: $request\n');
return;
}
@@ -323,9 +323,11 @@
// capture the print output for testing.
print(message.message);
} else if (message.level == 'error') {
- safeStdioWrite(stderr, '${message.message}\n');
+ globals.stdio.stderrWrite('${message.message}\n');
if (message.stackTrace != null) {
- safeStdioWrite(stderr, '${message.stackTrace.toString().trimRight()}\n');
+ globals.stdio.stderrWrite(
+ '${message.stackTrace.toString().trimRight()}\n',
+ );
}
}
} else {
@@ -862,7 +864,7 @@
}
}
-Stream<Map<String, dynamic>> get stdinCommandStream => stdin
+Stream<Map<String, dynamic>> get stdinCommandStream => globals.stdio.stdin
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.where((String line) => line.startsWith('[{') && line.endsWith('}]'))
@@ -872,8 +874,7 @@
});
void stdoutCommandResponse(Map<String, dynamic> command) {
- safeStdioWrite(
- stdout,
+ globals.stdio.stdoutWrite(
'[${jsonEncodeObject(command)}]\n',
fallback: (String message, dynamic error, StackTrace stack) {
throwToolExit('Failed to write daemon command response to stdout: $error');
diff --git a/packages/flutter_tools/lib/src/commands/shell_completion.dart b/packages/flutter_tools/lib/src/commands/shell_completion.dart
index 1892058..b85bd30 100644
--- a/packages/flutter_tools/lib/src/commands/shell_completion.dart
+++ b/packages/flutter_tools/lib/src/commands/shell_completion.dart
@@ -8,7 +8,6 @@
import '../base/common.dart';
import '../base/file_system.dart';
-import '../base/io.dart';
import '../globals.dart' as globals;
import '../runner/flutter_command.dart';
@@ -50,7 +49,7 @@
if (argResults.rest.isEmpty || argResults.rest.first == '-') {
final String script = generateCompletionScript(<String>['flutter']);
- safeStdioWrite(stdout, script);
+ globals.stdio.stdoutWrite(script);
return FlutterCommandResult.warning();
}
diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart
index ff4b18b..b4d45db 100644
--- a/packages/flutter_tools/lib/src/dart/pub.dart
+++ b/packages/flutter_tools/lib/src/dart/pub.dart
@@ -308,7 +308,7 @@
);
// Pipe the Flutter tool stdin to the pub stdin.
- unawaited(process.stdin.addStream(io.stdin)
+ unawaited(process.stdin.addStream(globals.stdio.stdin)
// If pub exits unexpectedly with an error, that will be reported below
// by the tool exit after the exit code check.
.catchError((dynamic err, StackTrace stack) {
@@ -320,8 +320,8 @@
// Pipe the pub stdout and stderr to the tool stdout and stderr.
try {
await Future.wait<dynamic>(<Future<dynamic>>[
- io.stdout.addStream(process.stdout),
- io.stderr.addStream(process.stderr),
+ globals.stdio.addStdoutStream(process.stdout),
+ globals.stdio.addStderrStream(process.stderr),
]);
} catch (err, stack) {
globals.printTrace('Echoing stdout or stderr from the pub subprocess failed:');
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 312845d..92e03e4 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -630,7 +630,8 @@
final Map<CustomDimensions, String> additionalUsageValues =
<CustomDimensions, String>{
...?await usageValues,
- CustomDimensions.commandHasTerminal: io.stdout.hasTerminal ? 'true' : 'false',
+ CustomDimensions.commandHasTerminal:
+ globals.stdio.hasTerminal ? 'true' : 'false',
};
Usage.command(commandPath, parameters: additionalUsageValues);
}
diff --git a/packages/flutter_tools/lib/src/test/event_printer.dart b/packages/flutter_tools/lib/src/test/event_printer.dart
index 91ef721..9d753d6 100644
--- a/packages/flutter_tools/lib/src/test/event_printer.dart
+++ b/packages/flutter_tools/lib/src/test/event_printer.dart
@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import '../base/io.dart' show stdout;
import '../convert.dart';
+import '../globals.dart' as globals;
import 'watcher.dart';
/// Prints JSON events when running a test in --machine mode.
class EventPrinter extends TestWatcher {
- EventPrinter({StringSink out}) : _out = out ?? stdout;
+ EventPrinter({StringSink out}) : _out = out ?? globals.stdio.stdout;
final StringSink _out;
diff --git a/packages/flutter_tools/test/general.shard/base/logger_test.dart b/packages/flutter_tools/test/general.shard/base/logger_test.dart
index e1c3374..0b6ff80 100644
--- a/packages/flutter_tools/test/general.shard/base/logger_test.dart
+++ b/packages/flutter_tools/test/general.shard/base/logger_test.dart
@@ -20,9 +20,18 @@
final String resetBold = RegExp.escape(AnsiTerminal.resetBold);
final String resetColor = RegExp.escape(AnsiTerminal.resetColor);
-class MockStdio extends Mock implements Stdio {}
class MockStdout extends Mock implements Stdout {}
+class ThrowingStdio extends Stdio {
+ ThrowingStdio(this.stdout, this.stderr);
+
+ @override
+ final Stdout stdout;
+
+ @override
+ final IOSink stderr;
+}
+
void main() {
group('AppContext', () {
FakeStopwatch fakeStopWatch;
@@ -82,13 +91,11 @@
});
testWithoutContext('Logger does not throw when stdio write throws', () async {
- final MockStdio stdio = MockStdio();
final MockStdout stdout = MockStdout();
final MockStdout stderr = MockStdout();
+ final ThrowingStdio stdio = ThrowingStdio(stdout, stderr);
bool stdoutThrew = false;
bool stderrThrew = false;
- when(stdio.stdout).thenReturn(stdout);
- when(stdio.stderr).thenReturn(stderr);
when(stdout.write(any)).thenAnswer((_) {
stdoutThrew = true;
throw 'Error';