Add 'pkg/dart2js_info/' from commit '86ccc7749bd01278473d1d3843c442b5046b36fa'

git-subtree-dir: pkg/dart2js_info
git-subtree-mainline: 7fe597bd8e
git-subtree-split: 86ccc7749b
This commit is contained in:
Alexander Thomas 2021-08-11 10:17:15 +02:00
commit cf411dc539
59 changed files with 36786 additions and 0 deletions

16
pkg/dart2js_info/.github/move.yml vendored Normal file
View file

@ -0,0 +1,16 @@
# Configuration for move-issues - https://github.com/dessant/move-issues
# Delete the command comment when it contains no other content.
deleteCommand: true
# Close the source issue after moving.
closeSourceIssue: true
# Lock the source issue after moving.
lockSourceIssue: false
# Mention issue and comment authors.
mentionAuthors: true
# Preserve mentions in the issue content.
keepContentMentions: true

View file

@ -0,0 +1,61 @@
name: Dart CI
on:
# Run on PRs and pushes to the default branch.
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: "0 0 * * 0"
env:
PUB_ENVIRONMENT: bot.github
jobs:
# Check code formatting and static analysis on a single OS (linux)
# against Dart dev.
analyze:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sdk: [dev]
steps:
- uses: actions/checkout@v2
- uses: dart-lang/setup-dart@v1.0
with:
sdk: ${{ matrix.sdk }}
- id: install
name: Install dependencies
run: dart pub get
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
if: always() && steps.install.outcome == 'success'
- name: Analyze code
run: dart analyze
if: always() && steps.install.outcome == 'success'
# Run tests on a matrix consisting of two dimensions:
# 1. OS: ubuntu-latest, (macos-latest, windows-latest)
# 2. release channel: dev
test:
needs: analyze
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Add macos-latest and/or windows-latest if relevant for this package.
os: [ubuntu-latest]
sdk: [2.12.0, dev]
steps:
- uses: actions/checkout@v2
- uses: dart-lang/setup-dart@v1.0
with:
sdk: ${{ matrix.sdk }}
- id: install
name: Install dependencies
run: dart pub get
- name: Run VM tests
run: dart test --platform vm
if: always() && steps.install.outcome == 'success'

14
pkg/dart2js_info/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
.buildlog
.DS_Store
*.swp
*.swo
.idea
*.iml
.pub/
.settings/
build/
packages
.packages
pubspec.lock
bin/inference/client.dart.js*
.dart_tool/

4
pkg/dart2js_info/.status Normal file
View file

@ -0,0 +1,4 @@
# 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.

6
pkg/dart2js_info/AUTHORS Normal file
View file

@ -0,0 +1,6 @@
# Below is a list of people and organizations that have contributed
# to the project. Names should be added to the list like so:
#
# Name/Organization <email address>
Google Inc.

View file

@ -0,0 +1,180 @@
## 0.6.5
* Drop unused dependencies.
## 0.6.4
* Make compatible with the null-safe version of `args`.
## 0.6.3
* Broaden version ranges for `fixnum` and `protobuf` dependencies to make
`dart2js_info` compatible with null-safe `protobuf` version.
## 0.6.2
* Update `protobuf` dependency.
* Set min SDK to `2.3.0`, as generated code contains this version.
## 0.6.1
* Move binary subcommands under src folder. Otherwise, `pub global activate`
fails.
## 0.6.0
This release contains several **breaking changes**:
* The fields `Info.id` and `Info.serializedId` have been removed. These
properties were only used for serialization and deserialization. Those values
are now computed during the serialization process instead.
* Added `CodeSpan` - a representation of code regions referring to output files.
This will be used to transition to a lighterweight dump-info that doesn't
embed code snippets (since they are duplicated with the output program).
Encoder produces a new format for code-spans, but for a transitional period
a flag is provided to produce the old format. The decoder is still backwards
compatible (filling in just the `text` in `CodeSpan` where the json contained
a String).
* Deleted unused `Measurements`.
* Split the json codec from info.dart.
* Introduced `lib/binary_serialization.dart` a lighterweight
serialization/deserialization implementation. This will eventually be used by
default by dart2js.
* Added backwards compatibility flag to the JSON codec, to make transition to
new tools more gradual.
* Added a tool to dump info files in a readable text form.
* Consolidated all binary tools under a single command. Now you can access all
tools as follows:
```
pub global activate dart2js_info
dart2js_info <command> [arguments] ...
```
See updated documentation in README.md
## 0.5.17
* Make `live_code_size_analysis` print library URIs and not library names.
## 0.5.16
* Split out IO dependency from `util.dart`, so all other utilities can be used
on any platform.
## 0.5.15
* Add `BasicInfo.resetIds` to free internal cache used for id uniqueness.
## 0.5.14
* Updates `coverage_log_server.dart` and `live_code_size_analysis.dart` to make
them strong clean and match the latest changes in dart2js.
## 0.5.13
* Use a more efficient `Map` implementation for decoding existing info files.
* Use a relative path when generating unique IDs for elements in non-package
sources.
## 0.5.12
* Improved output of `dart2js_info_diff` by sorting the diffs by
size and outputting the summary in full output mode.
## 0.5.11
* Added `--summary` option to `dart2js_info_diff` tool.
## 0.5.10
* Set max SDK version to `<3.0.0`, and adjust other dependencies.
## 0.5.6+4
- Changes to make the library strong mode (runtime) clean.
## 0.5.6
- Added `isRuntimeTypeUsed`, `isIsolateInUse`, `isFunctionApplyUsed` and `isMirrorsUsed` to
`ProgramInfo`.
## 0.5.5+1
- Support the latest versions of `shelf` and `args` packages.
## 0.5.5
- Added `diff` tool.
## 0.5.4+2
- Updated minimum SDK dependency to align with package dependencies.
- Allowed the latest version of `pkg/quiver`.
- Updated the homepage.
- Improved the stability and eliminated duplicates in "holding" dump info
output.
## 0.5.4+1
- Remove files published accidentally.
## 0.5.4
- Added script to show inferred types of functions and fields on the command
line.
## 0.5.3+1
- Improved the stability of `ConstantInfo.id`.
## 0.5.3
- Made IDs in the JSON format stable. Improves plain text diffing.
## 0.2.7
- Make dart2js_info strong-mode clean.
## 0.2.6
- Add tool to get breakdown of deferred libraries by size.
## 0.2.5
- Changed the `deferred_library_check` tool to allow parts to exclude packages
and to not assume that unspecified packages are in the main part.
## 0.2.4
- Added `imports` field for `OutputUnitInfo`
## 0.2.3
- Moved `deferred_library_check` functionality to a library
## 0.2.2
- Added `deferred_libary_check` tool
## 0.2.1
- Merged `verify_deps` tool into `debug_info` tool
## 0.2.0
- Added `AllInfoJsonCodec`
- Added `verify_deps` tool
## 0.1.0
- Added `ProgramInfo.entrypoint`.
- Added experimental information about calls in function bodies. This will
likely change again in the near future.
## 0.0.3
- Added executable names
## 0.0.2
- Add support for `ConstantInfo`
## 0.0.1
- Initial version

View file

