├── .github ├── move.yml └── workflows │ └── test-package.yml ├── .gitignore ├── .status ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DEVELOPER.md ├── LICENSE ├── README.md ├── bin ├── src │ ├── code_deps.dart │ ├── convert.dart │ ├── coverage_log_server.dart │ ├── debug_info.dart │ ├── deferred_library_check.dart │ ├── deferred_library_layout.dart │ ├── deferred_library_size.dart │ ├── diff.dart │ ├── function_size_analysis.dart │ ├── inject_text.dart │ ├── library_size_split.dart │ ├── live_code_size_analysis.dart │ ├── show_inferred_types.dart │ ├── text_print.dart │ ├── to_binary.dart │ ├── to_json.dart │ ├── to_proto.dart │ └── usage_exception.dart └── tools.dart ├── info.proto ├── lib ├── binary_serialization.dart ├── deferred_library_check.dart ├── info.dart ├── json_info_codec.dart ├── proto_info_codec.dart └── src │ ├── binary │ ├── sink.dart │ └── source.dart │ ├── diff.dart │ ├── graph.dart │ ├── io.dart │ ├── proto │ ├── info.pb.dart │ ├── info.pbenum.dart │ ├── info.pbjson.dart │ └── info.pbserver.dart │ ├── string_edit_buffer.dart │ ├── table.dart │ └── util.dart ├── pubspec.yaml ├── test ├── binary_serialization_test.dart ├── graph_test.dart ├── hello_world │ ├── hello_world.dart │ └── hello_world.js.info.json ├── hello_world_deferred │ ├── deferred_import.dart │ ├── hello_world_deferred.dart │ └── hello_world_deferred.js.info.json ├── json_to_proto_deferred_test.dart ├── json_to_proto_test.dart └── parse_test.dart └── tool └── update_proto.sh /.github/move.yml: -------------------------------------------------------------------------------- 1 | # Configuration for move-issues - https://github.com/dessant/move-issues 2 | 3 | # Delete the command comment when it contains no other content. 4 | deleteCommand: true 5 | 6 | # Close the source issue after moving. 7 | closeSourceIssue: true 8 | 9 | # Lock the source issue after moving. 10 | lockSourceIssue: false 11 | 12 | # Mention issue and comment authors. 13 | mentionAuthors: true 14 | 15 | # Preserve mentions in the issue content. 16 | keepContentMentions: true 17 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: dart-lang/setup-dart@v1.0 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [2.12.0, dev] 51 | steps: 52 | - uses: actions/checkout@v2 53 | - uses: dart-lang/setup-dart@v1.0 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | *.swp 4 | *.swo 5 | .idea 6 | *.iml 7 | .pub/ 8 | .settings/ 9 | build/ 10 | packages 11 | .packages 12 | pubspec.lock 13 | bin/inference/client.dart.js* 14 | .dart_tool/ 15 | -------------------------------------------------------------------------------- /.status: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | # for details. All rights reserved. Use of this source code is governed by a 3 | # BSD-style license that can be found in the LICENSE file. 4 | 5 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.6.5 2 | 3 | * Drop unused dependencies. 4 | 5 | ## 0.6.4 6 | 7 | * Make compatible with the null-safe version of `args`. 8 | 9 | ## 0.6.3 10 | 11 | * Broaden version ranges for `fixnum` and `protobuf` dependencies to make 12 | `dart2js_info` compatible with null-safe `protobuf` version. 13 | 14 | ## 0.6.2 15 | 16 | * Update `protobuf` dependency. 17 | * Set min SDK to `2.3.0`, as generated code contains this version. 18 | 19 | ## 0.6.1 20 | 21 | * Move binary subcommands under src folder. Otherwise, `pub global activate` 22 | fails. 23 | 24 | ## 0.6.0 25 | 26 | This release contains several **breaking changes**: 27 | 28 | * The fields `Info.id` and `Info.serializedId` have been removed. These 29 | properties were only used for serialization and deserialization. Those values 30 | are now computed during the serialization process instead. 31 | 32 | * Added `CodeSpan` - a representation of code regions referring to output files. 33 | This will be used to transition to a lighterweight dump-info that doesn't 34 | embed code snippets (since they are duplicated with the output program). 35 | 36 | Encoder produces a new format for code-spans, but for a transitional period 37 | a flag is provided to produce the old format. The decoder is still backwards 38 | compatible (filling in just the `text` in `CodeSpan` where the json contained 39 | a String). 40 | 41 | * Deleted unused `Measurements`. 42 | 43 | * Split the json codec from info.dart. 44 | 45 | * Introduced `lib/binary_serialization.dart` a lighterweight 46 | serialization/deserialization implementation. This will eventually be used by 47 | default by dart2js. 48 | 49 | * Added backwards compatibility flag to the JSON codec, to make transition to 50 | new tools more gradual. 51 | 52 | * Added a tool to dump info files in a readable text form. 53 | 54 | * Consolidated all binary tools under a single command. Now you can access all 55 | tools as follows: 56 | ``` 57 | pub global activate dart2js_info 58 | dart2js_info [arguments] ... 59 | ``` 60 | 61 | See updated documentation in README.md 62 | 63 | ## 0.5.17 64 | 65 | * Make `live_code_size_analysis` print library URIs and not library names. 66 | 67 | ## 0.5.16 68 | 69 | * Split out IO dependency from `util.dart`, so all other utilities can be used 70 | on any platform. 71 | 72 | ## 0.5.15 73 | 74 | * Add `BasicInfo.resetIds` to free internal cache used for id uniqueness. 75 | 76 | ## 0.5.14 77 | * Updates `coverage_log_server.dart` and `live_code_size_analysis.dart` to make 78 | them strong clean and match the latest changes in dart2js. 79 | 80 | ## 0.5.13 81 | 82 | * Use a more efficient `Map` implementation for decoding existing info files. 83 | 84 | * Use a relative path when generating unique IDs for elements in non-package 85 | sources. 86 | 87 | ## 0.5.12 88 | 89 | * Improved output of `dart2js_info_diff` by sorting the diffs by 90 | size and outputting the summary in full output mode. 91 | 92 | ## 0.5.11 93 | 94 | * Added `--summary` option to `dart2js_info_diff` tool. 95 | 96 | ## 0.5.10 97 | 98 | * Set max SDK version to `<3.0.0`, and adjust other dependencies. 99 | 100 | ## 0.5.6+4 101 | 102 | - Changes to make the library strong mode (runtime) clean. 103 | 104 | ## 0.5.6 105 | 106 | - Added `isRuntimeTypeUsed`, `isIsolateInUse`, `isFunctionApplyUsed` and `isMirrorsUsed` to 107 | `ProgramInfo`. 108 | 109 | ## 0.5.5+1 110 | 111 | - Support the latest versions of `shelf` and `args` packages. 112 | 113 | ## 0.5.5 114 | 115 | - Added `diff` tool. 116 | 117 | ## 0.5.4+2 118 | 119 | - Updated minimum SDK dependency to align with package dependencies. 120 | - Allowed the latest version of `pkg/quiver`. 121 | - Updated the homepage. 122 | - Improved the stability and eliminated duplicates in "holding" dump info 123 | output. 124 | 125 | ## 0.5.4+1 126 | 127 | - Remove files published accidentally. 128 | 129 | ## 0.5.4 130 | 131 | - Added script to show inferred types of functions and fields on the command 132 | line. 133 | 134 | ## 0.5.3+1 135 | 136 | - Improved the stability of `ConstantInfo.id`. 137 | 138 | ## 0.5.3 139 | 140 | - Made IDs in the JSON format stable. Improves plain text diffing. 141 | 142 | ## 0.2.7 143 | - Make dart2js_info strong-mode clean. 144 | 145 | ## 0.2.6 146 | - Add tool to get breakdown of deferred libraries by size. 147 | 148 | ## 0.2.5 149 | - Changed the `deferred_library_check` tool to allow parts to exclude packages 150 | and to not assume that unspecified packages are in the main part. 151 | 152 | ## 0.2.4 153 | - Added `imports` field for `OutputUnitInfo` 154 | 155 | ## 0.2.3 156 | - Moved `deferred_library_check` functionality to a library 157 | 158 | ## 0.2.2 159 | - Added `deferred_libary_check` tool 160 | 161 | ## 0.2.1 162 | - Merged `verify_deps` tool into `debug_info` tool 163 | 164 | ## 0.2.0 165 | - Added `AllInfoJsonCodec` 166 | - Added `verify_deps` tool 167 | 168 | ## 0.1.0 169 | - Added `ProgramInfo.entrypoint`. 170 | - Added experimental information about calls in function bodies. This will 171 | likely change again in the near future. 172 | 173 | ## 0.0.3 174 | - Added executable names 175 | 176 | ## 0.0.2 177 | - Add support for `ConstantInfo` 178 | 179 | ## 0.0.1 180 | - Initial version 181 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | Before we can use your code, you must sign the 6 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | 15 | Before you start working on a larger contribution, you should get in touch with 16 | us first through the issue tracker with your idea so that we can help out and 17 | possibly guide you. Coordinating up front makes it much easier to avoid 18 | frustration later on. 19 | 20 | ### Code reviews 21 | All submissions, including submissions by project members, require review. 22 | 23 | ### File headers 24 | All files in the project must start with the following header. 25 | 26 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 27 | // for details. All rights reserved. Use of this source code is governed by a 28 | // BSD-style license that can be found in the LICENSE file. 29 | 30 | ### The small print 31 | Contributions made by corporations are covered by a different agreement than the 32 | one above, the 33 | [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). 34 | -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Notes for developers 2 | 3 | ## Developing locally together with dart2js: 4 | 5 | * Use a path dependency on this repo to prepare changes. 6 | 7 | ## Submitting changes. 8 | 9 | * Submit changes in this repo first. 10 | * Update the sdk/DEPS and sdk/tools/deps/dartium.deps/DEPS to use the latest 11 | hash of this repo. 12 | * Submit dart2js changes together with the roll in DEPS. 13 | 14 | ## Updating the dart2js\_info dart docs 15 | 16 | We use `dartdoc` and host the generated documentation as a [github page][1] in 17 | this repo. Here is how to update it: 18 | 19 | * Make sure you have the dartdoc tool installed: 20 | 21 | ``` 22 | pub global activate dartdoc 23 | ``` 24 | 25 | * Run the dartdoc tool on the root of the repo in master, specify an out 26 | directory different than `doc`: 27 | 28 | ```sh 29 | dartdoc --output _docs 30 | ``` 31 | 32 | * Switch to the `gh-pages` branch: 33 | 34 | ``` 35 | git checkout gh-pages 36 | git pull 37 | ``` 38 | 39 | * Override the existing docs by hand: 40 | 41 | ``` 42 | rm -r doc/api 43 | mv _docs doc/api 44 | git diff # validate changes look right 45 | git commit -a -m "Update documentation ... " 46 | ``` 47 | 48 | * Update the gh-pages branch in the server 49 | ``` 50 | git push origin gh-pages 51 | ``` 52 | 53 | 54 | [1]: http://dart-lang.github.io/dart2js_info/doc/api/dart2js_info.info/AllInfo-class.html 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /bin/src/code_deps.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Command to query for code dependencies. Currently this tool only 6 | /// supports the `some_path` query, which gives you the shortest path for how 7 | /// one function depends on another. 8 | /// 9 | /// You can run this tool as follows: 10 | /// ```bash 11 | /// pub global activate dart2js_info 12 | /// dart2js_info code_deps some_path out.js.info.json main foo 13 | /// ``` 14 | /// 15 | /// The arguments to the query are regular expressions that can be used to 16 | /// select a single element in your program. If your regular expression is too 17 | /// general and has more than one match, this tool will pick 18 | /// the first match and ignore the rest. Regular expressions are matched against 19 | /// a fully qualified element name, which includes the library and class name 20 | /// (if any) that contains it. A typical qualified name is of this form: 21 | /// 22 | /// libraryName::ClassName.elementName 23 | /// 24 | /// If the name of a function your are looking for is unique enough, it might be 25 | /// sufficient to just write that name as your regular expression. 26 | library dart2js_info.bin.code_deps; 27 | 28 | import 'dart:collection'; 29 | 30 | import 'package:args/command_runner.dart'; 31 | 32 | import 'package:dart2js_info/info.dart'; 33 | import 'package:dart2js_info/src/graph.dart'; 34 | import 'package:dart2js_info/src/io.dart'; 35 | import 'package:dart2js_info/src/util.dart'; 36 | 37 | import 'usage_exception.dart'; 38 | 39 | class CodeDepsCommand extends Command with PrintUsageException { 40 | final String name = "code_deps"; 41 | final String description = ""; 42 | 43 | CodeDepsCommand() { 44 | addSubcommand(new _SomePathQuery()); 45 | } 46 | } 47 | 48 | class _SomePathQuery extends Command with PrintUsageException { 49 | final String name = "some_path"; 50 | final String description = "find a call-graph path between two elements."; 51 | 52 | @override 53 | void run() async { 54 | var args = argResults.rest; 55 | if (args.length < 3) { 56 | usageException("Missing arguments for some_path, expected: " 57 | "info.data "); 58 | return; 59 | } 60 | 61 | var info = await infoFromFile(args.first); 62 | var graph = graphFromInfo(info); 63 | 64 | var source = info.functions 65 | .firstWhere(_longNameMatcher(new RegExp(args[1])), orElse: () => null); 66 | var target = info.functions 67 | .firstWhere(_longNameMatcher(new RegExp(args[2])), orElse: () => null); 68 | print('query: some_path'); 69 | if (source == null) { 70 | usageException("source '${args[1]}' not found in '${args[0]}'"); 71 | } 72 | print('source: ${longName(source)}'); 73 | if (target == null) { 74 | usageException("target '${args[2]}' not found in '${args[0]}'"); 75 | } 76 | print('target: ${longName(target)}'); 77 | var path = new SomePathQuery(source, target).run(graph); 78 | if (path.isEmpty) { 79 | print('result: no path found'); 80 | } else { 81 | print('result:'); 82 | for (int i = 0; i < path.length; i++) { 83 | print(' $i. ${longName(path[i])}'); 84 | } 85 | } 86 | } 87 | } 88 | 89 | /// A query supported by this tool. 90 | abstract class Query { 91 | run(Graph graph); 92 | } 93 | 94 | /// Query that searches for a single path between two elements. 95 | class SomePathQuery { 96 | /// The info associated with the source element. 97 | Info source; 98 | 99 | /// The info associated with the target element. 100 | Info target; 101 | 102 | SomePathQuery(this.source, this.target); 103 | 104 | List run(Graph graph) { 105 | var seen = {source: null}; 106 | var queue = new Queue(); 107 | queue.addLast(source); 108 | while (queue.isNotEmpty) { 109 | var node = queue.removeFirst(); 110 | if (identical(node, target)) { 111 | var result = new Queue(); 112 | while (node != null) { 113 | result.addFirst(node); 114 | node = seen[node]; 115 | } 116 | return result.toList(); 117 | } 118 | for (var neighbor in graph.targetsOf(node)) { 119 | if (seen.containsKey(neighbor)) continue; 120 | seen[neighbor] = node; 121 | queue.addLast(neighbor); 122 | } 123 | } 124 | return []; 125 | } 126 | } 127 | 128 | typedef bool LongNameMatcher(FunctionInfo info); 129 | 130 | LongNameMatcher _longNameMatcher(RegExp regexp) => 131 | (e) => regexp.hasMatch(longName(e)); 132 | -------------------------------------------------------------------------------- /bin/src/convert.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | 7 | import 'to_json.dart' show ToJsonCommand; 8 | import 'to_binary.dart' show ToBinaryCommand; 9 | import 'to_proto.dart' show ToProtoCommand; 10 | import 'usage_exception.dart'; 11 | 12 | /// This tool reports how code is divided among deferred chunks. 13 | class ConvertCommand extends Command with PrintUsageException { 14 | final String name = "convert"; 15 | final String description = "Convert between info formats."; 16 | 17 | ConvertCommand() { 18 | _addSubcommand(new ToJsonCommand()); 19 | _addSubcommand(new ToBinaryCommand()); 20 | _addSubcommand(new ToProtoCommand()); 21 | } 22 | 23 | _addSubcommand(Command command) { 24 | addSubcommand(command); 25 | command.argParser 26 | ..addOption('out', 27 | abbr: 'o', 28 | help: 'Output file ' 29 | '(to_json defauts to .json, to_binary defaults to\n' 30 | '.data, and to_proto defaults to .pb)') 31 | ..addFlag('inject-text', 32 | negatable: false, 33 | help: 'Whether to inject output code snippets.\n\n' 34 | 'By default dart2js produces code spans, but excludes the text. This\n' 35 | 'option can be used to embed the text directly in the output.\n' 36 | 'Note: this requires access to dart2js output files.\n'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bin/src/coverage_log_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A tool to gather coverage data from an app generated with dart2js. This 6 | /// depends on code that has been landed in the bleeding_edge version of dart2js 7 | /// and that we expect to become publicly visible in version 0.13.0 of the Dart 8 | /// SDK). 9 | /// 10 | /// This tool starts a server that answers to mainly 2 requests: 11 | /// * a GET request to retrieve the application 12 | /// * POST requests to record coverage data. 13 | /// 14 | /// It is intended to be used as follows: 15 | /// * generate an app by running dart2js with the environment value 16 | /// -DtraceCalls=post provided to the vm, and the --dump-info 17 | /// flag provided to dart2js. 18 | /// * start this server, and proxy requests from your normal frontend 19 | /// server to this one. 20 | library dart2js_info.bin.coverage_log_server; 21 | 22 | import 'dart:async'; 23 | import 'dart:convert'; 24 | import 'dart:io'; 25 | 26 | import 'package:args/command_runner.dart'; 27 | import 'package:path/path.dart' as path; 28 | import 'package:shelf/shelf.dart' as shelf; 29 | import 'package:shelf/shelf_io.dart' as shelf; 30 | 31 | import 'usage_exception.dart'; 32 | 33 | class CoverageLogServerCommand extends Command with PrintUsageException { 34 | final String name = 'coverage_server'; 35 | final String description = 'Server to gather code coverage data'; 36 | 37 | CoverageLogServerCommand() { 38 | argParser 39 | ..addOption('port', abbr: 'p', help: 'port number', defaultsTo: "8080") 40 | ..addOption('host', 41 | help: 'host name (use 0.0.0.0 for all interfaces)', 42 | defaultsTo: 'localhost') 43 | ..addOption('uri-prefix', 44 | help: 45 | 'uri path prefix that will hit this server. This will be injected' 46 | ' into the .js file', 47 | defaultsTo: '') 48 | ..addOption('out', 49 | abbr: 'o', 50 | help: 'output log file', 51 | defaultsTo: _DEFAULT_OUT_TEMPLATE); 52 | } 53 | 54 | void run() async { 55 | if (argResults.rest.isEmpty) { 56 | usageException('Missing arguments: []'); 57 | } 58 | 59 | var jsPath = argResults.rest[0]; 60 | var htmlPath = null; 61 | if (argResults.rest.length > 1) { 62 | htmlPath = argResults.rest[1]; 63 | } 64 | var outPath = argResults['out']; 65 | if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json'; 66 | var server = new _Server(argResults['host'], int.parse(argResults['port']), 67 | jsPath, htmlPath, outPath, argResults['uri-prefix']); 68 | await server.run(); 69 | } 70 | } 71 | 72 | const _DEFAULT_OUT_TEMPLATE = '.coverage.json'; 73 | 74 | class _Server { 75 | /// Server hostname, typically `localhost`, but can be `0.0.0.0`. 76 | final String hostname; 77 | 78 | /// Port the server will listen to. 79 | final int port; 80 | 81 | /// JS file (previously generated by dart2js) to serve. 82 | final String jsPath; 83 | 84 | /// HTML file to serve, if any. 85 | final String htmlPath; 86 | 87 | /// Contents of jsPath, adjusted to use the appropriate server url. 88 | String jsCode; 89 | 90 | /// Location where we'll dump the coverage data. 91 | final String outPath; 92 | 93 | /// Uri prefix used on all requests to this server. This will be injected into 94 | /// the .js file. 95 | final String prefix; 96 | 97 | // TODO(sigmund): add support to load also simple HTML files to test small 98 | // simple apps. 99 | 100 | /// Data received so far. The data is just an array of pairs, showing the 101 | /// hashCode and name of the element used. This can be later cross-checked 102 | /// against dump-info data. 103 | Map data = {}; 104 | 105 | String get _serializedData => new JsonEncoder.withIndent(' ').convert(data); 106 | 107 | _Server(this.hostname, this.port, String jsPath, this.htmlPath, this.outPath, 108 | String prefix) 109 | : jsPath = jsPath, 110 | jsCode = _adjustRequestUrl(new File(jsPath).readAsStringSync(), prefix), 111 | prefix = _normalize(prefix); 112 | 113 | run() async { 114 | await shelf.serve(_handler, hostname, port); 115 | var urlBase = "http://$hostname:$port${prefix == '' ? '/' : '/$prefix/'}"; 116 | var htmlFilename = htmlPath == null ? '' : path.basename(htmlPath); 117 | print("Server is listening\n" 118 | " - html page: $urlBase$htmlFilename\n" 119 | " - js code: $urlBase${path.basename(jsPath)}\n" 120 | " - coverage reporting: ${urlBase}coverage\n"); 121 | } 122 | 123 | _expectedPath(String tail) => prefix == '' ? tail : '$prefix/$tail'; 124 | 125 | FutureOr _handler(shelf.Request request) async { 126 | var urlPath = request.url.path; 127 | print('received request: $urlPath'); 128 | var baseJsName = path.basename(jsPath); 129 | var baseHtmlName = htmlPath == null ? '' : path.basename(htmlPath); 130 | 131 | // Serve an HTML file at the default prefix, or a path matching the HTML 132 | // file name 133 | if (urlPath == prefix || 134 | urlPath == '$prefix/' || 135 | urlPath == _expectedPath(baseHtmlName)) { 136 | var contents = htmlPath == null 137 | ? '' 138 | : await new File(htmlPath).readAsString(); 139 | return new shelf.Response.ok(contents, headers: HTML_HEADERS); 140 | } 141 | 142 | if (urlPath == _expectedPath(baseJsName)) { 143 | return new shelf.Response.ok(jsCode, headers: JS_HEADERS); 144 | } 145 | 146 | // Handle POST requests to record coverage data, and GET requests to display 147 | // the currently coverage results. 148 | if (urlPath == _expectedPath('coverage')) { 149 | if (request.method == 'GET') { 150 | return new shelf.Response.ok(_serializedData, headers: TEXT_HEADERS); 151 | } 152 | 153 | if (request.method == 'POST') { 154 | _record(jsonDecode(await request.readAsString())); 155 | return new shelf.Response.ok("Thanks!"); 156 | } 157 | } 158 | 159 | // Any other request is not supported. 160 | return new shelf.Response.notFound('Not found: "$urlPath"'); 161 | } 162 | 163 | _record(List entries) { 164 | for (var entry in entries) { 165 | var id = entry[0]; 166 | data.putIfAbsent('$id', () => {'name': entry[1], 'count': 0}); 167 | data['$id']['count']++; 168 | } 169 | _enqueueSave(); 170 | } 171 | 172 | bool _savePending = false; 173 | int _total = 0; 174 | _enqueueSave() async { 175 | if (!_savePending) { 176 | _savePending = true; 177 | await new Future.delayed(new Duration(seconds: 3)); 178 | await new File(outPath).writeAsString(_serializedData); 179 | var diff = data.length - _total; 180 | print(diff == 0 181 | ? ' - no new element covered' 182 | : ' - $diff new elements covered'); 183 | _savePending = false; 184 | _total = data.length; 185 | } 186 | } 187 | } 188 | 189 | /// Removes leading and trailing slashes of [uriPath]. 190 | _normalize(String uriPath) { 191 | if (uriPath.startsWith('/')) uriPath = uriPath.substring(1); 192 | if (uriPath.endsWith('/')) uriPath = uriPath.substring(0, uriPath.length - 1); 193 | return uriPath; 194 | } 195 | 196 | _adjustRequestUrl(String code, String prefix) { 197 | var url = prefix == '' ? 'coverage' : '$prefix/coverage'; 198 | var hook = ''' 199 | self.dartCallInstrumentation = function(id, name) { 200 | if (!this.traceBuffer) { 201 | this.traceBuffer = []; 202 | } 203 | var buffer = this.traceBuffer; 204 | if (buffer.length == 0) { 205 | window.setTimeout(function() { 206 | var xhr = new XMLHttpRequest(); 207 | xhr.open("POST", "/$url"); 208 | xhr.send(JSON.stringify(buffer)); 209 | buffer.length = 0; 210 | }, 1000); 211 | } 212 | buffer.push([id, name]); 213 | }; 214 | '''; 215 | return '$hook$code'; 216 | } 217 | 218 | const HTML_HEADERS = const {'content-type': 'text/html'}; 219 | const JS_HEADERS = const {'content-type': 'text/javascript'}; 220 | const TEXT_HEADERS = const {'content-type': 'text/plain'}; 221 | -------------------------------------------------------------------------------- /bin/src/debug_info.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Tool used mainly by dart2js developers to debug the generated info and check 6 | /// that it is consistent and that it covers all the data we expect it to cover. 7 | library dart2js_info.bin.debug_info; 8 | 9 | import 'package:args/command_runner.dart'; 10 | 11 | import 'package:dart2js_info/info.dart'; 12 | import 'package:dart2js_info/src/graph.dart'; 13 | import 'package:dart2js_info/src/io.dart'; 14 | import 'package:dart2js_info/src/util.dart'; 15 | 16 | import 'usage_exception.dart'; 17 | 18 | class DebugCommand extends Command with PrintUsageException { 19 | final String name = "debug"; 20 | final String description = "Dart2js-team diagnostics on a dump-info file."; 21 | 22 | DebugCommand() { 23 | argParser.addOption('show-library', 24 | help: "Show detailed data for a library with the given name"); 25 | } 26 | 27 | void run() async { 28 | var args = argResults.rest; 29 | if (args.length < 1) { 30 | usageException('Missing argument: info.data'); 31 | } 32 | 33 | var info = await infoFromFile(args.first); 34 | var debugLibName = argResults['show-library']; 35 | 36 | validateSize(info, debugLibName); 37 | validateParents(info); 38 | compareGraphs(info); 39 | verifyDeps(info); 40 | } 41 | } 42 | 43 | /// Validates that codesize of elements adds up to total codesize. 44 | validateSize(AllInfo info, String debugLibName) { 45 | // Gather data from visiting all info elements. 46 | var tracker = new _SizeTracker(debugLibName); 47 | info.accept(tracker); 48 | 49 | // Validate that listed elements include elements of each library. 50 | Set listed = new Set() 51 | ..addAll(info.functions) 52 | ..addAll(info.fields); 53 | // For our sanity we do some validation of dump-info invariants 54 | var diff1 = listed.difference(tracker.discovered); 55 | var diff2 = tracker.discovered.difference(listed); 56 | if (diff1.length == 0 || diff2.length == 0) { 57 | _pass('all fields and functions are covered'); 58 | } else { 59 | if (diff1.length > 0) { 60 | _fail("some elements where listed globally that weren't part of any " 61 | "library (non-zero ${diff1.where((f) => f.size > 0).length})"); 62 | } 63 | if (diff2.length > 0) { 64 | _fail("some elements found in libraries weren't part of the global list" 65 | " (non-zero ${diff2.where((f) => f.size > 0).length})"); 66 | } 67 | } 68 | 69 | // Validate that code-size adds up. 70 | int realTotal = info.program.size; 71 | int totalLib = info.libraries.fold(0, (n, lib) => n + lib.size); 72 | int constantsSize = info.constants.fold(0, (n, c) => n + c.size); 73 | int accounted = totalLib + constantsSize; 74 | 75 | if (accounted != realTotal) { 76 | var percent = 77 | ((realTotal - accounted) * 100 / realTotal).toStringAsFixed(2); 78 | _fail('$percent% size missing: $accounted (all libs + consts) ' 79 | '< $realTotal (total)'); 80 | } 81 | int missingTotal = tracker.missing.values.fold(0, (a, b) => a + b); 82 | if (missingTotal > 0) { 83 | var percent = (missingTotal * 100 / realTotal).toStringAsFixed(2); 84 | _fail('$percent% size missing in libraries (sum of elements > lib.size)'); 85 | } 86 | } 87 | 88 | /// Validates that every element in the model has a parent (except libraries). 89 | validateParents(AllInfo info) { 90 | final parentlessInfos = new Set(); 91 | 92 | failIfNoParents(List infos) { 93 | for (var info in infos) { 94 | if (info.parent == null) { 95 | parentlessInfos.add(info); 96 | } 97 | } 98 | } 99 | 100 | failIfNoParents(info.functions); 101 | failIfNoParents(info.typedefs); 102 | failIfNoParents(info.classes); 103 | failIfNoParents(info.fields); 104 | failIfNoParents(info.closures); 105 | if (parentlessInfos.isEmpty) { 106 | _pass('all elements have a parent'); 107 | } else { 108 | _fail('${parentlessInfos.length} elements have no parent'); 109 | } 110 | } 111 | 112 | class _SizeTracker extends RecursiveInfoVisitor { 113 | /// A library name for which to print debugging information (if not null). 114 | final String _debugLibName; 115 | 116 | _SizeTracker(this._debugLibName); 117 | 118 | /// [FunctionInfo]s and [FieldInfo]s transitively reachable from [LibraryInfo] 119 | /// elements. 120 | final Set discovered = new Set(); 121 | 122 | /// Total number of bytes missing if you look at the reported size compared 123 | /// to the sum of the nested infos (e.g. if a class size is smaller than the 124 | /// sum of its methods). Used for validation and debugging of the dump-info 125 | /// invariants. 126 | final Map missing = {}; 127 | 128 | /// Set of [FunctionInfo]s that appear to be unused by the app (they are not 129 | /// registed [coverage]). 130 | final List unused = []; 131 | 132 | /// Tracks the current state of this visitor. 133 | List<_State> stack = [new _State()]; 134 | 135 | /// Code discovered for a [LibraryInfo], only used for debugging. 136 | final StringBuffer _debugCode = new StringBuffer(); 137 | int _indent = 2; 138 | 139 | void _push() => stack.add(new _State()); 140 | 141 | void _pop(info) { 142 | var last = stack.removeLast(); 143 | var size = last._totalSize; 144 | if (size > info.size) { 145 | // record dump-info inconsistencies. 146 | missing[info] = size - info.size; 147 | } else { 148 | // if size < info.size, that is OK, the enclosing element might have code 149 | // of it's own (e.g. a class declaration includes the name of the class, 150 | // but the discovered size only counts the size of the members.) 151 | size = info.size; 152 | } 153 | stack.last 154 | .._totalSize += size 155 | .._count += last._count 156 | .._bodySize += last._bodySize; 157 | } 158 | 159 | bool _debug = false; 160 | visitLibrary(LibraryInfo info) { 161 | if (_debugLibName != null) _debug = info.name.contains(_debugLibName); 162 | _push(); 163 | if (_debug) { 164 | _debugCode.write('{\n'); 165 | _indent = 4; 166 | } 167 | super.visitLibrary(info); 168 | _pop(info); 169 | if (_debug) { 170 | _debug = false; 171 | _indent = 4; 172 | _debugCode.write('}\n'); 173 | } 174 | } 175 | 176 | _handleCodeInfo(info) { 177 | discovered.add(info); 178 | var code = info.code; 179 | if (_debug && code != null) { 180 | bool isClosureClass = info.name.endsWith('.call'); 181 | if (isClosureClass) { 182 | var cname = info.name.substring(0, info.name.indexOf('.')); 183 | _debugCode.write(' ' * _indent); 184 | _debugCode.write(cname); 185 | _debugCode.write(': {\n'); 186 | _indent += 2; 187 | _debugCode.write(' ' * _indent); 188 | _debugCode.write('...\n'); 189 | } 190 | 191 | print('$info ${isClosureClass} \n${info.code}'); 192 | _debugCode.write(' ' * _indent); 193 | var endsInNewLine = code.endsWith('\n'); 194 | if (endsInNewLine) code = code.substring(0, code.length - 1); 195 | _debugCode.write(code.replaceAll('\n', '\n' + (' ' * _indent))); 196 | if (endsInNewLine) _debugCode.write(',\n'); 197 | if (isClosureClass) { 198 | _indent -= 2; 199 | _debugCode.write(' ' * _indent); 200 | _debugCode.write('},\n'); 201 | } 202 | } 203 | stack.last._totalSize += info.size; 204 | stack.last._bodySize += info.size; 205 | stack.last._count++; 206 | } 207 | 208 | visitField(FieldInfo info) { 209 | _handleCodeInfo(info); 210 | super.visitField(info); 211 | } 212 | 213 | visitFunction(FunctionInfo info) { 214 | _handleCodeInfo(info); 215 | super.visitFunction(info); 216 | } 217 | 218 | visitTypedef(TypedefInfo info) { 219 | if (_debug) print('$info'); 220 | stack.last._totalSize += info.size; 221 | super.visitTypedef(info); 222 | } 223 | 224 | visitClass(ClassInfo info) { 225 | if (_debug) { 226 | print('$info'); 227 | _debugCode.write(' ' * _indent); 228 | _debugCode.write('${info.name}: {\n'); 229 | _indent += 2; 230 | } 231 | _push(); 232 | super.visitClass(info); 233 | _pop(info); 234 | if (_debug) { 235 | _debugCode.write(' ' * _indent); 236 | _debugCode.write('},\n'); 237 | _indent -= 2; 238 | } 239 | } 240 | } 241 | 242 | class _State { 243 | int _count = 0; 244 | int _totalSize = 0; 245 | int _bodySize = 0; 246 | } 247 | 248 | /// Validates that both forms of dependency information match. 249 | void compareGraphs(AllInfo info) { 250 | var g1 = new EdgeListGraph(); 251 | var g2 = new EdgeListGraph(); 252 | for (var f in info.functions) { 253 | g1.addNode(f); 254 | for (var g in f.uses) { 255 | g1.addEdge(f, g.target); 256 | } 257 | g2.addNode(f); 258 | if (info.dependencies[f] != null) { 259 | for (var g in info.dependencies[f]) { 260 | g2.addEdge(f, g); 261 | } 262 | } 263 | } 264 | 265 | for (var f in info.fields) { 266 | g1.addNode(f); 267 | for (var g in f.uses) { 268 | g1.addEdge(f, g.target); 269 | } 270 | g2.addNode(f); 271 | if (info.dependencies[f] != null) { 272 | for (var g in info.dependencies[f]) { 273 | g2.addEdge(f, g); 274 | } 275 | } 276 | } 277 | 278 | // Note: these checks right now show that 'uses' links are computed 279 | // differently than 'deps' links 280 | int inUsesNotInDependencies = 0; 281 | int inDependenciesNotInUses = 0; 282 | _sameEdges(f) { 283 | var targets1 = g1.targetsOf(f).toSet(); 284 | var targets2 = g2.targetsOf(f).toSet(); 285 | inUsesNotInDependencies += targets1.difference(targets2).length; 286 | inDependenciesNotInUses += targets2.difference(targets1).length; 287 | } 288 | 289 | info.functions.forEach(_sameEdges); 290 | info.fields.forEach(_sameEdges); 291 | if (inUsesNotInDependencies == 0 && inDependenciesNotInUses == 0) { 292 | _pass('dependency data is consistent'); 293 | } else { 294 | _fail('inconsistencies in dependency data:\n' 295 | ' $inUsesNotInDependencies edges missing from "dependencies" graph\n' 296 | ' $inDependenciesNotInUses edges missing from "uses" graph'); 297 | } 298 | } 299 | 300 | // Validates that all elements are reachable from `main` in the dependency 301 | // graph. 302 | verifyDeps(AllInfo info) { 303 | var graph = graphFromInfo(info); 304 | var entrypoint = info.program.entrypoint; 305 | var reachables = new Set.from(graph.preOrder(entrypoint)); 306 | 307 | var functionsAndFields = [] 308 | ..addAll(info.functions) 309 | ..addAll(info.fields); 310 | var unreachables = 311 | functionsAndFields.where((func) => !reachables.contains(func)); 312 | if (unreachables.isNotEmpty) { 313 | _fail('${unreachables.length} elements are unreachable from the ' 314 | 'entrypoint'); 315 | } else { 316 | _pass('all elements are reachable from the entrypoint'); 317 | } 318 | } 319 | 320 | _pass(String msg) => print('\x1b[32mPASS\x1b[0m: $msg'); 321 | _fail(String msg) => print('\x1b[31mFAIL\x1b[0m: $msg'); 322 | -------------------------------------------------------------------------------- /bin/src/deferred_library_check.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A command that verifies that deferred libraries split the code as expected. 6 | /// 7 | /// This tool checks that the output from dart2js meets a given specification, 8 | /// given in a YAML file. The format of the YAML file is: 9 | /// 10 | /// main: 11 | /// packages: 12 | /// - some_package 13 | /// - other_package 14 | /// 15 | /// foo: 16 | /// packages: 17 | /// - foo 18 | /// - bar 19 | /// 20 | /// baz: 21 | /// packages: 22 | /// - baz 23 | /// - quux 24 | /// 25 | /// The YAML file consists of a list of declarations, one for each deferred 26 | /// part expected in the output. At least one of these parts must be named 27 | /// "main"; this is the main part that contains the program entrypoint. Each 28 | /// top-level part contains a list of package names that are expected to be 29 | /// contained in that part. Any package that is not explicitly listed is 30 | /// expected to be in the main part. For instance, in the example YAML above 31 | /// the part named "baz" is expected to contain the packages "baz" and "quux". 32 | /// 33 | /// The names for parts given in the specification YAML file (besides "main") 34 | /// are arbitrary and just used for reporting when the output does not meet the 35 | /// specification. 36 | library dart2js_info.bin.deferred_library_check; 37 | 38 | import 'dart:io'; 39 | 40 | import 'package:args/command_runner.dart'; 41 | 42 | import 'package:dart2js_info/deferred_library_check.dart'; 43 | import 'package:dart2js_info/src/io.dart'; 44 | import 'package:yaml/yaml.dart'; 45 | 46 | import 'usage_exception.dart'; 47 | 48 | /// A command that computes the diff between two info files. 49 | class DeferredLibraryCheck extends Command with PrintUsageException { 50 | final String name = "deferred_check"; 51 | final String description = 52 | "Verify that deferred libraries are split as expected"; 53 | 54 | void run() async { 55 | var args = argResults.rest; 56 | if (args.length < 2) { 57 | usageException('Missing arguments, expected: info.data manifest.yaml'); 58 | } 59 | var info = await infoFromFile(args[0]); 60 | var manifest = await manifestFromFile(args[1]); 61 | 62 | var failures = checkDeferredLibraryManifest(info, manifest); 63 | failures.forEach(print); 64 | if (failures.isNotEmpty) exitCode = 1; 65 | } 66 | } 67 | 68 | Future manifestFromFile(String fileName) async { 69 | var file = await new File(fileName).readAsString(); 70 | return loadYaml(file); 71 | } 72 | -------------------------------------------------------------------------------- /bin/src/deferred_library_layout.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This tool reports how code is divided among deferred chunks. 6 | library dart2js_info.bin.deferred_library_layout; 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:args/command_runner.dart'; 11 | 12 | import 'package:dart2js_info/info.dart'; 13 | import 'package:dart2js_info/src/io.dart'; 14 | 15 | import 'usage_exception.dart'; 16 | 17 | /// This tool reports how code is divided among deferred chunks. 18 | class DeferredLibraryLayout extends Command with PrintUsageException { 19 | final String name = "deferred_layout"; 20 | final String description = "Show how code is divided among deferred parts."; 21 | 22 | void run() async { 23 | var args = argResults.rest; 24 | if (args.length < 1) { 25 | usageException('Missing argument: info.data'); 26 | } 27 | await _showLayout(args.first); 28 | } 29 | } 30 | 31 | _showLayout(String file) async { 32 | AllInfo info = await infoFromFile(file); 33 | 34 | Map>> hunkMembers = {}; 35 | Map> libToHunks = {}; 36 | void register(BasicInfo info) { 37 | var unit = info.outputUnit; 38 | var lib = _libOf(info); 39 | if (lib == null) return; 40 | libToHunks.putIfAbsent(lib, () => new Set()).add(unit); 41 | hunkMembers 42 | .putIfAbsent(unit, () => {}) 43 | .putIfAbsent(lib, () => []) 44 | .add(info); 45 | } 46 | 47 | info.functions.forEach(register); 48 | info.classes.forEach(register); 49 | info.fields.forEach(register); 50 | info.closures.forEach(register); 51 | 52 | var dir = Directory.current.path; 53 | hunkMembers.forEach((unit, map) { 54 | print('Output unit ${unit.name ?? "main"}:'); 55 | if (unit.name == null || unit.name == 'main') { 56 | print(' loaded by default'); 57 | } else { 58 | print(' loaded by importing: ${unit.imports}'); 59 | } 60 | 61 | print(' contains:'); 62 | map.forEach((lib, elements) { 63 | var uri = lib.uri; 64 | var shortUri = (uri.scheme == 'file' && uri.path.startsWith(dir)) 65 | ? uri.path.substring(dir.length + 1) 66 | : '$uri'; 67 | 68 | // If the entire library is in one chunk, just report the library name 69 | // otherwise report which functions are on this chunk. 70 | if (libToHunks[lib].length == 1) { 71 | print(' - $shortUri'); 72 | } else { 73 | print(' - $shortUri:'); 74 | for (var e in elements) { 75 | print(' - ${kindToString(e.kind)} ${e.name}'); 76 | } 77 | } 78 | }); 79 | print(''); 80 | }); 81 | } 82 | 83 | _libOf(e) => e is LibraryInfo || e == null ? e : _libOf(e.parent); 84 | -------------------------------------------------------------------------------- /bin/src/deferred_library_size.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This tool gives a breakdown of code size by deferred part in the program. 6 | library dart2js_info.bin.deferred_library_size; 7 | 8 | import 'dart:math'; 9 | 10 | import 'package:args/command_runner.dart'; 11 | 12 | import 'package:dart2js_info/info.dart'; 13 | import 'package:dart2js_info/src/io.dart'; 14 | 15 | import 'usage_exception.dart'; 16 | 17 | /// This tool gives a breakdown of code size by deferred part in the program. 18 | class DeferredLibrarySize extends Command with PrintUsageException { 19 | final String name = "deferred_size"; 20 | final String description = "Show breakdown of codesize by deferred part."; 21 | 22 | void run() async { 23 | var args = argResults.rest; 24 | if (args.length < 1) { 25 | usageException('Missing argument: info.data'); 26 | } 27 | // TODO(het): Would be faster to only parse the 'outputUnits' part 28 | var info = await infoFromFile(args.first); 29 | var sizeByImport = getSizeByImport(info); 30 | printSizes(sizeByImport, info.program.size); 31 | } 32 | } 33 | 34 | class ImportSize { 35 | final String import; 36 | final int size; 37 | 38 | const ImportSize(this.import, this.size); 39 | 40 | String toString() { 41 | return '$import: $size'; 42 | } 43 | } 44 | 45 | void printSizes(Map sizeByImport, int programSize) { 46 | var importSizes = []; 47 | sizeByImport.forEach((import, size) { 48 | importSizes.add(new ImportSize(import, size)); 49 | }); 50 | // Sort by size, largest first. 51 | importSizes.sort((a, b) => b.size - a.size); 52 | int longest = importSizes.fold('Percent of code deferred'.length, 53 | (longest, importSize) => max(longest, importSize.import.length)); 54 | 55 | _printRow(label, data, {int width: 15}) { 56 | print('${label.toString().padRight(longest + 1)}' 57 | '${data.toString().padLeft(width)}'); 58 | } 59 | 60 | print(''); 61 | print('Size by library'); 62 | print('-' * (longest + 16)); 63 | for (var importSize in importSizes) { 64 | // TODO(het): split into specific and shared size 65 | _printRow(importSize.import, importSize.size); 66 | } 67 | print('-' * (longest + 16)); 68 | 69 | var mainChunkSize = sizeByImport['main']; 70 | var deferredSize = programSize - mainChunkSize; 71 | var percentDeferred = (deferredSize * 100 / programSize).toStringAsFixed(2); 72 | _printRow('Main chunk size', mainChunkSize); 73 | _printRow('Deferred code size', deferredSize); 74 | _printRow('Percent of code deferred', '$percentDeferred%'); 75 | } 76 | 77 | Map getSizeByImport(AllInfo info) { 78 | var sizeByImport = {}; 79 | for (var outputUnit in info.outputUnits) { 80 | if (outputUnit.name == 'main' || outputUnit.name == null) { 81 | sizeByImport['main'] = outputUnit.size; 82 | } else { 83 | for (var import in outputUnit.imports) { 84 | sizeByImport.putIfAbsent(import, () => 0); 85 | sizeByImport[import] += outputUnit.size; 86 | } 87 | } 88 | } 89 | return sizeByImport; 90 | } 91 | -------------------------------------------------------------------------------- /bin/src/diff.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | 7 | import 'package:dart2js_info/info.dart'; 8 | import 'package:dart2js_info/src/diff.dart'; 9 | import 'package:dart2js_info/src/io.dart'; 10 | import 'package:dart2js_info/src/util.dart'; 11 | 12 | import 'usage_exception.dart'; 13 | 14 | /// A command that computes the diff between two info files. 15 | class DiffCommand extends Command with PrintUsageException { 16 | final String name = "diff"; 17 | final String description = 18 | "See code size differences between two dump-info files."; 19 | 20 | DiffCommand() { 21 | argParser.addFlag('summary-only', 22 | defaultsTo: false, 23 | help: "Show only a summary and hide details of each library"); 24 | } 25 | 26 | void run() async { 27 | var args = argResults.rest; 28 | if (args.length < 2) { 29 | usageException( 30 | 'Missing arguments, expected two dump-info files to compare'); 31 | return; 32 | } 33 | 34 | var oldInfo = await infoFromFile(args[0]); 35 | var newInfo = await infoFromFile(args[1]); 36 | var summaryOnly = argResults['summary-only']; 37 | 38 | var diffs = diff(oldInfo, newInfo); 39 | 40 | // Categorize the diffs 41 | var adds = []; 42 | var removals = []; 43 | var sizeChanges = []; 44 | var becameDeferred = []; 45 | var becameUndeferred = []; 46 | 47 | for (var diff in diffs) { 48 | switch (diff.kind) { 49 | case DiffKind.add: 50 | adds.add(diff as AddDiff); 51 | break; 52 | case DiffKind.remove: 53 | removals.add(diff as RemoveDiff); 54 | break; 55 | case DiffKind.size: 56 | sizeChanges.add(diff as SizeDiff); 57 | break; 58 | case DiffKind.deferred: 59 | var deferredDiff = diff as DeferredStatusDiff; 60 | if (deferredDiff.wasDeferredBefore) { 61 | becameUndeferred.add(deferredDiff); 62 | } else { 63 | becameDeferred.add(deferredDiff); 64 | } 65 | break; 66 | } 67 | } 68 | 69 | // Sort the changes by the size of the element that changed. 70 | for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) { 71 | diffs.sort((a, b) => b.info.size - a.info.size); 72 | } 73 | 74 | // Sort changes in size by size difference. 75 | sizeChanges.sort((a, b) => b.sizeDifference - a.sizeDifference); 76 | 77 | var totalSizes = , int>{}; 78 | for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) { 79 | var totalSize = 0; 80 | for (var diff in diffs) { 81 | // Only count diffs from leaf elements so we don't double count 82 | // them when we account for class size diff or library size diff. 83 | if (diff.info.kind == InfoKind.field || 84 | diff.info.kind == InfoKind.function || 85 | diff.info.kind == InfoKind.closure || 86 | diff.info.kind == InfoKind.typedef) { 87 | totalSize += diff.info.size; 88 | } 89 | } 90 | totalSizes[diffs] = totalSize; 91 | } 92 | var totalSizeChange = 0; 93 | for (var sizeChange in sizeChanges) { 94 | // Only count diffs from leaf elements so we don't double count 95 | // them when we account for class size diff or library size diff. 96 | if (sizeChange.info.kind == InfoKind.field || 97 | sizeChange.info.kind == InfoKind.function || 98 | sizeChange.info.kind == InfoKind.closure || 99 | sizeChange.info.kind == InfoKind.typedef) { 100 | totalSizeChange += sizeChange.sizeDifference; 101 | } 102 | } 103 | totalSizes[sizeChanges] = totalSizeChange; 104 | 105 | reportSummary(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred, 106 | becameUndeferred, totalSizes); 107 | if (!summaryOnly) { 108 | print(''); 109 | reportFull(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred, 110 | becameUndeferred, totalSizes); 111 | } 112 | } 113 | } 114 | 115 | void reportSummary( 116 | AllInfo oldInfo, 117 | AllInfo newInfo, 118 | List adds, 119 | List removals, 120 | List sizeChanges, 121 | List becameDeferred, 122 | List becameUndeferred, 123 | Map, int> totalSizes) { 124 | var overallSizeDiff = newInfo.program.size - oldInfo.program.size; 125 | print('total_size_difference $overallSizeDiff'); 126 | 127 | print('total_added ${totalSizes[adds]}'); 128 | print('total_removed ${totalSizes[removals]}'); 129 | print('total_size_changed ${totalSizes[sizeChanges]}'); 130 | print('total_became_deferred ${totalSizes[becameDeferred]}'); 131 | print('total_no_longer_deferred ${totalSizes[becameUndeferred]}'); 132 | } 133 | 134 | void reportFull( 135 | AllInfo oldInfo, 136 | AllInfo newInfo, 137 | List adds, 138 | List removals, 139 | List sizeChanges, 140 | List becameDeferred, 141 | List becameUndeferred, 142 | Map, int> totalSizes) { 143 | // TODO(het): Improve this output. Siggi has good suggestions in 144 | // https://github.com/dart-lang/dart2js_info/pull/19 145 | 146 | _section('ADDED', size: totalSizes[adds]); 147 | for (var add in adds) { 148 | print('${longName(add.info, useLibraryUri: true)}: ${add.info.size} bytes'); 149 | } 150 | print(''); 151 | 152 | _section('REMOVED', size: totalSizes[removals]); 153 | for (var removal in removals) { 154 | print('${longName(removal.info, useLibraryUri: true)}: ' 155 | '${removal.info.size} bytes'); 156 | } 157 | print(''); 158 | 159 | _section('CHANGED SIZE', size: totalSizes[sizeChanges]); 160 | for (var sizeChange in sizeChanges) { 161 | print('${longName(sizeChange.info, useLibraryUri: true)}: ' 162 | '${sizeChange.sizeDifference} bytes'); 163 | } 164 | print(''); 165 | 166 | _section('BECAME DEFERRED', size: totalSizes[becameDeferred]); 167 | for (var diff in becameDeferred) { 168 | print('${longName(diff.info, useLibraryUri: true)}: ' 169 | '${diff.info.size} bytes'); 170 | } 171 | print(''); 172 | 173 | _section('NO LONGER DEFERRED', size: totalSizes[becameUndeferred]); 174 | for (var diff in becameUndeferred) { 175 | print('${longName(diff.info, useLibraryUri: true)}: ' 176 | '${diff.info.size} bytes'); 177 | } 178 | } 179 | 180 | void _section(String title, {int size}) { 181 | print('$title ($size bytes)'); 182 | print('=' * 72); 183 | } 184 | -------------------------------------------------------------------------------- /bin/src/function_size_analysis.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Tool presenting how much each function contributes to the total code. 6 | library compiler.tool.function_size_analysis; 7 | 8 | import 'dart:math' as math; 9 | 10 | import 'package:args/command_runner.dart'; 11 | 12 | import 'package:dart2js_info/info.dart'; 13 | import 'package:dart2js_info/src/graph.dart'; 14 | import 'package:dart2js_info/src/io.dart'; 15 | import 'package:dart2js_info/src/util.dart'; 16 | 17 | import 'usage_exception.dart'; 18 | 19 | /// Command presenting how much each function contributes to the total code. 20 | class FunctionSizeCommand extends Command with PrintUsageException { 21 | final String name = "function_size"; 22 | final String description = "See breakdown of code size by function."; 23 | 24 | void run() async { 25 | var args = argResults.rest; 26 | if (args.length < 1) { 27 | usageException('Missing argument: info.data'); 28 | } 29 | var info = await infoFromFile(args.first); 30 | showCodeDistribution(info); 31 | } 32 | } 33 | 34 | showCodeDistribution(AllInfo info, 35 | {bool filter(Info info), bool showLibrarySizes: false}) { 36 | var realTotal = info.program.size; 37 | if (filter == null) filter = (i) => true; 38 | var reported = [] 39 | ..addAll(info.functions.where(filter)) 40 | ..addAll(info.fields.where(filter)); 41 | 42 | // Compute a graph from the dependencies in [info]. 43 | Graph graph = graphFromInfo(info); 44 | 45 | // Compute the strongly connected components and calculate their size. 46 | var components = graph.computeTopologicalSort(); 47 | print('total elements: ${graph.nodes.length}'); 48 | print('total strongly connected components: ${components.length}'); 49 | var maxS = 0; 50 | var totalCount = graph.nodeCount; 51 | var minS = totalCount; 52 | var nodeData = {}; 53 | for (var scc in components) { 54 | var sccData = new _SccData(); 55 | maxS = math.max(maxS, scc.length); 56 | minS = math.min(minS, scc.length); 57 | for (var f in scc) { 58 | sccData.size += f.size; 59 | for (var g in graph.targetsOf(f)) { 60 | var gData = nodeData[g]; 61 | if (gData != null) sccData.deps.add(gData); 62 | } 63 | } 64 | for (var f in scc) { 65 | nodeData[f] = sccData; 66 | } 67 | } 68 | print('scc sizes: min: $minS, max: $maxS, ' 69 | 'avg ${totalCount / components.length}'); 70 | 71 | // Compute a dominator tree and calculate the size dominated by each element. 72 | // TODO(sigmund): we need a more reliable way to fetch main. 73 | var mainMethod = info.functions.firstWhere((f) => f.name == 'main'); 74 | var dominatorTree = graph.dominatorTree(mainMethod); 75 | var dominatedSize = {}; 76 | helper(n) { 77 | int size = n.size; 78 | assert(!dominatedSize.containsKey(n)); 79 | dominatedSize[n] = 0; 80 | dominatorTree.targetsOf(n).forEach((m) { 81 | size += helper(m); 82 | }); 83 | dominatedSize[n] = size; 84 | return size; 85 | } 86 | 87 | helper(mainMethod); 88 | reported.forEach((n) => dominatedSize.putIfAbsent(n, () => n.size)); 89 | reported.sort((a, b) => 90 | (dominatedSize[b] + nodeData[b].maxSize) - 91 | (dominatedSize[a] + nodeData[a].maxSize)); 92 | 93 | if (showLibrarySizes) { 94 | print(' --- Results per library ---'); 95 | var totals = {}; 96 | var longest = 0; 97 | reported.forEach((info) { 98 | var size = info.size; 99 | while (info != null && info is! LibraryInfo) { 100 | info = info.parent; 101 | } 102 | if (info == null) return; 103 | LibraryInfo lib = info; 104 | totals.putIfAbsent(lib, () => 0); 105 | totals[lib] += size; 106 | longest = math.max(longest, '${lib.uri}'.length); 107 | }); 108 | 109 | _showLibHeader(longest + 1); 110 | var reportedByLibrary = totals.keys.toList(); 111 | reportedByLibrary.sort((a, b) => totals[b] - totals[a]); 112 | reportedByLibrary.forEach((info) { 113 | _showLib('${info.uri}', totals[info], realTotal, longest + 1); 114 | }); 115 | } 116 | 117 | print('\n --- Results per element (field or function) ---'); 118 | _showElementHeader(); 119 | reported.forEach((info) { 120 | var size = info.size; 121 | var min = dominatedSize[info]; 122 | var max = nodeData[info].maxSize; 123 | _showElement( 124 | longName(info, useLibraryUri: true), size, min, max, realTotal); 125 | }); 126 | } 127 | 128 | /// Data associated with an SCC. Used to compute the reachable code size. 129 | class _SccData { 130 | int size = 0; 131 | Set deps = new Set(); 132 | _SccData(); 133 | 134 | int _maxSize; 135 | int get maxSize { 136 | compute(); 137 | return _maxSize; 138 | } 139 | 140 | void compute() { 141 | if (_maxSize != null) return; 142 | var max = 0; 143 | var seen = new Set(); 144 | helper(n) { 145 | if (!seen.add(n)) return; 146 | max += n.size; 147 | n.deps.forEach(helper); 148 | } 149 | 150 | helper(this); 151 | _maxSize = max; 152 | } 153 | } 154 | 155 | _showLibHeader(int namePadding) { 156 | print(' ${pad("Library", namePadding, right: true)}' 157 | ' ${pad("bytes", 8)} ${pad("%", 6)}'); 158 | } 159 | 160 | _showLib(String msg, int size, int total, int namePadding) { 161 | var percent = (size * 100 / total).toStringAsFixed(2); 162 | print(' ${pad(msg, namePadding, right: true)}' 163 | ' ${pad(size, 8)} ${pad(percent, 6)}%'); 164 | } 165 | 166 | _showElementHeader() { 167 | print('${pad("element size", 16)} ' 168 | '${pad("dominated size", 18)} ' 169 | '${pad("reachable size", 18)} ' 170 | 'Element identifier'); 171 | } 172 | 173 | _showElement(String name, int size, int dominatedSize, int maxSize, int total) { 174 | var percent = (size * 100 / total).toStringAsFixed(2); 175 | var minPercent = (dominatedSize * 100 / total).toStringAsFixed(2); 176 | var maxPercent = (maxSize * 100 / total).toStringAsFixed(2); 177 | print('${pad(size, 8)} ${pad(percent, 6)}% ' 178 | '${pad(dominatedSize, 10)} ${pad(minPercent, 6)}% ' 179 | '${pad(maxSize, 10)} ${pad(maxPercent, 6)}% ' 180 | '$name'); 181 | } 182 | -------------------------------------------------------------------------------- /bin/src/inject_text.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:dart2js_info/info.dart'; 8 | 9 | /// Modify [info] to fill in the text of code spans. 10 | /// 11 | /// By default, code spans contains the offsets but omit the text 12 | /// (`CodeSpan.text` is null). This function reads the output files emitted by 13 | /// dart2js to extract the code denoted by each span. 14 | void injectText(AllInfo info) { 15 | // Fill the text of each code span. The binary form produced by dart2js 16 | // produces code spans, but excludes the orignal text 17 | info.functions.forEach((f) { 18 | f.code.forEach((span) => _fillSpan(span, f.outputUnit)); 19 | }); 20 | info.fields.forEach((f) { 21 | f.code.forEach((span) => _fillSpan(span, f.outputUnit)); 22 | }); 23 | info.constants.forEach((c) { 24 | c.code.forEach((span) => _fillSpan(span, c.outputUnit)); 25 | }); 26 | } 27 | 28 | Map _cache = {}; 29 | 30 | _getContents(OutputUnitInfo unit) => _cache.putIfAbsent(unit.filename, () { 31 | var uri = Uri.base.resolve(unit.filename); 32 | return new File.fromUri(uri).readAsStringSync(); 33 | }); 34 | 35 | _fillSpan(CodeSpan span, OutputUnitInfo unit) { 36 | if (span.text == null && span.start != null && span.end != 0) { 37 | var contents = _getContents(unit); 38 | span.text = contents.substring(span.start, span.end); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bin/src/library_size_split.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Command-line tool to show the size distribution of generated code among 6 | /// libraries. Libraries can be grouped using regular expressions. You can 7 | /// specify what regular expressions to use by providing a `grouping.yaml` file. 8 | /// The format of the `grouping.yaml` file is as follows: 9 | /// ```yaml 10 | /// groups: 11 | /// - { regexp: "package:(foo)/*.dart", name: "group name 1", cluster: 2} 12 | /// - { regexp: "dart:.*", name: "group name 2", cluster: 3} 13 | /// ``` 14 | /// The file should include a single key `groups` containing a list of group 15 | /// specifications. Each group is specified by a map of 3 entries: 16 | /// 17 | /// * regexp (required): a regexp used to match entries that belong to the 18 | /// group. 19 | /// 20 | /// * name (optional): the name given to this group in the output table. If 21 | /// omitted, the name is derived from the regexp as the match's group(1) or 22 | /// group(0) if no group was defined. When names are omitted the group 23 | /// specification implicitly defines several groups, one per observed name. 24 | /// 25 | /// * cluster (optional): a clustering index for how data is shown in a table. 26 | /// Groups with higher cluster indices are shown later in the table after a 27 | /// dividing line. If missing, the cluster index defaults to 0. 28 | /// 29 | /// Here is an example configuration, with comments about what each entry does: 30 | /// 31 | /// ```yaml 32 | /// groups: 33 | /// # This group shows the total size for all libraries that were loaded from 34 | /// # file:// urls, it is shown in cluster #2, which happens to be the last 35 | /// # cluster in this example before the totals are shown: 36 | /// - { name: "Loose files", regexp: "file://.*", cluster: 2} 37 | /// 38 | /// # This group shows the total size of all code loaded from packages: 39 | /// - { name: "All packages", regexp: "package:.*", cluster: 2} 40 | /// 41 | /// # This group shows the total size of all code loaded from core libraries: 42 | /// - { name: "Core libs", regexp: "dart:.*", cluster: 2} 43 | /// 44 | /// # This group shows the total size of all libraries in a single package. Here 45 | /// # we omitted the `name` entry, instead we extract it from the regexp 46 | /// # directly. In this case, the name will be the package-name portion of the 47 | /// # package-url (determined by group(1) of the regexp). 48 | /// - { regexp: "package:([^/]*)", cluster: 1} 49 | /// 50 | /// # The next two groups match the entire library url as the name of the group. 51 | /// - regexp: "package:.*" 52 | /// - regexp: "dart:.*" 53 | /// 54 | /// # If your code lives under /my/project/dir, this will match any file loaded 55 | /// from a file:// url, and we use as a name the relative path to it. 56 | /// - regexp: "file:///my/project/dir/(.*)" 57 | ///``` 58 | /// 59 | /// This example is very similar to [defaultGrouping]. 60 | library dart2js_info.bin.library_size_split; 61 | 62 | import 'dart:io'; 63 | import 'dart:math' show max; 64 | 65 | import 'package:args/command_runner.dart'; 66 | 67 | import 'package:dart2js_info/info.dart'; 68 | import 'package:dart2js_info/src/io.dart'; 69 | import 'package:yaml/yaml.dart'; 70 | 71 | import 'usage_exception.dart'; 72 | 73 | /// Command presenting how much each library contributes to the total code. 74 | class LibrarySizeCommand extends Command with PrintUsageException { 75 | final String name = "library_size"; 76 | final String description = "See breakdown of code size by library."; 77 | 78 | LibrarySizeCommand() { 79 | argParser.addOption('grouping', 80 | help: 'YAML file specifying how libraries should be grouped.'); 81 | } 82 | 83 | void run() async { 84 | var args = argResults.rest; 85 | if (args.length < 1) { 86 | usageException('Missing argument: info.data'); 87 | print('usage: dart tool/library_size_split.dart ' 88 | 'path-to-info.json [grouping.yaml]'); 89 | exit(1); 90 | } 91 | 92 | var info = await infoFromFile(args.first); 93 | 94 | var groupingFile = argResults['grouping']; 95 | var groupingText = groupingFile != null 96 | ? new File(groupingFile).readAsStringSync() 97 | : defaultGrouping; 98 | var groupingYaml = loadYaml(groupingText); 99 | var groups = []; 100 | for (var group in groupingYaml['groups']) { 101 | groups.add(new _Group( 102 | group['name'], new RegExp(group['regexp']), group['cluster'] ?? 0)); 103 | } 104 | 105 | var sizes = {}; 106 | var allLibs = 0; 107 | for (LibraryInfo lib in info.libraries) { 108 | allLibs += lib.size; 109 | groups.forEach((group) { 110 | var match = group.matcher.firstMatch('${lib.uri}'); 111 | if (match != null) { 112 | var name = group.name; 113 | if (name == null && match.groupCount > 0) name = match.group(1); 114 | if (name == null) name = match.group(0); 115 | sizes.putIfAbsent(name, () => new _SizeEntry(name, group.cluster)); 116 | sizes[name].size += lib.size; 117 | } 118 | }); 119 | } 120 | 121 | var allConstants = 0; 122 | for (var constant in info.constants) { 123 | allConstants += constant.size; 124 | } 125 | 126 | var all = sizes.keys.toList(); 127 | all.sort((a, b) => sizes[a].compareTo(sizes[b])); 128 | var realTotal = info.program.size; 129 | var longest = 0; 130 | var rows = <_Row>[]; 131 | _addRow(String label, int value) { 132 | rows.add(new _Row(label, value)); 133 | longest = max(longest, label.length); 134 | } 135 | 136 | _printRow(_Row row) { 137 | if (row is _Divider) { 138 | print(' ' + ('-' * (longest + 18))); 139 | return; 140 | } 141 | 142 | var percent = row.value == realTotal 143 | ? '100' 144 | : (row.value * 100 / realTotal).toStringAsFixed(2); 145 | print(' ${_pad(row.label, longest + 1, right: true)}' 146 | ' ${_pad(row.value, 8)} ${_pad(percent, 6)}%'); 147 | } 148 | 149 | var lastCluster = 0; 150 | for (var name in all) { 151 | var entry = sizes[name]; 152 | if (lastCluster < entry.cluster) { 153 | rows.add(const _Divider()); 154 | lastCluster = entry.cluster; 155 | } 156 | var size = entry.size; 157 | _addRow(name, size); 158 | } 159 | rows.add(const _Divider()); 160 | _addRow("All libraries (excludes preambles, statics & consts)", allLibs); 161 | _addRow("Shared consts", allConstants); 162 | _addRow("Total accounted", allLibs + allConstants); 163 | _addRow("Program Size", realTotal); 164 | rows.forEach(_printRow); 165 | } 166 | } 167 | 168 | /// A group defined in the configuration. 169 | class _Group { 170 | /// Name of the group. May be null if the name is derived from the matcher. In 171 | /// that case, the name would be group(1) of the matched expression if it 172 | /// exist, or group(0) otherwise. 173 | final String name; 174 | 175 | /// Regular expression matching members of the group. 176 | final RegExp matcher; 177 | 178 | /// Index used to cluster groups together. Useful when the grouping 179 | /// configuration describes some coarser groups than orders (e.g. summary of 180 | /// packages would be in a different cluster than a summary of libraries). 181 | final int cluster; 182 | 183 | _Group(this.name, this.matcher, this.cluster); 184 | } 185 | 186 | class _SizeEntry { 187 | final String name; 188 | final int cluster; 189 | int size = 0; 190 | 191 | _SizeEntry(this.name, this.cluster); 192 | 193 | int compareTo(_SizeEntry other) => 194 | cluster == other.cluster ? size - other.size : cluster - other.cluster; 195 | } 196 | 197 | class _Row { 198 | final String label; 199 | final int value; 200 | const _Row(this.label, this.value); 201 | } 202 | 203 | class _Divider extends _Row { 204 | const _Divider() : super('', 0); 205 | } 206 | 207 | _pad(value, n, {bool right: false}) { 208 | var s = '$value'; 209 | if (s.length >= n) return s; 210 | var pad = ' ' * (n - s.length); 211 | return right ? '$s$pad' : '$pad$s'; 212 | } 213 | 214 | /// Default grouping specification that includes an entry per library, and 215 | /// grouping entries for each package, all packages, all core libs, and loose 216 | /// files. 217 | final defaultGrouping = """ 218 | groups: 219 | - { name: "Loose files", regexp: "file://.*", cluster: 2} 220 | - { name: "All packages", regexp: "package:.*", cluster: 2} 221 | - { name: "Core libs", regexp: "dart:.*", cluster: 2} 222 | # We omitted `name` to extract the group name from the regexp directly. 223 | # Here the name is the name of the package: 224 | - { regexp: "package:([^/]*)", cluster: 1} 225 | # Here the name is the url of the package and dart core libraries: 226 | - { regexp: "package:.*"} 227 | - { regexp: "dart:.*"} 228 | # Here the name is the relative path of loose files: 229 | - { regexp: "file://${Directory.current.path}/(.*)" } 230 | """; 231 | -------------------------------------------------------------------------------- /bin/src/live_code_size_analysis.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Command-line tool presenting combined information from dump-info and 6 | /// coverage data. 7 | /// 8 | /// This tool requires two input files an `.info.json` and a 9 | /// `.coverage.json` file. To produce these files you need to follow these 10 | /// steps: 11 | /// 12 | /// * Compile an app with dart2js using --dump-info and defining the 13 | /// Dart environment `traceCalls=post`: 14 | /// 15 | /// DART_VM_OPTIONS="-DtraceCalls=post" dart2js --dump-info main.dart 16 | /// 17 | /// Because coverage/tracing data is currently experimental, the feature is 18 | /// not exposed as a flag in dart2js, but you can enable it using the Dart 19 | /// environment flag. The flag only works dart2js version 1.13.0 or newer. 20 | /// 21 | /// * Launch the coverage server tool (in this package) to serve up the 22 | /// Javascript code in your app: 23 | /// 24 | /// dart tool/coverage_log_server.dart main.dart.js 25 | /// 26 | /// * (optional) If you have a complex application setup, integrate your 27 | /// application server to proxy to the log server any GET request for the 28 | /// .dart.js file and /coverage POST requests that send coverage data. 29 | /// 30 | /// * Load your app and use it to exercise the entire code. 31 | /// 32 | /// * Shut down the coverage server (Ctrl-C) 33 | /// 34 | /// * Finally, run this tool. 35 | library compiler.tool.live_code_size_analysis; 36 | 37 | import 'dart:convert'; 38 | import 'dart:io'; 39 | 40 | import 'package:args/command_runner.dart'; 41 | 42 | import 'package:dart2js_info/info.dart'; 43 | import 'package:dart2js_info/src/io.dart'; 44 | import 'package:dart2js_info/src/util.dart'; 45 | 46 | import 'function_size_analysis.dart'; 47 | import 'usage_exception.dart'; 48 | 49 | class LiveCodeAnalysisCommand extends Command with PrintUsageException { 50 | final String name = "coverage_analysis"; 51 | final String description = "Analyze coverage data collected via the" 52 | " 'coverage_server' command"; 53 | 54 | LiveCodeAnalysisCommand() { 55 | argParser.addFlag('verbose', 56 | abbr: 'v', negatable: false, help: 'Show verbose details.'); 57 | } 58 | 59 | void run() async { 60 | var args = argResults.rest; 61 | if (args.length < 2) { 62 | usageException('Missing arguments, expected: info.data coverage.json'); 63 | } 64 | await _liveCodeAnalysis(args[0], args[1], argResults['verbose']); 65 | } 66 | } 67 | 68 | _liveCodeAnalysis(infoFile, coverageFile, bool verbose) async { 69 | var info = await infoFromFile(infoFile); 70 | var coverage = jsonDecode(new File(coverageFile).readAsStringSync()); 71 | 72 | int realTotal = info.program.size; 73 | int totalLib = info.libraries.fold(0, (n, lib) => n + lib.size); 74 | 75 | int totalCode = 0; 76 | int reachableCode = 0; 77 | List unused = []; 78 | 79 | void tallyCode(Info f) { 80 | totalCode += f.size; 81 | 82 | var data = coverage[f.coverageId]; 83 | if (data != null) { 84 | // Validate that the name match, it might not match if using a different 85 | // version of the app. 86 | // TODO(sigmund): use the same name. 87 | // TODO(sigmund): inject a time-stamp in the code and dumpinfo and 88 | // validate just once. 89 | var name = f.name; 90 | if (name.contains('.')) name = name.substring(name.lastIndexOf('.') + 1); 91 | var otherName = data['name']; 92 | if (otherName.contains('.')) { 93 | otherName = otherName.substring(otherName.lastIndexOf('.') + 1); 94 | } 95 | if (otherName != name && otherName != '') { 96 | print('invalid coverage: $data for $f, ($name vs $otherName)'); 97 | } 98 | reachableCode += f.size; 99 | } else { 100 | // we should track more precisely data about inlined functions 101 | unused.add(f); 102 | } 103 | } 104 | 105 | info.functions.forEach(tallyCode); 106 | info.fields.forEach(tallyCode); 107 | 108 | _showHeader('', 'bytes', '%'); 109 | _show('Program size', realTotal, realTotal); 110 | _show('Libraries (excluding statics)', totalLib, realTotal); 111 | _show('Code (functions + fields)', totalCode, realTotal); 112 | _show('Reachable code', reachableCode, realTotal); 113 | 114 | print(''); 115 | _showHeader('', 'count', '%'); 116 | var total = info.functions.length + info.fields.length; 117 | _show('Functions + fields', total, total); 118 | _show('Reachable', total - unused.length, total); 119 | 120 | // TODO(sigmund): support grouping results by package. 121 | if (verbose) { 122 | print('\nDistribution of code that was not used when running the app:'); 123 | showCodeDistribution(info, 124 | filter: (f) => !coverage.containsKey(f.coverageId) && f.size > 0, 125 | showLibrarySizes: true); 126 | } else { 127 | print('\nUse `-v` to see details about the size of unreachable code'); 128 | } 129 | } 130 | 131 | _showHeader(String msg, String header1, String header2) { 132 | print(' ${pad(msg, 30, right: true)} ${pad(header1, 8)} ${pad(header2, 6)}'); 133 | } 134 | 135 | _show(String msg, int size, int total) { 136 | var percent = (size * 100 / total).toStringAsFixed(2); 137 | print(' ${pad(msg, 30, right: true)} ${pad(size, 8)} ${pad(percent, 6)}%'); 138 | } 139 | -------------------------------------------------------------------------------- /bin/src/show_inferred_types.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Simple script that shows the inferred types of a function. 6 | library compiler.tool.show_inferred_types; 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:args/command_runner.dart'; 11 | 12 | import 'package:dart2js_info/src/util.dart'; 13 | import 'package:dart2js_info/src/io.dart'; 14 | 15 | import 'usage_exception.dart'; 16 | 17 | class ShowInferredTypesCommand extends Command with PrintUsageException { 18 | final String name = "show_inferred"; 19 | final String description = "Show data inferred by dart2js global inference"; 20 | 21 | ShowInferredTypesCommand() { 22 | argParser.addFlag('long-names', 23 | abbr: 'l', negatable: false, help: 'Show long qualified names.'); 24 | } 25 | 26 | void run() async { 27 | var args = argResults.rest; 28 | if (args.length < 2) { 29 | usageException( 30 | 'Missing arguments, expected: info.data '); 31 | } 32 | _showInferredTypes(args[0], args[1], argResults['long-names']); 33 | } 34 | } 35 | 36 | _showInferredTypes(String infoFile, String pattern, bool showLongName) async { 37 | var info = await infoFromFile(infoFile); 38 | var nameRegExp = new RegExp(pattern); 39 | matches(e) => nameRegExp.hasMatch(longName(e)); 40 | 41 | bool noResults = true; 42 | void showMethods() { 43 | var sources = info.functions.where(matches).toList(); 44 | if (sources.isEmpty) return; 45 | noResults = false; 46 | for (var s in sources) { 47 | var params = s.parameters.map((p) => '${p.name}: ${p.type}').join(', '); 48 | var name = showLongName ? longName(s) : s.name; 49 | print('$name($params): ${s.returnType}'); 50 | } 51 | } 52 | 53 | void showFields() { 54 | var sources = info.fields.where(matches).toList(); 55 | if (sources.isEmpty) return; 56 | noResults = false; 57 | for (var s in sources) { 58 | var name = showLongName ? longName(s) : s.name; 59 | print('$name: ${s.inferredType}'); 60 | } 61 | } 62 | 63 | showMethods(); 64 | showFields(); 65 | if (noResults) { 66 | print('error: no function or field that matches $pattern was found.'); 67 | exit(1); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /bin/src/text_print.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'package:args/command_runner.dart'; 7 | 8 | import 'package:dart2js_info/info.dart'; 9 | import 'package:dart2js_info/src/util.dart'; 10 | import 'package:dart2js_info/src/io.dart'; 11 | 12 | import 'inject_text.dart'; 13 | import 'usage_exception.dart'; 14 | 15 | /// Shows the contents of an info file as text. 16 | class ShowCommand extends Command with PrintUsageException { 17 | final String name = "show"; 18 | final String description = "Show a text representation of the info file."; 19 | 20 | ShowCommand() { 21 | argParser.addOption('out', 22 | abbr: 'o', help: 'Output file (defauts to stdout)'); 23 | 24 | argParser.addFlag('inject-text', 25 | negatable: false, 26 | help: 'Whether to inject output code snippets.\n\n' 27 | 'By default dart2js produces code spans, but excludes the text. This\n' 28 | 'option can be used to embed the text directly in the output.'); 29 | } 30 | 31 | void run() async { 32 | if (argResults.rest.length < 1) { 33 | usageException('Missing argument: '); 34 | } 35 | 36 | String filename = argResults.rest[0]; 37 | AllInfo info = await infoFromFile(filename); 38 | if (argResults['inject-text']) injectText(info); 39 | 40 | var buffer = new StringBuffer(); 41 | info.accept(new TextPrinter(buffer, argResults['inject-text'])); 42 | var outputPath = argResults['out']; 43 | if (outputPath == null) { 44 | print(buffer); 45 | } else { 46 | new File(outputPath).writeAsStringSync('$buffer'); 47 | } 48 | } 49 | } 50 | 51 | class TextPrinter implements InfoVisitor { 52 | final StringBuffer buffer; 53 | final bool injectText; 54 | 55 | TextPrinter(this.buffer, this.injectText); 56 | 57 | int _indent = 0; 58 | String get _textIndent => " " * _indent; 59 | void _writeIndentation() { 60 | buffer.write(_textIndent); 61 | } 62 | 63 | void _writeIndented(String s) { 64 | _writeIndentation(); 65 | buffer.writeln(s.replaceAll('\n', '\n$_textIndent')); 66 | } 67 | 68 | void _writeBlock(String s, void f()) { 69 | _writeIndented("$s"); 70 | _indent++; 71 | f(); 72 | _indent--; 73 | } 74 | 75 | void visitAll(AllInfo info) { 76 | _writeBlock("Summary data", () => visitProgram(info.program)); 77 | buffer.writeln(); 78 | _writeBlock("Libraries", () => info.libraries.forEach(visitLibrary)); 79 | // Note: classes, functions, typedefs, and fields are group;ed by library. 80 | 81 | if (injectText) { 82 | _writeBlock("Constants", () => info.constants.forEach(visitConstant)); 83 | } else { 84 | int size = info.constants.fold(0, (n, c) => n + c.size); 85 | _writeIndented("All constants: ${_size(size)}"); 86 | } 87 | _writeBlock("Output units", () => info.outputUnits.forEach(visitOutput)); 88 | } 89 | 90 | void visitProgram(ProgramInfo info) { 91 | _writeIndented('main: ${longName(info.entrypoint, useLibraryUri: true)}'); 92 | _writeIndented('size: ${info.size}'); 93 | _writeIndented('dart2js-version: ${info.dart2jsVersion}'); 94 | var features = []; 95 | if (info.noSuchMethodEnabled) features.add('no-such-method'); 96 | if (info.isRuntimeTypeUsed) features.add('runtime-type'); 97 | if (info.isFunctionApplyUsed) features.add('function-apply'); 98 | if (info.minified) features.add('minified'); 99 | if (features.isNotEmpty) { 100 | _writeIndented('features: ${features.join(' ')}'); 101 | } 102 | } 103 | 104 | String _size(int size) { 105 | if (size < 1024) return "$size b"; 106 | if (size < (1024 * 1024)) { 107 | return "${(size / 1024).toStringAsFixed(2)} Kb ($size b)"; 108 | } 109 | return "${(size / (1024 * 1024)).toStringAsFixed(2)} Mb ($size b)"; 110 | } 111 | 112 | void visitLibrary(LibraryInfo info) { 113 | _writeBlock('${info.uri}: ${_size(info.size)}', () { 114 | if (info.topLevelFunctions.isNotEmpty) { 115 | _writeBlock('Top-level functions', 116 | () => info.topLevelFunctions.forEach(visitFunction)); 117 | buffer.writeln(); 118 | } 119 | if (info.topLevelVariables.isNotEmpty) { 120 | _writeBlock('Top-level variables', 121 | () => info.topLevelVariables.forEach(visitField)); 122 | buffer.writeln(); 123 | } 124 | if (info.classes.isNotEmpty) { 125 | _writeBlock('Classes', () => info.classes.forEach(visitClass)); 126 | } 127 | if (info.typedefs.isNotEmpty) { 128 | _writeBlock("Typedefs", () => info.typedefs.forEach(visitTypedef)); 129 | buffer.writeln(); 130 | } 131 | buffer.writeln(); 132 | }); 133 | } 134 | 135 | void visitClass(ClassInfo info) { 136 | _writeBlock( 137 | '${info.name}: ${_size(info.size)} [${info.outputUnit.filename}]', () { 138 | if (info.functions.isNotEmpty) { 139 | _writeBlock('Methods:', () => info.functions.forEach(visitFunction)); 140 | } 141 | if (info.fields.isNotEmpty) { 142 | _writeBlock('Fields:', () => info.fields.forEach(visitField)); 143 | } 144 | if (info.functions.isNotEmpty || info.fields.isNotEmpty) buffer.writeln(); 145 | }); 146 | } 147 | 148 | void visitField(FieldInfo info) { 149 | _writeBlock('${info.type} ${info.name}: ${_size(info.size)}', () { 150 | _writeIndented('inferred type: ${info.inferredType}'); 151 | if (injectText) _writeBlock("code:", () => _writeCode(info.code)); 152 | if (info.closures.isNotEmpty) { 153 | _writeBlock('Closures:', () => info.closures.forEach(visitClosure)); 154 | } 155 | if (info.uses.isNotEmpty) { 156 | _writeBlock('Dependencies:', () => info.uses.forEach(showDependency)); 157 | } 158 | }); 159 | } 160 | 161 | void visitFunction(FunctionInfo info) { 162 | var outputUnitFile = ''; 163 | if (info.functionKind == FunctionInfo.TOP_LEVEL_FUNCTION_KIND) { 164 | outputUnitFile = ' [${info.outputUnit.filename}]'; 165 | } 166 | String params = 167 | info.parameters.map((p) => "${p.declaredType} ${p.name}").join(', '); 168 | _writeBlock( 169 | '${info.returnType} ${info.name}($params): ${_size(info.size)}$outputUnitFile', 170 | () { 171 | String params = info.parameters.map((p) => "${p.type}").join(', '); 172 | _writeIndented('declared type: ${info.type}'); 173 | _writeIndented( 174 | 'inferred type: ${info.inferredReturnType} Function($params)'); 175 | _writeIndented('side effects: ${info.sideEffects}'); 176 | if (injectText) _writeBlock("code:", () => _writeCode(info.code)); 177 | if (info.closures.isNotEmpty) { 178 | _writeBlock('Closures:', () => info.closures.forEach(visitClosure)); 179 | } 180 | if (info.uses.isNotEmpty) { 181 | _writeBlock('Dependencies:', () => info.uses.forEach(showDependency)); 182 | } 183 | }); 184 | } 185 | 186 | void showDependency(DependencyInfo info) { 187 | var mask = info.mask ?? ''; 188 | _writeIndented('- ${longName(info.target, useLibraryUri: true)} $mask'); 189 | } 190 | 191 | void visitTypedef(TypedefInfo info) { 192 | _writeIndented('${info.name}: ${info.type}'); 193 | } 194 | 195 | void visitClosure(ClosureInfo info) { 196 | _writeBlock('${info.name}', () => visitFunction(info.function)); 197 | } 198 | 199 | void visitConstant(ConstantInfo info) { 200 | _writeBlock('${_size(info.size)}:', () => _writeCode(info.code)); 201 | } 202 | 203 | void _writeCode(List code) { 204 | _writeIndented(code.map((c) => c.text).join('\n')); 205 | } 206 | 207 | void visitOutput(OutputUnitInfo info) { 208 | _writeIndented('${info.filename}: ${_size(info.size)}'); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /bin/src/to_binary.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:args/command_runner.dart'; 8 | 9 | import 'package:dart2js_info/info.dart'; 10 | import 'package:dart2js_info/binary_serialization.dart' as binary; 11 | import 'package:dart2js_info/src/io.dart'; 12 | 13 | import 'inject_text.dart'; 14 | import 'usage_exception.dart'; 15 | 16 | /// Converts a dump-info file emitted by dart2js in JSON to binary format. 17 | class ToBinaryCommand extends Command with PrintUsageException { 18 | final String name = "to_binary"; 19 | final String description = "Convert any info file to binary format."; 20 | 21 | void run() async { 22 | if (argResults.rest.length < 1) { 23 | usageException('Missing argument: '); 24 | exit(1); 25 | } 26 | 27 | String filename = argResults.rest[0]; 28 | AllInfo info = await infoFromFile(filename); 29 | if (argResults['inject-text']) injectText(info); 30 | String outputFilename = argResults['out'] ?? '$filename.data'; 31 | var outstream = new File(outputFilename).openWrite(); 32 | binary.encode(info, outstream); 33 | await outstream.done; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bin/src/to_json.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'dart:convert'; 7 | 8 | import 'package:args/command_runner.dart'; 9 | 10 | import 'package:dart2js_info/info.dart'; 11 | import 'package:dart2js_info/json_info_codec.dart'; 12 | import 'package:dart2js_info/src/io.dart'; 13 | 14 | import 'inject_text.dart'; 15 | import 'usage_exception.dart'; 16 | 17 | /// Converts a dump-info file emitted by dart2js in binary format to JSON. 18 | class ToJsonCommand extends Command with PrintUsageException { 19 | final String name = "to_json"; 20 | final String description = "Convert any info file to JSON format."; 21 | 22 | ToJsonCommand() { 23 | argParser.addFlag('compat-mode', 24 | negatable: false, 25 | help: 'Whether to generate an older version of the JSON format.\n\n' 26 | 'By default files are converted to the latest JSON format, but\n' 27 | 'passing `--compat-mode` will produce a JSON file that may still\n' 28 | 'work in the visualizer tool at:\n' 29 | 'https://dart-lang.github.io/dump-info-visualizer/.\n\n' 30 | 'This option enables `--inject-text` as well, but note that\n' 31 | 'files produced in this mode do not contain all the data\n' 32 | 'available in the input file.'); 33 | } 34 | 35 | void run() async { 36 | if (argResults.rest.length < 1) { 37 | usageException('Missing argument: '); 38 | } 39 | 40 | String filename = argResults.rest[0]; 41 | bool isBackwardCompatible = argResults['compat-mode']; 42 | AllInfo info = await infoFromFile(filename); 43 | 44 | if (isBackwardCompatible || argResults['inject-text']) { 45 | injectText(info); 46 | } 47 | 48 | var json = new AllInfoJsonCodec(isBackwardCompatible: isBackwardCompatible) 49 | .encode(info); 50 | String outputFilename = argResults['out'] ?? '$filename.json'; 51 | new File(outputFilename) 52 | .writeAsStringSync(const JsonEncoder.withIndent(" ").convert(json)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bin/src/to_proto.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Command-line tool to convert an info.json file ouputted by dart2js to the 6 | /// alternative protobuf format. 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:args/command_runner.dart'; 11 | 12 | import 'package:dart2js_info/proto_info_codec.dart'; 13 | import 'package:dart2js_info/src/io.dart'; 14 | 15 | import 'inject_text.dart'; 16 | import 'usage_exception.dart'; 17 | 18 | /// Converts a dump-info file emitted by dart2js to the proto format 19 | class ToProtoCommand extends Command with PrintUsageException { 20 | final String name = "to_proto"; 21 | final String description = "Convert any info file to proto format."; 22 | 23 | void run() async { 24 | if (argResults.rest.length < 1) { 25 | usageException('Missing argument: '); 26 | exit(1); 27 | } 28 | 29 | String filename = argResults.rest[0]; 30 | final info = await infoFromFile(filename); 31 | if (argResults['inject-text']) injectText(info); 32 | final proto = new AllInfoProtoCodec().encode(info); 33 | String outputFilename = argResults['out'] ?? '$filename.pb'; 34 | final outputFile = new File(outputFilename); 35 | await outputFile.writeAsBytes(proto.writeToBuffer(), mode: FileMode.write); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bin/src/usage_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'package:args/command_runner.dart'; 7 | 8 | abstract class PrintUsageException implements Command { 9 | // TODO(rnystrom): Use "Never" for the return type when this package is 10 | // migrated to null safety. 11 | usageException(String message) { 12 | print(message); 13 | printUsage(); 14 | exit(1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bin/tools.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:args/command_runner.dart'; 6 | 7 | import 'src/code_deps.dart'; 8 | import 'src/coverage_log_server.dart'; 9 | import 'src/debug_info.dart'; 10 | import 'src/diff.dart'; 11 | import 'src/deferred_library_check.dart'; 12 | import 'src/deferred_library_size.dart'; 13 | import 'src/deferred_library_layout.dart'; 14 | import 'src/convert.dart'; 15 | import 'src/function_size_analysis.dart'; 16 | import 'src/library_size_split.dart'; 17 | import 'src/live_code_size_analysis.dart'; 18 | import 'src/show_inferred_types.dart'; 19 | import 'src/text_print.dart'; 20 | 21 | /// Entrypoint to run all dart2js_info tools. 22 | void main(args) { 23 | var commandRunner = new CommandRunner("dart2js_info", 24 | "collection of tools to digest the output of dart2js's --dump-info") 25 | ..addCommand(new CodeDepsCommand()) 26 | ..addCommand(new CoverageLogServerCommand()) 27 | ..addCommand(new DebugCommand()) 28 | ..addCommand(new DiffCommand()) 29 | ..addCommand(new DeferredLibraryCheck()) 30 | ..addCommand(new DeferredLibrarySize()) 31 | ..addCommand(new DeferredLibraryLayout()) 32 | ..addCommand(new ConvertCommand()) 33 | ..addCommand(new FunctionSizeCommand()) 34 | ..addCommand(new LibrarySizeCommand()) 35 | ..addCommand(new LiveCodeAnalysisCommand()) 36 | ..addCommand(new ShowInferredTypesCommand()) 37 | ..addCommand(new ShowCommand()); 38 | commandRunner.run(args); 39 | } 40 | -------------------------------------------------------------------------------- /info.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "dart2js_info"; 4 | 5 | package dart2js_info.proto; 6 | 7 | message DependencyInfoPB { 8 | /** The dependency element's serialized_id, references as FunctionInfo or FieldInfo. */ 9 | string target_id = 1; 10 | 11 | /** Either a selector mask indicating how this is used, or 'inlined'. */ 12 | string mask = 2; 13 | } 14 | 15 | /** The entire information produced when compiling a program. */ 16 | message AllInfoPB { 17 | /** Summary information about the program. */ 18 | ProgramInfoPB program = 1; 19 | 20 | /** All the recorded information about elements processed by the compiler. */ 21 | map all_infos = 2; 22 | 23 | /** Details about all deferred imports and what files would be loaded when the import is resolved. */ 24 | repeated LibraryDeferredImportsPB deferred_imports = 3; 25 | } 26 | 27 | /* 28 | * Common interface to many pieces of information generated by the dart2js 29 | * compiler that are directly associated with an element (compilation unit, 30 | * library, class, function, or field). 31 | */ 32 | message InfoPB { 33 | /** Name of the element associated with this info. */ 34 | string name = 1; 35 | 36 | /** An id to uniquely identify this info among infos of the same kind. */ 37 | int32 id = 2; 38 | 39 | /** A globally unique id which combines kind and id together. */ 40 | string serialized_id = 3; 41 | 42 | /** Id used by the compiler when instrumenting code for code coverage. */ 43 | string coverage_id = 4; 44 | 45 | /** Bytes used in the generated code for the corresponding element. */ 46 | int32 size = 5; 47 | 48 | /** The serialized_id of the enclosing element. */ 49 | string parent_id = 6; 50 | 51 | /** How does this function or field depend on others. */ 52 | repeated DependencyInfoPB uses = 7; 53 | 54 | /** The serialized_id of the output unit the element is generated into. */ 55 | string output_unit_id = 8; 56 | 57 | /** Reserved tags for future common fields. */ 58 | reserved 9 to 99; 59 | 60 | /** The concrete info type. */ 61 | oneof concrete { 62 | /** Information about a library element. */ 63 | LibraryInfoPB library_info = 100; 64 | 65 | /** Information about a class element. */ 66 | ClassInfoPB class_info = 101; 67 | 68 | /** Information about a function element. */ 69 | FunctionInfoPB function_info = 102; 70 | 71 | /** Information about a field element. */ 72 | FieldInfoPB field_info = 103; 73 | 74 | /** Information about a constant element. */ 75 | ConstantInfoPB constant_info = 104; 76 | 77 | /** Information about an output unit element. */ 78 | OutputUnitInfoPB output_unit_info = 105; 79 | 80 | /** Information about a typedef element. */ 81 | TypedefInfoPB typedef_info = 106; 82 | 83 | /** Information about a closure element. */ 84 | ClosureInfoPB closure_info = 107; 85 | } 86 | } 87 | 88 | /** General metadata about the dart2js invocation. */ 89 | message ProgramInfoPB { 90 | /** serialized_id for the entrypoint FunctionInfo. */ 91 | string entrypoint_id = 1; 92 | 93 | /** The overall size of the dart2js binary. */ 94 | int32 size = 2; 95 | 96 | /** The version of dart2js used to compile the program. */ 97 | string dart2js_version = 3; 98 | 99 | /** The time at which the compilation was performed in microseconds since epoch. */ 100 | int64 compilation_moment = 4; 101 | 102 | /** The amount of time spent compiling the program in microseconds. */ 103 | int64 compilation_duration = 5; 104 | 105 | /** The amount of time spent converting the info to protobuf in microseconds. */ 106 | int64 to_proto_duration = 6; 107 | 108 | /** The amount of time spent writing out the serialized info in microseconds. */ 109 | int64 dump_info_duration = 7; 110 | 111 | /** true if noSuchMethod is used. */ 112 | bool no_such_method_enabled = 8; 113 | 114 | /** True if Object.runtimeType is used. */ 115 | bool is_runtime_type_used = 9; 116 | 117 | /** True if dart:isolate library is used. */ 118 | bool is_isolate_used = 10; 119 | 120 | /** True if Function.apply is used. */ 121 | bool is_function_apply_used = 11; 122 | 123 | /** True if dart:mirrors features are used. */ 124 | bool is_mirrors_used = 12; 125 | 126 | /** Whether the resulting dart2js binary is minified. */ 127 | bool minified = 13; 128 | } 129 | 130 | /** Info associated with a library element. */ 131 | message LibraryInfoPB { 132 | /** The canonical uri that identifies the library. */ 133 | string uri = 1; 134 | 135 | /** The serialized_ids of all FunctionInfo, FieldInfo, ClassInfo and TypedefInfo elements that are defined in the library. */ 136 | repeated string children_ids = 2; 137 | } 138 | 139 | /** 140 | * Information about an output unit. Normally there is just one for the entire 141 | * program unless the application uses deferred imports, in which case there 142 | * would be an additional output unit per deferred chunk. 143 | */ 144 | message OutputUnitInfoPB { 145 | /** The deferred imports that will load this output unit. */ 146 | repeated string imports = 1; 147 | } 148 | 149 | /** Information about a class element. */ 150 | message ClassInfoPB { 151 | /** Whether the class is abstract. */ 152 | bool is_abstract = 1; 153 | 154 | /** The serialized_ids of all FunctionInfo and FieldInfo elements defined in the class. */ 155 | repeated string children_ids = 2; 156 | } 157 | 158 | /** Information about a constant value. */ 159 | message ConstantInfoPB { 160 | /** The actual generated code for the constant. */ 161 | string code = 1; 162 | } 163 | 164 | /** Information about a field element. */ 165 | message FieldInfoPB { 166 | /** The type of the field. */ 167 | string type = 1; 168 | 169 | /** The type inferred by dart2js's whole program analysis. */ 170 | string inferred_type = 2; 171 | 172 | /** The serialized_ids of all ClosureInfo elements nested in the field initializer. */ 173 | repeated string children_ids = 3; 174 | 175 | /** The actual generated code for the field. */ 176 | string code = 4; 177 | 178 | /** Whether the field is a const declaration. */ 179 | bool is_const = 5; 180 | 181 | /** When isConst is true, the serialized_id of the ConstantInfo initializer expression. */ 182 | string initializer_id = 6; 183 | } 184 | 185 | /** Information about a typedef declaration. */ 186 | message TypedefInfoPB { 187 | /** The declared type. */ 188 | string type = 1; 189 | } 190 | 191 | /** Available function modifiers. */ 192 | message FunctionModifiersPB { 193 | /** Whether the function is declared as static. */ 194 | bool is_static = 1; 195 | 196 | /** Whether the function is declared as const. */ 197 | bool is_const = 2; 198 | 199 | /** Whether the function is a factory constructor. */ 200 | bool is_factory = 3; 201 | 202 | /** Whether the function is declared as extern. */ 203 | bool is_external = 4; 204 | } 205 | 206 | /** Information about a function parameter. */ 207 | message ParameterInfoPB { 208 | string name = 1; 209 | string type = 2; 210 | string declared_type = 3; 211 | } 212 | 213 | /** Information about a function or method. */ 214 | message FunctionInfoPB { 215 | /** Modifiers applied to the function. */ 216 | FunctionModifiersPB function_modifiers = 1; 217 | 218 | /** serialized_ids of any ClosureInfo elements declared in the function. */ 219 | repeated string children_ids = 2; 220 | 221 | /** The declared return type. */ 222 | string return_type = 3; 223 | 224 | /** The inferred return type. */ 225 | string inferred_return_type = 4; 226 | 227 | /** Name and type information for each parameter. */ 228 | repeated ParameterInfoPB parameters = 5; 229 | 230 | /** Side-effects of the function. */ 231 | string side_effects = 6; 232 | 233 | /** How many function calls were inlined into the function. */ 234 | int32 inlined_count = 7; 235 | 236 | /** The actual generated code. */ 237 | string code = 8; 238 | 239 | /** Measurements collected for this function. */ 240 | reserved 9; 241 | } 242 | 243 | /** Information about a closure, also known as a local function. */ 244 | message ClosureInfoPB { 245 | /** serialized_id of the FunctionInfo wrapped by this closure. */ 246 | string function_id = 1; 247 | } 248 | 249 | message DeferredImportPB { 250 | /** The prefix assigned to the deferred import. */ 251 | string prefix = 1; 252 | 253 | /** The list of filenames loaded by the import. */ 254 | repeated string files = 2; 255 | } 256 | 257 | /** Information about deferred imports within a dart library. */ 258 | message LibraryDeferredImportsPB { 259 | /** The uri of the library which makes the deferred import. */ 260 | string library_uri = 1; 261 | 262 | /** The name of the library, or "" if it is unnamed. */ 263 | string library_name = 2; 264 | 265 | /** The individual deferred imports within the library. */ 266 | repeated DeferredImportPB imports = 3; 267 | } 268 | -------------------------------------------------------------------------------- /lib/binary_serialization.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Info serialization to a binary form. 6 | /// 7 | /// Unlike the JSON codec, this serialization is designed to be streamed. 8 | 9 | import 'dart:convert'; 10 | 11 | import 'src/binary/sink.dart'; 12 | import 'src/binary/source.dart'; 13 | import 'info.dart'; 14 | 15 | void encode(AllInfo info, Sink> sink) { 16 | new BinaryPrinter(new BinarySink(sink)).visitAll(info); 17 | } 18 | 19 | AllInfo decode(List data) { 20 | return new BinaryReader(new BinarySource(data)).readAll(); 21 | } 22 | 23 | class BinaryPrinter implements InfoVisitor { 24 | final BinarySink sink; 25 | 26 | BinaryPrinter(this.sink); 27 | 28 | void writeDate(DateTime date) { 29 | sink.writeString(date.toIso8601String()); 30 | } 31 | 32 | void writeDuration(Duration duration) { 33 | sink.writeInt(duration.inMicroseconds); 34 | } 35 | 36 | void writeInfoWithKind(Info info) { 37 | sink.writeEnum(info.kind); 38 | info.accept(this); 39 | } 40 | 41 | void visitAll(AllInfo info) { 42 | sink.writeInt(info.version); 43 | sink.writeInt(info.minorVersion); 44 | sink.writeList(info.libraries, visitLibrary); 45 | // TODO(sigmund): synthesize the following lists instead of serializing the 46 | // values again. 47 | sink.writeList(info.classes, visitClass); 48 | sink.writeList(info.functions, visitFunction); 49 | sink.writeList(info.typedefs, visitTypedef); 50 | sink.writeList(info.fields, visitField); 51 | sink.writeList(info.constants, visitConstant); 52 | sink.writeList(info.closures, visitClosure); 53 | 54 | void writeDependencies(CodeInfo info) { 55 | sink.writeList(info.uses, _writeDependencyInfo); 56 | } 57 | 58 | info.fields.forEach(writeDependencies); 59 | info.functions.forEach(writeDependencies); 60 | 61 | sink.writeInt(info.dependencies.length); 62 | info.dependencies.forEach((Info key, List values) { 63 | writeInfoWithKind(key); 64 | sink.writeList(values, writeInfoWithKind); 65 | }); 66 | sink.writeList(info.outputUnits, visitOutput); 67 | sink.writeString(jsonEncode(info.deferredFiles)); 68 | visitProgram(info.program); 69 | sink.close(); 70 | } 71 | 72 | void visitProgram(ProgramInfo info) { 73 | visitFunction(info.entrypoint); 74 | sink.writeInt(info.size); 75 | sink.writeStringOrNull(info.dart2jsVersion); 76 | writeDate(info.compilationMoment); 77 | writeDuration(info.compilationDuration); 78 | // Note: we don't record the 'toJsonDuration' field. Consider deleting it? 79 | writeDuration(info.dumpInfoDuration); 80 | sink.writeBool(info.noSuchMethodEnabled); 81 | sink.writeBool(info.isRuntimeTypeUsed); 82 | sink.writeBool(info.isIsolateInUse); 83 | sink.writeBool(info.isFunctionApplyUsed); 84 | sink.writeBool(info.isMirrorsUsed); 85 | sink.writeBool(info.minified); 86 | } 87 | 88 | void _visitBasicInfo(BasicInfo info) { 89 | sink.writeStringOrNull(info.name); 90 | sink.writeInt(info.size); 91 | sink.writeStringOrNull(info.coverageId); 92 | _writeOutputOrNull(info.outputUnit); 93 | // Note: parent-pointers are not serialized, they get deduced during deserialization. 94 | } 95 | 96 | void visitLibrary(LibraryInfo library) { 97 | sink.writeCached(library, (LibraryInfo info) { 98 | sink.writeUri(info.uri); 99 | _visitBasicInfo(info); 100 | sink.writeList(info.topLevelFunctions, visitFunction); 101 | sink.writeList(info.topLevelVariables, visitField); 102 | sink.writeList(info.classes, visitClass); 103 | sink.writeList(info.typedefs, visitTypedef); 104 | }); 105 | } 106 | 107 | void visitClass(ClassInfo cls) { 108 | sink.writeCached(cls, (ClassInfo info) { 109 | _visitBasicInfo(info); 110 | sink.writeBool(info.isAbstract); 111 | sink.writeList(info.fields, visitField); 112 | sink.writeList(info.functions, visitFunction); 113 | }); 114 | } 115 | 116 | void visitField(FieldInfo field) { 117 | sink.writeCached(field, (FieldInfo info) { 118 | _visitBasicInfo(info); 119 | sink.writeList(info.closures, visitClosure); 120 | sink.writeString(info.inferredType); 121 | sink.writeList(info.code, _visitCodeSpan); 122 | sink.writeString(info.type); 123 | sink.writeBool(info.isConst); 124 | if (info.isConst) { 125 | _writeConstantOrNull(info.initializer); 126 | } 127 | }); 128 | } 129 | 130 | _visitCodeSpan(CodeSpan code) { 131 | sink.writeIntOrNull(code.start); 132 | sink.writeIntOrNull(code.end); 133 | sink.writeStringOrNull(code.text); 134 | } 135 | 136 | void _writeConstantOrNull(ConstantInfo info) { 137 | sink.writeBool(info != null); 138 | if (info != null) { 139 | visitConstant(info); 140 | } 141 | } 142 | 143 | void visitConstant(ConstantInfo constant) { 144 | sink.writeCached(constant, (ConstantInfo info) { 145 | _visitBasicInfo(info); 146 | sink.writeList(info.code, _visitCodeSpan); 147 | }); 148 | } 149 | 150 | void _visitFunctionModifiers(FunctionModifiers mods) { 151 | int value = 0; 152 | if (mods.isStatic) value |= _staticMask; 153 | if (mods.isConst) value |= _constMask; 154 | if (mods.isFactory) value |= _factoryMask; 155 | if (mods.isExternal) value |= _externalMask; 156 | sink.writeInt(value); 157 | } 158 | 159 | void _visitParameterInfo(ParameterInfo info) { 160 | sink.writeString(info.name); 161 | sink.writeString(info.type); 162 | sink.writeString(info.declaredType); 163 | } 164 | 165 | void visitFunction(FunctionInfo function) { 166 | sink.writeCached(function, (FunctionInfo info) { 167 | _visitBasicInfo(info); 168 | sink.writeList(info.closures, visitClosure); 169 | _visitFunctionModifiers(info.modifiers); 170 | sink.writeString(info.returnType); 171 | sink.writeString(info.inferredReturnType); 172 | sink.writeList(info.parameters, _visitParameterInfo); 173 | sink.writeString(info.sideEffects); 174 | sink.writeIntOrNull(info.inlinedCount); 175 | sink.writeList(info.code, _visitCodeSpan); 176 | sink.writeString(info.type); 177 | }); 178 | } 179 | 180 | void _writeDependencyInfo(DependencyInfo info) { 181 | writeInfoWithKind(info.target); 182 | sink.writeStringOrNull(info.mask); 183 | } 184 | 185 | void visitClosure(ClosureInfo closure) { 186 | sink.writeCached(closure, (ClosureInfo info) { 187 | _visitBasicInfo(info); 188 | visitFunction(info.function); 189 | }); 190 | } 191 | 192 | void visitTypedef(TypedefInfo typedef) { 193 | sink.writeCached(typedef, (TypedefInfo info) { 194 | _visitBasicInfo(info); 195 | sink.writeString(info.type); 196 | }); 197 | } 198 | 199 | void _writeOutputOrNull(OutputUnitInfo info) { 200 | sink.writeBool(info != null); 201 | if (info != null) { 202 | visitOutput(info); 203 | } 204 | } 205 | 206 | void visitOutput(OutputUnitInfo output) { 207 | sink.writeCached(output, (OutputUnitInfo info) { 208 | _visitBasicInfo(info); 209 | sink.writeStringOrNull(info.filename); 210 | sink.writeList(info.imports, sink.writeString); 211 | }); 212 | } 213 | } 214 | 215 | class BinaryReader { 216 | final BinarySource source; 217 | BinaryReader(this.source); 218 | 219 | DateTime readDate() { 220 | return DateTime.parse(source.readString()); 221 | } 222 | 223 | Duration readDuration() { 224 | return new Duration(microseconds: source.readInt()); 225 | } 226 | 227 | Info readInfoWithKind() { 228 | InfoKind kind = source.readEnum(InfoKind.values); 229 | switch (kind) { 230 | case InfoKind.library: 231 | return readLibrary(); 232 | case InfoKind.clazz: 233 | return readClass(); 234 | case InfoKind.function: 235 | return readFunction(); 236 | case InfoKind.field: 237 | return readField(); 238 | case InfoKind.constant: 239 | return readConstant(); 240 | case InfoKind.outputUnit: 241 | return readOutput(); 242 | case InfoKind.typedef: 243 | return readTypedef(); 244 | case InfoKind.closure: 245 | return readClosure(); 246 | } 247 | return null; 248 | } 249 | 250 | AllInfo readAll() { 251 | var info = new AllInfo(); 252 | int version = source.readInt(); 253 | int minorVersion = source.readInt(); 254 | if (info.version != version || info.minorVersion != minorVersion) { 255 | print("warning: data was encoded with format version " 256 | "$version.$minorVersion, but decoded with " 257 | "${info.version}.${info.minorVersion}"); 258 | } 259 | info.libraries = source.readList(readLibrary); 260 | info.classes = source.readList(readClass); 261 | info.functions = source.readList(readFunction); 262 | info.typedefs = source.readList(readTypedef); 263 | info.fields = source.readList(readField); 264 | info.constants = source.readList(readConstant); 265 | info.closures = source.readList(readClosure); 266 | 267 | void readDependencies(CodeInfo info) { 268 | info.uses = source.readList(_readDependencyInfo); 269 | } 270 | 271 | info.fields.forEach(readDependencies); 272 | info.functions.forEach(readDependencies); 273 | 274 | int dependenciesTotal = source.readInt(); 275 | while (dependenciesTotal > 0) { 276 | Info key = readInfoWithKind(); 277 | List values = source.readList(readInfoWithKind); 278 | info.dependencies[key] = values; 279 | dependenciesTotal--; 280 | } 281 | 282 | info.outputUnits = source.readList(readOutput); 283 | 284 | Map> map = 285 | jsonDecode(source.readString()).cast>(); 286 | for (final library in map.values) { 287 | if (library['imports'] != null) { 288 | // The importMap needs to be typed as >, but the 289 | // json parser produces . 290 | final importMap = library['imports'] as Map; 291 | importMap.forEach((prefix, files) { 292 | importMap[prefix] = (files as List).cast(); 293 | }); 294 | library['imports'] = importMap.cast>(); 295 | } 296 | } 297 | info.deferredFiles = map; 298 | info.program = readProgram(); 299 | return info; 300 | } 301 | 302 | ProgramInfo readProgram() { 303 | var info = new ProgramInfo(); 304 | info.entrypoint = readFunction(); 305 | info.size = source.readInt(); 306 | info.dart2jsVersion = source.readStringOrNull(); 307 | info.compilationMoment = readDate(); 308 | info.compilationDuration = readDuration(); 309 | info.toJsonDuration = new Duration(microseconds: 0); 310 | info.dumpInfoDuration = readDuration(); 311 | info.noSuchMethodEnabled = source.readBool(); 312 | info.isRuntimeTypeUsed = source.readBool(); 313 | info.isIsolateInUse = source.readBool(); 314 | info.isFunctionApplyUsed = source.readBool(); 315 | info.isMirrorsUsed = source.readBool(); 316 | info.minified = source.readBool(); 317 | return info; 318 | } 319 | 320 | void _readBasicInfo(BasicInfo info) { 321 | info.name = source.readStringOrNull(); 322 | info.size = source.readInt(); 323 | info.coverageId = source.readStringOrNull(); 324 | info.outputUnit = _readOutputOrNull(); 325 | // Note: parent pointers are added when deserializing parent nodes. 326 | } 327 | 328 | LibraryInfo readLibrary() => source.readCached(() { 329 | LibraryInfo info = new LibraryInfo.internal(); 330 | info.uri = source.readUri(); 331 | _readBasicInfo(info); 332 | info.topLevelFunctions = source.readList(readFunction); 333 | info.topLevelVariables = source.readList(readField); 334 | info.classes = source.readList(readClass); 335 | info.typedefs = source.readList(readTypedef); 336 | 337 | setParent(BasicInfo child) => child.parent = info; 338 | info.topLevelFunctions.forEach(setParent); 339 | info.topLevelVariables.forEach(setParent); 340 | info.classes.forEach(setParent); 341 | info.typedefs.forEach(setParent); 342 | return info; 343 | }); 344 | 345 | ClassInfo readClass() => source.readCached(() { 346 | ClassInfo info = new ClassInfo.internal(); 347 | _readBasicInfo(info); 348 | info.isAbstract = source.readBool(); 349 | info.fields = source.readList(readField); 350 | info.functions = source.readList(readFunction); 351 | 352 | setParent(BasicInfo child) => child.parent = info; 353 | info.fields.forEach(setParent); 354 | info.functions.forEach(setParent); 355 | return info; 356 | }); 357 | 358 | FieldInfo readField() => source.readCached(() { 359 | FieldInfo info = new FieldInfo.internal(); 360 | _readBasicInfo(info); 361 | info.closures = source.readList(readClosure); 362 | info.inferredType = source.readString(); 363 | info.code = source.readList(_readCodeSpan); 364 | info.type = source.readString(); 365 | info.isConst = source.readBool(); 366 | if (info.isConst) { 367 | info.initializer = _readConstantOrNull(); 368 | } 369 | info.closures.forEach((c) => c.parent = info); 370 | return info; 371 | }); 372 | 373 | CodeSpan _readCodeSpan() { 374 | return new CodeSpan() 375 | ..start = source.readIntOrNull() 376 | ..end = source.readIntOrNull() 377 | ..text = source.readStringOrNull(); 378 | } 379 | 380 | ConstantInfo _readConstantOrNull() { 381 | bool hasOutput = source.readBool(); 382 | if (hasOutput) return readConstant(); 383 | return null; 384 | } 385 | 386 | ConstantInfo readConstant() => source.readCached(() { 387 | ConstantInfo info = new ConstantInfo.internal(); 388 | _readBasicInfo(info); 389 | info.code = source.readList(_readCodeSpan); 390 | return info; 391 | }); 392 | 393 | FunctionModifiers _readFunctionModifiers() { 394 | int value = source.readInt(); 395 | return new FunctionModifiers( 396 | isStatic: value & _staticMask != 0, 397 | isConst: value & _constMask != 0, 398 | isFactory: value & _factoryMask != 0, 399 | isExternal: value & _externalMask != 0); 400 | } 401 | 402 | ParameterInfo _readParameterInfo() { 403 | return new ParameterInfo( 404 | source.readString(), source.readString(), source.readString()); 405 | } 406 | 407 | FunctionInfo readFunction() => source.readCached(() { 408 | FunctionInfo info = new FunctionInfo.internal(); 409 | _readBasicInfo(info); 410 | info.closures = source.readList(readClosure); 411 | info.modifiers = _readFunctionModifiers(); 412 | info.returnType = source.readString(); 413 | info.inferredReturnType = source.readString(); 414 | info.parameters = source.readList(_readParameterInfo); 415 | info.sideEffects = source.readString(); 416 | info.inlinedCount = source.readIntOrNull(); 417 | info.code = source.readList(_readCodeSpan); 418 | info.type = source.readString(); 419 | info.closures.forEach((c) => c.parent = info); 420 | return info; 421 | }); 422 | 423 | DependencyInfo _readDependencyInfo() => 424 | new DependencyInfo(readInfoWithKind(), source.readStringOrNull()); 425 | 426 | ClosureInfo readClosure() => source.readCached(() { 427 | ClosureInfo info = new ClosureInfo.internal(); 428 | _readBasicInfo(info); 429 | info.function = readFunction(); 430 | info.function.parent = info; 431 | return info; 432 | }); 433 | 434 | TypedefInfo readTypedef() => source.readCached(() { 435 | TypedefInfo info = new TypedefInfo.internal(); 436 | _readBasicInfo(info); 437 | info.type = source.readString(); 438 | return info; 439 | }); 440 | 441 | OutputUnitInfo _readOutputOrNull() { 442 | bool hasOutput = source.readBool(); 443 | if (hasOutput) return readOutput(); 444 | return null; 445 | } 446 | 447 | OutputUnitInfo readOutput() => source.readCached(() { 448 | OutputUnitInfo info = new OutputUnitInfo.internal(); 449 | _readBasicInfo(info); 450 | info.filename = source.readStringOrNull(); 451 | info.imports = source.readList(source.readString); 452 | return info; 453 | }); 454 | } 455 | 456 | const int _staticMask = 1 << 3; 457 | const int _constMask = 1 << 2; 458 | const int _factoryMask = 1 << 1; 459 | const int _externalMask = 1 << 0; 460 | -------------------------------------------------------------------------------- /lib/deferred_library_check.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This tool checks that the output from dart2js meets a given specification, 6 | /// given in a YAML file. The format of the YAML file is: 7 | /// 8 | /// main: 9 | /// include: 10 | /// - some_package 11 | /// - other_package 12 | /// 13 | /// foo: 14 | /// include: 15 | /// - foo 16 | /// - bar 17 | /// 18 | /// baz: 19 | /// include: 20 | /// - baz 21 | /// - quux 22 | /// exclude: 23 | /// - zardoz 24 | /// 25 | /// The YAML file consists of a list of declarations, one for each deferred 26 | /// part expected in the output. At least one of these parts must be named 27 | /// "main"; this is the main part that contains the program entrypoint. Each 28 | /// top-level part contains a list of package names that are expected to be 29 | /// contained in that part, a list of package names that are expected to be in 30 | /// another part, or both. For instance, in the example YAML above the part 31 | /// named "baz" is expected to contain the packages "baz" and "quux" and not to 32 | /// contain the package "zardoz". 33 | /// 34 | /// The names for parts given in the specification YAML file (besides "main") 35 | /// are the same as the name given to the deferred import in the dart file. For 36 | /// instance, if you have `import 'package:foo/bar.dart' deferred as baz;` in 37 | /// your dart file, then the corresponding name in the specification file is 38 | /// 'baz'. 39 | library dart2js_info.deferred_library_check; 40 | 41 | import 'info.dart'; 42 | 43 | List checkDeferredLibraryManifest( 44 | AllInfo info, Map manifest) { 45 | var includedPackages = new Map>(); 46 | var excludedPackages = new Map>(); 47 | for (var part in manifest.keys) { 48 | for (var package in manifest[part]['include'] ?? []) { 49 | (includedPackages[part] ??= {}).add(package); 50 | } 51 | for (var package in manifest[part]['exclude'] ?? []) { 52 | (excludedPackages[part] ??= {}).add(package); 53 | } 54 | } 55 | 56 | // There are 2 types of parts that are valid to mention in the specification 57 | // file. These are the main part and directly imported deferred parts. The 58 | // main part is always named 'main'; the directly imported deferred parts are 59 | // the outputUnits whose list of 'imports' contains a single import. If the 60 | // part is shared, it will have more than one import since it will include the 61 | // imports of all the top-level deferred parts that will load the shared part. 62 | List validParts = ['main']..addAll(info.outputUnits 63 | .where((unit) => unit.imports.length == 1) 64 | .map((unit) => unit.imports.single)); 65 | List mentionedParts = [] 66 | ..addAll(includedPackages.keys) 67 | ..addAll(excludedPackages.keys); 68 | var partNameFailures = <_InvalidPartName>[]; 69 | for (var part in mentionedParts) { 70 | if (!validParts.contains(part)) { 71 | partNameFailures.add(new _InvalidPartName(part, validParts)); 72 | } 73 | } 74 | if (partNameFailures.isNotEmpty) { 75 | return partNameFailures; 76 | } 77 | 78 | var mentionedPackages = { 79 | for (var values in includedPackages.values) ...values, 80 | for (var values in excludedPackages.values) ...values 81 | }; 82 | var actualIncludedPackages = new Map>(); 83 | 84 | var failures = []; 85 | 86 | checkInfo(BasicInfo info) { 87 | if (info.size == 0) return; 88 | var lib = _getLibraryOf(info); 89 | if (lib != null && _isPackageUri(lib.uri)) { 90 | var packageName = _getPackageName(lib.uri); 91 | if (!mentionedPackages.contains(packageName)) return; 92 | var containingParts = []; 93 | if (info.outputUnit.name == 'main') { 94 | containingParts.add('main'); 95 | } else { 96 | containingParts.addAll(info.outputUnit.imports); 97 | } 98 | for (var part in containingParts) { 99 | (actualIncludedPackages[part] ??= {}).add(packageName); 100 | if (excludedPackages[part].contains(packageName)) { 101 | failures 102 | .add(new _PartContainedExcludedPackage(part, packageName, info)); 103 | } 104 | } 105 | } 106 | } 107 | 108 | info.functions.forEach(checkInfo); 109 | info.fields.forEach(checkInfo); 110 | 111 | includedPackages.forEach((part, packages) { 112 | for (var package in packages) { 113 | if (!actualIncludedPackages.containsKey(part) || 114 | !actualIncludedPackages[part].contains(package)) { 115 | failures.add(new _PartDidNotContainPackage(part, package)); 116 | } 117 | } 118 | }); 119 | return failures; 120 | } 121 | 122 | LibraryInfo _getLibraryOf(Info info) { 123 | var current = info; 124 | while (current is! LibraryInfo) { 125 | if (current == null) { 126 | return null; 127 | } 128 | current = current.parent; 129 | } 130 | return current; 131 | } 132 | 133 | bool _isPackageUri(Uri uri) => uri.scheme == 'package'; 134 | 135 | String _getPackageName(Uri uri) { 136 | assert(_isPackageUri(uri)); 137 | return uri.pathSegments.first; 138 | } 139 | 140 | class ManifestComplianceFailure { 141 | const ManifestComplianceFailure(); 142 | } 143 | 144 | class _InvalidPartName extends ManifestComplianceFailure { 145 | final String part; 146 | final List validPartNames; 147 | const _InvalidPartName(this.part, this.validPartNames); 148 | 149 | String toString() { 150 | return 'Manifest file declares invalid part "$part". ' 151 | 'Valid part names are: $validPartNames'; 152 | } 153 | } 154 | 155 | class _PartContainedExcludedPackage extends ManifestComplianceFailure { 156 | final String part; 157 | final String package; 158 | final BasicInfo info; 159 | const _PartContainedExcludedPackage(this.part, this.package, this.info); 160 | 161 | String toString() { 162 | return 'Part "$part" was specified to exclude package "$package" but it ' 163 | 'actually contains ${kindToString(info.kind)} "${info.name}" which ' 164 | 'is from package "$package"'; 165 | } 166 | } 167 | 168 | class _PartDidNotContainPackage extends ManifestComplianceFailure { 169 | final String part; 170 | final String package; 171 | const _PartDidNotContainPackage(this.part, this.package); 172 | 173 | String toString() { 174 | return 'Part "$part" was specified to include package "$package" but it ' 175 | 'does not contain any elements from that package.'; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /lib/proto_info_codec.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Converters and codecs for converting between Protobuf and [Info] classes. 6 | 7 | import 'dart:convert'; 8 | import 'package:fixnum/fixnum.dart'; 9 | 10 | import 'info.dart'; 11 | import 'src/proto/info.pb.dart'; 12 | import 'src/util.dart'; 13 | 14 | export 'src/proto/info.pb.dart'; 15 | 16 | class ProtoToAllInfoConverter extends Converter { 17 | AllInfo convert(AllInfoPB info) { 18 | // TODO(lorenvs): Implement this conversion. It is unlikely to to be used 19 | // by production code since the goal of the proto codec is to consume this 20 | // information from other languages. However, it is useful for roundtrip 21 | // testing, so we should support it. 22 | throw new UnimplementedError('ProtoToAllInfoConverter is not implemented'); 23 | } 24 | } 25 | 26 | class AllInfoToProtoConverter extends Converter { 27 | final Map ids = {}; 28 | final Set usedIds = new Set(); 29 | 30 | Id idFor(Info info) { 31 | if (info == null) return null; 32 | var serializedId = ids[info]; 33 | if (serializedId != null) return serializedId; 34 | 35 | assert(info is LibraryInfo || 36 | info is ConstantInfo || 37 | info is OutputUnitInfo || 38 | info.parent != null); 39 | 40 | int id; 41 | if (info is ConstantInfo) { 42 | // No name and no parent, so `longName` isn't helpful 43 | assert(info.name == null); 44 | assert(info.parent == null); 45 | assert(info.code != null); 46 | // Instead, use the content of the code. 47 | id = info.code.first.text.hashCode; 48 | } else { 49 | id = longName(info, useLibraryUri: true, forId: true).hashCode; 50 | } 51 | while (!usedIds.add(id)) { 52 | id++; 53 | } 54 | serializedId = new Id(info.kind, id); 55 | return ids[info] = serializedId; 56 | } 57 | 58 | AllInfoPB convert(AllInfo info) => _convertToAllInfoPB(info); 59 | 60 | DependencyInfoPB _convertToDependencyInfoPB(DependencyInfo info) { 61 | var result = new DependencyInfoPB() 62 | ..targetId = idFor(info.target)?.serializedId; 63 | if (info.mask != null) { 64 | result.mask = info.mask; 65 | } 66 | return result; 67 | } 68 | 69 | static ParameterInfoPB _convertToParameterInfoPB(ParameterInfo info) { 70 | return new ParameterInfoPB() 71 | ..name = info.name 72 | ..type = info.type 73 | ..declaredType = info.declaredType; 74 | } 75 | 76 | LibraryInfoPB _convertToLibraryInfoPB(LibraryInfo info) { 77 | final proto = new LibraryInfoPB()..uri = info.uri.toString(); 78 | 79 | proto.childrenIds 80 | .addAll(info.topLevelFunctions.map((func) => idFor(func).serializedId)); 81 | proto.childrenIds.addAll( 82 | info.topLevelVariables.map((field) => idFor(field).serializedId)); 83 | proto.childrenIds 84 | .addAll(info.classes.map((clazz) => idFor(clazz).serializedId)); 85 | proto.childrenIds 86 | .addAll(info.typedefs.map((def) => idFor(def).serializedId)); 87 | 88 | return proto; 89 | } 90 | 91 | ClassInfoPB _convertToClassInfoPB(ClassInfo info) { 92 | final proto = new ClassInfoPB()..isAbstract = info.isAbstract; 93 | 94 | proto.childrenIds 95 | .addAll(info.functions.map((func) => idFor(func).serializedId)); 96 | proto.childrenIds 97 | .addAll(info.fields.map((field) => idFor(field).serializedId)); 98 | 99 | return proto; 100 | } 101 | 102 | static FunctionModifiersPB _convertToFunctionModifiers( 103 | FunctionModifiers modifiers) { 104 | return new FunctionModifiersPB() 105 | ..isStatic = modifiers.isStatic 106 | ..isConst = modifiers.isConst 107 | ..isFactory = modifiers.isFactory 108 | ..isExternal = modifiers.isExternal; 109 | } 110 | 111 | FunctionInfoPB _convertToFunctionInfoPB(FunctionInfo info) { 112 | final proto = new FunctionInfoPB() 113 | ..functionModifiers = _convertToFunctionModifiers(info.modifiers) 114 | ..inlinedCount = info.inlinedCount ?? 0; 115 | 116 | if (info.returnType != null) { 117 | proto.returnType = info.returnType; 118 | } 119 | 120 | if (info.inferredReturnType != null) { 121 | proto.inferredReturnType = info.inferredReturnType; 122 | } 123 | 124 | if (info.code != null) { 125 | proto.code = info.code.map((c) => c.text).join('\n'); 126 | } 127 | 128 | if (info.sideEffects != null) { 129 | proto.sideEffects = info.sideEffects; 130 | } 131 | 132 | proto.childrenIds 133 | .addAll(info.closures.map(((closure) => idFor(closure).serializedId))); 134 | proto.parameters.addAll(info.parameters.map(_convertToParameterInfoPB)); 135 | 136 | return proto; 137 | } 138 | 139 | FieldInfoPB _convertToFieldInfoPB(FieldInfo info) { 140 | final proto = new FieldInfoPB() 141 | ..type = info.type 142 | ..inferredType = info.inferredType 143 | ..isConst = info.isConst; 144 | 145 | if (info.code != null) { 146 | proto.code = info.code.map((c) => c.text).join('\n'); 147 | } 148 | 149 | if (info.initializer != null) { 150 | proto.initializerId = idFor(info.initializer).serializedId; 151 | } 152 | 153 | proto.childrenIds 154 | .addAll(info.closures.map((closure) => idFor(closure).serializedId)); 155 | 156 | return proto; 157 | } 158 | 159 | static ConstantInfoPB _convertToConstantInfoPB(ConstantInfo info) { 160 | return new ConstantInfoPB()..code = info.code.map((c) => c.text).join('\n'); 161 | } 162 | 163 | static OutputUnitInfoPB _convertToOutputUnitInfoPB(OutputUnitInfo info) { 164 | final proto = new OutputUnitInfoPB(); 165 | proto.imports.addAll(info.imports.where((import) => import != null)); 166 | return proto; 167 | } 168 | 169 | static TypedefInfoPB _convertToTypedefInfoPB(TypedefInfo info) { 170 | return new TypedefInfoPB()..type = info.type; 171 | } 172 | 173 | ClosureInfoPB _convertToClosureInfoPB(ClosureInfo info) { 174 | return new ClosureInfoPB()..functionId = idFor(info.function).serializedId; 175 | } 176 | 177 | InfoPB _convertToInfoPB(Info info) { 178 | final proto = new InfoPB() 179 | ..id = idFor(info).id 180 | ..serializedId = idFor(info).serializedId 181 | ..size = info.size; 182 | 183 | if (info.name != null) { 184 | proto.name = info.name; 185 | } 186 | 187 | if (info.parent != null) { 188 | proto.parentId = idFor(info.parent).serializedId; 189 | } 190 | 191 | if (info.coverageId != null) { 192 | proto.coverageId = info.coverageId; 193 | } 194 | 195 | if (info is BasicInfo && info.outputUnit != null) { 196 | // TODO(lorenvs): Similar to the JSON codec, omit this for the default 197 | // output unit. At the moment, there is no easy way to identify which 198 | // output unit is the default on [OutputUnitInfo]. 199 | proto.outputUnitId = idFor(info.outputUnit).serializedId; 200 | } 201 | 202 | if (info is CodeInfo) { 203 | proto.uses.addAll(info.uses.map(_convertToDependencyInfoPB)); 204 | } 205 | 206 | if (info is LibraryInfo) { 207 | proto.libraryInfo = _convertToLibraryInfoPB(info); 208 | } else if (info is ClassInfo) { 209 | proto.classInfo = _convertToClassInfoPB(info); 210 | } else if (info is FunctionInfo) { 211 | proto.functionInfo = _convertToFunctionInfoPB(info); 212 | } else if (info is FieldInfo) { 213 | proto.fieldInfo = _convertToFieldInfoPB(info); 214 | } else if (info is ConstantInfo) { 215 | proto.constantInfo = _convertToConstantInfoPB(info); 216 | } else if (info is OutputUnitInfo) { 217 | proto.outputUnitInfo = _convertToOutputUnitInfoPB(info); 218 | } else if (info is TypedefInfo) { 219 | proto.typedefInfo = _convertToTypedefInfoPB(info); 220 | } else if (info is ClosureInfo) { 221 | proto.closureInfo = _convertToClosureInfoPB(info); 222 | } 223 | 224 | return proto; 225 | } 226 | 227 | ProgramInfoPB _convertToProgramInfoPB(ProgramInfo info) { 228 | var result = new ProgramInfoPB() 229 | ..entrypointId = idFor(info.entrypoint).serializedId 230 | ..size = info.size 231 | ..compilationMoment = 232 | new Int64(info.compilationMoment.microsecondsSinceEpoch) 233 | ..compilationDuration = new Int64(info.compilationDuration.inMicroseconds) 234 | ..toProtoDuration = new Int64(info.toJsonDuration.inMicroseconds) 235 | ..dumpInfoDuration = new Int64(info.dumpInfoDuration.inMicroseconds) 236 | ..noSuchMethodEnabled = info.noSuchMethodEnabled ?? false 237 | ..isRuntimeTypeUsed = info.isRuntimeTypeUsed ?? false 238 | ..isIsolateUsed = info.isIsolateInUse ?? false 239 | ..isFunctionApplyUsed = info.isFunctionApplyUsed ?? false 240 | ..isMirrorsUsed = info.isMirrorsUsed ?? false 241 | ..minified = info.minified ?? false; 242 | 243 | if (info.dart2jsVersion != null) { 244 | result.dart2jsVersion = info.dart2jsVersion; 245 | } 246 | return result; 247 | } 248 | 249 | Iterable> _convertToAllInfosEntries( 250 | Iterable infos) sync* { 251 | for (final info in infos) { 252 | final infoProto = _convertToInfoPB(info); 253 | final entry = MapEntry(infoProto.serializedId, infoProto); 254 | yield entry; 255 | } 256 | } 257 | 258 | static LibraryDeferredImportsPB _convertToLibraryDeferredImportsPB( 259 | String libraryUri, Map fields) { 260 | final proto = new LibraryDeferredImportsPB() 261 | ..libraryUri = libraryUri 262 | ..libraryName = fields['name'] ?? ''; 263 | 264 | Map> imports = fields['imports']; 265 | imports.forEach((prefix, files) { 266 | final import = new DeferredImportPB()..prefix = prefix; 267 | import.files.addAll(files); 268 | proto.imports.add(import); 269 | }); 270 | 271 | return proto; 272 | } 273 | 274 | AllInfoPB _convertToAllInfoPB(AllInfo info) { 275 | final proto = new AllInfoPB() 276 | ..program = _convertToProgramInfoPB(info.program); 277 | 278 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.libraries)); 279 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.classes)); 280 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.functions)); 281 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.fields)); 282 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.constants)); 283 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.outputUnits)); 284 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.typedefs)); 285 | proto.allInfos.addEntries(_convertToAllInfosEntries(info.closures)); 286 | 287 | info.deferredFiles?.forEach((libraryUri, fields) { 288 | proto.deferredImports 289 | .add(_convertToLibraryDeferredImportsPB(libraryUri, fields)); 290 | }); 291 | 292 | return proto; 293 | } 294 | } 295 | 296 | /// A codec for converting [AllInfo] to a protobuf format. 297 | /// 298 | /// This codec is still experimental, and will likely crash on certain output 299 | /// from dart2js. 300 | class AllInfoProtoCodec extends Codec { 301 | final Converter encoder = new AllInfoToProtoConverter(); 302 | final Converter decoder = new ProtoToAllInfoConverter(); 303 | } 304 | 305 | class Id { 306 | final InfoKind kind; 307 | final int id; 308 | 309 | Id(this.kind, this.id); 310 | 311 | String get serializedId => '${kindToString(kind)}/$id'; 312 | } 313 | -------------------------------------------------------------------------------- /lib/src/binary/sink.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:typed_data'; 7 | 8 | /// Interface for serialization. 9 | // TODO(sigmund): share this with pkg:compiler/src/serialization/* 10 | abstract class DataSink { 11 | /// The amount of data written to this data sink. 12 | /// 13 | /// The units is based on the underlying data structure for this data sink. 14 | int get length; 15 | 16 | /// Flushes any pending data and closes this data sink. 17 | /// 18 | /// The data sink can no longer be written to after closing. 19 | void close(); 20 | 21 | /// Writes a reference to [value] to this data sink. If [value] has not yet 22 | /// been serialized, [f] is called to serialize the value itself. 23 | void writeCached(E value, void f(E value)); 24 | 25 | /// Writes the potentially `null` [value] to this data sink. If [value] is 26 | /// non-null [f] is called to write the non-null value to the data sink. 27 | /// 28 | /// This is a convenience method to be used together with 29 | /// [DataSource.readValueOrNull]. 30 | void writeValueOrNull(E value, void f(E value)); 31 | 32 | /// Writes the [values] to this data sink calling [f] to write each value to 33 | /// the data sink. If [allowNull] is `true`, [values] is allowed to be `null`. 34 | /// 35 | /// This is a convenience method to be used together with 36 | /// [DataSource.readList]. 37 | void writeList(Iterable values, void f(E value), 38 | {bool allowNull: false}); 39 | 40 | /// Writes the boolean [value] to this data sink. 41 | void writeBool(bool value); 42 | 43 | /// Writes the non-negative integer [value] to this data sink. 44 | void writeInt(int value); 45 | 46 | /// Writes the potentially `null` non-negative [value] to this data sink. 47 | /// 48 | /// This is a convenience method to be used together with 49 | /// [DataSource.readIntOrNull]. 50 | void writeIntOrNull(int value); 51 | 52 | /// Writes the string [value] to this data sink. 53 | void writeString(String value); 54 | 55 | /// Writes the potentially `null` string [value] to this data sink. 56 | /// 57 | /// This is a convenience method to be used together with 58 | /// [DataSource.readStringOrNull]. 59 | void writeStringOrNull(String value); 60 | 61 | /// Writes the string [values] to this data sink. If [allowNull] is `true`, 62 | /// [values] is allowed to be `null`. 63 | /// 64 | /// This is a convenience method to be used together with 65 | /// [DataSource.readStrings]. 66 | void writeStrings(Iterable values, {bool allowNull: false}); 67 | 68 | /// Writes the [map] from string to [V] values to this data sink, calling [f] 69 | /// to write each value to the data sink. If [allowNull] is `true`, [map] is 70 | /// allowed to be `null`. 71 | /// 72 | /// This is a convenience method to be used together with 73 | /// [DataSource.readStringMap]. 74 | void writeStringMap(Map map, void f(V value), 75 | {bool allowNull: false}); 76 | 77 | /// Writes the enum value [value] to this data sink. 78 | // TODO(johnniwinther): Change the signature to 79 | // `void writeEnum>(E value);` when an interface for enums 80 | // is added to the language. 81 | void writeEnum(dynamic value); 82 | 83 | /// Writes the URI [value] to this data sink. 84 | void writeUri(Uri value); 85 | } 86 | 87 | /// Mixin that implements all convenience methods of [DataSink]. 88 | abstract class DataSinkMixin implements DataSink { 89 | @override 90 | void writeIntOrNull(int value) { 91 | writeBool(value != null); 92 | if (value != null) { 93 | writeInt(value); 94 | } 95 | } 96 | 97 | @override 98 | void writeStringOrNull(String value) { 99 | writeBool(value != null); 100 | if (value != null) { 101 | writeString(value); 102 | } 103 | } 104 | 105 | @override 106 | void writeStrings(Iterable values, {bool allowNull: false}) { 107 | if (values == null) { 108 | assert(allowNull); 109 | writeInt(0); 110 | } else { 111 | writeInt(values.length); 112 | for (String value in values) { 113 | writeString(value); 114 | } 115 | } 116 | } 117 | 118 | @override 119 | void writeStringMap(Map map, void f(V value), 120 | {bool allowNull: false}) { 121 | if (map == null) { 122 | assert(allowNull); 123 | writeInt(0); 124 | } else { 125 | writeInt(map.length); 126 | map.forEach((String key, V value) { 127 | writeString(key); 128 | f(value); 129 | }); 130 | } 131 | } 132 | 133 | @override 134 | void writeList(Iterable values, void f(E value), 135 | {bool allowNull: false}) { 136 | if (values == null) { 137 | assert(allowNull); 138 | writeInt(0); 139 | } else { 140 | writeInt(values.length); 141 | values.forEach(f); 142 | } 143 | } 144 | 145 | @override 146 | void writeValueOrNull(E value, void f(E value)) { 147 | writeBool(value != null); 148 | if (value != null) { 149 | f(value); 150 | } 151 | } 152 | } 153 | 154 | /// Data sink helper that canonicalizes [E] values using indices. 155 | class IndexedSink { 156 | final void Function(int) _writeInt; 157 | final Map _cache = {}; 158 | 159 | IndexedSink(this._writeInt); 160 | 161 | /// Write a reference to [value] to the data sink. 162 | /// 163 | /// If [value] has not been canonicalized yet, [writeValue] is called to 164 | /// serialize the [value] itself. 165 | void write(E value, void writeValue(E value)) { 166 | int index = _cache[value]; 167 | if (index == null) { 168 | index = _cache.length; 169 | _cache[value] = index; 170 | _writeInt(index); 171 | writeValue(value); 172 | } else { 173 | _writeInt(index); 174 | } 175 | } 176 | } 177 | 178 | /// Base implementation of [DataSink] using [DataSinkMixin] to implement 179 | /// convenience methods. 180 | abstract class AbstractDataSink extends DataSinkMixin implements DataSink { 181 | IndexedSink _stringIndex; 182 | IndexedSink _uriIndex; 183 | Map _generalCaches = {}; 184 | 185 | AbstractDataSink() { 186 | _stringIndex = new IndexedSink(_writeIntInternal); 187 | _uriIndex = new IndexedSink(_writeIntInternal); 188 | } 189 | 190 | @override 191 | void writeCached(E value, void f(E value)) { 192 | IndexedSink sink = 193 | _generalCaches[E] ??= new IndexedSink(_writeIntInternal); 194 | sink.write(value, (v) => f(v)); 195 | } 196 | 197 | @override 198 | void writeEnum(dynamic value) { 199 | _writeEnumInternal(value); 200 | } 201 | 202 | @override 203 | void writeBool(bool value) { 204 | assert(value != null); 205 | _writeIntInternal(value ? 1 : 0); 206 | } 207 | 208 | @override 209 | void writeUri(Uri value) { 210 | assert(value != null); 211 | _writeUri(value); 212 | } 213 | 214 | @override 215 | void writeString(String value) { 216 | assert(value != null); 217 | _writeString(value); 218 | } 219 | 220 | @override 221 | void writeInt(int value) { 222 | assert(value != null); 223 | assert(value >= 0 && value >> 30 == 0); 224 | _writeIntInternal(value); 225 | } 226 | 227 | void _writeString(String value) { 228 | _stringIndex.write(value, _writeStringInternal); 229 | } 230 | 231 | void _writeUri(Uri value) { 232 | _uriIndex.write(value, _writeUriInternal); 233 | } 234 | 235 | /// Actual serialization of a URI value, implemented by subclasses. 236 | void _writeUriInternal(Uri value); 237 | 238 | /// Actual serialization of a String value, implemented by subclasses. 239 | void _writeStringInternal(String value); 240 | 241 | /// Actual serialization of a non-negative integer value, implemented by 242 | /// subclasses. 243 | void _writeIntInternal(int value); 244 | 245 | /// Actual serialization of an enum value, implemented by subclasses. 246 | void _writeEnumInternal(dynamic value); 247 | } 248 | 249 | /// [DataSink] that writes data as a sequence of bytes. 250 | /// 251 | /// This data sink works together with [BinarySource]. 252 | class BinarySink extends AbstractDataSink { 253 | final Sink> sink; 254 | BufferedSink _bufferedSink; 255 | int _length = 0; 256 | 257 | BinarySink(this.sink) : _bufferedSink = new BufferedSink(sink); 258 | 259 | @override 260 | void _writeUriInternal(Uri value) { 261 | _writeString(value.toString()); 262 | } 263 | 264 | @override 265 | void _writeStringInternal(String value) { 266 | List bytes = utf8.encode(value); 267 | _writeIntInternal(bytes.length); 268 | _bufferedSink.addBytes(bytes); 269 | _length += bytes.length; 270 | } 271 | 272 | @override 273 | void _writeIntInternal(int value) { 274 | assert(value >= 0 && value >> 30 == 0); 275 | if (value < 0x80) { 276 | _bufferedSink.addByte(value); 277 | _length += 1; 278 | } else if (value < 0x4000) { 279 | _bufferedSink.addByte2((value >> 8) | 0x80, value & 0xFF); 280 | _length += 2; 281 | } else { 282 | _bufferedSink.addByte4((value >> 24) | 0xC0, (value >> 16) & 0xFF, 283 | (value >> 8) & 0xFF, value & 0xFF); 284 | _length += 4; 285 | } 286 | } 287 | 288 | @override 289 | void _writeEnumInternal(dynamic value) { 290 | _writeIntInternal(value.index); 291 | } 292 | 293 | void close() { 294 | _bufferedSink.flushAndDestroy(); 295 | _bufferedSink = null; 296 | sink.close(); 297 | } 298 | 299 | /// Returns the number of bytes written to this data sink. 300 | int get length => _length; 301 | } 302 | 303 | /// Puts a buffer in front of a [Sink>]. 304 | // TODO(sigmund): share with the implementation in 305 | // package:kernel/binary/ast_to_binary.dart 306 | class BufferedSink { 307 | static const int SIZE = 100000; 308 | static const int SAFE_SIZE = SIZE - 5; 309 | static const int SMALL = 10000; 310 | final Sink> _sink; 311 | Uint8List _buffer = new Uint8List(SIZE); 312 | int length = 0; 313 | int flushedLength = 0; 314 | 315 | Float64List _doubleBuffer = new Float64List(1); 316 | Uint8List _doubleBufferUint8; 317 | 318 | int get offset => length + flushedLength; 319 | 320 | BufferedSink(this._sink); 321 | 322 | void addDouble(double d) { 323 | _doubleBufferUint8 ??= _doubleBuffer.buffer.asUint8List(); 324 | _doubleBuffer[0] = d; 325 | addByte4(_doubleBufferUint8[0], _doubleBufferUint8[1], 326 | _doubleBufferUint8[2], _doubleBufferUint8[3]); 327 | addByte4(_doubleBufferUint8[4], _doubleBufferUint8[5], 328 | _doubleBufferUint8[6], _doubleBufferUint8[7]); 329 | } 330 | 331 | void addByte(int byte) { 332 | _buffer[length++] = byte; 333 | if (length == SIZE) { 334 | _sink.add(_buffer); 335 | _buffer = new Uint8List(SIZE); 336 | length = 0; 337 | flushedLength += SIZE; 338 | } 339 | } 340 | 341 | void addByte2(int byte1, int byte2) { 342 | if (length < SAFE_SIZE) { 343 | _buffer[length++] = byte1; 344 | _buffer[length++] = byte2; 345 | } else { 346 | addByte(byte1); 347 | addByte(byte2); 348 | } 349 | } 350 | 351 | void addByte4(int byte1, int byte2, int byte3, int byte4) { 352 | if (length < SAFE_SIZE) { 353 | _buffer[length++] = byte1; 354 | _buffer[length++] = byte2; 355 | _buffer[length++] = byte3; 356 | _buffer[length++] = byte4; 357 | } else { 358 | addByte(byte1); 359 | addByte(byte2); 360 | addByte(byte3); 361 | addByte(byte4); 362 | } 363 | } 364 | 365 | void addBytes(List bytes) { 366 | // Avoid copying a large buffer into the another large buffer. Also, if 367 | // the bytes buffer is too large to fit in our own buffer, just emit both. 368 | if (length + bytes.length < SIZE && 369 | (bytes.length < SMALL || length < SMALL)) { 370 | _buffer.setRange(length, length + bytes.length, bytes); 371 | length += bytes.length; 372 | } else if (bytes.length < SMALL) { 373 | // Flush as much as we can in the current buffer. 374 | _buffer.setRange(length, SIZE, bytes); 375 | _sink.add(_buffer); 376 | // Copy over the remainder into a new buffer. It is guaranteed to fit 377 | // because the input byte array is small. 378 | int alreadyEmitted = SIZE - length; 379 | int remainder = bytes.length - alreadyEmitted; 380 | _buffer = new Uint8List(SIZE); 381 | _buffer.setRange(0, remainder, bytes, alreadyEmitted); 382 | length = remainder; 383 | flushedLength += SIZE; 384 | } else { 385 | flush(); 386 | _sink.add(bytes); 387 | flushedLength += bytes.length; 388 | } 389 | } 390 | 391 | void flush() { 392 | _sink.add(_buffer.sublist(0, length)); 393 | _buffer = new Uint8List(SIZE); 394 | flushedLength += length; 395 | length = 0; 396 | } 397 | 398 | void flushAndDestroy() { 399 | _sink.add(_buffer.sublist(0, length)); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /lib/src/binary/source.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:typed_data'; 6 | import 'dart:convert'; 7 | 8 | /// Interface for deserialization. 9 | // TODO(sigmund): share this with pkg:compiler/src/serialization/* 10 | abstract class DataSource { 11 | /// Reads a reference to an [E] value from this data source. If the value has 12 | /// not yet been deserialized, [f] is called to deserialize the value itself. 13 | E readCached(E f()); 14 | 15 | /// Reads a potentially `null` [E] value from this data source, calling [f] to 16 | /// read the non-null value from the data source. 17 | /// 18 | /// This is a convenience method to be used together with 19 | /// [DataSink.writeValueOrNull]. 20 | E readValueOrNull(E f()); 21 | 22 | /// Reads a list of [E] values from this data source. If [emptyAsNull] is 23 | /// `true`, `null` is returned instead of an empty list. 24 | /// 25 | /// This is a convenience method to be used together with 26 | /// [DataSink.writeList]. 27 | List readList(E f(), {bool emptyAsNull: false}); 28 | 29 | /// Reads a boolean value from this data source. 30 | bool readBool(); 31 | 32 | /// Reads a non-negative integer value from this data source. 33 | int readInt(); 34 | 35 | /// Reads a potentially `null` non-negative integer value from this data 36 | /// source. 37 | /// 38 | /// This is a convenience method to be used together with 39 | /// [DataSink.writeIntOrNull]. 40 | int readIntOrNull(); 41 | 42 | /// Reads a string value from this data source. 43 | String readString(); 44 | 45 | /// Reads a potentially `null` string value from this data source. 46 | /// 47 | /// This is a convenience method to be used together with 48 | /// [DataSink.writeStringOrNull]. 49 | String readStringOrNull(); 50 | 51 | /// Reads a list of string values from this data source. If [emptyAsNull] is 52 | /// `true`, `null` is returned instead of an empty list. 53 | /// 54 | /// This is a convenience method to be used together with 55 | /// [DataSink.writeStrings]. 56 | List readStrings({bool emptyAsNull: false}); 57 | 58 | /// Reads a map from string values to [V] values from this data source, 59 | /// calling [f] to read each value from the data source. If [emptyAsNull] is 60 | /// `true`, `null` is returned instead of an empty map. 61 | /// 62 | /// This is a convenience method to be used together with 63 | /// [DataSink.writeStringMap]. 64 | Map readStringMap(V f(), {bool emptyAsNull: false}); 65 | 66 | /// Reads an enum value from the list of enum [values] from this data source. 67 | /// 68 | /// The [values] argument is intended to be the static `.values` field on 69 | /// enum classes, for instance: 70 | /// 71 | /// enum Foo { bar, baz } 72 | /// ... 73 | /// Foo foo = source.readEnum(Foo.values); 74 | /// 75 | E readEnum(List values); 76 | 77 | /// Reads a URI value from this data source. 78 | Uri readUri(); 79 | } 80 | 81 | /// Mixin that implements all convenience methods of [DataSource]. 82 | abstract class DataSourceMixin implements DataSource { 83 | @override 84 | E readValueOrNull(E f()) { 85 | bool hasValue = readBool(); 86 | if (hasValue) { 87 | return f(); 88 | } 89 | return null; 90 | } 91 | 92 | @override 93 | List readList(E f(), {bool emptyAsNull: false}) { 94 | int count = readInt(); 95 | if (count == 0 && emptyAsNull) return null; 96 | List list = new List(count); 97 | for (int i = 0; i < count; i++) { 98 | list[i] = f(); 99 | } 100 | return list; 101 | } 102 | 103 | @override 104 | int readIntOrNull() { 105 | bool hasValue = readBool(); 106 | if (hasValue) { 107 | return readInt(); 108 | } 109 | return null; 110 | } 111 | 112 | @override 113 | String readStringOrNull() { 114 | bool hasValue = readBool(); 115 | if (hasValue) { 116 | return readString(); 117 | } 118 | return null; 119 | } 120 | 121 | @override 122 | List readStrings({bool emptyAsNull: false}) { 123 | int count = readInt(); 124 | if (count == 0 && emptyAsNull) return null; 125 | List list = new List(count); 126 | for (int i = 0; i < count; i++) { 127 | list[i] = readString(); 128 | } 129 | return list; 130 | } 131 | 132 | @override 133 | Map readStringMap(V f(), {bool emptyAsNull: false}) { 134 | int count = readInt(); 135 | if (count == 0 && emptyAsNull) return null; 136 | Map map = {}; 137 | for (int i = 0; i < count; i++) { 138 | String key = readString(); 139 | V value = f(); 140 | map[key] = value; 141 | } 142 | return map; 143 | } 144 | } 145 | 146 | /// Data source helper reads canonicalized [E] values through indices. 147 | class IndexedSource { 148 | final int Function() _readInt; 149 | final List _cache = []; 150 | final Set _pending = new Set(); 151 | 152 | IndexedSource(this._readInt); 153 | 154 | /// Reads a reference to an [E] value from the data source. 155 | /// 156 | /// If the value hasn't yet been read, [readValue] is called to deserialize 157 | /// the value itself. 158 | E read(E readValue()) { 159 | int index = _readInt(); 160 | if (_pending.contains(index)) throw "serialization cycles not supported"; 161 | if (index >= _cache.length) { 162 | _pending.add(index); 163 | _cache.add(null); 164 | E value = readValue(); 165 | _pending.remove(index); 166 | _cache[index] = value; 167 | return value; 168 | } else { 169 | return _cache[index]; 170 | } 171 | } 172 | } 173 | 174 | /// Base implementation of [DataSource] using [DataSourceMixin] to implement 175 | /// convenience methods. 176 | abstract class AbstractDataSource extends DataSourceMixin 177 | implements DataSource { 178 | IndexedSource _stringIndex; 179 | IndexedSource _uriIndex; 180 | Map _generalCaches = {}; 181 | 182 | AbstractDataSource() { 183 | _stringIndex = new IndexedSource(_readIntInternal); 184 | _uriIndex = new IndexedSource(_readIntInternal); 185 | } 186 | 187 | @override 188 | E readCached(E f()) { 189 | IndexedSource source = 190 | _generalCaches[E] ??= new IndexedSource(_readIntInternal); 191 | return source.read(f); 192 | } 193 | 194 | @override 195 | E readEnum(List values) { 196 | return _readEnumInternal(values); 197 | } 198 | 199 | @override 200 | Uri readUri() { 201 | return _readUri(); 202 | } 203 | 204 | Uri _readUri() { 205 | return _uriIndex.read(_readUriInternal); 206 | } 207 | 208 | @override 209 | bool readBool() { 210 | int value = _readIntInternal(); 211 | assert(value == 0 || value == 1); 212 | return value == 1; 213 | } 214 | 215 | @override 216 | String readString() { 217 | return _readString(); 218 | } 219 | 220 | String _readString() { 221 | return _stringIndex.read(_readStringInternal); 222 | } 223 | 224 | @override 225 | int readInt() { 226 | return _readIntInternal(); 227 | } 228 | 229 | /// Actual deserialization of a string value, implemented by subclasses. 230 | String _readStringInternal(); 231 | 232 | /// Actual deserialization of a non-negative integer value, implemented by 233 | /// subclasses. 234 | int _readIntInternal(); 235 | 236 | /// Actual deserialization of a URI value, implemented by subclasses. 237 | Uri _readUriInternal(); 238 | 239 | /// Actual deserialization of an enum value in [values], implemented by 240 | /// subclasses. 241 | E _readEnumInternal(List values); 242 | } 243 | 244 | /// [DataSource] that reads data from a sequence of bytes. 245 | /// 246 | /// This data source works together with [BinarySink]. 247 | class BinarySource extends AbstractDataSource { 248 | int _byteOffset = 0; 249 | final List _bytes; 250 | 251 | BinarySource(this._bytes); 252 | int _readByte() => _bytes[_byteOffset++]; 253 | 254 | @override 255 | String _readStringInternal() { 256 | int length = _readIntInternal(); 257 | List bytes = new Uint8List(length); 258 | bytes.setRange(0, bytes.length, _bytes, _byteOffset); 259 | _byteOffset += bytes.length; 260 | return utf8.decode(bytes); 261 | } 262 | 263 | @override 264 | int _readIntInternal() { 265 | var byte = _readByte(); 266 | if (byte & 0x80 == 0) { 267 | // 0xxxxxxx 268 | return byte; 269 | } else if (byte & 0x40 == 0) { 270 | // 10xxxxxx 271 | return ((byte & 0x3F) << 8) | _readByte(); 272 | } else { 273 | // 11xxxxxx 274 | return ((byte & 0x3F) << 24) | 275 | (_readByte() << 16) | 276 | (_readByte() << 8) | 277 | _readByte(); 278 | } 279 | } 280 | 281 | @override 282 | Uri _readUriInternal() { 283 | String text = _readString(); 284 | return Uri.parse(text); 285 | } 286 | 287 | @override 288 | E _readEnumInternal(List values) { 289 | int index = _readIntInternal(); 290 | assert( 291 | 0 <= index && index < values.length, 292 | "Invalid data kind index. " 293 | "Expected one of $values, found index $index."); 294 | return values[index]; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /lib/src/diff.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart2js_info/info.dart'; 2 | import 'package:dart2js_info/src/util.dart'; 3 | 4 | class Diff { 5 | final BasicInfo info; 6 | final DiffKind kind; 7 | Diff(this.info, this.kind); 8 | } 9 | 10 | enum DiffKind { add, remove, size, deferred } 11 | 12 | class RemoveDiff extends Diff { 13 | RemoveDiff(BasicInfo info) : super(info, DiffKind.remove); 14 | } 15 | 16 | class AddDiff extends Diff { 17 | AddDiff(BasicInfo info) : super(info, DiffKind.add); 18 | } 19 | 20 | class SizeDiff extends Diff { 21 | final int sizeDifference; 22 | SizeDiff(BasicInfo info, this.sizeDifference) : super(info, DiffKind.size); 23 | } 24 | 25 | class DeferredStatusDiff extends Diff { 26 | final bool wasDeferredBefore; 27 | DeferredStatusDiff(BasicInfo info, this.wasDeferredBefore) 28 | : super(info, DiffKind.deferred); 29 | } 30 | 31 | List diff(AllInfo oldInfo, AllInfo newInfo) { 32 | var differ = new _InfoDiffer(oldInfo, newInfo); 33 | differ.diff(); 34 | return differ.diffs; 35 | } 36 | 37 | class _InfoDiffer extends InfoVisitor { 38 | final AllInfo _old; 39 | final AllInfo _new; 40 | 41 | BasicInfo _other; 42 | 43 | List diffs = []; 44 | 45 | _InfoDiffer(this._old, this._new); 46 | 47 | void diff() { 48 | _diffList(_old.libraries, _new.libraries); 49 | } 50 | 51 | @override 52 | visitAll(AllInfo info) { 53 | throw new StateError('should not diff AllInfo'); 54 | } 55 | 56 | @override 57 | visitProgram(ProgramInfo info) { 58 | throw new StateError('should not diff ProgramInfo'); 59 | } 60 | 61 | @override 62 | visitOutput(OutputUnitInfo info) { 63 | throw new StateError('should not diff OutputUnitInfo'); 64 | } 65 | 66 | // TODO(het): diff constants 67 | @override 68 | visitConstant(ConstantInfo info) { 69 | throw new StateError('should not diff ConstantInfo'); 70 | } 71 | 72 | @override 73 | visitLibrary(LibraryInfo info) { 74 | var other = _other as LibraryInfo; 75 | _checkSize(info, other); 76 | _diffList(info.topLevelVariables, other.topLevelVariables); 77 | _diffList(info.topLevelFunctions, other.topLevelFunctions); 78 | _diffList(info.classes, other.classes); 79 | } 80 | 81 | @override 82 | visitClass(ClassInfo info) { 83 | var other = _other as ClassInfo; 84 | _checkSize(info, other); 85 | _checkDeferredStatus(info, other); 86 | _diffList(info.fields, other.fields); 87 | _diffList(info.functions, other.functions); 88 | } 89 | 90 | @override 91 | visitClosure(ClosureInfo info) { 92 | var other = _other as ClosureInfo; 93 | _checkSize(info, other); 94 | _checkDeferredStatus(info, other); 95 | _diffList([info.function], [other.function]); 96 | } 97 | 98 | @override 99 | visitField(FieldInfo info) { 100 | var other = _other as FieldInfo; 101 | _checkSize(info, other); 102 | _checkDeferredStatus(info, other); 103 | _diffList(info.closures, other.closures); 104 | } 105 | 106 | @override 107 | visitFunction(FunctionInfo info) { 108 | var other = _other as FunctionInfo; 109 | _checkSize(info, other); 110 | _checkDeferredStatus(info, other); 111 | _diffList(info.closures, other.closures); 112 | } 113 | 114 | @override 115 | visitTypedef(TypedefInfo info) { 116 | var other = _other as TypedefInfo; 117 | _checkSize(info, other); 118 | _checkDeferredStatus(info, other); 119 | } 120 | 121 | void _checkSize(BasicInfo info, BasicInfo other) { 122 | if (info.size != other.size) { 123 | diffs.add(new SizeDiff(info, other.size - info.size)); 124 | } 125 | } 126 | 127 | void _checkDeferredStatus(BasicInfo oldInfo, BasicInfo newInfo) { 128 | var oldIsDeferred = _isDeferred(oldInfo); 129 | var newIsDeferred = _isDeferred(newInfo); 130 | if (oldIsDeferred != newIsDeferred) { 131 | diffs.add(new DeferredStatusDiff(oldInfo, oldIsDeferred)); 132 | } 133 | } 134 | 135 | bool _isDeferred(BasicInfo info) { 136 | var outputUnit = info.outputUnit; 137 | return outputUnit.name != null && 138 | outputUnit.name.isNotEmpty && 139 | outputUnit.name != 'main'; 140 | } 141 | 142 | void _diffList(List oldInfos, List newInfos) { 143 | var oldNames = {}; 144 | var newNames = {}; 145 | for (var oldInfo in oldInfos) { 146 | oldNames[longName(oldInfo, useLibraryUri: true)] = oldInfo; 147 | } 148 | for (var newInfo in newInfos) { 149 | newNames[longName(newInfo, useLibraryUri: true)] = newInfo; 150 | } 151 | for (var oldName in oldNames.keys) { 152 | if (newNames[oldName] == null) { 153 | diffs.add(new RemoveDiff(oldNames[oldName])); 154 | } else { 155 | _other = newNames[oldName]; 156 | oldNames[oldName].accept(this); 157 | } 158 | } 159 | for (var newName in newNames.keys) { 160 | if (oldNames[newName] == null) { 161 | diffs.add(new AddDiff(newNames[newName])); 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/src/graph.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A library to work with graphs. It contains a couple algorithms, including 6 | /// Tarjan's algorithm to compute strongly connected components in a graph and 7 | /// Cooper et al's dominator algorithm. 8 | /// 9 | /// Portions of the code in this library was adapted from 10 | /// `package:analyzer/src/generated/collection_utilities.dart`. 11 | // TODO(sigmund): move this into a shared place, like quiver? 12 | library dart2js_info.src.graph; 13 | 14 | import 'dart:math' as math; 15 | 16 | abstract class Graph { 17 | Iterable get nodes; 18 | bool get isEmpty; 19 | int get nodeCount; 20 | Iterable targetsOf(N source); 21 | Iterable sourcesOf(N source); 22 | 23 | /// Run a topological sort of the graph. Since the graph may contain cycles, 24 | /// this results in a list of strongly connected components rather than a list 25 | /// of nodes. The nodes in each strongly connected components only have edges 26 | /// that point to nodes in the same component or earlier components. 27 | List> computeTopologicalSort() { 28 | _SccFinder finder = new _SccFinder(this); 29 | return finder.computeTopologicalSort(); 30 | } 31 | 32 | /// Whether [source] can transitively reach [target]. 33 | bool containsPath(N source, N target) { 34 | Set seen = new Set(); 35 | bool helper(N node) { 36 | if (identical(node, target)) return true; 37 | if (!seen.add(node)) return false; 38 | return targetsOf(node).any(helper); 39 | } 40 | 41 | return helper(source); 42 | } 43 | 44 | /// Returns all nodes reachable from [root] in post order. 45 | Iterable postOrder(N root) sync* { 46 | var seen = new Set(); 47 | Iterable helper(N n) sync* { 48 | if (!seen.add(n)) return; 49 | for (var x in targetsOf(n)) { 50 | yield* helper(x); 51 | } 52 | yield n; 53 | } 54 | 55 | yield* helper(root); 56 | } 57 | 58 | /// Returns an iterable of all nodes reachable from [root] in preorder. 59 | Iterable preOrder(N root) sync* { 60 | var seen = new Set(); 61 | var stack = [root]; 62 | while (stack.isNotEmpty) { 63 | var next = stack.removeLast(); 64 | if (!seen.contains(next)) { 65 | seen.add(next); 66 | yield next; 67 | stack.addAll(targetsOf(next)); 68 | } 69 | } 70 | } 71 | 72 | /// Returns a list of nodes that form a cycle containing the given node. If 73 | /// the node is not part of a cycle in this graph, then a list containing only 74 | /// the node itself will be returned. 75 | List findCycleContaining(N node) { 76 | assert(node != null); 77 | _SccFinder finder = new _SccFinder(this); 78 | return finder._componentContaining(node); 79 | } 80 | 81 | /// Returns a dominator tree starting from root. This is a new graph, with the 82 | /// same nodes as this graph, but where edges exist between a node and the 83 | /// nodes it immediately dominates. For example, this graph: 84 | /// 85 | /// root 86 | /// / \ 87 | /// a b 88 | /// | / \ 89 | /// c d e 90 | /// \ / \ / 91 | /// f g 92 | /// 93 | /// Produces this tree: 94 | /// 95 | /// root 96 | /// /| \ 97 | /// a | b 98 | /// | | /|\ 99 | /// c | d | e 100 | /// | | 101 | /// f g 102 | /// 103 | /// Internally we compute dominators using (Cooper, Harvey, and Kennedy's 104 | /// algorithm)[http://www.cs.rice.edu/~keith/EMBED/dom.pdf]. 105 | Graph dominatorTree(N root) { 106 | var iDom = (new _DominatorFinder(this)..run(root)).immediateDominators; 107 | var graph = new EdgeListGraph(); 108 | for (N node in iDom.keys) { 109 | if (node != root) graph.addEdge(iDom[node], node); 110 | } 111 | return graph; 112 | } 113 | } 114 | 115 | class EdgeListGraph extends Graph { 116 | /// Edges in the graph. 117 | Map> _edges = new Map>(); 118 | 119 | /// The reverse of _edges. 120 | Map> _revEdges = new Map>(); 121 | 122 | Iterable get nodes => _edges.keys; 123 | bool get isEmpty => _edges.isEmpty; 124 | int get nodeCount => _edges.length; 125 | 126 | final _empty = new Set(); 127 | 128 | Iterable targetsOf(N source) => _edges[source] ?? _empty; 129 | Iterable sourcesOf(N source) => _revEdges[source] ?? _empty; 130 | 131 | void addEdge(N source, N target) { 132 | assert(source != null); 133 | assert(target != null); 134 | addNode(source); 135 | addNode(target); 136 | _edges[source].add(target); 137 | _revEdges[target].add(source); 138 | } 139 | 140 | void addNode(N node) { 141 | assert(node != null); 142 | _edges.putIfAbsent(node, () => new Set()); 143 | _revEdges.putIfAbsent(node, () => new Set()); 144 | } 145 | 146 | /// Remove the edge from the given [source] node to the given [target] node. 147 | /// If there was no such edge then the graph will be unmodified: the number of 148 | /// edges will be the same and the set of nodes will be the same (neither node 149 | /// will either be added or removed). 150 | void removeEdge(N source, N target) { 151 | _edges[source]?.remove(target); 152 | } 153 | 154 | /// Remove the given node from this graph. As a consequence, any edges for 155 | /// which that node was either a head or a tail will also be removed. 156 | void removeNode(N node) { 157 | _edges.remove(node); 158 | var sources = _revEdges[node]; 159 | if (sources == null) return; 160 | for (var source in sources) { 161 | _edges[source].remove(node); 162 | } 163 | } 164 | 165 | /// Remove all of the given nodes from this graph. As a consequence, any edges 166 | /// for which those nodes were either a head or a tail will also be removed. 167 | void removeAllNodes(List nodes) => nodes.forEach(removeNode); 168 | } 169 | 170 | /// Used by the [SccFinder] to maintain information about the nodes that have 171 | /// been examined. There is an instance of this class per node in the graph. 172 | class _NodeInfo { 173 | /// Depth of the node corresponding to this info. 174 | int index = 0; 175 | 176 | /// Depth of the first node in a cycle. 177 | int lowlink = 0; 178 | 179 | /// Whether the corresponding node is on the stack. Used to remove the need 180 | /// for searching a collection for the node each time the question needs to be 181 | /// asked. 182 | bool onStack = false; 183 | 184 | /// Component that contains the corresponding node. 185 | List component; 186 | 187 | _NodeInfo(int depth) 188 | : index = depth, 189 | lowlink = depth, 190 | onStack = false; 191 | } 192 | 193 | /// Implements Tarjan's Algorithm for finding the strongly connected components 194 | /// in a graph. 195 | class _SccFinder { 196 | /// The graph to process. 197 | final Graph _graph; 198 | 199 | /// The index used to uniquely identify the depth of nodes. 200 | int _index = 0; 201 | 202 | /// Nodes that are being visited in order to identify components. 203 | List _stack = new List(); 204 | 205 | /// Information associated with each node. 206 | Map> _info = >{}; 207 | 208 | /// All strongly connected components found, in topological sort order (each 209 | /// node in a strongly connected component only has edges that point to nodes 210 | /// in the same component or earlier components). 211 | List> _allComponents = new List>(); 212 | 213 | _SccFinder(this._graph); 214 | 215 | /// Return a list containing the nodes that are part of the strongly connected 216 | /// component that contains the given node. 217 | List _componentContaining(N node) => _strongConnect(node).component; 218 | 219 | /// Run Tarjan's algorithm and return the resulting list of strongly connected 220 | /// components. The list is in topological sort order (each node in a strongly 221 | /// connected component only has edges that point to nodes in the same 222 | /// component or earlier components). 223 | List> computeTopologicalSort() { 224 | for (N node in _graph.nodes) { 225 | var nodeInfo = _info[node]; 226 | if (nodeInfo == null) _strongConnect(node); 227 | } 228 | return _allComponents; 229 | } 230 | 231 | /// Remove and return the top-most element from the stack. 232 | N _pop() { 233 | N node = _stack.removeAt(_stack.length - 1); 234 | _info[node].onStack = false; 235 | return node; 236 | } 237 | 238 | /// Add the given node to the stack. 239 | void _push(N node) { 240 | _info[node].onStack = true; 241 | _stack.add(node); 242 | } 243 | 244 | /// Compute the strongly connected component that contains the given node as 245 | /// well as any components containing nodes that are reachable from the given 246 | /// component. 247 | _NodeInfo _strongConnect(N v) { 248 | // Set the depth index for v to the smallest unused index 249 | var vInfo = new _NodeInfo(_index++); 250 | _info[v] = vInfo; 251 | _push(v); 252 | 253 | for (N w in _graph.targetsOf(v)) { 254 | var wInfo = _info[w]; 255 | if (wInfo == null) { 256 | // Successor w has not yet been visited; recurse on it 257 | wInfo = _strongConnect(w); 258 | vInfo.lowlink = math.min(vInfo.lowlink, wInfo.lowlink); 259 | } else if (wInfo.onStack) { 260 | // Successor w is in stack S and hence in the current SCC 261 | vInfo.lowlink = math.min(vInfo.lowlink, wInfo.index); 262 | } 263 | } 264 | 265 | // If v is a root node, pop the stack and generate an SCC 266 | if (vInfo.lowlink == vInfo.index) { 267 | var component = new List(); 268 | N w; 269 | do { 270 | w = _pop(); 271 | component.add(w); 272 | _info[w].component = component; 273 | } while (!identical(w, v)); 274 | _allComponents.add(component); 275 | } 276 | return vInfo; 277 | } 278 | } 279 | 280 | /// Computes dominators using (Cooper, Harvey, and Kennedy's 281 | /// algorithm)[http://www.cs.rice.edu/~keith/EMBED/dom.pdf]. 282 | class _DominatorFinder { 283 | final Graph _graph; 284 | Map immediateDominators = {}; 285 | Map postOrderId = {}; 286 | _DominatorFinder(this._graph); 287 | 288 | run(N root) { 289 | immediateDominators[root] = root; 290 | bool changed = true; 291 | int i = 0; 292 | var nodesInPostOrder = _graph.postOrder(root).toList(); 293 | for (var n in nodesInPostOrder) { 294 | postOrderId[n] = i++; 295 | } 296 | var nodesInReversedPostOrder = nodesInPostOrder.reversed; 297 | while (changed) { 298 | changed = false; 299 | for (var n in nodesInReversedPostOrder) { 300 | if (n == root) continue; 301 | bool first = true; 302 | N idom; 303 | for (var p in _graph.sourcesOf(n)) { 304 | if (immediateDominators[p] != null) { 305 | if (first) { 306 | idom = p; 307 | first = false; 308 | } else { 309 | idom = _intersect(p, idom); 310 | } 311 | } 312 | } 313 | if (immediateDominators[n] != idom) { 314 | immediateDominators[n] = idom; 315 | changed = true; 316 | } 317 | } 318 | } 319 | } 320 | 321 | N _intersect(N b1, N b2) { 322 | var finger1 = b1; 323 | var finger2 = b2; 324 | while (finger1 != finger2) { 325 | while (postOrderId[finger1] < postOrderId[finger2]) { 326 | finger1 = immediateDominators[finger1]; 327 | } 328 | while (postOrderId[finger2] < postOrderId[finger1]) { 329 | finger2 = immediateDominators[finger2]; 330 | } 331 | } 332 | return finger1; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /lib/src/io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dart2js_info/info.dart'; 5 | import 'package:dart2js_info/json_info_codec.dart'; 6 | import 'package:dart2js_info/binary_serialization.dart' as binary; 7 | 8 | Future infoFromFile(String fileName) async { 9 | var file = new File(fileName); 10 | if (fileName.endsWith('.json')) { 11 | return new AllInfoJsonCodec().decode(jsonDecode(await file.readAsString())); 12 | } else { 13 | return binary.decode(file.readAsBytesSync()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/proto/info.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: info.proto 4 | // 5 | // @dart = 2.3 6 | // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type 7 | -------------------------------------------------------------------------------- /lib/src/proto/info.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: info.proto 4 | // 5 | // @dart = 2.3 6 | // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type 7 | 8 | const DependencyInfoPB$json = const { 9 | '1': 'DependencyInfoPB', 10 | '2': const [ 11 | const {'1': 'target_id', '3': 1, '4': 1, '5': 9, '10': 'targetId'}, 12 | const {'1': 'mask', '3': 2, '4': 1, '5': 9, '10': 'mask'}, 13 | ], 14 | }; 15 | 16 | const AllInfoPB$json = const { 17 | '1': 'AllInfoPB', 18 | '2': const [ 19 | const { 20 | '1': 'program', 21 | '3': 1, 22 | '4': 1, 23 | '5': 11, 24 | '6': '.dart2js_info.proto.ProgramInfoPB', 25 | '10': 'program' 26 | }, 27 | const { 28 | '1': 'all_infos', 29 | '3': 2, 30 | '4': 3, 31 | '5': 11, 32 | '6': '.dart2js_info.proto.AllInfoPB.AllInfosEntry', 33 | '10': 'allInfos' 34 | }, 35 | const { 36 | '1': 'deferred_imports', 37 | '3': 3, 38 | '4': 3, 39 | '5': 11, 40 | '6': '.dart2js_info.proto.LibraryDeferredImportsPB', 41 | '10': 'deferredImports' 42 | }, 43 | ], 44 | '3': const [AllInfoPB_AllInfosEntry$json], 45 | }; 46 | 47 | const AllInfoPB_AllInfosEntry$json = const { 48 | '1': 'AllInfosEntry', 49 | '2': const [ 50 | const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, 51 | const { 52 | '1': 'value', 53 | '3': 2, 54 | '4': 1, 55 | '5': 11, 56 | '6': '.dart2js_info.proto.InfoPB', 57 | '10': 'value' 58 | }, 59 | ], 60 | '7': const {'7': true}, 61 | }; 62 | 63 | const InfoPB$json = const { 64 | '1': 'InfoPB', 65 | '2': const [ 66 | const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, 67 | const {'1': 'id', '3': 2, '4': 1, '5': 5, '10': 'id'}, 68 | const {'1': 'serialized_id', '3': 3, '4': 1, '5': 9, '10': 'serializedId'}, 69 | const {'1': 'coverage_id', '3': 4, '4': 1, '5': 9, '10': 'coverageId'}, 70 | const {'1': 'size', '3': 5, '4': 1, '5': 5, '10': 'size'}, 71 | const {'1': 'parent_id', '3': 6, '4': 1, '5': 9, '10': 'parentId'}, 72 | const { 73 | '1': 'uses', 74 | '3': 7, 75 | '4': 3, 76 | '5': 11, 77 | '6': '.dart2js_info.proto.DependencyInfoPB', 78 | '10': 'uses' 79 | }, 80 | const {'1': 'output_unit_id', '3': 8, '4': 1, '5': 9, '10': 'outputUnitId'}, 81 | const { 82 | '1': 'library_info', 83 | '3': 100, 84 | '4': 1, 85 | '5': 11, 86 | '6': '.dart2js_info.proto.LibraryInfoPB', 87 | '9': 0, 88 | '10': 'libraryInfo' 89 | }, 90 | const { 91 | '1': 'class_info', 92 | '3': 101, 93 | '4': 1, 94 | '5': 11, 95 | '6': '.dart2js_info.proto.ClassInfoPB', 96 | '9': 0, 97 | '10': 'classInfo' 98 | }, 99 | const { 100 | '1': 'function_info', 101 | '3': 102, 102 | '4': 1, 103 | '5': 11, 104 | '6': '.dart2js_info.proto.FunctionInfoPB', 105 | '9': 0, 106 | '10': 'functionInfo' 107 | }, 108 | const { 109 | '1': 'field_info', 110 | '3': 103, 111 | '4': 1, 112 | '5': 11, 113 | '6': '.dart2js_info.proto.FieldInfoPB', 114 | '9': 0, 115 | '10': 'fieldInfo' 116 | }, 117 | const { 118 | '1': 'constant_info', 119 | '3': 104, 120 | '4': 1, 121 | '5': 11, 122 | '6': '.dart2js_info.proto.ConstantInfoPB', 123 | '9': 0, 124 | '10': 'constantInfo' 125 | }, 126 | const { 127 | '1': 'output_unit_info', 128 | '3': 105, 129 | '4': 1, 130 | '5': 11, 131 | '6': '.dart2js_info.proto.OutputUnitInfoPB', 132 | '9': 0, 133 | '10': 'outputUnitInfo' 134 | }, 135 | const { 136 | '1': 'typedef_info', 137 | '3': 106, 138 | '4': 1, 139 | '5': 11, 140 | '6': '.dart2js_info.proto.TypedefInfoPB', 141 | '9': 0, 142 | '10': 'typedefInfo' 143 | }, 144 | const { 145 | '1': 'closure_info', 146 | '3': 107, 147 | '4': 1, 148 | '5': 11, 149 | '6': '.dart2js_info.proto.ClosureInfoPB', 150 | '9': 0, 151 | '10': 'closureInfo' 152 | }, 153 | ], 154 | '8': const [ 155 | const {'1': 'concrete'}, 156 | ], 157 | '9': const [ 158 | const {'1': 9, '2': 100}, 159 | ], 160 | }; 161 | 162 | const ProgramInfoPB$json = const { 163 | '1': 'ProgramInfoPB', 164 | '2': const [ 165 | const {'1': 'entrypoint_id', '3': 1, '4': 1, '5': 9, '10': 'entrypointId'}, 166 | const {'1': 'size', '3': 2, '4': 1, '5': 5, '10': 'size'}, 167 | const { 168 | '1': 'dart2js_version', 169 | '3': 3, 170 | '4': 1, 171 | '5': 9, 172 | '10': 'dart2jsVersion' 173 | }, 174 | const { 175 | '1': 'compilation_moment', 176 | '3': 4, 177 | '4': 1, 178 | '5': 3, 179 | '10': 'compilationMoment' 180 | }, 181 | const { 182 | '1': 'compilation_duration', 183 | '3': 5, 184 | '4': 1, 185 | '5': 3, 186 | '10': 'compilationDuration' 187 | }, 188 | const { 189 | '1': 'to_proto_duration', 190 | '3': 6, 191 | '4': 1, 192 | '5': 3, 193 | '10': 'toProtoDuration' 194 | }, 195 | const { 196 | '1': 'dump_info_duration', 197 | '3': 7, 198 | '4': 1, 199 | '5': 3, 200 | '10': 'dumpInfoDuration' 201 | }, 202 | const { 203 | '1': 'no_such_method_enabled', 204 | '3': 8, 205 | '4': 1, 206 | '5': 8, 207 | '10': 'noSuchMethodEnabled' 208 | }, 209 | const { 210 | '1': 'is_runtime_type_used', 211 | '3': 9, 212 | '4': 1, 213 | '5': 8, 214 | '10': 'isRuntimeTypeUsed' 215 | }, 216 | const { 217 | '1': 'is_isolate_used', 218 | '3': 10, 219 | '4': 1, 220 | '5': 8, 221 | '10': 'isIsolateUsed' 222 | }, 223 | const { 224 | '1': 'is_function_apply_used', 225 | '3': 11, 226 | '4': 1, 227 | '5': 8, 228 | '10': 'isFunctionApplyUsed' 229 | }, 230 | const { 231 | '1': 'is_mirrors_used', 232 | '3': 12, 233 | '4': 1, 234 | '5': 8, 235 | '10': 'isMirrorsUsed' 236 | }, 237 | const {'1': 'minified', '3': 13, '4': 1, '5': 8, '10': 'minified'}, 238 | ], 239 | }; 240 | 241 | const LibraryInfoPB$json = const { 242 | '1': 'LibraryInfoPB', 243 | '2': const [ 244 | const {'1': 'uri', '3': 1, '4': 1, '5': 9, '10': 'uri'}, 245 | const {'1': 'children_ids', '3': 2, '4': 3, '5': 9, '10': 'childrenIds'}, 246 | ], 247 | }; 248 | 249 | const OutputUnitInfoPB$json = const { 250 | '1': 'OutputUnitInfoPB', 251 | '2': const [ 252 | const {'1': 'imports', '3': 1, '4': 3, '5': 9, '10': 'imports'}, 253 | ], 254 | }; 255 | 256 | const ClassInfoPB$json = const { 257 | '1': 'ClassInfoPB', 258 | '2': const [ 259 | const {'1': 'is_abstract', '3': 1, '4': 1, '5': 8, '10': 'isAbstract'}, 260 | const {'1': 'children_ids', '3': 2, '4': 3, '5': 9, '10': 'childrenIds'}, 261 | ], 262 | }; 263 | 264 | const ConstantInfoPB$json = const { 265 | '1': 'ConstantInfoPB', 266 | '2': const [ 267 | const {'1': 'code', '3': 1, '4': 1, '5': 9, '10': 'code'}, 268 | ], 269 | }; 270 | 271 | const FieldInfoPB$json = const { 272 | '1': 'FieldInfoPB', 273 | '2': const [ 274 | const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'}, 275 | const {'1': 'inferred_type', '3': 2, '4': 1, '5': 9, '10': 'inferredType'}, 276 | const {'1': 'children_ids', '3': 3, '4': 3, '5': 9, '10': 'childrenIds'}, 277 | const {'1': 'code', '3': 4, '4': 1, '5': 9, '10': 'code'}, 278 | const {'1': 'is_const', '3': 5, '4': 1, '5': 8, '10': 'isConst'}, 279 | const { 280 | '1': 'initializer_id', 281 | '3': 6, 282 | '4': 1, 283 | '5': 9, 284 | '10': 'initializerId' 285 | }, 286 | ], 287 | }; 288 | 289 | const TypedefInfoPB$json = const { 290 | '1': 'TypedefInfoPB', 291 | '2': const [ 292 | const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'}, 293 | ], 294 | }; 295 | 296 | const FunctionModifiersPB$json = const { 297 | '1': 'FunctionModifiersPB', 298 | '2': const [ 299 | const {'1': 'is_static', '3': 1, '4': 1, '5': 8, '10': 'isStatic'}, 300 | const {'1': 'is_const', '3': 2, '4': 1, '5': 8, '10': 'isConst'}, 301 | const {'1': 'is_factory', '3': 3, '4': 1, '5': 8, '10': 'isFactory'}, 302 | const {'1': 'is_external', '3': 4, '4': 1, '5': 8, '10': 'isExternal'}, 303 | ], 304 | }; 305 | 306 | const ParameterInfoPB$json = const { 307 | '1': 'ParameterInfoPB', 308 | '2': const [ 309 | const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, 310 | const {'1': 'type', '3': 2, '4': 1, '5': 9, '10': 'type'}, 311 | const {'1': 'declared_type', '3': 3, '4': 1, '5': 9, '10': 'declaredType'}, 312 | ], 313 | }; 314 | 315 | const FunctionInfoPB$json = const { 316 | '1': 'FunctionInfoPB', 317 | '2': const [ 318 | const { 319 | '1': 'function_modifiers', 320 | '3': 1, 321 | '4': 1, 322 | '5': 11, 323 | '6': '.dart2js_info.proto.FunctionModifiersPB', 324 | '10': 'functionModifiers' 325 | }, 326 | const {'1': 'children_ids', '3': 2, '4': 3, '5': 9, '10': 'childrenIds'}, 327 | const {'1': 'return_type', '3': 3, '4': 1, '5': 9, '10': 'returnType'}, 328 | const { 329 | '1': 'inferred_return_type', 330 | '3': 4, 331 | '4': 1, 332 | '5': 9, 333 | '10': 'inferredReturnType' 334 | }, 335 | const { 336 | '1': 'parameters', 337 | '3': 5, 338 | '4': 3, 339 | '5': 11, 340 | '6': '.dart2js_info.proto.ParameterInfoPB', 341 | '10': 'parameters' 342 | }, 343 | const {'1': 'side_effects', '3': 6, '4': 1, '5': 9, '10': 'sideEffects'}, 344 | const {'1': 'inlined_count', '3': 7, '4': 1, '5': 5, '10': 'inlinedCount'}, 345 | const {'1': 'code', '3': 8, '4': 1, '5': 9, '10': 'code'}, 346 | ], 347 | '9': const [ 348 | const {'1': 9, '2': 10}, 349 | ], 350 | }; 351 | 352 | const ClosureInfoPB$json = const { 353 | '1': 'ClosureInfoPB', 354 | '2': const [ 355 | const {'1': 'function_id', '3': 1, '4': 1, '5': 9, '10': 'functionId'}, 356 | ], 357 | }; 358 | 359 | const DeferredImportPB$json = const { 360 | '1': 'DeferredImportPB', 361 | '2': const [ 362 | const {'1': 'prefix', '3': 1, '4': 1, '5': 9, '10': 'prefix'}, 363 | const {'1': 'files', '3': 2, '4': 3, '5': 9, '10': 'files'}, 364 | ], 365 | }; 366 | 367 | const LibraryDeferredImportsPB$json = const { 368 | '1': 'LibraryDeferredImportsPB', 369 | '2': const [ 370 | const {'1': 'library_uri', '3': 1, '4': 1, '5': 9, '10': 'libraryUri'}, 371 | const {'1': 'library_name', '3': 2, '4': 1, '5': 9, '10': 'libraryName'}, 372 | const { 373 | '1': 'imports', 374 | '3': 3, 375 | '4': 3, 376 | '5': 11, 377 | '6': '.dart2js_info.proto.DeferredImportPB', 378 | '10': 'imports' 379 | }, 380 | ], 381 | }; 382 | -------------------------------------------------------------------------------- /lib/src/proto/info.pbserver.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: info.proto 4 | // 5 | // @dart = 2.3 6 | // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type 7 | 8 | export 'info.pb.dart'; 9 | -------------------------------------------------------------------------------- /lib/src/string_edit_buffer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Defines [StringEditBuffer], a buffer that can be used to apply edits on a 6 | /// string. 7 | // TODO(sigmund): this should move to a separate package. 8 | library dart2js.src.string_edit_buffer; 9 | 10 | /// A buffer meant to apply edits on a string (rather than building a string 11 | /// from scratch). Each change is described using the location information on 12 | /// the original string. Internally this buffer keeps track of how a 13 | /// modification in one portion can offset a modification further down the 14 | /// string. 15 | class StringEditBuffer { 16 | final String original; 17 | final _edits = <_StringEdit>[]; 18 | 19 | StringEditBuffer(this.original); 20 | 21 | bool get hasEdits => _edits.length > 0; 22 | 23 | /// Edit the original text, replacing text on the range [begin] and 24 | /// exclusive [end] with the [replacement] string. 25 | void replace(int begin, int end, String replacement, [int sortId]) { 26 | _edits.add(new _StringEdit(begin, end, replacement, sortId)); 27 | } 28 | 29 | /// Insert [string] at [offset]. 30 | /// Equivalent to `replace(offset, offset, string)`. 31 | void insert(int offset, String string, [sortId]) => 32 | replace(offset, offset, string, sortId); 33 | 34 | /// Remove text from the range [begin] to exclusive [end]. 35 | /// Equivalent to `replace(begin, end, '')`. 36 | void remove(int begin, int end) => replace(begin, end, ''); 37 | 38 | /// Applies all pending [edit]s and returns a new string. 39 | /// 40 | /// This method is non-destructive: it does not discard existing edits or 41 | /// change the [original] string. Further edits can be added and this method 42 | /// can be called again. 43 | /// 44 | /// Throws [UnsupportedError] if the edits were overlapping. If no edits were 45 | /// made, the original string will be returned. 46 | String toString() { 47 | var sb = new StringBuffer(); 48 | if (_edits.length == 0) return original; 49 | 50 | // Sort edits by start location. 51 | _edits.sort(); 52 | 53 | int consumed = 0; 54 | for (var edit in _edits) { 55 | if (consumed > edit.begin) { 56 | sb = new StringBuffer(); 57 | sb.write('overlapping edits. Insert at offset '); 58 | sb.write(edit.begin); 59 | sb.write(' but have consumed '); 60 | sb.write(consumed); 61 | sb.write(' input characters. List of edits:'); 62 | for (var e in _edits) { 63 | sb.write('\n '); 64 | sb.write(e); 65 | } 66 | throw new UnsupportedError(sb.toString()); 67 | } 68 | 69 | // Add characters from the original string between this edit and the last 70 | // one, if any. 71 | var betweenEdits = original.substring(consumed, edit.begin); 72 | sb.write(betweenEdits); 73 | sb.write(edit.string); 74 | consumed = edit.end; 75 | } 76 | 77 | // Add any text from the end of the original string that was not replaced. 78 | sb.write(original.substring(consumed)); 79 | return sb.toString(); 80 | } 81 | } 82 | 83 | /// A single edit in a [StringEditBuffer]. 84 | class _StringEdit implements Comparable<_StringEdit> { 85 | // Offset where edit begins 86 | final int begin; 87 | 88 | // Offset where edit ends 89 | final int end; 90 | 91 | // Sort index as a tie-breaker for edits that have the same location. 92 | final int sortId; 93 | 94 | // String to insert 95 | final String string; 96 | 97 | _StringEdit(int begin, this.end, this.string, [int sortId]) 98 | : begin = begin, 99 | sortId = sortId == null ? begin : sortId; 100 | 101 | int get length => end - begin; 102 | 103 | String toString() => '(Edit @ $begin,$end: "$string")'; 104 | 105 | int compareTo(_StringEdit other) { 106 | int diff = begin - other.begin; 107 | if (diff != 0) return diff; 108 | diff = end - other.end; 109 | if (diff != 0) return diff; 110 | // use edit order as a tie breaker 111 | return sortId - other.sortId; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/table.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dart2js_info.src.table; 6 | 7 | import 'dart:math' show max; 8 | 9 | /// Helper class to present data on the command-line in a table form. 10 | class Table { 11 | int _totalColumns = 0; 12 | int get totalColumns => _totalColumns; 13 | 14 | /// Abbreviations, used to make headers shorter. 15 | Map abbreviations = {}; 16 | 17 | /// Width of each column. 18 | List widths = []; 19 | 20 | /// The header for each column (`header.length == totalColumns`). 21 | List header = []; 22 | 23 | /// The color for each column (`color.length == totalColumns`). 24 | List colors = []; 25 | 26 | /// Each row on the table. Note that all rows have the same size 27 | /// (`rows[*].length == totalColumns`). 28 | List rows = []; 29 | 30 | /// Columns to skip, for example, if they are all zero entries. 31 | List _skipped = []; 32 | 33 | /// Whether we started adding entries. Indicates that no more columns can be 34 | /// added. 35 | bool _sealed = false; 36 | 37 | /// Current row being built by [addEntry]. 38 | List _currentRow; 39 | 40 | /// Add a column with the given [name]. 41 | void declareColumn(String name, 42 | {bool abbreviate: false, String color: _NO_COLOR}) { 43 | assert(!_sealed); 44 | var headerName = name; 45 | if (abbreviate) { 46 | // abbreviate the header by using only the initials of each word 47 | headerName = 48 | name.split(' ').map((s) => s.substring(0, 1).toUpperCase()).join(''); 49 | while (abbreviations[headerName] != null) headerName = "$headerName'"; 50 | abbreviations[headerName] = name; 51 | } 52 | widths.add(max(5, headerName.length + 1)); 53 | header.add(headerName); 54 | colors.add(color); 55 | _skipped.add(_totalColumns > 0); 56 | _totalColumns++; 57 | } 58 | 59 | /// Add an entry in the table, creating a new row each time [totalColumns] 60 | /// entries are added. 61 | void addEntry(entry) { 62 | if (_currentRow == null) { 63 | _sealed = true; 64 | _currentRow = []; 65 | } 66 | int pos = _currentRow.length; 67 | assert(pos < _totalColumns); 68 | 69 | widths[pos] = max(widths[pos], '$entry'.length + 1); 70 | _currentRow.add('$entry'); 71 | if (entry is int && entry != 0) { 72 | _skipped[pos] = false; 73 | } 74 | 75 | if (pos + 1 == _totalColumns) { 76 | rows.add(_currentRow); 77 | _currentRow = []; 78 | } 79 | } 80 | 81 | /// Add an empty row to divide sections of the table. 82 | void addEmptyRow() { 83 | var emptyRow = []; 84 | for (int i = 0; i < _totalColumns; i++) { 85 | emptyRow.add('-' * widths[i]); 86 | } 87 | rows.add(emptyRow); 88 | } 89 | 90 | /// Enter the header titles. OK to do so more than once in long tables. 91 | void addHeader() { 92 | rows.add(header); 93 | } 94 | 95 | /// Generates a string representation of the table to print on a terminal. 96 | // TODO(sigmund): add also a .csv format 97 | String toString() { 98 | var sb = new StringBuffer(); 99 | sb.write('\n'); 100 | for (var row in rows) { 101 | var lastColor = _NO_COLOR; 102 | for (int i = 0; i < _totalColumns; i++) { 103 | if (_skipped[i]) continue; 104 | var entry = row[i]; 105 | var color = colors[i]; 106 | if (lastColor != color) { 107 | sb.write(color); 108 | lastColor = color; 109 | } 110 | // Align first column to the left, everything else to the right. 111 | sb.write( 112 | i == 0 ? entry.padRight(widths[i]) : entry.padLeft(widths[i] + 1)); 113 | } 114 | if (lastColor != _NO_COLOR) sb.write(_NO_COLOR); 115 | sb.write('\n'); 116 | } 117 | sb.write('\nWhere:\n'); 118 | for (var id in abbreviations.keys) { 119 | sb.write(' $id:'.padRight(7)); 120 | sb.write(' ${abbreviations[id]}\n'); 121 | } 122 | return sb.toString(); 123 | } 124 | } 125 | 126 | const _NO_COLOR = "\x1b[0m"; 127 | -------------------------------------------------------------------------------- /lib/src/util.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library dart2js_info.src.util; 6 | 7 | import 'package:dart2js_info/info.dart'; 8 | 9 | import 'graph.dart'; 10 | 11 | /// Computes a graph of dependencies from [info]. 12 | Graph graphFromInfo(AllInfo info) { 13 | print(' info: dependency graph information is work in progress and' 14 | ' might be incomplete'); 15 | // Note: we are combining dependency information that is computed in two ways 16 | // (functionInfo.uses vs allInfo.dependencies). 17 | // TODO(sigmund): fix inconsistencies between these two ways, stick with one 18 | // of them. 19 | // TODO(sigmund): create a concrete implementation of InfoGraph, instead of 20 | // using the EdgeListGraph. 21 | var graph = new EdgeListGraph(); 22 | for (var f in info.functions) { 23 | graph.addNode(f); 24 | for (var g in f.uses) { 25 | graph.addEdge(f, g.target); 26 | } 27 | if (info.dependencies[f] != null) { 28 | for (var g in info.dependencies[f]) { 29 | graph.addEdge(f, g); 30 | } 31 | } 32 | } 33 | 34 | for (var f in info.fields) { 35 | graph.addNode(f); 36 | for (var g in f.uses) { 37 | graph.addEdge(f, g.target); 38 | } 39 | if (info.dependencies[f] != null) { 40 | for (var g in info.dependencies[f]) { 41 | graph.addEdge(f, g); 42 | } 43 | } 44 | } 45 | 46 | return graph; 47 | } 48 | 49 | /// Provide a unique long name associated with [info]. 50 | // TODO(sigmund): guarantee that the name is actually unique. 51 | String longName(Info info, {bool useLibraryUri: false, bool forId: false}) { 52 | var infoPath = []; 53 | while (info != null) { 54 | infoPath.add(info); 55 | info = info.parent; 56 | } 57 | var sb = new StringBuffer(); 58 | var first = true; 59 | for (var segment in infoPath.reversed) { 60 | if (!first) sb.write('.'); 61 | // TODO(sigmund): ensure that the first segment is a LibraryInfo. 62 | // assert(!first || segment is LibraryInfo); 63 | // (today might not be true for for closure classes). 64 | if (segment is LibraryInfo) { 65 | // TODO(kevmoo): Remove this when dart2js can be invoked with an app-root 66 | // custom URI 67 | if (useLibraryUri && forId && segment.uri.isScheme('file')) { 68 | assert(Uri.base.isScheme('file')); 69 | var currentBase = Uri.base.path; 70 | var segmentString = segment.uri.path; 71 | 72 | // If longName is being called to calculate an element ID (forId = true) 73 | // then use a relative path for the longName calculation 74 | // This allows a more stable ID for cases when files are generated into 75 | // temp directories – e.g. with pkg:build_web_compilers 76 | if (segmentString.startsWith(currentBase)) { 77 | segmentString = segmentString.substring(currentBase.length); 78 | } 79 | 80 | sb.write(segmentString); 81 | } else { 82 | sb.write(useLibraryUri ? segment.uri : segment.name); 83 | } 84 | sb.write('::'); 85 | } else { 86 | first = false; 87 | sb.write(segment.name); 88 | } 89 | } 90 | return sb.toString(); 91 | } 92 | 93 | /// Produce a string containing [value] padded with white space up to [n] chars. 94 | pad(value, n, {bool right: false}) { 95 | var s = '$value'; 96 | if (s.length >= n) return s; 97 | var pad = ' ' * (n - s.length); 98 | return right ? '$s$pad' : '$pad$s'; 99 | } 100 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart2js_info 2 | version: 0.6.5 3 | 4 | description: >- 5 | Libraries and tools to process data produced when running dart2js with 6 | --dump-info. 7 | homepage: https://github.com/dart-lang/dart2js_info/ 8 | 9 | environment: 10 | sdk: '>=2.3.0 <3.0.0' 11 | 12 | dependencies: 13 | args: ^1.4.3 14 | collection: ^1.10.1 15 | fixnum: '>=0.10.5 <2.0.0' 16 | path: ^1.3.6 17 | protobuf: '>=1.0.1 <3.0.0' 18 | shelf: ^0.7.3 19 | yaml: ^2.1.0 20 | 21 | dev_dependencies: 22 | test: ^1.2.0 23 | 24 | executables: 25 | dart2js_info: tools 26 | -------------------------------------------------------------------------------- /test/binary_serialization_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:dart2js_info/json_info_codec.dart'; 9 | import 'package:dart2js_info/binary_serialization.dart' as binary; 10 | import 'package:test/test.dart'; 11 | 12 | class ByteSink implements Sink> { 13 | BytesBuilder builder = new BytesBuilder(); 14 | 15 | add(List data) => builder.add(data); 16 | close() {} 17 | } 18 | 19 | main() { 20 | group('json to proto conversion with deferred files', () { 21 | test('hello_world_deferred', () { 22 | var helloWorld = new File( 23 | 'test/hello_world_deferred/hello_world_deferred.js.info.json'); 24 | var contents = helloWorld.readAsStringSync(); 25 | var json = jsonDecode(contents); 26 | var info = new AllInfoJsonCodec().decode(json); 27 | 28 | var sink = new ByteSink(); 29 | binary.encode(info, sink); 30 | var info2 = binary.decode(sink.builder.toBytes()); 31 | var json2 = new AllInfoJsonCodec().encode(info2); 32 | 33 | info.program.toJsonDuration = new Duration(milliseconds: 0); 34 | var json1 = new AllInfoJsonCodec().encode(info); 35 | var contents1 = const JsonEncoder.withIndent(" ").convert(json1); 36 | var contents2 = const JsonEncoder.withIndent(" ").convert(json2); 37 | expect(contents1 == contents2, isTrue); 38 | }); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/graph_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:dart2js_info/src/graph.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | main() { 9 | var graph = makeTestGraph(); 10 | 11 | test('preorder traversal', () { 12 | expect(graph.preOrder('A').toList(), equals(['A', 'E', 'D', 'C', 'B'])); 13 | }); 14 | 15 | test('postorder traversal', () { 16 | expect(graph.postOrder('A').toList(), equals(['C', 'E', 'D', 'B', 'A'])); 17 | }); 18 | 19 | test('topological sort', () { 20 | expect( 21 | graph.computeTopologicalSort(), 22 | equals([ 23 | ['C'], 24 | ['E'], 25 | ['D', 'B', 'A'] 26 | ])); 27 | }); 28 | 29 | test('contains path', () { 30 | expect(graph.containsPath('A', 'C'), isTrue); 31 | expect(graph.containsPath('B', 'E'), isTrue); 32 | expect(graph.containsPath('C', 'A'), isFalse); 33 | expect(graph.containsPath('E', 'B'), isFalse); 34 | }); 35 | 36 | test('dominator tree', () { 37 | // A dominates all other nodes in the graph, the resulting tree looks like 38 | // A 39 | // / / | | 40 | // B C D E 41 | var dom = graph.dominatorTree('A'); 42 | expect(dom.targetsOf('A').length, equals(4)); 43 | }); 44 | 45 | test('cycle finding', () { 46 | expect(graph.findCycleContaining('B'), equals(['A', 'D', 'B'])); 47 | expect(graph.findCycleContaining('C'), equals(['C'])); 48 | }); 49 | } 50 | 51 | /// Creates a simple test graph with the following structure: 52 | /// ``` 53 | /// A -> E 54 | /// / ^ ^ 55 | /// / \ / 56 | /// v v / 57 | /// B -> D 58 | /// \ / 59 | /// v v 60 | /// C 61 | /// ``` 62 | Graph makeTestGraph() { 63 | var graph = new EdgeListGraph(); 64 | graph.addEdge('A', 'B'); 65 | graph.addEdge('A', 'D'); 66 | graph.addEdge('A', 'E'); 67 | graph.addEdge('B', 'C'); 68 | graph.addEdge('B', 'D'); 69 | graph.addEdge('D', 'A'); 70 | graph.addEdge('D', 'C'); 71 | graph.addEdge('D', 'E'); 72 | return graph; 73 | } 74 | -------------------------------------------------------------------------------- /test/hello_world/hello_world.dart: -------------------------------------------------------------------------------- 1 | main() { 2 | print("Hello, World!"); 3 | } 4 | -------------------------------------------------------------------------------- /test/hello_world_deferred/deferred_import.dart: -------------------------------------------------------------------------------- 1 | const helloWorld = "Hello, World!"; 2 | -------------------------------------------------------------------------------- /test/hello_world_deferred/hello_world_deferred.dart: -------------------------------------------------------------------------------- 1 | import 'deferred_import.dart' deferred as deferred_import; 2 | 3 | Future main() async { 4 | await deferred_import.loadLibrary(); 5 | print(deferred_import.helloWorld); 6 | } 7 | -------------------------------------------------------------------------------- /test/json_to_proto_deferred_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:dart2js_info/json_info_codec.dart'; 9 | import 'package:dart2js_info/proto_info_codec.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | main() { 13 | group('json to proto conversion with deferred files', () { 14 | test('hello_world_deferred', () { 15 | final helloWorld = new File( 16 | 'test/hello_world_deferred/hello_world_deferred.js.info.json'); 17 | final json = jsonDecode(helloWorld.readAsStringSync()); 18 | final decoded = new AllInfoJsonCodec().decode(json); 19 | final proto = new AllInfoProtoCodec().encode(decoded); 20 | 21 | expect(proto.deferredImports, hasLength(1)); 22 | final libraryImports = proto.deferredImports.first; 23 | expect(libraryImports.libraryUri, 'hello_world_deferred.dart'); 24 | expect(libraryImports.libraryName, ''); 25 | expect(libraryImports.imports, hasLength(1)); 26 | final import = libraryImports.imports.first; 27 | expect(import.prefix, 'deferred_import'); 28 | expect(import.files, hasLength(1)); 29 | expect(import.files.first, 'hello_world_deferred.js_1.part.js'); 30 | 31 | final infoMap = proto.allInfos; 32 | 33 | final entrypoint = infoMap[proto.program.entrypointId]; 34 | expect(entrypoint, isNotNull); 35 | expect(entrypoint.hasFunctionInfo(), isTrue); 36 | expect(entrypoint.outputUnitId, isNotNull); 37 | 38 | // The output unit of the entrypoint function should be the default 39 | // entrypoint, which should have no imports. 40 | final defaultOutputUnit = infoMap[entrypoint.outputUnitId]; 41 | expect(defaultOutputUnit, isNotNull); 42 | expect(defaultOutputUnit.hasOutputUnitInfo(), isTrue); 43 | expect(defaultOutputUnit.outputUnitInfo.imports, isEmpty); 44 | }); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /test/json_to_proto_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:dart2js_info/info.dart'; 9 | import 'package:dart2js_info/json_info_codec.dart'; 10 | import 'package:dart2js_info/proto_info_codec.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | main() { 14 | group('json to proto conversion', () { 15 | test('hello_world', () { 16 | final helloWorld = new File('test/hello_world/hello_world.js.info.json'); 17 | final json = jsonDecode(helloWorld.readAsStringSync()); 18 | final decoded = new AllInfoJsonCodec().decode(json); 19 | final proto = new AllInfoProtoCodec().encode(decoded); 20 | 21 | expect(proto.program.entrypointId, isNotNull); 22 | expect(proto.program.size, 10324); 23 | expect(proto.program.compilationMoment.toInt(), 24 | DateTime.parse("2017-04-17 09:46:41.661617").microsecondsSinceEpoch); 25 | expect(proto.program.toProtoDuration.toInt(), 26 | new Duration(milliseconds: 4).inMicroseconds); 27 | expect(proto.program.dumpInfoDuration.toInt(), 28 | new Duration(milliseconds: 0).inMicroseconds); 29 | expect(proto.program.noSuchMethodEnabled, isFalse); 30 | expect(proto.program.minified, isFalse); 31 | }); 32 | 33 | test('has proper id format', () { 34 | final helloWorld = new File('test/hello_world/hello_world.js.info.json'); 35 | final json = jsonDecode(helloWorld.readAsStringSync()); 36 | final decoded = new AllInfoJsonCodec().decode(json); 37 | final proto = new AllInfoProtoCodec().encode(decoded); 38 | 39 | final expectedPrefixes = {}; 40 | for (final kind in InfoKind.values) { 41 | expectedPrefixes[kind] = kindToString(kind) + '/'; 42 | } 43 | 44 | for (final info in proto.allInfos.entries) { 45 | final value = info.value; 46 | if (value.hasLibraryInfo()) { 47 | expect(value.serializedId, 48 | startsWith(expectedPrefixes[InfoKind.library])); 49 | } else if (value.hasClassInfo()) { 50 | expect( 51 | value.serializedId, startsWith(expectedPrefixes[InfoKind.clazz])); 52 | } else if (value.hasFunctionInfo()) { 53 | expect(value.serializedId, 54 | startsWith(expectedPrefixes[InfoKind.function])); 55 | } else if (value.hasFieldInfo()) { 56 | expect( 57 | value.serializedId, startsWith(expectedPrefixes[InfoKind.field])); 58 | } else if (value.hasConstantInfo()) { 59 | expect(value.serializedId, 60 | startsWith(expectedPrefixes[InfoKind.constant])); 61 | } else if (value.hasOutputUnitInfo()) { 62 | expect(value.serializedId, 63 | startsWith(expectedPrefixes[InfoKind.outputUnit])); 64 | } else if (value.hasTypedefInfo()) { 65 | expect(value.serializedId, 66 | startsWith(expectedPrefixes[InfoKind.typedef])); 67 | } else if (value.hasClosureInfo()) { 68 | expect(value.serializedId, 69 | startsWith(expectedPrefixes[InfoKind.closure])); 70 | } 71 | } 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /test/parse_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:dart2js_info/json_info_codec.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | main() { 12 | group('parse', () { 13 | test('hello_world', () { 14 | var helloWorld = new File('test/hello_world/hello_world.js.info.json'); 15 | var json = jsonDecode(helloWorld.readAsStringSync()); 16 | var decoded = new AllInfoJsonCodec().decode(json); 17 | 18 | var program = decoded.program; 19 | expect(program, isNotNull); 20 | 21 | expect(program.entrypoint, isNotNull); 22 | expect(program.size, 10324); 23 | expect(program.compilationMoment, 24 | DateTime.parse("2017-04-17 09:46:41.661617")); 25 | expect(program.compilationDuration, new Duration(microseconds: 357402)); 26 | expect(program.toJsonDuration, new Duration(milliseconds: 4)); 27 | expect(program.dumpInfoDuration, new Duration(seconds: 0)); 28 | expect(program.noSuchMethodEnabled, false); 29 | expect(program.minified, false); 30 | }); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /tool/update_proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -z "$1" ]; then 6 | echo "Expected exactly one argument which is the protoc_plugin version to use" 7 | else 8 | echo "Using protoc_plugin version $1" 9 | pub global activate protoc_plugin "$1" 10 | fi 11 | 12 | protoc --proto_path="." --dart_out=lib/src/proto info.proto 13 | dartfmt -w lib/src/proto 14 | --------------------------------------------------------------------------------