add a JsonInfoCodec class
This separates the serialization format from the Info objects themselves
and allows us to explore other serialization formats in the future.
BUG=
R=sigmund@google.com
Review URL: https://codereview.chromium.org//1411523003 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f374723..513b704 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
## 0.2.0
+- Added AllInfoJsonCodec
- Added `verify_deps` tool
## 0.1.0
diff --git a/README.md b/README.md
index c5d01bf..b2f53b7 100644
--- a/README.md
+++ b/README.md
@@ -37,8 +37,9 @@
## Info API
-[AllInfo][AllInfo] exposes a Dart representation of the `.info.json` files.
-You can parse the information using `AllInfo.fromJson`. For example:
+[AllInfo][AllInfo] exposes a Dart representation of all of the collected
+information. You can decode an `AllInfo` object from the JSON form produced by
+the `dart2js` `--dump-info` option using the `AllInfoJsonCodec`. For example:
```dart
import 'dart:convert';
@@ -49,7 +50,7 @@
main(args) {
var infoPath = args[0];
var json = JSON.decode(new File(infoPath).readAsStringSync());
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
...
}
```
diff --git a/bin/code_deps.dart b/bin/code_deps.dart
index 4395526..9899e8d 100644
--- a/bin/code_deps.dart
+++ b/bin/code_deps.dart
@@ -49,7 +49,7 @@
print('error: could not read ${args[0]}');
exit(1);
}
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
var graph = graphFromInfo(info);
var queryName = args[1];
diff --git a/bin/debug_info.dart b/bin/debug_info.dart
index b8c5025..1c34cca 100644
--- a/bin/debug_info.dart
+++ b/bin/debug_info.dart
@@ -21,7 +21,7 @@
var filename = args[0];
var json = JSON.decode(new File(filename).readAsStringSync());
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
var debugLibName;
if (args.length > 2 && args[1] == '--show-library') {
diff --git a/bin/function_size_analysis.dart b/bin/function_size_analysis.dart
index ee10def..0de3db4 100644
--- a/bin/function_size_analysis.dart
+++ b/bin/function_size_analysis.dart
@@ -16,7 +16,7 @@
main(args) {
var json = JSON.decode(new File(args[0]).readAsStringSync());
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
showCodeDistribution(info);
}
diff --git a/bin/inference/client.dart b/bin/inference/client.dart
index 0674653..e5b05d7 100644
--- a/bin/inference/client.dart
+++ b/bin/inference/client.dart
@@ -13,8 +13,8 @@
AllInfo data;
main() async {
- data =
- new AllInfo.fromJson(JSON.decode(await HttpRequest.getString('/data')));
+ data = new AllInfoJsonCodec()
+ .decode(JSON.decode(await HttpRequest.getString('/data')));
routeByHash();
window.onHashChange.listen((_) => routeByHash());
diff --git a/bin/inference/print_summary.dart b/bin/inference/print_summary.dart
index c2926e0..a0dbc0b 100644
--- a/bin/inference/print_summary.dart
+++ b/bin/inference/print_summary.dart
@@ -13,7 +13,7 @@
main(args) {
var file = args.length > 0 ? args[0] : 'out.js.info.json';
var json = JSON.decode(new File(file).readAsStringSync());
- var results = new AllInfo.fromJson(json);
+ var results = new AllInfoJsonCodec().decode(json);
print(formatAsTable(results));
}
diff --git a/bin/library_size_split.dart b/bin/library_size_split.dart
index 6228085..8dd419a 100644
--- a/bin/library_size_split.dart
+++ b/bin/library_size_split.dart
@@ -75,7 +75,7 @@
var filename = args[0];
var json = JSON.decode(new File(filename).readAsStringSync());
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
var groupingText =
args.length > 1 ? new File(args[1]).readAsStringSync() : defaultGrouping;
diff --git a/bin/live_code_size_analysis.dart b/bin/live_code_size_analysis.dart
index def29d5..333f5c4 100644
--- a/bin/live_code_size_analysis.dart
+++ b/bin/live_code_size_analysis.dart
@@ -49,7 +49,7 @@
}
var json = JSON.decode(new File(args[0]).readAsStringSync());
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
var coverage = JSON.decode(new File(args[1]).readAsStringSync());
var verbose = args.length > 2 && args[2] == '-v';
diff --git a/bin/verify_deps.dart b/bin/verify_deps.dart
index 3da8ece..61542c7 100644
--- a/bin/verify_deps.dart
+++ b/bin/verify_deps.dart
@@ -24,7 +24,7 @@
exit(1);
}
var json = JSON.decode(await new File(args[0]).readAsString());
- var info = new AllInfo.fromJson(json);
+ var info = new AllInfoJsonCodec().decode(json);
var graph = graphFromInfo(info);
var entrypoint = info.program.entrypoint;
var reachables = findReachable(graph, entrypoint);
diff --git a/lib/info.dart b/lib/info.dart
index 27e1fe7..5e79a70 100644
--- a/lib/info.dart
+++ b/lib/info.dart
@@ -5,9 +5,13 @@
/// Data produced by dart2js when run with the `--dump-info` flag.
library dart2js_info.info;
+import 'dart:convert';
+
import 'src/measurements.dart';
export 'src/measurements.dart';
+part 'json_info_codec.dart';
+
/// Common interface to many pieces of information generated by the dart2js
/// compiler that are directly associated with an element (compilation unit,
/// library, class, function, or field).
@@ -35,12 +39,7 @@
/// 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);
+ dynamic accept(InfoVisitor visitor);
}
/// Common information used for most kind of elements.
@@ -68,21 +67,6 @@
: 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]';
}
@@ -150,57 +134,7 @@
AllInfo();
- // TODO(het): Remove this when we have an external InfoCodec, see
- // https://github.com/dart-lang/dart2js_info/issues/4
- factory AllInfo.fromJson(Map json) => new _ParseHelper().parseAll(json);
-
- 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);
+ dynamic accept(InfoVisitor visitor) => visitor.visitAll(this);
}
class ProgramInfo {
@@ -226,216 +160,7 @@
this.noSuchMethodEnabled,
this.minified});
- Map toJson() => {
- 'entrypoint': entrypoint.serializedId,
- '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']
- ..entrypoint = parseId(json['entrypoint']);
-
- 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()
- ..measurements = parseMeasurements(json['measurements']);
- }
-
- ParameterInfo parseParameter(Map json) =>
- new ParameterInfo(json['name'], json['type'], json['declaredType']);
-
- Measurements parseMeasurements(Map json) {
- if (json == null) return null;
- var uri = json['sourceFile'];
- var res = new Measurements(uri == null ? null : Uri.parse(uri));
- for (var key in json.keys) {
- var value = json[key];
- if (value == null) continue;
- if (key == 'entries') {
- value.forEach((metricName, entries) {
- var metric = Metric.fromJson(metricName);
- for (var i = 0; i < entries.length; i += 2) {
- res.record(metric, entries[i], entries[i + 1]);
- }
- });
- } else {
- res.counters[Metric.fromJson(key)] = value;
- }
- }
- return res;
- }
-
- 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);
- });
+ dynamic accept(InfoVisitor visitor) => visitor.visitProgram(this);
}
/// Info associated with a library element.
@@ -470,17 +195,7 @@
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);
+ dynamic accept(InfoVisitor visitor) => visitor.visitLibrary(this);
}
/// Information about an output unit. Normally there is just one for the entire
@@ -493,7 +208,7 @@
OutputUnitInfo._(String serializedId) : super._fromId(serializedId);
- void accept(InfoVisitor visitor) => visitor.visitOutput(this);
+ dynamic accept(InfoVisitor visitor) => visitor.visitOutput(this);
}
/// Information about a class element.
@@ -517,16 +232,7 @@
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);
+ dynamic accept(InfoVisitor visitor) => visitor.visitClass(this);
}
/// Information about a constant value.
@@ -542,9 +248,7 @@
ConstantInfo._(String serializedId) : super._fromId(serializedId);
- Map toJson() => super.toJson()..addAll({'code': code});
-
- void accept(InfoVisitor visitor) => visitor.visitConstant(this);
+ dynamic accept(InfoVisitor visitor) => visitor.visitConstant(this);
}
/// Information about a field element.
@@ -582,22 +286,7 @@
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);
+ dynamic accept(InfoVisitor visitor) => visitor.visitField(this);
}
/// Information about a typedef declaration.
@@ -611,9 +300,7 @@
TypedefInfo._(String serializedId) : super._fromId(serializedId);
- Map toJson() => super.toJson()..['type'] = '$type';
-
- void accept(InfoVisitor visitor) => visitor.visitTypedef(this);
+ dynamic accept(InfoVisitor visitor) => visitor.visitTypedef(this);
}
/// Information about a function or method.
@@ -678,23 +365,7 @@
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,
- 'measurements': measurements?.toJson(),
- // 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);
+ dynamic accept(InfoVisitor visitor) => visitor.visitFunction(this);
}
/// Information about how a dependency is used.
@@ -708,8 +379,6 @@
final String mask;
DependencyInfo(this.target, this.mask);
-
- Map toJson() => {'id': target.serializedId, 'mask': mask};
}
/// Name and type information about a function parameter.
@@ -719,8 +388,6 @@
final String declaredType;
ParameterInfo(this.name, this.type, this.declaredType);
-
- Map toJson() => {'name': name, 'type': type, 'declaredType': declaredType};
}
/// Modifiers that may apply to methods.
@@ -735,22 +402,6 @@
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.
@@ -813,16 +464,16 @@
}
/// 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) {}
+abstract class InfoVisitor<T> {
+ T visitAll(AllInfo info);
+ T visitProgram(ProgramInfo info);
+ T visitLibrary(LibraryInfo info);
+ T visitClass(ClassInfo info);
+ T visitField(FieldInfo info);
+ T visitConstant(ConstantInfo info);
+ T visitFunction(FunctionInfo info);
+ T visitTypedef(TypedefInfo info);
+ T visitOutput(OutputUnitInfo info);
}
/// A visitor that recursively walks each portion of the program. Because the
@@ -831,7 +482,7 @@
/// 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 {
+class RecursiveInfoVisitor extends InfoVisitor<Null> {
visitAll(AllInfo info) {
// Note: we don't visit functions, fields, classes, and typedefs because
// they are reachable from the library info.
@@ -839,6 +490,8 @@
info.constants.forEach(visitConstant);
}
+ visitProgram(ProgramInfo info) {}
+
visitLibrary(LibraryInfo info) {
info.topLevelFunctions.forEach(visitFunction);
info.topLevelVariables.forEach(visitField);
@@ -855,7 +508,12 @@
info.closures.forEach(visitFunction);
}
+ visitConstant(ConstantInfo info) {}
+
visitFunction(FunctionInfo info) {
info.closures.forEach(visitFunction);
}
+
+ visitTypedef(TypedefInfo info) {}
+ visitOutput(OutputUnitInfo info) {}
}
diff --git a/lib/json_info_codec.dart b/lib/json_info_codec.dart
new file mode 100644
index 0000000..1293d30
--- /dev/null
+++ b/lib/json_info_codec.dart
@@ -0,0 +1,415 @@
+// 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.
+
+/// Converters and codecs for converting between JSON and [Info] classes.
+part of dart2js_info.info;
+
+// TODO(sigmund): add unit tests.
+class JsonToAllInfoConverter extends Converter<Map, AllInfo> {
+ Map<String, Info> registry;
+
+ AllInfo convert(Map json) {
+ registry = <String, Info>{};
+
+ 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']
+ ..entrypoint = parseId(json['entrypoint']);
+
+ 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()
+ ..measurements = parseMeasurements(json['measurements']);
+ }
+
+ ParameterInfo parseParameter(Map json) =>
+ new ParameterInfo(json['name'], json['type'], json['declaredType']);
+
+ Measurements parseMeasurements(Map json) {
+ if (json == null) return null;
+ var uri = json['sourceFile'];
+ var res = new Measurements(uri == null ? null : Uri.parse(uri));
+ for (var key in json.keys) {
+ var value = json[key];
+ if (value == null) continue;
+ if (key == 'entries') {
+ value.forEach((metricName, entries) {
+ var metric = new Metric.fromName(metricName);
+ for (var i = 0; i < entries.length; i += 2) {
+ res.record(metric, entries[i], entries[i + 1]);
+ }
+ });
+ } else {
+ res.counters[new Metric.fromName(key)] = value;
+ }
+ }
+ return res;
+ }
+
+ 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);
+ });
+}
+
+class AllInfoToJsonConverter extends Converter<AllInfo, Map>
+ implements InfoVisitor<Map> {
+ Map convert(AllInfo info) => info.accept(this);
+
+ Map _visitList(List<Info> infos) {
+ var map = <String, Map>{};
+ for (var info in infos) {
+ map['${info.id}'] = info.accept(this);
+ }
+ return map;
+ }
+
+ Map _visitAllInfoElements(AllInfo info) {
+ var jsonLibraries = _visitList(info.libraries);
+ var jsonClasses = _visitList(info.classes);
+ var jsonFunctions = _visitList(info.functions);
+ var jsonTypedefs = _visitList(info.typedefs);
+ var jsonFields = _visitList(info.fields);
+ var jsonConstants = _visitList(info.constants);
+ return {
+ 'library': jsonLibraries,
+ 'class': jsonClasses,
+ 'function': jsonFunctions,
+ 'typedef': jsonTypedefs,
+ 'field': jsonFields,
+ 'constant': jsonConstants
+ };
+ }
+
+ Map _visitDependencyInfo(DependencyInfo info) =>
+ {'id': info.target.serializedId, 'mask': info.mask};
+
+ Map _visitAllInfoHolding(AllInfo allInfo) {
+ var map = <String, List>{};
+ void helper(CodeInfo info) {
+ if (info.uses.isEmpty) return;
+ map[info.serializedId] =
+ info.uses.map((u) => _visitDependencyInfo(u)).toList();
+ }
+ allInfo.functions.forEach(helper);
+ allInfo.fields.forEach(helper);
+ return map;
+ }
+
+ Map _visitAllInfoDependencies(AllInfo allInfo) {
+ var map = <String, List>{};
+ allInfo.dependencies.forEach((k, v) {
+ map[k.serializedId] = v.map((i) => i.serializedId).toList();
+ });
+ return map;
+ }
+
+ Map visitAll(AllInfo info) {
+ var elements = _visitAllInfoElements(info);
+ var jsonHolding = _visitAllInfoHolding(info);
+ var jsonDependencies = _visitAllInfoDependencies(info);
+ return {
+ 'elements': elements,
+ 'holding': jsonHolding,
+ 'dependencies': jsonDependencies,
+ 'outputUnits': info.outputUnits.map((u) => u.accept(this)).toList(),
+ 'dump_version': info.version,
+ 'deferredFiles': info.deferredFiles,
+ 'dump_minor_version': '${info.minorVersion}',
+ 'program': info.program.accept(this)
+ };
+ }
+
+ Map visitProgram(ProgramInfo info) {
+ return {
+ 'entrypoint': info.entrypoint.serializedId,
+ 'size': info.size,
+ 'dart2jsVersion': info.dart2jsVersion,
+ 'compilationMoment': '${info.compilationMoment}',
+ 'compilationDuration': '${info.compilationDuration}',
+ 'toJsonDuration': info.toJsonDuration,
+ 'dumpInfoDuration': '${info.dumpInfoDuration}',
+ 'noSuchMethodEnabled': info.noSuchMethodEnabled,
+ 'minified': info.minified,
+ };
+ }
+
+ Map _visitBasicInfo(BasicInfo info) {
+ var res = {
+ 'id': info.serializedId,
+ 'kind': _kindToString(info.kind),
+ 'name': info.name,
+ 'size': info.size,
+ };
+ // TODO(sigmund): Omit this also when outputUnit.id == 0 (most code is in
+ // the main output unit by default).
+ if (info.outputUnit != null) res['outputUnit'] =
+ info.outputUnit.serializedId;
+ if (info.coverageId != null) res['coverageId'] = info.coverageId;
+ if (info.parent != null) res['parent'] = info.parent.serializedId;
+ return res;
+ }
+
+ Map visitLibrary(LibraryInfo info) {
+ return _visitBasicInfo(info)
+ ..addAll({
+ 'children': []
+ ..addAll(info.topLevelFunctions.map((f) => f.serializedId))
+ ..addAll(info.topLevelVariables.map((v) => v.serializedId))
+ ..addAll(info.classes.map((c) => c.serializedId))
+ ..addAll(info.typedefs.map((t) => t.serializedId)),
+ 'canonicalUri': '${info.uri}',
+ });
+ }
+
+ Map visitClass(ClassInfo info) {
+ return _visitBasicInfo(info)
+ ..addAll({
+ // TODO(sigmund): change format, include only when abstract is true.
+ 'modifiers': {'abstract': info.isAbstract},
+ 'children': []
+ ..addAll(info.fields.map((f) => f.serializedId))
+ ..addAll(info.functions.map((m) => m.serializedId))
+ });
+ }
+
+ Map visitField(FieldInfo info) {
+ var result = _visitBasicInfo(info)
+ ..addAll({
+ 'children': info.closures.map((i) => i.serializedId).toList(),
+ 'inferredType': info.inferredType,
+ 'code': info.code,
+ 'type': info.type,
+ });
+ if (info.isConst) {
+ result['const'] = true;
+ if (info.initializer != null) result['initializer'] =
+ info.initializer.serializedId;
+ }
+ return result;
+ }
+
+ Map visitConstant(ConstantInfo info) =>
+ _visitBasicInfo(info)..addAll({'code': info.code});
+
+ // TODO(sigmund): exclude false values (requires bumping the format version):
+ // 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 _visitFunctionModifiers(FunctionModifiers mods) => {
+ 'static': mods.isStatic,
+ 'const': mods.isConst,
+ 'factory': mods.isFactory,
+ 'external': mods.isExternal,
+ };
+
+ Map _visitParameterInfo(ParameterInfo info) =>
+ {'name': info.name, 'type': info.type, 'declaredType': info.declaredType};
+
+ String _visitMetric(Metric metric) => metric.name;
+
+ Map _visitMeasurements(Measurements measurements) {
+ if (measurements == null) return null;
+ var jsonEntries = <String, List<Map>>{};
+ measurements.entries.forEach((metric, values) {
+ jsonEntries[_visitMetric(metric)] =
+ values.expand((e) => [e.begin, e.end]).toList();
+ });
+ var json = {'entries': jsonEntries};
+ // TODO(sigmund): encode uri as an offset of the URIs available in the parts
+ // of the library info.
+ if (measurements.uri != null) json['sourceFile'] = '${measurements.uri}';
+ if (measurements.counters[Metric.functions] != null) {
+ json[_visitMetric(Metric.functions)] =
+ measurements.counters[Metric.functions];
+ }
+ if (measurements.counters[Metric.reachableFunctions] != null) {
+ json[_visitMetric(Metric.reachableFunctions)] =
+ measurements.counters[Metric.reachableFunctions];
+ }
+ return json;
+ }
+
+ Map visitFunction(FunctionInfo info) {
+ return _visitBasicInfo(info)
+ ..addAll({
+ 'children': info.closures.map((i) => i.serializedId).toList(),
+ 'modifiers': _visitFunctionModifiers(info.modifiers),
+ 'returnType': info.returnType,
+ 'inferredReturnType': info.inferredReturnType,
+ 'parameters':
+ info.parameters.map((p) => _visitParameterInfo(p)).toList(),
+ 'sideEffects': info.sideEffects,
+ 'inlinedCount': info.inlinedCount,
+ 'code': info.code,
+ 'type': info.type,
+ 'measurements': _visitMeasurements(info.measurements),
+ // Note: version 3.2 of dump-info serializes `uses` in a section called
+ // `holding` at the top-level.
+ });
+ }
+
+ visitTypedef(TypedefInfo info) => _visitBasicInfo(info)..['type'] = info.type;
+
+ visitOutput(OutputUnitInfo info) => _visitBasicInfo(info);
+}
+
+class AllInfoJsonCodec extends Codec<AllInfo, Map> {
+ final Converter<AllInfo, Map> encoder = new AllInfoToJsonConverter();
+ final Converter<Map, AllInfo> decoder = new JsonToAllInfoConverter();
+}
diff --git a/lib/src/measurements.dart b/lib/src/measurements.dart
index 499f001..b919678 100644
--- a/lib/src/measurements.dart
+++ b/lib/src/measurements.dart
@@ -7,10 +7,7 @@
library dart2js_info.src.measurements;
/// Top-level set of metrics
-const List<Metric> _topLevelMetrics = const [
- Metric.functions,
- Metric.send,
-];
+const List<Metric> _topLevelMetrics = const [Metric.functions, Metric.send];
/// Apply `f` on each metric in DFS order on the metric tree. [Metric.functions]
/// and [Metric.send] are the top level metrics. See those declarations for
@@ -35,13 +32,14 @@
const Metric(this.name);
+ factory Metric.fromName(String name) => _nameToMetricMap[name];
+
String toString() => name;
/// Total functions in a library/package/program. Parent of
/// [reachableFunction].
- static const Metric functions = const GroupedMetric('functions', const [
- reachableFunctions,
- ]);
+ static const Metric functions =
+ const GroupedMetric('functions', const [reachableFunctions]);
/// Subset of the functions that are reachable.
static const Metric reachableFunctions = const Metric('reachable functions');
@@ -68,24 +66,22 @@
/// |- multi-interceptor (1 of n possible interceptors)
/// '- dynamic (any combination of the above)
///
- static const Metric send = const GroupedMetric('send', const [
- monomorphicSend,
- polymorphicSend,
- ]);
+ static const Metric send =
+ const GroupedMetric('send', const [monomorphicSend, polymorphicSend]);
/// Parent of monomorphic sends, see [send] for details.
- static const Metric monomorphicSend = const GroupedMetric('monomorphic',
- const [
- staticSend,
- superSend,
- localSend,
- constructorSend,
- typeVariableSend,
- nsmErrorSend,
- singleNsmCallSend,
- instanceSend,
- interceptorSend,
- ]);
+ static const Metric monomorphicSend =
+ const GroupedMetric('monomorphic', const [
+ staticSend,
+ superSend,
+ localSend,
+ constructorSend,
+ typeVariableSend,
+ nsmErrorSend,
+ singleNsmCallSend,
+ instanceSend,
+ interceptorSend
+ ]);
/// Metric for static calls, see [send] for details.
static const Metric staticSend = const Metric('static');
@@ -120,12 +116,7 @@
/// Parent of polymorphic sends, see [send] for details.
static const Metric polymorphicSend = const GroupedMetric('polymorphic',
- const [
- multiNsmCallSend,
- virtualSend,
- multiInterceptorSend,
- dynamicSend,
- ]);
+ const [multiNsmCallSend, virtualSend, multiInterceptorSend, dynamicSend]);
/// Metric for calls to noSuchMethod methods with more than one possible
/// target, see [send] for details.
@@ -143,14 +134,11 @@
/// method. See [send] for details.
static const Metric dynamicSend = const Metric('dynamic');
- String toJson() => name;
static Map<String, Metric> _nameToMetricMap = () {
var res = {};
visitAllMetrics((m, _) => res[m.name] = m);
return res;
}();
-
- static Metric fromJson(String name) => _nameToMetricMap[name];
}
/// A metric that is subdivided in smaller metrics.
@@ -180,10 +168,12 @@
counters = <Metric, int>{};
const Measurements.unreachableFunction()
- : counters = const { Metric.functions: 1}, entries = const {}, uri = null;
+ : counters = const {Metric.functions: 1},
+ entries = const {},
+ uri = null;
Measurements.reachableFunction([this.uri])
- : counters = { Metric.functions: 1, Metric.reachableFunctions: 1},
+ : counters = {Metric.functions: 1, Metric.reachableFunctions: 1},
entries = {};
/// Record [metric] was seen. The optional [begin] and [end] offsets are
@@ -228,24 +218,4 @@
}
return total == submetricTotal;
}
-
- Map toJson() {
- var jsonEntries = <String, List<Map>>{};
- entries.forEach((metric, values) {
- jsonEntries[metric.toJson()] =
- values.expand((e) => [e.begin, e.end]).toList();
- });
- var json = {'entries': jsonEntries};
- // TODO(sigmund): encode uri as an offset of the URIs available in the parts
- // of the library info.
- if (uri != null) json['sourceFile'] = '$uri';
- if (counters[Metric.functions] != null) {
- json[Metric.functions.toJson()] = counters[Metric.functions];
- }
- if (counters[Metric.reachableFunctions] != null) {
- json[Metric.reachableFunctions.toJson()] =
- counters[Metric.reachableFunctions];
- }
- return json;
- }
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 082d211..ff85bc1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: dart2js_info
-version: 0.2.0-dev
+version: 0.2.0
description: >
Libraries and tools to process data produced when running dart2js with
--dump-info.
diff --git a/test/all_test.dart b/test/all_test.dart
index 40cdf2e..3dc5546 100644
--- a/test/all_test.dart
+++ b/test/all_test.dart
@@ -21,7 +21,7 @@
'program': {'size': 10},
};
- expect(new AllInfo.fromJson(json).program.size, 10);
+ expect(new AllInfoJsonCodec().decode(json).program.size, 10);
});
});
}