mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 14:32:24 +00:00
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:
commit
cf411dc539
59 changed files with 36786 additions and 0 deletions
16
pkg/dart2js_info/.github/move.yml
vendored
Normal file
16
pkg/dart2js_info/.github/move.yml
vendored
Normal 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
|
61
pkg/dart2js_info/.github/workflows/test-package.yml
vendored
Normal file
61
pkg/dart2js_info/.github/workflows/test-package.yml
vendored
Normal 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
14
pkg/dart2js_info/.gitignore
vendored
Normal 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
4
pkg/dart2js_info/.status
Normal 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
6
pkg/dart2js_info/AUTHORS
Normal 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.
|
180
pkg/dart2js_info/CHANGELOG.md
Normal file
180
pkg/dart2js_info/CHANGELOG.md
Normal 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
|
33
pkg/dart2js_info/CONTRIBUTING.md
Normal file
33
pkg/dart2js_info/CONTRIBUTING.md
Normal 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).
|
54
pkg/dart2js_info/DEVELOPER.md
Normal file
54
pkg/dart2js_info/DEVELOPER.md
Normal 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
27
pkg/dart2js_info/LICENSE
Normal 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
520
pkg/dart2js_info/README.md
Normal 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
|
131
pkg/dart2js_info/bin/src/code_deps.dart
Normal file
131
pkg/dart2js_info/bin/src/code_deps.dart
Normal 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));
|
38
pkg/dart2js_info/bin/src/convert.dart
Normal file
38
pkg/dart2js_info/bin/src/convert.dart
Normal 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');
|
||||
}
|
||||
}
|
220
pkg/dart2js_info/bin/src/coverage_log_server.dart
Normal file
220
pkg/dart2js_info/bin/src/coverage_log_server.dart
Normal 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'};
|
321
pkg/dart2js_info/bin/src/debug_info.dart
Normal file
321
pkg/dart2js_info/bin/src/debug_info.dart
Normal 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');
|
71
pkg/dart2js_info/bin/src/deferred_library_check.dart
Normal file
71
pkg/dart2js_info/bin/src/deferred_library_check.dart
Normal 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);
|
||||
}
|
83
pkg/dart2js_info/bin/src/deferred_library_layout.dart
Normal file
83
pkg/dart2js_info/bin/src/deferred_library_layout.dart
Normal 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);
|
90
pkg/dart2js_info/bin/src/deferred_library_size.dart
Normal file
90
pkg/dart2js_info/bin/src/deferred_library_size.dart
Normal 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;
|
||||
}
|
183
pkg/dart2js_info/bin/src/diff.dart
Normal file
183
pkg/dart2js_info/bin/src/diff.dart
Normal 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);
|
||||
}
|
181
pkg/dart2js_info/bin/src/function_size_analysis.dart
Normal file
181
pkg/dart2js_info/bin/src/function_size_analysis.dart
Normal 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');
|
||||
}
|
40
pkg/dart2js_info/bin/src/inject_text.dart
Normal file
40
pkg/dart2js_info/bin/src/inject_text.dart
Normal 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);
|
||||
}
|
||||
}
|
230
pkg/dart2js_info/bin/src/library_size_split.dart
Normal file
230
pkg/dart2js_info/bin/src/library_size_split.dart
Normal 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}/(.*)" }
|
||||
""";
|
138
pkg/dart2js_info/bin/src/live_code_size_analysis.dart
Normal file
138
pkg/dart2js_info/bin/src/live_code_size_analysis.dart
Normal 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)}%');
|
||||
}
|
69
pkg/dart2js_info/bin/src/show_inferred_types.dart
Normal file
69
pkg/dart2js_info/bin/src/show_inferred_types.dart
Normal 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);
|
||||
}
|
||||
}
|
210
pkg/dart2js_info/bin/src/text_print.dart
Normal file
210
pkg/dart2js_info/bin/src/text_print.dart
Normal 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)}');
|
||||
}
|
||||
}
|
35
pkg/dart2js_info/bin/src/to_binary.dart
Normal file
35
pkg/dart2js_info/bin/src/to_binary.dart
Normal 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;
|
||||
}
|
||||
}
|
54
pkg/dart2js_info/bin/src/to_json.dart
Normal file
54
pkg/dart2js_info/bin/src/to_json.dart
Normal 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));
|
||||
}
|
||||
}
|
37
pkg/dart2js_info/bin/src/to_proto.dart
Normal file
37
pkg/dart2js_info/bin/src/to_proto.dart
Normal 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);
|
||||
}
|
||||
}
|
16
pkg/dart2js_info/bin/src/usage_exception.dart
Normal file
16
pkg/dart2js_info/bin/src/usage_exception.dart
Normal 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);
|
||||
}
|
||||
}
|
39
pkg/dart2js_info/bin/tools.dart
Normal file
39
pkg/dart2js_info/bin/tools.dart
Normal 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
267
pkg/dart2js_info/info.proto
Normal 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;
|
||||
}
|
459
pkg/dart2js_info/lib/binary_serialization.dart
Normal file
459
pkg/dart2js_info/lib/binary_serialization.dart
Normal 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;
|
177
pkg/dart2js_info/lib/deferred_library_check.dart
Normal file
177
pkg/dart2js_info/lib/deferred_library_check.dart
Normal 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.';
|
||||
}
|
||||
}
|
549
pkg/dart2js_info/lib/info.dart
Normal file
549
pkg/dart2js_info/lib/info.dart
Normal 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);
|
||||
}
|
||||
}
|
600
pkg/dart2js_info/lib/json_info_codec.dart
Normal file
600
pkg/dart2js_info/lib/json_info_codec.dart
Normal 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';
|
||||
}
|
312
pkg/dart2js_info/lib/proto_info_codec.dart
Normal file
312
pkg/dart2js_info/lib/proto_info_codec.dart
Normal 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';
|
||||
}
|
401
pkg/dart2js_info/lib/src/binary/sink.dart
Normal file
401
pkg/dart2js_info/lib/src/binary/sink.dart
Normal 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));
|
||||
}
|
||||
}
|
296
pkg/dart2js_info/lib/src/binary/source.dart
Normal file
296
pkg/dart2js_info/lib/src/binary/source.dart
Normal 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];
|
||||
}
|
||||
}
|
165
pkg/dart2js_info/lib/src/diff.dart
Normal file
165
pkg/dart2js_info/lib/src/diff.dart
Normal 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
334
pkg/dart2js_info/lib/src/graph.dart
Normal file
334
pkg/dart2js_info/lib/src/graph.dart
Normal 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;
|
||||
}
|
||||
}
|
15
pkg/dart2js_info/lib/src/io.dart
Normal file
15
pkg/dart2js_info/lib/src/io.dart
Normal 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());
|
||||
}
|
||||
}
|
1318
pkg/dart2js_info/lib/src/proto/info.pb.dart
Normal file
1318
pkg/dart2js_info/lib/src/proto/info.pb.dart
Normal file
File diff suppressed because it is too large
Load diff
6
pkg/dart2js_info/lib/src/proto/info.pbenum.dart
Normal file
6
pkg/dart2js_info/lib/src/proto/info.pbenum.dart
Normal 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
|
381
pkg/dart2js_info/lib/src/proto/info.pbjson.dart
Normal file
381
pkg/dart2js_info/lib/src/proto/info.pbjson.dart
Normal 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'
|
||||
},
|
||||
],
|
||||
};
|
8
pkg/dart2js_info/lib/src/proto/info.pbserver.dart
Normal file
8
pkg/dart2js_info/lib/src/proto/info.pbserver.dart
Normal 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';
|
113
pkg/dart2js_info/lib/src/string_edit_buffer.dart
Normal file
113
pkg/dart2js_info/lib/src/string_edit_buffer.dart
Normal 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;
|
||||
}
|
||||
}
|
126
pkg/dart2js_info/lib/src/table.dart
Normal file
126
pkg/dart2js_info/lib/src/table.dart
Normal 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";
|
99
pkg/dart2js_info/lib/src/util.dart
Normal file
99
pkg/dart2js_info/lib/src/util.dart
Normal 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';
|
||||
}
|
25
pkg/dart2js_info/pubspec.yaml
Normal file
25
pkg/dart2js_info/pubspec.yaml
Normal 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
|
40
pkg/dart2js_info/test/binary_serialization_test.dart
Normal file
40
pkg/dart2js_info/test/binary_serialization_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
73
pkg/dart2js_info/test/graph_test.dart
Normal file
73
pkg/dart2js_info/test/graph_test.dart
Normal 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;
|
||||
}
|
3
pkg/dart2js_info/test/hello_world/hello_world.dart
Normal file
3
pkg/dart2js_info/test/hello_world/hello_world.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
main() {
|
||||
print("Hello, World!");
|
||||
}
|
1618
pkg/dart2js_info/test/hello_world/hello_world.js.info.json
Normal file
1618
pkg/dart2js_info/test/hello_world/hello_world.js.info.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1 @@
|
|||
const helloWorld = "Hello, World!";
|
|
@ -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
46
pkg/dart2js_info/test/json_to_proto_deferred_test.dart
Normal file
46
pkg/dart2js_info/test/json_to_proto_deferred_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
74
pkg/dart2js_info/test/json_to_proto_test.dart
Normal file
74
pkg/dart2js_info/test/json_to_proto_test.dart
Normal 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]));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
32
pkg/dart2js_info/test/parse_test.dart
Normal file
32
pkg/dart2js_info/test/parse_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
13
pkg/dart2js_info/tool/update_proto.sh
Executable file
13
pkg/dart2js_info/tool/update_proto.sh
Executable 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
|
Loading…
Reference in a new issue