@ -0,0 +1,33 @@
Want to contribute? Great! First, read this page (including the small print at
the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
### Code reviews
All submissions, including submissions by project members, require review.
### File headers
All files in the project must start with the following header.
// 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.
### The small print
Contributions made by corporations are covered by a different agreement than the
one above, the
[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).

View file

@ -0,0 +1,54 @@
# Notes for developers
## Developing locally together with dart2js:
* Use a path dependency on this repo to prepare changes.
## Submitting changes.
* Submit changes in this repo first.
* Update the sdk/DEPS and sdk/tools/deps/dartium.deps/DEPS to use the latest
hash of this repo.
* Submit dart2js changes together with the roll in DEPS.
## Updating the dart2js\_info dart docs
We use `dartdoc` and host the generated documentation as a [github page][1] in
this repo. Here is how to update it:
* Make sure you have the dartdoc tool installed:
```
pub global activate dartdoc
```
* Run the dartdoc tool on the root of the repo in master, specify an out
directory different than `doc`:
```sh
dartdoc --output _docs
```
* Switch to the `gh-pages` branch:
```
git checkout gh-pages
git pull
```
* Override the existing docs by hand:
```
rm -r doc/api
mv _docs doc/api
git diff # validate changes look right
git commit -a -m "Update documentation ... "
```
* Update the gh-pages branch in the server
```
git push origin gh-pages
```
[1]: http://dart-lang.github.io/dart2js_info/doc/api/dart2js_info.info/AllInfo-class.html

27
pkg/dart2js_info/LICENSE Normal file
View file

@ -0,0 +1,27 @@
Copyright 2015, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

520
pkg/dart2js_info/README.md Normal file
View file

@ -0,0 +1,520 @@
# Dart2js Info
This package contains libraries and tools you can use to process info
files produced when running dart2js with `--dump-info`.
The info files contain data about each element included in the output of your
program. The data includes information such as:
* the size that each function adds to the `.dart.js` output,
* dependencies between functions,
* how the code is clustered when using deferred libraries, and
* the declared and inferred type of each function argument.
All of this information can help you understand why some piece of code is
included in your compiled application, and how far was dart2js able to
understand your code. This data can help you make changes to improve the quality
and size of your framework or app.
This package focuses on gathering libraries and tools that summarize all of that
information. Bear in mind that even with all these tools, it is not trivial to
isolate code-size issues. We just hope that these tools make things a bit
easier.
## Status
[![Build Status](https://github.com/dart-lang/dart2js_info/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/dart2js_info/actions?query=workflow%3A"Dart+CI"+branch%3Amaster)
Currently, most tools available here can be used to analyze code-size and
attribution of code-size to different parts of your app. With time, we hope to
add more data to the info files, and include better tools to help
understand the results of type inference.
This package is still in flux and we might make breaking changes at any time.
Our current goal is not to provide a stable API, we mainly want to expose the
functionality and iterate on it. We recommend that you pin a specific version
of this package and update when needed.
## Tools
All tools are provided as commands of a single command-line interface. To
install:
```console
pub global activate dart2js_info
```
To run a tool, then run:
```console
dart2js_info <command> [arguments]
```
There is a short help available on the tool, and more details are provided
below.
## Format
There are several formats of info files. Dart2js today produces a JSON format,
but very soon will switch to produce a binary format by default.
## Info API
This package also exposes libraries to parse and represent the information from
the info files. If there is data that is stored in the info files but not
exposed by one of our tools, you may be able to use the info APIs to quickly put
together your own tool.
[AllInfo][AllInfo] exposes a Dart representation of all of the collected
information. There are deserialization libraries in this package to decode any
info file produced by the `dart2js` `--dump-info` option. See
`lib/binary_serialization.dart` and `lib/json_info_codec.dart` to find the
binary and JSON decoders respectively. For convenience,
`package:dart2js_info/src/io.dart` also exposes a helper method that can choose,
depending on the extension of the info file, whether to deserialize it using the
binary or JSON decoder. For example:
```dart
import 'dart:convert';
import 'dart:io';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
main(args) async {
var infoPath = args[0];
var info = await infoFromFile(infoPath);
...
}
```
## Available tools
The following tools are a available today:
* [`code_deps`][code_deps]: simple tool that can answer queries about the
dependency between functions and fields in your program. Currently it only
supports the `some_path` query, which shows a dependency path from one
function to another.
* [`diff`][diff]: a tool that diffs two info files and reports which
program elements have been added, removed, or changed size. This also
tells which elements are no longer deferred or have become deferred.
* [`library_size`][library_size]: a tool that shows how much code was
attributed to each library. This tool is configurable so it can group data
in many ways (e.g. to tally together all libraries that belong to a package,
or all libraries that match certain name pattern).
* [`deferred_check`][deferred_check]: a tool that verifies that code
was split into deferred parts as expected. This tool takes a specification
of the expected layout of code into deferred parts, and checks that the
output from `dart2js` meets the specification.
* [`deferred_size`][deferred_size]: a tool that gives a breakdown of
the sizes of the deferred parts of the program. This can show how much of
your total code size can be loaded deferred.
* [`deferred_layout`][deferred_layout]: a tool that reports which
code is included on each output unit.
* [`function_size`][function_size]: a tool that shows how much
code was attributed to each function. This tool also uses dependency
information to compute dominance and reachability data. This information can
sometimes help determine how much savings could come if the function was not
included in the program.
* [`coverage_server`][coverage_server] and [`coverage_analysis`][coverage_analysis]:
dart2js has an experimental feature to gather coverage data of your
application. The `coverage_log_server` can record this data, and
`live_code_size_analysis` can correlate that with the info file, so you
determine why code that is not used is being included in your app.
* [`convert`][convert]: a tool that converts info files from one format to
another. Accepted inputs are JSON or the internal binary form, outputs can
be JSON, backward-compatible JSON, binary, or protobuf schema (as defined in
`info.proto`).
* [`show`][show]: a tool that dumps info files in a readable text format.
Next we describe in detail how to use each of these tools.
### Code deps tool
This command-line tool can be used to query for code dependencies. Currently
this tool only supports the `some_path` query, which gives you the shortest path
for how one function depends on another.
Run this tool as follows:
```console
# activate is only needed once to install the dart2js_info tool
$ pub global activate dart2js_info
$ dart2js_info code_deps some_path out.js.info.data main foo
```
The arguments to the query are regular expressions that can be used to
select a single element in your program. If your regular expression is too
general and has more than one match, this tool will pick
the first match and ignore the rest. Regular expressions are matched against
a fully qualified element name, which includes the library and class name
(if any) that contains it. A typical qualified name is of this form:
libraryName::ClassName.elementName
If the name of a function your are looking for is unique enough, it might be
sufficient to just write that name as your regular expression.
### Diff tool
This command-line tool shows a diff between two info files. It can be run
as follows:
```console
$ pub global activate dart2js_info # only needed once
$ dart2js_info diff old.js.info.data new.js.info.data [--summary]
```
The tool gives a breakdown of the difference between the two info files.
Here's an example output:
```
total_size_difference -2688
total_added 0
total_removed 2321
total_size_changed -203
total_became_deferred 0
total_no_longer_deferred 0
ADDED (0 bytes)
========================================================================
REMOVED (2321 bytes)
========================================================================
dart:_js_helper::getRuntimeTypeString: 488 bytes
dart:_js_helper::substitute: 479 bytes
dart:_js_helper::TypeImpl.toString: 421 bytes
dart:_js_helper::computeSignature: 204 bytes
dart:_js_helper::getRuntimeTypeArguments: 181 bytes
dart:_js_helper::extractFunctionTypeObjectFrom: 171 bytes
dart:_js_helper::getTypeArgumentByIndex: 147 bytes
dart:_js_helper::runtimeTypeToString: 136 bytes
dart:_js_helper::setRuntimeTypeInfo: 94 bytes
dart:core::Object.runtimeType: 0 bytes
dart:_js_helper::getRawRuntimeType: 0 bytes
dart:_js_helper::invoke: 0 bytes
dart:_js_helper::invokeOn: 0 bytes
dart:_js_helper::getField: 0 bytes
dart:_js_helper::getClassName: 0 bytes
dart:_js_helper::getRuntimeType: 0 bytes
dart:_js_helper::TypeImpl.TypeImpl: 0 bytes
CHANGED SIZE (-203 bytes)
========================================================================
dart:_interceptors::JSUnmodifiableArray: -3 bytes
dart:core::List: -3 bytes
dart:_interceptors::ArrayIterator: -4 bytes
dart:_js_helper::TypeImpl._typeName: -10 bytes
dart:_js_helper::TypeImpl._unmangledName: -15 bytes
dart:_js_names::: -30 bytes
dart:_js_names::extractKeys: -30 bytes
dart:core::StringBuffer: -40 bytes
dart:core::StringBuffer._writeAll: -40 bytes
dart:core::: -43 bytes
dart:_interceptors::JSArray.+: -63 bytes
dart:_interceptors::JSArray: -66 bytes
dart:_interceptors::: -73 bytes
dart:_js_helper::TypeImpl: -481 bytes
dart:_js_helper::: -2445 bytes
BECAME DEFERRED (0 bytes)
========================================================================
NO LONGER DEFERRED (0 bytes)
========================================================================
```
You can also pass `--summary` to only show the summary section.
### Library size split tool
This command-line tool shows the size distribution of generated code among
libraries. It can be run as follows:
```console
$ pub global activate dart2js_info # only needed once
$ dart2js_info library_size out.js.info.data
```
Libraries can be grouped using regular expressions. You can
specify what regular expressions to use by providing a `grouping.yaml` file
with the `--grouping` flag:
```console
$ dart2js_info library_size out.js.info.data --grouping grouping.yaml
```
The format of the `grouping.yaml` file is as follows:
```yaml
groups:
- { regexp: "package:(foo)/*.dart", name: "group name 1", cluster: 2}
- { regexp: "dart:.*", name: "group name 2", cluster: 3}
```
The file should include a single key `groups` containing a list of group
specifications. Each group is specified by a map of 3 entries:
* `regexp` (required): a regexp used to match entries that belong to the
group.
* `name` (optional): the name given to this group in the output table. If
omitted, the name is derived from the regexp as the match's group(1) or
group(0) if no group was defined. When names are omitted the group
specification implicitly defines several groups, one per observed name.
* `cluster` (optional): a clustering index for how data is shown in a table.
Groups with higher cluster indices are shown later in the table after a
dividing line. If missing, the cluster index defaults to 0.
Here is an example configuration, with comments about what each entry does:
```yaml
groups:
# This group shows the total size for all libraries that were loaded from
# file:// urls, it is shown in cluster #2, which happens to be the last
# cluster in this example before the totals are shown:
- name: "Loose files"
regexp: "file://.*"
cluster: 2
# This group shows the total size of all code loaded from packages:
- { name: "All packages", regexp: "package:.*", cluster: 2}
# This group shows the total size of all code loaded from core libraries:
- { name: "Core libs", regexp: "dart:.*", cluster: 2}
# This group shows the total size of all libraries in a single package. Here
# we omitted the `name` entry, instead we extract it from the regexp
# directly. In this case, the name will be the package-name portion of the
# package-url (determined by group(1) of the regexp).
- { regexp: "package:([^/]*)", cluster: 1}
# The next two groups match the entire library url as the name of the group.
- regexp: "package:.*"
- regexp: "dart:.*"
# If your code lives under /my/project/dir, this will match any file loaded
from a file:// url, and we use as a name the relative path to it.
- regexp: "file:///my/project/dir/(.*)"
```
Regardless of the grouping configuration, the tool will display the total code
size attributed of all libraries, constants, and the program size.
**Note**: eventually you should expect all numbers to add up to the program
size. Currently dart2js's `--dump-info` is not complete, so numbers for
bootstrapping code and lazy static initializers are missing.
### Deferred library verification
This tool checks that the output from dart2js meets a given specification,
given in a YAML file. It can be run as follows:
```console
$ pub global activate dart2js_info # only needed once
$ dart2js_info deferred_check out.js.info.data manifest.yaml
```
The format of the YAML file is:
```yaml
main:
include:
- some_package
- other_package
exclude:
- some_other_package
foo:
include:
- foo
- bar
baz:
include:
- baz
- quux
exclude:
- zardoz
```
The YAML file consists of a list of declarations, one for each deferred
part expected in the output. At least one of these parts must be named
"main"; this is the main part that contains the program entrypoint. Each
top-level part contains a list of package names that are expected to be
contained in that part, a list of package names that are expected to be in
another part, or both. For instance, in the example YAML above the part named
"baz" is expected to contain the packages "baz" and "quux" and exclude the
package "zardoz".
The names for parts given in the specification YAML file (besides "main")
are the same as the name given to the deferred import in the dart file. For
instance, if you have `import 'package:foo/bar.dart' deferred as baz;` in your
dart file, then the corresponding name in the specification file is 'baz'.
### Deferred library size tool
This tool gives a breakdown of all of the deferred code in the program by size.
It can show how much of the total code size is deferred. It can be run as
follows:
```console
pub global activate dart2js_info # only needed once
dart2js_info deferred_size out.js.info.data
```
The tool will output a table listing all of the deferred imports in the program
as well as the "main" chunk, which is not deferred. The output looks like:
```
Size by library
------------------------------------------------
main 12345678
foo 7654321
bar 1234567
------------------------------------------------
Main chunk size 12345678
Deferred code size 8888888
Percent of code deferred 41.86%
```
### Deferred library layout tool
This tool reports which code is included in each output unit. It can be run as
follows:
```console
$ pub global activate dart2js_info # only needed once
$ dart2js_info deferred_layout out.js.info.data
```
The tool will output a table listing all of the deferred output units or chunks,
for each unit it will list the set of libraries that contribute code to this
unit. If a library contributes to more than one output unit, the tool lists
which elements are in one or another output unit. For example, the output might
look like this:
```
Output unit main:
loaded by default
contains:
- hello_world.dart
- dart:core
...
Output unit 2:
loaded by importing: [b]
contains:
- c.dart:
- function d
- b.dart
Output unit 1:
loaded by importing: [a]
contains:
- c.dart:
- function c
- a.dart
```
In this example, all the code of `b.dart` after tree-shaking was included in the
output unit 2, but `c.dart` was split between output unit 1 and output unit 2.
### Function size analysis tool
This command-line tool presents how much each function contributes to the total
code of your application. We use dependency information to compute dominance
and reachability data as well.
When you run:
```console
$ pub global activate dart2js_info # only needed once
$ dart2js_info function_size out.js.info.data
```
the tool produces a table output with lots of entries. Here is an example entry
with the corresponding table header:
```
--- Results per element (field or function) ---
element size dominated size reachable size Element identifier
...
275 0.01% 283426 13.97% 1506543 74.28% some.library.name::ClassName.myMethodName
```
Such entry means that the function `myMethodName` uses 275 bytes, which is 0.01%
of the application. That function however calls other functions, which
transitively can include up to 74.28% of the application size. Of all those
reachable functions, some of them are reachable from other parts of the program,
but a subset are dominated by `myMethodName`, that is, other parts of the
program starting from `main` would first go through `myMethodName` before
reaching those functions. In this example, that subset is 13.97% of the
application size. This means that if you somehow can remove your dependency on
`myMethodName`, you will save at least that 13.97%, and possibly some more from
the reachable size, but how much of that we are not certain.
### Coverage tools
Coverage information requires a bit more setup and work to get them running. The
steps are as follows:
* Compile an app with dart2js using `--dump-info` and
`--experiment-call-instrumentation`
```console
$ dart2js --dump-info --experiment-call-instrumentation main.dart
```
The flag only works dart2js version 2.2.0 or newer.
* Launch the coverage server tool to serve up the JS code of your app:
```console
$ dart2js_info coverage_server main.dart.js
```
* (optional) If you have a complex application setup, you may need to serve an
html file or integrate your application server to proxy to the log server
any GET request for the .dart.js file and /coverage POST requests that send
coverage data.
* Load your app and use it to exercise the entire code.
* Shut down the coverage server (Ctrl-C). This will emit a file named
`mail.dart.js.coverage.json`
* Finally, run the live code analysis tool given it both the info and
coverage json files:
```console
$ dart2js_info coverage_analysis main.dart.info.data main.dart.coverage.json
```
## Code location, features and bugs
This package is developed in [github][repo]. Please file feature requests and
bugs at the [issue tracker][tracker].
[repo]: https://github.com/dart-lang/dart2js_info/
[tracker]: https://github.com/dart-lang/dart2js_info/issues
[code_deps]: https://github.com/dart-lang/dart2js_info/blob/master/bin/code_deps.dart
[diff]: https://github.com/dart-lang/dart2js_info/blob/master/bin/diff.dart
[library_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/library_size_split.dart
[deferred_check]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_check.dart
[deferred_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_size.dart
[deferred_layout]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_layout.dart
[coverage_server]: https://github.com/dart-lang/dart2js_info/blob/master/bin/coverage_log_server.dart
[coverage_analysis]: https://github.com/dart-lang/dart2js_info/blob/master/bin/live_code_size_analysis.dart
[function_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/function_size_analysis.dart
[AllInfo]: http://dart-lang.github.io/dart2js_info/doc/api/dart2js_info.info/AllInfo-class.html
[convert]: https://github.com/dart-lang/dart2js_info/blob/master/bin/convert.dart
[show]: https://github.com/dart-lang/dart2js_info/blob/master/bin/text_print.dart

View file

@ -0,0 +1,131 @@
// 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 to query for code dependencies. Currently this tool only
/// supports the `some_path` query, which gives you the shortest path for how
/// one function depends on another.
///
/// You can run this tool as follows:
/// ```bash
/// pub global activate dart2js_info
/// dart2js_info code_deps some_path out.js.info.json main foo
/// ```
///
/// The arguments to the query are regular expressions that can be used to
/// select a single element in your program. If your regular expression is too
/// general and has more than one match, this tool will pick
/// the first match and ignore the rest. Regular expressions are matched against
/// a fully qualified element name, which includes the library and class name
/// (if any) that contains it. A typical qualified name is of this form:
///
/// libraryName::ClassName.elementName
///
/// If the name of a function your are looking for is unique enough, it might be
/// sufficient to just write that name as your regular expression.
library dart2js_info.bin.code_deps;
import 'dart:collection';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/graph.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'usage_exception.dart';
class CodeDepsCommand extends Command<void> with PrintUsageException {
final String name = "code_deps";
final String description = "";
CodeDepsCommand() {
addSubcommand(new _SomePathQuery());
}
}
class _SomePathQuery extends Command<void> with PrintUsageException {
final String name = "some_path";
final String description = "find a call-graph path between two elements.";
@override
void run() async {
var args = argResults.rest;
if (args.length < 3) {
usageException("Missing arguments for some_path, expected: "
"info.data <element-regexp-1> <element-regexp-2>");
return;
}
var info = await infoFromFile(args.first);
var graph = graphFromInfo(info);
var source = info.functions
.firstWhere(_longNameMatcher(new RegExp(args[1])), orElse: () => null);
var target = info.functions
.firstWhere(_longNameMatcher(new RegExp(args[2])), orElse: () => null);
print('query: some_path');
if (source == null) {
usageException("source '${args[1]}' not found in '${args[0]}'");
}
print('source: ${longName(source)}');
if (target == null) {
usageException("target '${args[2]}' not found in '${args[0]}'");
}
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])}');
}
}
}
}
/// 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 = <Info, Info>{source: null};
var queue = new Queue<Info>();
queue.addLast(source);
while (queue.isNotEmpty) {
var node = queue.removeFirst();
if (identical(node, target)) {
var result = new Queue<Info>();
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 [];
}
}
typedef bool LongNameMatcher(FunctionInfo info);
LongNameMatcher _longNameMatcher(RegExp regexp) =>
(e) => regexp.hasMatch(longName(e));

View file

@ -0,0 +1,38 @@
// Copyright (c) 2019, 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.
import 'package:args/command_runner.dart';
import 'to_json.dart' show ToJsonCommand;
import 'to_binary.dart' show ToBinaryCommand;
import 'to_proto.dart' show ToProtoCommand;
import 'usage_exception.dart';
/// This tool reports how code is divided among deferred chunks.
class ConvertCommand extends Command<void> with PrintUsageException {
final String name = "convert";
final String description = "Convert between info formats.";
ConvertCommand() {
_addSubcommand(new ToJsonCommand());
_addSubcommand(new ToBinaryCommand());
_addSubcommand(new ToProtoCommand());
}
_addSubcommand(Command<void> command) {
addSubcommand(command);
command.argParser
..addOption('out',
abbr: 'o',
help: 'Output file '
'(to_json defauts to <input>.json, to_binary defaults to\n'
'<input>.data, and to_proto defaults to <input>.pb)')
..addFlag('inject-text',
negatable: false,
help: 'Whether to inject output code snippets.\n\n'
'By default dart2js produces code spans, but excludes the text. This\n'
'option can be used to embed the text directly in the output.\n'
'Note: this requires access to dart2js output files.\n');
}
}

View file

@ -0,0 +1,220 @@
// 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
/// depends on code that has been landed in the bleeding_edge version of dart2js
/// and that we expect to become publicly visible in version 0.13.0 of the Dart
/// SDK).
///
/// 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 value
/// -DtraceCalls=post 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 dart2js_info.bin.coverage_log_server;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as path;
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf;
import 'usage_exception.dart';
class CoverageLogServerCommand extends Command<void> with PrintUsageException {
final String name = 'coverage_server';
final String description = 'Server to gather code coverage data';
CoverageLogServerCommand() {
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')
..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);
}
void run() async {
if (argResults.rest.isEmpty) {
usageException('Missing arguments: <dart2js-out-file> [<html-file>]');
}
var jsPath = argResults.rest[0];
var htmlPath = null;
if (argResults.rest.length > 1) {
htmlPath = argResults.rest[1];
}
var outPath = argResults['out'];
if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json';
var server = new _Server(argResults['host'], int.parse(argResults['port']),
jsPath, htmlPath, outPath, argResults['uri-prefix']);
await server.run();
}
}
const _DEFAULT_OUT_TEMPLATE = '<dart2js-out-file>.coverage.json';
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';
FutureOr<shelf.Response> _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 results.
if (urlPath == _expectedPath('coverage')) {
if (request.method == 'GET') {
return new shelf.Response.ok(_serializedData, headers: TEXT_HEADERS);
}
if (request.method == 'POST') {
_record(jsonDecode(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 == 0
? ' - 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 url = prefix == '' ? 'coverage' : '$prefix/coverage';
var hook = '''
self.dartCallInstrumentation = function(id, name) {
if (!this.traceBuffer) {
this.traceBuffer = [];
}
var buffer = this.traceBuffer;
if (buffer.length == 0) {
window.setTimeout(function() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/$url");
xhr.send(JSON.stringify(buffer));
buffer.length = 0;
}, 1000);
}
buffer.push([id, name]);
};
''';
return '$hook$code';
}
const HTML_HEADERS = const {'content-type': 'text/html'};
const JS_HEADERS = const {'content-type': 'text/javascript'};
const TEXT_HEADERS = const {'content-type': 'text/plain'};

View file

@ -0,0 +1,321 @@
// 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.
/// Tool used mainly by dart2js developers to debug the generated info and check
/// that it is consistent and that it covers all the data we expect it to cover.
library dart2js_info.bin.debug_info;
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/graph.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'usage_exception.dart';
class DebugCommand extends Command<void> with PrintUsageException {
final String name = "debug";
final String description = "Dart2js-team diagnostics on a dump-info file.";
DebugCommand() {
argParser.addOption('show-library',
help: "Show detailed data for a library with the given name");
}
void run() async {
var args = argResults.rest;
if (args.length < 1) {
usageException('Missing argument: info.data');
}
var info = await infoFromFile(args.first);
var debugLibName = argResults['show-library'];
validateSize(info, debugLibName);
validateParents(info);
compareGraphs(info);
verifyDeps(info);
}
}
/// Validates that codesize of elements adds up to total codesize.
validateSize(AllInfo info, String debugLibName) {
// Gather data from visiting all info elements.
var tracker = new _SizeTracker(debugLibName);
info.accept(tracker);
// Validate that listed elements include elements of each library.
Set<Info> listed = new Set()
..addAll(info.functions)
..addAll(info.fields);
// For our sanity we do some validation of dump-info invariants
var diff1 = listed.difference(tracker.discovered);
var diff2 = tracker.discovered.difference(listed);
if (diff1.length == 0 || diff2.length == 0) {
_pass('all fields and functions are covered');
} else {
if (diff1.length > 0) {
_fail("some elements where listed globally that weren't part of any "
"library (non-zero ${diff1.where((f) => f.size > 0).length})");
}
if (diff2.length > 0) {
_fail("some elements found in libraries weren't part of the global list"
" (non-zero ${diff2.where((f) => f.size > 0).length})");
}
}
// Validate that code-size adds up.
int realTotal = info.program.size;
int totalLib = info.libraries.fold(0, (n, lib) => n + lib.size);
int constantsSize = info.constants.fold(0, (n, c) => n + c.size);
int accounted = totalLib + constantsSize;
if (accounted != realTotal) {
var percent =
((realTotal - accounted) * 100 / realTotal).toStringAsFixed(2);
_fail('$percent% size missing: $accounted (all libs + consts) '
'< $realTotal (total)');
}
int missingTotal = tracker.missing.values.fold(0, (a, b) => a + b);
if (missingTotal > 0) {
var percent = (missingTotal * 100 / realTotal).toStringAsFixed(2);
_fail('$percent% size missing in libraries (sum of elements > lib.size)');
}
}
/// Validates that every element in the model has a parent (except libraries).
validateParents(AllInfo info) {
final parentlessInfos = new Set<Info>();
failIfNoParents(List<Info> infos) {
for (var info in infos) {
if (info.parent == null) {
parentlessInfos.add(info);
}
}
}
failIfNoParents(info.functions);
failIfNoParents(info.typedefs);
failIfNoParents(info.classes);
failIfNoParents(info.fields);
failIfNoParents(info.closures);
if (parentlessInfos.isEmpty) {
_pass('all elements have a parent');
} else {
_fail('${parentlessInfos.length} elements have no parent');
}
}
class _SizeTracker extends RecursiveInfoVisitor {
/// A library name for which to print debugging information (if not null).
final String _debugLibName;
_SizeTracker(this._debugLibName);
/// [FunctionInfo]s and [FieldInfo]s transitively reachable from [LibraryInfo]
/// elements.
final Set<Info> discovered = new Set<Info>();
/// Total number of bytes missing if you look at the reported size compared
/// to the sum of the nested infos (e.g. if a class size is smaller than the
/// sum of its methods). Used for validation and debugging of the dump-info
/// invariants.
final Map<Info, int> missing = {};
/// Set of [FunctionInfo]s that appear to be unused by the app (they are not
/// registed [coverage]).
final List unused = [];
/// Tracks the current state of this visitor.
List<_State> stack = [new _State()];
/// Code discovered for a [LibraryInfo], only used for debugging.
final StringBuffer _debugCode = new StringBuffer();
int _indent = 2;
void _push() => stack.add(new _State());
void _pop(info) {
var last = stack.removeLast();
var size = last._totalSize;
if (size > info.size) {
// record dump-info inconsistencies.
missing[info] = size - info.size;
} else {
// if size < info.size, that is OK, the enclosing element might have code
// of it's own (e.g. a class declaration includes the name of the class,
// but the discovered size only counts the size of the members.)
size = info.size;
}
stack.last
.._totalSize += size
.._count += last._count
.._bodySize += last._bodySize;
}
bool _debug = false;
visitLibrary(LibraryInfo info) {
if (_debugLibName != null) _debug = info.name.contains(_debugLibName);
_push();
if (_debug) {
_debugCode.write('{\n');
_indent = 4;
}
super.visitLibrary(info);
_pop(info);
if (_debug) {
_debug = false;
_indent = 4;
_debugCode.write('}\n');
}
}
_handleCodeInfo(info) {
discovered.add(info);
var code = info.code;
if (_debug && code != null) {
bool isClosureClass = info.name.endsWith('.call');
if (isClosureClass) {
var cname = info.name.substring(0, info.name.indexOf('.'));
_debugCode.write(' ' * _indent);
_debugCode.write(cname);
_debugCode.write(': {\n');
_indent += 2;
_debugCode.write(' ' * _indent);
_debugCode.write('...\n');
}
print('$info ${isClosureClass} \n${info.code}');
_debugCode.write(' ' * _indent);
var endsInNewLine = code.endsWith('\n');
if (endsInNewLine) code = code.substring(0, code.length - 1);
_debugCode.write(code.replaceAll('\n', '\n' + (' ' * _indent)));
if (endsInNewLine) _debugCode.write(',\n');
if (isClosureClass) {
_indent -= 2;
_debugCode.write(' ' * _indent);
_debugCode.write('},\n');
}
}
stack.last._totalSize += info.size;
stack.last._bodySize += info.size;
stack.last._count++;
}
visitField(FieldInfo info) {
_handleCodeInfo(info);
super.visitField(info);
}
visitFunction(FunctionInfo info) {
_handleCodeInfo(info);
super.visitFunction(info);
}
visitTypedef(TypedefInfo info) {
if (_debug) print('$info');
stack.last._totalSize += info.size;
super.visitTypedef(info);
}
visitClass(ClassInfo info) {
if (_debug) {
print('$info');
_debugCode.write(' ' * _indent);
_debugCode.write('${info.name}: {\n');
_indent += 2;
}
_push();
super.visitClass(info);
_pop(info);
if (_debug) {
_debugCode.write(' ' * _indent);
_debugCode.write('},\n');
_indent -= 2;
}
}
}
class _State {
int _count = 0;
int _totalSize = 0;
int _bodySize = 0;
}
/// Validates that both forms of dependency information match.
void compareGraphs(AllInfo info) {
var g1 = new EdgeListGraph<Info>();
var g2 = new EdgeListGraph<Info>();
for (var f in info.functions) {
g1.addNode(f);
for (var g in f.uses) {
g1.addEdge(f, g.target);
}
g2.addNode(f);
if (info.dependencies[f] != null) {
for (var g in info.dependencies[f]) {
g2.addEdge(f, g);
}
}
}
for (var f in info.fields) {
g1.addNode(f);
for (var g in f.uses) {
g1.addEdge(f, g.target);
}
g2.addNode(f);
if (info.dependencies[f] != null) {
for (var g in info.dependencies[f]) {
g2.addEdge(f, g);
}
}
}
// Note: these checks right now show that 'uses' links are computed
// differently than 'deps' links
int inUsesNotInDependencies = 0;
int inDependenciesNotInUses = 0;
_sameEdges(f) {
var targets1 = g1.targetsOf(f).toSet();
var targets2 = g2.targetsOf(f).toSet();
inUsesNotInDependencies += targets1.difference(targets2).length;
inDependenciesNotInUses += targets2.difference(targets1).length;
}
info.functions.forEach(_sameEdges);
info.fields.forEach(_sameEdges);
if (inUsesNotInDependencies == 0 && inDependenciesNotInUses == 0) {
_pass('dependency data is consistent');
} else {
_fail('inconsistencies in dependency data:\n'
' $inUsesNotInDependencies edges missing from "dependencies" graph\n'
' $inDependenciesNotInUses edges missing from "uses" graph');
}
}
// Validates that all elements are reachable from `main` in the dependency
// graph.
verifyDeps(AllInfo info) {
var graph = graphFromInfo(info);
var entrypoint = info.program.entrypoint;
var reachables = new Set.from(graph.preOrder(entrypoint));
var functionsAndFields = []
..addAll(info.functions)
..addAll(info.fields);
var unreachables =
functionsAndFields.where((func) => !reachables.contains(func));
if (unreachables.isNotEmpty) {
_fail('${unreachables.length} elements are unreachable from the '
'entrypoint');
} else {
_pass('all elements are reachable from the entrypoint');
}
}
_pass(String msg) => print('\x1b[32mPASS\x1b[0m: $msg');
_fail(String msg) => print('\x1b[31mFAIL\x1b[0m: $msg');

View file

@ -0,0 +1,71 @@
// 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 command that verifies that deferred libraries split the code as expected.
///
/// This tool checks that the output from dart2js meets a given specification,
/// given in a YAML file. The format of the YAML file is:
///
/// main:
/// packages:
/// - some_package
/// - other_package
///
/// foo:
/// packages:
/// - foo
/// - bar
///
/// baz:
/// packages:
/// - baz
/// - quux
///
/// The YAML file consists of a list of declarations, one for each deferred
/// part expected in the output. At least one of these parts must be named
/// "main"; this is the main part that contains the program entrypoint. Each
/// top-level part contains a list of package names that are expected to be
/// contained in that part. Any package that is not explicitly listed is
/// expected to be in the main part. For instance, in the example YAML above
/// the part named "baz" is expected to contain the packages "baz" and "quux".
///
/// The names for parts given in the specification YAML file (besides "main")
/// are arbitrary and just used for reporting when the output does not meet the
/// specification.
library dart2js_info.bin.deferred_library_check;
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/deferred_library_check.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:yaml/yaml.dart';
import 'usage_exception.dart';
/// A command that computes the diff between two info files.
class DeferredLibraryCheck extends Command<void> with PrintUsageException {
final String name = "deferred_check";
final String description =
"Verify that deferred libraries are split as expected";
void run() async {
var args = argResults.rest;
if (args.length < 2) {
usageException('Missing arguments, expected: info.data manifest.yaml');
}
var info = await infoFromFile(args[0]);
var manifest = await manifestFromFile(args[1]);
var failures = checkDeferredLibraryManifest(info, manifest);
failures.forEach(print);
if (failures.isNotEmpty) exitCode = 1;
}
}
Future manifestFromFile(String fileName) async {
var file = await new File(fileName).readAsString();
return loadYaml(file);
}

View file

@ -0,0 +1,83 @@
// Copyright (c) 2016, 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.
/// This tool reports how code is divided among deferred chunks.
library dart2js_info.bin.deferred_library_layout;
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
import 'usage_exception.dart';
/// This tool reports how code is divided among deferred chunks.
class DeferredLibraryLayout extends Command<void> with PrintUsageException {
final String name = "deferred_layout";
final String description = "Show how code is divided among deferred parts.";
void run() async {
var args = argResults.rest;
if (args.length < 1) {
usageException('Missing argument: info.data');
}
await _showLayout(args.first);
}
}
_showLayout(String file) async {
AllInfo info = await infoFromFile(file);
Map<OutputUnitInfo, Map<LibraryInfo, List<BasicInfo>>> hunkMembers = {};
Map<LibraryInfo, Set<OutputUnitInfo>> libToHunks = {};
void register(BasicInfo info) {
var unit = info.outputUnit;
var lib = _libOf(info);
if (lib == null) return;
libToHunks.putIfAbsent(lib, () => new Set()).add(unit);
hunkMembers
.putIfAbsent(unit, () => {})
.putIfAbsent(lib, () => [])
.add(info);
}
info.functions.forEach(register);
info.classes.forEach(register);
info.fields.forEach(register);
info.closures.forEach(register);
var dir = Directory.current.path;
hunkMembers.forEach((unit, map) {
print('Output unit ${unit.name ?? "main"}:');
if (unit.name == null || unit.name == 'main') {
print(' loaded by default');
} else {
print(' loaded by importing: ${unit.imports}');
}
print(' contains:');
map.forEach((lib, elements) {
var uri = lib.uri;
var shortUri = (uri.scheme == 'file' && uri.path.startsWith(dir))
? uri.path.substring(dir.length + 1)
: '$uri';
// If the entire library is in one chunk, just report the library name
// otherwise report which functions are on this chunk.
if (libToHunks[lib].length == 1) {
print(' - $shortUri');
} else {
print(' - $shortUri:');
for (var e in elements) {
print(' - ${kindToString(e.kind)} ${e.name}');
}
}
});
print('');
});
}
_libOf(e) => e is LibraryInfo || e == null ? e : _libOf(e.parent);

View file

@ -0,0 +1,90 @@
// Copyright (c) 2016, 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.
/// This tool gives a breakdown of code size by deferred part in the program.
library dart2js_info.bin.deferred_library_size;
import 'dart:math';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
import 'usage_exception.dart';
/// This tool gives a breakdown of code size by deferred part in the program.
class DeferredLibrarySize extends Command<void> with PrintUsageException {
final String name = "deferred_size";
final String description = "Show breakdown of codesize by deferred part.";
void run() async {
var args = argResults.rest;
if (args.length < 1) {
usageException('Missing argument: info.data');
}
// TODO(het): Would be faster to only parse the 'outputUnits' part
var info = await infoFromFile(args.first);
var sizeByImport = getSizeByImport(info);
printSizes(sizeByImport, info.program.size);
}
}
class ImportSize {
final String import;
final int size;
const ImportSize(this.import, this.size);
String toString() {
return '$import: $size';
}
}
void printSizes(Map<String, int> sizeByImport, int programSize) {
var importSizes = <ImportSize>[];
sizeByImport.forEach((import, size) {
importSizes.add(new ImportSize(import, size));
});
// Sort by size, largest first.
importSizes.sort((a, b) => b.size - a.size);
int longest = importSizes.fold('Percent of code deferred'.length,
(longest, importSize) => max(longest, importSize.import.length));
_printRow(label, data, {int width: 15}) {
print('${label.toString().padRight(longest + 1)}'
'${data.toString().padLeft(width)}');
}
print('');
print('Size by library');
print('-' * (longest + 16));
for (var importSize in importSizes) {
// TODO(het): split into specific and shared size
_printRow(importSize.import, importSize.size);
}
print('-' * (longest + 16));
var mainChunkSize = sizeByImport['main'];
var deferredSize = programSize - mainChunkSize;
var percentDeferred = (deferredSize * 100 / programSize).toStringAsFixed(2);
_printRow('Main chunk size', mainChunkSize);
_printRow('Deferred code size', deferredSize);
_printRow('Percent of code deferred', '$percentDeferred%');
}
Map<String, int> getSizeByImport(AllInfo info) {
var sizeByImport = <String, int>{};
for (var outputUnit in info.outputUnits) {
if (outputUnit.name == 'main' || outputUnit.name == null) {
sizeByImport['main'] = outputUnit.size;
} else {
for (var import in outputUnit.imports) {
sizeByImport.putIfAbsent(import, () => 0);
sizeByImport[import] += outputUnit.size;
}
}
}
return sizeByImport;
}

View file

@ -0,0 +1,183 @@
// Copyright (c) 2017, 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.
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/diff.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'usage_exception.dart';
/// A command that computes the diff between two info files.
class DiffCommand extends Command<void> with PrintUsageException {
final String name = "diff";
final String description =
"See code size differences between two dump-info files.";
DiffCommand() {
argParser.addFlag('summary-only',
defaultsTo: false,
help: "Show only a summary and hide details of each library");
}
void run() async {
var args = argResults.rest;
if (args.length < 2) {
usageException(
'Missing arguments, expected two dump-info files to compare');
return;
}
var oldInfo = await infoFromFile(args[0]);
var newInfo = await infoFromFile(args[1]);
var summaryOnly = argResults['summary-only'];
var diffs = diff(oldInfo, newInfo);
// Categorize the diffs
var adds = <AddDiff>[];
var removals = <RemoveDiff>[];
var sizeChanges = <SizeDiff>[];
var becameDeferred = <DeferredStatusDiff>[];
var becameUndeferred = <DeferredStatusDiff>[];
for (var diff in diffs) {
switch (diff.kind) {
case DiffKind.add:
adds.add(diff as AddDiff);
break;
case DiffKind.remove:
removals.add(diff as RemoveDiff);
break;
case DiffKind.size:
sizeChanges.add(diff as SizeDiff);
break;
case DiffKind.deferred:
var deferredDiff = diff as DeferredStatusDiff;
if (deferredDiff.wasDeferredBefore) {
becameUndeferred.add(deferredDiff);
} else {
becameDeferred.add(deferredDiff);
}
break;
}
}
// Sort the changes by the size of the element that changed.
for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) {
diffs.sort((a, b) => b.info.size - a.info.size);
}
// Sort changes in size by size difference.
sizeChanges.sort((a, b) => b.sizeDifference - a.sizeDifference);
var totalSizes = <List<Diff>, int>{};
for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) {
var totalSize = 0;
for (var diff in diffs) {
// Only count diffs from leaf elements so we don't double count
// them when we account for class size diff or library size diff.
if (diff.info.kind == InfoKind.field ||
diff.info.kind == InfoKind.function ||
diff.info.kind == InfoKind.closure ||
diff.info.kind == InfoKind.typedef) {
totalSize += diff.info.size;
}
}
totalSizes[diffs] = totalSize;
}
var totalSizeChange = 0;
for (var sizeChange in sizeChanges) {
// Only count diffs from leaf elements so we don't double count
// them when we account for class size diff or library size diff.
if (sizeChange.info.kind == InfoKind.field ||
sizeChange.info.kind == InfoKind.function ||
sizeChange.info.kind == InfoKind.closure ||
sizeChange.info.kind == InfoKind.typedef) {
totalSizeChange += sizeChange.sizeDifference;
}
}
totalSizes[sizeChanges] = totalSizeChange;
reportSummary(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred,
becameUndeferred, totalSizes);
if (!summaryOnly) {
print('');
reportFull(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred,
becameUndeferred, totalSizes);
}
}
}
void reportSummary(
AllInfo oldInfo,
AllInfo newInfo,
List<AddDiff> adds,
List<RemoveDiff> removals,
List<SizeDiff> sizeChanges,
List<DeferredStatusDiff> becameDeferred,
List<DeferredStatusDiff> becameUndeferred,
Map<List<Diff>, int> totalSizes) {
var overallSizeDiff = newInfo.program.size - oldInfo.program.size;
print('total_size_difference $overallSizeDiff');
print('total_added ${totalSizes[adds]}');
print('total_removed ${totalSizes[removals]}');
print('total_size_changed ${totalSizes[sizeChanges]}');
print('total_became_deferred ${totalSizes[becameDeferred]}');
print('total_no_longer_deferred ${totalSizes[becameUndeferred]}');
}
void reportFull(
AllInfo oldInfo,
AllInfo newInfo,
List<AddDiff> adds,
List<RemoveDiff> removals,
List<SizeDiff> sizeChanges,
List<DeferredStatusDiff> becameDeferred,
List<DeferredStatusDiff> becameUndeferred,
Map<List<Diff>, int> totalSizes) {
// TODO(het): Improve this output. Siggi has good suggestions in
// https://github.com/dart-lang/dart2js_info/pull/19
_section('ADDED', size: totalSizes[adds]);
for (var add in adds) {
print('${longName(add.info, useLibraryUri: true)}: ${add.info.size} bytes');
}
print('');
_section('REMOVED', size: totalSizes[removals]);
for (var removal in removals) {
print('${longName(removal.info, useLibraryUri: true)}: '
'${removal.info.size} bytes');
}
print('');
_section('CHANGED SIZE', size: totalSizes[sizeChanges]);
for (var sizeChange in sizeChanges) {
print('${longName(sizeChange.info, useLibraryUri: true)}: '
'${sizeChange.sizeDifference} bytes');
}
print('');
_section('BECAME DEFERRED', size: totalSizes[becameDeferred]);
for (var diff in becameDeferred) {
print('${longName(diff.info, useLibraryUri: true)}: '
'${diff.info.size} bytes');
}
print('');
_section('NO LONGER DEFERRED', size: totalSizes[becameUndeferred]);
for (var diff in becameUndeferred) {
print('${longName(diff.info, useLibraryUri: true)}: '
'${diff.info.size} bytes');
}
}
void _section(String title, {int size}) {
print('$title ($size bytes)');
print('=' * 72);
}

View file

@ -0,0 +1,181 @@
// 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.
/// Tool presenting how much each function contributes to the total code.
library compiler.tool.function_size_analysis;
import 'dart:math' as math;
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/graph.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'usage_exception.dart';
/// Command presenting how much each function contributes to the total code.
class FunctionSizeCommand extends Command<void> with PrintUsageException {
final String name = "function_size";
final String description = "See breakdown of code size by function.";
void run() async {
var args = argResults.rest;
if (args.length < 1) {
usageException('Missing argument: info.data');
}
var info = await infoFromFile(args.first);
showCodeDistribution(info);
}
}
showCodeDistribution(AllInfo info,
{bool filter(Info info), bool showLibrarySizes: false}) {
var realTotal = info.program.size;
if (filter == null) filter = (i) => true;
var reported = <BasicInfo>[]
..addAll(info.functions.where(filter))
..addAll(info.fields.where(filter));
// Compute a graph from the dependencies in [info].
Graph<Info> graph = graphFromInfo(info);
// Compute the strongly connected components and calculate their size.
var components = graph.computeTopologicalSort();
print('total elements: ${graph.nodes.length}');
print('total strongly connected components: ${components.length}');
var maxS = 0;
var totalCount = graph.nodeCount;
var minS = totalCount;
var nodeData = {};
for (var scc in components) {
var sccData = new _SccData();
maxS = math.max(maxS, scc.length);
minS = math.min(minS, scc.length);
for (var f in scc) {
sccData.size += f.size;
for (var g in graph.targetsOf(f)) {
var gData = nodeData[g];
if (gData != null) sccData.deps.add(gData);
}
}
for (var f in scc) {
nodeData[f] = sccData;
}
}
print('scc sizes: min: $minS, max: $maxS, '
'avg ${totalCount / components.length}');
// Compute a dominator tree and calculate the size dominated by each element.
// TODO(sigmund): we need a more reliable way to fetch main.
var mainMethod = info.functions.firstWhere((f) => f.name == 'main');
var dominatorTree = graph.dominatorTree(mainMethod);
var dominatedSize = {};
helper(n) {
int size = n.size;
assert(!dominatedSize.containsKey(n));
dominatedSize[n] = 0;
dominatorTree.targetsOf(n).forEach((m) {
size += helper(m);
});
dominatedSize[n] = size;
return size;
}
helper(mainMethod);
reported.forEach((n) => dominatedSize.putIfAbsent(n, () => n.size));
reported.sort((a, b) =>
(dominatedSize[b] + nodeData[b].maxSize) -
(dominatedSize[a] + nodeData[a].maxSize));
if (showLibrarySizes) {
print(' --- Results per library ---');
var totals = <LibraryInfo, int>{};
var longest = 0;
reported.forEach((info) {
var size = info.size;
while (info != null && info is! LibraryInfo) {
info = info.parent;
}
if (info == null) return;
LibraryInfo lib = info;
totals.putIfAbsent(lib, () => 0);
totals[lib] += size;
longest = math.max(longest, '${lib.uri}'.length);
});
_showLibHeader(longest + 1);
var reportedByLibrary = totals.keys.toList();
reportedByLibrary.sort((a, b) => totals[b] - totals[a]);
reportedByLibrary.forEach((info) {
_showLib('${info.uri}', totals[info], realTotal, longest + 1);
});
}
print('\n --- Results per element (field or function) ---');
_showElementHeader();
reported.forEach((info) {
var size = info.size;
var min = dominatedSize[info];
var max = nodeData[info].maxSize;
_showElement(
longName(info, useLibraryUri: true), size, min, max, realTotal);
});
}
/// Data associated with an SCC. Used to compute the reachable code size.
class _SccData {
int size = 0;
Set deps = new Set();
_SccData();
int _maxSize;
int get maxSize {
compute();
return _maxSize;
}
void compute() {
if (_maxSize != null) return;
var max = 0;
var seen = new Set();
helper(n) {
if (!seen.add(n)) return;
max += n.size;
n.deps.forEach(helper);
}
helper(this);
_maxSize = max;
}
}
_showLibHeader(int namePadding) {
print(' ${pad("Library", namePadding, right: true)}'
' ${pad("bytes", 8)} ${pad("%", 6)}');
}
_showLib(String msg, int size, int total, int namePadding) {
var percent = (size * 100 / total).toStringAsFixed(2);
print(' ${pad(msg, namePadding, right: true)}'
' ${pad(size, 8)} ${pad(percent, 6)}%');
}
_showElementHeader() {
print('${pad("element size", 16)} '
'${pad("dominated size", 18)} '
'${pad("reachable size", 18)} '
'Element identifier');
}
_showElement(String name, int size, int dominatedSize, int maxSize, int total) {
var percent = (size * 100 / total).toStringAsFixed(2);
var minPercent = (dominatedSize * 100 / total).toStringAsFixed(2);
var maxPercent = (maxSize * 100 / total).toStringAsFixed(2);
print('${pad(size, 8)} ${pad(percent, 6)}% '
'${pad(dominatedSize, 10)} ${pad(minPercent, 6)}% '
'${pad(maxSize, 10)} ${pad(maxPercent, 6)}% '
'$name');
}

View file

@ -0,0 +1,40 @@
// Copyright (c) 2019, 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.
import 'dart:io';
import 'package:dart2js_info/info.dart';
/// Modify [info] to fill in the text of code spans.
///
/// By default, code spans contains the offsets but omit the text
/// (`CodeSpan.text` is null). This function reads the output files emitted by
/// dart2js to extract the code denoted by each span.
void injectText(AllInfo info) {
// Fill the text of each code span. The binary form produced by dart2js
// produces code spans, but excludes the orignal text
info.functions.forEach((f) {
f.code.forEach((span) => _fillSpan(span, f.outputUnit));
});
info.fields.forEach((f) {
f.code.forEach((span) => _fillSpan(span, f.outputUnit));
});
info.constants.forEach((c) {
c.code.forEach((span) => _fillSpan(span, c.outputUnit));
});
}
Map<String, String> _cache = {};
_getContents(OutputUnitInfo unit) => _cache.putIfAbsent(unit.filename, () {
var uri = Uri.base.resolve(unit.filename);
return new File.fromUri(uri).readAsStringSync();
});
_fillSpan(CodeSpan span, OutputUnitInfo unit) {
if (span.text == null && span.start != null && span.end != 0) {
var contents = _getContents(unit);
span.text = contents.substring(span.start, span.end);
}
}

View file

@ -0,0 +1,230 @@
// 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. You can
/// specify what regular expressions to use by providing a `grouping.yaml` file.
/// The format of the `grouping.yaml` file is as follows:
/// ```yaml
/// groups:
/// - { regexp: "package:(foo)/*.dart", name: "group name 1", cluster: 2}
/// - { regexp: "dart:.*", name: "group name 2", cluster: 3}
/// ```
/// The file should include a single key `groups` containing a list of group
/// specifications. Each group is specified by a map of 3 entries:
///
/// * regexp (required): a regexp used to match entries that belong to the
/// group.
///
/// * name (optional): the name given to this group in the output table. If
/// omitted, the name is derived from the regexp as the match's group(1) or
/// group(0) if no group was defined. When names are omitted the group
/// specification implicitly defines several groups, one per observed name.
///
/// * cluster (optional): a clustering index for how data is shown in a table.
/// Groups with higher cluster indices are shown later in the table after a
/// dividing line. If missing, the cluster index defaults to 0.
///
/// Here is an example configuration, with comments about what each entry does:
///
/// ```yaml
/// groups:
/// # This group shows the total size for all libraries that were loaded from
/// # file:// urls, it is shown in cluster #2, which happens to be the last
/// # cluster in this example before the totals are shown:
/// - { name: "Loose files", regexp: "file://.*", cluster: 2}
///
/// # This group shows the total size of all code loaded from packages:
/// - { name: "All packages", regexp: "package:.*", cluster: 2}
///
/// # This group shows the total size of all code loaded from core libraries:
/// - { name: "Core libs", regexp: "dart:.*", cluster: 2}
///
/// # This group shows the total size of all libraries in a single package. Here
/// # we omitted the `name` entry, instead we extract it from the regexp
/// # directly. In this case, the name will be the package-name portion of the
/// # package-url (determined by group(1) of the regexp).
/// - { regexp: "package:([^/]*)", cluster: 1}
///
/// # The next two groups match the entire library url as the name of the group.
/// - regexp: "package:.*"
/// - regexp: "dart:.*"
///
/// # If your code lives under /my/project/dir, this will match any file loaded
/// from a file:// url, and we use as a name the relative path to it.
/// - regexp: "file:///my/project/dir/(.*)"
///```
///
/// This example is very similar to [defaultGrouping].
library dart2js_info.bin.library_size_split;
import 'dart:io';
import 'dart:math' show max;
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:yaml/yaml.dart';
import 'usage_exception.dart';
/// Command presenting how much each library contributes to the total code.
class LibrarySizeCommand extends Command<void> with PrintUsageException {
final String name = "library_size";
final String description = "See breakdown of code size by library.";
LibrarySizeCommand() {
argParser.addOption('grouping',
help: 'YAML file specifying how libraries should be grouped.');
}
void run() async {
var args = argResults.rest;
if (args.length < 1) {
usageException('Missing argument: info.data');
print('usage: dart tool/library_size_split.dart '
'path-to-info.json [grouping.yaml]');
exit(1);
}
var info = await infoFromFile(args.first);
var groupingFile = argResults['grouping'];
var groupingText = groupingFile != null
? new File(groupingFile).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 = {};
var allLibs = 0;
for (LibraryInfo lib in info.libraries) {
allLibs += lib.size;
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 allConstants = 0;
for (var constant in info.constants) {
allConstants += constant.size;
}
var all = sizes.keys.toList();
all.sort((a, b) => sizes[a].compareTo(sizes[b]));
var realTotal = info.program.size;
var longest = 0;
var rows = <_Row>[];
_addRow(String label, int value) {
rows.add(new _Row(label, value));
longest = max(longest, label.length);
}
_printRow(_Row row) {
if (row is _Divider) {
print(' ' + ('-' * (longest + 18)));
return;
}
var percent = row.value == realTotal
? '100'
: (row.value * 100 / realTotal).toStringAsFixed(2);
print(' ${_pad(row.label, longest + 1, right: true)}'
' ${_pad(row.value, 8)} ${_pad(percent, 6)}%');
}
var lastCluster = 0;
for (var name in all) {
var entry = sizes[name];
if (lastCluster < entry.cluster) {
rows.add(const _Divider());
lastCluster = entry.cluster;
}
var size = entry.size;
_addRow(name, size);
}
rows.add(const _Divider());
_addRow("All libraries (excludes preambles, statics & consts)", allLibs);
_addRow("Shared consts", allConstants);
_addRow("Total accounted", allLibs + allConstants);
_addRow("Program Size", realTotal);
rows.forEach(_printRow);
}
}
/// 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;
}
class _Row {
final String label;
final int value;
const _Row(this.label, this.value);
}
class _Divider extends _Row {
const _Divider() : super('', 0);
}
_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';
}
/// Default grouping specification that includes an entry per library, and
/// grouping entries for each package, all packages, all core libs, and loose
/// files.
final defaultGrouping = """
groups:
- { 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}/(.*)" }
""";

