blob: 7ab92bde7e2aac832895eb2a9df22bbbfc1757f5 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Data model shared between client and server.
library symbolizer.model;
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
part 'model.g.dart';
/// Specifies an engine variant (a combination of target os, CPU architecture
/// and build mode).
class EngineVariant with _$EngineVariant {
factory EngineVariant({
required String os,
required String? arch,
required String? mode,
}) = _EngineVariant;
factory EngineVariant.fromJson(Map<String, dynamic> json) =>
/// Generate all posibile variants from the given [variant] by varying
/// [EngineVariant.mode].
static Iterable<EngineVariant> allModesFor(EngineVariant variant) sync* {
for (var mode in [
if (!_isX86Variant(variant)) 'release',
if (!_isX86Variant(variant)) 'profile'
]) {
yield variant.copyWith(mode: mode);
static bool _isX86Variant(EngineVariant variant) =>
variant.arch == 'x86' || variant.arch == 'x64';
extension EngineVariantEx on EngineVariant {
String get pretty => '$os-$arch-$mode';
/// Specific engine variant built at the given engine hash.
class EngineBuild with _$EngineBuild {
factory EngineBuild({
required String engineHash,
required EngineVariant variant,
}) = _EngineBuild;
factory EngineBuild.fromJson(Map<String, dynamic> json) =>
/// Backtrace frame extracted from a textual crash report.
class CrashFrame with _$CrashFrame {
/// Frame of a native iOS crash.
factory CrashFrame.ios({
required String no,
required String binary,
/// Absolute PC of the frame.
required int pc,
required String symbol,
required int? offset,
required String location,
}) = IosCrashFrame;
/// Frame of a native Android crash.
required String no,
/// Relative PC of the frame.
required int pc,
required String binary,
required String rest,
required String? buildId,
}) = AndroidCrashFrame;
factory CrashFrame.custom({
required String no,
required int pc,
required String binary,
required int? offset,
required String? location,
required String? symbol,
}) = CustomCrashFrame;
/// Frame of a Dart VM crash.
factory CrashFrame.dartvm({
/// Absolute PC of the frame.
required int pc,
/// Binary which contains the given PC.
required String binary,
/// Offset from load base of the binary to the PC.
required int offset,
}) = DartvmCrashFrame;
factory CrashFrame.fromJson(Map<String, dynamic> json) =>
static const crashalyticsMissingSymbol = '(Missing)';
/// Information about an engine crash extracted from a GitHub comment.
class Crash with _$Crash {
factory Crash({
required EngineVariant engineVariant,
required List<CrashFrame> frames,
required String format,
int? androidMajorVersion,
}) = _Crash;
factory Crash.fromJson(Map<String, dynamic> json) => _$CrashFromJson(json);
enum SymbolizationNoteKind {
const noteMessage = <SymbolizationNoteKind, String>{
SymbolizationNoteKind.unknownEngineHash: 'Unknown engine hash',
SymbolizationNoteKind.unknownAbi: 'Unknown engine ABI',
'Exception occurred while trying to lookup full engine hash',
'Exception occurred while symbolizing',
'Exception occurred while trying to find symbols using build-id',
'Defaulted to release engine because build-id is unavailable or unreliable',
'Symbols are available only for release iOS builds',
SymbolizationNoteKind.buildIdMismatch: 'Build-ID mismatch',
'Load address missing from the report, detected heuristically',
'Exception occured while parsing',
class SymbolizationResult with _$SymbolizationResult {
@JsonSerializable(explicitToJson: true)
factory SymbolizationResult.ok({
required List<CrashSymbolizationResult> results,
}) = SymbolizationResultOk;
@JsonSerializable(explicitToJson: true)
factory SymbolizationResult.error({
required SymbolizationNote error,
}) = SymbolizationResultError;
factory SymbolizationResult.fromJson(Map<String, dynamic> json) =>
/// Result of symbolizing an engine crash.
class CrashSymbolizationResult with _$CrashSymbolizationResult {
@JsonSerializable(explicitToJson: true)
factory CrashSymbolizationResult({
required Crash crash,
required EngineBuild? engineBuild,
/// Symbolization result - not null if symbolization succeeded.
required String? symbolized,
@Default([]) List<SymbolizationNote> notes,
}) = _CrashSymbolizationResult;
factory CrashSymbolizationResult.fromJson(Map<String, dynamic> json) =>
extension WithNote on CrashSymbolizationResult {
CrashSymbolizationResult withNote(SymbolizationNoteKind kind,
[String? message]) {
return copyWith(
notes: [
SymbolizationNote(kind: kind, message: message),
class SymbolizationNote with _$SymbolizationNote {
factory SymbolizationNote(
{required SymbolizationNoteKind kind,
String? message}) = _SymbolizationNote;
factory SymbolizationNote.fromJson(Map<String, dynamic> json) =>
/// Command to [Bot].
class BotCommand with _$BotCommand {
factory BotCommand({
/// Overrides that should be used for symbolization. These overrides
/// replace or augment information available in the comments themselves.
required SymbolizationOverrides overrides,
/// [true] if the user requested to symbolize the comment that contains
/// command.
required bool symbolizeThis,
/// List of references to comments which need to be symbolized. Each reference
/// is either in `issue-id` or in `issuecomment-id` format.
required Set<String> worklist,
}) = _BotCommand;
class SymbolizationOverrides with _$SymbolizationOverrides {
factory SymbolizationOverrides({
String? engineHash,
String? flutterVersion,
String? arch,
String? mode,
@Default(false) bool force,
String? format,
String? os,
}) = _SymbolizationOverrides;
/// Parse the given [overrides].
/// See for overrides documentation.
static SymbolizationOverrides? tryParse(String overrides) {
String? engineHash;
String? flutterVersion;
String? os;
String? arch;
String? mode;
String? format;
var force = false;
// Command is just a sequence of keywords which specify which comments
// to symbolize and which symbols to use.
for (var keyword in overrides.split(' ')) {
switch (keyword) {
case 'x86':
case 'arm':
case 'arm64':
case 'x64':
arch = keyword;
case 'debug':
case 'profile':
case 'release':
mode = keyword;
case 'internal':
format = 'internal';
case 'force':
force = true;
case 'ios':
os = 'ios';
// Check if this keyword is an engine hash.
var m = _engineHashPattern.firstMatch(keyword);
if (m != null) {
engineHash = m.namedGroup('sha');
m = _flutterHashOrVersionPattern.firstMatch(keyword);
if (m != null) {
flutterVersion = m.namedGroup('version');
return SymbolizationOverrides(
arch: arch,
engineHash: engineHash,
flutterVersion: flutterVersion,
mode: mode,
os: os,
force: force,
format: format,
static final _engineHashPattern = RegExp(r'^engine#(?<sha>[a-f0-9]+)$');
static final _flutterHashOrVersionPattern =
class ServerConfig with _$ServerConfig {
factory ServerConfig({
required String githubToken,
}) = _ServerConfig;
factory ServerConfig.fromJson(Map<String, dynamic> json) =>