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