View file

@ -0,0 +1,138 @@
// 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 presenting combined information from dump-info and
/// coverage data.
///
/// This tool requires two input files an `.info.json` and a
/// `.coverage.json` file. To produce these files you need to follow these
/// steps:
///
/// * Compile an app with dart2js using --dump-info and defining the
/// Dart environment `traceCalls=post`:
///
/// DART_VM_OPTIONS="-DtraceCalls=post" dart2js --dump-info main.dart
///
/// Because coverage/tracing data is currently experimental, the feature is
/// not exposed as a flag in dart2js, but you can enable it using the Dart
/// environment flag. The flag only works dart2js version 1.13.0 or newer.
///
/// * Launch the coverage server tool (in this package) to serve up the
/// Javascript code in your app:
///
/// dart tool/coverage_log_server.dart main.dart.js
///
/// * (optional) If you have a complex application setup, integrate your
/// application server to proxy to the log server any GET request for the
/// .dart.js file and /coverage POST requests that send coverage data.
///
/// * Load your app and use it to exercise the entire code.
///
/// * Shut down the coverage server (Ctrl-C)
///
/// * Finally, run this tool.
library compiler.tool.live_code_size_analysis;
import 'dart:convert';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'function_size_analysis.dart';
import 'usage_exception.dart';
class LiveCodeAnalysisCommand extends Command<void> with PrintUsageException {
final String name = "coverage_analysis";
final String description = "Analyze coverage data collected via the"
" 'coverage_server' command";
LiveCodeAnalysisCommand() {
argParser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'Show verbose details.');
}
void run() async {
var args = argResults.rest;
if (args.length < 2) {
usageException('Missing arguments, expected: info.data coverage.json');
}
await _liveCodeAnalysis(args[0], args[1], argResults['verbose']);
}
}
_liveCodeAnalysis(infoFile, coverageFile, bool verbose) async {
var info = await infoFromFile(infoFile);
var coverage = jsonDecode(new File(coverageFile).readAsStringSync());
int realTotal = info.program.size;
int totalLib = info.libraries.fold(0, (n, lib) => n + lib.size);
int totalCode = 0;
int reachableCode = 0;
List<Info> unused = [];
void tallyCode(Info f) {
totalCode += f.size;
var data = coverage[f.coverageId];
if (data != null) {
// Validate that the name match, it might not match if using a different
// version of the app.
// TODO(sigmund): use the same name.
// TODO(sigmund): inject a time-stamp in the code and dumpinfo and
// validate just once.
var name = f.name;
if (name.contains('.')) name = name.substring(name.lastIndexOf('.') + 1);
var otherName = data['name'];
if (otherName.contains('.')) {
otherName = otherName.substring(otherName.lastIndexOf('.') + 1);
}
if (otherName != name && otherName != '') {
print('invalid coverage: $data for $f, ($name vs $otherName)');
}
reachableCode += f.size;
} else {
// we should track more precisely data about inlined functions
unused.add(f);
}
}
info.functions.forEach(tallyCode);
info.fields.forEach(tallyCode);
_showHeader('', 'bytes', '%');
_show('Program size', realTotal, realTotal);
_show('Libraries (excluding statics)', totalLib, realTotal);
_show('Code (functions + fields)', totalCode, realTotal);
_show('Reachable code', reachableCode, realTotal);
print('');
_showHeader('', 'count', '%');
var total = info.functions.length + info.fields.length;
_show('Functions + fields', total, total);
_show('Reachable', total - unused.length, total);
// TODO(sigmund): support grouping results by package.
if (verbose) {
print('\nDistribution of code that was not used when running the app:');
showCodeDistribution(info,
filter: (f) => !coverage.containsKey(f.coverageId) && f.size > 0,
showLibrarySizes: true);
} else {
print('\nUse `-v` to see details about the size of unreachable code');
}
}
_showHeader(String msg, String header1, String header2) {
print(' ${pad(msg, 30, right: true)} ${pad(header1, 8)} ${pad(header2, 6)}');
}
_show(String msg, int size, int total) {
var percent = (size * 100 / total).toStringAsFixed(2);
print(' ${pad(msg, 30, right: true)} ${pad(size, 8)} ${pad(percent, 6)}%');
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2016, 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.
/// Simple script that shows the inferred types of a function.
library compiler.tool.show_inferred_types;
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/src/util.dart';
import 'package:dart2js_info/src/io.dart';
import 'usage_exception.dart';
class ShowInferredTypesCommand extends Command<void> with PrintUsageException {
final String name = "show_inferred";
final String description = "Show data inferred by dart2js global inference";
ShowInferredTypesCommand() {
argParser.addFlag('long-names',
abbr: 'l', negatable: false, help: 'Show long qualified names.');
}
void run() async {
var args = argResults.rest;
if (args.length < 2) {
usageException(
'Missing arguments, expected: info.data <function-name-regex>');
}
_showInferredTypes(args[0], args[1], argResults['long-names']);
}
}
_showInferredTypes(String infoFile, String pattern, bool showLongName) async {
var info = await infoFromFile(infoFile);
var nameRegExp = new RegExp(pattern);
matches(e) => nameRegExp.hasMatch(longName(e));
bool noResults = true;
void showMethods() {
var sources = info.functions.where(matches).toList();
if (sources.isEmpty) return;
noResults = false;
for (var s in sources) {
var params = s.parameters.map((p) => '${p.name}: ${p.type}').join(', ');
var name = showLongName ? longName(s) : s.name;
print('$name($params): ${s.returnType}');
}
}
void showFields() {
var sources = info.fields.where(matches).toList();
if (sources.isEmpty) return;
noResults = false;
for (var s in sources) {
var name = showLongName ? longName(s) : s.name;
print('$name: ${s.inferredType}');
}
}
showMethods();
showFields();
if (noResults) {
print('error: no function or field that matches $pattern was found.');
exit(1);
}
}

View file

@ -0,0 +1,210 @@
// Copyright (c) 2019, 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.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/util.dart';
import 'package:dart2js_info/src/io.dart';
import 'inject_text.dart';
import 'usage_exception.dart';
/// Shows the contents of an info file as text.
class ShowCommand extends Command<void> with PrintUsageException {
final String name = "show";
final String description = "Show a text representation of the info file.";
ShowCommand() {
argParser.addOption('out',
abbr: 'o', help: 'Output file (defauts to stdout)');
argParser.addFlag('inject-text',
negatable: false,
help: 'Whether to inject output code snippets.\n\n'
'By default dart2js produces code spans, but excludes the text. This\n'
'option can be used to embed the text directly in the output.');
}
void run() async {
if (argResults.rest.length < 1) {
usageException('Missing argument: <input-info>');
}
String filename = argResults.rest[0];
AllInfo info = await infoFromFile(filename);
if (argResults['inject-text']) injectText(info);
var buffer = new StringBuffer();
info.accept(new TextPrinter(buffer, argResults['inject-text']));
var outputPath = argResults['out'];
if (outputPath == null) {
print(buffer);
} else {
new File(outputPath).writeAsStringSync('$buffer');
}
}
}
class TextPrinter implements InfoVisitor<void> {
final StringBuffer buffer;
final bool injectText;
TextPrinter(this.buffer, this.injectText);
int _indent = 0;
String get _textIndent => " " * _indent;
void _writeIndentation() {
buffer.write(_textIndent);
}
void _writeIndented(String s) {
_writeIndentation();
buffer.writeln(s.replaceAll('\n', '\n$_textIndent'));
}
void _writeBlock(String s, void f()) {
_writeIndented("$s");
_indent++;
f();
_indent--;
}
void visitAll(AllInfo info) {
_writeBlock("Summary data", () => visitProgram(info.program));
buffer.writeln();
_writeBlock("Libraries", () => info.libraries.forEach(visitLibrary));
// Note: classes, functions, typedefs, and fields are group;ed by library.
if (injectText) {
_writeBlock("Constants", () => info.constants.forEach(visitConstant));
} else {
int size = info.constants.fold(0, (n, c) => n + c.size);
_writeIndented("All constants: ${_size(size)}");
}
_writeBlock("Output units", () => info.outputUnits.forEach(visitOutput));
}
void visitProgram(ProgramInfo info) {
_writeIndented('main: ${longName(info.entrypoint, useLibraryUri: true)}');
_writeIndented('size: ${info.size}');
_writeIndented('dart2js-version: ${info.dart2jsVersion}');
var features = [];
if (info.noSuchMethodEnabled) features.add('no-such-method');
if (info.isRuntimeTypeUsed) features.add('runtime-type');
if (info.isFunctionApplyUsed) features.add('function-apply');
if (info.minified) features.add('minified');
if (features.isNotEmpty) {
_writeIndented('features: ${features.join(' ')}');
}
}
String _size(int size) {
if (size < 1024) return "$size b";
if (size < (1024 * 1024)) {
return "${(size / 1024).toStringAsFixed(2)} Kb ($size b)";
}
return "${(size / (1024 * 1024)).toStringAsFixed(2)} Mb ($size b)";
}
void visitLibrary(LibraryInfo info) {
_writeBlock('${info.uri}: ${_size(info.size)}', () {
if (info.topLevelFunctions.isNotEmpty) {
_writeBlock('Top-level functions',
() => info.topLevelFunctions.forEach(visitFunction));
buffer.writeln();
}
if (info.topLevelVariables.isNotEmpty) {
_writeBlock('Top-level variables',
() => info.topLevelVariables.forEach(visitField));
buffer.writeln();
}
if (info.classes.isNotEmpty) {
_writeBlock('Classes', () => info.classes.forEach(visitClass));
}
if (info.typedefs.isNotEmpty) {
_writeBlock("Typedefs", () => info.typedefs.forEach(visitTypedef));
buffer.writeln();
}
buffer.writeln();
});
}
void visitClass(ClassInfo info) {
_writeBlock(
'${info.name}: ${_size(info.size)} [${info.outputUnit.filename}]', () {
if (info.functions.isNotEmpty) {
_writeBlock('Methods:', () => info.functions.forEach(visitFunction));
}
if (info.fields.isNotEmpty) {
_writeBlock('Fields:', () => info.fields.forEach(visitField));
}
if (info.functions.isNotEmpty || info.fields.isNotEmpty) buffer.writeln();
});
}
void visitField(FieldInfo info) {
_writeBlock('${info.type} ${info.name}: ${_size(info.size)}', () {
_writeIndented('inferred type: ${info.inferredType}');
if (injectText) _writeBlock("code:", () => _writeCode(info.code));
if (info.closures.isNotEmpty) {
_writeBlock('Closures:', () => info.closures.forEach(visitClosure));
}
if (info.uses.isNotEmpty) {
_writeBlock('Dependencies:', () => info.uses.forEach(showDependency));
}
});
}
void visitFunction(FunctionInfo info) {
var outputUnitFile = '';
if (info.functionKind == FunctionInfo.TOP_LEVEL_FUNCTION_KIND) {
outputUnitFile = ' [${info.outputUnit.filename}]';
}
String params =
info.parameters.map((p) => "${p.declaredType} ${p.name}").join(', ');
_writeBlock(
'${info.returnType} ${info.name}($params): ${_size(info.size)}$outputUnitFile',
() {
String params = info.parameters.map((p) => "${p.type}").join(', ');
_writeIndented('declared type: ${info.type}');
_writeIndented(
'inferred type: ${info.inferredReturnType} Function($params)');
_writeIndented('side effects: ${info.sideEffects}');
if (injectText) _writeBlock("code:", () => _writeCode(info.code));
if (info.closures.isNotEmpty) {
_writeBlock('Closures:', () => info.closures.forEach(visitClosure));
}
if (info.uses.isNotEmpty) {
_writeBlock('Dependencies:', () => info.uses.forEach(showDependency));
}
});
}
void showDependency(DependencyInfo info) {
var mask = info.mask ?? '';
_writeIndented('- ${longName(info.target, useLibraryUri: true)} $mask');
}
void visitTypedef(TypedefInfo info) {
_writeIndented('${info.name}: ${info.type}');
}
void visitClosure(ClosureInfo info) {
_writeBlock('${info.name}', () => visitFunction(info.function));
}
void visitConstant(ConstantInfo info) {
_writeBlock('${_size(info.size)}:', () => _writeCode(info.code));
}
void _writeCode(List<CodeSpan> code) {
_writeIndented(code.map((c) => c.text).join('\n'));
}
void visitOutput(OutputUnitInfo info) {
_writeIndented('${info.filename}: ${_size(info.size)}');
}
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2019, 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.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/binary_serialization.dart' as binary;
import 'package:dart2js_info/src/io.dart';
import 'inject_text.dart';
import 'usage_exception.dart';
/// Converts a dump-info file emitted by dart2js in JSON to binary format.
class ToBinaryCommand extends Command<void> with PrintUsageException {
final String name = "to_binary";
final String description = "Convert any info file to binary format.";
void run() async {
if (argResults.rest.length < 1) {
usageException('Missing argument: <input-info>');
exit(1);
}
String filename = argResults.rest[0];
AllInfo info = await infoFromFile(filename);
if (argResults['inject-text']) injectText(info);
String outputFilename = argResults['out'] ?? '$filename.data';
var outstream = new File(outputFilename).openWrite();
binary.encode(info, outstream);
await outstream.done;
}
}

View file

@ -0,0 +1,54 @@
// Copyright (c) 2019, 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.
import 'dart:io';
import 'dart:convert';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/json_info_codec.dart';
import 'package:dart2js_info/src/io.dart';
import 'inject_text.dart';
import 'usage_exception.dart';
/// Converts a dump-info file emitted by dart2js in binary format to JSON.
class ToJsonCommand extends Command<void> with PrintUsageException {
final String name = "to_json";
final String description = "Convert any info file to JSON format.";
ToJsonCommand() {
argParser.addFlag('compat-mode',
negatable: false,
help: 'Whether to generate an older version of the JSON format.\n\n'
'By default files are converted to the latest JSON format, but\n'
'passing `--compat-mode` will produce a JSON file that may still\n'
'work in the visualizer tool at:\n'
'https://dart-lang.github.io/dump-info-visualizer/.\n\n'
'This option enables `--inject-text` as well, but note that\n'
'files produced in this mode do not contain all the data\n'
'available in the input file.');
}
void run() async {
if (argResults.rest.length < 1) {
usageException('Missing argument: <input-info>');
}
String filename = argResults.rest[0];
bool isBackwardCompatible = argResults['compat-mode'];
AllInfo info = await infoFromFile(filename);
if (isBackwardCompatible || argResults['inject-text']) {
injectText(info);
}
var json = new AllInfoJsonCodec(isBackwardCompatible: isBackwardCompatible)
.encode(info);
String outputFilename = argResults['out'] ?? '$filename.json';
new File(outputFilename)
.writeAsStringSync(const JsonEncoder.withIndent(" ").convert(json));
}
}

View file

@ -0,0 +1,37 @@
// Copyright (c) 2018, 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 convert an info.json file ouputted by dart2js to the
/// alternative protobuf format.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/proto_info_codec.dart';
import 'package:dart2js_info/src/io.dart';
import 'inject_text.dart';
import 'usage_exception.dart';
/// Converts a dump-info file emitted by dart2js to the proto format
class ToProtoCommand extends Command<void> with PrintUsageException {
final String name = "to_proto";
final String description = "Convert any info file to proto format.";
void run() async {
if (argResults.rest.length < 1) {
usageException('Missing argument: <input-info>');
exit(1);
}
String filename = argResults.rest[0];
final info = await infoFromFile(filename);
if (argResults['inject-text']) injectText(info);
final proto = new AllInfoProtoCodec().encode(info);
String outputFilename = argResults['out'] ?? '$filename.pb';
final outputFile = new File(outputFilename);
await outputFile.writeAsBytes(proto.writeToBuffer(), mode: FileMode.write);
}
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2019, 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.
import 'dart:io';
import 'package:args/command_runner.dart';
abstract class PrintUsageException implements Command<void> {
// TODO(rnystrom): Use "Never" for the return type when this package is
// migrated to null safety.
usageException(String message) {
print(message);
printUsage();
exit(1);
}
}

View file

@ -0,0 +1,39 @@
// Copyright (c) 2019, 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.
import 'package:args/command_runner.dart';
import 'src/code_deps.dart';
import 'src/coverage_log_server.dart';
import 'src/debug_info.dart';
import 'src/diff.dart';
import 'src/deferred_library_check.dart';
import 'src/deferred_library_size.dart';
import 'src/deferred_library_layout.dart';
import 'src/convert.dart';
import 'src/function_size_analysis.dart';
import 'src/library_size_split.dart';
import 'src/live_code_size_analysis.dart';
import 'src/show_inferred_types.dart';
import 'src/text_print.dart';
/// Entrypoint to run all dart2js_info tools.
void main(args) {
var commandRunner = new CommandRunner("dart2js_info",
"collection of tools to digest the output of dart2js's --dump-info")
..addCommand(new CodeDepsCommand())
..addCommand(new CoverageLogServerCommand())
..addCommand(new DebugCommand())
..addCommand(new DiffCommand())
..addCommand(new DeferredLibraryCheck())
..addCommand(new DeferredLibrarySize())
..addCommand(new DeferredLibraryLayout())
..addCommand(new ConvertCommand())
..addCommand(new FunctionSizeCommand())
..addCommand(new LibrarySizeCommand())
..addCommand(new LiveCodeAnalysisCommand())
..addCommand(new ShowInferredTypesCommand())
..addCommand(new ShowCommand());
commandRunner.run(args);
}

267
pkg/dart2js_info/info.proto Normal file
View file

@ -0,0 +1,267 @@
syntax = "proto3";
option go_package = "dart2js_info";
package dart2js_info.proto;
message DependencyInfoPB {
/** The dependency element's serialized_id, references as FunctionInfo or FieldInfo. */
string target_id = 1;
/** Either a selector mask indicating how this is used, or 'inlined'. */
string mask = 2;
}
/** The entire information produced when compiling a program. */
message AllInfoPB {
/** Summary information about the program. */
ProgramInfoPB program = 1;
/** All the recorded information about elements processed by the compiler. */
map<string, InfoPB> all_infos = 2;
/** Details about all deferred imports and what files would be loaded when the import is resolved. */
repeated LibraryDeferredImportsPB deferred_imports = 3;
}
/*
* 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).
*/
message InfoPB {
/** Name of the element associated with this info. */
string name = 1;
/** An id to uniquely identify this info among infos of the same kind. */
int32 id = 2;
/** A globally unique id which combines kind and id together. */
string serialized_id = 3;
/** Id used by the compiler when instrumenting code for code coverage. */
string coverage_id = 4;
/** Bytes used in the generated code for the corresponding element. */
int32 size = 5;
/** The serialized_id of the enclosing element. */
string parent_id = 6;
/** How does this function or field depend on others. */
repeated DependencyInfoPB uses = 7;
/** The serialized_id of the output unit the element is generated into. */
string output_unit_id = 8;
/** Reserved tags for future common fields. */
reserved 9 to 99;
/** The concrete info type. */
oneof concrete {
/** Information about a library element. */
LibraryInfoPB library_info = 100;
/** Information about a class element. */
ClassInfoPB class_info = 101;
/** Information about a function element. */
FunctionInfoPB function_info = 102;
/** Information about a field element. */
FieldInfoPB field_info = 103;
/** Information about a constant element. */
ConstantInfoPB constant_info = 104;
/** Information about an output unit element. */
OutputUnitInfoPB output_unit_info = 105;
/** Information about a typedef element. */
TypedefInfoPB typedef_info = 106;
/** Information about a closure element. */
ClosureInfoPB closure_info = 107;
}
}
/** General metadata about the dart2js invocation. */
message ProgramInfoPB {
/** serialized_id for the entrypoint FunctionInfo. */
string entrypoint_id = 1;
/** The overall size of the dart2js binary. */
int32 size = 2;
/** The version of dart2js used to compile the program. */
string dart2js_version = 3;
/** The time at which the compilation was performed in microseconds since epoch. */
int64 compilation_moment = 4;
/** The amount of time spent compiling the program in microseconds. */
int64 compilation_duration = 5;
/** The amount of time spent converting the info to protobuf in microseconds. */
int64 to_proto_duration = 6;
/** The amount of time spent writing out the serialized info in microseconds. */
int64 dump_info_duration = 7;
/** true if noSuchMethod is used. */
bool no_such_method_enabled = 8;
/** True if Object.runtimeType is used. */
bool is_runtime_type_used = 9;
/** True if dart:isolate library is used. */
bool is_isolate_used = 10;
/** True if Function.apply is used. */
bool is_function_apply_used = 11;
/** True if dart:mirrors features are used. */
bool is_mirrors_used = 12;
/** Whether the resulting dart2js binary is minified. */
bool minified = 13;
}
/** Info associated with a library element. */
message LibraryInfoPB {
/** The canonical uri that identifies the library. */
string uri = 1;
/** The serialized_ids of all FunctionInfo, FieldInfo, ClassInfo and TypedefInfo elements that are defined in the library. */
repeated string children_ids = 2;
}
/**
* 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.
*/
message OutputUnitInfoPB {
/** The deferred imports that will load this output unit. */
repeated string imports = 1;
}
/** Information about a class element. */
message ClassInfoPB {
/** Whether the class is abstract. */
bool is_abstract = 1;
/** The serialized_ids of all FunctionInfo and FieldInfo elements defined in the class. */
repeated string children_ids = 2;
}
/** Information about a constant value. */
message ConstantInfoPB {
/** The actual generated code for the constant. */
string code = 1;
}
/** Information about a field element. */
message FieldInfoPB {
/** The type of the field. */
string type = 1;
/** The type inferred by dart2js's whole program analysis. */
string inferred_type = 2;
/** The serialized_ids of all ClosureInfo elements nested in the field initializer. */
repeated string children_ids = 3;
/** The actual generated code for the field. */
string code = 4;
/** Whether the field is a const declaration. */
bool is_const = 5;
/** When isConst is true, the serialized_id of the ConstantInfo initializer expression. */
string initializer_id = 6;
}
/** Information about a typedef declaration. */
message TypedefInfoPB {
/** The declared type. */
string type = 1;
}
/** Available function modifiers. */
message FunctionModifiersPB {
/** Whether the function is declared as static. */
bool is_static = 1;
/** Whether the function is declared as const. */
bool is_const = 2;
/** Whether the function is a factory constructor. */
bool is_factory = 3;
/** Whether the function is declared as extern. */
bool is_external = 4;
}
/** Information about a function parameter. */
message ParameterInfoPB {
string name = 1;
string type = 2;
string declared_type = 3;
}
/** Information about a function or method. */
message FunctionInfoPB {
/** Modifiers applied to the function. */
FunctionModifiersPB function_modifiers = 1;
/** serialized_ids of any ClosureInfo elements declared in the function. */
repeated string children_ids = 2;
/** The declared return type. */
string return_type = 3;
/** The inferred return type. */
string inferred_return_type = 4;
/** Name and type information for each parameter. */
repeated ParameterInfoPB parameters = 5;
/** Side-effects of the function. */
string side_effects = 6;
/** How many function calls were inlined into the function. */
int32 inlined_count = 7;
/** The actual generated code. */
string code = 8;
/** Measurements collected for this function. */
reserved 9;
}
/** Information about a closure, also known as a local function. */
message ClosureInfoPB {
/** serialized_id of the FunctionInfo wrapped by this closure. */
string function_id = 1;
}
message DeferredImportPB {
/** The prefix assigned to the deferred import. */
string prefix = 1;
/** The list of filenames loaded by the import. */
repeated string files = 2;
}
/** Information about deferred imports within a dart library. */
message LibraryDeferredImportsPB {
/** The uri of the library which makes the deferred import. */
string library_uri = 1;
/** The name of the library, or "<unnamed>" if it is unnamed. */
string library_name = 2;
/** The individual deferred imports within the library. */
repeated DeferredImportPB imports = 3;
}

View file

@ -0,0 +1,459 @@
// 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.
/// Info serialization to a binary form.
///
/// Unlike the JSON codec, this serialization is designed to be streamed.
import 'dart:convert';
import 'src/binary/sink.dart';
import 'src/binary/source.dart';
import 'info.dart';
void encode(AllInfo info, Sink<List<int>> sink) {
new BinaryPrinter(new BinarySink(sink)).visitAll(info);
}
AllInfo decode(List<int> data) {
return new BinaryReader(new BinarySource(data)).readAll();
}
class BinaryPrinter implements InfoVisitor<void> {
final BinarySink sink;
BinaryPrinter(this.sink);
void writeDate(DateTime date) {
sink.writeString(date.toIso8601String());
}
void writeDuration(Duration duration) {
sink.writeInt(duration.inMicroseconds);
}
void writeInfoWithKind(Info info) {
sink.writeEnum(info.kind);
info.accept(this);
}
void visitAll(AllInfo info) {
sink.writeInt(info.version);
sink.writeInt(info.minorVersion);
sink.writeList(info.libraries, visitLibrary);
// TODO(sigmund): synthesize the following lists instead of serializing the
// values again.
sink.writeList(info.classes, visitClass);
sink.writeList(info.functions, visitFunction);
sink.writeList(info.typedefs, visitTypedef);
sink.writeList(info.fields, visitField);
sink.writeList(info.constants, visitConstant);
sink.writeList(info.closures, visitClosure);
void writeDependencies(CodeInfo info) {
sink.writeList(info.uses, _writeDependencyInfo);
}
info.fields.forEach(writeDependencies);
info.functions.forEach(writeDependencies);
sink.writeInt(info.dependencies.length);
info.dependencies.forEach((Info key, List<Info> values) {
writeInfoWithKind(key);
sink.writeList(values, writeInfoWithKind);
});
sink.writeList(info.outputUnits, visitOutput);
sink.writeString(jsonEncode(info.deferredFiles));
visitProgram(info.program);
sink.close();
}
void visitProgram(ProgramInfo info) {
visitFunction(info.entrypoint);
sink.writeInt(info.size);
sink.writeStringOrNull(info.dart2jsVersion);
writeDate(info.compilationMoment);
writeDuration(info.compilationDuration);
// Note: we don't record the 'toJsonDuration' field. Consider deleting it?
writeDuration(info.dumpInfoDuration);
sink.writeBool(info.noSuchMethodEnabled);
sink.writeBool(info.isRuntimeTypeUsed);
sink.writeBool(info.isIsolateInUse);
sink.writeBool(info.isFunctionApplyUsed);
sink.writeBool(info.isMirrorsUsed);
sink.writeBool(info.minified);
}
void _visitBasicInfo(BasicInfo info) {
sink.writeStringOrNull(info.name);
sink.writeInt(info.size);
sink.writeStringOrNull(info.coverageId);
_writeOutputOrNull(info.outputUnit);
// Note: parent-pointers are not serialized, they get deduced during deserialization.
}
void visitLibrary(LibraryInfo library) {
sink.writeCached(library, (LibraryInfo info) {
sink.writeUri(info.uri);
_visitBasicInfo(info);
sink.writeList(info.topLevelFunctions, visitFunction);
sink.writeList(info.topLevelVariables, visitField);
sink.writeList(info.classes, visitClass);
sink.writeList(info.typedefs, visitTypedef);
});
}
void visitClass(ClassInfo cls) {
sink.writeCached(cls, (ClassInfo info) {
_visitBasicInfo(info);
sink.writeBool(info.isAbstract);
sink.writeList(info.fields, visitField);
sink.writeList(info.functions, visitFunction);
});
}
void visitField(FieldInfo field) {
sink.writeCached(field, (FieldInfo info) {
_visitBasicInfo(info);
sink.writeList(info.closures, visitClosure);
sink.writeString(info.inferredType);
sink.writeList(info.code, _visitCodeSpan);
sink.writeString(info.type);
sink.writeBool(info.isConst);
if (info.isConst) {
_writeConstantOrNull(info.initializer);
}
});
}
_visitCodeSpan(CodeSpan code) {
sink.writeIntOrNull(code.start);
sink.writeIntOrNull(code.end);
sink.writeStringOrNull(code.text);
}
void _writeConstantOrNull(ConstantInfo info) {
sink.writeBool(info != null);
if (info != null) {
visitConstant(info);
}
}
void visitConstant(ConstantInfo constant) {
sink.writeCached(constant, (ConstantInfo info) {
_visitBasicInfo(info);
sink.writeList(info.code, _visitCodeSpan);
});
}
void _visitFunctionModifiers(FunctionModifiers mods) {
int value = 0;
if (mods.isStatic) value |= _staticMask;
if (mods.isConst) value |= _constMask;
if (mods.isFactory) value |= _factoryMask;
if (mods.isExternal) value |= _externalMask;
sink.writeInt(value);
}
void _visitParameterInfo(ParameterInfo info) {
sink.writeString(info.name);
sink.writeString(info.type);
sink.writeString(info.declaredType);
}
void visitFunction(FunctionInfo function) {
sink.writeCached(function, (FunctionInfo info) {
_visitBasicInfo(info);
sink.writeList(info.closures, visitClosure);
_visitFunctionModifiers(info.modifiers);
sink.writeString(info.returnType);
sink.writeString(info.inferredReturnType);
sink.writeList(info.parameters, _visitParameterInfo);
sink.writeString(info.sideEffects);
sink.writeIntOrNull(info.inlinedCount);
sink.writeList(info.code, _visitCodeSpan);
sink.writeString(info.type);
});
}
void _writeDependencyInfo(DependencyInfo info) {
writeInfoWithKind(info.target);
sink.writeStringOrNull(info.mask);
}
void visitClosure(ClosureInfo closure) {
sink.writeCached(closure, (ClosureInfo info) {
_visitBasicInfo(info);
visitFunction(info.function);
});
}
void visitTypedef(TypedefInfo typedef) {
sink.writeCached(typedef, (TypedefInfo info) {
_visitBasicInfo(info);
sink.writeString(info.type);
});
}
void _writeOutputOrNull(OutputUnitInfo info) {
sink.writeBool(info != null);
if (info != null) {
visitOutput(info);
}
}
void visitOutput(OutputUnitInfo output) {
sink.writeCached(output, (OutputUnitInfo info) {
_visitBasicInfo(info);
sink.writeStringOrNull(info.filename);
sink.writeList(info.imports, sink.writeString);
});
}
}
class BinaryReader {
final BinarySource source;
BinaryReader(this.source);
DateTime readDate() {
return DateTime.parse(source.readString());
}
Duration readDuration() {
return new Duration(microseconds: source.readInt());
}
Info readInfoWithKind() {
InfoKind kind = source.readEnum(InfoKind.values);
switch (kind) {
case InfoKind.library:
return readLibrary();
case InfoKind.clazz:
return readClass();
case InfoKind.function:
return readFunction();
case InfoKind.field:
return readField();
case InfoKind.constant:
return readConstant();
case InfoKind.outputUnit:
return readOutput();
case InfoKind.typedef:
return readTypedef();
case InfoKind.closure:
return readClosure();
}
return null;
}
AllInfo readAll() {
var info = new AllInfo();
int version = source.readInt();
int minorVersion = source.readInt();
if (info.version != version || info.minorVersion != minorVersion) {
print("warning: data was encoded with format version "
"$version.$minorVersion, but decoded with "
"${info.version}.${info.minorVersion}");
}
info.libraries = source.readList(readLibrary);
info.classes = source.readList(readClass);
info.functions = source.readList(readFunction);
info.typedefs = source.readList(readTypedef);
info.fields = source.readList(readField);
info.constants = source.readList(readConstant);
info.closures = source.readList(readClosure);
void readDependencies(CodeInfo info) {
info.uses = source.readList(_readDependencyInfo);
}
info.fields.forEach(readDependencies);
info.functions.forEach(readDependencies);
int dependenciesTotal = source.readInt();
while (dependenciesTotal > 0) {
Info key = readInfoWithKind();
List<Info> values = source.readList(readInfoWithKind);
info.dependencies[key] = values;
dependenciesTotal--;
}
info.outputUnits = source.readList(readOutput);
Map<String, Map<String, dynamic>> map =
jsonDecode(source.readString()).cast<String, Map<String, dynamic>>();
for (final library in map.values) {
if (library['imports'] != null) {
// The importMap needs to be typed as <String, List<String>>, but the
// json parser produces <String, dynamic>.
final importMap = library['imports'] as Map<String, dynamic>;
importMap.forEach((prefix, files) {
importMap[prefix] = (files as List<dynamic>).cast<String>();
});
library['imports'] = importMap.cast<String, List<String>>();
}
}
info.deferredFiles = map;
info.program = readProgram();
return info;
}
ProgramInfo readProgram() {
var info = new ProgramInfo();
info.entrypoint = readFunction();
info.size = source.readInt();
info.dart2jsVersion = source.readStringOrNull();
info.compilationMoment = readDate();
info.compilationDuration = readDuration();
info.toJsonDuration = new Duration(microseconds: 0);
info.dumpInfoDuration = readDuration();
info.noSuchMethodEnabled = source.readBool();
info.isRuntimeTypeUsed = source.readBool();
info.isIsolateInUse = source.readBool();
info.isFunctionApplyUsed = source.readBool();
info.isMirrorsUsed = source.readBool();
info.minified = source.readBool();
return info;
}
void _readBasicInfo(BasicInfo info) {
info.name = source.readStringOrNull();
info.size = source.readInt();
info.coverageId = source.readStringOrNull();
info.outputUnit = _readOutputOrNull();
// Note: parent pointers are added when deserializing parent nodes.
}
LibraryInfo readLibrary() => source.readCached<LibraryInfo>(() {
LibraryInfo info = new LibraryInfo.internal();
info.uri = source.readUri();
_readBasicInfo(info);
info.topLevelFunctions = source.readList(readFunction);
info.topLevelVariables = source.readList(readField);
info.classes = source.readList(readClass);
info.typedefs = source.readList(readTypedef);
setParent(BasicInfo child) => child.parent = info;
info.topLevelFunctions.forEach(setParent);
info.topLevelVariables.forEach(setParent);
info.classes.forEach(setParent);
info.typedefs.forEach(setParent);
return info;
});
ClassInfo readClass() => source.readCached<ClassInfo>(() {
ClassInfo info = new ClassInfo.internal();
_readBasicInfo(info);
info.isAbstract = source.readBool();
info.fields = source.readList(readField);
info.functions = source.readList(readFunction);
setParent(BasicInfo child) => child.parent = info;
info.fields.forEach(setParent);
info.functions.forEach(setParent);
return info;
});
FieldInfo readField() => source.readCached<FieldInfo>(() {
FieldInfo info = new FieldInfo.internal();
_readBasicInfo(info);
info.closures = source.readList(readClosure);
info.inferredType = source.readString();
info.code = source.readList(_readCodeSpan);
info.type = source.readString();
info.isConst = source.readBool();
if (info.isConst) {
info.initializer = _readConstantOrNull();
}
info.closures.forEach((c) => c.parent = info);
return info;
});
CodeSpan _readCodeSpan() {
return new CodeSpan()
..start = source.readIntOrNull()
..end = source.readIntOrNull()
..text = source.readStringOrNull();
}
ConstantInfo _readConstantOrNull() {
bool hasOutput = source.readBool();
if (hasOutput) return readConstant();
return null;
}
ConstantInfo readConstant() => source.readCached<ConstantInfo>(() {
ConstantInfo info = new ConstantInfo.internal();
_readBasicInfo(info);
info.code = source.readList(_readCodeSpan);
return info;
});
FunctionModifiers _readFunctionModifiers() {
int value = source.readInt();
return new FunctionModifiers(
isStatic: value & _staticMask != 0,
isConst: value & _constMask != 0,
isFactory: value & _factoryMask != 0,
isExternal: value & _externalMask != 0);
}
ParameterInfo _readParameterInfo() {
return new ParameterInfo(
source.readString(), source.readString(), source.readString());
}
FunctionInfo readFunction() => source.readCached<FunctionInfo>(() {
FunctionInfo info = new FunctionInfo.internal();
_readBasicInfo(info);
info.closures = source.readList(readClosure);
info.modifiers = _readFunctionModifiers();
info.returnType = source.readString();
info.inferredReturnType = source.readString();
info.parameters = source.readList(_readParameterInfo);
info.sideEffects = source.readString();
info.inlinedCount = source.readIntOrNull();
info.code = source.readList(_readCodeSpan);
info.type = source.readString();
info.closures.forEach((c) => c.parent = info);
return info;
});
DependencyInfo _readDependencyInfo() =>
new DependencyInfo(readInfoWithKind(), source.readStringOrNull());
ClosureInfo readClosure() => source.readCached<ClosureInfo>(() {
ClosureInfo info = new ClosureInfo.internal();
_readBasicInfo(info);
info.function = readFunction();
info.function.parent = info;
return info;
});
TypedefInfo readTypedef() => source.readCached<TypedefInfo>(() {
TypedefInfo info = new TypedefInfo.internal();
_readBasicInfo(info);
info.type = source.readString();
return info;
});
OutputUnitInfo _readOutputOrNull() {
bool hasOutput = source.readBool();
if (hasOutput) return readOutput();
return null;
}
OutputUnitInfo readOutput() => source.readCached<OutputUnitInfo>(() {
OutputUnitInfo info = new OutputUnitInfo.internal();
_readBasicInfo(info);
info.filename = source.readStringOrNull();
info.imports = source.readList(source.readString);
return info;
});
}
const int _staticMask = 1 << 3;
const int _constMask = 1 << 2;
const int _factoryMask = 1 << 1;
const int _externalMask = 1 << 0;

View file

@ -0,0 +1,177 @@
// 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.
/// This tool checks that the output from dart2js meets a given specification,
/// given in a YAML file. The format of the YAML file is:
///
/// main:
/// include:
/// - some_package
/// - other_package
///
/// foo:
/// include:
/// - foo
/// - bar
///
/// baz:
/// include:
/// - baz
/// - quux
/// exclude:
/// - zardoz
///
/// The YAML file consists of a list of declarations, one for each deferred
/// part expected in the output. At least one of these parts must be named
/// "main"; this is the main part that contains the program entrypoint. Each
/// top-level part contains a list of package names that are expected to be
/// contained in that part, a list of package names that are expected to be in
/// another part, or both. For instance, in the example YAML above the part
/// named "baz" is expected to contain the packages "baz" and "quux" and not to
/// contain the package "zardoz".
///
/// The names for parts given in the specification YAML file (besides "main")
/// are the same as the name given to the deferred import in the dart file. For
/// instance, if you have `import 'package:foo/bar.dart' deferred as baz;` in
/// your dart file, then the corresponding name in the specification file is
/// 'baz'.
library dart2js_info.deferred_library_check;
import 'info.dart';
List<ManifestComplianceFailure> checkDeferredLibraryManifest(
AllInfo info, Map manifest) {
var includedPackages = new Map<String, Set<String>>();
var excludedPackages = new Map<String, Set<String>>();
for (var part in manifest.keys) {
for (var package in manifest[part]['include'] ?? []) {
(includedPackages[part] ??= {}).add(package);
}
for (var package in manifest[part]['exclude'] ?? []) {
(excludedPackages[part] ??= {}).add(package);
}
}
// There are 2 types of parts that are valid to mention in the specification
// file. These are the main part and directly imported deferred parts. The
// main part is always named 'main'; the directly imported deferred parts are
// the outputUnits whose list of 'imports' contains a single import. If the
// part is shared, it will have more than one import since it will include the
// imports of all the top-level deferred parts that will load the shared part.
List<String> validParts = ['main']..addAll(info.outputUnits
.where((unit) => unit.imports.length == 1)
.map((unit) => unit.imports.single));
List<String> mentionedParts = []
..addAll(includedPackages.keys)
..addAll(excludedPackages.keys);
var partNameFailures = <_InvalidPartName>[];
for (var part in mentionedParts) {
if (!validParts.contains(part)) {
partNameFailures.add(new _InvalidPartName(part, validParts));
}
}
if (partNameFailures.isNotEmpty) {
return partNameFailures;
}
var mentionedPackages = {
for (var values in includedPackages.values) ...values,
for (var values in excludedPackages.values) ...values
};
var actualIncludedPackages = new Map<String, Set<String>>();
var failures = <ManifestComplianceFailure>[];
checkInfo(BasicInfo info) {
if (info.size == 0) return;
var lib = _getLibraryOf(info);
if (lib != null && _isPackageUri(lib.uri)) {
var packageName = _getPackageName(lib.uri);
if (!mentionedPackages.contains(packageName)) return;
var containingParts = <String>[];
if (info.outputUnit.name == 'main') {
containingParts.add('main');
} else {
containingParts.addAll(info.outputUnit.imports);
}
for (var part in containingParts) {
(actualIncludedPackages[part] ??= {}).add(packageName);
if (excludedPackages[part].contains(packageName)) {
failures
.add(new _PartContainedExcludedPackage(part, packageName, info));
}
}
}
}
info.functions.forEach(checkInfo);
info.fields.forEach(checkInfo);
includedPackages.forEach((part, packages) {
for (var package in packages) {
if (!actualIncludedPackages.containsKey(part) ||
!actualIncludedPackages[part].contains(package)) {
failures.add(new _PartDidNotContainPackage(part, package));
}
}
});
return failures;
}
LibraryInfo _getLibraryOf(Info info) {
var current = info;
while (current is! LibraryInfo) {
if (current == null) {
return null;
}
current = current.parent;
}
return current;
}
bool _isPackageUri(Uri uri) => uri.scheme == 'package';
String _getPackageName(Uri uri) {
assert(_isPackageUri(uri));
return uri.pathSegments.first;
}
class ManifestComplianceFailure {
const ManifestComplianceFailure();
}
class _InvalidPartName extends ManifestComplianceFailure {
final String part;
final List<String> validPartNames;
const _InvalidPartName(this.part, this.validPartNames);
String toString() {
return 'Manifest file declares invalid part "$part". '
'Valid part names are: $validPartNames';
}
}
class _PartContainedExcludedPackage extends ManifestComplianceFailure {
final String part;
final String package;
final BasicInfo info;
const _PartContainedExcludedPackage(this.part, this.package, this.info);
String toString() {
return 'Part "$part" was specified to exclude package "$package" but it '
'actually contains ${kindToString(info.kind)} "${info.name}" which '
'is from package "$package"';
}
}
class _PartDidNotContainPackage extends ManifestComplianceFailure {
final String part;
final String package;
const _PartDidNotContainPackage(this.part, this.package);
String toString() {
return 'Part "$part" was specified to include package "$package" but it '
'does not contain any elements from that package.';
}
}

View file

@ -0,0 +1,549 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Data produced by dart2js when run with the `--dump-info` flag.
library dart2js_info.info;
/// Common interface to many pieces of information generated by the dart2js
/// compiler that are directly associated with an element (compilation unit,
/// library, class, function, or field).
abstract class Info {
/// An identifier for the kind of information.
InfoKind get kind;
/// Name of the element associated with this info.
String name;
/// 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;
T accept<T>(InfoVisitor<T> 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;
String coverageId;
int size;
Info parent;
String name;
/// If using deferred libraries, where the element associated with this info
/// is generated.
OutputUnitInfo outputUnit;
BasicInfo(this.kind, this.name, this.outputUnit, this.size, this.coverageId);
BasicInfo.internal(this.kind);
String toString() => '$kind $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.
List<DependencyInfo> uses = [];
}
/// The entire information produced while compiling a program.
class AllInfo {
/// Summary information about the program.
ProgramInfo program;
/// Information about each library processed by the compiler.
List<LibraryInfo> libraries = <LibraryInfo>[];
/// Information about each function (includes methods and getters in any
/// library)
List<FunctionInfo> functions = <FunctionInfo>[];
/// Information about type defs in the program.
List<TypedefInfo> typedefs = <TypedefInfo>[];
/// Information about each class (in any library).
List<ClassInfo> classes = <ClassInfo>[];
/// Information about fields (in any class).
List<FieldInfo> fields = <FieldInfo>[];
/// Information about constants anywhere in the program.
// TODO(sigmund): expand docs about canonicalization. We don't put these
// inside library because a single constant can be used in more than one lib,
// and we'll include it only once in the output.
List<ConstantInfo> constants = <ConstantInfo>[];
/// Information about closures anywhere in the program.
List<ClosureInfo> closures = <ClosureInfo>[];
/// 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 from 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 = 6;
/// 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 = 0;
AllInfo();
T accept<T>(InfoVisitor<T> visitor) => visitor.visitAll(this);
}
class ProgramInfo {
FunctionInfo entrypoint;
int size;
String dart2jsVersion;
DateTime compilationMoment;
Duration compilationDuration;
Duration toJsonDuration;
Duration dumpInfoDuration;
/// `true` if `noSuchMethod` is used.
bool noSuchMethodEnabled;
/// `true` if `Object.runtimeType` is used.
bool isRuntimeTypeUsed;
/// `true` if the `dart:isolate` library is in use.
bool isIsolateInUse;
/// `true` if `Function.apply` is used.
bool isFunctionApplyUsed;
/// `true` if `dart:mirrors` features are used.
bool isMirrorsUsed;
bool minified;
ProgramInfo(
{this.entrypoint,
this.size,
this.dart2jsVersion,
this.compilationMoment,
this.compilationDuration,
this.toJsonDuration,
this.dumpInfoDuration,
this.noSuchMethodEnabled,
this.isRuntimeTypeUsed,
this.isIsolateInUse,
this.isFunctionApplyUsed,
this.isMirrorsUsed,
this.minified});
T accept<T>(InfoVisitor<T> visitor) => visitor.visitProgram(this);
}
/// 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.
List<FunctionInfo> topLevelFunctions = <FunctionInfo>[];
/// Top level fields defined within the library.
List<FieldInfo> topLevelVariables = <FieldInfo>[];
/// Classes defined within the library.
List<ClassInfo> classes = <ClassInfo>[];
/// Typedefs defined within the library.
List<TypedefInfo> typedefs = <TypedefInfo>[];
// TODO(sigmund): add here a list of parts. That can help us improve how we
// encode source-span information in metrics (rather than include the uri on
// each function, include an index into this list).
/// 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, name, outputUnit, size, null);
LibraryInfo.internal() : super.internal(InfoKind.library);
T accept<T>(InfoVisitor<T> 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 {
String filename;
/// The deferred imports that will load this output unit.
List<String> imports = <String>[];
OutputUnitInfo(this.filename, String name, int size)
: super(InfoKind.outputUnit, name, null, size, null);
OutputUnitInfo.internal() : super.internal(InfoKind.outputUnit);
T accept<T>(InfoVisitor<T> 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.
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.
List<FieldInfo> fields = <FieldInfo>[];
ClassInfo(
{String name, this.isAbstract, OutputUnitInfo outputUnit, int size: 0})
: super(InfoKind.clazz, name, outputUnit, size, null);
ClassInfo.internal() : super.internal(InfoKind.clazz);
T accept<T>(InfoVisitor<T> visitor) => visitor.visitClass(this);
}
/// A code span of generated code. A [CodeSpan] object is associated with a
/// single [BasicInfo]. The offsets in the span corresponds to offsets on the
/// file of [BasicInfo.outputUnit].
class CodeSpan {
/// Start offset in the generated file.
int start;
/// end offset in the generated file.
int end;
/// The actual code (optional, blank when using a compact representation of
/// the encoding).
String text;
CodeSpan({this.start, this.end, this.text});
}
/// Information about a constant value.
// TODO(sigmund): add dependency data for ConstantInfo
class ConstantInfo extends BasicInfo {
/// The actual generated code for the constant.
List<CodeSpan> code;
// TODO(sigmund): Add coverage support to constants?
ConstantInfo({int size: 0, this.code, OutputUnitInfo outputUnit})
: super(InfoKind.constant, null, outputUnit, size, null);
ConstantInfo.internal() : super.internal(InfoKind.constant);
T accept<T>(InfoVisitor<T> visitor) => visitor.visitConstant(this);
}
/// Information about a field element.
class FieldInfo extends BasicInfo with CodeInfo {
/// The type of the field.
String type;
/// The type inferred by dart2js's whole program analysis
String inferredType;
/// Nested closures seen in the field initializer.
List<ClosureInfo> closures;
/// The actual generated code for the field.
List<CodeSpan> code;
/// Whether this corresponds to a const field declaration.
bool isConst;
/// When [isConst] is true, the constant initializer expression.
ConstantInfo initializer;
FieldInfo(
{String name,
String coverageId,
int size: 0,
this.type,
this.inferredType,
this.closures,
this.code,
OutputUnitInfo outputUnit,
this.isConst})
: super(InfoKind.field, name, outputUnit, size, coverageId);
FieldInfo.internal() : super.internal(InfoKind.field);
T accept<T>(InfoVisitor<T> visitor) => visitor.visitField(this);
}
/// Information about a typedef declaration.
class TypedefInfo extends BasicInfo {
/// The declared type.
String type;
TypedefInfo(String name, this.type, OutputUnitInfo outputUnit)
: super(InfoKind.typedef, name, outputUnit, 0, null);
TypedefInfo.internal() : super.internal(InfoKind.typedef);
T accept<T>(InfoVisitor<T> 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;
/// 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<ClosureInfo> 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.
List<CodeSpan> 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, name, outputUnit, size, coverageId);
FunctionInfo.internal() : super.internal(InfoKind.function);
T accept<T>(InfoVisitor<T> visitor) => visitor.visitFunction(this);
}
/// Information about a closure, also known as a local function.
class ClosureInfo extends BasicInfo {
/// The function that is wrapped by this closure.
FunctionInfo function;
ClosureInfo(
{String name, OutputUnitInfo outputUnit, int size: 0, this.function})
: super(InfoKind.closure, name, outputUnit, size, null);
ClosureInfo.internal() : super.internal(InfoKind.closure);
T accept<T>(InfoVisitor<T> visitor) => visitor.visitClosure(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);
}
/// 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);
}
/// 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});
}
/// Possible values of the `kind` field in the serialized infos.
enum InfoKind {
library,
clazz,
function,
field,
constant,
outputUnit,
typedef,
closure,
}
String kindToString(InfoKind kind) {
switch (kind) {
case InfoKind.library:
return 'library';
case InfoKind.clazz:
return 'class';
case InfoKind.function:
return 'function';
case InfoKind.field:
return 'field';
case InfoKind.constant:
return 'constant';
case InfoKind.outputUnit:
return 'outputUnit';
case InfoKind.typedef:
return 'typedef';
case InfoKind.closure:
return 'closure';
default:
return null;
}
}
InfoKind kindFromString(String kind) {
switch (kind) {
case 'library':
return InfoKind.library;
case 'class':
return InfoKind.clazz;
case 'function':
return InfoKind.function;
case 'field':
return InfoKind.field;
case 'constant':
return InfoKind.constant;
case 'outputUnit':
return InfoKind.outputUnit;
case 'typedef':
return InfoKind.typedef;
case 'closure':
return InfoKind.closure;
default:
return null;
}
}
/// A simple visitor for information produced by the dart2js compiler.
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 visitClosure(ClosureInfo info);
T 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<Null> {
visitAll(AllInfo info) {
// Note: we don't visit functions, fields, classes, and typedefs because
// they are reachable from the library info.
info.libraries.forEach(visitLibrary);
info.constants.forEach(visitConstant);
}
visitProgram(ProgramInfo info) {}
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(visitClosure);
}
visitConstant(ConstantInfo info) {}
visitFunction(FunctionInfo info) {
info.closures.forEach(visitClosure);
}
visitTypedef(TypedefInfo info) {}
visitOutput(OutputUnitInfo info) {}
visitClosure(ClosureInfo info) {
visitFunction(info.function);
}
}

View file

@ -0,0 +1,600 @@
// 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.
import 'dart:collection';
import 'dart:convert';
import 'package:collection/collection.dart';
import 'src/util.dart';
import 'info.dart';
List<String> _toSortedSerializedIds(
Iterable<Info> infos, Id Function(Info) getId) =>
infos.map((i) => getId(i).serializedId).toList()..sort(compareNatural);
// TODO(sigmund): add unit tests.
class JsonToAllInfoConverter extends Converter<Map<String, dynamic>, AllInfo> {
// Using `MashMap` here because it's faster than the default `LinkedHashMap`.
final Map<String, Info> registry = new HashMap<String, Info>();
AllInfo convert(Map<String, dynamic> json) {
registry.clear();
var result = new AllInfo();
var elements = json['elements'];
// TODO(srawlins): Since only the Map values are being extracted below,
// replace `as` with `cast` when `cast` becomes available in Dart 2.0:
//
// .addAll(elements['library'].values.cast<Map>().map(parseLibrary));
result.libraries.addAll(
(elements['library'] as Map).values.map((l) => parseLibrary(l)));
result.classes
.addAll((elements['class'] as Map).values.map((c) => parseClass(c)));
result.functions.addAll(
(elements['function'] as Map).values.map((f) => parseFunction(f)));
// TODO(het): Revert this when the dart2js with the new codec is in stable
if (elements['closure'] != null) {
result.closures.addAll(
(elements['closure'] as Map).values.map((c) => parseClosure(c)));
}
result.fields
.addAll((elements['field'] as Map).values.map((f) => parseField(f)));
result.typedefs.addAll(
(elements['typedef'] as Map).values.map((t) => parseTypedef(t)));
result.constants.addAll(
(elements['constant'] as Map).values.map((c) => parseConstant(c)));
json['holding'].forEach((k, deps) {
CodeInfo src = registry[k];
assert(src != null);
for (var dep in deps) {
var target = registry[dep['id']];
assert(target != null);
src.uses.add(new DependencyInfo(target, dep['mask']));
}
});
json['dependencies']?.forEach((String k, dependencies) {
List<String> deps = dependencies;
result.dependencies[registry[k]] = deps.map((d) => registry[d]).toList();
});
result.outputUnits
.addAll((json['outputUnits'] as List).map((o) => parseOutputUnit(o)));
result.program = parseProgram(json['program']);
if (json['deferredFiles'] != null) {
final deferredFilesMap =
(json['deferredFiles'] as Map).cast<String, Map<String, dynamic>>();
for (final library in deferredFilesMap.values) {
if (library['imports'] != null) {
// The importMap needs to be typed as <String, List<String>>, but the
// json parser produces <String, dynamic>.
final importMap = library['imports'] as Map<String, dynamic>;
importMap.forEach((prefix, files) {
importMap[prefix] = (files as List<dynamic>).cast<String>();
});
library['imports'] = importMap.cast<String, List<String>>();
}
}
result.deferredFiles = deferredFilesMap;
}
// todo: version, etc
return result;
}
OutputUnitInfo parseOutputUnit(Map json) {
OutputUnitInfo result = parseId(json['id']);
result
..filename = json['filename']
..name = json['name']
..size = json['size'];
result.imports
.addAll((json['imports'] as List).map((s) => s as String) ?? const []);
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 = parseCode(json['code'])
..isConst = json['const'] ?? false
..initializer = parseId(json['initializer'])
..closures = (json['children'] as List)
.map<ClosureInfo>((c) => parseId(c))
.toList();
}
ConstantInfo parseConstant(Map json) {
ConstantInfo result = parseId(json['id']);
return result
..code = parseCode(json['code'])
..size = json['size']
..outputUnit = parseId(json['outputUnit']);
}
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) {
var programInfo = new ProgramInfo()
..entrypoint = parseId(json['entrypoint'])
..size = json['size']
..compilationMoment = DateTime.parse(json['compilationMoment'])
..dart2jsVersion = json['dart2jsVersion']
..noSuchMethodEnabled = json['noSuchMethodEnabled']
..isRuntimeTypeUsed = json['isRuntimeTypeUsed']
..isIsolateInUse = json['isIsolateInUse']
..isFunctionApplyUsed = json['isFunctionApplyUsed']
..isMirrorsUsed = json['isMirrorsUsed']
..minified = json['minified'];
// TODO(het): Revert this when the dart2js with the new codec is in stable
var compilationDuration = json['compilationDuration'];
if (compilationDuration is String) {
programInfo.compilationDuration = _parseDuration(compilationDuration);
} else {
assert(compilationDuration is int);
programInfo.compilationDuration =
new Duration(microseconds: compilationDuration);
}
var toJsonDuration = json['toJsonDuration'];
if (toJsonDuration is String) {
programInfo.toJsonDuration = _parseDuration(toJsonDuration);
} else {
assert(toJsonDuration is int);
programInfo.toJsonDuration = new Duration(microseconds: toJsonDuration);
}
var dumpInfoDuration = json['dumpInfoDuration'];
if (dumpInfoDuration is String) {
programInfo.dumpInfoDuration = _parseDuration(dumpInfoDuration);
} else {
assert(dumpInfoDuration is int);
programInfo.dumpInfoDuration =
new Duration(microseconds: dumpInfoDuration);
}
return programInfo;
}
/// Parse a string formatted as "XX:YY:ZZ.ZZZZZ" into a [Duration].
Duration _parseDuration(String duration) {
if (!duration.contains(':')) {
return new Duration(milliseconds: int.parse(duration));
}
var parts = duration.split(':');
var hours = double.parse(parts[0]);
var minutes = double.parse(parts[1]);
var seconds = double.parse(parts[2]);
const secondsInMillis = 1000;
const minutesInMillis = 60 * secondsInMillis;
const hoursInMillis = 60 * minutesInMillis;
var totalMillis = secondsInMillis * seconds +
minutesInMillis * minutes +
hoursInMillis * hours;
return new Duration(milliseconds: totalMillis.round());
}
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'] as List).map((p) => parseParameter(p)).toList()
..code = parseCode(json['code'])
..sideEffects = json['sideEffects']
..inlinedCount = json['inlinedCount']
..modifiers =
parseModifiers(new Map<String, bool>.from(json['modifiers']))
..closures = (json['children'] as List)
.map<ClosureInfo>((c) => parseId(c))
.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);
}
ClosureInfo parseClosure(Map json) {
ClosureInfo result = parseId(json['id']);
return result
..name = json['name']
..parent = parseId(json['parent'])
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..function = parseId(json['function']);
}
Info parseId(id) {
String serializedId = id;
if (serializedId == null) {
return null;
}
return registry.putIfAbsent(serializedId, () {
if (serializedId.startsWith('function/')) {
return new FunctionInfo.internal();
} else if (serializedId.startsWith('closure/')) {
return new ClosureInfo.internal();
} else if (serializedId.startsWith('library/')) {
return new LibraryInfo.internal();
} else if (serializedId.startsWith('class/')) {
return new ClassInfo.internal();
} else if (serializedId.startsWith('field/')) {
return new FieldInfo.internal();
} else if (serializedId.startsWith('constant/')) {
return new ConstantInfo.internal();
} else if (serializedId.startsWith('typedef/')) {
return new TypedefInfo.internal();
} else if (serializedId.startsWith('outputUnit/')) {
return new OutputUnitInfo.internal();
}
assert(false);
return null;
});
}
List<CodeSpan> parseCode(dynamic json) {
// backwards compatibility with format 5.1:
if (json is String) {
return [new CodeSpan(start: null, end: null, text: json)];
}
if (json is List) {
return json.map((dynamic value) {
Map<String, dynamic> jsonCode = value;
return new CodeSpan(
start: jsonCode['start'],
end: jsonCode['end'],
text: jsonCode['text']);
}).toList();
}
return [];
}
}
class AllInfoToJsonConverter extends Converter<AllInfo, Map>
implements InfoVisitor<Map> {
/// Whether to generate json compatible with format 5.1
final bool isBackwardCompatible;
final Map<Info, Id> ids = new HashMap<Info, Id>();
final Set<int> usedIds = new Set<int>();
AllInfoToJsonConverter({this.isBackwardCompatible: false});
Id idFor(Info info) {
var serializedId = ids[info];
if (serializedId != null) return serializedId;
assert(
info is LibraryInfo ||
info is ConstantInfo ||
info is OutputUnitInfo ||
info.parent != null,
"$info");
int id;
if (info is ConstantInfo) {
// No name and no parent, so `longName` isn't helpful
assert(info.name == null);
assert(info.parent == null);
assert(info.code != null);
// Instead, use the content of the code.
id = info.code.first.text.hashCode;
} else {
id = longName(info, useLibraryUri: true, forId: true).hashCode;
}
while (!usedIds.add(id)) {
id++;
}
serializedId = new Id(info.kind, '$id');
return ids[info] = serializedId;
}
Map convert(AllInfo info) => info.accept(this);
Map _visitList(List<Info> infos) {
// Using SplayTree to maintain a consistent order of keys
var map = new SplayTreeMap<String, Map>(compareNatural);
for (var info in infos) {
map['${idFor(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);
var jsonClosures = _visitList(info.closures);
return {
'library': jsonLibraries,
'class': jsonClasses,
'function': jsonFunctions,
'typedef': jsonTypedefs,
'field': jsonFields,
'constant': jsonConstants,
'closure': jsonClosures,
};
}
Map _visitDependencyInfo(DependencyInfo info) =>
{'id': idFor(info.target).serializedId, 'mask': info.mask};
Map _visitAllInfoHolding(AllInfo allInfo) {
var map = new SplayTreeMap<String, List>(compareNatural);
void helper(CodeInfo info) {
if (info.uses.isEmpty) return;
map[idFor(info).serializedId] = info.uses
.map(_visitDependencyInfo)
.toList()
..sort((a, b) => a['id'].compareTo(b['id']));
}
allInfo.functions.forEach(helper);
allInfo.fields.forEach(helper);
return map;
}
Map _visitAllInfoDependencies(AllInfo allInfo) {
var map = new SplayTreeMap<String, List>(compareNatural);
allInfo.dependencies.forEach((k, v) {
map[idFor(k).serializedId] = _toSortedSerializedIds(v, idFor);
});
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': isBackwardCompatible ? 5 : info.version,
'deferredFiles': info.deferredFiles,
'dump_minor_version': isBackwardCompatible ? 1 : info.minorVersion,
'program': info.program.accept(this)
};
}
Map visitProgram(ProgramInfo info) {
return {
'entrypoint': idFor(info.entrypoint).serializedId,
'size': info.size,
'dart2jsVersion': info.dart2jsVersion,
'compilationMoment': '${info.compilationMoment}',
'compilationDuration': info.compilationDuration.inMicroseconds,
'toJsonDuration': info.toJsonDuration.inMicroseconds,
'dumpInfoDuration': info.dumpInfoDuration.inMicroseconds,
'noSuchMethodEnabled': info.noSuchMethodEnabled,
'isRuntimeTypeUsed': info.isRuntimeTypeUsed,
'isIsolateInUse': info.isIsolateInUse,
'isFunctionApplyUsed': info.isFunctionApplyUsed,
'isMirrorsUsed': info.isMirrorsUsed,
'minified': info.minified,
};
}
Map _visitBasicInfo(BasicInfo info) {
var res = {
'id': idFor(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'] = idFor(info.outputUnit).serializedId;
}
if (info.coverageId != null) res['coverageId'] = info.coverageId;
if (info.parent != null) res['parent'] = idFor(info.parent).serializedId;
return res;
}
Map visitLibrary(LibraryInfo info) {
return _visitBasicInfo(info)
..addAll(<String, Object>{
'children': _toSortedSerializedIds(
[
info.topLevelFunctions,
info.topLevelVariables,
info.classes,
info.typedefs
].expand((i) => i),
idFor),
'canonicalUri': '${info.uri}',
});
}
Map visitClass(ClassInfo info) {
return _visitBasicInfo(info)
..addAll(<String, Object>{
// TODO(sigmund): change format, include only when abstract is true.
'modifiers': {'abstract': info.isAbstract},
'children': _toSortedSerializedIds(
[info.fields, info.functions].expand((i) => i), idFor)
});
}
Map visitField(FieldInfo info) {
var result = _visitBasicInfo(info)
..addAll(<String, Object>{
'children': _toSortedSerializedIds(info.closures, idFor),
'inferredType': info.inferredType,
'code': _serializeCode(info.code),
'type': info.type,
});
if (info.isConst) {
result['const'] = true;
if (info.initializer != null) {
result['initializer'] = idFor(info.initializer).serializedId;
}
}
return result;
}
Map visitConstant(ConstantInfo info) => _visitBasicInfo(info)
..addAll(<String, Object>{'code': _serializeCode(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};
Map visitFunction(FunctionInfo info) {
return _visitBasicInfo(info)
..addAll(<String, Object>{
'children': _toSortedSerializedIds(info.closures, idFor),
'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': _serializeCode(info.code),
'type': info.type,
// Note: version 3.2 of dump-info serializes `uses` in a section called
// `holding` at the top-level.
});
}
Map visitClosure(ClosureInfo info) {
return _visitBasicInfo(info)
..addAll(<String, Object>{'function': idFor(info.function).serializedId});
}
visitTypedef(TypedefInfo info) => _visitBasicInfo(info)..['type'] = info.type;
visitOutput(OutputUnitInfo info) => _visitBasicInfo(info)
..['filename'] = info.filename
..['imports'] = info.imports;
Object _serializeCode(List<CodeSpan> code) {
if (isBackwardCompatible) {
return code.map((c) => c.text).join('\n');
}
return code
.map<Object>((c) => {
'start': c.start,
'end': c.end,
'text': c.text,
})
.toList();
}
}
class AllInfoJsonCodec extends Codec<AllInfo, Map> {
final Converter<AllInfo, Map> encoder;
final Converter<Map, AllInfo> decoder = new JsonToAllInfoConverter();
AllInfoJsonCodec({bool isBackwardCompatible: false})
: encoder = new AllInfoToJsonConverter(
isBackwardCompatible: isBackwardCompatible);
}
class Id {
final InfoKind kind;
final String id;
Id(this.kind, this.id);
String get serializedId => '${kindToString(kind)}/$id';
}

View file

@ -0,0 +1,312 @@
// Copyright (c) 2018, 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 Protobuf and [Info] classes.
import 'dart:convert';
import 'package:fixnum/fixnum.dart';
import 'info.dart';
import 'src/proto/info.pb.dart';
import 'src/util.dart';
export 'src/proto/info.pb.dart';
class ProtoToAllInfoConverter extends Converter<AllInfoPB, AllInfo> {
AllInfo convert(AllInfoPB info) {
// TODO(lorenvs): Implement this conversion. It is unlikely to to be used
// by production code since the goal of the proto codec is to consume this
// information from other languages. However, it is useful for roundtrip
// testing, so we should support it.
throw new UnimplementedError('ProtoToAllInfoConverter is not implemented');
}
}
class AllInfoToProtoConverter extends Converter<AllInfo, AllInfoPB> {
final Map<Info, Id> ids = {};
final Set<int> usedIds = new Set<int>();
Id idFor(Info info) {
if (info == null) return null;
var serializedId = ids[info];
if (serializedId != null) return serializedId;
assert(info is LibraryInfo ||
info is ConstantInfo ||
info is OutputUnitInfo ||
info.parent != null);
int id;
if (info is ConstantInfo) {
// No name and no parent, so `longName` isn't helpful
assert(info.name == null);
assert(info.parent == null);
assert(info.code != null);
// Instead, use the content of the code.
id = info.code.first.text.hashCode;
} else {
id = longName(info, useLibraryUri: true, forId: true).hashCode;
}
while (!usedIds.add(id)) {
id++;
}
serializedId = new Id(info.kind, id);
return ids[info] = serializedId;
}
AllInfoPB convert(AllInfo info) => _convertToAllInfoPB(info);
DependencyInfoPB _convertToDependencyInfoPB(DependencyInfo info) {
var result = new DependencyInfoPB()
..targetId = idFor(info.target)?.serializedId;
if (info.mask != null) {
result.mask = info.mask;
}
return result;
}
static ParameterInfoPB _convertToParameterInfoPB(ParameterInfo info) {
return new ParameterInfoPB()
..name = info.name
..type = info.type
..declaredType = info.declaredType;
}
LibraryInfoPB _convertToLibraryInfoPB(LibraryInfo info) {
final proto = new LibraryInfoPB()..uri = info.uri.toString();
proto.childrenIds
.addAll(info.topLevelFunctions.map((func) => idFor(func).serializedId));
proto.childrenIds.addAll(
info.topLevelVariables.map((field) => idFor(field).serializedId));
proto.childrenIds
.addAll(info.classes.map((clazz) => idFor(clazz).serializedId));
proto.childrenIds
.addAll(info.typedefs.map((def) => idFor(def).serializedId));
return proto;
}
ClassInfoPB _convertToClassInfoPB(ClassInfo info) {
final proto = new ClassInfoPB()..isAbstract = info.isAbstract;
proto.childrenIds
.addAll(info.functions.map((func) => idFor(func).serializedId));
proto.childrenIds
.addAll(info.fields.map((field) => idFor(field).serializedId));
return proto;
}
static FunctionModifiersPB _convertToFunctionModifiers(
FunctionModifiers modifiers) {
return new FunctionModifiersPB()
..isStatic = modifiers.isStatic
..isConst = modifiers.isConst
..isFactory = modifiers.isFactory
..isExternal = modifiers.isExternal;
}
FunctionInfoPB _convertToFunctionInfoPB(FunctionInfo info) {
final proto = new FunctionInfoPB()
..functionModifiers = _convertToFunctionModifiers(info.modifiers)
..inlinedCount = info.inlinedCount ?? 0;
if (info.returnType != null) {
proto.returnType = info.returnType;
}
if (info.inferredReturnType != null) {
proto.inferredReturnType = info.inferredReturnType;
}
if (info.code != null) {
proto.code = info.code.map((c) => c.text).join('\n');
}
if (info.sideEffects != null) {
proto.sideEffects = info.sideEffects;
}
proto.childrenIds
.addAll(info.closures.map(((closure) => idFor(closure).serializedId)));
proto.parameters.addAll(info.parameters.map(_convertToParameterInfoPB));
return proto;
}
FieldInfoPB _convertToFieldInfoPB(FieldInfo info) {
final proto = new FieldInfoPB()
..type = info.type
..inferredType = info.inferredType
..isConst = info.isConst;
if (info.code != null) {
proto.code = info.code.map((c) => c.text).join('\n');
}
if (info.initializer != null) {
proto.initializerId = idFor(info.initializer).serializedId;
}
proto.childrenIds
.addAll(info.closures.map((closure) => idFor(closure).serializedId));
return proto;
}
static ConstantInfoPB _convertToConstantInfoPB(ConstantInfo info) {
return new ConstantInfoPB()..code = info.code.map((c) => c.text).join('\n');
}
static OutputUnitInfoPB _convertToOutputUnitInfoPB(OutputUnitInfo info) {
final proto = new OutputUnitInfoPB();
proto.imports.addAll(info.imports.where((import) => import != null));
return proto;
}
static TypedefInfoPB _convertToTypedefInfoPB(TypedefInfo info) {
return new TypedefInfoPB()..type = info.type;
}
ClosureInfoPB _convertToClosureInfoPB(ClosureInfo info) {
return new ClosureInfoPB()..functionId = idFor(info.function).serializedId;
}
InfoPB _convertToInfoPB(Info info) {
final proto = new InfoPB()
..id = idFor(info).id
..serializedId = idFor(info).serializedId
..size = info.size;
if (info.name != null) {
proto.name = info.name;
}
if (info.parent != null) {
proto.parentId = idFor(info.parent).serializedId;
}
if (info.coverageId != null) {
proto.coverageId = info.coverageId;
}
if (info is BasicInfo && info.outputUnit != null) {
// TODO(lorenvs): Similar to the JSON codec, omit this for the default
// output unit. At the moment, there is no easy way to identify which
// output unit is the default on [OutputUnitInfo].
proto.outputUnitId = idFor(info.outputUnit).serializedId;
}
if (info is CodeInfo) {
proto.uses.addAll(info.uses.map(_convertToDependencyInfoPB));
}
if (info is LibraryInfo) {
proto.libraryInfo = _convertToLibraryInfoPB(info);
} else if (info is ClassInfo) {
proto.classInfo = _convertToClassInfoPB(info);
} else if (info is FunctionInfo) {
proto.functionInfo = _convertToFunctionInfoPB(info);
} else if (info is FieldInfo) {
proto.fieldInfo = _convertToFieldInfoPB(info);
} else if (info is ConstantInfo) {
proto.constantInfo = _convertToConstantInfoPB(info);
} else if (info is OutputUnitInfo) {
proto.outputUnitInfo = _convertToOutputUnitInfoPB(info);
} else if (info is TypedefInfo) {
proto.typedefInfo = _convertToTypedefInfoPB(info);
} else if (info is ClosureInfo) {
proto.closureInfo = _convertToClosureInfoPB(info);
}
return proto;
}
ProgramInfoPB _convertToProgramInfoPB(ProgramInfo info) {
var result = new ProgramInfoPB()
..entrypointId = idFor(info.entrypoint).serializedId
..size = info.size
..compilationMoment =
new Int64(info.compilationMoment.microsecondsSinceEpoch)
..compilationDuration = new Int64(info.compilationDuration.inMicroseconds)
..toProtoDuration = new Int64(info.toJsonDuration.inMicroseconds)
..dumpInfoDuration = new Int64(info.dumpInfoDuration.inMicroseconds)
..noSuchMethodEnabled = info.noSuchMethodEnabled ?? false
..isRuntimeTypeUsed = info.isRuntimeTypeUsed ?? false
..isIsolateUsed = info.isIsolateInUse ?? false
..isFunctionApplyUsed = info.isFunctionApplyUsed ?? false
..isMirrorsUsed = info.isMirrorsUsed ?? false
..minified = info.minified ?? false;
if (info.dart2jsVersion != null) {
result.dart2jsVersion = info.dart2jsVersion;
}
return result;
}
Iterable<MapEntry<String, InfoPB>> _convertToAllInfosEntries<T extends Info>(
Iterable<T> infos) sync* {
for (final info in infos) {
final infoProto = _convertToInfoPB(info);
final entry = MapEntry<String, InfoPB>(infoProto.serializedId, infoProto);
yield entry;
}
}
static LibraryDeferredImportsPB _convertToLibraryDeferredImportsPB(
String libraryUri, Map<String, dynamic> fields) {
final proto = new LibraryDeferredImportsPB()
..libraryUri = libraryUri
..libraryName = fields['name'] ?? '<unnamed>';
Map<String, List<String>> imports = fields['imports'];
imports.forEach((prefix, files) {
final import = new DeferredImportPB()..prefix = prefix;
import.files.addAll(files);
proto.imports.add(import);
});
return proto;
}
AllInfoPB _convertToAllInfoPB(AllInfo info) {
final proto = new AllInfoPB()
..program = _convertToProgramInfoPB(info.program);
proto.allInfos.addEntries(_convertToAllInfosEntries(info.libraries));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.classes));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.functions));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.fields));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.constants));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.outputUnits));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.typedefs));
proto.allInfos.addEntries(_convertToAllInfosEntries(info.closures));
info.deferredFiles?.forEach((libraryUri, fields) {
proto.deferredImports
.add(_convertToLibraryDeferredImportsPB(libraryUri, fields));
});
return proto;
}
}
/// A codec for converting [AllInfo] to a protobuf format.
///
/// This codec is still experimental, and will likely crash on certain output
/// from dart2js.
class AllInfoProtoCodec extends Codec<AllInfo, AllInfoPB> {
final Converter<AllInfo, AllInfoPB> encoder = new AllInfoToProtoConverter();
final Converter<AllInfoPB, AllInfo> decoder = new ProtoToAllInfoConverter();
}
class Id {
final InfoKind kind;
final int id;
Id(this.kind, this.id);
String get serializedId => '${kindToString(kind)}/$id';
}

