Version 2.12.0-136.0.dev
Merge commit '425780f410f668608bb3a675f9d52811bde973ad' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0d0877..685ce1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,14 @@
* `LinkedList` made it explicit that elements are compared by identity,
and updated `contains` to take advantage of this.
+#### `dart:html`
+
+* `EventStreamSubscription.cancel` has been updated to retain its synchronous
+ timing when running in both sound and unsound null safety modes. See issue
+ [#44157][] for more details.
+
+[#44157]: https://github.com/dart-lang/sdk/issues/44157
+
### Dart VM
* **Breaking Change** [#42312][]: `Dart_WeakPersistentHandle`s will no longer
@@ -117,9 +125,23 @@
* New command `dart pub add` that adds new dependencies to your `pubspec.yaml`.
And a corresponding `dart pub remove` that removes dependencies.
+* New option `dart pub upgrade --major-versions` will update constraints in
+ your `pubspec.yaml` to match the the _resolvable_ column reported in
+ `dart pub outdated`. This allows users to easily upgrade to latest version for
+ all dependencies where this is possible, even if such upgrade requires an
+ update to the version constraint in `pubspec.yaml`.
+
+ It is also possible to only upgrade the major version for a subset of your
+ dependencies using `dart pub upgrade --major-versions <dependencies...>`.
+* New option `dart pub upgrade --null-safety` will attempt to update constraints
+ in your `pubspec.yaml`, such that only null-safety migrated versions of
+ dependencies are allowed.
* New option `dart pub outdated --mode=null-safety` that will analyze your
dependencies for null-safety.
* `dart pub publish` will now check your pubspec keys for likely typos.
+* `dart pub upgrade package_foo` will fetch dependencies, but ignore the
+ `pubspec.lock` for `package_foo`, allowing users to only upgrade a subset of
+ dependencies.
* New command `dart pub login` that logs in to pub.dev.
* The `--server` option to `dart pub publish` and `dart pub uploader` have been
deprecated. Use `publish_to` in your `pubspec.yaml` or set the
diff --git a/DEPS b/DEPS
index 113420e..5eb902f 100644
--- a/DEPS
+++ b/DEPS
@@ -108,7 +108,7 @@
"glob_rev": "7c0ef8d4fa086f6b185c4dd724b700e7d7ad8f79",
"html_rev": "22f17e97fedeacaa1e945cf84d8016284eed33a6",
"http_io_rev": "2fa188caf7937e313026557713f7feffedd4978b",
- "http_multi_server_rev" : "f1d1c9c024a293ab0a0e16f8b7632e87c708b448",
+ "http_multi_server_rev" : "e8c8be7f15b4fb50757ff5bf29766721fbe24fe4",
"http_parser_rev": "5dd4d16693242049dfb43b5efa429fedbf932e98",
"http_retry_tag": "0.1.1",
"http_rev": "1617b728fc48f64fb0ed7dc16078c03adcc64179",
@@ -133,7 +133,7 @@
"ply_rev": "604b32590ffad5cbb82e4afef1d305512d06ae93",
"pool_rev": "7abe634002a1ba8a0928eded086062f1307ccfae",
"protobuf_rev": "0d03fd588df69e9863e2a2efc0059dee8f18d5b2",
- "pub_rev": "228e69e53862879c283c42b98086aa7536012a66",
+ "pub_rev": "8ea96121dbb3f762f63e641d49141927dc30aac1",
"pub_semver_rev": "10569a1e867e909cf5db201f73118020453e5db8",
"resource_rev": "6b79867d0becf5395e5819a75720963b8298e9a7",
"root_certificates_rev": "7e5ec82c99677a2e5b95ce296c4d68b0d3378ed8",
@@ -141,7 +141,7 @@
"shelf_static_rev": "779a6e99b320ce9ed4ff6b88bd0cdc40ea5c62c4",
"shelf_packages_handler_tag": "2.0.0",
"shelf_proxy_tag": "0.1.0+7",
- "shelf_rev": "5a77d25861db91db4343b19dfe213b8992e0a837",
+ "shelf_rev": "fa5afaa38bd51dedeeaa25b7bfd8822cabbcc57f",
"shelf_web_socket_rev": "8050a55b16faa5052a3e5d7dcdc170c59b6644f2",
"source_map_stack_trace_rev": "1c3026f69d9771acf2f8c176a1ab750463309cce",
"source_maps-0.9.4_rev": "38524",
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index c37c64c..62cf759 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -31,6 +31,7 @@
import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/src/util/glob.dart';
import 'package:analyzer/src/util/yaml.dart';
+import 'package:analyzer/src/workspace/bazel.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
import 'package:path/path.dart' as pathos;
@@ -393,6 +394,11 @@
final Map<Folder, StreamSubscription<WatchEvent>> changeSubscriptions =
<Folder, StreamSubscription<WatchEvent>>{};
+ /// For each root directory stores subscriptions and watchers that we
+ /// established to detect changes to Bazel generated files.
+ final Map<Folder, _BazelWorkspaceSubscription> bazelSubscriptions =
+ <Folder, _BazelWorkspaceSubscription>{};
+
ContextManagerImpl(
this.resourceProvider,
this.sdkManager,
@@ -1016,6 +1022,7 @@
contextRoot.optionsFilePath = optionsFile.path;
}
info.analysisDriver = callbacks.addAnalysisDriver(folder, contextRoot);
+ _watchBazelFilesIfNeeded(folder, info.analysisDriver);
if (optionsFile != null) {
_analyzeAnalysisOptionsFile(info.analysisDriver, optionsFile.path);
}
@@ -1116,6 +1123,7 @@
/// Clean up and destroy the context associated with the given folder.
void _destroyContext(ContextInfo info) {
changeSubscriptions.remove(info.folder)?.cancel();
+ bazelSubscriptions.remove(info.folder)?.cancel();
callbacks.removeContext(info.folder, _computeFlushedFiles(info));
var wasRemoved = info.parent.children.remove(info);
assert(wasRemoved);
@@ -1220,6 +1228,47 @@
return rootInfo;
}
+ /// Establishes watch(es) for the Bazel generated files provided in
+ /// [notification].
+ ///
+ /// Whenever the files change, we trigger re-analysis. This allows us to react
+ /// to creation/modification of files that were generated by Bazel.
+ void _handleBazelFileNotification(
+ Folder folder, BazelFileNotification notification) {
+ var fileSubscriptions = bazelSubscriptions[folder].fileSubscriptions;
+ if (fileSubscriptions.containsKey(notification.requested)) {
+ // We have already established a Watcher for this particular path.
+ return;
+ }
+ var watcher = notification.watcher(
+ pollingDelayShort: Duration(seconds: 10),
+ pollingDelayLong: Duration(seconds: 30));
+ var subscription = watcher.events.listen(_handleBazelWatchEvent);
+ fileSubscriptions[notification.requested] =
+ _BazelFilesSubscription(watcher, subscription);
+ watcher.start();
+ }
+
+ /// Notifies the drivers that a generated Bazel file has changed.
+ void _handleBazelWatchEvent(WatchEvent event) {
+ if (event.type == ChangeType.ADD) {
+ for (var driver in driverMap.values) {
+ driver.addFile(event.path);
+ // Since the file has been created after we've searched for it, the
+ // URI resolution is likely wrong, so we need to reset it.
+ driver.resetUriResolution();
+ }
+ } else if (event.type == ChangeType.MODIFY) {
+ for (var driver in driverMap.values) {
+ driver.changeFile(event.path);
+ }
+ } else if (event.type == ChangeType.REMOVE) {
+ for (var driver in driverMap.values) {
+ driver.removeFile(event.path);
+ }
+ }
+ }
+
void _handleWatchEvent(WatchEvent event) {
callbacks.broadcastWatchEvent(event);
_handleWatchEventImpl(event);
@@ -1504,6 +1553,22 @@
driver.configure(sourceFactory: sourceFactory);
}
+ /// Listens to files generated by Bazel that were found or searched for.
+ ///
+ /// This is handled specially because the files are outside the package
+ /// folder, but we still want to watch for changes to them.
+ ///
+ /// Does nothing if the [driver] is not in a Bazel workspace.
+ void _watchBazelFilesIfNeeded(Folder folder, AnalysisDriver analysisDriver) {
+ var workspace = analysisDriver.analysisContext.workspace;
+ if (workspace is BazelWorkspace &&
+ !bazelSubscriptions.containsKey(folder)) {
+ var subscription = workspace.bazelCandidateFiles.listen(
+ (notification) => _handleBazelFileNotification(folder, notification));
+ bazelSubscriptions[folder] = _BazelWorkspaceSubscription(subscription);
+ }
+ }
+
/// Create and return a source representing the given [file] within the given
/// [driver].
static Source createSourceInContext(AnalysisDriver driver, File file) {
@@ -1622,3 +1687,36 @@
return _embedderLocator;
}
}
+
+/// A watcher with subscription used to detect changes to some file.
+class _BazelFilesSubscription {
+ final BazelFileWatcher watcher;
+ final StreamSubscription<WatchEvent> subscription;
+
+ _BazelFilesSubscription(this.watcher, this.subscription);
+
+ void cancel() {
+ subscription.cancel();
+ watcher.stop();
+ }
+}
+
+/// A subscription to notifications from a Bazel workspace.
+class _BazelWorkspaceSubscription {
+ final StreamSubscription<BazelFileNotification> workspaceSubscription;
+
+ /// For each absolute path that we searched for, provides the subscriptions
+ /// that we established to watch for changes.
+ ///
+ /// Note that the absolute path used when searching for a file is not
+ /// necessarily the actual path of the file (see [BazelWorkspace.findFile] for
+ /// details on how the files are searched).
+ final fileSubscriptions = <String, _BazelFilesSubscription>{};
+
+ _BazelWorkspaceSubscription(this.workspaceSubscription);
+
+ void cancel() {
+ workspaceSubscription.cancel();
+ fileSubscriptions.values.forEach((sub) => sub.cancel());
+ }
+}
diff --git a/pkg/analysis_server/test/analysis/bazel_changes_test.dart b/pkg/analysis_server/test/analysis/bazel_changes_test.dart
new file mode 100644
index 0000000..168f461
--- /dev/null
+++ b/pkg/analysis_server/test/analysis/bazel_changes_test.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_constants.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../analysis_abstract.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(BazelChangesTest);
+ });
+}
+
+@reflectiveTest
+class BazelChangesTest extends AbstractAnalysisTest {
+ Map<String, List<AnalysisError>> filesErrors = {};
+ Completer<void> processedNotification;
+
+ @override
+ void processNotification(Notification notification) {
+ if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) {
+ var decoded = AnalysisErrorsParams.fromNotification(notification);
+ filesErrors[decoded.file] = decoded.errors;
+ processedNotification?.complete();
+ }
+ }
+
+ @override
+ void setUp() {
+ super.setUp();
+
+ projectPath = convertPath('/workspaceRoot/third_party/dart/project');
+ testFile =
+ convertPath('/workspaceRoot/third_party/dart/project/lib/test.dart');
+ newFile('/workspaceRoot/WORKSPACE');
+ newFolder('/workspaceRoot/bazel-lib/project');
+ newFolder('/workspaceRoot/bazel-genfiles/project');
+ }
+
+ @override
+ void tearDown() {
+ // Make sure to destroy all the contexts and cancel all subscriptions to
+ // file watchers.
+ server.contextManager.setRoots([], []);
+
+ super.tearDown();
+ }
+
+ Future<void> test_findingFileInGenfiles() async {
+ processedNotification = Completer();
+
+ newFile(testFile, content: r'''
+import 'generated.dart';
+void main() { fun(); }
+''');
+ createProject();
+
+ // We should have some errors since the `generated.dart` is not there yet.
+ await processedNotification.future;
+ expect(filesErrors[testFile], isNotEmpty);
+
+ // Clear errors, so that we'll notice new results.
+ filesErrors.clear();
+ processedNotification = Completer();
+
+ // Simulate the creation of a generated file.
+ newFile(
+ '/workspaceRoot/bazel-genfiles/'
+ 'third_party/dart/project/lib/generated.dart',
+ content: 'fun() {}');
+
+ // No errors.
+ await processedNotification.future;
+ expect(filesErrors[testFile], isEmpty);
+ }
+}
diff --git a/pkg/analysis_server/test/analysis/test_all.dart b/pkg/analysis_server/test/analysis/test_all.dart
index 1187065..3329d7f 100644
--- a/pkg/analysis_server/test/analysis/test_all.dart
+++ b/pkg/analysis_server/test/analysis/test_all.dart
@@ -4,6 +4,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import 'bazel_changes_test.dart' as bazel_changes;
import 'get_errors_test.dart' as get_errors;
import 'get_hover_test.dart' as get_hover;
import 'get_navigation_test.dart' as get_navigation;
@@ -26,6 +27,7 @@
void main() {
defineReflectiveSuite(() {
+ bazel_changes.main();
get_errors.main();
get_hover.main();
get_navigation.main();
diff --git a/pkg/analyzer/lib/src/context/builder.dart b/pkg/analyzer/lib/src/context/builder.dart
index 7a43df7..c91e372 100644
--- a/pkg/analyzer/lib/src/context/builder.dart
+++ b/pkg/analyzer/lib/src/context/builder.dart
@@ -114,7 +114,10 @@
if (builderOptions.librarySummaryPaths != null) {
summaryData = SummaryDataStore(builderOptions.librarySummaryPaths);
}
- final sf = createSourceFactory(path, summaryData: summaryData);
+ Workspace workspace =
+ ContextBuilder.createWorkspace(resourceProvider, path, this);
+ final sf =
+ createSourceFactoryFromWorkspace(workspace, summaryData: summaryData);
AnalysisDriver driver = AnalysisDriver(
analysisDriverScheduler,
@@ -143,6 +146,7 @@
resourceProvider,
apiContextRoots.first,
driver,
+ workspace: workspace,
),
);
@@ -209,6 +213,15 @@
return workspace.createSourceFactory(sdk, summaryData);
}
+ SourceFactory createSourceFactoryFromWorkspace(Workspace workspace,
+ {SummaryDataStore summaryData}) {
+ DartSdk sdk = findSdk(workspace);
+ if (summaryData != null && sdk is SummaryBasedDartSdk) {
+ summaryData.addBundle(null, sdk.bundle);
+ }
+ return workspace.createSourceFactory(sdk, summaryData);
+ }
+
/// Add any [declaredVariables] to the list of declared variables used by the
/// given analysis [driver].
void declareVariablesInDriver(AnalysisDriver driver) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
index d02bc2c..0b09d2e 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
@@ -29,7 +29,9 @@
/// to access the file system and that is based on the given analysis
/// [driver].
DriverBasedAnalysisContext(
- this.resourceProvider, this.contextRoot, this.driver) {
+ this.resourceProvider, this.contextRoot, this.driver,
+ {Workspace workspace})
+ : _workspace = workspace {
driver.analysisContext = this;
}
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 1f98644..5a9f210 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -4357,10 +4357,15 @@
List<DartType> typeArguments,
NullabilitySuffix nullabilitySuffix,
}) {
- return super.instantiate(
+ var type = super.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: nullabilitySuffix,
- ) as FunctionType;
+ );
+ if (type is FunctionType) {
+ return type;
+ } else {
+ return _errorFunctionType(nullabilitySuffix);
+ }
}
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index 32e61ba..3413ad8 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -132,6 +132,13 @@
name,
_resolver.typeProvider.typeType.element,
);
+ } else if (element is TypeAliasElement) {
+ var aliasedType = element.aliasedType;
+ if (aliasedType is InterfaceType) {
+ _resolveReceiverTypeLiteral(
+ node, aliasedType.element, nameNode, name);
+ return;
+ }
}
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index a271757e..a649b31 100644
--- a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -298,15 +298,24 @@
if (target is Identifier) {
var targetElement = target.staticElement;
if (targetElement is ClassElement) {
- if (isCascaded) {
- targetElement = _resolver.typeProvider.typeType.element;
- }
return _resolveTargetClassElement(
typeReference: targetElement,
+ isCascaded: isCascaded,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
+ } else if (targetElement is TypeAliasElement) {
+ var aliasedType = targetElement.aliasedType;
+ if (aliasedType is InterfaceType) {
+ return _resolveTargetClassElement(
+ typeReference: aliasedType.element,
+ isCascaded: isCascaded,
+ propertyName: propertyName,
+ hasRead: hasRead,
+ hasWrite: hasWrite,
+ );
+ }
}
}
@@ -382,10 +391,15 @@
PropertyElementResolverResult _resolveTargetClassElement({
@required ClassElement typeReference,
+ @required bool isCascaded,
@required SimpleIdentifier propertyName,
@required bool hasRead,
@required bool hasWrite,
}) {
+ if (isCascaded) {
+ typeReference = _resolver.typeProvider.typeType.element;
+ }
+
ExecutableElement readElement;
if (hasRead) {
readElement = typeReference.getGetter(propertyName.name);
diff --git a/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart
index d3b354b..785ea9f 100644
--- a/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/simple_identifier_resolver.dart
@@ -246,9 +246,8 @@
}
return;
} else if (element is TypeAliasElement) {
- if (node.inDeclarationContext() || node.parent is TypeName) {
- // no type
- } else {
+ if (_isExpressionIdentifier(node) ||
+ element.aliasedType is! InterfaceType) {
node.staticType = _typeProvider.typeType;
}
return;
diff --git a/pkg/analyzer/lib/src/generated/element_resolver.dart b/pkg/analyzer/lib/src/generated/element_resolver.dart
index 5864421..54fb25c 100644
--- a/pkg/analyzer/lib/src/generated/element_resolver.dart
+++ b/pkg/analyzer/lib/src/generated/element_resolver.dart
@@ -723,9 +723,14 @@
/// returned.
static ClassElement getTypeReference(Expression expression) {
if (expression is Identifier) {
- Element staticElement = expression.staticElement;
- if (staticElement is ClassElement) {
- return staticElement;
+ var element = expression.staticElement;
+ if (element is ClassElement) {
+ return element;
+ } else if (element is TypeAliasElement) {
+ var aliasedType = element.aliasedType;
+ if (aliasedType is InterfaceType) {
+ return aliasedType.element;
+ }
}
}
return null;
diff --git a/pkg/analyzer/lib/src/summary2/type_alias.dart b/pkg/analyzer/lib/src/summary2/type_alias.dart
index f69f097..303646c 100644
--- a/pkg/analyzer/lib/src/summary2/type_alias.dart
+++ b/pkg/analyzer/lib/src/summary2/type_alias.dart
@@ -43,12 +43,8 @@
}
void genericTypeAlias(GenericTypeAlias node) {
- var functionType = node.functionType;
- if (functionType != null) {
- _typeParameterList(functionType.typeParameters);
- _formalParameterList(functionType.parameters);
- _visit(functionType.returnType);
- }
+ _typeParameterList(node.typeParameters);
+ _visit(node.type);
}
void _argumentList(TypeArgumentList node) {
diff --git a/pkg/analyzer/lib/src/workspace/bazel.dart b/pkg/analyzer/lib/src/workspace/bazel.dart
index da3cafa..d32be7a 100644
--- a/pkg/analyzer/lib/src/workspace/bazel.dart
+++ b/pkg/analyzer/lib/src/workspace/bazel.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
import 'dart:collection';
import 'dart:core';
@@ -14,8 +15,43 @@
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/util/uri.dart';
import 'package:analyzer/src/workspace/workspace.dart';
+import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:pub_semver/pub_semver.dart';
+import 'package:watcher/watcher.dart';
+
+/// Notification that we issue in [BazelWorkspace.findFile] when searching for
+/// generated files.
+///
+/// This allows clients to watch for changes to the generated files.
+class BazelFileNotification {
+ /// Candidate paths that we searched.
+ ///
+ /// If it's a singleton, then the file was found/resolved.
+ final List<String> candidates;
+
+ /// Absolute path that we tried searching for.
+ ///
+ /// This is not necessarily the path of the actual file that will be used. See
+ /// [BazelWorkspace.findFile] for details.
+ final String requested;
+
+ final ResourceProvider _provider;
+
+ BazelFileNotification(this.requested, this.candidates, this._provider);
+
+ BazelFileWatcher watcher(
+ {@required Duration pollingDelayShort,
+ @required Duration pollingDelayLong,
+ Timer Function(Duration, void Function(Timer)) timerFactory =
+ _defaultTimerFactory}) =>
+ BazelFileWatcher(candidates, _provider, pollingDelayShort,
+ pollingDelayLong, timerFactory);
+
+ static Timer _defaultTimerFactory(
+ Duration duration, void Function(Timer) callback) =>
+ Timer.periodic(duration, callback);
+}
/// Instances of the class `BazelFileUriResolver` resolve `file` URI's by first
/// resolving file uri's in the expected way, and then by looking in the
@@ -41,6 +77,140 @@
}
}
+/// Watches a list of files that should be generated by Bazel.
+///
+/// If we didn't find the file initially, we'll have more that one potential
+/// path. We'll poll them with a shorter delay waiting for at least one
+/// of the paths to become valid (we assume that users will generate the rather
+/// sooner than later since they will likely see errors due to unresolved
+/// files). Once a file appears, we switch to a different mode, where we only
+/// watch that particular file and poll it less frequently. If the file is
+/// deleted we go back to the initial mode of eager polling for all paths.
+class BazelFileWatcher {
+ /// The paths of files that we watch for.
+ ///
+ /// If it's a singleton, then the file was found/resolved by the
+ /// [BazelWorkspace].
+ final List<String> _candidates;
+
+ final _eventsController = StreamController<WatchEvent>.broadcast();
+
+ /// The time of last modification of the file under [_validPath].
+ int _lastModified;
+
+ /// How often do we poll a file that we have found.
+ final Duration _pollingDelayLong;
+
+ /// How often do we poll when none of potential files exist.
+ final Duration _pollingDelayShort;
+
+ final ResourceProvider _provider;
+
+ /// One of the [_candidates] that is valid, i.e., we found a file with that
+ /// path.
+ String _validPath;
+
+ Timer _timer;
+
+ /// Used to contruct a [Timer] for polling.
+ final Timer Function(Duration, void Function(Timer)) _timerFactory;
+
+ BazelFileWatcher(this._candidates, this._provider, this._pollingDelayShort,
+ this._pollingDelayLong, this._timerFactory);
+
+ Stream<WatchEvent> get events => _eventsController.stream;
+
+ /// Starts watching the files.
+ ///
+ /// To avoid missing events, the clients should first start listening on
+ /// [events] and then call [start].
+ void start() {
+ assert(_timer == null);
+ var info = _pollAll();
+ if (info != null) {
+ _validPath = info.path;
+ _lastModified = info.modified;
+ _setPollingDelayToLong();
+ } else {
+ _setPollingDelayToShort();
+ }
+ }
+
+ void stop() {
+ _timer.cancel();
+ _eventsController.close();
+ }
+
+ void _poll() {
+ if (_eventsController.isClosed) return;
+
+ int modified;
+ if (_validPath == null) {
+ var info = _pollAll();
+ if (info != null) {
+ _validPath = info.path;
+ modified = info.modified;
+ }
+ } else {
+ modified = _pollOne(_validPath);
+ }
+
+ // If there is no file, then we have nothing to do.
+ if (_validPath == null) return;
+
+ if (modified == null && _lastModified != null) {
+ // The file is no longer there, so let's issue a REMOVE event, unset
+ // `_validPath` and set the timer to poll more frequently.
+ _eventsController.add(WatchEvent(ChangeType.REMOVE, _validPath));
+ _validPath = null;
+ _setPollingDelayToShort();
+ } else if (modified != null && _lastModified == null) {
+ _eventsController.add(WatchEvent(ChangeType.ADD, _validPath));
+ _setPollingDelayToLong();
+ } else if (_lastModified != null && modified != _lastModified) {
+ _eventsController.add(WatchEvent(ChangeType.MODIFY, _validPath));
+ }
+ _lastModified = modified;
+ }
+
+ /// Tries polling all the possible paths.
+ ///
+ /// Will set [_validPath] and return its modified time if a file is found.
+ /// Returns [null] if nothing is found.
+ FileInfo _pollAll() {
+ assert(_validPath == null);
+ for (var path in _candidates) {
+ var modified = _pollOne(path);
+ if (modified != null) {
+ return FileInfo(path, modified);
+ }
+ }
+ return null;
+ }
+
+ /// Returns the modified time of the path or `null` if the file does not
+ /// exist.
+ int _pollOne(String path) {
+ try {
+ var file = _provider.getFile(path);
+ return file.modificationStamp;
+ } on FileSystemException catch (_) {
+ // File doesn't exist, so return null.
+ return null;
+ }
+ }
+
+ void _setPollingDelayToLong() {
+ _timer?.cancel();
+ _timer = _timerFactory(_pollingDelayLong, (_) => _poll());
+ }
+
+ void _setPollingDelayToShort() {
+ _timer?.cancel();
+ _timer = _timerFactory(_pollingDelayShort, (_) => _poll());
+ }
+}
+
/// The [UriResolver] that can resolve `package` URIs in [BazelWorkspace].
class BazelPackageUriResolver extends UriResolver {
final BazelWorkspace _workspace;
@@ -189,9 +359,17 @@
/// The absolute path to the `bazel-genfiles` folder.
final String genfiles;
+ final _bazelCandidateFiles =
+ StreamController<BazelFileNotification>.broadcast();
+
BazelWorkspace._(
this.provider, this.root, this.readonly, this.binPaths, this.genfiles);
+ /// Stream of files that we tried to find along with their potential or actual
+ /// paths.
+ Stream<BazelFileNotification> get bazelCandidateFiles =>
+ _bazelCandidateFiles.stream;
+
@override
bool get isBazel => true;
@@ -224,17 +402,16 @@
if (relative == '.') {
return null;
}
- // genfiles
- if (genfiles != null) {
- File file = provider.getFile(context.join(genfiles, relative));
+ // First check genfiles and bin directories
+ var generatedCandidates = <String>[
+ if (genfiles != null) genfiles,
+ ...?binPaths
+ ].map((prefix) => context.join(prefix, relative));
+ for (var path in generatedCandidates) {
+ File file = provider.getFile(path);
if (file.exists) {
- return file;
- }
- }
- // bin
- for (String bin in binPaths) {
- File file = provider.getFile(context.join(bin, relative));
- if (file.exists) {
+ _bazelCandidateFiles.add(BazelFileNotification(
+ relative, generatedCandidates.toList(), provider));
return file;
}
}
@@ -250,6 +427,10 @@
return file;
}
}
+ // If we couldn't find the file, assume that it has not yet been
+ // generated, so send an event with all the paths that we tried.
+ _bazelCandidateFiles.add(BazelFileNotification(
+ relative, generatedCandidates.toList(), provider));
// Not generated, return the default one.
return writableFile;
} catch (_) {
@@ -613,3 +794,9 @@
}
}
}
+
+class FileInfo {
+ String path;
+ int modified;
+ FileInfo(this.path, this.modified);
+}
diff --git a/pkg/analyzer/pubspec.yaml b/pkg/analyzer/pubspec.yaml
index c14bbd5..7c09d2c 100644
--- a/pkg/analyzer/pubspec.yaml
+++ b/pkg/analyzer/pubspec.yaml
@@ -24,6 +24,7 @@
dev_dependencies:
analyzer_utilities:
path: ../analyzer_utilities
+ async: ^2.0.0
linter: any
matcher: ^0.12.3
pedantic: ^1.9.0
diff --git a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
index d07c1ac..ee34035 100644
--- a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
@@ -13,6 +13,9 @@
defineReflectiveSuite(() {
defineReflectiveTests(AssignmentDriverResolutionTest);
defineReflectiveTests(AssignmentDriverResolutionWithNullSafetyTest);
+ defineReflectiveTests(
+ AssignmentDriverResolutionWithNonFunctionTypeAliasesTest,
+ );
});
}
@@ -2572,6 +2575,49 @@
}
@reflectiveTest
+class AssignmentDriverResolutionWithNonFunctionTypeAliasesTest
+ extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+ test_prefixedIdentifier_typeAlias_static_compound() async {
+ await assertNoErrorsInCode(r'''
+class A {
+ static int get x => 0;
+ static set x(int _) {}
+}
+
+typedef B = A;
+
+void f() {
+ B.x += 2;
+}
+''');
+
+ var assignment = findNode.assignment('x += 2');
+ assertAssignment(
+ assignment,
+ readElement: findElement.getter('x'),
+ readType: 'int',
+ writeElement: findElement.setter('x'),
+ writeType: 'int',
+ operatorElement: elementMatcher(
+ numElement.getMethod('+'),
+ isLegacy: isNullSafetySdkAndLegacyLibrary,
+ ),
+ type: 'int',
+ );
+
+ var prefixed = assignment.leftHandSide as PrefixedIdentifier;
+ assertSimpleIdentifierAssignmentTarget(
+ prefixed.identifier,
+ readElement: null,
+ writeElement: null,
+ type: 'dynamic',
+ );
+
+ assertType(assignment.rightHandSide, 'int');
+ }
+}
+
+@reflectiveTest
class AssignmentDriverResolutionWithNullSafetyTest
extends PubPackageResolutionTest
with WithNullSafetyMixin, AssignmentDriverResolutionTestCases {}
diff --git a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
index bdd82f2..4421117e 100644
--- a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
@@ -15,6 +15,9 @@
defineReflectiveSuite(() {
defineReflectiveTests(MethodInvocationResolutionTest);
defineReflectiveTests(MethodInvocationResolutionWithNullSafetyTest);
+ defineReflectiveTests(
+ MethodInvocationResolutionWithNonFunctionTypeAliasesTest,
+ );
});
}
@@ -2342,6 +2345,60 @@
}
@reflectiveTest
+class MethodInvocationResolutionWithNonFunctionTypeAliasesTest
+ extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+ test_hasReceiver_typeAlias_staticMethod() async {
+ await assertNoErrorsInCode(r'''
+class A {
+ static void foo(int _) {}
+}
+
+typedef B = A;
+
+void f() {
+ B.foo(0);
+}
+''');
+
+ assertMethodInvocation(
+ findNode.methodInvocation('foo(0)'),
+ findElement.method('foo'),
+ 'void Function(int)',
+ );
+
+ assertTypeAliasRef(
+ findNode.simple('B.foo'),
+ findElement.typeAlias('B'),
+ );
+ }
+
+ test_hasReceiver_typeAlias_staticMethod_generic() async {
+ await assertNoErrorsInCode(r'''
+class A<T> {
+ static void foo(int _) {}
+}
+
+typedef B<T> = A<T>;
+
+void f() {
+ B.foo(0);
+}
+''');
+
+ assertMethodInvocation(
+ findNode.methodInvocation('foo(0)'),
+ findElement.method('foo'),
+ 'void Function(int)',
+ );
+
+ assertTypeAliasRef(
+ findNode.simple('B.foo'),
+ findElement.typeAlias('B'),
+ );
+ }
+}
+
+@reflectiveTest
class MethodInvocationResolutionWithNullSafetyTest
extends PubPackageResolutionTest
with WithNullSafetyMixin, MethodInvocationResolutionTestCases {
diff --git a/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart b/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart
index 50f5527..b952a96 100644
--- a/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart
@@ -12,6 +12,9 @@
defineReflectiveSuite(() {
defineReflectiveTests(PrefixedIdentifierResolutionTest);
defineReflectiveTests(PrefixedIdentifierResolutionWithNullSafetyTest);
+ defineReflectiveTests(
+ PrefixedIdentifierResolutionWithNonFunctionTypeAliasesTest,
+ );
});
}
@@ -271,6 +274,42 @@
}
@reflectiveTest
+class PrefixedIdentifierResolutionWithNonFunctionTypeAliasesTest
+ extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+ test_hasReceiver_typeAlias_staticGetter() async {
+ await assertNoErrorsInCode(r'''
+class A {
+ static int get foo => 0;
+}
+
+typedef B = A;
+
+void f() {
+ B.foo;
+}
+''');
+
+ assertPrefixedIdentifier(
+ findNode.prefixed('B.foo'),
+ element: findElement.getter('foo'),
+ type: 'int',
+ );
+
+ assertTypeAliasRef(
+ findNode.simple('B.foo'),
+ findElement.typeAlias('B'),
+ );
+
+ assertSimpleIdentifier(
+ findNode.simple('foo;'),
+ readElement: findElement.getter('foo'),
+ writeElement: null,
+ type: 'int',
+ );
+ }
+}
+
+@reflectiveTest
class PrefixedIdentifierResolutionWithNullSafetyTest
extends PrefixedIdentifierResolutionTest with WithNullSafetyMixin {
test_deferredImportPrefix_loadLibrary_optIn_fromOptOut() async {
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index 31bed01..3b735c3 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -760,6 +760,15 @@
}
}
+ /// Assert that the given [identifier] is a reference to a type alias, in the
+ /// form that is not a separate expression, e.g. in a static method
+ /// invocation like `C.staticMethod()`, or a type annotation `C c = null`.
+ void assertTypeAliasRef(
+ SimpleIdentifier identifier, TypeAliasElement expected) {
+ assertElement(identifier, expected);
+ assertTypeNull(identifier);
+ }
+
void assertTypeArgumentTypes(
InvocationExpression node,
List<String> expected,
diff --git a/pkg/analyzer/test/src/diagnostics/type_alias_cannot_reference_itself_test.dart b/pkg/analyzer/test/src/diagnostics/type_alias_cannot_reference_itself_test.dart
index af8a61a..4c9ceb3 100644
--- a/pkg/analyzer/test/src/diagnostics/type_alias_cannot_reference_itself_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/type_alias_cannot_reference_itself_test.dart
@@ -10,11 +10,22 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(TypeAliasCannotReferenceItselfTest);
+ defineReflectiveTests(
+ TypeAliasCannotReferenceItselfWithNonFunctionTypeAliasesTest,
+ );
});
}
@reflectiveTest
class TypeAliasCannotReferenceItselfTest extends PubPackageResolutionTest {
+ test_functionTypeAlias_typeParameterBounds() async {
+ await assertErrorsInCode('''
+typedef A<T extends A<int>>();
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 30),
+ ]);
+ }
+
test_functionTypedParameter_returnType() async {
await assertErrorsInCode('''
typedef A(A b());
@@ -37,6 +48,14 @@
]);
}
+ test_genericTypeAlias_typeParameterBounds() async {
+ await assertErrorsInCode('''
+typedef A<T extends A<int>> = void Function();
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 46),
+ ]);
+ }
+
test_infiniteParameterBoundCycle() async {
await assertErrorsInCode(r'''
typedef F<X extends F<X>> = F Function();
@@ -141,12 +160,58 @@
error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 15, 14),
]);
}
+}
- test_typeVariableBounds() async {
+@reflectiveTest
+class TypeAliasCannotReferenceItselfWithNonFunctionTypeAliasesTest
+ extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+ test_nonFunction_aliasedType_cycleOf2() async {
await assertErrorsInCode('''
-typedef A<T extends A<int>>();
+typedef T1 = T2;
+typedef T2 = T1;
''', [
- error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 30),
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 16),
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 17, 16),
+ ]);
+ }
+
+ test_nonFunction_aliasedType_directly_functionWithIt() async {
+ await assertErrorsInCode('''
+typedef T = void Function(T);
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 29),
+ ]);
+ }
+
+ test_nonFunction_aliasedType_directly_it_none() async {
+ await assertErrorsInCode('''
+typedef T = T;
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 14),
+ ]);
+ }
+
+ test_nonFunction_aliasedType_directly_it_question() async {
+ await assertErrorsInCode('''
+typedef T = T?;
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 15),
+ ]);
+ }
+
+ test_nonFunction_aliasedType_directly_ListOfIt() async {
+ await assertErrorsInCode('''
+typedef T = List<T>;
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 20),
+ ]);
+ }
+
+ test_nonFunction_typeParameterBounds() async {
+ await assertErrorsInCode('''
+typedef T<X extends T<Never>> = List<X>;
+''', [
+ error(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, 0, 40),
]);
}
}
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 7ff0b3a..f3e7722 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -10690,7 +10690,7 @@
typedef F<T extends F> = void Function();
''');
checkElementText(library, r'''
-notSimplyBounded typedef F<T extends void Function()> = void Function();
+notSimplyBounded typedef F<T extends dynamic Function()> = void Function();
''');
}
diff --git a/pkg/analyzer/test/src/workspace/bazel_test.dart b/pkg/analyzer/test/src/workspace/bazel_test.dart
index 2129789..f64cfc9 100644
--- a/pkg/analyzer/test/src/workspace/bazel_test.dart
+++ b/pkg/analyzer/test/src/workspace/bazel_test.dart
@@ -2,13 +2,17 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
+
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer/src/workspace/bazel.dart';
+import 'package:async/async.dart';
import 'package:meta/meta.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import 'package:watcher/watcher.dart';
import '../../generated/test_support.dart';
@@ -766,6 +770,152 @@
class BazelWorkspaceTest with ResourceProviderMixin {
BazelWorkspace workspace;
+ void test_bazelFileWatcher() async {
+ _addResources([
+ '/workspace/WORKSPACE',
+ ]);
+ _MockTimer timer;
+ var timerFactory = (Duration _, void Function(Timer) callback) {
+ timer = _MockTimer(callback);
+ return timer;
+ };
+ var candidates = [
+ convertPath('/workspace/bazel-bin/my/module/test1.dart'),
+ convertPath('/workspace/bazel-genfiles/my/module/test1.dart'),
+ ];
+ var watcher = BazelFileWatcher(candidates, resourceProvider, Duration.zero,
+ Duration.zero, timerFactory);
+ var events = StreamQueue(watcher.events);
+ watcher.start();
+
+ // First do some tests with the first candidate path.
+ _addResources([candidates[0]]);
+ timer.triggerCallback();
+
+ var event = await events.next;
+ expect(event.type, ChangeType.ADD);
+ expect(event.path, candidates[0]);
+
+ modifyFile(candidates[0], 'const foo = 42;');
+ timer.triggerCallback();
+
+ event = await events.next;
+ expect(event.type, ChangeType.MODIFY);
+ expect(event.path, candidates[0]);
+
+ _deleteResources([candidates[0]]);
+ timer.triggerCallback();
+
+ event = await events.next;
+ expect(event.type, ChangeType.REMOVE);
+ expect(event.path, candidates[0]);
+
+ // Now check that if we add the *second* candidate, we'll get the
+ // notification for it.
+ _addResources([candidates[1]]);
+ timer.triggerCallback();
+
+ event = await events.next;
+ expect(event.type, ChangeType.ADD);
+ expect(event.path, candidates[1]);
+
+ watcher.stop();
+ expect(await events.rest.isEmpty, true);
+ }
+
+ void test_bazelFileWatcher_existingFile() async {
+ _addResources([
+ '/workspace/WORKSPACE',
+ '/workspace/bazel-bin/my/module/test1.dart',
+ ]);
+ BazelWorkspace workspace = BazelWorkspace.find(
+ resourceProvider, convertPath('/workspace/my/module'));
+ _MockTimer timer;
+ var timerFactory = (Duration _, void Function(Timer) callback) {
+ timer = _MockTimer(callback);
+ return timer;
+ };
+ var watcherCompleter = Completer<BazelFileWatcher>();
+ workspace.bazelCandidateFiles.listen((notification) =>
+ watcherCompleter.complete(notification.watcher(
+ pollingDelayLong: Duration.zero,
+ pollingDelayShort: Duration.zero,
+ timerFactory: timerFactory)));
+
+ var file1 =
+ workspace.findFile(convertPath('/workspace/my/module/test1.dart'));
+ expect(file1.exists, true);
+ var watcher = await watcherCompleter.future;
+ var events = StreamQueue(watcher.events);
+ watcher.start();
+
+ // Make sure that triggering the callback, will not generate extra events.
+ timer.triggerCallback();
+
+ var convertedPath =
+ convertPath('/workspace/bazel-bin/my/module/test1.dart');
+
+ // Change the file -- we should get a single MODIFY event and not an ADD
+ // event, since the file already existed.
+ modifyFile(convertedPath, 'const foo = 42;');
+ timer.triggerCallback();
+ var event = await events.next;
+
+ expect(event.type, ChangeType.MODIFY);
+ expect(event.path, convertedPath);
+
+ // But if we delete the file and then re-create it, we should get an ADD
+ // event (after the REMOVE one).
+ deleteFile(convertedPath);
+ timer.triggerCallback();
+ event = await events.next;
+
+ expect(event.type, ChangeType.REMOVE);
+ expect(event.path, convertedPath);
+
+ newFile(convertedPath);
+ timer.triggerCallback();
+ event = await events.next;
+
+ expect(event.type, ChangeType.ADD);
+ expect(event.path, convertedPath);
+
+ watcher.stop();
+ expect(await events.rest.isEmpty, true);
+ }
+
+ void test_bazelNotifications() async {
+ _addResources([
+ '/workspace/WORKSPACE',
+ '/workspace/bazel-bin/my/module/test1.dart',
+ ]);
+ BazelWorkspace workspace = BazelWorkspace.find(
+ resourceProvider, convertPath('/workspace/my/module'));
+ var notifications = StreamQueue(workspace.bazelCandidateFiles);
+
+ var file1 =
+ workspace.findFile(convertPath('/workspace/my/module/test1.dart'));
+ expect(file1.exists, true);
+ var notification = await notifications.next;
+ expect(notification.requested, convertPath('my/module/test1.dart'));
+ expect(
+ notification.candidates,
+ containsAll(
+ [convertPath('/workspace/bazel-bin/my/module/test1.dart')]));
+
+ var file2 =
+ workspace.findFile(convertPath('/workspace/my/module/test2.dart'));
+ expect(file2.exists, false);
+ notification = await notifications.next;
+ expect(notification.requested, convertPath('my/module/test2.dart'));
+ expect(
+ notification.candidates,
+ containsAll([
+ convertPath('/workspace/bazel-bin/my/module/test2.dart'),
+ convertPath('/workspace/bazel-genfiles/my/module/test2.dart'),
+ ]));
+ }
+
void test_find_fail_notAbsolute() {
expect(
() =>
@@ -1015,6 +1165,17 @@
}
}
+ /// Create new files and directories from [paths].
+ void _deleteResources(List<String> paths) {
+ for (String path in paths) {
+ if (path.endsWith('/')) {
+ deleteFolder(path.substring(0, path.length - 1));
+ } else {
+ deleteFile(path);
+ }
+ }
+ }
+
/// Expect that [BazelWorkspace.findFile], given [path], returns [equals].
void _expectFindFile(String path, {@required String equals}) =>
expect(workspace.findFile(convertPath(path)).path, convertPath(equals));
@@ -1031,3 +1192,20 @@
throw StateError('Unexpected invocation of ${invocation.memberName}');
}
}
+
+class _MockTimer implements Timer {
+ final void Function(Timer) callback;
+
+ @override
+ bool isActive = true;
+
+ _MockTimer(this.callback);
+
+ @override
+ int get tick => throw UnimplementedError();
+
+ @override
+ void cancel() => isActive = false;
+
+ void triggerCallback() => callback(this);
+}
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 8800d4c..87fed8f 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -5530,27 +5530,6 @@
js_ast.Expression visitAsExpression(AsExpression node) {
var fromExpr = node.operand;
var jsFrom = _visitExpression(fromExpr);
-
- // The `_EventStreamSubscription.cancel()` method dart:html returns null in
- // weak mode. This causes unwanted warnings/failures when you turn on the
- // weak mode warnings/errors so we remove these specific runtime casts.
- // TODO(44157) Remove this workaround once it returns a consistent type.
- if (_isWebLibrary(currentLibraryUri) && node.parent is ReturnStatement) {
- var parent = node.parent;
- while (parent != null && parent is! FunctionNode) {
- parent = parent?.parent;
- }
- parent = parent?.parent;
- if (parent is Procedure) {
- if (parent.enclosingClass != null &&
- parent.enclosingClass.name == '_EventStreamSubscription' &&
- parent.name.name == 'cancel') {
- // Ignore these casts and just emit the expression.
- return jsFrom;
- }
- }
- }
-
var to = node.type;
var from = fromExpr.getStaticType(_staticTypeContext);
diff --git a/pkg/dev_compiler/tool/dart2js_nnbd_sdk_error_golden.txt b/pkg/dev_compiler/tool/dart2js_nnbd_sdk_error_golden.txt
index d85029a..22d60a1 100644
--- a/pkg/dev_compiler/tool/dart2js_nnbd_sdk_error_golden.txt
+++ b/pkg/dev_compiler/tool/dart2js_nnbd_sdk_error_golden.txt
@@ -1,12 +1,12 @@
-ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '&': JSNumber.& (num Function(num)), int.& (int Function(int)).
-ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '<<': JSNumber.<< (num Function(num)), int.<< (int Function(int)).
-ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '>>': JSNumber.>> (num Function(num)), int.>> (int Function(int)).
-ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '\|': JSNumber.\| (num Function(num)), int.\| (int Function(int)).
-ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '^': JSNumber.^ (num Function(num)), int.^ (int Function(int)).
-ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1561|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
-ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1563|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
-ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1723|28|1|The operator '&' isn't defined for the type 'JSInt'.
-ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1725|27|1|The operator '&' isn't defined for the type 'JSInt'.
-ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1728|17|1|The operator '&' isn't defined for the type 'JSInt'.
-ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1733|18|1|The operator '&' isn't defined for the type 'JSInt'.
-ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1733|44|1|The operator '&' isn't defined for the type 'JSInt'.
+ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1718|7|5|Superinterfaces don't have a valid override for '&': JSNumber.& (num Function(num)), int.& (int Function(int)).
+ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1718|7|5|Superinterfaces don't have a valid override for '<<': JSNumber.<< (num Function(num)), int.<< (int Function(int)).
+ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1718|7|5|Superinterfaces don't have a valid override for '>>': JSNumber.>> (num Function(num)), int.>> (int Function(int)).
+ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1718|7|5|Superinterfaces don't have a valid override for '\|': JSNumber.\| (num Function(num)), int.\| (int Function(int)).
+ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1718|7|5|Superinterfaces don't have a valid override for '^': JSNumber.^ (num Function(num)), int.^ (int Function(int)).
+ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1573|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
+ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1575|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
+ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1735|28|1|The operator '&' isn't defined for the type 'JSInt'.
+ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1737|27|1|The operator '&' isn't defined for the type 'JSInt'.
+ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1740|17|1|The operator '&' isn't defined for the type 'JSInt'.
+ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1745|18|1|The operator '&' isn't defined for the type 'JSInt'.
+ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1745|44|1|The operator '&' isn't defined for the type 'JSInt'.
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 3a41006..85286cb 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -634,11 +634,7 @@
char* error = nullptr;
auto group = state_->isolate_group();
-#if defined(DART_PRECOMPILED_RUNTIME)
- Isolate* isolate = CreateWithinExistingIsolateGroupAOT(group, name, &error);
-#else
Isolate* isolate = CreateWithinExistingIsolateGroup(group, name, &error);
-#endif
parent_isolate_->DecrementSpawnCount();
parent_isolate_ = nullptr;
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index 9f63a72..7100f1a 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -687,28 +687,17 @@
return isolate;
}
-#if defined(DART_PRECOMPILED_RUNTIME)
-static bool CloneIntoChildIsolateAOT(Thread* T,
- Isolate* I,
- IsolateGroup* source_isolate_group) {
- // In AOT we speed up isolate spawning by copying donor's isolate structure.
- if (source_isolate_group == nullptr) {
- return false;
- }
- I->isolate_object_store()->Init();
- I->isolate_object_store()->PreallocateObjects();
- I->set_field_table(T, source_isolate_group->initial_field_table()->Clone(I));
-
- return true;
-}
-#endif
-
ErrorPtr Dart::InitIsolateFromSnapshot(Thread* T,
Isolate* I,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions,
const uint8_t* kernel_buffer,
intptr_t kernel_buffer_size) {
+ if (kernel_buffer != nullptr) {
+ SafepointReadRwLocker reader(T, I->group()->program_lock());
+ I->field_table()->MarkReadyToUse();
+ }
+
Error& error = Error::Handle(T->zone());
error = Object::Init(I, kernel_buffer, kernel_buffer_size);
if (!error.IsNull()) {
@@ -742,7 +731,11 @@
return error.raw();
}
- I->set_field_table(T, I->group()->initial_field_table()->Clone(I));
+ {
+ SafepointReadRwLocker reader(T, I->group()->program_lock());
+ I->set_field_table(T, I->group()->initial_field_table()->Clone(I));
+ I->field_table()->MarkReadyToUse();
+ }
#if defined(SUPPORT_TIMELINE)
if (tbes.enabled()) {
@@ -883,37 +876,58 @@
StackZone zone(T);
HandleScope handle_scope(T);
bool was_child_cloned_into_existing_isolate = false;
-#if defined(DART_PRECOMPILED_RUNTIME)
- if (CloneIntoChildIsolateAOT(T, I, source_isolate_group)) {
+ if (source_isolate_group != nullptr) {
+ I->isolate_object_store()->Init();
+ I->isolate_object_store()->PreallocateObjects();
+
+ // If a static field gets registered in [IsolateGroup::RegisterStaticField]:
+ //
+ // * before this block it will ignore this isolate. The [Clone] of the
+ // initial field table will pick up the new value.
+ // * after this block it will add the new static field to this isolate.
+ {
+ SafepointReadRwLocker reader(T, source_isolate_group->program_lock());
+ I->set_field_table(T,
+ source_isolate_group->initial_field_table()->Clone(I));
+ I->field_table()->MarkReadyToUse();
+ }
+
was_child_cloned_into_existing_isolate = true;
} else {
-#endif
const Error& error = Error::Handle(
InitIsolateFromSnapshot(T, I, snapshot_data, snapshot_instructions,
kernel_buffer, kernel_buffer_size));
if (!error.IsNull()) {
return error.raw();
}
-#if defined(DART_PRECOMPILED_RUNTIME)
}
-#endif
Object::VerifyBuiltinVtables();
DEBUG_ONLY(I->heap()->Verify(kForbidMarked));
#if defined(DART_PRECOMPILED_RUNTIME)
- ASSERT(I->object_store()->build_method_extractor_code() != Code::null());
- if (FLAG_print_llvm_constant_pool) {
- PrintLLVMConstantPool(T, I);
- }
+ const bool kIsAotRuntime = true;
#else
-#if !defined(TARGET_ARCH_IA32)
- if (I != Dart::vm_isolate()) {
- I->object_store()->set_build_method_extractor_code(
- Code::Handle(StubCode::GetBuildMethodExtractorStub(nullptr)));
- }
+ const bool kIsAotRuntime = false;
#endif
+
+ if (kIsAotRuntime || was_child_cloned_into_existing_isolate) {
+#if !defined(TARGET_ARCH_IA32)
+ ASSERT(I->object_store()->build_method_extractor_code() != Code::null());
+#endif
+#if defined(DART_PRECOMPILED_RUNTIME)
+ if (FLAG_print_llvm_constant_pool) {
+ PrintLLVMConstantPool(T, I);
+ }
#endif // defined(DART_PRECOMPILED_RUNTIME)
+ } else {
+#if !defined(TARGET_ARCH_IA32)
+ if (I != Dart::vm_isolate()) {
+ I->object_store()->set_build_method_extractor_code(
+ Code::Handle(StubCode::GetBuildMethodExtractorStub(nullptr)));
+ }
+#endif // !defined(TARGET_ARCH_IA32)
+ }
I->set_ic_miss_code(StubCode::SwitchableCallMiss());
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 92fa101..60f008d 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1308,10 +1308,9 @@
return false;
}
-Isolate* CreateWithinExistingIsolateGroupAOT(IsolateGroup* group,
- const char* name,
- char** error) {
-#if defined(DART_PRECOMPILED_RUNTIME)
+Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
+ const char* name,
+ char** error) {
API_TIMELINE_DURATION(Thread::Current());
CHECK_NO_ISOLATE(Isolate::Current());
@@ -1326,171 +1325,6 @@
ASSERT(isolate->source() == source);
return isolate;
-#else
- UNREACHABLE();
-#endif
-}
-
-Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
- const char* name,
- char** error) {
-#if !defined(DART_PRECOMPILED_RUNTIME)
- API_TIMELINE_DURATION(Thread::Current());
- CHECK_NO_ISOLATE(Isolate::Current());
-
- // During isolate start we'll make a temporary anonymous group from the same
- // [source]. Once the isolate has been fully loaded we will merge it's heap
- // into the shared heap.
- auto spawning_group = new IsolateGroup(group->shareable_source(),
- /*isolate_group_data=*/nullptr);
- IsolateGroup::RegisterIsolateGroup(spawning_group);
- spawning_group->CreateHeap(
- /*is_vm_isolate=*/false,
- IsServiceOrKernelIsolateName(group->source()->name));
-
- Isolate* isolate = reinterpret_cast<Isolate*>(
- CreateIsolate(spawning_group, /*is_new_group=*/false, name,
- /*isolate_data=*/nullptr, error));
- if (isolate == nullptr) return nullptr;
-
- auto source = spawning_group->source();
- ASSERT(isolate->source() == source);
-
- if (source->script_kernel_buffer != nullptr) {
- Dart_EnterScope();
- {
- Thread* T = Thread::Current();
- TransitionNativeToVM transition(T);
- HANDLESCOPE(T);
- StackZone zone(T);
-
- // NOTE: We do not attach a finalizer for this object, because the
- // embedder will free it once the isolate group has shutdown.
- const auto& td = ExternalTypedData::Handle(ExternalTypedData::New(
- kExternalTypedDataUint8ArrayCid,
- const_cast<uint8_t*>(source->script_kernel_buffer),
- source->script_kernel_size, Heap::kOld));
-
- std::unique_ptr<kernel::Program> program =
- kernel::Program::ReadFromTypedData(td,
- const_cast<const char**>(error));
- if (program == nullptr) {
- UNIMPLEMENTED();
- }
- const Object& tmp =
- kernel::KernelLoader::LoadEntireProgram(program.get());
-
- // If the existing isolate could spawn with a root library we should be
- // able to do the same
- RELEASE_ASSERT(!tmp.IsNull() && tmp.IsLibrary());
- isolate->object_store()->set_root_library(Library::Cast(tmp));
- }
- Dart_ExitScope();
- }
-
- // If we are running in AppJIT training mode we'll have to remap class ids.
- if (auto permutation_map = group->source()->cid_permutation_map.get()) {
- Dart_EnterScope();
- {
- auto T = Thread::Current();
- TransitionNativeToVM transition(T);
- HANDLESCOPE(T);
-
- // Remap all class ids loaded atm (e.g. from snapshot) and do appropriate
- // re-hashing of constants and types.
- ClassFinalizer::RemapClassIds(permutation_map);
- // Types use cid's as part of their hashes.
- ClassFinalizer::RehashTypes();
- // Const objects use cid's as part of their hashes.
- isolate->RehashConstants();
- }
- Dart_ExitScope();
- }
-
- auto thread = Thread::Current();
- {
- TransitionNativeToVM native_to_vm(thread);
-
- // Ensure there are no helper threads running.
- BackgroundCompiler::Stop(isolate);
- isolate->heap()->WaitForMarkerTasks(thread);
- isolate->heap()->WaitForSweeperTasks(thread);
- SafepointOperationScope safepoint_operation(thread);
- isolate->group()->ReleaseStoreBuffers();
- RELEASE_ASSERT(isolate->heap()->old_space()->tasks() == 0);
- }
-
- Dart_ExitIsolate();
- {
- const bool kBypassSafepoint = false;
- Thread::EnterIsolateGroupAsHelper(group, Thread::kUnknownTask,
- kBypassSafepoint);
- ASSERT(group == IsolateGroup::Current());
-
- {
- auto thread = Thread::Current();
-
- // Prevent additions of new isolates to [group] until we're done.
- group->RunWithLockedGroup([&]() {
- // Ensure no other old space GC tasks are running and "occupy" the old
- // space.
- SafepointOperationScope safepoint_scope(thread);
- {
- auto old_space = group->heap()->old_space();
- MonitorLocker ml(old_space->tasks_lock());
- while (old_space->tasks() > 0) {
- ml.Wait();
- }
- old_space->set_tasks(1);
- }
-
- // Merge the heap from [spawning_group] to [group].
- group->heap()->MergeFrom(isolate->group()->heap());
-
- spawning_group->UnregisterIsolate(isolate);
- const bool shutdown_group =
- spawning_group->UnregisterIsolateDecrementCount(isolate);
- ASSERT(shutdown_group);
-
- isolate->isolate_group_ = group;
- group->RegisterIsolateLocked(isolate);
- isolate->class_table()->shared_class_table_ =
- group->shared_class_table();
- isolate->set_shared_class_table(group->shared_class_table());
-
- // Even though the mutator thread was descheduled, it will still
- // retain its [Thread] structure with valid isolate/isolate_group
- // pointers.
- // If GC happens before the mutator gets scheduled again, we have to
- // ensure the isolate group change is reflected in the threads
- // structure.
- ASSERT(isolate->mutator_thread() != nullptr);
- ASSERT(isolate->mutator_thread()->isolate_group() == spawning_group);
- isolate->mutator_thread()->isolate_group_ = group;
-
- // Allow other old space GC tasks to run again.
- {
- auto old_space = group->heap()->old_space();
- MonitorLocker ml(old_space->tasks_lock());
- ASSERT(old_space->tasks() == 1);
- old_space->set_tasks(0);
- ml.NotifyAll();
- }
- });
- }
-
- Thread::ExitIsolateGroupAsHelper(kBypassSafepoint);
- }
-
- spawning_group->Shutdown();
-
- Dart_EnterIsolate(Api::CastIsolate(isolate));
- ASSERT(Thread::Current()->isolate_group() == isolate->group());
-
- return isolate;
-#else
- UNREACHABLE();
-#endif
}
DART_EXPORT void Dart_IsolateFlagsInitialize(Dart_IsolateFlags* flags) {
@@ -1589,7 +1423,7 @@
Isolate* isolate;
#if defined(DART_PRECOMPILED_RUNTIME)
- isolate = CreateWithinExistingIsolateGroupAOT(member->group(), name, error);
+ isolate = CreateWithinExistingIsolateGroup(member->group(), name, error);
if (isolate != nullptr) {
isolate->set_origin_id(member->origin_id());
isolate->set_init_callback_data(child_isolate_data);
diff --git a/runtime/vm/dart_api_impl.h b/runtime/vm/dart_api_impl.h
index 80f0462..7c58510 100644
--- a/runtime/vm/dart_api_impl.h
+++ b/runtime/vm/dart_api_impl.h
@@ -353,9 +353,6 @@
Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
const char* name,
char** error);
-Isolate* CreateWithinExistingIsolateGroupAOT(IsolateGroup* group,
- const char* name,
- char** error);
} // namespace dart.
diff --git a/runtime/vm/field_table.cc b/runtime/vm/field_table.cc
index a6358a1..fe40d10 100644
--- a/runtime/vm/field_table.cc
+++ b/runtime/vm/field_table.cc
@@ -22,6 +22,25 @@
free(table_); // Allocated in FieldTable::Grow()
}
+bool FieldTable::IsReadyToUse() const {
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
+ return is_ready_to_use_;
+}
+
+void FieldTable::MarkReadyToUse() {
+ // The isolate will mark it's field table ready-to-use upon initialization of
+ // the isolate. Only after it was marked as ready-to-use will it participate
+ // in new static field registrations.
+ //
+ // By requiring a read lock here we ensure no other thread is is registering a
+ // new static field at this moment (it would need exlusive writer lock).
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
+ ASSERT(!is_ready_to_use_);
+ is_ready_to_use_ = true;
+}
+
void FieldTable::FreeOldTables() {
while (old_tables_->length() > 0) {
free(old_tables_->RemoveLast());
@@ -32,21 +51,27 @@
return field_id * sizeof(InstancePtr); // NOLINT
}
-void FieldTable::Register(const Field& field) {
+bool FieldTable::Register(const Field& field, intptr_t expected_field_id) {
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(Thread::Current()->IsMutatorThread());
+ ASSERT(is_ready_to_use_);
+
if (free_head_ < 0) {
+ bool grown_backing_store = false;
if (top_ == capacity_) {
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
Grow(new_capacity);
+ grown_backing_store = true;
}
ASSERT(top_ < capacity_);
-
+ ASSERT(expected_field_id == -1 || expected_field_id == top_);
field.set_field_id(top_);
table_[top_] = Object::sentinel().raw();
++top_;
- return;
+ return grown_backing_store;
}
// Reuse existing free element. This is "slow path" that should only be
@@ -55,6 +80,7 @@
free_head_ = Smi::Value(Smi::RawCast(table_[free_head_]));
field.set_field_id(reused_free);
table_[reused_free] = Object::sentinel().raw();
+ return false;
}
void FieldTable::Free(intptr_t field_id) {
@@ -104,6 +130,9 @@
}
FieldTable* FieldTable::Clone(Isolate* for_isolate) {
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
+
FieldTable* clone = new FieldTable(for_isolate);
auto new_table = static_cast<InstancePtr*>(
malloc(capacity_ * sizeof(InstancePtr))); // NOLINT
diff --git a/runtime/vm/field_table.h b/runtime/vm/field_table.h
index 4fc1ca8..4350745 100644
--- a/runtime/vm/field_table.h
+++ b/runtime/vm/field_table.h
@@ -27,10 +27,14 @@
free_head_(-1),
table_(nullptr),
old_tables_(new MallocGrowableArray<InstancePtr*>()),
- isolate_(isolate) {}
+ isolate_(isolate),
+ is_ready_to_use_(isolate == nullptr) {}
~FieldTable();
+ bool IsReadyToUse() const;
+ void MarkReadyToUse();
+
intptr_t NumFieldIds() const { return top_; }
intptr_t Capacity() const { return capacity_; }
@@ -43,7 +47,9 @@
bool IsValidIndex(intptr_t index) const { return index >= 0 && index < top_; }
- void Register(const Field& field);
+ // Returns whether registering this field caused a growth in the backing
+ // store.
+ bool Register(const Field& field, intptr_t expected_field_id = -1);
void AllocateIndex(intptr_t index);
// Static field elements are being freed only during isolate reload
@@ -89,6 +95,10 @@
// mutator thread up-to-date.
Isolate* isolate_;
+ // Whether this field table is ready to use by e.g. registering new static
+ // fields.
+ bool is_ready_to_use_ = false;
+
DISALLOW_COPY_AND_ASSIGN(FieldTable);
};
diff --git a/runtime/vm/heap/freelist.cc b/runtime/vm/heap/freelist.cc
index 2923aee..e2fd5f8 100644
--- a/runtime/vm/heap/freelist.cc
+++ b/runtime/vm/heap/freelist.cc
@@ -373,41 +373,4 @@
return NULL;
}
-void FreeList::MergeFrom(FreeList* donor, bool is_protected) {
- // The [other] free list is from a dying isolate. There are no other threads
- // accessing it, so there is no need to lock here.
- MutexLocker ml(&mutex_);
- for (intptr_t i = 0; i < (kNumLists + 1); ++i) {
- FreeListElement* donor_head = donor->free_lists_[i];
- if (donor_head != nullptr) {
- // If we didn't have a freelist element before we have to set the bit now,
- // since we will get 1+ elements from [other].
- FreeListElement* old_head = free_lists_[i];
- if (old_head == nullptr && i != kNumLists) {
- free_map_.Set(i, true);
- }
-
- // Chain other's list in.
- FreeListElement* last = donor_head;
- while (last->next() != nullptr) {
- last = last->next();
- }
-
- if (is_protected) {
- VirtualMemory::Protect(reinterpret_cast<void*>(last), sizeof(*last),
- VirtualMemory::kReadWrite);
- }
- last->set_next(old_head);
- if (is_protected) {
- VirtualMemory::Protect(reinterpret_cast<void*>(last), sizeof(*last),
- VirtualMemory::kReadExecute);
- }
- free_lists_[i] = donor_head;
- }
- }
-
- last_free_small_size_ =
- Utils::Maximum(last_free_small_size_, donor->last_free_small_size_);
-}
-
} // namespace dart
diff --git a/runtime/vm/heap/freelist.h b/runtime/vm/heap/freelist.h
index 677a0f2..b868c19 100644
--- a/runtime/vm/heap/freelist.h
+++ b/runtime/vm/heap/freelist.h
@@ -154,8 +154,6 @@
void set_end(uword value) { end_ = value; }
void AddUnaccountedSize(intptr_t size) { unaccounted_size_ += size; }
- void MergeFrom(FreeList* donor, bool is_protected);
-
private:
static const int kNumLists = 128;
static const intptr_t kInitialFreeListSearchBudget = 1000;
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index a1d5866..e5c2426 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -717,30 +717,6 @@
gc_on_nth_allocation_ = num_allocations;
}
-void Heap::MergeFrom(Heap* donor) {
- ASSERT(!donor->read_only_);
- ASSERT(donor->old_space()->tasks() == 0);
-
- new_space_.MergeFrom(donor->new_space());
- old_space_.MergeFrom(donor->old_space());
-
- for (intptr_t i = 0; i < kNumWeakSelectors; ++i) {
- // The new space rehashing should not be necessary.
- new_weak_tables_[i]->MergeFrom(donor->new_weak_tables_[i]);
- old_weak_tables_[i]->MergeFrom(donor->old_weak_tables_[i]);
- }
-
- StoreBufferBlock* block =
- donor->isolate_group()->store_buffer()->TakeBlocks();
- while (block != nullptr) {
- StoreBufferBlock* next = block->next();
- block->set_next(nullptr);
- isolate_group()->store_buffer()->PushBlock(block,
- StoreBuffer::kIgnoreThreshold);
- block = next;
- }
-}
-
void Heap::CollectForDebugging() {
if (gc_on_nth_allocation_ == kNoForcedGarbageCollection) return;
if (Thread::Current()->IsAtSafepoint()) {
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 026d793..f303a15 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -322,8 +322,6 @@
void CollectOnNthAllocation(intptr_t num_allocations);
- void MergeFrom(Heap* donor);
-
private:
class GCStats : public ValueObject {
public:
diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc
index d3b62fa..39180e8 100644
--- a/runtime/vm/heap/pages.cc
+++ b/runtime/vm/heap/pages.cc
@@ -1447,95 +1447,6 @@
return false;
}
-static void AppendList(OldPage** pages,
- OldPage** pages_tail,
- OldPage** other_pages,
- OldPage** other_pages_tail) {
- ASSERT((*pages == nullptr) == (*pages_tail == nullptr));
- ASSERT((*other_pages == nullptr) == (*other_pages_tail == nullptr));
-
- if (*other_pages != nullptr) {
- if (*pages_tail == nullptr) {
- *pages = *other_pages;
- *pages_tail = *other_pages_tail;
- } else {
- const bool is_execute = FLAG_write_protect_code &&
- (*pages_tail)->type() == OldPage::kExecutable;
- if (is_execute) {
- (*pages_tail)->WriteProtect(false);
- }
- (*pages_tail)->set_next(*other_pages);
- if (is_execute) {
- (*pages_tail)->WriteProtect(true);
- }
- *pages_tail = *other_pages_tail;
- }
- *other_pages = nullptr;
- *other_pages_tail = nullptr;
- }
-}
-
-static void EnsureEqualImagePages(OldPage* pages, OldPage* other_pages) {
-#if defined(DEBUG)
- while (pages != nullptr) {
- ASSERT((pages == nullptr) == (other_pages == nullptr));
- ASSERT(pages->object_start() == other_pages->object_start());
- ASSERT(pages->object_end() == other_pages->object_end());
- pages = pages->next();
- other_pages = other_pages->next();
- }
-#endif
-}
-
-void PageSpace::MergeFrom(PageSpace* donor) {
- donor->AbandonBumpAllocation();
-
- ASSERT(donor->tasks_ == 0);
- ASSERT(donor->concurrent_marker_tasks_ == 0);
- ASSERT(donor->phase_ == kDone);
- DEBUG_ASSERT(donor->iterating_thread_ == nullptr);
- ASSERT(donor->marker_ == nullptr);
-
- for (intptr_t i = 0; i < num_freelists_; ++i) {
- ASSERT(donor->freelists_[i].top() == 0);
- ASSERT(donor->freelists_[i].end() == 0);
- const bool is_protected =
- FLAG_write_protect_code && i == OldPage::kExecutable;
- freelists_[i].MergeFrom(&donor->freelists_[i], is_protected);
- donor->freelists_[i].Reset();
- }
-
- // The freelist locks will be taken in MergeOtherFreelist above, and the
- // locking order is the freelist locks are taken before the page list locks,
- // so don't take the pages lock until after MergeOtherFreelist.
- MutexLocker ml(&pages_lock_);
- MutexLocker ml2(&donor->pages_lock_);
-
- AppendList(&pages_, &pages_tail_, &donor->pages_, &donor->pages_tail_);
- AppendList(&exec_pages_, &exec_pages_tail_, &donor->exec_pages_,
- &donor->exec_pages_tail_);
- AppendList(&large_pages_, &large_pages_tail_, &donor->large_pages_,
- &donor->large_pages_tail_);
- // We intentionall do not merge [image_pages_] beause [this] and [other] have
- // the same mmap()ed image page areas.
- EnsureEqualImagePages(image_pages_, donor->image_pages_);
-
- // We intentionaly do not increase [max_capacity_in_words_] because this can
- // lead [max_capacity_in_words_] to become larger and larger and eventually
- // wrap-around and become negative.
- allocated_black_in_words_ += donor->allocated_black_in_words_;
- gc_time_micros_ += donor->gc_time_micros_;
- collections_ += donor->collections_;
-
- usage_.capacity_in_words += donor->usage_.capacity_in_words;
- usage_.used_in_words += donor->usage_.used_in_words;
- usage_.external_in_words += donor->usage_.external_in_words;
-
- page_space_controller_.MergeFrom(&donor->page_space_controller_);
-
- ASSERT(FLAG_concurrent_mark || donor->enable_concurrent_mark_ == false);
-}
-
PageSpaceController::PageSpaceController(Heap* heap,
int heap_growth_ratio,
int heap_growth_max,
@@ -1771,12 +1682,6 @@
// TODO(rmacnak): Hasten the soft threshold at some discount?
}
-void PageSpaceController::MergeFrom(PageSpaceController* donor) {
- last_usage_.capacity_in_words += donor->last_usage_.capacity_in_words;
- last_usage_.used_in_words += donor->last_usage_.used_in_words;
- last_usage_.external_in_words += donor->last_usage_.external_in_words;
-}
-
void PageSpaceGarbageCollectionHistory::AddGarbageCollectionTime(int64_t start,
int64_t end) {
Entry entry;
diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h
index a786f78..434ad7f 100644
--- a/runtime/vm/heap/pages.h
+++ b/runtime/vm/heap/pages.h
@@ -240,8 +240,6 @@
friend class PageSpace; // For MergeOtherPageSpaceController
void RecordUpdate(SpaceUsage before, SpaceUsage after, const char* reason);
- void MergeFrom(PageSpaceController* donor);
-
void RecordUpdate(SpaceUsage before,
SpaceUsage after,
intptr_t growth_in_pages,
@@ -489,8 +487,6 @@
bool IsObjectFromImagePages(ObjectPtr object);
- void MergeFrom(PageSpace* donor);
-
private:
// Ids for time and data records in Heap::GCStats.
enum {
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index 3f6f09d..f22ab54 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -715,19 +715,6 @@
tail_ = tail;
}
-void SemiSpace::MergeFrom(SemiSpace* donor) {
- for (NewPage* page = donor->head_; page != nullptr; page = page->next()) {
- page->Release();
- }
-
- AddList(donor->head_, donor->tail_);
- capacity_in_words_ += donor->capacity_in_words_;
-
- donor->head_ = nullptr;
- donor->tail_ = nullptr;
- donor->capacity_in_words_ = 0;
-}
-
// The initial estimate of how many words we can scavenge per microsecond (usage
// before / scavenge time). This is a conservative value observed running
// Flutter on a Nexus 4. After the first scavenge, we instead use a value based
@@ -1721,13 +1708,4 @@
ASSERT((UsedInWords() == 0) || failed_to_promote_);
}
-void Scavenger::MergeFrom(Scavenger* donor) {
- MutexLocker ml(&space_lock_);
- MutexLocker ml2(&donor->space_lock_);
- to_->MergeFrom(donor->to_);
-
- external_size_ += donor->external_size_;
- donor->external_size_ = 0;
-}
-
} // namespace dart
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index 5e7486b..785825e 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -184,7 +184,6 @@
NewPage* head() const { return head_; }
void AddList(NewPage* head, NewPage* tail);
- void MergeFrom(SemiSpace* donor);
private:
// Size of NewPages in this semi-space.
@@ -281,8 +280,6 @@
// Promote all live objects.
void Evacuate();
- void MergeFrom(Scavenger* donor);
-
int64_t UsedInWords() const {
MutexLocker ml(&space_lock_);
return to_->capacity_in_words();
diff --git a/runtime/vm/heap/weak_table.cc b/runtime/vm/heap/weak_table.cc
index fdb0a7d..4070590 100644
--- a/runtime/vm/heap/weak_table.cc
+++ b/runtime/vm/heap/weak_table.cc
@@ -140,12 +140,4 @@
free(old_data);
}
-void WeakTable::MergeFrom(WeakTable* donor) {
- for (intptr_t i = 0; i < donor->size(); i++) {
- if (donor->IsValidEntryAtExclusive(i)) {
- SetValueExclusive(donor->ObjectAtExclusive(i), ValueIndex(i));
- }
- }
-}
-
} // namespace dart
diff --git a/runtime/vm/heap/weak_table.h b/runtime/vm/heap/weak_table.h
index c7728b0..c72d773 100644
--- a/runtime/vm/heap/weak_table.h
+++ b/runtime/vm/heap/weak_table.h
@@ -133,8 +133,6 @@
void Reset();
- void MergeFrom(WeakTable* donor);
-
private:
enum {
kObjectOffset = 0,
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 4500d86..7f229b7 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -345,11 +345,7 @@
safepoint_handler_(new SafepointHandler(this)),
shared_class_table_(new SharedClassTable()),
object_store_(object_store),
-#if defined(DART_PRECOMPILED_RUNTIME)
class_table_(new ClassTable(shared_class_table_.get())),
-#else
- class_table_(nullptr),
-#endif
store_buffer_(new StoreBuffer()),
heap_(nullptr),
saved_unlinked_calls_(Array::null()),
@@ -393,14 +389,7 @@
IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
void* embedder_data)
- : IsolateGroup(source,
- embedder_data,
-#if !defined(DART_PRECOMPILED_RUNTIME)
- // in JIT, with --enable_isolate_groups keep object store
- // on isolate, rather than on isolate group
- FLAG_enable_isolate_groups ? nullptr :
-#endif
- new ObjectStore()) {
+ : IsolateGroup(source, embedder_data, new ObjectStore()) {
if (object_store() != nullptr) {
object_store()->InitStubs();
}
@@ -908,15 +897,31 @@
ASSERT(program_lock()->IsCurrentThreadWriter());
ASSERT(field.is_static());
- initial_field_table()->Register(field);
- initial_field_table()->SetAt(field.field_id(), initial_value.raw());
+ const bool need_to_grow_backing_store =
+ initial_field_table()->Register(field);
+ const intptr_t field_id = field.field_id();
+ initial_field_table()->SetAt(field_id, initial_value.raw());
- // TODO(dartbug.com/36097): When we start sharing the object stores (and
- // therefore libraries, classes, fields) we'll have to register the initial
- // static field value in all isolates.
- auto current = Isolate::Current();
- current->field_table()->AllocateIndex(field.field_id());
- current->field_table()->SetAt(field.field_id(), initial_value.raw());
+ if (need_to_grow_backing_store) {
+ // We have to stop other isolates from accessing their field state, since
+ // we'll have to grow the backing store.
+ SafepointOperationScope ops(Thread::Current());
+ for (auto isolate : isolates_) {
+ auto field_table = isolate->field_table();
+ if (field_table->IsReadyToUse()) {
+ field_table->Register(field, field_id);
+ field_table->SetAt(field_id, initial_value.raw());
+ }
+ }
+ } else {
+ for (auto isolate : isolates_) {
+ auto field_table = isolate->field_table();
+ if (field_table->IsReadyToUse()) {
+ field_table->Register(field, field_id);
+ field_table->SetAt(field_id, initial_value.raw());
+ }
+ }
+ }
}
void Isolate::RehashConstants() {
@@ -1636,11 +1641,7 @@
isolate_object_store_(
new IsolateObjectStore(isolate_group->object_store())),
object_store_shared_ptr_(isolate_group->object_store_shared_ptr()),
-#if defined(DART_PRECOMPILED_RUNTIME)
class_table_(isolate_group->class_table_shared_ptr()),
-#else
- class_table_(new ClassTable(shared_class_table_)),
-#endif
#if !defined(DART_PRECOMPILED_RUNTIME)
native_callback_trampolines_(),
#endif
@@ -1773,12 +1774,7 @@
// Non-vm isolates need to have isolate object store initialized is that
// exit_listeners have to be null-initialized as they will be used if
// we fail to create isolate below, have to do low level shutdown.
- if (result->object_store() == nullptr) {
- // in JIT with --enable-isolate-groups each isolate still
- // has to have its own object store
- result->set_object_store(new ObjectStore());
- result->object_store()->InitStubs();
- }
+ ASSERT(result->object_store() != nullptr);
result->isolate_object_store()->Init();
}
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index e88229a..a3b1b8a 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -718,8 +718,8 @@
uint64_t id_ = 0;
std::unique_ptr<SharedClassTable> shared_class_table_;
- std::shared_ptr<ObjectStore> object_store_; // nullptr in JIT mode
- std::shared_ptr<ClassTable> class_table_; // nullptr in JIT mode
+ std::shared_ptr<ObjectStore> object_store_;
+ std::shared_ptr<ClassTable> class_table_;
std::unique_ptr<StoreBuffer> store_buffer_;
std::unique_ptr<Heap> heap_;
std::unique_ptr<DispatchTable> dispatch_table_;
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index e673c0a..acb3646 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -10731,16 +10731,23 @@
void Field::SetStaticValue(const Instance& value,
bool save_initial_value) const {
- ASSERT(Thread::Current()->IsMutatorThread());
+ auto thread = Thread::Current();
+ ASSERT(thread->IsMutatorThread());
ASSERT(is_static()); // Valid only for static dart fields.
- Isolate* isolate = Isolate::Current();
const intptr_t id = field_id();
ASSERT(id >= 0);
- isolate->field_table()->SetAt(id, value.raw());
+
+ SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
+ thread->isolate()->field_table()->SetAt(id, value.raw());
if (save_initial_value) {
+ // TODO(https://dartbug.com/36097): We should re-visit call-sites where
+ // `save_initial_value == true` and try to have a different path. This
+ // method should only modify the isolate-local field state and not modify
+ // the initial field table.
#if !defined(DART_PRECOMPILED_RUNTIME)
- isolate->group()->initial_field_table()->SetAt(field_id(), value.raw());
+ thread->isolate_group()->initial_field_table()->SetAt(field_id(),
+ value.raw());
#endif
}
}
diff --git a/runtime/vm/object_reload.cc b/runtime/vm/object_reload.cc
index 907dda1..4aab7f8 100644
--- a/runtime/vm/object_reload.cc
+++ b/runtime/vm/object_reload.cc
@@ -219,6 +219,12 @@
if (update_values && !field.is_const()) {
// Make new field point to the old field value so that both
// old and new code see and update same value.
+ //
+ // TODO(https://dartbug.com/36097): Once we look into enabling
+ // hot-reload with --enable-isolate-groups we have to do this
+ // for all isolates.
+ reload_context->isolate()->group()->initial_field_table()->Free(
+ field.field_id());
reload_context->isolate()->field_table()->Free(field.field_id());
field.set_field_id(old_field.field_id());
}
diff --git a/runtime/vm/unit_test.cc b/runtime/vm/unit_test.cc
index 52f46f8..3364072 100644
--- a/runtime/vm/unit_test.cc
+++ b/runtime/vm/unit_test.cc
@@ -164,13 +164,8 @@
void* group_data,
void* isolate_data) {
char* error;
-#if defined(DART_PRECOMPILED_RUNTIME)
- Isolate* result = CreateWithinExistingIsolateGroupAOT(
- reinterpret_cast<Isolate*>(parent)->group(), name, &error);
-#else
Isolate* result = CreateWithinExistingIsolateGroup(
reinterpret_cast<Isolate*>(parent)->group(), name, &error);
-#endif
if (error != nullptr) {
OS::PrintErr("CreateTestIsolateInGroup failed: %s\n", error);
free(error);
diff --git a/sdk/lib/_internal/js_runtime/lib/js_array.dart b/sdk/lib/_internal/js_runtime/lib/js_array.dart
index 93bd80c..ffefd1f 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_array.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_array.dart
@@ -282,15 +282,27 @@
}
void addAll(Iterable<E> collection) {
- int i = this.length;
checkGrowable('addAll');
+ if (collection is JSArray) {
+ _addAllFromArray(JS('', '#', collection));
+ return;
+ }
+ int i = this.length;
for (E e in collection) {
- assert(
- i++ == this.length || (throw new ConcurrentModificationError(this)));
+ assert(i++ == this.length || (throw ConcurrentModificationError(this)));
JS('void', r'#.push(#)', this, e);
}
}
+ void _addAllFromArray(JSArray array) {
+ int len = array.length;
+ if (len == 0) return;
+ if (identical(this, array)) throw ConcurrentModificationError(this);
+ for (int i = 0; i < len; i++) {
+ JS('', '#.push(#[#])', this, array, i);
+ }
+ }
+
void clear() {
length = 0;
}
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index a79aff6..bc7bdfb 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -37285,18 +37285,13 @@
}
Future cancel() {
- // Check for strong mode. This function can no longer return null in strong
- // mode, so only return null in weak mode to preserve synchronous timing.
- // See issue 41653 for more details.
- dynamic emptyFuture =
- typeAcceptsNull<Event>() ? null : Future<void>.value();
- if (_canceled) return emptyFuture as Future;
+ if (_canceled) return nullFuture;
_unlisten();
// Clear out the target to indicate this is complete.
_target = null;
_onData = null;
- return emptyFuture as Future;
+ return nullFuture;
}
bool get _canceled => _target == null;
diff --git a/tools/VERSION b/tools/VERSION
index 84ed07b..e263e32 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 135
+PRERELEASE 136
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/src/EventStreamProvider.dart b/tools/dom/src/EventStreamProvider.dart
index fe01be9..feadc26 100644
--- a/tools/dom/src/EventStreamProvider.dart
+++ b/tools/dom/src/EventStreamProvider.dart
@@ -248,18 +248,13 @@
}
Future cancel() {
- // Check for strong mode. This function can no longer return null in strong
- // mode, so only return null in weak mode to preserve synchronous timing.
- // See issue 41653 for more details.
- dynamic emptyFuture =
- typeAcceptsNull<Event>() ? null : Future<void>.value();
- if (_canceled) return emptyFuture as Future;
+ if (_canceled) return nullFuture;
_unlisten();
// Clear out the target to indicate this is complete.
_target = null;
_onData = null;
- return emptyFuture as Future;
+ return nullFuture;
}
bool get _canceled => _target == null;