blob: 82288441ea261c6f6bb81bd931495799f0213e80 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:fidl_fuchsia_feedback/fidl_async.dart';
import 'package:fidl_fuchsia_mem/fidl_async.dart';
import 'package:fuchsia_logger/logger.dart';
import 'package:fuchsia_services/services.dart' show StartupContext;
import 'package:zircon/zircon.dart';
/// Defines a class to run programs under an error [Zone].
///
/// Unhandled errors are reported to |fuchsia.feedback.CrashReporter| service.
class CrashReportingRunner {
CrashReporter reporter;
SizedVmo Function(String) vmoBuilder;
final DateTime _startTime;
CrashReportingRunner({this.reporter, this.vmoBuilder})
: _startTime = DateTime.now();
/// Run the function [body] in a error zone to catch unhandled errors.
Future<void> run(Future<void> Function() body) async {
FlutterError.onError = _report;
runZoned(
() {
// Use try-catch for non-flutter framework errors or synchronous errors.
try {
body();
// ignore: avoid_catches_without_on_clauses
} catch (e, stackTrace) {
// Never ever use error-handling-zones in user code:
// go/no-error-handling-zones.
throw _SynchronousException(e, stackTrace);
}
},
onError: _onError,
);
}
// Creates and files a [CrashReport] upon receiving [FlutterErrorDetails].
void _report(FlutterErrorDetails details) {
final uptime = DateTime.now().difference(_startTime);
var errorType = 'UnknownError';
var errorMessage = details.summary.toString();
final index = errorMessage.toString().indexOf(':');
if (index >= 0) {
errorType = errorMessage.substring(0, index);
errorMessage =
errorMessage.substring(index + 2 /*to get rid of the leading ': '*/);
}
// Optionally you can print the error to console. This is the default
// Flutter behavior for uncaught errors.
// This won't dump anything to console if flutter_build_mode=release
FlutterError.dumpErrorToConsole(details, forceReport: true);
// Convert stacktrace to VMO.
vmoBuilder ??= _vmoFromStackTrace;
final vmo = vmoBuilder(details.stack.toString());
// Generate [CrashReport] from errorType, errorMessage and stackTrace.
final report = CrashReport(
programName: 'ermine.cmx',
programUptime: uptime.inMilliseconds * 1000,
specificReport: SpecificCrashReport.withDart(
RuntimeCrashReport(
exceptionType: errorType,
exceptionMessage: errorMessage,
exceptionStackTrace: Buffer(vmo: vmo, size: vmo.size),
),
),
);
// File the report.
_fileReport(report);
}
Future<void> _fileReport(CrashReport report) async {
log.severe('Caught unhandled error in ermine. Generating crash report');
if (reporter != null) {
await reporter.file(report);
} else {
final reporterProxy = CrashReporterProxy();
StartupContext.fromStartupInfo().incoming.connectToService(reporterProxy);
await reporterProxy.file(report);
reporterProxy.ctrl.close();
}
}
// Handles error thrown by [run].
void _onError(Object error, StackTrace stackTrace) {
var realError = error;
var realStackTrace = stackTrace;
if (error is _SynchronousException) {
realError = error.cause;
realStackTrace = error.stack;
}
// Pipes the error over to [FlutterError], which will run the custom
// onError handler.
FlutterError.reportError(
FlutterErrorDetails(exception: realError, stack: realStackTrace));
// Just logging and ignoring an uncaught synchronous exception is not safe.
// After unwinding the stack, Agents and features might be in an
// arbitrarily inconsistent state.
if (error is _SynchronousException) {
throw error;
}
}
}
/// Returns a [SizedVmo] from String.
SizedVmo _vmoFromStackTrace(String stackTrace) {
final list = Uint8List.fromList(stackTrace.codeUnits);
return SizedVmo.fromUint8List(list);
}
/// An exception wrapper to indicate synchronous exceptions thrown from main().
class _SynchronousException implements Exception {
dynamic cause;
StackTrace stack;
_SynchronousException(this.cause, this.stack);
}