View file

@ -0,0 +1,401 @@
// Copyright (c) 2019, 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.
import 'dart:convert';
import 'dart:typed_data';
/// Interface for serialization.
// TODO(sigmund): share this with pkg:compiler/src/serialization/*
abstract class DataSink {
/// The amount of data written to this data sink.
///
/// The units is based on the underlying data structure for this data sink.
int get length;
/// Flushes any pending data and closes this data sink.
///
/// The data sink can no longer be written to after closing.
void close();
/// Writes a reference to [value] to this data sink. If [value] has not yet
/// been serialized, [f] is called to serialize the value itself.
void writeCached<E>(E value, void f(E value));
/// Writes the potentially `null` [value] to this data sink. If [value] is
/// non-null [f] is called to write the non-null value to the data sink.
///
/// This is a convenience method to be used together with
/// [DataSource.readValueOrNull].
void writeValueOrNull<E>(E value, void f(E value));
/// Writes the [values] to this data sink calling [f] to write each value to
/// the data sink. If [allowNull] is `true`, [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSource.readList].
void writeList<E>(Iterable<E> values, void f(E value),
{bool allowNull: false});
/// Writes the boolean [value] to this data sink.
void writeBool(bool value);
/// Writes the non-negative integer [value] to this data sink.
void writeInt(int value);
/// Writes the potentially `null` non-negative [value] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSource.readIntOrNull].
void writeIntOrNull(int value);
/// Writes the string [value] to this data sink.
void writeString(String value);
/// Writes the potentially `null` string [value] to this data sink.
///
/// This is a convenience method to be used together with
/// [DataSource.readStringOrNull].
void writeStringOrNull(String value);
/// Writes the string [values] to this data sink. If [allowNull] is `true`,
/// [values] is allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSource.readStrings].
void writeStrings(Iterable<String> values, {bool allowNull: false});
/// Writes the [map] from string to [V] values to this data sink, calling [f]
/// to write each value to the data sink. If [allowNull] is `true`, [map] is
/// allowed to be `null`.
///
/// This is a convenience method to be used together with
/// [DataSource.readStringMap].
void writeStringMap<V>(Map<String, V> map, void f(V value),
{bool allowNull: false});
/// Writes the enum value [value] to this data sink.
// TODO(johnniwinther): Change the signature to
// `void writeEnum<E extends Enum<E>>(E value);` when an interface for enums
// is added to the language.
void writeEnum(dynamic value);
/// Writes the URI [value] to this data sink.
void writeUri(Uri value);
}
/// Mixin that implements all convenience methods of [DataSink].
abstract class DataSinkMixin implements DataSink {
@override
void writeIntOrNull(int value) {
writeBool(value != null);
if (value != null) {
writeInt(value);
}
}
@override
void writeStringOrNull(String value) {
writeBool(value != null);
if (value != null) {
writeString(value);
}
}
@override
void writeStrings(Iterable<String> values, {bool allowNull: false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
for (String value in values) {
writeString(value);
}
}
}
@override
void writeStringMap<V>(Map<String, V> map, void f(V value),
{bool allowNull: false}) {
if (map == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(map.length);
map.forEach((String key, V value) {
writeString(key);
f(value);
});
}
}
@override
void writeList<E>(Iterable<E> values, void f(E value),
{bool allowNull: false}) {
if (values == null) {
assert(allowNull);
writeInt(0);
} else {
writeInt(values.length);
values.forEach(f);
}
}
@override
void writeValueOrNull<E>(E value, void f(E value)) {
writeBool(value != null);
if (value != null) {
f(value);
}
}
}
/// Data sink helper that canonicalizes [E] values using indices.
class IndexedSink<E> {
final void Function(int) _writeInt;
final Map<E, int> _cache = {};
IndexedSink(this._writeInt);
/// Write a reference to [value] to the data sink.
///
/// If [value] has not been canonicalized yet, [writeValue] is called to
/// serialize the [value] itself.
void write(E value, void writeValue(E value)) {
int index = _cache[value];
if (index == null) {
index = _cache.length;
_cache[value] = index;
_writeInt(index);
writeValue(value);
} else {
_writeInt(index);
}
}
}
/// Base implementation of [DataSink] using [DataSinkMixin] to implement
/// convenience methods.
abstract class AbstractDataSink extends DataSinkMixin implements DataSink {
IndexedSink<String> _stringIndex;
IndexedSink<Uri> _uriIndex;
Map<Type, IndexedSink> _generalCaches = {};
AbstractDataSink() {
_stringIndex = new IndexedSink<String>(_writeIntInternal);
_uriIndex = new IndexedSink<Uri>(_writeIntInternal);
}
@override
void writeCached<E>(E value, void f(E value)) {
IndexedSink sink =
_generalCaches[E] ??= new IndexedSink<E>(_writeIntInternal);
sink.write(value, (v) => f(v));
}
@override
void writeEnum(dynamic value) {
_writeEnumInternal(value);
}
@override
void writeBool(bool value) {
assert(value != null);
_writeIntInternal(value ? 1 : 0);
}
@override
void writeUri(Uri value) {
assert(value != null);
_writeUri(value);
}
@override
void writeString(String value) {
assert(value != null);
_writeString(value);
}
@override
void writeInt(int value) {
assert(value != null);
assert(value >= 0 && value >> 30 == 0);
_writeIntInternal(value);
}
void _writeString(String value) {
_stringIndex.write(value, _writeStringInternal);
}
void _writeUri(Uri value) {
_uriIndex.write(value, _writeUriInternal);
}
/// Actual serialization of a URI value, implemented by subclasses.
void _writeUriInternal(Uri value);
/// Actual serialization of a String value, implemented by subclasses.
void _writeStringInternal(String value);
/// Actual serialization of a non-negative integer value, implemented by
/// subclasses.
void _writeIntInternal(int value);
/// Actual serialization of an enum value, implemented by subclasses.
void _writeEnumInternal(dynamic value);
}
/// [DataSink] that writes data as a sequence of bytes.
///
/// This data sink works together with [BinarySource].
class BinarySink extends AbstractDataSink {
final Sink<List<int>> sink;
BufferedSink _bufferedSink;
int _length = 0;
BinarySink(this.sink) : _bufferedSink = new BufferedSink(sink);
@override
void _writeUriInternal(Uri value) {
_writeString(value.toString());
}
@override
void _writeStringInternal(String value) {
List<int> bytes = utf8.encode(value);
_writeIntInternal(bytes.length);
_bufferedSink.addBytes(bytes);
_length += bytes.length;
}
@override
void _writeIntInternal(int value) {
assert(value >= 0 && value >> 30 == 0);
if (value < 0x80) {
_bufferedSink.addByte(value);
_length += 1;
} else if (value < 0x4000) {
_bufferedSink.addByte2((value >> 8) | 0x80, value & 0xFF);
_length += 2;
} else {
_bufferedSink.addByte4((value >> 24) | 0xC0, (value >> 16) & 0xFF,
(value >> 8) & 0xFF, value & 0xFF);
_length += 4;
}
}
@override
void _writeEnumInternal(dynamic value) {
_writeIntInternal(value.index);
}
void close() {
_bufferedSink.flushAndDestroy();
_bufferedSink = null;
sink.close();
}
/// Returns the number of bytes written to this data sink.
int get length => _length;
}
/// Puts a buffer in front of a [Sink<List<int>>].
// TODO(sigmund): share with the implementation in
// package:kernel/binary/ast_to_binary.dart
class BufferedSink {
static const int SIZE = 100000;
static const int SAFE_SIZE = SIZE - 5;
static const int SMALL = 10000;
final Sink<List<int>> _sink;
Uint8List _buffer = new Uint8List(SIZE);
int length = 0;
int flushedLength = 0;
Float64List _doubleBuffer = new Float64List(1);
Uint8List _doubleBufferUint8;
int get offset => length + flushedLength;
BufferedSink(this._sink);
void addDouble(double d) {
_doubleBufferUint8 ??= _doubleBuffer.buffer.asUint8List();
_doubleBuffer[0] = d;
addByte4(_doubleBufferUint8[0], _doubleBufferUint8[1],
_doubleBufferUint8[2], _doubleBufferUint8[3]);
addByte4(_doubleBufferUint8[4], _doubleBufferUint8[5],
_doubleBufferUint8[6], _doubleBufferUint8[7]);
}
void addByte(int byte) {
_buffer[length++] = byte;
if (length == SIZE) {
_sink.add(_buffer);
_buffer = new Uint8List(SIZE);
length = 0;
flushedLength += SIZE;
}
}
void addByte2(int byte1, int byte2) {
if (length < SAFE_SIZE) {
_buffer[length++] = byte1;
_buffer[length++] = byte2;
} else {
addByte(byte1);
addByte(byte2);
}
}
void addByte4(int byte1, int byte2, int byte3, int byte4) {
if (length < SAFE_SIZE) {
_buffer[length++] = byte1;
_buffer[length++] = byte2;
_buffer[length++] = byte3;
_buffer[length++] = byte4;
} else {
addByte(byte1);
addByte(byte2);
addByte(byte3);
addByte(byte4);
}
}
void addBytes(List<int> bytes) {
// Avoid copying a large buffer into the another large buffer. Also, if
// the bytes buffer is too large to fit in our own buffer, just emit both.
if (length + bytes.length < SIZE &&
(bytes.length < SMALL || length < SMALL)) {
_buffer.setRange(length, length + bytes.length, bytes);
length += bytes.length;
} else if (bytes.length < SMALL) {
// Flush as much as we can in the current buffer.
_buffer.setRange(length, SIZE, bytes);
_sink.add(_buffer);
// Copy over the remainder into a new buffer. It is guaranteed to fit
// because the input byte array is small.
int alreadyEmitted = SIZE - length;
int remainder = bytes.length - alreadyEmitted;
_buffer = new Uint8List(SIZE);
_buffer.setRange(0, remainder, bytes, alreadyEmitted);
length = remainder;
flushedLength += SIZE;
} else {
flush();
_sink.add(bytes);
flushedLength += bytes.length;
}
}
void flush() {
_sink.add(_buffer.sublist(0, length));
_buffer = new Uint8List(SIZE);
flushedLength += length;
length = 0;
}
void flushAndDestroy() {
_sink.add(_buffer.sublist(0, length));
}
}

