blob: e82124926f1afadb717c9fd9b588fc253862a436 [file] [log] [blame]
// Copyright (c) 2015, 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 produced by dart2js when run with the `--dump-info` flag.
library dart2js_info.info;
/// Common interface to many pieces of information generated by the dart2js
/// compiler.
abstract class Info {
/// An identifier for the kind of information.
InfoKind get kind;
/// Name of the element associated with this info.
String name;
/// An id to uniquely identify this info among infos of the same [kind].
int get id;
/// A globally unique id combining [kind] and [id] together.
String get serializedId;
/// Id used by the compiler when instrumenting code for code coverage.
// TODO(sigmund): It would be nice if we could use the same id for
// serialization and for coverage. Could we unify them?
String coverageId;
/// Bytes used in the generated code for the corresponding element.
int size;
/// Info of the enclosing element.
Info parent;
/// Serializes the information into a JSON format.
// TODO(sigmund): refactor and put toJson outside the class, so we can have 2
// different serializer/deserializers at once.
Map toJson();
void accept(InfoVisitor visitor);
}
/// Common information used for most kind of elements.
// TODO(sigmund): add more:
// - inputSize: bytes used in the Dart source program
abstract class BasicInfo implements Info {
final InfoKind kind;
final int id;
String coverageId;
int size;
Info parent;
String get serializedId => '${_kindToString(kind)}/$id';
String name;
/// If using deferred libraries, where the element associated with this info
/// is generated.
OutputUnitInfo outputUnit;
BasicInfo(this.kind, this.id, this.name, this.outputUnit, this.size,
this.coverageId);
BasicInfo._fromId(String serializedId)
: kind = _kindFromSerializedId(serializedId),
id = _idFromSerializedId(serializedId);
Map toJson() {
var res = {
'id': serializedId,
'kind': _kindToString(kind),
'name': name,
'size': size,
};
// TODO(sigmund): Omit this also when outputUnit.id == 0 (most code is in
// the main output unit by default).
if (outputUnit != null) res['outputUnit'] = outputUnit.serializedId;
if (coverageId != null) res['coverageId'] = coverageId;
if (parent != null) res['parent'] = parent.serializedId;
return res;
}
String toString() => '$serializedId $name [$size]';
}
/// Info associated with elements containing executable code (like fields and
/// methods)
abstract class CodeInfo implements Info {
/// How does this function or field depend on others.
final List<DependencyInfo> uses = <DependencyInfo>[];
}
/// The entire information produced while compiling a program.
class AllInfo {
/// Summary information about the program.
ProgramInfo program;
/// Information about each library processed by the compiler.
List<LibraryInfo> libraries = <LibraryInfo>[];
/// Information about each function (includes methods and getters in any
/// library)
List<FunctionInfo> functions = <FunctionInfo>[];
/// Information about type defs in the program.
List<TypedefInfo> typedefs = <TypedefInfo>[];
/// Information about each class (in any library).
List<ClassInfo> classes = <ClassInfo>[];
/// Information about fields (in any class).
List<FieldInfo> fields = <FieldInfo>[];
/// Information about constants anywhere in the program.
// TODO(sigmund): expand docs about canonicalization. We don't put these
// inside library because a single constant can be used in more than one lib,
// and we'll include it only once in the output.
List<ConstantInfo> constants = <ConstantInfo>[];
/// Information about output units (should be just one entry if not using
/// deferred loading).
List<OutputUnitInfo> outputUnits = <OutputUnitInfo>[];
/// Details about all deferred imports and what files would be loaded when the
/// import is resolved.
// TODO(sigmund): use a different format for dump-info. This currently emits
// the same map that is created for the `--deferred-map` flag.
Map<String, Map<String, dynamic>> deferredFiles;
/// A new representation of dependencies form one info to another. An entry in
/// this map indicates that an Info depends on another (e.g. a function
/// invokes another). Please note that the data in this field might not be
/// accurate yet (this is work in progress).
Map<Info, List<Info>> dependencies = {};
/// Major version indicating breaking changes in the format. A new version
/// means that an old deserialization algorithm will not work with the new
/// format.
final int version = 3;
/// Minor version indicating non-breaking changes in the format. A change in
/// this version number means that the json parsing in this library from a
/// previous will continue to work after the change. This is typically
/// increased when adding new entries to the file format.
// Note: the dump-info.viewer app was written using a json parser version 3.2.
final int minorVersion = 5;
AllInfo();
static AllInfo parseFromJson(Map map) => new _ParseHelper().parseAll(map);
Map _listAsJsonMap(List<Info> list) {
var map = <String, Map>{};
for (var info in list) {
map['${info.id}'] = info.toJson();
}
return map;
}
Map _extractHoldingInfo() {
var map = <String, List>{};
void helper(CodeInfo info) {
if (info.uses.isEmpty) return;
map[info.serializedId] = info.uses.map((u) => u.toJson()).toList();
}
functions.forEach(helper);
fields.forEach(helper);
return map;
}
Map _extractDependencies() {
var map = <String, List>{};
dependencies.forEach((k, v) {
map[k.serializedId] = v.map((i) => i.serializedId).toList();
});
return map;
}
Map toJson() => {
'elements': {
'library': _listAsJsonMap(libraries),
'class': _listAsJsonMap(classes),
'function': _listAsJsonMap(functions),
'typedef': _listAsJsonMap(typedefs),
'field': _listAsJsonMap(fields),
'constant': _listAsJsonMap(constants),
},
'holding': _extractHoldingInfo(),
'dependencies': _extractDependencies(),
'outputUnits': outputUnits.map((u) => u.toJson()).toList(),
'dump_version': version,
'deferredFiles': deferredFiles,
'dump_minor_version': '$minorVersion',
// TODO(sigmund): change viewer to accept an int?
'program': program.toJson(),
};
void accept(InfoVisitor visitor) => visitor.visitAll(this);
}
class ProgramInfo {
int size;
String dart2jsVersion;
DateTime compilationMoment;
Duration compilationDuration;
// TODO(sigmund): use Duration.
int toJsonDuration;
int dumpInfoDuration;
bool noSuchMethodEnabled;
bool minified;
ProgramInfo(
{this.size,
this.dart2jsVersion,
this.compilationMoment,
this.compilationDuration,
this.toJsonDuration,
this.dumpInfoDuration,
this.noSuchMethodEnabled,
this.minified});
Map toJson() => {
'size': size,
'dart2jsVersion': dart2jsVersion,
'compilationMoment': '$compilationMoment',
'compilationDuration': '${compilationDuration}',
'toJsonDuration': toJsonDuration,
'dumpInfoDuration': '$dumpInfoDuration',
'noSuchMethodEnabled': noSuchMethodEnabled,
'minified': minified,
};
void accept(InfoVisitor visitor) => visitor.visitProgram(this);
}
// TODO(sigmund): add unit tests.
class _ParseHelper {
Map<String, Info> registry = {};
AllInfo parseAll(Map json) {
var result = new AllInfo();
var elements = json['elements'];
result.libraries.addAll(elements['library'].values.map(parseLibrary));
result.classes.addAll(elements['class'].values.map(parseClass));
result.functions.addAll(elements['function'].values.map(parseFunction));
result.fields.addAll(elements['field'].values.map(parseField));
result.typedefs.addAll(elements['typedef'].values.map(parseTypedef));
// TODO(sigmund): remove null check on next breaking version
var constants = elements['constant'];
if (constants != null) {
result.constants.addAll(constants.values.map(parseConstant));
}
var idMap = {};
for (var f in result.functions) {
idMap[f.serializedId] = f;
}
for (var f in result.fields) {
idMap[f.serializedId] = f;
}
json['holding'].forEach((k, deps) {
var src = idMap[k];
assert(src != null);
for (var dep in deps) {
var target = idMap[dep['id']];
assert(target != null);
src.uses.add(new DependencyInfo(target, dep['mask']));
}
});
json['dependencies']?.forEach((k, deps) {
result.dependencies[idMap[k]] = deps.map((d) => idMap[d]).toList();
});
result.program = parseProgram(json['program']);
// todo: version, etc
return result;
}
LibraryInfo parseLibrary(Map json) {
LibraryInfo result = parseId(json['id']);
result
..name = json['name']
..uri = Uri.parse(json['canonicalUri'])
..outputUnit = parseId(json['outputUnit'])
..size = json['size'];
for (var child in json['children'].map(parseId)) {
if (child is FunctionInfo) {
result.topLevelFunctions.add(child);
} else if (child is FieldInfo) {
result.topLevelVariables.add(child);
} else if (child is ClassInfo) {
result.classes.add(child);
} else {
assert(child is TypedefInfo);
result.typedefs.add(child);
}
}
return result;
}
ClassInfo parseClass(Map json) {
ClassInfo result = parseId(json['id']);
result
..name = json['name']
..parent = parseId(json['parent'])
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..isAbstract = json['modifiers']['abstract'] == true;
assert(result is ClassInfo);
for (var child in json['children'].map(parseId)) {
if (child is FunctionInfo) {
result.functions.add(child);
} else {
assert(child is FieldInfo);
result.fields.add(child);
}
}
return result;
}
FieldInfo parseField(Map json) {
FieldInfo result = parseId(json['id']);
return result
..name = json['name']
..parent = parseId(json['parent'])
..coverageId = json['coverageId']
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..type = json['type']
..inferredType = json['inferredType']
..code = json['code']
..isConst = json['const'] ?? false
..initializer = parseId(json['initializer'])
..closures = json['children'].map(parseId).toList();
}
ConstantInfo parseConstant(Map json) {
ConstantInfo result = parseId(json['id']);
return result
..code = json['code']
..size = json['size'];
}
TypedefInfo parseTypedef(Map json) {
TypedefInfo result = parseId(json['id']);
return result
..name = json['name']
..parent = parseId(json['parent'])
..type = json['type']
..size = 0;
}
ProgramInfo parseProgram(Map json) => new ProgramInfo()..size = json['size'];
FunctionInfo parseFunction(Map json) {
FunctionInfo result = parseId(json['id']);
return result
..name = json['name']
..parent = parseId(json['parent'])
..coverageId = json['coverageId']
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..type = json['type']
..returnType = json['returnType']
..inferredReturnType = json['inferredReturnType']
..parameters = json['parameters'].map(parseParameter).toList()
..code = json['code']
..sideEffects = json['sideEffects']
..modifiers = parseModifiers(json['modifiers'])
..closures = json['children'].map(parseId).toList();
}
ParameterInfo parseParameter(Map json) =>
new ParameterInfo(json['name'], json['type'], json['declaredType']);
FunctionModifiers parseModifiers(Map<String, bool> json) {
return new FunctionModifiers(
isStatic: json['static'] == true,
isConst: json['const'] == true,
isFactory: json['factory'] == true,
isExternal: json['external'] == true);
}
Info parseId(String serializedId) => registry.putIfAbsent(serializedId, () {
if (serializedId == null) {
return null;
} else if (serializedId.startsWith('function/')) {
return new FunctionInfo._(serializedId);
} else if (serializedId.startsWith('library/')) {
return new LibraryInfo._(serializedId);
} else if (serializedId.startsWith('class/')) {
return new ClassInfo._(serializedId);
} else if (serializedId.startsWith('field/')) {
return new FieldInfo._(serializedId);
} else if (serializedId.startsWith('constant/')) {
return new ConstantInfo._(serializedId);
} else if (serializedId.startsWith('typedef/')) {
return new TypedefInfo._(serializedId);
} else if (serializedId.startsWith('outputUnit/')) {
return new OutputUnitInfo._(serializedId);
}
assert(false);
});
}
/// Info associated with a library element.
class LibraryInfo extends BasicInfo {
/// Canonical uri that identifies the library.
Uri uri;
/// Top level functions defined within the library.
final List<FunctionInfo> topLevelFunctions = <FunctionInfo>[];
/// Top level fields defined within the library.
final List<FieldInfo> topLevelVariables = <FieldInfo>[];
/// Classes defined within the library.
final List<ClassInfo> classes = <ClassInfo>[];
/// Typedefs defined within the library.
final List<TypedefInfo> typedefs = <TypedefInfo>[];
static int _id = 0;
/// Whether there is any information recorded for this library.
bool get isEmpty =>
topLevelFunctions.isEmpty && topLevelVariables.isEmpty && classes.isEmpty;
LibraryInfo(String name, this.uri, OutputUnitInfo outputUnit, int size)
: super(InfoKind.library, _id++, name, outputUnit, size, null);
LibraryInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
'children': []
..addAll(topLevelFunctions.map((f) => f.serializedId))
..addAll(topLevelVariables.map((v) => v.serializedId))
..addAll(classes.map((c) => c.serializedId))
..addAll(typedefs.map((t) => t.serializedId)),
'canonicalUri': '$uri',
});
void accept(InfoVisitor visitor) => visitor.visitLibrary(this);
}
/// Information about an output unit. Normally there is just one for the entire
/// program unless the application uses deferred imports, in which case there
/// would be an additional output unit per deferred chunk.
class OutputUnitInfo extends BasicInfo {
static int _ids = 0;
OutputUnitInfo(String name, int size)
: super(InfoKind.outputUnit, _ids++, name, null, size, null);
OutputUnitInfo._(String serializedId) : super._fromId(serializedId);
void accept(InfoVisitor visitor) => visitor.visitOutput(this);
}
/// Information about a class element.
class ClassInfo extends BasicInfo {
/// Whether the class is abstract.
bool isAbstract;
// TODO(sigmund): split static vs instance vs closures
/// Functions (static or instance) defined in the class.
final List<FunctionInfo> functions = <FunctionInfo>[];
/// Fields defined in the class.
// TODO(sigmund): currently appears to only be populated with instance fields,
// but this should be fixed.
final List<FieldInfo> fields = <FieldInfo>[];
static int _ids = 0;
ClassInfo(
{String name, this.isAbstract, OutputUnitInfo outputUnit, int size: 0})
: super(InfoKind.clazz, _ids++, name, outputUnit, size, null);
ClassInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
// TODO(sigmund): change format, include only when abstract is true.
'modifiers': {'abstract': isAbstract},
'children': []
..addAll(fields.map((f) => f.serializedId))
..addAll(functions.map((m) => m.serializedId))
});
void accept(InfoVisitor visitor) => visitor.visitClass(this);
}
/// Information about a constant value.
// TODO(sigmund): add dependency data for ConstantInfo
class ConstantInfo extends BasicInfo {
/// The actual generated code for the field.
String code;
static int _ids = 0;
// TODO(sigmund): Add coverage support to constants?
ConstantInfo({int size: 0, this.code, OutputUnitInfo outputUnit})
: super(InfoKind.constant, _ids++, null, outputUnit, size, null);
ConstantInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()..addAll({'code': code});
void accept(InfoVisitor visitor) => visitor.visitConstant(this);
}
/// Information about a field element.
class FieldInfo extends BasicInfo with CodeInfo {
/// The type of the field.
String type;
/// The type inferred by dart2js's whole program analysis
String inferredType;
/// Nested closures seen in the field initializer.
List<FunctionInfo> closures;
/// The actual generated code for the field.
String code;
/// Whether this corresponds to a const field declaration.
bool isConst;
/// When [isConst] is true, the constant initializer expression.
ConstantInfo initializer;
static int _ids = 0;
FieldInfo(
{String name,
String coverageId,
int size: 0,
this.type,
this.inferredType,
this.closures,
this.code,
OutputUnitInfo outputUnit,
this.isConst})
: super(InfoKind.field, _ids++, name, outputUnit, size, coverageId);
FieldInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() {
var result = super.toJson()
..addAll({
'children': closures.map((i) => i.serializedId).toList(),
'inferredType': inferredType,
'code': code,
'type': type,
});
if (isConst) {
result['const'] = true;
if (initializer != null) result['initializer'] = initializer.serializedId;
}
return result;
}
void accept(InfoVisitor visitor) => visitor.visitField(this);
}
/// Information about a typedef declaration.
class TypedefInfo extends BasicInfo {
/// The declared type.
String type;
static int _ids = 0;
TypedefInfo(String name, this.type, OutputUnitInfo outputUnit)
: super(InfoKind.typedef, _ids++, name, outputUnit, 0, null);
TypedefInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()..['type'] = '$type';
void accept(InfoVisitor visitor) => visitor.visitTypedef(this);
}
/// Information about a function or method.
class FunctionInfo extends BasicInfo with CodeInfo {
static const int TOP_LEVEL_FUNCTION_KIND = 0;
static const int CLOSURE_FUNCTION_KIND = 1;
static const int METHOD_FUNCTION_KIND = 2;
static const int CONSTRUCTOR_FUNCTION_KIND = 3;
static int _ids = 0;
/// Kind of function (top-level function, closure, method, or constructor).
int functionKind;
/// Modifiers applied to this function.
FunctionModifiers modifiers;
/// Nested closures that appear within the body of this function.
List<FunctionInfo> closures;
/// The type of this function.
String type;
/// The declared return type.
String returnType;
/// The inferred return type.
String inferredReturnType;
/// Name and type information for each parameter.
List<ParameterInfo> parameters;
/// Side-effects.
// TODO(sigmund): serialize more precisely, not just a string representation.
String sideEffects;
/// How many function calls were inlined into this function.
int inlinedCount;
/// The actual generated code.
String code;
FunctionInfo(
{String name,
String coverageId,
OutputUnitInfo outputUnit,
int size: 0,
this.functionKind,
this.modifiers,
this.closures,
this.type,
this.returnType,
this.inferredReturnType,
this.parameters,
this.sideEffects,
this.inlinedCount,
this.code})
: super(InfoKind.function, _ids++, name, outputUnit, size, coverageId);
FunctionInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
'children': closures.map((i) => i.serializedId).toList(),
'modifiers': modifiers.toJson(),
'returnType': returnType,
'inferredReturnType': inferredReturnType,
'parameters': parameters.map((p) => p.toJson()).toList(),
'sideEffects': sideEffects,
'inlinedCount': inlinedCount,
'code': code,
'type': type,
// Note: version 3.2 of dump-info serializes `uses` in a section called
// `holding` at the top-level.
});
void accept(InfoVisitor visitor) => visitor.visitFunction(this);
}
/// Information about how a dependency is used.
class DependencyInfo {
/// The dependency, either a FunctionInfo or FieldInfo.
final Info target;
/// Either a selector mask indicating how this is used, or 'inlined'.
// TODO(sigmund): split mask into an enum or something more precise to really
// describe the dependencies in detail.
final String mask;
DependencyInfo(this.target, this.mask);
Map toJson() => {'id': target.serializedId, 'mask': mask};
}
/// Name and type information about a function parameter.
class ParameterInfo {
final String name;
final String type;
final String declaredType;
ParameterInfo(this.name, this.type, this.declaredType);
Map toJson() => {'name': name, 'type': type, 'declaredType': declaredType};
}
/// Modifiers that may apply to methods.
class FunctionModifiers {
final bool isStatic;
final bool isConst;
final bool isFactory;
final bool isExternal;
FunctionModifiers(
{this.isStatic: false,
this.isConst: false,
this.isFactory: false,
this.isExternal: false});
// TODO(sigmund): exclude false values (requires bumping the format version):
// Map toJson() {
// var res = <String, bool>{};
// if (isStatic) res['static'] = true;
// if (isConst) res['const'] = true;
// if (isFactory) res['factory'] = true;
// if (isExternal) res['external'] = true;
// return res;
// }
Map toJson() => {
'static': isStatic,
'const': isConst,
'factory': isFactory,
'external': isExternal,
};
}
/// Possible values of the `kind` field in the serialied infos.
enum InfoKind {
library,
clazz,
function,
field,
constant,
outputUnit,
typedef,
}
String _kindToString(InfoKind kind) {
switch (kind) {
case InfoKind.library:
return 'library';
case InfoKind.clazz:
return 'class';
case InfoKind.function:
return 'function';
case InfoKind.field:
return 'field';
case InfoKind.constant:
return 'constant';
case InfoKind.outputUnit:
return 'outputUnit';
case InfoKind.typedef:
return 'typedef';
default:
return null;
}
}
int _idFromSerializedId(String serializedId) =>
int.parse(serializedId.substring(serializedId.indexOf('/') + 1));
InfoKind _kindFromSerializedId(String serializedId) =>
_kindFromString(serializedId.substring(0, serializedId.indexOf('/')));
InfoKind _kindFromString(String kind) {
switch (kind) {
case 'library':
return InfoKind.library;
case 'class':
return InfoKind.clazz;
case 'function':
return InfoKind.function;
case 'field':
return InfoKind.field;
case 'constant':
return InfoKind.constant;
case 'outputUnit':
return InfoKind.outputUnit;
case 'typedef':
return InfoKind.typedef;
default:
return null;
}
}
/// A simple visitor for information produced by the dart2js compiler.
class InfoVisitor {
visitAll(AllInfo info) {}
visitProgram(ProgramInfo info) {}
visitLibrary(LibraryInfo info) {}
visitClass(ClassInfo info) {}
visitField(FieldInfo info) {}
visitConstant(ConstantInfo info) {}
visitFunction(FunctionInfo info) {}
visitTypedef(TypedefInfo info) {}
visitOutput(OutputUnitInfo info) {}
}
/// A visitor that recursively walks each portion of the program. Because the
/// info representation is redundant, this visitor only walks the structure of
/// the program and skips some redundant links. For example, even though
/// visitAll contains references to functions, this visitor only recurses to
/// visit libraries, then from each library we visit functions and classes, and
/// so on.
class RecursiveInfoVisitor extends InfoVisitor {
visitAll(AllInfo info) {
// Note: we don't visit functions, fields, classes, and typedefs because
// they are reachable from the library info.
info.libraries.forEach(visitLibrary);
info.constants.forEach(visitConstant);
}
visitLibrary(LibraryInfo info) {
info.topLevelFunctions.forEach(visitFunction);
info.topLevelVariables.forEach(visitField);
info.classes.forEach(visitClass);
info.typedefs.forEach(visitTypedef);
}
visitClass(ClassInfo info) {
info.functions.forEach(visitFunction);
info.fields.forEach(visitField);
}
visitField(FieldInfo info) {
info.closures.forEach(visitFunction);
}
visitFunction(FunctionInfo info) {
info.closures.forEach(visitFunction);
}
}