mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
dart2js: switch to use dart2js_info/info.dart
R=het@google.com Review URL: https://codereview.chromium.org//1298553002 .
This commit is contained in:
parent
07b6dad1f5
commit
8896ccd385
3
DEPS
3
DEPS
|
@ -49,6 +49,7 @@ vars = {
|
|||
"collection_rev": "@1da9a07f32efa2ba0c391b289e2037391e31da0e",
|
||||
"crypto_rev" : "@2df57a1e26dd88e8d0614207d4b062c73209917d",
|
||||
"csslib_tag" : "@0.12.0",
|
||||
"dart2js_info_rev" : "@5902be71c24bbda96675f646ba7d3ff911d26eae",
|
||||
"dartdoc_tag" : "@v0.5.0",
|
||||
"dart_services_rev" : "@7aea2574e6f3924bf409a80afb8ad52aa2be4f97",
|
||||
"dart_style_tag": "@0.2.0",
|
||||
|
@ -195,6 +196,8 @@ deps = {
|
|||
(Var("github_mirror") % "csslib") + Var("csslib_tag"),
|
||||
Var("dart_root") + "/third_party/pkg_tested/dart_style":
|
||||
(Var("github_mirror") % "dart_style") + Var("dart_style_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/dart2js_info":
|
||||
(Var("github_mirror") % "dart2js_info") + Var("dart2js_info_rev"),
|
||||
Var("dart_root") + "/third_party/pkg/dartdoc":
|
||||
(Var("github_mirror") % "dartdoc") + Var("dartdoc_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/dev_compiler":
|
||||
|
|
|
@ -7,6 +7,8 @@ library dump_info;
|
|||
import 'dart:convert'
|
||||
show HtmlEscape, JsonEncoder, StringConversionSink, ChunkedConversionSink;
|
||||
|
||||
import 'package:dart2js_info/info.dart';
|
||||
|
||||
import 'common/tasks.dart' show
|
||||
CompilerTask;
|
||||
import 'compiler.dart' show
|
||||
|
@ -21,7 +23,6 @@ import 'types/types.dart' show
|
|||
TypeMask;
|
||||
import 'deferred_load.dart' show
|
||||
OutputUnit;
|
||||
import 'info/info.dart';
|
||||
import 'js_backend/js_backend.dart' show
|
||||
JavaScriptBackend;
|
||||
import 'js_emitter/full_emitter/emitter.dart' as full show
|
||||
|
|
|
@ -1,745 +0,0 @@
|
|||
// 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 collected by the dump-info task.
|
||||
library compiler.src.lib.info;
|
||||
|
||||
// Note: this file intentionally doesn't import anything from the compiler. That
|
||||
// should make it easier for tools to depend on this library. The idea is that
|
||||
// by using this library, tools can consume the information in the same way it
|
||||
// is produced by the compiler.
|
||||
// TODO(sigmund): make this a proper public API (export this explicitly at the
|
||||
// lib folder level.)
|
||||
|
||||
/// Common interface to many pieces of information generated by the 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 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),
|
||||
},
|
||||
'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));
|
||||
|
||||
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']
|
||||
..closures = json['children'].map(parseId).toList();
|
||||
}
|
||||
|
||||
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('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 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;
|
||||
|
||||
static int _ids = 0;
|
||||
FieldInfo(
|
||||
{String name,
|
||||
String coverageId,
|
||||
int size: 0,
|
||||
this.type,
|
||||
this.inferredType,
|
||||
this.closures,
|
||||
this.code,
|
||||
OutputUnitInfo outputUnit})
|
||||
: super(InfoKind.field, _ids++, name, outputUnit, size, coverageId);
|
||||
|
||||
FieldInfo._(String serializedId) : super._fromId(serializedId);
|
||||
|
||||
Map toJson() => super.toJson()
|
||||
..addAll({
|
||||
'children': closures.map((i) => i.serializedId).toList(),
|
||||
'inferredType': inferredType,
|
||||
'code': code,
|
||||
'type': type,
|
||||
});
|
||||
|
||||
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,
|
||||
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.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 '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) {}
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -10,11 +10,8 @@ dependencies:
|
|||
path: ../../sdk/lib/_internal/js_runtime
|
||||
sdk_library_metadata:
|
||||
path: ../../sdk/lib/_internal/sdk_library_metadata
|
||||
dev_dependencies:
|
||||
path: ^1.3.6
|
||||
args: ^0.13.2
|
||||
shelf: ^0.6.1+2
|
||||
yaml: ^2.1.3
|
||||
dart2js_info: ^0.0.1
|
||||
|
||||
|
||||
# Uncomment if running gclient, so you can depend directly on the downloaded
|
||||
# versions of dart2js's transitive dependencies:
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
// 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.
|
||||
|
||||
/// Command-line tool to query for code dependencies.
|
||||
library compiler.tool.code_deps;
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:compiler/src/info/info.dart';
|
||||
import 'graph.dart';
|
||||
import 'util.dart';
|
||||
|
||||
main(args) {
|
||||
if (args.length < 2) {
|
||||
print('usage: dart tool/code_deps.dart path-to-info.json <query>');
|
||||
print(' where <query> can be:');
|
||||
print(' - some_path elementA elementB');
|
||||
// TODO(sigmund): add other queries, such as 'all_paths'.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var json = JSON.decode(new File(args[0]).readAsStringSync());
|
||||
var info = AllInfo.parseFromJson(json);
|
||||
var graph = graphFromInfo(info);
|
||||
|
||||
var queryName = args[1];
|
||||
if (queryName == 'some_path') {
|
||||
if (args.length < 4) {
|
||||
print('missing arguments for `some_path`');
|
||||
exit(1);
|
||||
}
|
||||
var source =
|
||||
info.functions.firstWhere(_longNameMatcher(new RegExp(args[2])),
|
||||
orElse: () => null);
|
||||
var target =
|
||||
info.functions.firstWhere(_longNameMatcher(new RegExp(args[3])),
|
||||
orElse: () => null);
|
||||
print('query: some_path');
|
||||
if (source == null) {
|
||||
print("source '${args[2]}' not found in '${args[0]}'");
|
||||
exit(1);
|
||||
}
|
||||
print('source: ${longName(source)}');
|
||||
if (target == null) {
|
||||
print("target '${args[3]}' not found in '${args[0]}'");
|
||||
exit(1);
|
||||
}
|
||||
print('target: ${longName(target)}');
|
||||
var path = new SomePathQuery(source, target).run(graph);
|
||||
if (path.isEmpty) {
|
||||
print('result: no path found');
|
||||
} else {
|
||||
print('result:');
|
||||
for (int i = 0; i < path.length; i++) {
|
||||
print(' $i. ${longName(path[i])}');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print('unrecognized query: $queryName');
|
||||
}
|
||||
}
|
||||
|
||||
/// A query supported by this tool.
|
||||
abstract class Query {
|
||||
run(Graph<Info> graph);
|
||||
}
|
||||
|
||||
/// Query that searches for a single path between two elements.
|
||||
class SomePathQuery {
|
||||
/// The info associated with the source element.
|
||||
Info source;
|
||||
|
||||
/// The info associated with the target element.
|
||||
Info target;
|
||||
|
||||
SomePathQuery(this.source, this.target);
|
||||
|
||||
List<Info> run(Graph<Info> graph) {
|
||||
var seen = {source: null};
|
||||
var queue = new Queue();
|
||||
queue.addLast(source);
|
||||
while (queue.isNotEmpty) {
|
||||
var node = queue.removeFirst();
|
||||
if (identical(node, target)) {
|
||||
var result = new Queue();
|
||||
while (node != null) {
|
||||
result.addFirst(node);
|
||||
node = seen[node];
|
||||
}
|
||||
return result.toList();
|
||||
}
|
||||
for (var neighbor in graph.targetsOf(node)) {
|
||||
if (seen.containsKey(neighbor)) continue;
|
||||
seen[neighbor] = node;
|
||||
queue.addLast(neighbor);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
_longNameMatcher(RegExp regexp) => (e) => regexp.hasMatch(longName(e));
|
|
@ -1,190 +0,0 @@
|
|||
// 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.
|
||||
|
||||
/// A tool to gather coverage data from an app generated with dart2js.
|
||||
/// This tool starts a server that answers to mainly 2 requests:
|
||||
/// * a GET request to retrieve the application
|
||||
/// * POST requests to record coverage data.
|
||||
///
|
||||
/// It is intended to be used as follows:
|
||||
/// * generate an app by running dart2js with the environment boolean
|
||||
/// -DinstrumentForCoverage=true provided to the vm, and the --dump-info
|
||||
/// flag provided to dart2js.
|
||||
/// * start this server, and proxy requests from your normal frontend
|
||||
/// server to this one.
|
||||
library compiler.tool.coverage_log_server;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:args/args.dart';
|
||||
import 'package:shelf/shelf.dart' as shelf;
|
||||
import 'package:shelf/shelf_io.dart' as shelf;
|
||||
|
||||
const _DEFAULT_OUT_TEMPLATE = '<dart2js-out-file>.coverage.json';
|
||||
|
||||
main(argv) async {
|
||||
var parser = new ArgParser()
|
||||
..addOption('port', abbr: 'p', help: 'port number', defaultsTo: "8080")
|
||||
..addOption('host', help: 'host name (use 0.0.0.0 for all interfaces)',
|
||||
defaultsTo: 'localhost')
|
||||
..addFlag('help', abbr: 'h', help: 'show this help message',
|
||||
negatable: false)
|
||||
..addOption('uri-prefix',
|
||||
help: 'uri path prefix that will hit this server. This will be injected'
|
||||
' into the .js file',
|
||||
defaultsTo: '')
|
||||
..addOption('out', abbr: 'o', help: 'output log file',
|
||||
defaultsTo: _DEFAULT_OUT_TEMPLATE);
|
||||
var args = parser.parse(argv);
|
||||
if (args['help'] == true || args.rest.isEmpty) {
|
||||
print('usage: dart coverage_logging.dart [options] '
|
||||
'<dart2js-out-file> [<html-file>]');
|
||||
print(parser.usage);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var jsPath = args.rest[0];
|
||||
var htmlPath = null;
|
||||
if (args.rest.length > 1) {
|
||||
htmlPath = args.rest[1];
|
||||
}
|
||||
var outPath = args['out'];
|
||||
if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json';
|
||||
var server = new _Server(args['host'], int.parse(args['port']), jsPath,
|
||||
htmlPath, outPath, args['uri-prefix']);
|
||||
await server.run();
|
||||
}
|
||||
|
||||
class _Server {
|
||||
/// Server hostname, typically `localhost`, but can be `0.0.0.0`.
|
||||
final String hostname;
|
||||
|
||||
/// Port the server will listen to.
|
||||
final int port;
|
||||
|
||||
/// JS file (previously generated by dart2js) to serve.
|
||||
final String jsPath;
|
||||
|
||||
/// HTML file to serve, if any.
|
||||
final String htmlPath;
|
||||
|
||||
/// Contents of jsPath, adjusted to use the appropriate server url.
|
||||
String jsCode;
|
||||
|
||||
/// Location where we'll dump the coverage data.
|
||||
final String outPath;
|
||||
|
||||
/// Uri prefix used on all requests to this server. This will be injected into
|
||||
/// the .js file.
|
||||
final String prefix;
|
||||
|
||||
// TODO(sigmund): add support to load also simple HTML files to test small
|
||||
// simple apps.
|
||||
|
||||
/// Data received so far. The data is just an array of pairs, showing the
|
||||
/// hashCode and name of the element used. This can be later cross-checked
|
||||
/// against dump-info data.
|
||||
Map data = {};
|
||||
|
||||
String get _serializedData => new JsonEncoder.withIndent(' ').convert(data);
|
||||
|
||||
_Server(this.hostname, this.port, String jsPath, this.htmlPath,
|
||||
this.outPath, String prefix)
|
||||
: jsPath = jsPath,
|
||||
jsCode = _adjustRequestUrl(new File(jsPath).readAsStringSync(), prefix),
|
||||
prefix = _normalize(prefix);
|
||||
|
||||
run() async {
|
||||
await shelf.serve(_handler, hostname, port);
|
||||
var urlBase = "http://$hostname:$port${prefix == '' ? '/' : '/$prefix/'}";
|
||||
var htmlFilename = htmlPath == null ? '' : path.basename(htmlPath);
|
||||
print("Server is listening\n"
|
||||
" - html page: $urlBase$htmlFilename\n"
|
||||
" - js code: $urlBase${path.basename(jsPath)}\n"
|
||||
" - coverage reporting: ${urlBase}coverage\n");
|
||||
}
|
||||
|
||||
_expectedPath(String tail) => prefix == '' ? tail : '$prefix/$tail';
|
||||
|
||||
_handler(shelf.Request request) async {
|
||||
var urlPath = request.url.path;
|
||||
print('received request: $urlPath');
|
||||
var baseJsName = path.basename(jsPath);
|
||||
var baseHtmlName = htmlPath == null ? '' : path.basename(htmlPath);
|
||||
|
||||
// Serve an HTML file at the default prefix, or a path matching the HTML
|
||||
// file name
|
||||
if (urlPath == prefix || urlPath == '$prefix/'
|
||||
|| urlPath == _expectedPath(baseHtmlName)) {
|
||||
var contents = htmlPath == null
|
||||
? '<html><script src="$baseJsName"></script>'
|
||||
: await new File(htmlPath).readAsString();
|
||||
return new shelf.Response.ok(contents, headers: HTML_HEADERS);
|
||||
}
|
||||
|
||||
if (urlPath == _expectedPath(baseJsName)) {
|
||||
return new shelf.Response.ok(jsCode, headers: JS_HEADERS);
|
||||
}
|
||||
|
||||
// Handle POST requests to record coverage data, and GET requests to display
|
||||
// the currently coverage resutls.
|
||||
if (urlPath == _expectedPath('coverage')) {
|
||||
if (request.method == 'GET') {
|
||||
return new shelf.Response.ok(_serializedData, headers: TEXT_HEADERS);
|
||||
}
|
||||
|
||||
if (request.method == 'POST') {
|
||||
_record(JSON.decode(await request.readAsString()));
|
||||
return new shelf.Response.ok("Thanks!");
|
||||
}
|
||||
}
|
||||
|
||||
// Any other request is not supported.
|
||||
return new shelf.Response.notFound('Not found: "$urlPath"');
|
||||
}
|
||||
|
||||
_record(List entries) {
|
||||
for (var entry in entries) {
|
||||
var id = entry[0];
|
||||
data.putIfAbsent('$id', () => {'name': entry[1], 'count': 0});
|
||||
data['$id']['count']++;
|
||||
}
|
||||
_enqueueSave();
|
||||
}
|
||||
|
||||
bool _savePending = false;
|
||||
int _total = 0;
|
||||
_enqueueSave() async {
|
||||
if (!_savePending) {
|
||||
_savePending = true;
|
||||
await new Future.delayed(new Duration(seconds: 3));
|
||||
await new File(outPath).writeAsString(_serializedData);
|
||||
var diff = data.length - _total;
|
||||
print(diff ? ' - no new element covered'
|
||||
: ' - $diff new elements covered');
|
||||
_savePending = false;
|
||||
_total = data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes leading and trailing slashes of [uriPath].
|
||||
_normalize(String uriPath) {
|
||||
if (uriPath.startsWith('/')) uriPath = uriPath.substring(1);
|
||||
if (uriPath.endsWith('/')) uriPath = uriPath.substring(0, uriPath.length - 1);
|
||||
return uriPath;
|
||||
}
|
||||
|
||||
_adjustRequestUrl(String code, String prefix) {
|
||||
var newUrl = prefix == '' ? 'coverage' : '$prefix/coverage';
|
||||
return code.replaceFirst(
|
||||
'"/coverage_uri_to_amend_by_server"',
|
||||
'"/$newUrl" /*url-prefix updated!*/');
|
||||
}
|
||||
|
||||
const HTML_HEADERS = const {'content-type': 'text/html'};
|
||||
const JS_HEADERS = const {'content-type': 'text/javascript'};
|
||||
const TEXT_HEADERS = const {'content-type': 'text/plain'};
|
|
@ -1,292 +0,0 @@
|
|||
// 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.
|
||||
|
||||
/// A library to work with graphs. It contains a couple algorithms, including
|
||||
/// Tarjan's algorithm to compute strongest connected components in a graph and
|
||||
/// Cooper et al's dominator algorithm.
|
||||
///
|
||||
/// Portions of the code in this library was adapted from
|
||||
/// `package:analyzer/src/generated/collection_utilities.dart`.
|
||||
// TODO(sigmund): move this into a shared place?
|
||||
library compiler.tool.graph;
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
abstract class Graph<N> {
|
||||
Iterable<N> get nodes;
|
||||
bool get isEmpty;
|
||||
int get nodeCount;
|
||||
Iterable<N> targetsOf(N source);
|
||||
Iterable<N> sourcesOf(N source);
|
||||
|
||||
/// Run a topological sort of the graph. Since the graph may contain cycles,
|
||||
/// this results in a list of strongly connected components rather than a list
|
||||
/// of nodes. The nodes in each strongly connected components only have edges
|
||||
/// that point to nodes in the same component or earlier components.
|
||||
List<List<N>> computeTopologicalSort() {
|
||||
_SccFinder<N> finder = new _SccFinder<N>(this);
|
||||
return finder.computeTopologicalSort();
|
||||
}
|
||||
|
||||
/// Whether [source] can transitively reach [target].
|
||||
bool containsPath(N source, N target) {
|
||||
Set<N> seen = new Set<N>();
|
||||
bool helper(N node) {
|
||||
if (identical(node, target)) return true;
|
||||
if (!seen.add(node)) return false;
|
||||
return targetsOf(node).any(helper);
|
||||
}
|
||||
return helper(source);
|
||||
}
|
||||
|
||||
/// Returns all nodes reachable from [root] in post order.
|
||||
List<N> postOrder(N root) {
|
||||
var seen = new Set<N>();
|
||||
var result = <N>[];
|
||||
helper(n) {
|
||||
if (!seen.add(n)) return;
|
||||
targetsOf(n).forEach(helper);
|
||||
result.add(n);
|
||||
}
|
||||
helper(root);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Return a list of nodes that form a cycle containing the given node. If the
|
||||
/// node is not part of this graph, then a list containing only the node
|
||||
/// itself will be returned.
|
||||
List<N> findCycleContaining(N node) {
|
||||
assert(node != null);
|
||||
_SccFinder<N> finder = new _SccFinder<N>(this);
|
||||
return finder._componentContaining(node);
|
||||
}
|
||||
|
||||
Graph<N> dominatorTree(N root) {
|
||||
var iDom = (new _DominatorFinder(this)..run(root)).immediateDominators;
|
||||
var graph = new EdgeListGraph<N>();
|
||||
for (N node in iDom.keys) {
|
||||
if (node != root) graph.addEdge(iDom[node], node);
|
||||
}
|
||||
return graph;
|
||||
}
|
||||
}
|
||||
|
||||
class EdgeListGraph<N> extends Graph<N> {
|
||||
/// Edges in the graph.
|
||||
Map<N, Set<N>> _edges = new Map<N, Set<N>>();
|
||||
|
||||
/// The reverse of _edges.
|
||||
Map<N, Set<N>> _revEdges = new Map<N, Set<N>>();
|
||||
|
||||
Iterable<N> get nodes => _edges.keys;
|
||||
bool get isEmpty => _edges.isEmpty;
|
||||
int get nodeCount => _edges.length;
|
||||
|
||||
final _empty = new Set<N>();
|
||||
|
||||
Iterable<N> targetsOf(N source) => _edges[source] ?? _empty;
|
||||
Iterable<N> sourcesOf(N source) => _revEdges[source] ?? _empty;
|
||||
|
||||
void addEdge(N source, N target) {
|
||||
assert(source != null);
|
||||
assert(target != null);
|
||||
addNode(source);
|
||||
addNode(target);
|
||||
_edges[source].add(target);
|
||||
_revEdges[target].add(source);
|
||||
}
|
||||
|
||||
void addNode(N node) {
|
||||
assert(node != null);
|
||||
_edges.putIfAbsent(node, () => new Set<N>());
|
||||
_revEdges.putIfAbsent(node, () => new Set<N>());
|
||||
}
|
||||
|
||||
/// Remove the edge from the given [source] node to the given [target] node.
|
||||
/// If there was no such edge then the graph will be unmodified: the number of
|
||||
/// edges will be the same and the set of nodes will be the same (neither node
|
||||
/// will either be added or removed).
|
||||
void removeEdge(N source, N target) {
|
||||
_edges[source]?.remove(target);
|
||||
}
|
||||
|
||||
/// Remove the given node from this graph. As a consequence, any edges for
|
||||
/// which that node was either a head or a tail will also be removed.
|
||||
void removeNode(N node) {
|
||||
_edges.remove(node);
|
||||
var sources = _revEdges[node];
|
||||
if (sources == null) return;
|
||||
for (var source in sources) {
|
||||
_edges[source].remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all of the given nodes from this graph. As a consequence, any edges
|
||||
/// for which those nodes were either a head or a tail will also be removed.
|
||||
void removeAllNodes(List<N> nodes) => nodes.forEach(removeNode);
|
||||
}
|
||||
|
||||
/// Used by the [SccFinder] to maintain information about the nodes that have
|
||||
/// been examined. There is an instance of this class per node in the graph.
|
||||
class _NodeInfo<N> {
|
||||
/// Depth of the node corresponding to this info.
|
||||
int index = 0;
|
||||
|
||||
/// Depth of the first node in a cycle.
|
||||
int lowlink = 0;
|
||||
|
||||
/// Whether the corresponding node is on the stack. Used to remove the need
|
||||
/// for searching a collection for the node each time the question needs to be
|
||||
/// asked.
|
||||
bool onStack = false;
|
||||
|
||||
/// Component that contains the corresponding node.
|
||||
List<N> component;
|
||||
|
||||
_NodeInfo(int depth)
|
||||
: index = depth, lowlink = depth, onStack = false;
|
||||
}
|
||||
|
||||
/// Implements Tarjan's Algorithm for finding the strongly connected components
|
||||
/// in a graph.
|
||||
class _SccFinder<N> {
|
||||
/// The graph to process.
|
||||
final Graph<N> _graph;
|
||||
|
||||
/// The index used to uniquely identify the depth of nodes.
|
||||
int _index = 0;
|
||||
|
||||
/// Nodes that are being visited in order to identify components.
|
||||
List<N> _stack = new List<N>();
|
||||
|
||||
/// Information associated with each node.
|
||||
Map<N, _NodeInfo<N>> _info = <N, _NodeInfo<N>>{};
|
||||
|
||||
/// All strongly connected components found, in topological sort order (each
|
||||
/// node in a strongly connected component only has edges that point to nodes
|
||||
/// in the same component or earlier components).
|
||||
List<List<N>> _allComponents = new List<List<N>>();
|
||||
|
||||
_SccFinder(this._graph) : super();
|
||||
|
||||
/// Return a list containing the nodes that are part of the strongly connected
|
||||
/// component that contains the given node.
|
||||
List<N> _componentContaining(N node) => _strongConnect(node).component;
|
||||
|
||||
/// Run Tarjan's algorithm and return the resulting list of strongly connected
|
||||
/// components. The list is in topological sort order (each node in a strongly
|
||||
/// connected component only has edges that point to nodes in the same
|
||||
/// component or earlier components).
|
||||
List<List<N>> computeTopologicalSort() {
|
||||
for (N node in _graph.nodes) {
|
||||
var nodeInfo = _info[node];
|
||||
if (nodeInfo == null) _strongConnect(node);
|
||||
}
|
||||
return _allComponents;
|
||||
}
|
||||
|
||||
/// Remove and return the top-most element from the stack.
|
||||
N _pop() {
|
||||
N node = _stack.removeAt(_stack.length - 1);
|
||||
_info[node].onStack = false;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// Add the given node to the stack.
|
||||
void _push(N node) {
|
||||
_info[node].onStack = true;
|
||||
_stack.add(node);
|
||||
}
|
||||
|
||||
/// Compute the strongly connected component that contains the given node as
|
||||
/// well as any components containing nodes that are reachable from the given
|
||||
/// component.
|
||||
_NodeInfo<N> _strongConnect(N v) {
|
||||
// Set the depth index for v to the smallest unused index
|
||||
var vInfo = new _NodeInfo<N>(_index++);
|
||||
_info[v] = vInfo;
|
||||
_push(v);
|
||||
|
||||
for (N w in _graph.targetsOf(v)) {
|
||||
var wInfo = _info[w];
|
||||
if (wInfo == null) {
|
||||
// Successor w has not yet been visited; recurse on it
|
||||
wInfo = _strongConnect(w);
|
||||
vInfo.lowlink = math.min(vInfo.lowlink, wInfo.lowlink);
|
||||
} else if (wInfo.onStack) {
|
||||
// Successor w is in stack S and hence in the current SCC
|
||||
vInfo.lowlink = math.min(vInfo.lowlink, wInfo.index);
|
||||
}
|
||||
}
|
||||
|
||||
// If v is a root node, pop the stack and generate an SCC
|
||||
if (vInfo.lowlink == vInfo.index) {
|
||||
var component = new List<N>();
|
||||
N w;
|
||||
do {
|
||||
w = _pop();
|
||||
component.add(w);
|
||||
_info[w].component = component;
|
||||
} while (!identical(w, v));
|
||||
_allComponents.add(component);
|
||||
}
|
||||
return vInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes dominators using (Cooper, Harvey, and Kennedy's
|
||||
/// algorithm)[http://www.cs.rice.edu/~keith/EMBED/dom.pdf].
|
||||
class _DominatorFinder<N> {
|
||||
final Graph<N> _graph;
|
||||
Map<N, N> immediateDominators = {};
|
||||
Map<N, int> postOrderId = {};
|
||||
_DominatorFinder(this._graph);
|
||||
|
||||
run(N root) {
|
||||
immediateDominators[root] = root;
|
||||
bool changed = true;
|
||||
int i = 0;
|
||||
var nodesInPostOrder = _graph.postOrder(root);
|
||||
for (var n in nodesInPostOrder) {
|
||||
postOrderId[n] = i++;
|
||||
}
|
||||
var nodesInReversedPostOrder = nodesInPostOrder.reversed;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (var n in nodesInReversedPostOrder) {
|
||||
if (n == root) continue;
|
||||
bool first = true;
|
||||
var idom = null;
|
||||
for (var p in _graph.sourcesOf(n)) {
|
||||
if (immediateDominators[p] != null) {
|
||||
if (first) {
|
||||
idom = p;
|
||||
first = false;
|
||||
} else {
|
||||
idom = _intersect(p, idom);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (immediateDominators[n] != idom) {
|
||||
immediateDominators[n] = idom;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
N _intersect(N b1, N b2) {
|
||||
var finger1 = b1;
|
||||
var finger2 = b2;
|
||||
while (finger1 != finger2) {
|
||||
while (postOrderId[finger1] < postOrderId[finger2]) {
|
||||
finger1 = immediateDominators[finger1];
|
||||
}
|
||||
while (postOrderId[finger2] < postOrderId[finger1]) {
|
||||
finger2 = immediateDominators[finger2];
|
||||
}
|
||||
}
|
||||
return finger1;
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
// 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.
|
||||
|
||||
/// Command-line tool to show the size distribution of generated code among
|
||||
/// libraries. Libraries can be grouped using regular expressions. See
|
||||
/// [defaultGrouping] for an example.
|
||||
library compiler.tool.library_size_split;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show max;
|
||||
|
||||
import 'package:compiler/src/info/info.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
main(args) {
|
||||
if (args.length < 1) {
|
||||
print('usage: dart tool/library_size_split.dart '
|
||||
'path-to-info.json [grouping.yaml]');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var filename = args[0];
|
||||
var json = JSON.decode(new File(filename).readAsStringSync());
|
||||
var info = AllInfo.parseFromJson(json);
|
||||
|
||||
var groupingText = args.length > 1
|
||||
? new File(args[1]).readAsStringSync() : defaultGrouping;
|
||||
var groupingYaml = loadYaml(groupingText);
|
||||
var groups = [];
|
||||
for (var group in groupingYaml['groups']) {
|
||||
groups.add(new _Group(group['name'],
|
||||
new RegExp(group['regexp']),
|
||||
group['cluster'] ?? 0));
|
||||
}
|
||||
|
||||
var sizes = {};
|
||||
for (LibraryInfo lib in info.libraries) {
|
||||
groups.forEach((group) {
|
||||
var match = group.matcher.firstMatch('${lib.uri}');
|
||||
if (match != null) {
|
||||
var name = group.name;
|
||||
if (name == null && match.groupCount > 0) name = match.group(1);
|
||||
if (name == null) name = match.group(0);
|
||||
sizes.putIfAbsent(name, () => new _SizeEntry(name, group.cluster));
|
||||
sizes[name].size += lib.size;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var all = sizes.keys.toList();
|
||||
all.sort((a, b) => sizes[a].compareTo(sizes[b]));
|
||||
var realTotal = info.program.size;
|
||||
var longest = all.fold(0, (count, value) => max(count, value.length));
|
||||
longest = max(longest, 'Program Size'.length);
|
||||
var lastCluster = 0;
|
||||
for (var name in all) {
|
||||
var entry = sizes[name];
|
||||
if (lastCluster < entry.cluster) {
|
||||
print(' ' + ('-' * (longest + 18)));
|
||||
lastCluster = entry.cluster;
|
||||
}
|
||||
var size = entry.size;
|
||||
var percent = (size * 100 / realTotal).toStringAsFixed(2);
|
||||
print(' ${_pad(name, longest + 1, right: true)}'
|
||||
' ${_pad(size, 8)} ${_pad(percent, 6)}%');
|
||||
}
|
||||
print(' ${_pad("Program Size", longest + 1, right: true)}'
|
||||
' ${_pad(realTotal, 8)} ${_pad(100, 6)}%');
|
||||
}
|
||||
|
||||
/// A group defined in the configuration.
|
||||
class _Group {
|
||||
/// Name of the group. May be null if the name is derived from the matcher. In
|
||||
/// that case, the name would be group(1) of the matched expression if it
|
||||
/// exist, or group(0) otherwise.
|
||||
final String name;
|
||||
|
||||
/// Regular expression matching members of the group.
|
||||
final RegExp matcher;
|
||||
|
||||
/// Index used to cluster groups together. Useful when the grouping
|
||||
/// configuration describes some coarser groups than orders (e.g. summary of
|
||||
/// packages would be in a different cluster than a summary of libraries).
|
||||
final int cluster;
|
||||
|
||||
_Group(this.name, this.matcher, this.cluster);
|
||||
}
|
||||
|
||||
class _SizeEntry {
|
||||
final String name;
|
||||
final int cluster;
|
||||
int size = 0;
|
||||
|
||||
_SizeEntry(this.name, this.cluster);
|
||||
|
||||
int compareTo(_SizeEntry other) =>
|
||||
cluster == other.cluster ? size - other.size : cluster - other.cluster;
|
||||
}
|
||||
|
||||
_pad(value, n, {bool right: false}) {
|
||||
var s = '$value';
|
||||
if (s.length >= n) return s;
|
||||
var pad = ' ' * (n - s.length);
|
||||
return right ? '$s$pad' : '$pad$s';
|
||||
}
|
||||
|
||||
/// Example grouping specification: a yaml format containing a list of
|
||||
/// group specifications. A group is specified by 3 parameters:
|
||||
/// - name: the name that will be shown in the table of results
|
||||
/// - regexp: a regexp used to match entries that belong to the group
|
||||
/// - cluster: a clustering index, the higher the value, the later it will be
|
||||
/// shown in the results.
|
||||
/// Both cluster and name are optional. If cluster is omitted, the default value
|
||||
/// is 0. If the name is omitted, it is extracted from the regexp, either as
|
||||
/// group(1) if it is available or group(0) otherwise.
|
||||
final defaultGrouping = """
|
||||
groups:
|
||||
- { name: "Total (excludes preambles, statics & consts)", regexp: ".*", cluster: 3}
|
||||
- { name: "Loose files", regexp: "file://.*", cluster: 2}
|
||||
- { name: "All packages", regexp: "package:.*", cluster: 2}
|
||||
- { name: "Core libs", regexp: "dart:.*", cluster: 2}
|
||||
# We omitted `name` to extract the group name from the regexp directly.
|
||||
# Here the name is the name of the package:
|
||||
- { regexp: "package:([^/]*)", cluster: 1}
|
||||
# Here the name is the url of the package and dart core libraries:
|
||||
- { regexp: "package:.*"}
|
||||
- { regexp: "dart:.*"}
|
||||
# Here the name is the relative path of loose files:
|
||||
- { regexp: "file://${Directory.current.path}/(.*)" }
|
||||
""";
|
|
@ -1,96 +0,0 @@
|
|||
library compiler.tool.util;
|
||||
|
||||
import 'package:compiler/src/info/info.dart';
|
||||
import 'graph.dart';
|
||||
|
||||
/// Computes a graph of dependencies from [info].
|
||||
Graph<Info> graphFromInfo(AllInfo info) {
|
||||
// Note: currently we build two graphs to debug the differences between to
|
||||
// places where we collect this information in dump-info.
|
||||
// TODO(sigmund): fix inconsistencies between graphs, stick with one of them.
|
||||
// TODO(sigmund): create a concrete implementation of InfoGraph, instead of
|
||||
// using the EdgeListGraph.
|
||||
var g1 = new EdgeListGraph<Info>();
|
||||
var g2 = new EdgeListGraph<Info>();
|
||||
var g3 = new EdgeListGraph<Info>();
|
||||
for (var f in info.functions) {
|
||||
g1.addNode(f);
|
||||
g3.addNode(f);
|
||||
for (var g in f.uses) {
|
||||
g1.addEdge(f, g.target);
|
||||
g3.addEdge(f, g.target);
|
||||
}
|
||||
g2.addNode(f);
|
||||
if (info.dependencies[f] != null) {
|
||||
for (var g in info.dependencies[f]) {
|
||||
g2.addEdge(f, g);
|
||||
g3.addEdge(f, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var f in info.fields) {
|
||||
g1.addNode(f);
|
||||
g3.addNode(f);
|
||||
for (var g in f.uses) {
|
||||
g1.addEdge(f, g.target);
|
||||
g3.addEdge(f, g.target);
|
||||
}
|
||||
g2.addNode(f);
|
||||
if (info.dependencies[f] != null) {
|
||||
for (var g in info.dependencies[f]) {
|
||||
g2.addEdge(f, g);
|
||||
g3.addEdge(f, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: these checks right now show that 'uses' links are computed
|
||||
// differently than 'deps' links
|
||||
int more1 = 0;
|
||||
int more2 = 0;
|
||||
int more1b = 0;
|
||||
int more2b = 0;
|
||||
_sameEdges(f) {
|
||||
var targets1 = g1.targetsOf(f).toSet();
|
||||
var targets2 = g2.targetsOf(f).toSet();
|
||||
var len1 = targets1.length;
|
||||
var len2 = targets2.length;
|
||||
if (len1 > len2) more1b++;
|
||||
if (len1 < len2) more2b++;
|
||||
var diff1 = targets1.difference(targets2);
|
||||
var diff2 = targets2.difference(targets1);
|
||||
if (diff1.isNotEmpty) {
|
||||
more1++;
|
||||
}
|
||||
if (diff2.isNotEmpty) {
|
||||
more2++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
info.functions.forEach(_sameEdges);
|
||||
info.fields.forEach(_sameEdges);
|
||||
if (more1 > 0 || more2 > 0 || more1b > 0 || more2b > 0) {
|
||||
print("Dep graph is not consistent: $more1 $more2");
|
||||
}
|
||||
|
||||
return g3;
|
||||
}
|
||||
|
||||
/// Provide a unique long name associated with [info].
|
||||
// TODO(sigmund): guarantee that the name is actually unique.
|
||||
String longName(Info info) {
|
||||
var sb = new StringBuffer();
|
||||
helper(i) {
|
||||
if (i.parent == null) {
|
||||
// TODO(sigmund): ensure `i is LibraryInfo`, we still don't set parents
|
||||
// for closure classes correctly.
|
||||
sb.write('${i.name}..');
|
||||
} else {
|
||||
helper(i.parent);
|
||||
sb.write('.${i.name}');
|
||||
}
|
||||
}
|
||||
helper(info);
|
||||
return sb.toString();
|
||||
}
|
|
@ -44,9 +44,6 @@ const Map<String, List<String>> WHITE_LIST = const {
|
|||
"accept", "FunctionExpression", "CreateFunction"
|
||||
],
|
||||
|
||||
// AllInfo.fromJson and visit methods are not used yet.
|
||||
"lib/src/info/info.dart": const [ "is never" ],
|
||||
|
||||
"lib/src/universe/universe.dart": const [
|
||||
"The method 'getterInvocationsByName' is never called.",
|
||||
"The method 'setterInvocationsByName' is never called."],
|
||||
|
|
|
@ -21,6 +21,7 @@ vars.update({
|
|||
"collection_rev": "@1da9a07f32efa2ba0c391b289e2037391e31da0e",
|
||||
"crypto_rev" : "@2df57a1e26dd88e8d0614207d4b062c73209917d",
|
||||
"csslib_tag" : "@0.12.0",
|
||||
"dart2js_info_rev" : "@5902be71c24bbda96675f646ba7d3ff911d26eae",
|
||||
"glob_rev": "@704cf75e4f26b417505c5c611bdaacd8808467dd",
|
||||
"html_tag" : "@0.12.1+1",
|
||||
"http_rev" : "@9b93e1542c753090c50b46ef1592d44bc858bfe7",
|
||||
|
@ -93,6 +94,8 @@ deps.update({
|
|||
"https://github.com/dart-lang/crypto.git" + Var("crypto_rev"),
|
||||
"src/dart/third_party/pkg/csslib":
|
||||
"https://github.com/dart-lang/csslib.git" + Var("csslib_tag"),
|
||||
"src/dart/third_party/pkg/dart2js_info":
|
||||
"https://github.com/dart-lang/dart2js_info.git" + Var("dart2js_info_rev"),
|
||||
"src/dart/third_party/pkg/collection":
|
||||
"https://github.com/dart-lang/collection.git" + Var("collection_rev"),
|
||||
"src/dart/third_party/pkg/glob":
|
||||
|
|
Loading…
Reference in a new issue