View file

@ -0,0 +1,296 @@
// Copyright (c) 2019, 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.
import 'dart:typed_data';
import 'dart:convert';
/// Interface for deserialization.
// TODO(sigmund): share this with pkg:compiler/src/serialization/*
abstract class DataSource {
/// Reads a reference to an [E] value from this data source. If the value has
/// not yet been deserialized, [f] is called to deserialize the value itself.
E readCached<E>(E f());
/// Reads a potentially `null` [E] value from this data source, calling [f] to
/// read the non-null value from the data source.
///
/// This is a convenience method to be used together with
/// [DataSink.writeValueOrNull].
E readValueOrNull<E>(E f());
/// Reads a list of [E] values from this data source. If [emptyAsNull] is
/// `true`, `null` is returned instead of an empty list.
///
/// This is a convenience method to be used together with
/// [DataSink.writeList].
List<E> readList<E>(E f(), {bool emptyAsNull: false});
/// Reads a boolean value from this data source.
bool readBool();
/// Reads a non-negative integer value from this data source.
int readInt();
/// Reads a potentially `null` non-negative integer value from this data
/// source.
///
/// This is a convenience method to be used together with
/// [DataSink.writeIntOrNull].
int readIntOrNull();
/// Reads a string value from this data source.
String readString();
/// Reads a potentially `null` string value from this data source.
///
/// This is a convenience method to be used together with
/// [DataSink.writeStringOrNull].
String readStringOrNull();
/// Reads a list of string values from this data source. If [emptyAsNull] is
/// `true`, `null` is returned instead of an empty list.
///
/// This is a convenience method to be used together with
/// [DataSink.writeStrings].
List<String> readStrings({bool emptyAsNull: false});
/// Reads a map from string values to [V] values from this data source,
/// calling [f] to read each value from the data source. If [emptyAsNull] is
/// `true`, `null` is returned instead of an empty map.
///
/// This is a convenience method to be used together with
/// [DataSink.writeStringMap].
Map<String, V> readStringMap<V>(V f(), {bool emptyAsNull: false});
/// Reads an enum value from the list of enum [values] from this data source.
///
/// The [values] argument is intended to be the static `.values` field on
/// enum classes, for instance:
///
/// enum Foo { bar, baz }
/// ...
/// Foo foo = source.readEnum(Foo.values);
///
E readEnum<E>(List<E> values);
/// Reads a URI value from this data source.
Uri readUri();
}
/// Mixin that implements all convenience methods of [DataSource].
abstract class DataSourceMixin implements DataSource {
@override
E readValueOrNull<E>(E f()) {
bool hasValue = readBool();
if (hasValue) {
return f();
}
return null;
}
@override
List<E> readList<E>(E f(), {bool emptyAsNull: false}) {
int count = readInt();
if (count == 0 && emptyAsNull) return null;
List<E> list = new List<E>(count);
for (int i = 0; i < count; i++) {
list[i] = f();
}
return list;
}
@override
int readIntOrNull() {
bool hasValue = readBool();
if (hasValue) {
return readInt();
}
return null;
}
@override
String readStringOrNull() {
bool hasValue = readBool();
if (hasValue) {
return readString();
}
return null;
}
@override
List<String> readStrings({bool emptyAsNull: false}) {
int count = readInt();
if (count == 0 && emptyAsNull) return null;
List<String> list = new List<String>(count);
for (int i = 0; i < count; i++) {
list[i] = readString();
}
return list;
}
@override
Map<String, V> readStringMap<V>(V f(), {bool emptyAsNull: false}) {
int count = readInt();
if (count == 0 && emptyAsNull) return null;
Map<String, V> map = {};
for (int i = 0; i < count; i++) {
String key = readString();
V value = f();
map[key] = value;
}
return map;
}
}
/// Data source helper reads canonicalized [E] values through indices.
class IndexedSource<E> {
final int Function() _readInt;
final List<E> _cache = [];
final Set<int> _pending = new Set();
IndexedSource(this._readInt);
/// Reads a reference to an [E] value from the data source.
///
/// If the value hasn't yet been read, [readValue] is called to deserialize
/// the value itself.
E read(E readValue()) {
int index = _readInt();
if (_pending.contains(index)) throw "serialization cycles not supported";
if (index >= _cache.length) {
_pending.add(index);
_cache.add(null);
E value = readValue();
_pending.remove(index);
_cache[index] = value;
return value;
} else {
return _cache[index];
}
}
}
/// Base implementation of [DataSource] using [DataSourceMixin] to implement
/// convenience methods.
abstract class AbstractDataSource extends DataSourceMixin
implements DataSource {
IndexedSource<String> _stringIndex;
IndexedSource<Uri> _uriIndex;
Map<Type, IndexedSource> _generalCaches = {};
AbstractDataSource() {
_stringIndex = new IndexedSource<String>(_readIntInternal);
_uriIndex = new IndexedSource<Uri>(_readIntInternal);
}
@override
E readCached<E>(E f()) {
IndexedSource source =
_generalCaches[E] ??= new IndexedSource<E>(_readIntInternal);
return source.read(f);
}
@override
E readEnum<E>(List<E> values) {
return _readEnumInternal(values);
}
@override
Uri readUri() {
return _readUri();
}
Uri _readUri() {
return _uriIndex.read(_readUriInternal);
}
@override
bool readBool() {
int value = _readIntInternal();
assert(value == 0 || value == 1);
return value == 1;
}
@override
String readString() {
return _readString();
}
String _readString() {
return _stringIndex.read(_readStringInternal);
}
@override
int readInt() {
return _readIntInternal();
}
/// Actual deserialization of a string value, implemented by subclasses.
String _readStringInternal();
/// Actual deserialization of a non-negative integer value, implemented by
/// subclasses.
int _readIntInternal();
/// Actual deserialization of a URI value, implemented by subclasses.
Uri _readUriInternal();
/// Actual deserialization of an enum value in [values], implemented by
/// subclasses.
E _readEnumInternal<E>(List<E> values);
}
/// [DataSource] that reads data from a sequence of bytes.
///
/// This data source works together with [BinarySink].
class BinarySource extends AbstractDataSource {
int _byteOffset = 0;
final List<int> _bytes;
BinarySource(this._bytes);
int _readByte() => _bytes[_byteOffset++];
@override
String _readStringInternal() {
int length = _readIntInternal();
List<int> bytes = new Uint8List(length);
bytes.setRange(0, bytes.length, _bytes, _byteOffset);
_byteOffset += bytes.length;
return utf8.decode(bytes);
}
@override
int _readIntInternal() {
var byte = _readByte();
if (byte & 0x80 == 0) {
// 0xxxxxxx
return byte;
} else if (byte & 0x40 == 0) {
// 10xxxxxx
return ((byte & 0x3F) << 8) | _readByte();
} else {
// 11xxxxxx
return ((byte & 0x3F) << 24) |
(_readByte() << 16) |
(_readByte() << 8) |
_readByte();
}
}
@override
Uri _readUriInternal() {
String text = _readString();
return Uri.parse(text);
}
@override
E _readEnumInternal<E>(List<E> values) {
int index = _readIntInternal();
assert(
0 <= index && index < values.length,
"Invalid data kind index. "
"Expected one of $values, found index $index.");
return values[index];
}
}

View file

@ -0,0 +1,165 @@
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/util.dart';
class Diff {
final BasicInfo info;
final DiffKind kind;
Diff(this.info, this.kind);
}
enum DiffKind { add, remove, size, deferred }
class RemoveDiff extends Diff {
RemoveDiff(BasicInfo info) : super(info, DiffKind.remove);
}
class AddDiff extends Diff {
AddDiff(BasicInfo info) : super(info, DiffKind.add);
}
class SizeDiff extends Diff {
final int sizeDifference;
SizeDiff(BasicInfo info, this.sizeDifference) : super(info, DiffKind.size);
}
class DeferredStatusDiff extends Diff {
final bool wasDeferredBefore;
DeferredStatusDiff(BasicInfo info, this.wasDeferredBefore)
: super(info, DiffKind.deferred);
}
List<Diff> diff(AllInfo oldInfo, AllInfo newInfo) {
var differ = new _InfoDiffer(oldInfo, newInfo);
differ.diff();
return differ.diffs;
}
class _InfoDiffer extends InfoVisitor<Null> {
final AllInfo _old;
final AllInfo _new;
BasicInfo _other;
List<Diff> diffs = <Diff>[];
_InfoDiffer(this._old, this._new);
void diff() {
_diffList(_old.libraries, _new.libraries);
}
@override
visitAll(AllInfo info) {
throw new StateError('should not diff AllInfo');
}
@override
visitProgram(ProgramInfo info) {
throw new StateError('should not diff ProgramInfo');
}
@override
visitOutput(OutputUnitInfo info) {
throw new StateError('should not diff OutputUnitInfo');
}
// TODO(het): diff constants
@override
visitConstant(ConstantInfo info) {
throw new StateError('should not diff ConstantInfo');
}
@override
visitLibrary(LibraryInfo info) {
var other = _other as LibraryInfo;
_checkSize(info, other);
_diffList(info.topLevelVariables, other.topLevelVariables);
_diffList(info.topLevelFunctions, other.topLevelFunctions);
_diffList(info.classes, other.classes);
}
@override
visitClass(ClassInfo info) {
var other = _other as ClassInfo;
_checkSize(info, other);
_checkDeferredStatus(info, other);
_diffList(info.fields, other.fields);
_diffList(info.functions, other.functions);
}
@override
visitClosure(ClosureInfo info) {
var other = _other as ClosureInfo;
_checkSize(info, other);
_checkDeferredStatus(info, other);
_diffList([info.function], [other.function]);
}
@override
visitField(FieldInfo info) {
var other = _other as FieldInfo;
_checkSize(info, other);
_checkDeferredStatus(info, other);
_diffList(info.closures, other.closures);
}
@override
visitFunction(FunctionInfo info) {
var other = _other as FunctionInfo;
_checkSize(info, other);
_checkDeferredStatus(info, other);
_diffList(info.closures, other.closures);
}
@override
visitTypedef(TypedefInfo info) {
var other = _other as TypedefInfo;
_checkSize(info, other);
_checkDeferredStatus(info, other);
}
void _checkSize(BasicInfo info, BasicInfo other) {
if (info.size != other.size) {
diffs.add(new SizeDiff(info, other.size - info.size));
}
}
void _checkDeferredStatus(BasicInfo oldInfo, BasicInfo newInfo) {
var oldIsDeferred = _isDeferred(oldInfo);
var newIsDeferred = _isDeferred(newInfo);
if (oldIsDeferred != newIsDeferred) {
diffs.add(new DeferredStatusDiff(oldInfo, oldIsDeferred));
}
}
bool _isDeferred(BasicInfo info) {
var outputUnit = info.outputUnit;
return outputUnit.name != null &&
outputUnit.name.isNotEmpty &&
outputUnit.name != 'main';
}
void _diffList(List<BasicInfo> oldInfos, List<BasicInfo> newInfos) {
var oldNames = <String, BasicInfo>{};
var newNames = <String, BasicInfo>{};
for (var oldInfo in oldInfos) {
oldNames[longName(oldInfo, useLibraryUri: true)] = oldInfo;
}
for (var newInfo in newInfos) {
newNames[longName(newInfo, useLibraryUri: true)] = newInfo;
}
for (var oldName in oldNames.keys) {
if (newNames[oldName] == null) {
diffs.add(new RemoveDiff(oldNames[oldName]));
} else {
_other = newNames[oldName];
oldNames[oldName].accept(this);
}
}
for (var newName in newNames.keys) {
if (oldNames[newName] == null) {
diffs.add(new AddDiff(newNames[newName]));
}
}
}
}

View file

@ -0,0 +1,334 @@
// 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 strongly 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, like quiver?
library dart2js_info.src.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.
Iterable<N> postOrder(N root) sync* {
var seen = new Set<N>();
Iterable<N> helper(N n) sync* {
if (!seen.add(n)) return;
for (var x in targetsOf(n)) {
yield* helper(x);
}
yield n;
}
yield* helper(root);
}
/// Returns an iterable of all nodes reachable from [root] in preorder.
Iterable<N> preOrder(N root) sync* {
var seen = new Set<N>();
var stack = <N>[root];
while (stack.isNotEmpty) {
var next = stack.removeLast();
if (!seen.contains(next)) {
seen.add(next);
yield next;
stack.addAll(targetsOf(next));
}
}
}
/// Returns a list of nodes that form a cycle containing the given node. If
/// the node is not part of a cycle in 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);
}
/// Returns a dominator tree starting from root. This is a new graph, with the
/// same nodes as this graph, but where edges exist between a node and the
/// nodes it immediately dominates. For example, this graph:
///
/// root
/// / \
/// a b
/// | / \
/// c d e
/// \ / \ /
/// f g
///
/// Produces this tree:
///
/// root
/// /| \
/// a | b
/// | | /|\
/// c | d | e
/// | |
/// f g
///
/// Internally we compute dominators using (Cooper, Harvey, and Kennedy's
/// algorithm)[http://www.cs.rice.edu/~keith/EMBED/dom.pdf].
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);
/// 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).toList();
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;
N idom;
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;
}
}

View file

