Commit 68ed5c2b authored by Zachary Anderson's avatar Zachary Anderson Committed by Flutter GitHub Bot

[flutter_tool] Hide unsafe std{out,err} operations (#49561)

parent fabeb2a1
......@@ -111,12 +111,12 @@ Future<int> _handleToolError(
}
} 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 @@ Future<int> _handleToolError(
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',
);
......
......@@ -323,19 +323,34 @@ class AndroidLicenseValidator extends DoctorValidator {
// 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;
}
}
......
......@@ -205,11 +205,17 @@ class _PosixProcessSignal extends ProcessSignal {
}
}
/// 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 @@ class Stdio {
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 [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 [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 [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`.
......
......@@ -9,7 +9,7 @@ import 'package:platform/platform.dart';
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 @@ class StdoutLogger extends Logger {
}
@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 @@ class WindowsStdoutLogger extends StdoutLogger {
final String windowsMessage = message
.replaceAll('✗', 'X')
.replaceAll('✓', '√');
safeStdioWrite(_stdio.stdout, windowsMessage);
_stdio.stdoutWrite(windowsMessage);
}
}
......@@ -794,9 +790,7 @@ class SummaryStatus extends Status {
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 @@ class AnsiSpinner extends Status {
_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
......
......@@ -143,7 +143,7 @@ class Daemon {
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 @@ class DaemonDomain extends Domain {
// 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 @@ class DeviceDomain extends Domain {
}
}
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 @@ Stream<Map<String, dynamic>> get stdinCommandStream => stdin
});
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');
......
......@@ -8,7 +8,6 @@ import 'package:completion/completion.dart';
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 @@ class ShellCompletionCommand extends FlutterCommand {
if (argResults.rest.isEmpty || argResults.rest.first == '-') {
final String script = generateCompletionScript(<String>['flutter']);
safeStdioWrite(stdout, script);
globals.stdio.stdoutWrite(script);
return FlutterCommandResult.warning();
}
......
......@@ -308,7 +308,7 @@ class _DefaultPub implements Pub {
);
// 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 @@ class _DefaultPub implements Pub {
// 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:');
......
......@@ -630,7 +630,8 @@ abstract class FlutterCommand extends Command<void> {
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);
}
......
......@@ -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;
......
......@@ -20,9 +20,18 @@ final String bold = RegExp.escape(AnsiTerminal.bold);
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 @@ void main() {
});
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';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment