Version 2.17.0-44.0.dev
Merge commit '90542c2903fddc106fae87859157fd0caf007bc8' into 'dev'
diff --git a/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart b/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart
new file mode 100644
index 0000000..577aee6
--- /dev/null
+++ b/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Command-line tool presenting combined information from dump-info and
+/// runtime coverage data.
+///
+/// This tool requires two input files an `.info.data` and a
+/// `.coverage.json` file. To produce these files you need to follow these
+/// steps:
+///
+/// * Compile an app with dart2js using --dump-info and save the .info.data
+/// file:
+///
+/// dart2js --dump-info main.dart
+///
+/// * Build the same app with dart2js using --experimental-track-allocations:
+///
+/// dart2js --experimental-track-allocations main.dart
+///
+/// This can be combined with the --dump-info step above.
+///
+/// * Load your app, exercise your code, then extract the runtime code
+/// coverage JSON blob by querying `$__dart_deferred_initializers__.allocations` in the page.
+///
+/// * Finally, run this tool.
+library compiler.tool.runtime_coverage_analysis;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/command_runner.dart';
+import 'package:collection/collection.dart';
+
+import 'package:dart2js_info/info.dart';
+import 'package:dart2js_info/src/io.dart';
+import 'package:dart2js_info/src/util.dart';
+
+import 'usage_exception.dart';
+
+class RuntimeCoverageAnalysisCommand extends Command<void>
+ with PrintUsageException {
+ @override
+ final String name = "runtime_coverage";
+ @override
+ final String description = "Analyze runtime coverage data";
+
+ RuntimeCoverageAnalysisCommand();
+
+ @override
+ void run() async {
+ var args = argResults.rest;
+ if (args.length < 2) {
+ usageException('Missing arguments, expected: info.data coverage.json');
+ }
+ await _runtimeCoverageAnalysis(args[0], args[1]);
+ }
+}
+
+Future<void> _runtimeCoverageAnalysis(infoFile, coverageFile) async {
+ var info = await infoFromFile(infoFile);
+ var coverageRaw = jsonDecode(File(coverageFile).readAsStringSync());
+ var coverage = <String, bool>{};
+ coverageRaw
+ .forEach((k, v) => coverage[k] = coverage[k] ?? false || v as bool);
+
+ int totalProgramSize = info.program.size;
+ int totalLibSize = info.libraries.fold(0, (n, lib) => n + lib.size);
+
+ int totalCode = 0;
+ int usedCode = 0;
+ var unused = PriorityQueue<Info>((a, b) => b.size.compareTo(a.size));
+
+ void tallyCode(Info i) {
+ totalCode += i.size;
+ var name = qualifiedName(i);
+ var used = coverage[name];
+
+ if (used != null) {
+ usedCode += i.size;
+ } else {
+ unused.add(i);
+ }
+ }
+
+ info.classes.forEach(tallyCode);
+ info.closures.forEach(tallyCode);
+
+ _section('Runtime Coverage Summary');
+ _showHeader('', 'bytes', '%');
+ _show('Program size', totalProgramSize, totalProgramSize);
+ _show('Libraries (excluding statics)', totalLibSize, totalProgramSize);
+ _show('Code (classes + closures)', totalCode, totalProgramSize);
+ _show('Used', usedCode, totalProgramSize);
+
+ print('');
+ _showHeader('', 'count', '%');
+ var total = info.classes.length + info.closures.length;
+ _show('Classes + closures', total, total);
+ _show('Used', total - unused.length, total);
+
+ print('');
+ var unusedTotal = totalCode - usedCode;
+ _section('Runtime Coverage Breakdown', size: unusedTotal);
+
+ // TODO(markzipan): support grouping results by package/library.
+ for (int i = 0; i < unused.length; i++) {
+ var item = unused.removeFirst();
+ var percent = (item.size * 100 / unusedTotal).toStringAsFixed(2);
+ print('${qualifiedName(item)}: ${item.size} bytes, $percent%');
+ }
+}
+
+void _section(String title, {int size}) {
+ if (size == null) {
+ print(title);
+ } else {
+ print('$title ($size bytes)');
+ }
+ print('=' * 72);
+}
+
+_showHeader(String msg, String header1, String header2) {
+ print(' ${pad(msg, 30, right: true)} ${pad(header1, 8)} ${pad(header2, 6)}');
+}
+
+_show(String msg, int size, int total) {
+ var percent = (size * 100 / total).toStringAsFixed(2);
+ print(' ${pad(msg, 30, right: true)} ${pad(size, 8)} ${pad(percent, 6)}%');
+}
diff --git a/pkg/dart2js_info/bin/tools.dart b/pkg/dart2js_info/bin/tools.dart
index 9e2cda8..7dc64bd 100644
--- a/pkg/dart2js_info/bin/tools.dart
+++ b/pkg/dart2js_info/bin/tools.dart
@@ -16,6 +16,7 @@
import 'src/function_size_analysis.dart';
import 'src/library_size_split.dart';
import 'src/live_code_size_analysis.dart';
+import 'src/runtime_coverage_analysis.dart';
import 'src/show_inferred_types.dart';
import 'src/text_print.dart';
@@ -35,6 +36,7 @@
..addCommand(FunctionSizeCommand())
..addCommand(LibrarySizeCommand())
..addCommand(LiveCodeAnalysisCommand())
+ ..addCommand(RuntimeCoverageAnalysisCommand())
..addCommand(ShowInferredTypesCommand())
..addCommand(ShowCommand());
commandRunner.run(args);
diff --git a/pkg/dart2js_info/lib/src/util.dart b/pkg/dart2js_info/lib/src/util.dart
index eebb1c7..9ee83cb 100644
--- a/pkg/dart2js_info/lib/src/util.dart
+++ b/pkg/dart2js_info/lib/src/util.dart
@@ -46,6 +46,26 @@
return graph;
}
+/// Provide a qualified name associated with [info]. Qualified names consist of
+/// the library's canonical URI concatenated with a library-unique kernel name.
+// See: https://github.com/dart-lang/sdk/blob/47eff41cdbfea4a178208dfc3137ba2b6bea0e36/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart#L978
+// TODO(sigmund): guarantee that the name is actually unique.
+String qualifiedName(Info f) {
+ assert(f is ClosureInfo || f is ClassInfo);
+ var element = f;
+ String name;
+ while (element != null) {
+ if (element is LibraryInfo) {
+ name = '${element.uri}:$name';
+ return name;
+ } else {
+ name = name ?? element.name;
+ element = element.parent;
+ }
+ }
+ return '';
+}
+
/// Provide a unique long name associated with [info].
// TODO(sigmund): guarantee that the name is actually unique.
String longName(Info info, {bool useLibraryUri = false, bool forId = false}) {
diff --git a/runtime/lib/string.cc b/runtime/lib/string.cc
index 9fb91ee..e411e07 100644
--- a/runtime/lib/string.cc
+++ b/runtime/lib/string.cc
@@ -343,8 +343,8 @@
ASSERT(receiver.IsOneByteString());
GET_NON_NULL_NATIVE_ARGUMENT(Smi, index_obj, arguments->NativeArgAt(1));
GET_NON_NULL_NATIVE_ARGUMENT(Smi, code_point_obj, arguments->NativeArgAt(2));
- ASSERT((0 <= code_point_obj.Value()) && (code_point_obj.Value() <= 0xFF));
- OneByteString::SetCharAt(receiver, index_obj.Value(), code_point_obj.Value());
+ OneByteString::SetCharAt(receiver, index_obj.Value(),
+ code_point_obj.Value() & 0xFF);
return Object::null();
}
@@ -353,8 +353,8 @@
ASSERT(receiver.IsTwoByteString());
GET_NON_NULL_NATIVE_ARGUMENT(Smi, index_obj, arguments->NativeArgAt(1));
GET_NON_NULL_NATIVE_ARGUMENT(Smi, code_point_obj, arguments->NativeArgAt(2));
- ASSERT((0 <= code_point_obj.Value()) && (code_point_obj.Value() <= 0xFFFF));
- TwoByteString::SetCharAt(receiver, index_obj.Value(), code_point_obj.Value());
+ TwoByteString::SetCharAt(receiver, index_obj.Value(),
+ code_point_obj.Value() & 0xFFFF);
return Object::null();
}
diff --git a/sdk/lib/_internal/vm/lib/convert_patch.dart b/sdk/lib/_internal/vm/lib/convert_patch.dart
index f0b84ad..2885579 100644
--- a/sdk/lib/_internal/vm/lib/convert_patch.dart
+++ b/sdk/lib/_internal/vm/lib/convert_patch.dart
@@ -1979,7 +1979,7 @@
}
byte = (byte << 6) | e;
}
- writeIntoOneByteString(result, j++, byte & 0xFF);
+ writeIntoOneByteString(result, j++, byte);
}
// Output size must match, unless we are doing single conversion and are
// inside an unfinished sequence (which will trigger an error later).
diff --git a/sdk/lib/core/iterator.dart b/sdk/lib/core/iterator.dart
index 7ea322e..a33e273 100644
--- a/sdk/lib/core/iterator.dart
+++ b/sdk/lib/core/iterator.dart
@@ -49,6 +49,14 @@
/// If that happens, the iterator may be in an inconsistent
/// state, and any further behavior of the iterator is unspecified,
/// including the effect of reading [current].
+ /// ```dart
+ /// final colors = ['blue', 'yellow', 'red'];
+ /// final colorsIterator = colors.iterator;
+ /// print(colorsIterator.moveNext()); // true
+ /// print(colorsIterator.moveNext()); // true
+ /// print(colorsIterator.moveNext()); // true
+ /// print(colorsIterator.moveNext()); // false
+ /// ```
bool moveNext();
/// The current element.
@@ -65,5 +73,18 @@
/// [moveNext], even if an underlying collection changes.
/// After a successful call to `moveNext`, the user doesn't need to cache
/// the current value, but can keep reading it from the iterator.
+ /// ```dart
+ /// final colors = ['blue', 'yellow', 'red'];
+ /// var colorsIterator = colors.iterator;
+ /// while (colorsIterator.moveNext()) {
+ /// print(colorsIterator.current);
+ /// }
+ /// ```
+ /// The output of the example is:
+ /// ```
+ /// blue
+ /// yellow
+ /// red
+ /// ```
E get current;
}
diff --git a/sdk/lib/core/regexp.dart b/sdk/lib/core/regexp.dart
index 993da0a5..c2020a6 100644
--- a/sdk/lib/core/regexp.dart
+++ b/sdk/lib/core/regexp.dart
@@ -19,15 +19,32 @@
/// and returns the first [RegExpMatch].
/// All other methods in [RegExp] can be build from that.
///
+/// The following example finds the first match of a regular expression in
+/// a string.
+/// ```dart
+/// RegExp exp = RegExp(r'(\w+)');
+/// String str = 'Parse my string';
+/// RegExpMatch? match = exp.firstMatch(str);
+/// print(match![0]); // "Parse"
+/// ```
/// Use [allMatches] to look for all matches of a regular expression in
/// a string.
///
/// The following example finds all matches of a regular expression in
/// a string.
/// ```dart
-/// RegExp exp = RegExp(r"(\w+)");
-/// String str = "Parse my string";
+/// RegExp exp = RegExp(r'(\w+)');
+/// String str = 'Parse my string';
/// Iterable<RegExpMatch> matches = exp.allMatches(str);
+/// for (final m in matches) {
+/// print(m[0]);
+/// }
+/// ```
+/// The output of the example is:
+/// ```
+/// Parse
+/// my
+/// string
/// ```
///
/// Note the use of a _raw string_ (a string prefixed with `r`)
@@ -54,8 +71,8 @@
/// Example:
///
/// ```dart
- /// var wordPattern = RegExp(r"(\w+)");
- /// var bracketedNumberValue = RegExp("$key: \\[\\d+\\]");
+ /// final wordPattern = RegExp(r'(\w+)');
+ /// final digitPattern = RegExp(r'(\d+)');
/// ```
///
/// Notice the use of a _raw string_ in the first example, and a regular
@@ -82,22 +99,55 @@
/// larger regular expression. Since a [String] is itself a [Pattern]
/// which matches itself, converting the string to a regular expression
/// isn't needed in order to search for just that string.
+ /// ```dart
+ /// print(RegExp.escape('dash@example.com')); // dash@example\.com
+ /// print(RegExp.escape('a+b')); // a\+b
+ /// print(RegExp.escape('a*b')); // a\*b
+ /// print(RegExp.escape('{a-b}')); // \{a-b\}
+ /// print(RegExp.escape('a?')); // a\?
+ /// ```
external static String escape(String text);
/// Finds the first match of the regular expression in the string [input].
///
/// Returns `null` if there is no match.
+ /// ```dart
+ /// final string = '[00:13.37] This is a chat message.';
+ /// final regExp = RegExp(r'c\w*');
+ /// final match = regExp.firstMatch(string)!;
+ /// print(match[0]); // chat
+ /// ```
RegExpMatch? firstMatch(String input);
Iterable<RegExpMatch> allMatches(String input, [int start = 0]);
/// Whether the regular expression has a match in the string [input].
+ /// ```dart
+ /// var string = 'Dash is a bird';
+ /// var regExp = RegExp(r'(humming)?bird');
+ /// var match = regExp.hasMatch(string); // true
+ ///
+ /// regExp = RegExp(r'dog');
+ /// match = regExp.hasMatch(string); // false
+ /// ```
bool hasMatch(String input);
/// The substring of the first match of this regular expression in [input].
+ /// ```dart
+ /// var string = 'Dash is a bird';
+ /// var regExp = RegExp(r'(humming)?bird');
+ /// var match = regExp.stringMatch(string); // Match
+ ///
+ /// regExp = RegExp(r'dog');
+ /// match = regExp.stringMatch(string); // No match
+ /// ```
String? stringMatch(String input);
/// The source regular expression string used to create this `RegExp`.
+ /// ```dart
+ /// final regExp = RegExp(r'\p{L}');
+ /// print(regExp.pattern); // \p{L}
+ /// ```
String get pattern;
/// Whether this regular expression matches multiple lines.
@@ -112,6 +162,16 @@
/// If the regular expression is not case sensitive, it will match an input
/// letter with a pattern letter even if the two letters are different case
/// versions of the same letter.
+ /// ```dart
+ /// final str = 'Parse my string';
+ /// var regExp = RegExp(r'STRING', caseSensitive: false);
+ /// final hasMatch = regExp.hasMatch(str); // Has matches.
+ /// print(regExp.isCaseSensitive); // false
+ ///
+ /// regExp = RegExp(r'STRING', caseSensitive: true);
+ /// final hasCaseSensitiveMatch = regExp.hasMatch(str); // No matches.
+ /// print(regExp.isCaseSensitive); // true
+ /// ```
bool get isCaseSensitive;
/// Whether this regular expression is in Unicode mode.
@@ -124,6 +184,19 @@
/// In Unicode mode, the syntax of the RegExp pattern is more restricted, but
/// some pattern features, like Unicode property escapes, are only available in
/// this mode.
+ /// ```dart
+ /// var regExp = RegExp(r'^\p{L}$', unicode: true);
+ /// print(regExp.hasMatch('a')); // true
+ /// print(regExp.hasMatch('b')); // true
+ /// print(regExp.hasMatch('?')); // false
+ /// print(regExp.hasMatch(r'p{L}')); // false
+ ///
+ /// regExp = RegExp(r'^\p{L}$', unicode: false);
+ /// print(regExp.hasMatch('a')); // false
+ /// print(regExp.hasMatch('b')); // false
+ /// print(regExp.hasMatch('?')); // false
+ /// print(regExp.hasMatch(r'p{L}')); // true
+ /// ```
@Since("2.4")
bool get isUnicode;
@@ -145,6 +218,41 @@
/// Regular expression matches are [Match]es, but also include the ability
/// to retrieve the names for any named capture groups and to retrieve
/// matches for named capture groups by name instead of their index.
+///
+/// Example:
+/// ```dart
+/// const pattern =
+/// r'^\[(?<Time>\s*((?<hour>\d+)):((?<minute>\d+))\.((?<second>\d+)))\]'
+/// r'\s(?<Message>\s*(.*)$)';
+///
+/// final regExp = RegExp(
+/// pattern,
+/// multiLine: true,
+/// );
+///
+/// const multilineText = '[00:13.37] This is a first message.\n'
+/// '[01:15.57] This is a second message.\n';
+///
+/// RegExpMatch regExpMatch = regExp.firstMatch(multilineText)!;
+/// print(regExpMatch.groupNames.join('-')); // hour-minute-second-Time-Message.
+/// final time = regExpMatch.namedGroup('Time'); // 00:13.37
+/// final hour = regExpMatch.namedGroup('hour'); // 00
+/// final minute = regExpMatch.namedGroup('minute'); // 13
+/// final second = regExpMatch.namedGroup('second'); // 37
+/// final message =
+/// regExpMatch.namedGroup('Message'); // This is a first message.
+/// final date = regExpMatch.namedGroup('Date'); // Undefined `Date`, throws.
+///
+/// Iterable<RegExpMatch> matches = regExp.allMatches(multilineText);
+/// for (final m in matches) {
+/// print(m.namedGroup('Time'));
+/// print(m.namedGroup('Message'));
+/// // 00:13.37
+/// // This is a first message.
+/// // 01:15.57
+/// // This is a second message.
+/// }
+/// ```
@Since("2.3")
abstract class RegExpMatch implements Match {
/// The string matched by the group named [name].
diff --git a/tests/lib/convert/streamed_conversion_json_utf8_decode_test.dart b/tests/lib/convert/streamed_conversion_json_utf8_decode_test.dart
index 8e1f50d..5282193 100644
--- a/tests/lib/convert/streamed_conversion_json_utf8_decode_test.dart
+++ b/tests/lib/convert/streamed_conversion_json_utf8_decode_test.dart
@@ -7,6 +7,7 @@
// VMOptions=--verify_after_gc
// VMOptions=--verify_before_gc --verify_after_gc
// VMOptions=--verify_store_buffer
+// VMOptions=--no_intrinsify
import "package:expect/expect.dart";
import 'dart:async';
diff --git a/tests/lib_2/convert/streamed_conversion_json_utf8_decode_test.dart b/tests/lib_2/convert/streamed_conversion_json_utf8_decode_test.dart
index 9faad73..19094bf 100644
--- a/tests/lib_2/convert/streamed_conversion_json_utf8_decode_test.dart
+++ b/tests/lib_2/convert/streamed_conversion_json_utf8_decode_test.dart
@@ -9,6 +9,7 @@
// VMOptions=--verify_after_gc
// VMOptions=--verify_before_gc --verify_after_gc
// VMOptions=--verify_store_buffer
+// VMOptions=--no_intrinsify
import "package:expect/expect.dart";
import 'dart:async';
diff --git a/tools/VERSION b/tools/VERSION
index 9d1057b..663e7c4 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 43
+PRERELEASE 44
PRERELEASE_PATCH 0
\ No newline at end of file