@ -0,0 +1,15 @@
import 'dart:convert';
import 'dart:io';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/json_info_codec.dart';
import 'package:dart2js_info/binary_serialization.dart' as binary;
Future<AllInfo> infoFromFile(String fileName) async {
var file = new File(fileName);
if (fileName.endsWith('.json')) {
return new AllInfoJsonCodec().decode(jsonDecode(await file.readAsString()));
} else {
return binary.decode(file.readAsBytesSync());
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
///
// Generated code. Do not modify.
// source: info.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type

View file

@ -0,0 +1,381 @@
///
// Generated code. Do not modify.
// source: info.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
const DependencyInfoPB$json = const {
'1': 'DependencyInfoPB',
'2': const [
const {'1': 'target_id', '3': 1, '4': 1, '5': 9, '10': 'targetId'},
const {'1': 'mask', '3': 2, '4': 1, '5': 9, '10': 'mask'},
],
};
const AllInfoPB$json = const {
'1': 'AllInfoPB',
'2': const [
const {
'1': 'program',
'3': 1,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.ProgramInfoPB',
'10': 'program'
},
const {
'1': 'all_infos',
'3': 2,
'4': 3,
'5': 11,
'6': '.dart2js_info.proto.AllInfoPB.AllInfosEntry',
'10': 'allInfos'
},
const {
'1': 'deferred_imports',
'3': 3,
'4': 3,
'5': 11,
'6': '.dart2js_info.proto.LibraryDeferredImportsPB',
'10': 'deferredImports'
},
],
'3': const [AllInfoPB_AllInfosEntry$json],
};
const AllInfoPB_AllInfosEntry$json = const {
'1': 'AllInfosEntry',
'2': const [
const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
const {
'1': 'value',
'3': 2,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.InfoPB',
'10': 'value'
},
],
'7': const {'7': true},
};
const InfoPB$json = const {
'1': 'InfoPB',
'2': const [
const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
const {'1': 'id', '3': 2, '4': 1, '5': 5, '10': 'id'},
const {'1': 'serialized_id', '3': 3, '4': 1, '5': 9, '10': 'serializedId'},
const {'1': 'coverage_id', '3': 4, '4': 1, '5': 9, '10': 'coverageId'},
const {'1': 'size', '3': 5, '4': 1, '5': 5, '10': 'size'},
const {'1': 'parent_id', '3': 6, '4': 1, '5': 9, '10': 'parentId'},
const {
'1': 'uses',
'3': 7,
'4': 3,
'5': 11,
'6': '.dart2js_info.proto.DependencyInfoPB',
'10': 'uses'
},
const {'1': 'output_unit_id', '3': 8, '4': 1, '5': 9, '10': 'outputUnitId'},
const {
'1': 'library_info',
'3': 100,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.LibraryInfoPB',
'9': 0,
'10': 'libraryInfo'
},
const {
'1': 'class_info',
'3': 101,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.ClassInfoPB',
'9': 0,
'10': 'classInfo'
},
const {
'1': 'function_info',
'3': 102,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.FunctionInfoPB',
'9': 0,
'10': 'functionInfo'
},
const {
'1': 'field_info',
'3': 103,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.FieldInfoPB',
'9': 0,
'10': 'fieldInfo'
},
const {
'1': 'constant_info',
'3': 104,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.ConstantInfoPB',
'9': 0,
'10': 'constantInfo'
},
const {
'1': 'output_unit_info',
'3': 105,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.OutputUnitInfoPB',
'9': 0,
'10': 'outputUnitInfo'
},
const {
'1': 'typedef_info',
'3': 106,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.TypedefInfoPB',
'9': 0,
'10': 'typedefInfo'
},
const {
'1': 'closure_info',
'3': 107,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.ClosureInfoPB',
'9': 0,
'10': 'closureInfo'
},
],
'8': const [
const {'1': 'concrete'},
],
'9': const [
const {'1': 9, '2': 100},
],
};
const ProgramInfoPB$json = const {
'1': 'ProgramInfoPB',
'2': const [
const {'1': 'entrypoint_id', '3': 1, '4': 1, '5': 9, '10': 'entrypointId'},
const {'1': 'size', '3': 2, '4': 1, '5': 5, '10': 'size'},
const {
'1': 'dart2js_version',
'3': 3,
'4': 1,
'5': 9,
'10': 'dart2jsVersion'
},
const {
'1': 'compilation_moment',
'3': 4,
'4': 1,
'5': 3,
'10': 'compilationMoment'
},
const {
'1': 'compilation_duration',
'3': 5,
'4': 1,
'5': 3,
'10': 'compilationDuration'
},
const {
'1': 'to_proto_duration',
'3': 6,
'4': 1,
'5': 3,
'10': 'toProtoDuration'
},
const {
'1': 'dump_info_duration',
'3': 7,
'4': 1,
'5': 3,
'10': 'dumpInfoDuration'
},
const {
'1': 'no_such_method_enabled',
'3': 8,
'4': 1,
'5': 8,
'10': 'noSuchMethodEnabled'
},
const {
'1': 'is_runtime_type_used',
'3': 9,
'4': 1,
'5': 8,
'10': 'isRuntimeTypeUsed'
},
const {
'1': 'is_isolate_used',
'3': 10,
'4': 1,
'5': 8,
'10': 'isIsolateUsed'
},
const {
'1': 'is_function_apply_used',
'3': 11,
'4': 1,
'5': 8,
'10': 'isFunctionApplyUsed'
},
const {
'1': 'is_mirrors_used',
'3': 12,
'4': 1,
'5': 8,
'10': 'isMirrorsUsed'
},
const {'1': 'minified', '3': 13, '4': 1, '5': 8, '10': 'minified'},
],
};
const LibraryInfoPB$json = const {
'1': 'LibraryInfoPB',
'2': const [
const {'1': 'uri', '3': 1, '4': 1, '5': 9, '10': 'uri'},
const {'1': 'children_ids', '3': 2, '4': 3, '5': 9, '10': 'childrenIds'},
],
};
const OutputUnitInfoPB$json = const {
'1': 'OutputUnitInfoPB',
'2': const [
const {'1': 'imports', '3': 1, '4': 3, '5': 9, '10': 'imports'},
],
};
const ClassInfoPB$json = const {
'1': 'ClassInfoPB',
'2': const [
const {'1': 'is_abstract', '3': 1, '4': 1, '5': 8, '10': 'isAbstract'},
const {'1': 'children_ids', '3': 2, '4': 3, '5': 9, '10': 'childrenIds'},
],
};
const ConstantInfoPB$json = const {
'1': 'ConstantInfoPB',
'2': const [
const {'1': 'code', '3': 1, '4': 1, '5': 9, '10': 'code'},
],
};
const FieldInfoPB$json = const {
'1': 'FieldInfoPB',
'2': const [
const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'},
const {'1': 'inferred_type', '3': 2, '4': 1, '5': 9, '10': 'inferredType'},
const {'1': 'children_ids', '3': 3, '4': 3, '5': 9, '10': 'childrenIds'},
const {'1': 'code', '3': 4, '4': 1, '5': 9, '10': 'code'},
const {'1': 'is_const', '3': 5, '4': 1, '5': 8, '10': 'isConst'},
const {
'1': 'initializer_id',
'3': 6,
'4': 1,
'5': 9,
'10': 'initializerId'
},
],
};
const TypedefInfoPB$json = const {
'1': 'TypedefInfoPB',
'2': const [
const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'},
],
};
const FunctionModifiersPB$json = const {
'1': 'FunctionModifiersPB',
'2': const [
const {'1': 'is_static', '3': 1, '4': 1, '5': 8, '10': 'isStatic'},
const {'1': 'is_const', '3': 2, '4': 1, '5': 8, '10': 'isConst'},
const {'1': 'is_factory', '3': 3, '4': 1, '5': 8, '10': 'isFactory'},
const {'1': 'is_external', '3': 4, '4': 1, '5': 8, '10': 'isExternal'},
],
};
const ParameterInfoPB$json = const {
'1': 'ParameterInfoPB',
'2': const [
const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
const {'1': 'type', '3': 2, '4': 1, '5': 9, '10': 'type'},
const {'1': 'declared_type', '3': 3, '4': 1, '5': 9, '10': 'declaredType'},
],
};
const FunctionInfoPB$json = const {
'1': 'FunctionInfoPB',
'2': const [
const {
'1': 'function_modifiers',
'3': 1,
'4': 1,
'5': 11,
'6': '.dart2js_info.proto.FunctionModifiersPB',
'10': 'functionModifiers'
},
const {'1': 'children_ids', '3': 2, '4': 3, '5': 9, '10': 'childrenIds'},
const {'1': 'return_type', '3': 3, '4': 1, '5': 9, '10': 'returnType'},
const {
'1': 'inferred_return_type',
'3': 4,
'4': 1,
'5': 9,
'10': 'inferredReturnType'
},
const {
'1': 'parameters',
'3': 5,
'4': 3,
'5': 11,
'6': '.dart2js_info.proto.ParameterInfoPB',
'10': 'parameters'
},
const {'1': 'side_effects', '3': 6, '4': 1, '5': 9, '10': 'sideEffects'},
const {'1': 'inlined_count', '3': 7, '4': 1, '5': 5, '10': 'inlinedCount'},
const {'1': 'code', '3': 8, '4': 1, '5': 9, '10': 'code'},
],
'9': const [
const {'1': 9, '2': 10},
],
};
const ClosureInfoPB$json = const {
'1': 'ClosureInfoPB',
'2': const [
const {'1': 'function_id', '3': 1, '4': 1, '5': 9, '10': 'functionId'},
],
};
const DeferredImportPB$json = const {
'1': 'DeferredImportPB',
'2': const [
const {'1': 'prefix', '3': 1, '4': 1, '5': 9, '10': 'prefix'},
const {'1': 'files', '3': 2, '4': 3, '5': 9, '10': 'files'},
],
};
const LibraryDeferredImportsPB$json = const {
'1': 'LibraryDeferredImportsPB',
'2': const [
const {'1': 'library_uri', '3': 1, '4': 1, '5': 9, '10': 'libraryUri'},
const {'1': 'library_name', '3': 2, '4': 1, '5': 9, '10': 'libraryName'},
const {
'1': 'imports',
'3': 3,
'4': 3,
'5': 11,
'6': '.dart2js_info.proto.DeferredImportPB',
'10': 'imports'
},
],
};

View file

@ -0,0 +1,8 @@
///
// Generated code. Do not modify.
// source: info.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
export 'info.pb.dart';

View file

@ -0,0 +1,113 @@
// 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.
/// Defines [StringEditBuffer], a buffer that can be used to apply edits on a
/// string.
// TODO(sigmund): this should move to a separate package.
library dart2js.src.string_edit_buffer;
/// A buffer meant to apply edits on a string (rather than building a string
/// from scratch). Each change is described using the location information on
/// the original string. Internally this buffer keeps track of how a
/// modification in one portion can offset a modification further down the
/// string.
class StringEditBuffer {
final String original;
final _edits = <_StringEdit>[];
StringEditBuffer(this.original);
bool get hasEdits => _edits.length > 0;
/// Edit the original text, replacing text on the range [begin] and
/// exclusive [end] with the [replacement] string.
void replace(int begin, int end, String replacement, [int sortId]) {
_edits.add(new _StringEdit(begin, end, replacement, sortId));
}
/// Insert [string] at [offset].
/// Equivalent to `replace(offset, offset, string)`.
void insert(int offset, String string, [sortId]) =>
replace(offset, offset, string, sortId);
/// Remove text from the range [begin] to exclusive [end].
/// Equivalent to `replace(begin, end, '')`.
void remove(int begin, int end) => replace(begin, end, '');
/// Applies all pending [edit]s and returns a new string.
///
/// This method is non-destructive: it does not discard existing edits or
/// change the [original] string. Further edits can be added and this method
/// can be called again.
///
/// Throws [UnsupportedError] if the edits were overlapping. If no edits were
/// made, the original string will be returned.
String toString() {
var sb = new StringBuffer();
if (_edits.length == 0) return original;
// Sort edits by start location.
_edits.sort();
int consumed = 0;
for (var edit in _edits) {
if (consumed > edit.begin) {
sb = new StringBuffer();
sb.write('overlapping edits. Insert at offset ');
sb.write(edit.begin);
sb.write(' but have consumed ');
sb.write(consumed);
sb.write(' input characters. List of edits:');
for (var e in _edits) {
sb.write('\n ');
sb.write(e);
}
throw new UnsupportedError(sb.toString());
}
// Add characters from the original string between this edit and the last
// one, if any.
var betweenEdits = original.substring(consumed, edit.begin);
sb.write(betweenEdits);
sb.write(edit.string);
consumed = edit.end;
}
// Add any text from the end of the original string that was not replaced.
sb.write(original.substring(consumed));
return sb.toString();
}
}
/// A single edit in a [StringEditBuffer].
class _StringEdit implements Comparable<_StringEdit> {
// Offset where edit begins
final int begin;
// Offset where edit ends
final int end;
// Sort index as a tie-breaker for edits that have the same location.
final int sortId;
// String to insert
final String string;
_StringEdit(int begin, this.end, this.string, [int sortId])
: begin = begin,
sortId = sortId == null ? begin : sortId;
int get length => end - begin;
String toString() => '(Edit @ $begin,$end: "$string")';
int compareTo(_StringEdit other) {
int diff = begin - other.begin;
if (diff != 0) return diff;
diff = end - other.end;
if (diff != 0) return diff;
// use edit order as a tie breaker
return sortId - other.sortId;
}
}

View file

@ -0,0 +1,126 @@
// 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.
library dart2js_info.src.table;
import 'dart:math' show max;
/// Helper class to present data on the command-line in a table form.
class Table {
int _totalColumns = 0;
int get totalColumns => _totalColumns;
/// Abbreviations, used to make headers shorter.
Map<String, String> abbreviations = {};
/// Width of each column.
List<int> widths = <int>[];
/// The header for each column (`header.length == totalColumns`).
List header = [];
/// The color for each column (`color.length == totalColumns`).
List colors = [];
/// Each row on the table. Note that all rows have the same size
/// (`rows[*].length == totalColumns`).
List<List> rows = [];
/// Columns to skip, for example, if they are all zero entries.
List<bool> _skipped = <bool>[];
/// Whether we started adding entries. Indicates that no more columns can be
/// added.
bool _sealed = false;
/// Current row being built by [addEntry].
List _currentRow;
/// Add a column with the given [name].
void declareColumn(String name,
{bool abbreviate: false, String color: _NO_COLOR}) {
assert(!_sealed);
var headerName = name;
if (abbreviate) {
// abbreviate the header by using only the initials of each word
headerName =
name.split(' ').map((s) => s.substring(0, 1).toUpperCase()).join('');
while (abbreviations[headerName] != null) headerName = "$headerName'";
abbreviations[headerName] = name;
}
widths.add(max(5, headerName.length + 1));
header.add(headerName);
colors.add(color);
_skipped.add(_totalColumns > 0);
_totalColumns++;
}
/// Add an entry in the table, creating a new row each time [totalColumns]
/// entries are added.
void addEntry(entry) {
if (_currentRow == null) {
_sealed = true;
_currentRow = [];
}
int pos = _currentRow.length;
assert(pos < _totalColumns);
widths[pos] = max(widths[pos], '$entry'.length + 1);
_currentRow.add('$entry');
if (entry is int && entry != 0) {
_skipped[pos] = false;
}
if (pos + 1 == _totalColumns) {
rows.add(_currentRow);
_currentRow = [];
}
}
/// Add an empty row to divide sections of the table.
void addEmptyRow() {
var emptyRow = [];
for (int i = 0; i < _totalColumns; i++) {
emptyRow.add('-' * widths[i]);
}
rows.add(emptyRow);
}
/// Enter the header titles. OK to do so more than once in long tables.
void addHeader() {
rows.add(header);
}
/// Generates a string representation of the table to print on a terminal.
// TODO(sigmund): add also a .csv format
String toString() {
var sb = new StringBuffer();
sb.write('\n');
for (var row in rows) {
var lastColor = _NO_COLOR;
for (int i = 0; i < _totalColumns; i++) {
if (_skipped[i]) continue;
var entry = row[i];
var color = colors[i];
if (lastColor != color) {
sb.write(color);
lastColor = color;
}
// Align first column to the left, everything else to the right.
sb.write(
i == 0 ? entry.padRight(widths[i]) : entry.padLeft(widths[i] + 1));
}
if (lastColor != _NO_COLOR) sb.write(_NO_COLOR);
sb.write('\n');
}
sb.write('\nWhere:\n');
for (var id in abbreviations.keys) {
sb.write(' $id:'.padRight(7));
sb.write(' ${abbreviations[id]}\n');
}
return sb.toString();
}
}
const _NO_COLOR = "\x1b[0m";

View file

@ -0,0 +1,99 @@
// 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.
library dart2js_info.src.util;
import 'package:dart2js_info/info.dart';
import 'graph.dart';
/// Computes a graph of dependencies from [info].
Graph<Info> graphFromInfo(AllInfo info) {
print(' info: dependency graph information is work in progress and'
' might be incomplete');
// Note: we are combining dependency information that is computed in two ways
// (functionInfo.uses vs allInfo.dependencies).
// TODO(sigmund): fix inconsistencies between these two ways, stick with one
// of them.
// TODO(sigmund): create a concrete implementation of InfoGraph, instead of
// using the EdgeListGraph.
var graph = new EdgeListGraph<Info>();
for (var f in info.functions) {
graph.addNode(f);
for (var g in f.uses) {
graph.addEdge(f, g.target);
}
if (info.dependencies[f] != null) {
for (var g in info.dependencies[f]) {
graph.addEdge(f, g);
}
}
}
for (var f in info.fields) {
graph.addNode(f);
for (var g in f.uses) {
graph.addEdge(f, g.target);
}
if (info.dependencies[f] != null) {
for (var g in info.dependencies[f]) {
graph.addEdge(f, g);
}
}
}
return graph;
}
/// Provide a unique long name associated with [info].
// TODO(sigmund): guarantee that the name is actually unique.
String longName(Info info, {bool useLibraryUri: false, bool forId: false}) {
var infoPath = [];
while (info != null) {
infoPath.add(info);
info = info.parent;
}
var sb = new StringBuffer();
var first = true;
for (var segment in infoPath.reversed) {
if (!first) sb.write('.');
// TODO(sigmund): ensure that the first segment is a LibraryInfo.
// assert(!first || segment is LibraryInfo);
// (today might not be true for for closure classes).
if (segment is LibraryInfo) {
// TODO(kevmoo): Remove this when dart2js can be invoked with an app-root
// custom URI
if (useLibraryUri && forId && segment.uri.isScheme('file')) {
assert(Uri.base.isScheme('file'));
var currentBase = Uri.base.path;
var segmentString = segment.uri.path;
// If longName is being called to calculate an element ID (forId = true)
// then use a relative path for the longName calculation
// This allows a more stable ID for cases when files are generated into
// temp directories e.g. with pkg:build_web_compilers
if (segmentString.startsWith(currentBase)) {
segmentString = segmentString.substring(currentBase.length);
}
sb.write(segmentString);
} else {
sb.write(useLibraryUri ? segment.uri : segment.name);
}
sb.write('::');
} else {
first = false;
sb.write(segment.name);
}
}
return sb.toString();
}
/// Produce a string containing [value] padded with white space up to [n] chars.
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';
}

View file

@ -0,0 +1,25 @@
name: dart2js_info
version: 0.6.5
description: >-
Libraries and tools to process data produced when running dart2js with
--dump-info.
homepage: https://github.com/dart-lang/dart2js_info/
environment:
sdk: '>=2.3.0 <3.0.0'
dependencies:
args: ^1.4.3
collection: ^1.10.1
fixnum: '>=0.10.5 <2.0.0'
path: ^1.3.6
protobuf: '>=1.0.1 <3.0.0'
shelf: ^0.7.3
yaml: ^2.1.0
dev_dependencies:
test: ^1.2.0
executables:
dart2js_info: tools

View file

@ -0,0 +1,40 @@
// 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.
import 'dart:convert';
import 'dart:io';
import 'package:dart2js_info/json_info_codec.dart';
import 'package:dart2js_info/binary_serialization.dart' as binary;
import 'package:test/test.dart';
class ByteSink implements Sink<List<int>> {
BytesBuilder builder = new BytesBuilder();
add(List<int> data) => builder.add(data);
close() {}
}
main() {
group('json to proto conversion with deferred files', () {
test('hello_world_deferred', () {
var helloWorld = new File(
'test/hello_world_deferred/hello_world_deferred.js.info.json');
var contents = helloWorld.readAsStringSync();
var json = jsonDecode(contents);
var info = new AllInfoJsonCodec().decode(json);
var sink = new ByteSink();
binary.encode(info, sink);
var info2 = binary.decode(sink.builder.toBytes());
var json2 = new AllInfoJsonCodec().encode(info2);
info.program.toJsonDuration = new Duration(milliseconds: 0);
var json1 = new AllInfoJsonCodec().encode(info);
var contents1 = const JsonEncoder.withIndent(" ").convert(json1);
var contents2 = const JsonEncoder.withIndent(" ").convert(json2);
expect(contents1 == contents2, isTrue);
});
});
}

View file

@ -0,0 +1,73 @@
// 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.
import 'package:dart2js_info/src/graph.dart';
import 'package:test/test.dart';
main() {
var graph = makeTestGraph();
test('preorder traversal', () {
expect(graph.preOrder('A').toList(), equals(['A', 'E', 'D', 'C', 'B']));
});
test('postorder traversal', () {
expect(graph.postOrder('A').toList(), equals(['C', 'E', 'D', 'B', 'A']));
});
test('topological sort', () {
expect(
graph.computeTopologicalSort(),
equals([
['C'],
['E'],
['D', 'B', 'A']
]));
});
test('contains path', () {
expect(graph.containsPath('A', 'C'), isTrue);
expect(graph.containsPath('B', 'E'), isTrue);
expect(graph.containsPath('C', 'A'), isFalse);
expect(graph.containsPath('E', 'B'), isFalse);
});
test('dominator tree', () {
// A dominates all other nodes in the graph, the resulting tree looks like
// A
// / / | |
// B C D E
var dom = graph.dominatorTree('A');
expect(dom.targetsOf('A').length, equals(4));
});
test('cycle finding', () {
expect(graph.findCycleContaining('B'), equals(['A', 'D', 'B']));
expect(graph.findCycleContaining('C'), equals(['C']));
});
}
/// Creates a simple test graph with the following structure:
/// ```
/// A -> E
/// / ^ ^
/// / \ /
/// v v /
/// B -> D
/// \ /
/// v v
/// C
/// ```
Graph<String> makeTestGraph() {
var graph = new EdgeListGraph<String>();
graph.addEdge('A', 'B');
graph.addEdge('A', 'D');
graph.addEdge('A', 'E');
graph.addEdge('B', 'C');
graph.addEdge('B', 'D');
graph.addEdge('D', 'A');
graph.addEdge('D', 'C');
graph.addEdge('D', 'E');
return graph;
}

View file

@ -0,0 +1,3 @@
main() {
print("Hello, World!");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
const helloWorld = "Hello, World!";

View file

@ -0,0 +1,6 @@
import 'deferred_import.dart' deferred as deferred_import;
Future<void> main() async {
await deferred_import.loadLibrary();
print(deferred_import.helloWorld);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
// 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.
import 'dart:convert';
import 'dart:io';
import 'package:dart2js_info/json_info_codec.dart';
import 'package:dart2js_info/proto_info_codec.dart';
import 'package:test/test.dart';
main() {
group('json to proto conversion with deferred files', () {
test('hello_world_deferred', () {
final helloWorld = new File(
'test/hello_world_deferred/hello_world_deferred.js.info.json');
final json = jsonDecode(helloWorld.readAsStringSync());
final decoded = new AllInfoJsonCodec().decode(json);
final proto = new AllInfoProtoCodec().encode(decoded);
expect(proto.deferredImports, hasLength(1));
final libraryImports = proto.deferredImports.first;
expect(libraryImports.libraryUri, 'hello_world_deferred.dart');
expect(libraryImports.libraryName, '<unnamed>');
expect(libraryImports.imports, hasLength(1));
final import = libraryImports.imports.first;
expect(import.prefix, 'deferred_import');
expect(import.files, hasLength(1));
expect(import.files.first, 'hello_world_deferred.js_1.part.js');
final infoMap = proto.allInfos;
final entrypoint = infoMap[proto.program.entrypointId];
expect(entrypoint, isNotNull);
expect(entrypoint.hasFunctionInfo(), isTrue);
expect(entrypoint.outputUnitId, isNotNull);
// The output unit of the entrypoint function should be the default
// entrypoint, which should have no imports.
final defaultOutputUnit = infoMap[entrypoint.outputUnitId];
expect(defaultOutputUnit, isNotNull);
expect(defaultOutputUnit.hasOutputUnitInfo(), isTrue);
expect(defaultOutputUnit.outputUnitInfo.imports, isEmpty);
});
});
}

View file

@ -0,0 +1,74 @@
// 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.
import 'dart:convert';
import 'dart:io';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/json_info_codec.dart';
import 'package:dart2js_info/proto_info_codec.dart';
import 'package:test/test.dart';
main() {
group('json to proto conversion', () {
test('hello_world', () {
final helloWorld = new File('test/hello_world/hello_world.js.info.json');
final json = jsonDecode(helloWorld.readAsStringSync());
final decoded = new AllInfoJsonCodec().decode(json);
final proto = new AllInfoProtoCodec().encode(decoded);
expect(proto.program.entrypointId, isNotNull);
expect(proto.program.size, 10324);
expect(proto.program.compilationMoment.toInt(),
DateTime.parse("2017-04-17 09:46:41.661617").microsecondsSinceEpoch);
expect(proto.program.toProtoDuration.toInt(),
new Duration(milliseconds: 4).inMicroseconds);
expect(proto.program.dumpInfoDuration.toInt(),
new Duration(milliseconds: 0).inMicroseconds);
expect(proto.program.noSuchMethodEnabled, isFalse);
expect(proto.program.minified, isFalse);
});
test('has proper id format', () {
final helloWorld = new File('test/hello_world/hello_world.js.info.json');
final json = jsonDecode(helloWorld.readAsStringSync());
final decoded = new AllInfoJsonCodec().decode(json);
final proto = new AllInfoProtoCodec().encode(decoded);
final expectedPrefixes = <InfoKind, String>{};
for (final kind in InfoKind.values) {
expectedPrefixes[kind] = kindToString(kind) + '/';
}
for (final info in proto.allInfos.entries) {
final value = info.value;
if (value.hasLibraryInfo()) {
expect(value.serializedId,
startsWith(expectedPrefixes[InfoKind.library]));
} else if (value.hasClassInfo()) {
expect(
value.serializedId, startsWith(expectedPrefixes[InfoKind.clazz]));
} else if (value.hasFunctionInfo()) {
expect(value.serializedId,
startsWith(expectedPrefixes[InfoKind.function]));
} else if (value.hasFieldInfo()) {
expect(
value.serializedId, startsWith(expectedPrefixes[InfoKind.field]));
} else if (value.hasConstantInfo()) {
expect(value.serializedId,
startsWith(expectedPrefixes[InfoKind.constant]));
} else if (value.hasOutputUnitInfo()) {
expect(value.serializedId,
startsWith(expectedPrefixes[InfoKind.outputUnit]));
} else if (value.hasTypedefInfo()) {
expect(value.serializedId,
startsWith(expectedPrefixes[InfoKind.typedef]));
} else if (value.hasClosureInfo()) {
expect(value.serializedId,
startsWith(expectedPrefixes[InfoKind.closure]));
}
}
});
});
}

View file

@ -0,0 +1,32 @@
// 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.
import 'dart:convert';
import 'dart:io';
import 'package:dart2js_info/json_info_codec.dart';
import 'package:test/test.dart';
main() {
group('parse', () {
test('hello_world', () {
var helloWorld = new File('test/hello_world/hello_world.js.info.json');
var json = jsonDecode(helloWorld.readAsStringSync());
var decoded = new AllInfoJsonCodec().decode(json);
var program = decoded.program;
expect(program, isNotNull);
expect(program.entrypoint, isNotNull);
expect(program.size, 10324);
expect(program.compilationMoment,
DateTime.parse("2017-04-17 09:46:41.661617"));
expect(program.compilationDuration, new Duration(microseconds: 357402));
expect(program.toJsonDuration, new Duration(milliseconds: 4));
expect(program.dumpInfoDuration, new Duration(seconds: 0));
expect(program.noSuchMethodEnabled, false);
expect(program.minified, false);
});
});
}

View file

@ -0,0 +1,13 @@
#!/bin/bash
set -e
if [ -z "$1" ]; then
echo "Expected exactly one argument which is the protoc_plugin version to use"
else
echo "Using protoc_plugin version $1"
pub global activate protoc_plugin "$1"
fi
protoc --proto_path="." --dart_out=lib/src/proto info.proto
dartfmt -w lib/src/proto