Unverified Commit 56817279 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add "flutter symbolize" command (#49465)

parent ffc85591
...@@ -34,6 +34,8 @@ dart_library("flutter_tools") { ...@@ -34,6 +34,8 @@ dart_library("flutter_tools") {
"//third_party/dart-pkg/pub/multi_server_socket", "//third_party/dart-pkg/pub/multi_server_socket",
"//third_party/dart-pkg/pub/multicast_dns", "//third_party/dart-pkg/pub/multicast_dns",
"//third_party/dart-pkg/pub/mustache", "//third_party/dart-pkg/pub/mustache",
"//third_party/dart-pkg/pub/native_stack_traces",
"//third_party/dart-pkg/pub/node_preamble",
"//third_party/dart-pkg/pub/package_config", "//third_party/dart-pkg/pub/package_config",
"//third_party/dart-pkg/pub/path", "//third_party/dart-pkg/pub/path",
"//third_party/dart-pkg/pub/platform", "//third_party/dart-pkg/pub/platform",
...@@ -51,7 +53,6 @@ dart_library("flutter_tools") { ...@@ -51,7 +53,6 @@ dart_library("flutter_tools") {
"//third_party/dart-pkg/pub/webkit_inspection_protocol", "//third_party/dart-pkg/pub/webkit_inspection_protocol",
"//third_party/dart-pkg/pub/xml", "//third_party/dart-pkg/pub/xml",
"//third_party/dart-pkg/pub/yaml", "//third_party/dart-pkg/pub/yaml",
"//third_party/dart-pkg/pub/node_preamble",
] ]
} }
......
...@@ -39,12 +39,14 @@ import 'src/commands/precache.dart'; ...@@ -39,12 +39,14 @@ import 'src/commands/precache.dart';
import 'src/commands/run.dart'; import 'src/commands/run.dart';
import 'src/commands/screenshot.dart'; import 'src/commands/screenshot.dart';
import 'src/commands/shell_completion.dart'; import 'src/commands/shell_completion.dart';
import 'src/commands/symbolize.dart';
import 'src/commands/test.dart'; import 'src/commands/test.dart';
import 'src/commands/train.dart'; import 'src/commands/train.dart';
import 'src/commands/unpack.dart'; import 'src/commands/unpack.dart';
import 'src/commands/update_packages.dart'; import 'src/commands/update_packages.dart';
import 'src/commands/upgrade.dart'; import 'src/commands/upgrade.dart';
import 'src/commands/version.dart'; import 'src/commands/version.dart';
import 'src/globals.dart' as globals;
import 'src/runner/flutter_command.dart'; import 'src/runner/flutter_command.dart';
import 'src/web/compile.dart'; import 'src/web/compile.dart';
import 'src/web/web_runner.dart'; import 'src/web/web_runner.dart';
...@@ -94,6 +96,10 @@ Future<void> main(List<String> args) async { ...@@ -94,6 +96,10 @@ Future<void> main(List<String> args) async {
UpdatePackagesCommand(hidden: !verboseHelp), UpdatePackagesCommand(hidden: !verboseHelp),
UpgradeCommand(), UpgradeCommand(),
VersionCommand(), VersionCommand(),
SymbolizeCommand(
stdio: globals.stdio,
fileSystem: globals.fs,
),
], verbose: verbose, ], verbose: verbose,
muteCommandLogging: muteCommandLogging, muteCommandLogging: muteCommandLogging,
verboseHelp: verboseHelp, verboseHelp: verboseHelp,
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../convert.dart';
import '../runner/flutter_command.dart';
/// Support for symbolicating a Dart stack trace.
///
/// This command accepts either paths to an input file containing the
/// stack trace and an output file for the symbolicated trace to be
/// written, or it accepts a stack trace over stdin and outputs it
/// over stdout.
class SymbolizeCommand extends FlutterCommand {
SymbolizeCommand({
@required Stdio stdio,
@required FileSystem fileSystem,
DwarfSymbolizationService dwarfSymbolizationService = const DwarfSymbolizationService(),
}) : _stdio = stdio,
_fileSystem = fileSystem,
_dwarfSymbolizationService = dwarfSymbolizationService {
argParser.addOption(
'debug-info',
abbr: 'd',
valueHelp: '/out/android/app.arm64.symbols',
help: 'A path to the symbols file generated with "--split-debug-info".'
);
argParser.addOption(
'input',
abbr: 'i',
valueHelp: '/crashes/stack_trace.err',
help: 'A file path containing a Dart stack trace.'
);
argParser.addOption(
'output',
abbr: 'o',
valueHelp: 'A file path for a symbolicated stack trace to be written to.'
);
}
final Stdio _stdio;
final FileSystem _fileSystem;
final DwarfSymbolizationService _dwarfSymbolizationService;
@override
String get description => 'Symbolize a stack trace from an AOT compiled flutter application.';
@override
String get name => 'symbolize';
@override
bool get shouldUpdateCache => false;
@override
Future<void> validateCommand() {
if (!argResults.wasParsed('debug-info')) {
throwToolExit('"--debug-info" is required to symbolicate stack traces.');
}
if (!_fileSystem.isFileSync(stringArg('debug-info'))) {
throwToolExit('${stringArg('debug-info')} does not exist.');
}
if (argResults.wasParsed('input') && !_fileSystem.isFileSync(stringArg('input'))) {
throwToolExit('${stringArg('input')} does not exist.');
}
return super.validateCommand();
}
@override
Future<FlutterCommandResult> runCommand() async {
Stream<List<int>> input;
IOSink output;
// Configure output to either specified file or stdout.
if (argResults.wasParsed('output')) {
final File outputFile = _fileSystem.file(stringArg('output'));
if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
}
output = outputFile.openWrite();
} else {
final StreamController<List<int>> outputController = StreamController<List<int>>();
outputController
.stream
.transform(utf8.decoder)
.listen(_stdio.stdoutWrite);
output = IOSink(outputController);
}
// Configure input from either specified file or stdin.
if (argResults.wasParsed('input')) {
input = _fileSystem.file(stringArg('input')).openRead();
} else {
input = _stdio.stdin;
}
final Uint8List symbols = _fileSystem.file(stringArg('debug-info')).readAsBytesSync();
await _dwarfSymbolizationService.decode(
input: input,
output: output,
symbols: symbols,
);
return FlutterCommandResult.success();
}
}
/// A service which decodes stack traces from Dart applications.
class DwarfSymbolizationService {
const DwarfSymbolizationService();
/// Decode a stack trace from [input] and place the results in [output].
///
/// Requires [symbols] to be a buffer created from the `--split-debug-info`
/// command line flag.
///
/// Throws a [ToolExit] if the symbols cannot be parsed or the stack trace
/// cannot be decoded.
Future<void> decode({
@required Stream<List<int>> input,
@required IOSink output,
@required Uint8List symbols,
}) async {
final Dwarf dwarf = Dwarf.fromBytes(symbols);
if (dwarf == null) {
throwToolExit('Failed to decode symbols file');
}
final Completer<void> onDone = Completer<void>();
StreamSubscription<void> subscription;
subscription = input
.transform(const Utf8Decoder())
.transform(const LineSplitter())
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: true))
.listen((String line) {
try {
output.writeln(line);
} on Exception catch(e, s) {
subscription.cancel().whenComplete(() {
if (!onDone.isCompleted) {
onDone.completeError(e, s);
}
});
}
}, onDone: onDone.complete, onError: onDone.completeError);
try {
await onDone.future;
await output.close();
} on Exception catch (err) {
throwToolExit('Failed to symbolize stack trace:\n $err');
}
}
}
...@@ -375,8 +375,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -375,8 +375,7 @@ abstract class FlutterCommand extends Command<void> {
'symbol files can be stored for later use. These symbol files contain ' 'symbol files can be stored for later use. These symbol files contain '
'the information needed to symbolize Dart stack traces. For an app built ' 'the information needed to symbolize Dart stack traces. For an app built '
'with this flag, the \'flutter symbolize\' command with the right program ' 'with this flag, the \'flutter symbolize\' command with the right program '
'symbol file is required to obtain a human readable stack trace. This ' 'symbol file is required to obtain a human readable stack trace.',
'command is tracked by https://github.com/flutter/flutter/issues/50206',
valueHelp: '/project-name/v1.2.3/', valueHelp: '/project-name/v1.2.3/',
); );
} }
......
...@@ -35,6 +35,7 @@ dependencies: ...@@ -35,6 +35,7 @@ dependencies:
webkit_inspection_protocol: 0.5.0 webkit_inspection_protocol: 0.5.0
xml: 3.5.0 xml: 3.5.0
yaml: 2.2.0 yaml: 2.2.0
native_stack_traces: 0.2.2
flutter_goldens_client: flutter_goldens_client:
path: ../flutter_goldens_client path: ../flutter_goldens_client
...@@ -62,12 +63,12 @@ dependencies: ...@@ -62,12 +63,12 @@ dependencies:
build_config: 0.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" build_config: 0.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
build_resolvers: 1.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" build_resolvers: 1.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
built_collection: 4.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_collection: 4.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
built_value: 7.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_value: 7.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
checked_yaml: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" checked_yaml: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
devtools: 0.1.14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" devtools: 0.1.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
devtools_server: 0.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" devtools_server: 0.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
fixnum: 0.10.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fixnum: 0.10.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
...@@ -127,4 +128,4 @@ dartdoc: ...@@ -127,4 +128,4 @@ dartdoc:
# Exclude this package from the hosted API docs. # Exclude this package from the hosted API docs.
nodoc: true nodoc: true
# PUBSPEC CHECKSUM: 3a4b # PUBSPEC CHECKSUM: 557d
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/symbolize.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
void main() {
MemoryFileSystem fileSystem;
MockStdio stdio;
SymbolizeCommand command;
MockDwarfSymbolizationService mockDwarfSymbolizationService;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
fileSystem = MemoryFileSystem.test();
stdio = MockStdio();
mockDwarfSymbolizationService = MockDwarfSymbolizationService();
command = SymbolizeCommand(
stdio: stdio,
fileSystem: fileSystem,
dwarfSymbolizationService: mockDwarfSymbolizationService,
);
applyMocksToCommand(command);
});
testUsingContext('symbolize exits when --debug-info argument is missing', () async {
final Future<void> result = createTestCommandRunner(command)
.run(const <String>['symbolize']);
expect(result, throwsToolExit(message: '"--debug-info" is required to symbolicate stack traces.'));
});
testUsingContext('symbolize exits when --debug-info file is missing', () async {
final Future<void> result = createTestCommandRunner(command)
.run(const <String>['symbolize', '--debug-info=app.debug']);
expect(result, throwsToolExit(message: 'app.debug does not exist.'));
});
testUsingContext('symbolize exits when --input file is missing', () async {
fileSystem.file('app.debug').createSync();
final Future<void> result = createTestCommandRunner(command)
.run(const <String>['symbolize', '--debug-info=app.debug', '--input=foo.stack', '--output=results/foo.result']);
expect(result, throwsToolExit(message: ''));
});
testUsingContext('symbolize succeedes when DwarfSymbolizationService does not throw', () async {
fileSystem.file('app.debug').writeAsBytesSync(<int>[1, 2, 3]);
fileSystem.file('foo.stack').writeAsStringSync('hello');
when(mockDwarfSymbolizationService.decode(
input: anyNamed('input'),
output: anyNamed('output'),
symbols: anyNamed('symbols'))
).thenAnswer((Invocation invocation) async {
// Data is passed correctly to service
expect((await (invocation.namedArguments[#input] as Stream<List<int>>).toList()).first,
utf8.encode('hello'));
expect(invocation.namedArguments[#symbols] as Uint8List, <int>[1, 2, 3,]);
return;
});
await createTestCommandRunner(command)
.run(const <String>['symbolize', '--debug-info=app.debug', '--input=foo.stack', '--output=results/foo.result']);
});
testUsingContext('symbolize throws when DwarfSymbolizationService throws', () async {
fileSystem.file('app.debug').writeAsBytesSync(<int>[1, 2, 3]);
fileSystem.file('foo.stack').writeAsStringSync('hello');
when(mockDwarfSymbolizationService.decode(
input: anyNamed('input'),
output: anyNamed('output'),
symbols: anyNamed('symbols'))
).thenThrow(ToolExit('test'));
expect(
createTestCommandRunner(command).run(const <String>[
'symbolize', '--debug-info=app.debug', '--input=foo.stack', '--output=results/foo.result']),
throwsToolExit(message: 'test'),
);
});
}
class MockDwarfSymbolizationService extends Mock implements DwarfSymbolizationService {}
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