Version 2.14.0-313.0.dev
Merge commit '902709109c4befd612af76a2ed95eba6a0cdc1ed' into 'dev'
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 66ade81..ec4e5d2 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -133,17 +133,13 @@
dart,
'format',
'--set-exit-if-changed',
+ '--output=none',
+ '--summary=none',
+ filename,
]
- if not contents:
- args += [
- '--output=none',
- '--summary=none',
- filename,
- ]
process = subprocess.Popen(
args, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
- process.communicate(input=contents)
# Check for exit code 1 explicitly to distinguish it from a syntax error
# in the file (exit code 65). The repo contains many Dart files that are
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 443a663..5513519 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -32,7 +32,6 @@
import 'package:analysis_server/src/services/correction/status.dart';
import 'package:analysis_server/src/services/refactoring/refactoring.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
-import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart' as engine;
@@ -575,8 +574,8 @@
if (fixes.isNotEmpty) {
fixes.sort(Fix.SORT_BY_RELEVANCE);
var lineInfo = LineInfo.fromContent(content);
- ResolvedUnitResult result = engine.ResolvedUnitResultImpl(session, file,
- Uri.file(file), true, content, lineInfo, false, null, errors);
+ var result = engine.ErrorsResultImpl(
+ session, file, Uri.file(file), lineInfo, false, errors);
var serverError = newAnalysisError_fromEngine(result, error);
var errorFixes = AnalysisErrorFixes(serverError);
errorFixesList.add(errorFixes);
@@ -674,8 +673,8 @@
if (fixes.isNotEmpty) {
fixes.sort(Fix.SORT_BY_RELEVANCE);
var lineInfo = LineInfo.fromContent(content);
- ResolvedUnitResult result = engine.ResolvedUnitResultImpl(session, file,
- Uri.file(file), true, content, lineInfo, false, null, errors);
+ var result = engine.ErrorsResultImpl(
+ session, file, Uri.file(file), lineInfo, false, errors);
var serverError = newAnalysisError_fromEngine(result, error);
var errorFixes = AnalysisErrorFixes(serverError);
errorFixesList.add(errorFixes);
@@ -723,8 +722,8 @@
if (fixes.isNotEmpty) {
fixes.sort(Fix.SORT_BY_RELEVANCE);
var lineInfo = LineInfo.fromContent(content);
- ResolvedUnitResult result = engine.ResolvedUnitResultImpl(session, file,
- Uri.file(file), true, content, lineInfo, false, null, errors);
+ var result = engine.ErrorsResultImpl(
+ session, file, Uri.file(file), lineInfo, false, errors);
var serverError = newAnalysisError_fromEngine(result, error);
var errorFixes = AnalysisErrorFixes(serverError);
errorFixesList.add(errorFixes);
diff --git a/pkg/analyzer/lib/src/context/builder.dart b/pkg/analyzer/lib/src/context/builder.dart
index 9bb60eb..e143315 100644
--- a/pkg/analyzer/lib/src/context/builder.dart
+++ b/pkg/analyzer/lib/src/context/builder.dart
@@ -5,24 +5,8 @@
import 'dart:collection';
import 'dart:core';
-import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/context/context_root.dart';
import 'package:analyzer/src/context/packages.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
-import 'package:analyzer/src/dart/analysis/driver.dart'
- show AnalysisDriver, AnalysisDriverScheduler;
-import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
-import 'package:analyzer/src/dart/analysis/performance_logger.dart';
-import 'package:analyzer/src/dart/sdk/sdk.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/sdk.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/hint/sdk_constraint_extractor.dart';
-import 'package:analyzer/src/summary/package_bundle_reader.dart';
-import 'package:analyzer/src/summary/summary_sdk.dart';
-import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/src/workspace/basic.dart';
import 'package:analyzer/src/workspace/bazel.dart';
import 'package:analyzer/src/workspace/gn.dart';
@@ -50,246 +34,6 @@
///
/// [1]: https://github.com/dart-lang/dart_enhancement_proposals/blob/master/Accepted/0005%20-%20Package%20Specification/DEP-pkgspec.md.
class ContextBuilder {
- /// The [ResourceProvider] by which paths are converted into [Resource]s.
- final ResourceProvider resourceProvider;
-
- /// The manager used to manage the DartSdk's that have been created so that
- /// they can be shared across contexts.
- final DartSdkManager sdkManager;
-
- /// The options used by the context builder.
- final ContextBuilderOptions builderOptions;
-
- /// The scheduler used by any analysis drivers created through this interface.
- late final AnalysisDriverScheduler analysisDriverScheduler;
-
- /// The performance log used by any analysis drivers created through this
- /// interface.
- late final PerformanceLog performanceLog;
-
- /// If `true`, additional analysis data useful for testing is stored.
- bool retainDataForTesting = false;
-
- /// The byte store used by any analysis drivers created through this interface.
- late final ByteStore byteStore;
-
- /// Whether any analysis driver created through this interface should support
- /// indexing and search.
- bool enableIndex = false;
-
- /// Sometimes `BUILD` files are not preserved, and other files are created
- /// instead. But looking for them is expensive, so we want to avoid this
- /// in cases when `BUILD` files are always available.
- bool lookForBazelBuildFileSubstitutes = true;
-
- /// Initialize a newly created builder to be ready to build a context rooted in
- /// the directory with the given [rootDirectoryPath].
- ContextBuilder(this.resourceProvider, this.sdkManager,
- {ContextBuilderOptions? options})
- : builderOptions = options ?? ContextBuilderOptions();
-
- /// Return an analysis driver that is configured correctly to analyze code in
- /// the directory with the given [path].
- AnalysisDriver buildDriver(
- ContextRoot contextRoot,
- Workspace workspace, {
- void Function(AnalysisOptionsImpl)? updateAnalysisOptions,
- FileContentCache? fileContentCache,
- }) {
- String path = contextRoot.root;
-
- var options = getAnalysisOptions(path, workspace, contextRoot: contextRoot);
-
- if (updateAnalysisOptions != null) {
- updateAnalysisOptions(options);
- }
- //_processAnalysisOptions(context, optionMap);
-
- SummaryDataStore? summaryData;
- var librarySummaryPaths = builderOptions.librarySummaryPaths;
- if (librarySummaryPaths != null) {
- summaryData = SummaryDataStore(librarySummaryPaths);
- }
-
- final sf =
- createSourceFactoryFromWorkspace(workspace, summaryData: summaryData);
-
- AnalysisDriver driver = AnalysisDriver.tmp1(
- scheduler: analysisDriverScheduler,
- logger: performanceLog,
- resourceProvider: resourceProvider,
- byteStore: byteStore,
- sourceFactory: sf,
- analysisOptions: options,
- packages: createPackageMap(
- resourceProvider: resourceProvider,
- options: builderOptions,
- rootPath: path,
- ),
- enableIndex: enableIndex,
- externalSummaries: summaryData,
- retainDataForTesting: retainDataForTesting,
- fileContentCache: fileContentCache,
- );
-
- declareVariablesInDriver(driver);
- return driver;
- }
-
-// void _processAnalysisOptions(
-// AnalysisContext context, Map<String, YamlNode> optionMap) {
-// List<OptionsProcessor> optionsProcessors =
-// AnalysisEngine.instance.optionsPlugin.optionsProcessors;
-// try {
-// optionsProcessors.forEach(
-// (OptionsProcessor p) => p.optionsProcessed(context, optionMap));
-//
-// // Fill in lint rule defaults in case lints are enabled and rules are
-// // not specified in an options file.
-// if (context.analysisOptions.lint && !containsLintRuleEntry(optionMap)) {
-// setLints(context, linterPlugin.contributedRules);
-// }
-//
-// // Ask engine to further process options.
-// if (optionMap != null) {
-// configureContextOptions(context, optionMap);
-// }
-// } on Exception catch (e) {
-// optionsProcessors.forEach((OptionsProcessor p) => p.onError(e));
-// }
-// }
-
- SourceFactory createSourceFactory(String rootPath, Workspace workspace,
- {SummaryDataStore? summaryData}) {
- DartSdk sdk = findSdk(workspace);
- if (summaryData != null && sdk is SummaryBasedDartSdk) {
- summaryData.addBundle(null, sdk.bundle);
- }
- 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) {
- var variables = builderOptions.declaredVariables;
- if (variables.isNotEmpty) {
- driver.declaredVariables = DeclaredVariables.fromMap(variables);
- driver.configure();
- }
- }
-
- /// Return the SDK that should be used to analyze code. Use the given
- /// [workspace] to locate the SDK.
- DartSdk findSdk(Workspace? workspace) {
- String? summaryPath = builderOptions.dartSdkSummaryPath;
- if (summaryPath != null) {
- return SummaryBasedDartSdk(summaryPath, true,
- resourceProvider: resourceProvider);
- }
-
- DartSdk folderSdk;
- {
- String sdkPath = sdkManager.defaultSdkDirectory;
- SdkDescription description = SdkDescription(sdkPath);
- folderSdk = sdkManager.getSdk(description, () {
- return FolderBasedDartSdk(
- resourceProvider,
- resourceProvider.getFolder(sdkPath),
- );
- });
- }
-
- if (workspace != null) {
- var partialSourceFactory = workspace.createSourceFactory(null, null);
- var embedderYamlSource = partialSourceFactory.forUri(
- 'package:sky_engine/_embedder.yaml',
- );
- if (embedderYamlSource != null) {
- var embedderYamlPath = embedderYamlSource.fullName;
- var libFolder = resourceProvider.getFile(embedderYamlPath).parent2;
- EmbedderYamlLocator locator =
- EmbedderYamlLocator.forLibFolder(libFolder);
- Map<Folder, YamlMap> embedderMap = locator.embedderYamls;
- if (embedderMap.isNotEmpty) {
- EmbedderSdk embedderSdk = EmbedderSdk(
- resourceProvider,
- embedderMap,
- languageVersion: folderSdk.languageVersion,
- );
- return embedderSdk;
- }
- }
- }
-
- return folderSdk;
- }
-
- /// Return the analysis options that should be used to analyze code in the
- /// directory with the given [path]. Use [verbosePrint] to echo verbose
- /// information about the analysis options selection process.
- AnalysisOptionsImpl getAnalysisOptions(String path, Workspace workspace,
- {void Function(String text)? verbosePrint, ContextRoot? contextRoot}) {
- void verbose(String text) {
- if (verbosePrint != null) {
- verbosePrint(text);
- }
- }
-
- SourceFactory sourceFactory = workspace.createSourceFactory(null, null);
- AnalysisOptionsProvider optionsProvider =
- AnalysisOptionsProvider(sourceFactory);
-
- AnalysisOptionsImpl options = AnalysisOptionsImpl();
-
- var optionsPath = builderOptions.defaultAnalysisOptionsFilePath;
- if (optionsPath != null) {
- var optionsFile = resourceProvider.getFile(optionsPath);
- try {
- contextRoot?.optionsFilePath = optionsFile.path;
- var optionsMap = optionsProvider.getOptionsFromFile(optionsFile);
- applyToAnalysisOptions(options, optionsMap);
- verbose('Loaded analysis options from ${optionsFile.path}');
- } catch (e) {
- // Ignore exceptions thrown while trying to load the options file.
- verbose('Exception: $e\n when loading ${optionsFile.path}');
- }
- } else {
- verbose('Using default analysis options');
- }
-
- var pubspecFile = _findPubspecFile(path);
- if (pubspecFile != null) {
- var extractor = SdkConstraintExtractor(pubspecFile);
- var sdkVersionConstraint = extractor.constraint();
- if (sdkVersionConstraint != null) {
- options.sdkVersionConstraint = sdkVersionConstraint;
- }
- }
-
- return options;
- }
-
- /// Return the `pubspec.yaml` file that should be used when analyzing code in
- /// the directory with the given [path], possibly `null`.
- File? _findPubspecFile(String path) {
- var folder = resourceProvider.getFolder(path);
- for (var current in folder.withAncestors) {
- var file = current.getChildAssumingFile('pubspec.yaml');
- if (file.exists) {
- return file;
- }
- }
- }
-
/// Return [Packages] to analyze a resource with the [rootPath].
static Packages createPackageMap({
required ResourceProvider resourceProvider,
@@ -360,11 +104,6 @@
/// Options used by a [ContextBuilder].
class ContextBuilderOptions {
- /// The file path of the file containing the summary of the SDK that should be
- /// used to "analyze" the SDK. This option should only be specified by
- /// command-line tools such as 'dartanalyzer' or 'ddc'.
- String? dartSdkSummaryPath;
-
/// The file path of the analysis options file that should be used in place of
/// any file in the root directory or a parent of the root directory, or `null`
/// if the normal lookup mechanism should be used.
@@ -378,10 +117,6 @@
/// or `null` if the normal lookup mechanism should be used.
String? defaultPackageFilePath;
- /// A list of the paths of summary files that are to be used, or `null` if no
- /// summary information is available.
- List<String>? librarySummaryPaths;
-
/// Initialize a newly created set of options
ContextBuilderOptions();
}
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 52d65fc..df488a6 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -8,7 +8,6 @@
import 'package:analyzer/dart/analysis/analysis_context.dart' as api;
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/results.dart';
-import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
@@ -1000,11 +999,11 @@
}
// Notify the completers.
_requestedFiles.remove(path)!.forEach((completer) {
- completer.complete(result);
+ completer.complete(result.unitResult!);
});
// Remove from to be analyzed and produce it now.
_fileTracker.fileWasAnalyzed(path);
- _resultController.add(result);
+ _resultController.add(result.unitResult!);
} catch (exception, stackTrace) {
_reportException(path, exception, stackTrace);
_fileTracker.fileWasAnalyzed(path);
@@ -1129,7 +1128,7 @@
if (result == null) {
_partsToAnalyze.add(path);
} else {
- _resultController.add(result);
+ _resultController.add(result.unitResult!);
}
} catch (exception, stackTrace) {
_reportException(path, exception, stackTrace);
@@ -1150,12 +1149,12 @@
withUnit: false, skipIfSameSignature: true);
if (result == null) {
_partsToAnalyze.add(path);
- } else if (result == AnalysisResult._UNCHANGED) {
+ } else if (result.isUnchangedErrors) {
// We found that the set of errors is the same as we produced the
// last time, so we don't need to produce it again now.
} else {
- _resultController.add(result);
- _lastProducedSignatures[result.path] = result._signature;
+ _resultController.add(result.errorsResult!);
+ _lastProducedSignatures[path] = result._signature;
}
} catch (exception, stackTrace) {
_reportException(path, exception, stackTrace);
@@ -1174,11 +1173,11 @@
withUnit: true, asIsIfPartWithoutLibrary: true)!;
// Notify the completers.
_requestedParts.remove(path)!.forEach((completer) {
- completer.complete(result);
+ completer.complete(result.unitResult!);
});
// Remove from to be analyzed and produce it now.
_partsToAnalyze.remove(path);
- _resultController.add(result);
+ _resultController.add(result.unitResult!);
} catch (exception, stackTrace) {
_reportException(path, exception, stackTrace);
_partsToAnalyze.remove(path);
@@ -1195,10 +1194,16 @@
String path = _partsToAnalyze.first;
_partsToAnalyze.remove(path);
try {
- var result = _computeAnalysisResult(path,
- withUnit: _priorityFiles.contains(path),
- asIsIfPartWithoutLibrary: true)!;
- _resultController.add(result);
+ var withUnit = _priorityFiles.contains(path);
+ if (withUnit) {
+ var result = _computeAnalysisResult(path,
+ withUnit: true, asIsIfPartWithoutLibrary: true)!;
+ _resultController.add(result.unitResult!);
+ } else {
+ var result = _computeAnalysisResult(path,
+ withUnit: false, asIsIfPartWithoutLibrary: true)!;
+ _resultController.add(result.errorsResult!);
+ }
} catch (exception, stackTrace) {
_reportException(path, exception, stackTrace);
_clearLibraryContextAfterException();
@@ -1300,8 +1305,9 @@
/// Return the cached or newly computed analysis result of the file with the
/// given [path].
///
- /// The result will have the fully resolved unit and will always be newly
- /// compute only if [withUnit] is `true`.
+ /// The [withUnit] flag control which result will be returned.
+ /// When `true`, [AnalysisResult.unitResult] will be set.
+ /// Otherwise [AnalysisResult.errorsResult] will be set.
///
/// Return `null` if the file is a part of an unknown library, so cannot be
/// analyzed yet. But [asIsIfPartWithoutLibrary] is `true`, then the file is
@@ -1311,7 +1317,7 @@
/// the resolved signature of the file in its library is the same as the one
/// that was the most recently produced to the client.
AnalysisResult? _computeAnalysisResult(String path,
- {bool withUnit = false,
+ {required bool withUnit,
bool asIsIfPartWithoutLibrary = false,
bool skipIfSameSignature = false}) {
FileState file = _fsState.getFileForPath(path);
@@ -1334,7 +1340,7 @@
if (skipIfSameSignature) {
assert(!withUnit);
if (_lastProducedSignatures[path] == signature) {
- return AnalysisResult._UNCHANGED;
+ return AnalysisResult.unchangedErrors(signature);
}
}
@@ -1394,7 +1400,7 @@
content: withUnit ? file.content : null,
resolvedUnit: withUnit ? resolvedUnit : null);
if (withUnit && _priorityFiles.contains(path)) {
- _priorityResults[path] = result;
+ _priorityResults[path] = result.unitResult!;
}
return result;
} catch (exception, stackTrace) {
@@ -1416,8 +1422,7 @@
return null;
}
- return ErrorsResultImpl(currentSession, path, analysisResult.uri,
- analysisResult.lineInfo, analysisResult.isPart, analysisResult.errors);
+ return analysisResult.errorsResult;
}
AnalysisDriverUnitIndex _computeIndex(String path) {
@@ -1666,7 +1671,9 @@
var unit = AnalysisDriverResolvedUnit.fromBuffer(bytes);
List<AnalysisError> errors = _getErrorsFromSerialized(file, unit.errors);
_updateHasErrorOrWarningFlag(file, errors);
- return AnalysisResult(
+ var index = unit.index!;
+ if (resolvedUnit != null) {
+ var resolvedUnitResult = ResolvedUnitResultImpl(
currentSession,
file.path,
file.uri,
@@ -1674,10 +1681,21 @@
content,
file.lineInfo,
file.isPart,
- signature,
resolvedUnit,
errors,
- unit.index);
+ );
+ return AnalysisResult.unit(signature, resolvedUnitResult, index);
+ } else {
+ var errorsResult = ErrorsResultImpl(
+ currentSession,
+ file.path,
+ file.uri,
+ file.lineInfo,
+ file.isPart,
+ errors,
+ );
+ return AnalysisResult.errors(signature, errorsResult, index);
+ }
}
/// Return [AnalysisError]s for the given [serialized] errors.
@@ -1726,21 +1744,19 @@
AnalysisResult _newMissingDartLibraryResult(
FileState file, String missingUri) {
// TODO(scheglov) Find a better way to report this.
- return AnalysisResult(
- currentSession,
- file.path,
- file.uri,
- file.exists,
- null,
- file.lineInfo,
- file.isPart,
- 'missing',
- null,
- [
- AnalysisError(file.source, 0, 0,
- CompileTimeErrorCode.MISSING_DART_LIBRARY, [missingUri])
- ],
- null);
+ var errorsResult = ErrorsResultImpl(
+ currentSession,
+ file.path,
+ file.uri,
+ file.lineInfo,
+ file.isPart,
+ [
+ AnalysisError(file.source, 0, 0,
+ CompileTimeErrorCode.MISSING_DART_LIBRARY, [missingUri])
+ ],
+ );
+ return AnalysisResult.errors(
+ 'missing', errorsResult, AnalysisDriverUnitIndexBuilder());
}
void _reportException(String path, Object exception, StackTrace stackTrace) {
@@ -2121,38 +2137,50 @@
/// The result of analyzing of a single file.
///
-/// These results are self-consistent, i.e. [content], [lineInfo], the
-/// resolved [unit] correspond to each other. All referenced elements, even
+/// These results are self-consistent, i.e. the file content, line info, the
+/// resolved unit correspond to each other. All referenced elements, even
/// external ones, are also self-consistent. But none of the results is
/// guaranteed to be consistent with the state of the files.
///
/// Every result is independent, and is not guaranteed to be consistent with
/// any previously returned result, even inside of the same library.
-class AnalysisResult extends ResolvedUnitResultImpl {
- static final _UNCHANGED = _UnchangedAnalysisResult();
-
+class AnalysisResult {
/// The signature of the result based on the content of the file, and the
/// transitive closure of files imported and exported by the library of
/// the requested file.
final String _signature;
+ final bool isUnchangedErrors;
+
+ /// Is not `null` if this result is a result with errors.
+ /// Otherwise is `null`, and usually [unitResult] is set.
+ final ErrorsResultImpl? errorsResult;
+
+ /// Is not `null` if this result is a result with a resolved unit.
+ /// Otherwise is `null`, and usually [errorsResult] is set.
+ final ResolvedUnitResultImpl? unitResult;
+
/// The index of the unit.
final AnalysisDriverUnitIndex? _index;
- AnalysisResult(
- AnalysisSession session,
- String path,
- Uri uri,
- bool exists,
- String? content,
- LineInfo lineInfo,
- bool isPart,
- this._signature,
- CompilationUnit? unit,
- List<AnalysisError> errors,
- this._index)
- : super(session, path, uri, exists, content, lineInfo, isPart, unit,
- errors);
+ AnalysisResult.errors(
+ this._signature, this.errorsResult, AnalysisDriverUnitIndex index)
+ : isUnchangedErrors = false,
+ unitResult = null,
+ _index = index;
+
+ AnalysisResult.unchangedErrors(this._signature)
+ : isUnchangedErrors = true,
+ errorsResult = null,
+ unitResult = null,
+ _index = null;
+
+ AnalysisResult.unit(this._signature, ResolvedUnitResultImpl unitResult,
+ AnalysisDriverUnitIndex index)
+ : isUnchangedErrors = false,
+ errorsResult = null,
+ unitResult = unitResult,
+ _index = index;
}
/// An object that watches for the creation and removal of analysis drivers.
@@ -2494,8 +2522,3 @@
return true;
}
}
-
-class _UnchangedAnalysisResult implements AnalysisResult {
- @override
- noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
-}
diff --git a/pkg/analyzer/test/src/context/builder_test.dart b/pkg/analyzer/test/src/context/builder_test.dart
index e439283..66c9590 100644
--- a/pkg/analyzer/test/src/context/builder_test.dart
+++ b/pkg/analyzer/test/src/context/builder_test.dart
@@ -4,22 +4,6 @@
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/builder.dart';
-import 'package:analyzer/src/context/context_root.dart';
-import 'package:analyzer/src/context/packages.dart';
-import 'package:analyzer/src/context/source.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/sdk.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/source/package_map_resolver.dart';
-import 'package:analyzer/src/test_utilities/mock_sdk.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
-import 'package:analyzer/src/workspace/basic.dart';
-import 'package:analyzer/src/workspace/bazel.dart';
-import 'package:analyzer/src/workspace/gn.dart';
-import 'package:analyzer/src/workspace/package_build.dart';
-import 'package:analyzer/src/workspace/pub.dart';
-import 'package:analyzer/src/workspace/workspace.dart';
-import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -27,461 +11,11 @@
main() {
defineReflectiveSuite(() {
- defineReflectiveTests(ContextBuilderTest);
defineReflectiveTests(EmbedderYamlLocatorTest);
});
}
@reflectiveTest
-class ContextBuilderTest with ResourceProviderMixin {
- /// The SDK manager used by the tests;
- late final DartSdkManager sdkManager;
-
- /// The options passed to the context builder.
- ContextBuilderOptions builderOptions = ContextBuilderOptions();
-
- /// The context builder to be used in the test.
- late ContextBuilder builder;
-
- /// The path to the default SDK, or `null` if the test has not explicitly
- /// invoked [createDefaultSdk].
- late final String defaultSdkPath;
-
- Uri convertedDirectoryUri(String directoryPath) {
- return Uri.directory(convertPath(directoryPath),
- windows: resourceProvider.pathContext.style == path.windows.style);
- }
-
- void createDefaultSdk() {
- defaultSdkPath = convertPath(sdkRoot);
- MockSdk(resourceProvider: resourceProvider);
- }
-
- void setUp() {
- MockSdk(resourceProvider: resourceProvider);
- sdkManager = DartSdkManager(convertPath('/sdk'));
- builder = ContextBuilder(
- resourceProvider,
- sdkManager,
- options: builderOptions,
- );
- }
-
- @failingTest
- void test_buildContext() {
- fail('Incomplete test');
- }
-
- @failingTest
- void test_cmdline_options_override_options_file() {
- fail('No clear choice of option to override.');
-// ArgParser argParser = new ArgParser();
-// defineAnalysisArguments(argParser);
-// ArgResults argResults = argParser.parse(['--$enableSuperMixinFlag']);
-// var builder = new ContextBuilder(resourceProvider, sdkManager, contentCache,
-// options: createContextBuilderOptions(argResults));
-//
-// AnalysisOptionsImpl expected = new AnalysisOptionsImpl();
-// expected.option = true;
-//
-// String path = resourceProvider.convertPath('/some/directory/path');
-// String filePath =
-// join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-// resourceProvider.newFile(filePath, '''
-//analyzer:
-// language:
-// option: true
-//''');
-//
-// AnalysisOptions options = builder.getAnalysisOptions(path);
-// _expectEqualOptions(options, expected);
- }
-
- void test_createPackageMap_fromPackageFile_explicit() {
- // Use a package file that is outside the project directory's hierarchy.
- String rootPath = convertPath('/root');
- String projectPath = join(rootPath, 'project');
- String packageFilePath = join(rootPath, 'child', '.packages');
- newFolder(projectPath);
- newFile(packageFilePath, content: '''
-foo:${toUriStr('/pkg/foo')}
-bar:${toUriStr('/pkg/bar')}
-''');
-
- builderOptions.defaultPackageFilePath = packageFilePath;
- Packages packages = _createPackageMap(projectPath);
- _assertPackages(
- packages,
- {
- 'foo': convertPath('/pkg/foo'),
- 'bar': convertPath('/pkg/bar'),
- },
- );
- }
-
- void test_createPackageMap_fromPackageFile_inParentOfRoot() {
- // Use a package file that is inside the parent of the project directory.
- String rootPath = convertPath('/root');
- String projectPath = join(rootPath, 'project');
- String packageFilePath = join(rootPath, '.packages');
- newFolder(projectPath);
- newFile(packageFilePath, content: '''
-foo:${toUriStr('/pkg/foo')}
-bar:${toUriStr('/pkg/bar')}
-''');
-
- Packages packages = _createPackageMap(projectPath);
- _assertPackages(
- packages,
- {
- 'foo': convertPath('/pkg/foo'),
- 'bar': convertPath('/pkg/bar'),
- },
- );
- }
-
- void test_createPackageMap_fromPackageFile_inRoot() {
- // Use a package file that is inside the project directory.
- String rootPath = convertPath('/root');
- String projectPath = join(rootPath, 'project');
- String packageFilePath = join(projectPath, '.packages');
- newFolder(projectPath);
- newFile(packageFilePath, content: '''
-foo:${toUriStr('/pkg/foo')}
-bar:${toUriStr('/pkg/bar')}
-''');
-
- Packages packages = _createPackageMap(projectPath);
- _assertPackages(
- packages,
- {
- 'foo': convertPath('/pkg/foo'),
- 'bar': convertPath('/pkg/bar'),
- },
- );
- }
-
- void test_createPackageMap_none() {
- String rootPath = convertPath('/root');
- newFolder(rootPath);
- Packages packages = _createPackageMap(rootPath);
- expect(packages.packages, isEmpty);
- }
-
- void test_createPackageMap_rootDoesNotExist() {
- String rootPath = convertPath('/root');
- Packages packages = _createPackageMap(rootPath);
- expect(packages.packages, isEmpty);
- }
-
- void test_createSourceFactory_bazelWorkspace_fileProvider() {
- String projectPath = convertPath('/workspace/my/module');
- newFile('/workspace/WORKSPACE');
- newFolder('/workspace/bazel-bin');
- newFolder('/workspace/bazel-genfiles');
- newFolder(projectPath);
-
- var factory = _createSourceFactory(projectPath);
- expect(factory.resolvers,
- contains(predicate((r) => r is BazelFileUriResolver)));
- expect(factory.resolvers,
- contains(predicate((r) => r is BazelPackageUriResolver)));
- }
-
- void test_createSourceFactory_bazelWorkspace_withPackagesFile() {
- String projectPath = convertPath('/workspace/my/module');
- newFile('/workspace/WORKSPACE');
- newFolder('/workspace/bazel-bin');
- newFolder('/workspace/bazel-genfiles');
- newFolder(projectPath);
- newFile(join(projectPath, '.packages'));
-
- var factory = _createSourceFactory(projectPath);
- expect(factory.resolvers,
- contains(predicate((r) => r is ResourceUriResolver)));
- expect(factory.resolvers,
- contains(predicate((r) => r is PackageMapUriResolver)));
- }
-
- void test_createSourceFactory_noProvider_packages_embedder_noExtensions() {
- String rootPath = convertPath('/root');
- createDefaultSdk();
- String projectPath = join(rootPath, 'project');
- String packageFilePath = join(projectPath, '.packages');
-
- String skyEnginePath = join(rootPath, 'pkgs', 'sky_engine');
- String embedderPath = join(skyEnginePath, '_embedder.yaml');
- String asyncPath = join(skyEnginePath, 'sdk', 'async.dart');
- String corePath = join(skyEnginePath, 'sdk', 'core.dart');
- newFile(embedderPath, content: '''
-embedded_libs:
- "dart:async": ${_relativeUri(asyncPath, from: skyEnginePath)}
- "dart:core": ${_relativeUri(corePath, from: skyEnginePath)}
-''');
-
- String packageB = join(rootPath, 'pkgs', 'b');
- newFile(packageFilePath, content: '''
-sky_engine:${resourceProvider.pathContext.toUri(skyEnginePath)}
-b:${resourceProvider.pathContext.toUri(packageB)}
-''');
-
- SourceFactory factory = _createSourceFactory(projectPath);
-
- var dartSource = factory.forUri('dart:async')!;
- expect(dartSource, isNotNull);
- expect(dartSource.fullName, asyncPath);
-
- var packageSource = factory.forUri('package:b/b.dart')!;
- expect(packageSource, isNotNull);
- expect(packageSource.fullName, join(packageB, 'b.dart'));
- }
-
- @failingTest
- void test_createSourceFactory_noProvider_packages_noEmbedder_extensions() {
- fail('Incomplete test');
- }
-
- void test_createSourceFactory_noProvider_packages_noEmbedder_noExtensions() {
- String rootPath = convertPath('/root');
- createDefaultSdk();
- String projectPath = join(rootPath, 'project');
- String packageFilePath = join(projectPath, '.packages');
- String packageA = join(rootPath, 'pkgs', 'a');
- String packageB = join(rootPath, 'pkgs', 'b');
- newFile(packageFilePath, content: '''
-a:${resourceProvider.pathContext.toUri(packageA)}
-b:${resourceProvider.pathContext.toUri(packageB)}
-''');
-
- SourceFactory factory = _createSourceFactory(projectPath);
-
- var dartSource = factory.forUri('dart:core')!;
- expect(dartSource, isNotNull);
- expect(
- dartSource.fullName, join(defaultSdkPath, 'lib', 'core', 'core.dart'));
-
- var packageSource = factory.forUri('package:a/a.dart')!;
- expect(packageSource, isNotNull);
- expect(packageSource.fullName, join(packageA, 'a.dart'));
- }
-
- void test_createWorkspace_hasPackagesFile_hasDartToolAndPubspec() {
- newDotPackagesFile('/workspace');
- newFolder('/workspace/.dart_tool/build/generated/project/lib');
- newPubspecYamlFile('/workspace', 'name: project');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<PackageBuildWorkspace>());
- }
-
- void test_createWorkspace_hasPackagesFile_hasPubspec() {
- newDotPackagesFile('/workspace');
- newPubspecYamlFile('/workspace', 'name: project');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<PubWorkspace>());
- }
-
- void test_createWorkspace_hasPackagesFile_noMarkerFiles() {
- newDotPackagesFile('/workspace');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<BasicWorkspace>());
- }
-
- void test_createWorkspace_noPackagesFile_hasBazelMarkerFiles() {
- newFile('/workspace/WORKSPACE');
- newFolder('/workspace/bazel-genfiles');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<BazelWorkspace>());
- }
-
- void test_createWorkspace_noPackagesFile_hasDartToolAndPubspec() {
- newFolder('/workspace/.dart_tool/build/generated/project/lib');
- newPubspecYamlFile('/workspace', 'name: project');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<PackageBuildWorkspace>());
- }
-
- void test_createWorkspace_noPackagesFile_hasGnMarkerFiles() {
- newFolder('/workspace/.jiri_root');
- newFile(
- '/workspace/out/debug-x87_128/dartlang/gen/project/lib/lib_package_config.json',
- content: '''{
- "configVersion": 2,
- "packages": []
-}''');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<GnWorkspace>());
- }
-
- void test_createWorkspace_noPackagesFile_hasPubspec() {
- newPubspecYamlFile('/workspace', 'name: project');
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<PubWorkspace>());
- }
-
- void test_createWorkspace_noPackagesFile_noMarkerFiles() {
- Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
- expect(workspace, TypeMatcher<BasicWorkspace>());
- }
-
- @failingTest
- void test_findSdk_embedder_extensions() {
- // See test_createSourceFactory_noProvider_packages_embedder_extensions
- fail('Incomplete test');
- }
-
- @failingTest
- void test_findSdk_embedder_noExtensions() {
- // See test_createSourceFactory_noProvider_packages_embedder_noExtensions
- fail('Incomplete test');
- }
-
- @failingTest
- void test_findSdk_noEmbedder_extensions() {
- // See test_createSourceFactory_noProvider_packages_noEmbedder_extensions
- fail('Incomplete test');
- }
-
- @failingTest
- void test_findSdk_noEmbedder_noExtensions() {
- // See test_createSourceFactory_noProvider_packages_noEmbedder_noExtensions
- fail('Incomplete test');
- }
-
- void test_findSdk_noPackageMap() {
- DartSdk sdk = builder.findSdk(null);
- expect(sdk, isNotNull);
- }
-
- void test_findSdk_noPackageMap_html_strong() {
- DartSdk sdk = builder.findSdk(null);
- expect(sdk, isNotNull);
- Source htmlSource = sdk.mapDartUri('dart:html')!;
- expect(htmlSource.fullName,
- convertPath('/sdk/lib/html/dart2js/html_dart2js.dart'));
- expect(htmlSource.exists(), isTrue);
- }
-
- void test_getAnalysisOptions_gnWorkspace() {
- String projectPath = convertPath('/workspace/some/path');
- newFolder('/workspace/.jiri_root');
- newFile('/workspace/out/debug/gen/dart.sources/foo_pkg',
- content: convertPath('/workspace/foo_pkg/lib'));
- newFolder(projectPath);
- builder =
- ContextBuilder(resourceProvider, sdkManager, options: builderOptions);
- AnalysisOptionsImpl expected = AnalysisOptionsImpl();
- var options = _getAnalysisOptions(builder, projectPath);
- _expectEqualOptions(options, expected);
- }
-
- void test_getAnalysisOptions_invalid() {
- String path = convertPath('/some/directory/path');
- newAnalysisOptionsYamlFile(path, content: ';');
-
- AnalysisOptions options = _getAnalysisOptions(builder, path);
- expect(options, isNotNull);
- }
-
- void test_getAnalysisOptions_noDefault_noOverrides() {
- String path = convertPath('/some/directory/path');
- newAnalysisOptionsYamlFile(path, content: '''
-linter:
- rules:
- - non_existent_lint_rule
-''');
-
- var options = _getAnalysisOptions(builder, path);
- _expectEqualOptions(options, AnalysisOptionsImpl());
- }
-
- void test_getAnalysisOptions_sdkVersionConstraint() {
- var projectPath = convertPath('/test');
- newPubspecYamlFile(projectPath, '''
-environment:
- sdk: ^2.1.0
-''');
-
- var options = _getAnalysisOptions(builder, projectPath);
- expect(options.sdkVersionConstraint.toString(), '^2.1.0');
- }
-
- void test_getAnalysisOptions_sdkVersionConstraint_any_noOptionsFile() {
- var projectPath = convertPath('/test');
- var options = _getAnalysisOptions(builder, projectPath);
- expect(options.sdkVersionConstraint, isNull);
- }
-
- void _assertPackages(Packages packages, Map<String, String> nameToPath) {
- expect(packages, isNotNull);
- expect(packages.packages, hasLength(nameToPath.length));
- for (var name in nameToPath.keys) {
- var expectedPath = nameToPath[name];
- var path = packages[name]!.libFolder.path;
- expect(path, expectedPath, reason: 'package $name');
- }
- }
-
- Packages _createPackageMap(String rootPath) {
- return ContextBuilder.createPackageMap(
- resourceProvider: resourceProvider,
- options: builderOptions,
- rootPath: rootPath,
- );
- }
-
- SourceFactoryImpl _createSourceFactory(String projectPath) {
- Workspace workspace = ContextBuilder.createWorkspace(
- resourceProvider: resourceProvider,
- options: builderOptions,
- rootPath: projectPath,
- );
- return builder.createSourceFactory(projectPath, workspace)
- as SourceFactoryImpl;
- }
-
- Workspace _createWorkspace(String posixPath) {
- return ContextBuilder.createWorkspace(
- resourceProvider: resourceProvider,
- options: ContextBuilderOptions(),
- rootPath: convertPath(posixPath),
- );
- }
-
- void _expectEqualOptions(
- AnalysisOptionsImpl actual, AnalysisOptionsImpl expected) {
- // TODO(brianwilkerson) Consider moving this to AnalysisOptionsImpl.==.
- expect(actual.enableTiming, expected.enableTiming);
- expect(actual.hint, expected.hint);
- expect(actual.lint, expected.lint);
- expect(
- actual.lintRules.map((l) => l.name),
- unorderedEquals(expected.lintRules.map((l) => l.name)),
- );
- expect(actual.implicitCasts, expected.implicitCasts);
- expect(actual.implicitDynamic, expected.implicitDynamic);
- expect(actual.strictInference, expected.strictInference);
- expect(actual.strictRawTypes, expected.strictRawTypes);
- }
-
- AnalysisOptionsImpl _getAnalysisOptions(ContextBuilder builder, String path,
- {ContextRoot? contextRoot}) {
- Workspace workspace = ContextBuilder.createWorkspace(
- resourceProvider: resourceProvider,
- options: builder.builderOptions,
- rootPath: path,
- );
- return builder.getAnalysisOptions(path, workspace,
- contextRoot: contextRoot);
- }
-
- Uri _relativeUri(String path, {String? from}) {
- var pathContext = resourceProvider.pathContext;
- String relativePath = pathContext.relative(path, from: from);
- return pathContext.toUri(relativePath);
- }
-}
-
-@reflectiveTest
class EmbedderYamlLocatorTest extends EmbedderRelatedTest {
void test_empty() {
EmbedderYamlLocator locator = EmbedderYamlLocator({
diff --git a/pkg/analyzer/test/src/dart/analysis/base.dart b/pkg/analyzer/test/src/dart/analysis/base.dart
index 6b8cfd1..1bb79b1 100644
--- a/pkg/analyzer/test/src/dart/analysis/base.dart
+++ b/pkg/analyzer/test/src/dart/analysis/base.dart
@@ -33,7 +33,7 @@
late final AnalysisDriverScheduler scheduler;
late final AnalysisDriver driver;
final List<AnalysisStatus> allStatuses = <AnalysisStatus>[];
- final List<ResolvedUnitResult> allResults = <ResolvedUnitResult>[];
+ final List<AnalysisResultWithErrors> allResults = [];
final List<ExceptionResult> allExceptions = <ExceptionResult>[];
late final String testProject;
@@ -142,7 +142,7 @@
scheduler.start();
scheduler.status.listen(allStatuses.add);
driver.results.listen((result) {
- if (result is ResolvedUnitResult) {
+ if (result is AnalysisResultWithErrors) {
allResults.add(result);
}
});
diff --git a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
index af27464..5c75695 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
@@ -5,14 +5,25 @@
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/context_root.dart';
import 'package:analyzer/dart/analysis/declared_variables.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/source.dart';
import 'package:analyzer/src/dart/analysis/context_builder.dart';
+import 'package:analyzer/src/dart/analysis/context_locator.dart';
import 'package:analyzer/src/dart/analysis/context_root.dart';
+import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
+import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer/src/workspace/basic.dart';
+import 'package:analyzer/src/workspace/bazel.dart';
+import 'package:analyzer/src/workspace/pub.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import '../resolution/context_collection_resolution.dart';
+
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ContextBuilderImplTest);
@@ -40,6 +51,61 @@
contextRoot = ContextRootImpl(resourceProvider, folder, workspace);
}
+ void test_analysisOptions_invalid() {
+ MockSdk(resourceProvider: resourceProvider);
+
+ var projectPath = convertPath('/home/test');
+ newAnalysisOptionsYamlFile(projectPath, content: ';');
+
+ var analysisContext = _createSingleAnalysisContext(projectPath);
+ var analysisOptions = analysisContext.analysisOptionsImpl;
+ _expectEqualOptions(analysisOptions, AnalysisOptionsImpl());
+ }
+
+ void test_analysisOptions_languageOptions() {
+ MockSdk(resourceProvider: resourceProvider);
+
+ var projectPath = convertPath('/home/test');
+ newAnalysisOptionsYamlFile(
+ projectPath,
+ content: AnalysisOptionsFileConfig(
+ strictRawTypes: true,
+ ).toContent(),
+ );
+
+ var analysisContext = _createSingleAnalysisContext(projectPath);
+ var analysisOptions = analysisContext.analysisOptionsImpl;
+ _expectEqualOptions(
+ analysisOptions,
+ AnalysisOptionsImpl()..strictRawTypes = true,
+ );
+ }
+
+ void test_analysisOptions_sdkVersionConstraint_hasPubspec_hasSdk() {
+ MockSdk(resourceProvider: resourceProvider);
+
+ var projectPath = convertPath('/home/test');
+ newPubspecYamlFile(projectPath, '''
+environment:
+ sdk: ^2.1.0
+''');
+
+ var analysisContext = _createSingleAnalysisContext(projectPath);
+ var analysisOptions = analysisContext.analysisOptionsImpl;
+ expect(analysisOptions.sdkVersionConstraint.toString(), '^2.1.0');
+ }
+
+ void test_analysisOptions_sdkVersionConstraint_noPubspec() {
+ MockSdk(resourceProvider: resourceProvider);
+
+ var projectPath = convertPath('/home/test');
+ newFile('$projectPath/lib/a.dart');
+
+ var analysisContext = _createSingleAnalysisContext(projectPath);
+ var analysisOptions = analysisContext.driver.analysisOptions;
+ expect(analysisOptions.sdkVersionConstraint, isNull);
+ }
+
test_createContext_declaredVariables() {
MockSdk(resourceProvider: resourceProvider);
DeclaredVariables declaredVariables =
@@ -101,4 +167,86 @@
expect(context.contextRoot, contextRoot);
expect(context.sdkRoot?.path, resourceProvider.convertPath(sdkRoot));
}
+
+ void test_sourceFactory_bazelWorkspace() {
+ MockSdk(resourceProvider: resourceProvider);
+
+ var projectPath = convertPath('/workspace/my/module');
+ newFile('/workspace/WORKSPACE');
+ newFolder('/workspace/bazel-bin');
+ newFolder('/workspace/bazel-genfiles');
+
+ var analysisContext = _createSingleAnalysisContext(projectPath);
+ expect(analysisContext.contextRoot.workspace, isA<BazelWorkspace>());
+
+ expect(
+ analysisContext.uriResolvers,
+ unorderedEquals([
+ isA<DartUriResolver>(),
+ isA<BazelPackageUriResolver>(),
+ isA<BazelFileUriResolver>(),
+ ]),
+ );
+ }
+
+ void test_sourceFactory_pubWorkspace() {
+ MockSdk(resourceProvider: resourceProvider);
+
+ var projectPath = convertPath('/home/my');
+ newFile('/home/my/pubspec.yaml');
+
+ var analysisContext = _createSingleAnalysisContext(projectPath);
+ expect(analysisContext.contextRoot.workspace, isA<PubWorkspace>());
+
+ expect(
+ analysisContext.uriResolvers,
+ unorderedEquals([
+ isA<DartUriResolver>(),
+ isA<PackageMapUriResolver>(),
+ isA<ResourceUriResolver>(),
+ ]),
+ );
+ }
+
+ /// Return a single expected analysis context at the [path].
+ DriverBasedAnalysisContext _createSingleAnalysisContext(String path) {
+ var roots = ContextLocatorImpl(
+ resourceProvider: resourceProvider,
+ ).locateRoots(includedPaths: [path]);
+
+ return ContextBuilderImpl(
+ resourceProvider: resourceProvider,
+ ).createContext(
+ contextRoot: roots.single,
+ sdkPath: sdkRoot,
+ );
+ }
+
+ static void _expectEqualOptions(
+ AnalysisOptionsImpl actual,
+ AnalysisOptionsImpl expected,
+ ) {
+ // TODO(brianwilkerson) Consider moving this to AnalysisOptionsImpl.==.
+ expect(actual.enableTiming, expected.enableTiming);
+ expect(actual.hint, expected.hint);
+ expect(actual.lint, expected.lint);
+ expect(
+ actual.lintRules.map((l) => l.name),
+ unorderedEquals(expected.lintRules.map((l) => l.name)),
+ );
+ expect(actual.implicitCasts, expected.implicitCasts);
+ expect(actual.implicitDynamic, expected.implicitDynamic);
+ expect(actual.strictInference, expected.strictInference);
+ expect(actual.strictRawTypes, expected.strictRawTypes);
+ }
+}
+
+extension on DriverBasedAnalysisContext {
+ AnalysisOptionsImpl get analysisOptionsImpl {
+ return driver.analysisOptions as AnalysisOptionsImpl;
+ }
+
+ List<UriResolver> get uriResolvers {
+ return (driver.sourceFactory as SourceFactoryImpl).resolvers;
+ }
}
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 998836a..0ee0dc7 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -59,7 +59,7 @@
late final AnalysisDriverScheduler scheduler;
- List<ResolvedUnitResult> allResults = [];
+ final List<AnalysisResultWithErrors> allResults = [];
AnalysisDriver newDriver() {
sdk = MockSdk(resourceProvider: resourceProvider);
@@ -75,7 +75,7 @@
packages: Packages.empty,
);
driver.results.listen((result) {
- if (result is ResolvedUnitResult) {
+ if (result is AnalysisResultWithErrors) {
allResults.add(result);
}
});
@@ -673,7 +673,9 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
{
- ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+ ResolvedUnitResult ar = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == a);
_assertTopLevelVarType(ar.unit!, 'A', 'int');
}
allResults.clear();
@@ -690,7 +692,9 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
{
- ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+ ResolvedUnitResult ar = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == a);
_assertTopLevelVarType(ar.unit!, 'A', 'double');
}
}
@@ -744,12 +748,16 @@
// We have results for both "a" and "b".
expect(allResults, hasLength(2));
{
- ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+ ResolvedUnitResult ar = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == a);
_assertTopLevelVarType(ar.unit!, 'A1', 'int');
_assertTopLevelVarType(ar.unit!, 'A2', 'int');
}
{
- ResolvedUnitResult br = allResults.firstWhere((r) => r.path == b);
+ ResolvedUnitResult br = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == b);
_assertTopLevelVarType(br.unit!, 'B1', 'int');
}
@@ -767,12 +775,16 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(2));
{
- ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+ ResolvedUnitResult ar = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == a);
_assertTopLevelVarType(ar.unit!, 'A1', 'double');
_assertTopLevelVarType(ar.unit!, 'A2', 'double');
}
{
- ResolvedUnitResult br = allResults.firstWhere((r) => r.path == b);
+ ResolvedUnitResult br = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == b);
_assertTopLevelVarType(br.unit!, 'B1', 'double');
}
}
@@ -784,7 +796,7 @@
{
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
- ResolvedUnitResult result = allResults[0];
+ var result = allResults[0] as ResolvedUnitResult;
expect(result.path, testFile);
_assertTopLevelVarType(result.unit!, 'V', 'int');
}
@@ -807,7 +819,7 @@
{
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
- ResolvedUnitResult result = allResults[0];
+ var result = allResults[0] as ResolvedUnitResult;
expect(result.path, testFile);
_assertTopLevelVarType(result.unit!, 'V', 'double');
}
@@ -2739,9 +2751,9 @@
// c.dart was added after a.dart, so it is analyzed after a.dart,
// so we know that a.dart is the library of c.dart, so no errors.
- ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+ var result =
+ allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
expect(result.errors, isEmpty);
- expect(result.unit, isNull);
}
// Update a.dart so that c.dart is not a part.
@@ -2752,9 +2764,9 @@
// Now c.dart does not have a library context, so A and B cannot be
// resolved, so there are errors.
- ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+ var result =
+ allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
expect(result.errors, isNotEmpty);
- expect(result.unit, isNull);
}
}
@@ -2788,9 +2800,9 @@
// a.dart, but we cannot find the library for it, so we delay analysis
// until all other files are analyzed, including a.dart, after which we
// analyze the delayed parts.
- ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+ var result =
+ allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
expect(result.errors, isEmpty);
- expect(result.unit, isNull);
}
test_part_results_noLibrary() async {
@@ -2808,9 +2820,9 @@
// There is no library which c.dart is a part of, so it has unresolved
// A and B references.
- ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+ var result =
+ allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
expect(result.errors, isNotEmpty);
- expect(result.unit, isNull);
}
test_part_results_priority_beforeLibrary() async {
@@ -2844,7 +2856,9 @@
// a.dart, but we cannot find the library for it, so we delay analysis
// until all other files are analyzed, including a.dart, after which we
// analyze the delayed parts.
- ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+ ResolvedUnitResult result = allResults
+ .whereType<ResolvedUnitResult>()
+ .lastWhere((r) => r.path == c);
expect(result.errors, isEmpty);
expect(result.unit, isNotNull);
}
@@ -2866,11 +2880,15 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(2));
{
- ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+ ResolvedUnitResult ar = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == a);
_assertTopLevelVarType(ar.unit!, 'A', 'int');
}
{
- ResolvedUnitResult br = allResults.firstWhere((r) => r.path == b);
+ ResolvedUnitResult br = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == b);
_assertTopLevelVarType(br.unit!, 'B', 'int');
}
allResults.clear();
@@ -2886,7 +2904,9 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
{
- ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+ ResolvedUnitResult ar = allResults
+ .whereType<ResolvedUnitResult>()
+ .firstWhere((r) => r.path == a);
_assertTopLevelVarType(ar.unit!, 'A', 'double');
}
}
@@ -3092,7 +3112,7 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
- ResolvedUnitResult result = allResults.single;
+ var result = allResults.single as ResolvedUnitResult;
expect(result.path, testFile);
expect(result.uri.toString(), 'package:test/test.dart');
expect(result.content, content);
@@ -3119,7 +3139,7 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(3));
- ResolvedUnitResult result = allResults[0];
+ var result = allResults[0] as ResolvedUnitResult;
expect(result.path, b);
expect(result.unit, isNotNull);
expect(result.errors, hasLength(0));
@@ -3131,11 +3151,9 @@
await waitForIdleWithoutExceptions();
expect(allResults, hasLength(1));
- ResolvedUnitResult result = allResults.single;
+ var result = allResults.single as ErrorsResult;
expect(result.path, testFile);
expect(result.uri.toString(), 'package:test/test.dart');
- expect(result.content, isNull);
- expect(result.unit, isNull);
expect(result.errors, hasLength(0));
}
diff --git a/pkg/analyzer_cli/test/options_test.dart b/pkg/analyzer_cli/test/options_test.dart
index 242c86d..8d3a0fa 100644
--- a/pkg/analyzer_cli/test/options_test.dart
+++ b/pkg/analyzer_cli/test/options_test.dart
@@ -318,7 +318,6 @@
_parse(['a.dart']);
var builderOptions = commandLineOptions.contextBuilderOptions;
expect(builderOptions, isNotNull);
- expect(builderOptions.dartSdkSummaryPath, isNull);
expect(builderOptions.declaredVariables, isEmpty);
expect(builderOptions.defaultAnalysisOptionsFilePath, isNull);
expect(builderOptions.defaultPackageFilePath, isNull);
diff --git a/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart b/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
index 5b20db1..09e4bf0 100644
--- a/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
+++ b/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
@@ -4,7 +4,7 @@
/*class: A:
class_unit=1{libb},
- type_unit=2{libb, liba}
+ type_unit=2{liba, libb}
*/
/*member: A.:member_unit=1{libb}*/
class A {}
@@ -25,7 +25,7 @@
/*class: D:
class_unit=1{libb},
- type_unit=2{libb, liba}
+ type_unit=2{liba, libb}
*/
/*member: D.:member_unit=1{libb}*/
class D {}
diff --git a/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart b/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
index d32f920..9c29878 100644
--- a/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
@@ -6,7 +6,7 @@
a_pre_fragments=[
p1: {units: [3{liba}], usedBy: [], needs: []},
p2: {units: [1{libb}], usedBy: [], needs: []},
- p3: {units: [2{libb, liba}], usedBy: [], needs: []}],
+ p3: {units: [2{liba, libb}], usedBy: [], needs: []}],
b_finalized_fragments=[
f1: [3{liba}],
f2: [1{libb}]],
@@ -19,7 +19,7 @@
a_pre_fragments=[
p1: {units: [3{liba}], usedBy: [p3], needs: []},
p2: {units: [1{libb}], usedBy: [p3], needs: []},
- p3: {units: [2{libb, liba}], usedBy: [], needs: [p1, p2]}],
+ p3: {units: [2{liba, libb}], usedBy: [], needs: [p1, p2]}],
b_finalized_fragments=[
f1: [3{liba}],
f2: [1{libb}]],
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
index 621ba83..7ef31f8 100644
--- a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
@@ -5,24 +5,10 @@
// @dart = 2.7
import 'dart:io' hide Link;
-import 'package:_fe_analyzer_shared/src/testing/features.dart';
import 'package:async_helper/async_helper.dart';
-import 'package:compiler/src/closure.dart';
-import 'package:compiler/src/common.dart';
-import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/deferred_load.dart';
-import 'package:compiler/src/elements/entities.dart';
-import 'package:compiler/src/ir/util.dart';
-import 'package:compiler/src/js_model/element_map.dart';
-import 'package:compiler/src/js_model/js_world.dart';
-import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart';
-import 'package:compiler/src/kernel/kernel_strategy.dart';
-import 'package:expect/expect.dart';
-import '../equivalence/id_equivalence.dart';
import '../equivalence/id_equivalence_helper.dart';
-import 'package:compiler/src/constants/values.dart';
-
-import 'package:kernel/ast.dart' as ir;
+import 'deferred_loading_test_helper.dart';
/// Add in options to pass to the compiler like
/// `Flags.disableTypeInference` or `Flags.disableInlining`
@@ -44,342 +30,3 @@
[twoDeferredFragmentConfig, threeDeferredFragmentConfig]);
});
}
-
-// For ease of testing and making our tests easier to read, we impose an
-// artificial constraint of requiring every deferred import use a different
-// named prefix per test. We enforce this constraint here by checking that no
-// prefix name responds to two different libraries.
-Map<String, Uri> importPrefixes = {};
-
-String importPrefixString(OutputUnit unit) {
- StringBuffer sb = StringBuffer();
- bool first = true;
- for (ImportEntity import in unit.imports) {
- if (!first) sb.write(', ');
- sb.write('${import.name}');
- first = false;
- Expect.isTrue(import.isDeferred);
-
- if (importPrefixes.containsKey(import.name)) {
- var existing = importPrefixes[import.name];
- var current = import.enclosingLibraryUri;
- Expect.equals(
- existing,
- current,
- '\n Duplicate prefix \'${import.name}\' used in both:\n'
- ' - $existing and\n'
- ' - $current.\n'
- ' We require using unique prefixes on these tests to make '
- 'the expectations more readable.');
- }
- importPrefixes[import.name] = import.enclosingLibraryUri;
- }
- return sb.toString();
-}
-
-/// Create a consistent string representation of [OutputUnit]s for both
-/// KImportEntities and ImportElements.
-String outputUnitString(OutputUnit unit) {
- if (unit == null) return 'none';
- String sb = importPrefixString(unit);
- return '${unit.name}{$sb}';
-}
-
-Map<String, List<PreFragment>> buildPreFragmentMap(
- Map<String, List<FinalizedFragment>> fragmentsToLoad,
- List<PreFragment> preDeferredFragments) {
- Map<FinalizedFragment, PreFragment> fragmentMap = {};
- for (var preFragment in preDeferredFragments) {
- fragmentMap[preFragment.finalizedFragment] = preFragment;
- }
- Map<String, List<PreFragment>> preFragmentMap = {};
- fragmentsToLoad.forEach((loadId, fragments) {
- List<PreFragment> preFragments = [];
- for (var fragment in fragments) {
- preFragments.add(fragmentMap[fragment]);
- }
- preFragmentMap[loadId] = preFragments.toList();
- });
- return preFragmentMap;
-}
-
-class Tags {
- static const String cls = 'class_unit';
- static const String member = 'member_unit';
- static const String closure = 'closure_unit';
- static const String constants = 'constants';
- static const String type = 'type_unit';
- // The below tags appear in a single block comment in the main file.
- // To keep them appearing in sequential order we prefix characters.
- static const String preFragments = 'a_pre_fragments';
- static const String finalizedFragments = 'b_finalized_fragments';
- static const String steps = 'c_steps';
-}
-
-class OutputUnitDataComputer extends DataComputer<Features> {
- const OutputUnitDataComputer();
-
- /// OutputData for [member] as a kernel based element.
- ///
- /// At this point the compiler has already been run, so it is holding the
- /// relevant OutputUnits, we just need to extract that information from it. We
- /// fill [actualMap] with the data computed about what the resulting OutputUnit
- /// is.
- @override
- void computeMemberData(Compiler compiler, MemberEntity member,
- Map<Id, ActualData<Features>> actualMap,
- {bool verbose: false}) {
- JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
- JsToElementMap elementMap = closedWorld.elementMap;
- MemberDefinition definition = elementMap.getMemberDefinition(member);
- OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
- closedWorld.outputUnitData, closedWorld.closureDataLookup)
- .run(definition.node);
- }
-
- @override
- void computeClassData(Compiler compiler, ClassEntity cls,
- Map<Id, ActualData<Features>> actualMap,
- {bool verbose: false}) {
- JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
- JsToElementMap elementMap = closedWorld.elementMap;
- ClassDefinition definition = elementMap.getClassDefinition(cls);
- OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
- closedWorld.outputUnitData, closedWorld.closureDataLookup)
- .computeForClass(definition.node);
- }
-
- @override
- void computeLibraryData(Compiler compiler, LibraryEntity library,
- Map<Id, ActualData<Features>> actualMap,
- {bool verbose}) {
- KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
- ir.Library node = frontendStrategy.elementMap.getLibraryNode(library);
- List<PreFragment> preDeferredFragments = compiler
- .backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
- Map<String, List<FinalizedFragment>> fragmentsToLoad =
- compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
- Set<OutputUnit> omittedOutputUnits =
- compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
- PreFragmentsIrComputer(compiler.reporter, actualMap, preDeferredFragments,
- fragmentsToLoad, omittedOutputUnits)
- .computeForLibrary(node);
- }
-
- @override
- DataInterpreter<Features> get dataValidator =>
- const FeaturesDataInterpreter();
-}
-
-class PreFragmentsIrComputer extends IrDataExtractor<Features> {
- final List<PreFragment> _preDeferredFragments;
- final Map<String, List<FinalizedFragment>> _fragmentsToLoad;
- final Set<OutputUnit> _omittedOutputUnits;
-
- PreFragmentsIrComputer(
- DiagnosticReporter reporter,
- Map<Id, ActualData<Features>> actualMap,
- this._preDeferredFragments,
- this._fragmentsToLoad,
- this._omittedOutputUnits)
- : super(reporter, actualMap);
-
- @override
- Features computeLibraryValue(Id id, ir.Library library) {
- var name = '${library.importUri.pathSegments.last}';
- Features features = new Features();
- if (!name.startsWith('main')) return features;
-
- // First build a list of pre fragments and their dependencies.
- int index = 1;
- Map<FinalizedFragment, int> finalizedFragmentIndices = {};
- Map<PreFragment, int> preFragmentIndices = {};
- Map<int, PreFragment> reversePreFragmentIndices = {};
- Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {};
- for (var preFragment in _preDeferredFragments) {
- if (!preFragmentIndices.containsKey(preFragment)) {
- var finalizedFragment = preFragment.finalizedFragment;
- preFragmentIndices[preFragment] = index;
- finalizedFragmentIndices[finalizedFragment] = index;
- reversePreFragmentIndices[index] = preFragment;
- reverseFinalizedFragmentIndices[index] = finalizedFragment;
- index++;
- }
- }
-
- for (int i = 1; i < index; i++) {
- var preFragment = reversePreFragmentIndices[i];
- List<String> needs = [];
- List<OutputUnit> supplied = [];
- List<String> usedBy = [];
- for (var dependent in preFragment.successors) {
- if (preFragmentIndices.containsKey(dependent)) {
- usedBy.add('p${preFragmentIndices[dependent]}');
- }
- }
-
- for (var dependency in preFragment.predecessors) {
- if (preFragmentIndices.containsKey(dependency)) {
- needs.add('p${preFragmentIndices[dependency]}');
- }
- }
-
- for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
- supplied.add(emittedOutputUnit.outputUnit);
- }
-
- var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
- features.addElement(Tags.preFragments,
- 'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
- }
-
- // Now dump finalized fragments and load ids.
- for (int i = 1; i < index; i++) {
- var finalizedFragment = reverseFinalizedFragmentIndices[i];
- List<String> supplied = [];
-
- for (var codeFragment in finalizedFragment.codeFragments) {
- List<String> outputUnitStrings = [];
- for (var outputUnit in codeFragment.outputUnits) {
- if (!_omittedOutputUnits.contains(outputUnit)) {
- outputUnitStrings.add(outputUnitString(outputUnit));
- }
- }
- if (outputUnitStrings.isNotEmpty) {
- supplied.add(outputUnitStrings.join('+'));
- }
- }
-
- if (supplied.isNotEmpty) {
- var suppliedString = '[${supplied.join(', ')}]';
- features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString');
- }
- }
-
- _fragmentsToLoad.forEach((loadId, finalizedFragments) {
- List<String> finalizedFragmentNeeds = [];
- for (var finalizedFragment in finalizedFragments) {
- assert(finalizedFragmentIndices.containsKey(finalizedFragment));
- finalizedFragmentNeeds
- .add('f${finalizedFragmentIndices[finalizedFragment]}');
- }
- features.addElement(
- Tags.steps, '$loadId=(${finalizedFragmentNeeds.join(', ')})');
- });
-
- return features;
- }
-}
-
-class OutputUnitIrComputer extends IrDataExtractor<Features> {
- final JsToElementMap _elementMap;
- final OutputUnitData _data;
- final ClosureData _closureDataLookup;
-
- Set<String> _constants = {};
-
- OutputUnitIrComputer(
- DiagnosticReporter reporter,
- Map<Id, ActualData<Features>> actualMap,
- this._elementMap,
- this._data,
- this._closureDataLookup)
- : super(reporter, actualMap);
-
- Features getMemberValue(
- String tag, MemberEntity member, Set<String> constants) {
- Features features = Features();
- features.add(tag,
- value: outputUnitString(_data.outputUnitForMemberForTesting(member)));
- for (var constant in constants) {
- features.addElement(Tags.constants, constant);
- }
- return features;
- }
-
- @override
- Features computeClassValue(Id id, ir.Class node) {
- var cls = _elementMap.getClass(node);
- Features features = Features();
- features.add(Tags.cls,
- value: outputUnitString(_data.outputUnitForClassForTesting(cls)));
- features.add(Tags.type,
- value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)));
- return features;
- }
-
- @override
- Features computeMemberValue(Id id, ir.Member node) {
- if (node is ir.Field && node.isConst) {
- ir.Expression initializer = node.initializer;
- ConstantValue constant = _elementMap.getConstantValue(node, initializer);
- if (!constant.isPrimitive) {
- SourceSpan span = computeSourceSpanFromTreeNode(initializer);
- if (initializer is ir.ConstructorInvocation) {
- // Adjust the source-span to match the AST-based location. The kernel FE
- // skips the "const" keyword for the expression offset and any prefix in
- // front of the constructor. The "-6" is an approximation assuming that
- // there is just a single space after "const" and no prefix.
- // TODO(sigmund): offsets should be fixed in the FE instead.
- span = SourceSpan(span.uri, span.begin - 6, span.end - 6);
- }
- _registerValue(
- NodeId(span.begin, IdKind.node),
- Features.fromMap({
- Tags.member: outputUnitString(
- _data.outputUnitForConstantForTesting(constant))
- }),
- node,
- span,
- actualMap,
- reporter);
- }
- }
-
- Features features =
- getMemberValue(Tags.member, _elementMap.getMember(node), _constants);
- _constants = {};
- return features;
- }
-
- @override
- visitConstantExpression(ir.ConstantExpression node) {
- ConstantValue constant = _elementMap.getConstantValue(null, node);
- if (!constant.isPrimitive) {
- _constants.add('${constant.toStructuredText(_elementMap.types)}='
- '${outputUnitString(_data.outputUnitForConstant(constant))}');
- }
- return super.visitConstantExpression(node);
- }
-
- @override
- Features computeNodeValue(Id id, ir.TreeNode node) {
- if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
- ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
- return getMemberValue(Tags.closure, info.callMethod, const {});
- }
- return null;
- }
-}
-
-/// Set [actualMap] to hold a key of [id] with the computed data [value]
-/// corresponding to [object] at location [sourceSpan]. We also perform error
-/// checking to ensure that the same [id] isn't added twice.
-void _registerValue<T>(Id id, T value, Object object, SourceSpan sourceSpan,
- Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter reporter) {
- if (actualMap.containsKey(id)) {
- ActualData<T> existingData = actualMap[id];
- reportHere(reporter, sourceSpan,
- "Duplicate id ${id}, value=$value, object=$object");
- reportHere(
- reporter,
- sourceSpan,
- "Duplicate id ${id}, value=${existingData.value}, "
- "object=${existingData.object}");
- Expect.fail("Duplicate id $id.");
- }
- if (value != null) {
- actualMap[id] =
- ActualData<T>(id, value, sourceSpan.uri, sourceSpan.begin, object);
- }
-}
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart
new file mode 100644
index 0000000..8028c05
--- /dev/null
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart
@@ -0,0 +1,360 @@
+// Copyright (c) 2021, 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.
+
+// @dart = 2.7
+
+import 'package:_fe_analyzer_shared/src/testing/features.dart';
+import 'package:compiler/src/closure.dart';
+import 'package:compiler/src/common.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/deferred_load.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/ir/util.dart';
+import 'package:compiler/src/js_model/element_map.dart';
+import 'package:compiler/src/js_model/js_world.dart';
+import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart';
+import 'package:compiler/src/kernel/kernel_strategy.dart';
+import 'package:expect/expect.dart';
+import '../equivalence/id_equivalence.dart';
+import '../equivalence/id_equivalence_helper.dart';
+import 'package:compiler/src/constants/values.dart';
+
+import 'package:kernel/ast.dart' as ir;
+
+// For ease of testing and making our tests easier to read, we impose an
+// artificial constraint of requiring every deferred import use a different
+// named prefix per test. We enforce this constraint here by checking that no
+// prefix name responds to two different libraries.
+Map<String, Uri> importPrefixes = {};
+
+String importPrefixString(OutputUnit unit) {
+ List<String> importNames = [];
+ for (ImportEntity import in unit.imports) {
+ importNames.add(import.name);
+ Expect.isTrue(import.isDeferred);
+
+ if (importPrefixes.containsKey(import.name)) {
+ var existing = importPrefixes[import.name];
+ var current = import.enclosingLibraryUri;
+ Expect.equals(
+ existing,
+ current,
+ '\n Duplicate prefix \'${import.name}\' used in both:\n'
+ ' - $existing and\n'
+ ' - $current.\n'
+ ' We require using unique prefixes on these tests to make '
+ 'the expectations more readable.');
+ }
+ importPrefixes[import.name] = import.enclosingLibraryUri;
+ }
+ importNames.sort();
+ return importNames.join(', ');
+}
+
+/// Create a consistent string representation of [OutputUnit]s for both
+/// KImportEntities and ImportElements.
+String outputUnitString(OutputUnit unit) {
+ if (unit == null) return 'none';
+ String sb = importPrefixString(unit);
+ return '${unit.name}{$sb}';
+}
+
+Map<String, List<PreFragment>> buildPreFragmentMap(
+ Map<String, List<FinalizedFragment>> fragmentsToLoad,
+ List<PreFragment> preDeferredFragments) {
+ Map<FinalizedFragment, PreFragment> fragmentMap = {};
+ for (var preFragment in preDeferredFragments) {
+ fragmentMap[preFragment.finalizedFragment] = preFragment;
+ }
+ Map<String, List<PreFragment>> preFragmentMap = {};
+ fragmentsToLoad.forEach((loadId, fragments) {
+ List<PreFragment> preFragments = [];
+ for (var fragment in fragments) {
+ preFragments.add(fragmentMap[fragment]);
+ }
+ preFragmentMap[loadId] = preFragments.toList();
+ });
+ return preFragmentMap;
+}
+
+class Tags {
+ static const String cls = 'class_unit';
+ static const String member = 'member_unit';
+ static const String closure = 'closure_unit';
+ static const String constants = 'constants';
+ static const String type = 'type_unit';
+ // The below tags appear in a single block comment in the main file.
+ // To keep them appearing in sequential order we prefix characters.
+ static const String preFragments = 'a_pre_fragments';
+ static const String finalizedFragments = 'b_finalized_fragments';
+ static const String steps = 'c_steps';
+}
+
+class OutputUnitDataComputer extends DataComputer<Features> {
+ const OutputUnitDataComputer();
+
+ /// OutputData for [member] as a kernel based element.
+ ///
+ /// At this point the compiler has already been run, so it is holding the
+ /// relevant OutputUnits, we just need to extract that information from it. We
+ /// fill [actualMap] with the data computed about what the resulting OutputUnit
+ /// is.
+ @override
+ void computeMemberData(Compiler compiler, MemberEntity member,
+ Map<Id, ActualData<Features>> actualMap,
+ {bool verbose: false}) {
+ JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+ JsToElementMap elementMap = closedWorld.elementMap;
+ MemberDefinition definition = elementMap.getMemberDefinition(member);
+ OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
+ closedWorld.outputUnitData, closedWorld.closureDataLookup)
+ .run(definition.node);
+ }
+
+ @override
+ void computeClassData(Compiler compiler, ClassEntity cls,
+ Map<Id, ActualData<Features>> actualMap,
+ {bool verbose: false}) {
+ JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+ JsToElementMap elementMap = closedWorld.elementMap;
+ ClassDefinition definition = elementMap.getClassDefinition(cls);
+ OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
+ closedWorld.outputUnitData, closedWorld.closureDataLookup)
+ .computeForClass(definition.node);
+ }
+
+ @override
+ void computeLibraryData(Compiler compiler, LibraryEntity library,
+ Map<Id, ActualData<Features>> actualMap,
+ {bool verbose}) {
+ KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
+ ir.Library node = frontendStrategy.elementMap.getLibraryNode(library);
+ List<PreFragment> preDeferredFragments = compiler
+ .backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
+ Map<String, List<FinalizedFragment>> fragmentsToLoad =
+ compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
+ Set<OutputUnit> omittedOutputUnits =
+ compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
+ PreFragmentsIrComputer(compiler.reporter, actualMap, preDeferredFragments,
+ fragmentsToLoad, omittedOutputUnits)
+ .computeForLibrary(node);
+ }
+
+ @override
+ DataInterpreter<Features> get dataValidator =>
+ const FeaturesDataInterpreter();
+}
+
+class PreFragmentsIrComputer extends IrDataExtractor<Features> {
+ final List<PreFragment> _preDeferredFragments;
+ final Map<String, List<FinalizedFragment>> _fragmentsToLoad;
+ final Set<OutputUnit> _omittedOutputUnits;
+
+ PreFragmentsIrComputer(
+ DiagnosticReporter reporter,
+ Map<Id, ActualData<Features>> actualMap,
+ this._preDeferredFragments,
+ this._fragmentsToLoad,
+ this._omittedOutputUnits)
+ : super(reporter, actualMap);
+
+ @override
+ Features computeLibraryValue(Id id, ir.Library library) {
+ var name = '${library.importUri.pathSegments.last}';
+ Features features = new Features();
+ if (!name.startsWith('main')) return features;
+
+ // First build a list of pre fragments and their dependencies.
+ int index = 1;
+ Map<FinalizedFragment, int> finalizedFragmentIndices = {};
+ Map<PreFragment, int> preFragmentIndices = {};
+ Map<int, PreFragment> reversePreFragmentIndices = {};
+ Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {};
+ for (var preFragment in _preDeferredFragments) {
+ if (!preFragmentIndices.containsKey(preFragment)) {
+ var finalizedFragment = preFragment.finalizedFragment;
+ preFragmentIndices[preFragment] = index;
+ finalizedFragmentIndices[finalizedFragment] = index;
+ reversePreFragmentIndices[index] = preFragment;
+ reverseFinalizedFragmentIndices[index] = finalizedFragment;
+ index++;
+ }
+ }
+
+ for (int i = 1; i < index; i++) {
+ var preFragment = reversePreFragmentIndices[i];
+ List<String> needs = [];
+ List<OutputUnit> supplied = [];
+ List<String> usedBy = [];
+ for (var dependent in preFragment.successors) {
+ if (preFragmentIndices.containsKey(dependent)) {
+ usedBy.add('p${preFragmentIndices[dependent]}');
+ }
+ }
+
+ for (var dependency in preFragment.predecessors) {
+ if (preFragmentIndices.containsKey(dependency)) {
+ needs.add('p${preFragmentIndices[dependency]}');
+ }
+ }
+
+ for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
+ supplied.add(emittedOutputUnit.outputUnit);
+ }
+
+ var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
+ features.addElement(Tags.preFragments,
+ 'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
+ }
+
+ // Now dump finalized fragments and load ids.
+ for (int i = 1; i < index; i++) {
+ var finalizedFragment = reverseFinalizedFragmentIndices[i];
+ List<String> supplied = [];
+
+ for (var codeFragment in finalizedFragment.codeFragments) {
+ List<String> outputUnitStrings = [];
+ for (var outputUnit in codeFragment.outputUnits) {
+ if (!_omittedOutputUnits.contains(outputUnit)) {
+ outputUnitStrings.add(outputUnitString(outputUnit));
+ }
+ }
+ if (outputUnitStrings.isNotEmpty) {
+ supplied.add(outputUnitStrings.join('+'));
+ }
+ }
+
+ if (supplied.isNotEmpty) {
+ var suppliedString = '[${supplied.join(', ')}]';
+ features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString');
+ }
+ }
+
+ _fragmentsToLoad.forEach((loadId, finalizedFragments) {
+ List<String> finalizedFragmentNeeds = [];
+ for (var finalizedFragment in finalizedFragments) {
+ assert(finalizedFragmentIndices.containsKey(finalizedFragment));
+ finalizedFragmentNeeds
+ .add('f${finalizedFragmentIndices[finalizedFragment]}');
+ }
+ features.addElement(
+ Tags.steps, '$loadId=(${finalizedFragmentNeeds.join(', ')})');
+ });
+
+ return features;
+ }
+}
+
+class OutputUnitIrComputer extends IrDataExtractor<Features> {
+ final JsToElementMap _elementMap;
+ final OutputUnitData _data;
+ final ClosureData _closureDataLookup;
+
+ Set<String> _constants = {};
+
+ OutputUnitIrComputer(
+ DiagnosticReporter reporter,
+ Map<Id, ActualData<Features>> actualMap,
+ this._elementMap,
+ this._data,
+ this._closureDataLookup)
+ : super(reporter, actualMap);
+
+ Features getMemberValue(
+ String tag, MemberEntity member, Set<String> constants) {
+ Features features = Features();
+ features.add(tag,
+ value: outputUnitString(_data.outputUnitForMemberForTesting(member)));
+ for (var constant in constants) {
+ features.addElement(Tags.constants, constant);
+ }
+ return features;
+ }
+
+ @override
+ Features computeClassValue(Id id, ir.Class node) {
+ var cls = _elementMap.getClass(node);
+ Features features = Features();
+ features.add(Tags.cls,
+ value: outputUnitString(_data.outputUnitForClassForTesting(cls)));
+ features.add(Tags.type,
+ value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)));
+ return features;
+ }
+
+ @override
+ Features computeMemberValue(Id id, ir.Member node) {
+ if (node is ir.Field && node.isConst) {
+ ir.Expression initializer = node.initializer;
+ ConstantValue constant = _elementMap.getConstantValue(node, initializer);
+ if (!constant.isPrimitive) {
+ SourceSpan span = computeSourceSpanFromTreeNode(initializer);
+ if (initializer is ir.ConstructorInvocation) {
+ // Adjust the source-span to match the AST-based location. The kernel FE
+ // skips the "const" keyword for the expression offset and any prefix in
+ // front of the constructor. The "-6" is an approximation assuming that
+ // there is just a single space after "const" and no prefix.
+ // TODO(sigmund): offsets should be fixed in the FE instead.
+ span = SourceSpan(span.uri, span.begin - 6, span.end - 6);
+ }
+ _registerValue(
+ NodeId(span.begin, IdKind.node),
+ Features.fromMap({
+ Tags.member: outputUnitString(
+ _data.outputUnitForConstantForTesting(constant))
+ }),
+ node,
+ span,
+ actualMap,
+ reporter);
+ }
+ }
+
+ Features features =
+ getMemberValue(Tags.member, _elementMap.getMember(node), _constants);
+ _constants = {};
+ return features;
+ }
+
+ @override
+ visitConstantExpression(ir.ConstantExpression node) {
+ ConstantValue constant = _elementMap.getConstantValue(null, node);
+ if (!constant.isPrimitive) {
+ _constants.add('${constant.toStructuredText(_elementMap.types)}='
+ '${outputUnitString(_data.outputUnitForConstant(constant))}');
+ }
+ return super.visitConstantExpression(node);
+ }
+
+ @override
+ Features computeNodeValue(Id id, ir.TreeNode node) {
+ if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
+ ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
+ return getMemberValue(Tags.closure, info.callMethod, const {});
+ }
+ return null;
+ }
+}
+
+/// Set [actualMap] to hold a key of [id] with the computed data [value]
+/// corresponding to [object] at location [sourceSpan]. We also perform error
+/// checking to ensure that the same [id] isn't added twice.
+void _registerValue<T>(Id id, T value, Object object, SourceSpan sourceSpan,
+ Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter reporter) {
+ if (actualMap.containsKey(id)) {
+ ActualData<T> existingData = actualMap[id];
+ reportHere(reporter, sourceSpan,
+ "Duplicate id ${id}, value=$value, object=$object");
+ reportHere(
+ reporter,
+ sourceSpan,
+ "Duplicate id ${id}, value=${existingData.value}, "
+ "object=${existingData.object}");
+ Expect.fail("Duplicate id $id.");
+ }
+ if (value != null) {
+ actualMap[id] =
+ ActualData<T>(id, value, sourceSpan.uri, sourceSpan.begin, object);
+ }
+}
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 922ad2b..79e6f1e 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -20,6 +20,7 @@
#include "vm/longjump.h"
#include "vm/message_handler.h"
#include "vm/object.h"
+#include "vm/object_graph_copy.h"
#include "vm/object_store.h"
#include "vm/port.h"
#include "vm/resolver.h"
@@ -111,10 +112,22 @@
PortMap::PostMessage(
Message::New(destination_port_id, obj.ptr(), Message::kNormalPriority));
} else {
- MessageWriter writer(can_send_any_object);
- // TODO(turnidge): Throw an exception when the return value is false?
- PortMap::PostMessage(writer.WriteMessage(obj, destination_port_id,
- Message::kNormalPriority));
+ const bool same_group = IsolateGroup::AreIsolateGroupsEnabled() &&
+ PortMap::IsReceiverInThisIsolateGroup(
+ destination_port_id, isolate->group());
+ if (same_group) {
+ const auto& copy = Object::Handle(CopyMutableObjectGraph(obj));
+ auto handle = isolate->group()->api_state()->AllocatePersistentHandle();
+ handle->set_ptr(copy.ptr());
+ std::unique_ptr<Message> message(
+ new Message(destination_port_id, handle, Message::kNormalPriority));
+ PortMap::PostMessage(std::move(message));
+ } else {
+ MessageWriter writer(can_send_any_object);
+ // TODO(turnidge): Throw an exception when the return value is false?
+ PortMap::PostMessage(writer.WriteMessage(obj, destination_port_id,
+ Message::kNormalPriority));
+ }
}
return Object::null();
}
diff --git a/runtime/platform/growable_array.h b/runtime/platform/growable_array.h
index c568862..34ad019 100644
--- a/runtime/platform/growable_array.h
+++ b/runtime/platform/growable_array.h
@@ -188,6 +188,10 @@
// The content is uninitialized after calling it.
void SetLength(intptr_t new_length);
+ // The content (if expanded) is uninitialized after calling it.
+ // The backing store (if expanded) will grow with by a power-of-2.
+ void Resize(intptr_t new_length);
+
// Sort the array in place.
inline void Sort(int compare(const T*, const T*));
@@ -211,9 +215,6 @@
T* data_;
Allocator* allocator_; // Used to (re)allocate the array.
- // Used for growing the array.
- void Resize(intptr_t new_length);
-
DISALLOW_COPY_AND_ASSIGN(BaseGrowableArray);
};
diff --git a/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart b/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
new file mode 100644
index 0000000..5dce054
--- /dev/null
+++ b/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2021, 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.
+
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+
+// The tests in this file will only succeed when isolate groups are enabled
+// (hence the VMOptions above).
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+import 'fast_object_copy_test.dart'
+ show UserObject, SendReceiveTestBase, notAllocatableInTLAB;
+
+// When running with isolate groups enabled, we can share all of the following
+// objects.
+final sharableObjects = [
+ 1,
+ 0xffffffffffffffff,
+ 'foobar',
+ const UserObject(1, 1.2, ''),
+ (() {
+ final rp = ReceivePort();
+ final sp = rp.sendPort;
+ rp.close();
+ return sp;
+ })(),
+ const [1, 2, 3],
+ const {1: 1, 2: 2, 3: 2},
+ const {1, 2, 3},
+ RegExp('a'),
+ Isolate.current.pauseCapability,
+ Int32x4(1, 2, 3, 4),
+];
+
+class SendReceiveTest extends SendReceiveTestBase {
+ Future runTests() async {
+ await testSharable();
+ await testSharable2();
+ }
+
+ Future testSharable() async {
+ final sharableObjectsCopy = await sendReceive([
+ ...sharableObjects,
+ ]);
+ Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+ for (int i = 0; i < sharableObjects.length; ++i) {
+ Expect.identical(sharableObjects[i], sharableObjectsCopy[i]);
+ }
+ }
+
+ Future testSharable2() async {
+ final sharableObjectsCopy = await sendReceive([
+ notAllocatableInTLAB,
+ ...sharableObjects,
+ ]);
+ Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+ Expect.equals(
+ notAllocatableInTLAB[0], (sharableObjectsCopy[0] as Uint8List)[0]);
+ for (int i = 0; i < sharableObjects.length; ++i) {
+ Expect.identical(sharableObjects[i], sharableObjectsCopy[i + 1]);
+ }
+ }
+}
+
+main() async {
+ await SendReceiveTest().run();
+}
diff --git a/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart b/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
index 00297e1..447026e 100644
--- a/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
+++ b/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
@@ -3,7 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
// VMOptions=
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
// The tests in this file are particularly for an implementation that tries to
// allocate the entire graph in BFS order using a fast new space allocation
@@ -28,7 +31,7 @@
final Uint8List largeInternalTypedData = Uint8List(20 * 1024 * 1024)..[0] = 42;
final Uint8List smallExternalTypedData =
- File(Platform.script.toFilePath()).readAsBytesSync();
+ File(Platform.script.toFilePath()).readAsBytesSync()..[0] = 21;
final Uint8List smallExternalTypedDataView =
Uint8List.view(smallExternalTypedData.buffer, 1, 1);
@@ -110,6 +113,17 @@
}
return;
}
+ if (a is Set) {
+ final cb = b as Set;
+ Expect.equals(a.length, cb.length);
+ final aKeys = a.toList();
+ final cbKeys = cb.toList();
+ for (int i = 0; i < a.length; ++i) {
+ expectGraphsMatch(aKeys[i], cbKeys[i]);
+ }
+ return;
+ }
+
throw 'Unexpected object encountered when matching object graphs $a / $b';
}
@@ -126,6 +140,8 @@
class HashIncrementer {
static int counter = 1;
+ const HashIncrementer();
+
int get hashCode => counter++;
bool operator ==(other) => identical(this, other);
}
@@ -135,21 +151,39 @@
final double unboxedDouble;
final dynamic slot;
- UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
+ const UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
}
-class SendReceiveTest {
+abstract class SendReceiveTestBase {
late final ReceivePort receivePort;
late final SendPort sendPort;
late final StreamIterator si;
- SendReceiveTest();
+ SendReceiveTestBase();
Future run() async {
receivePort = ReceivePort();
sendPort = receivePort.sendPort;
si = StreamIterator(receivePort);
+ await runTests();
+
+ si.cancel();
+ receivePort.close();
+ print('done');
+ }
+
+ Future runTests();
+
+ Future<T> sendReceive<T>(T graph) async {
+ sendPort.send(graph);
+ Expect.isTrue(await si.moveNext());
+ return si.current as T;
+ }
+}
+
+class SendReceiveTest extends SendReceiveTestBase {
+ Future runTests() async {
await testTransferrable();
await testTransferrable2();
await testTransferrable3();
@@ -161,6 +195,7 @@
await testExternalTypedData3();
await testExternalTypedData4();
await testExternalTypedData5();
+ await testExternalTypedData6();
await testInternalTypedDataView();
await testInternalTypedDataView2();
@@ -172,16 +207,18 @@
await testExternalTypedDataView3();
await testExternalTypedDataView4();
+ await testArray();
+
await testMapRehash();
await testMapRehash2();
await testMapRehash3();
+ await testSetRehash();
+ await testSetRehash2();
+ await testSetRehash3();
+
await testFastOnly();
await testSlowOnly();
-
- si.cancel();
- receivePort.close();
- print('done');
}
Future testTransferrable() async {
@@ -293,6 +330,16 @@
}
}
+ Future testExternalTypedData6() async {
+ print('testExternalTypedData6');
+ final etd = await sendReceive([
+ smallExternalTypedData,
+ largeExternalTypedData,
+ ]);
+ Expect.equals(21, etd[0][0]);
+ Expect.equals(42, etd[1][0]);
+ }
+
Future testInternalTypedDataView() async {
print('testInternalTypedDataView');
final graph = [
@@ -401,6 +448,18 @@
expectGraphsMatch(graph, copiedGraph);
}
+ Future testArray() async {
+ print('testArray');
+ final oldSpace = List<dynamic>.filled(1024 * 1024, null);
+ final newSpace = UserObject(1, 1.1, 'foobar');
+ oldSpace[0] = newSpace;
+ final oldSpaceCopy = await sendReceive(oldSpace);
+ final newSpaceCopy = oldSpaceCopy[0] as UserObject;
+ Expect.equals(newSpaceCopy.unboxedInt, 1);
+ Expect.equals(newSpaceCopy.unboxedDouble, 1.1);
+ Expect.equals(newSpaceCopy.slot, 'foobar');
+ }
+
Future testMapRehash() async {
print('testMapRehash');
final obj = Object();
@@ -437,7 +496,7 @@
Future testMapRehash3() async {
print('testMapRehash3');
- final obj = HashIncrementer();
+ final obj = const HashIncrementer();
final graph = [
{obj: 42},
notAllocatableInTLAB,
@@ -448,6 +507,57 @@
Expect.equals(before + 1, after);
}
+ Future testSetRehash() async {
+ print('testSetRehash');
+ final obj = Object();
+ final graph = <dynamic>[
+ <dynamic>{42, obj},
+ notAllocatableInTLAB,
+ ];
+ final result = await sendReceive(graph);
+ final setCopy = result[0] as Set<dynamic>;
+ Expect.equals(2, setCopy.length);
+ Expect.equals(42, setCopy.toList()[0]);
+ Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+ Expect.notIdentical(obj, setCopy.toList()[1]);
+ Expect.notEquals(
+ identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+ Expect.isFalse(setCopy.contains(obj));
+ Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+ }
+
+ Future testSetRehash2() async {
+ print('testSetRehash2');
+ final obj = Object();
+ final graph = <dynamic>[
+ notAllocatableInTLAB,
+ <dynamic>{42, obj},
+ ];
+ final result = await sendReceive(graph);
+ final setCopy = result[1] as Set<dynamic>;
+ Expect.equals(2, setCopy.length);
+ Expect.equals(42, setCopy.toList()[0]);
+ Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+ Expect.notIdentical(obj, setCopy.toList()[1]);
+ Expect.notEquals(
+ identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+ Expect.isFalse(setCopy.contains(obj));
+ Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+ }
+
+ Future testSetRehash3() async {
+ print('testSetRehash3');
+ final obj = const HashIncrementer();
+ final graph = [
+ {42, obj},
+ notAllocatableInTLAB,
+ ];
+ final int before = HashIncrementer.counter;
+ await sendReceive(graph);
+ final int after = HashIncrementer.counter;
+ Expect.equals(before + 1, after);
+ }
+
Future testFastOnly() async {
print('testFastOnly');
for (final smallPrimitive in smallPrimitives) {
@@ -469,12 +579,6 @@
await sendReceive([notAllocatableInTLAB, smallContainer]));
}
}
-
- Future<T> sendReceive<T>(T graph) async {
- sendPort.send(graph);
- Expect.isTrue(await si.moveNext());
- return si.current as T;
- }
}
main() async {
diff --git a/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart b/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
new file mode 100644
index 0000000..5dce054
--- /dev/null
+++ b/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2021, 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.
+
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+
+// The tests in this file will only succeed when isolate groups are enabled
+// (hence the VMOptions above).
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+import 'fast_object_copy_test.dart'
+ show UserObject, SendReceiveTestBase, notAllocatableInTLAB;
+
+// When running with isolate groups enabled, we can share all of the following
+// objects.
+final sharableObjects = [
+ 1,
+ 0xffffffffffffffff,
+ 'foobar',
+ const UserObject(1, 1.2, ''),
+ (() {
+ final rp = ReceivePort();
+ final sp = rp.sendPort;
+ rp.close();
+ return sp;
+ })(),
+ const [1, 2, 3],
+ const {1: 1, 2: 2, 3: 2},
+ const {1, 2, 3},
+ RegExp('a'),
+ Isolate.current.pauseCapability,
+ Int32x4(1, 2, 3, 4),
+];
+
+class SendReceiveTest extends SendReceiveTestBase {
+ Future runTests() async {
+ await testSharable();
+ await testSharable2();
+ }
+
+ Future testSharable() async {
+ final sharableObjectsCopy = await sendReceive([
+ ...sharableObjects,
+ ]);
+ Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+ for (int i = 0; i < sharableObjects.length; ++i) {
+ Expect.identical(sharableObjects[i], sharableObjectsCopy[i]);
+ }
+ }
+
+ Future testSharable2() async {
+ final sharableObjectsCopy = await sendReceive([
+ notAllocatableInTLAB,
+ ...sharableObjects,
+ ]);
+ Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+ Expect.equals(
+ notAllocatableInTLAB[0], (sharableObjectsCopy[0] as Uint8List)[0]);
+ for (int i = 0; i < sharableObjects.length; ++i) {
+ Expect.identical(sharableObjects[i], sharableObjectsCopy[i + 1]);
+ }
+ }
+}
+
+main() async {
+ await SendReceiveTest().run();
+}
diff --git a/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart b/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
index 9737462..d27b474 100644
--- a/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
@@ -3,7 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
// VMOptions=
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
// The tests in this file are particularly for an implementation that tries to
// allocate the entire graph in BFS order using a fast new space allocation
@@ -28,7 +31,7 @@
final Uint8List largeInternalTypedData = Uint8List(20 * 1024 * 1024)..[0] = 42;
final Uint8List smallExternalTypedData =
- File(Platform.script.toFilePath()).readAsBytesSync();
+ File(Platform.script.toFilePath()).readAsBytesSync()..[0] = 21;
final Uint8List smallExternalTypedDataView =
Uint8List.view(smallExternalTypedData.buffer, 1, 1);
@@ -110,6 +113,17 @@
}
return;
}
+ if (a is Set) {
+ final cb = b as Set;
+ Expect.equals(a.length, cb.length);
+ final aKeys = a.toList();
+ final cbKeys = cb.toList();
+ for (int i = 0; i < a.length; ++i) {
+ expectGraphsMatch(aKeys[i], cbKeys[i]);
+ }
+ return;
+ }
+
throw 'Unexpected object encountered when matching object graphs $a / $b';
}
@@ -126,6 +140,8 @@
class HashIncrementer {
static int counter = 1;
+ const HashIncrementer();
+
int get hashCode => counter++;
bool operator ==(other) => identical(this, other);
}
@@ -135,21 +151,39 @@
final double unboxedDouble;
final dynamic slot;
- UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
+ const UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
}
-class SendReceiveTest {
+abstract class SendReceiveTestBase {
ReceivePort receivePort;
SendPort sendPort;
StreamIterator si;
- SendReceiveTest();
+ SendReceiveTestBase();
Future run() async {
receivePort = ReceivePort();
sendPort = receivePort.sendPort;
si = StreamIterator(receivePort);
+ await runTests();
+
+ si.cancel();
+ receivePort.close();
+ print('done');
+ }
+
+ Future runTests();
+
+ Future<T> sendReceive<T>(T graph) async {
+ sendPort.send(graph);
+ Expect.isTrue(await si.moveNext());
+ return si.current as T;
+ }
+}
+
+class SendReceiveTest extends SendReceiveTestBase {
+ Future runTests() async {
await testTransferrable();
await testTransferrable2();
await testTransferrable3();
@@ -161,6 +195,7 @@
await testExternalTypedData3();
await testExternalTypedData4();
await testExternalTypedData5();
+ await testExternalTypedData6();
await testInternalTypedDataView();
await testInternalTypedDataView2();
@@ -172,16 +207,18 @@
await testExternalTypedDataView3();
await testExternalTypedDataView4();
+ await testArray();
+
await testMapRehash();
await testMapRehash2();
await testMapRehash3();
+ await testSetRehash();
+ await testSetRehash2();
+ await testSetRehash3();
+
await testFastOnly();
await testSlowOnly();
-
- si.cancel();
- receivePort.close();
- print('done');
}
Future testTransferrable() async {
@@ -293,6 +330,16 @@
}
}
+ Future testExternalTypedData6() async {
+ print('testExternalTypedData6');
+ final etd = await sendReceive([
+ smallExternalTypedData,
+ largeExternalTypedData,
+ ]);
+ Expect.equals(21, etd[0][0]);
+ Expect.equals(42, etd[1][0]);
+ }
+
Future testInternalTypedDataView() async {
print('testInternalTypedDataView');
final graph = [
@@ -401,6 +448,18 @@
expectGraphsMatch(graph, copiedGraph);
}
+ Future testArray() async {
+ print('testArray');
+ final oldSpace = List<dynamic>.filled(1024 * 1024, null);
+ final newSpace = UserObject(1, 1.1, 'foobar');
+ oldSpace[0] = newSpace;
+ final oldSpaceCopy = await sendReceive(oldSpace);
+ final newSpaceCopy = oldSpaceCopy[0] as UserObject;
+ Expect.equals(newSpaceCopy.unboxedInt, 1);
+ Expect.equals(newSpaceCopy.unboxedDouble, 1.1);
+ Expect.equals(newSpaceCopy.slot, 'foobar');
+ }
+
Future testMapRehash() async {
print('testMapRehash');
final obj = Object();
@@ -437,7 +496,7 @@
Future testMapRehash3() async {
print('testMapRehash3');
- final obj = HashIncrementer();
+ final obj = const HashIncrementer();
final graph = [
{obj: 42},
notAllocatableInTLAB,
@@ -448,6 +507,57 @@
Expect.equals(before + 1, after);
}
+ Future testSetRehash() async {
+ print('testSetRehash');
+ final obj = Object();
+ final graph = <dynamic>[
+ <dynamic>{42, obj},
+ notAllocatableInTLAB,
+ ];
+ final result = await sendReceive(graph);
+ final setCopy = result[0] as Set<dynamic>;
+ Expect.equals(2, setCopy.length);
+ Expect.equals(42, setCopy.toList()[0]);
+ Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+ Expect.notIdentical(obj, setCopy.toList()[1]);
+ Expect.notEquals(
+ identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+ Expect.isFalse(setCopy.contains(obj));
+ Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+ }
+
+ Future testSetRehash2() async {
+ print('testSetRehash2');
+ final obj = Object();
+ final graph = <dynamic>[
+ notAllocatableInTLAB,
+ <dynamic>{42, obj},
+ ];
+ final result = await sendReceive(graph);
+ final setCopy = result[1] as Set<dynamic>;
+ Expect.equals(2, setCopy.length);
+ Expect.equals(42, setCopy.toList()[0]);
+ Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+ Expect.notIdentical(obj, setCopy.toList()[1]);
+ Expect.notEquals(
+ identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+ Expect.isFalse(setCopy.contains(obj));
+ Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+ }
+
+ Future testSetRehash3() async {
+ print('testSetRehash3');
+ final obj = const HashIncrementer();
+ final graph = [
+ {42, obj},
+ notAllocatableInTLAB,
+ ];
+ final int before = HashIncrementer.counter;
+ await sendReceive(graph);
+ final int after = HashIncrementer.counter;
+ Expect.equals(before + 1, after);
+ }
+
Future testFastOnly() async {
print('testFastOnly');
for (final smallPrimitive in smallPrimitives) {
@@ -469,12 +579,6 @@
await sendReceive([notAllocatableInTLAB, smallContainer]));
}
}
-
- Future<T> sendReceive<T>(T graph) async {
- sendPort.send(graph);
- Expect.isTrue(await si.moveNext());
- return si.current as T;
- }
}
main() async {
diff --git a/runtime/vm/class_table.cc b/runtime/vm/class_table.cc
index ccba236..b5491bb 100644
--- a/runtime/vm/class_table.cc
+++ b/runtime/vm/class_table.cc
@@ -519,10 +519,17 @@
// This is called by snapshot reader and class finalizer.
ASSERT(cid < capacity_);
+ UpdateClassSize(cid, raw_cls);
+ table_.load()[cid] = raw_cls;
+}
+
+void ClassTable::UpdateClassSize(intptr_t cid, ClassPtr raw_cls) {
+ ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
+ ASSERT(!IsTopLevelCid(cid)); // "top-level" classes don't get instantiated
+ ASSERT(cid < capacity_);
const intptr_t size =
raw_cls == nullptr ? 0 : Class::host_instance_size(raw_cls);
shared_class_table_->SetSizeAt(cid, size);
- table_.load()[cid] = raw_cls;
}
#ifndef PRODUCT
diff --git a/runtime/vm/class_table.h b/runtime/vm/class_table.h
index 5ef0df1..e89b978 100644
--- a/runtime/vm/class_table.h
+++ b/runtime/vm/class_table.h
@@ -354,6 +354,7 @@
}
void SetAt(intptr_t index, ClassPtr raw_cls);
+ void UpdateClassSize(intptr_t cid, ClassPtr raw_cls);
bool IsValidIndex(intptr_t cid) const {
if (IsTopLevelCid(cid)) {
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index dfd02fa..b289ca1 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -5724,7 +5724,10 @@
void PostLoad(Deserializer* d, const Array& refs) {
auto isolate_group = d->isolate_group();
- isolate_group->class_table()->CopySizesFromClassObjects();
+ {
+ SafepointWriteRwLocker ml(d->thread(), isolate_group->program_lock());
+ isolate_group->class_table()->CopySizesFromClassObjects();
+ }
d->heap()->old_space()->EvaluateAfterLoading();
const Array& units =
@@ -7800,6 +7803,7 @@
{
TIMELINE_DURATION(thread(), Isolate, "ReadFill");
+ SafepointWriteRwLocker ml(thread(), isolate_group()->program_lock());
for (intptr_t i = 0; i < num_clusters_; i++) {
TIMELINE_DURATION(thread(), Isolate, clusters_[i]->name());
clusters_[i]->ReadFill(this, primary);
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index 5be85a1..4bbf74e 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -1867,9 +1867,13 @@
saved_class_table_.load(std::memory_order_relaxed);
ClassPtr* local_saved_tlc_class_table =
saved_tlc_class_table_.load(std::memory_order_relaxed);
- IG->class_table()->ResetAfterHotReload(
- local_saved_class_table, local_saved_tlc_class_table, saved_num_cids_,
- saved_num_tlc_cids_, is_rollback);
+ {
+ auto thread = Thread::Current();
+ SafepointWriteRwLocker sl(thread, thread->isolate_group()->program_lock());
+ IG->class_table()->ResetAfterHotReload(
+ local_saved_class_table, local_saved_tlc_class_table, saved_num_cids_,
+ saved_num_tlc_cids_, is_rollback);
+ }
saved_class_table_.store(nullptr, std::memory_order_release);
saved_tlc_class_table_.store(nullptr, std::memory_order_release);
}
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 7c9214a..77f8e4d 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -4023,8 +4023,11 @@
// fields.
const auto host_bitmap = CalculateFieldOffsets();
if (ptr() == isolate_group->class_table()->At(id())) {
- // Sets the new size in the class table.
- isolate_group->class_table()->SetAt(id(), ptr());
+ if (!ClassTable::IsTopLevelCid(id())) {
+ // Unless class is top-level, which don't get instantiated,
+ // sets the new size in the class table.
+ isolate_group->class_table()->UpdateClassSize(id(), ptr());
+ }
if (FLAG_precompiled_mode && !ClassTable::IsTopLevelCid(id())) {
isolate_group->shared_class_table()->SetUnboxedFieldsMapAt(id(),
host_bitmap);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index f6d88ea..d46d8b4 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -620,6 +620,8 @@
};
protected:
+ friend ObjectPtr AllocateObject(intptr_t, intptr_t);
+
// Used for extracting the C++ vtable during bringup.
Object() : ptr_(null_) {}
@@ -1511,6 +1513,9 @@
void set_num_native_fields(uint16_t value) const {
StoreNonPointer(&untag()->num_native_fields_, value);
}
+ static uint16_t NumNativeFieldsOf(ClassPtr clazz) {
+ return clazz->untag()->num_native_fields_;
+ }
#if !defined(DART_PRECOMPILED_RUNTIME)
CodePtr allocation_stub() const { return untag()->allocation_stub(); }
@@ -8978,6 +8983,11 @@
static ClassPtr Class();
static intptr_t Value(const SmiPtr raw_smi) { return RawSmiValue(raw_smi); }
+#if defined(DART_COMPRESSED_POINTERS)
+ static intptr_t Value(const CompressedSmiPtr raw_smi) {
+ return Smi::Value(static_cast<SmiPtr>(raw_smi.DecompressSmi()));
+ }
+#endif
static intptr_t RawValue(intptr_t value) {
return static_cast<intptr_t>(New(value));
@@ -11221,6 +11231,9 @@
static intptr_t function_offset() {
return OFFSET_OF(UntaggedClosure, function_);
}
+ static FunctionPtr FunctionOf(ClosurePtr closure) {
+ return closure.untag()->function();
+ }
#if defined(DART_PRECOMPILER)
FunctionTypePtr signature() const {
@@ -11237,6 +11250,9 @@
static intptr_t context_offset() {
return OFFSET_OF(UntaggedClosure, context_);
}
+ static ContextPtr ContextOf(ClosurePtr closure) {
+ return closure.untag()->context();
+ }
bool IsGeneric(Thread* thread) const { return NumTypeParameters(thread) > 0; }
intptr_t NumTypeParameters(Thread* thread) const;
diff --git a/runtime/vm/object_graph_copy.cc b/runtime/vm/object_graph_copy.cc
new file mode 100644
index 0000000..96e6771
--- /dev/null
+++ b/runtime/vm/object_graph_copy.cc
@@ -0,0 +1,1677 @@
+// Copyright (c) 2021, 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.
+
+#include "vm/object_graph_copy.h"
+#include "vm/dart_api_state.h"
+#include "vm/flags.h"
+#include "vm/heap/weak_table.h"
+#include "vm/longjump.h"
+#include "vm/object.h"
+#include "vm/snapshot.h"
+#include "vm/symbols.h"
+
+#define Z zone_
+
+// The list here contains two kinds of classes of objects
+// * objects that will be shared and we will therefore never need to copy
+// * objects that user object graphs should never reference
+#define FOR_UNSUPPORTED_CLASSES(V) \
+ V(AbstractType) \
+ V(ApiError) \
+ V(Bool) \
+ V(CallSiteData) \
+ V(Capability) \
+ V(Class) \
+ V(ClosureData) \
+ V(Code) \
+ V(CodeSourceMap) \
+ V(CompressedStackMaps) \
+ V(Context) \
+ V(ContextScope) \
+ V(DynamicLibrary) \
+ V(Error) \
+ V(ExceptionHandlers) \
+ V(FfiTrampolineData) \
+ V(Field) \
+ V(Function) \
+ V(FunctionType) \
+ V(FutureOr) \
+ V(ICData) \
+ V(Instance) \
+ V(Instructions) \
+ V(InstructionsSection) \
+ V(InstructionsTable) \
+ V(Int32x4) \
+ V(Integer) \
+ V(KernelProgramInfo) \
+ V(LanguageError) \
+ V(Library) \
+ V(LibraryPrefix) \
+ V(LoadingUnit) \
+ V(LocalVarDescriptors) \
+ V(MegamorphicCache) \
+ V(Mint) \
+ V(MirrorReference) \
+ V(MonomorphicSmiableCall) \
+ V(Namespace) \
+ V(Number) \
+ V(ObjectPool) \
+ V(PatchClass) \
+ V(PcDescriptors) \
+ V(Pointer) \
+ V(ReceivePort) \
+ V(RegExp) \
+ V(Script) \
+ V(Sentinel) \
+ V(SendPort) \
+ V(SingleTargetCache) \
+ V(Smi) \
+ V(StackTrace) \
+ V(SubtypeTestCache) \
+ V(Type) \
+ V(TypeArguments) \
+ V(TypeParameter) \
+ V(TypeParameters) \
+ V(TypeRef) \
+ V(TypedDataBase) \
+ V(UnhandledException) \
+ V(UnlinkedCall) \
+ V(UnwindError) \
+ V(UserTag) \
+ V(WeakProperty) \
+ V(WeakSerializationReference)
+
+namespace dart {
+
+DEFINE_FLAG(bool,
+ enable_fast_object_copy,
+ true,
+ "Enable fast path for fast object copy.");
+DEFINE_FLAG(bool,
+ gc_on_foc_slow_path,
+ false,
+ "Cause a GC when falling off the fast path for fast object copy.");
+
+const char* kFastAllocationFailed = "fast allocation failed";
+
+struct PtrTypes {
+ using Object = ObjectPtr;
+ static const dart::UntaggedObject* UntagObject(Object arg) {
+ return arg.untag();
+ }
+ static const dart::ObjectPtr GetObjectPtr(Object arg) { return arg; }
+ static const dart::Object& HandlifyObject(ObjectPtr arg) {
+ return dart::Object::Handle(arg);
+ }
+
+#define DO(V) \
+ using V = V##Ptr; \
+ static Untagged##V* Untag##V(V##Ptr arg) { return arg.untag(); } \
+ static V##Ptr Get##V##Ptr(V##Ptr arg) { return arg; } \
+ static V##Ptr Cast##V(ObjectPtr arg) { return dart::V::RawCast(arg); }
+ CLASS_LIST_FOR_HANDLES(DO)
+#undef DO
+};
+
+struct HandleTypes {
+ using Object = const Object&;
+ static const dart::UntaggedObject* UntagObject(Object arg) {
+ return arg.ptr().untag();
+ }
+ static dart::ObjectPtr GetObjectPtr(Object arg) { return arg.ptr(); }
+ static Object HandlifyObject(Object arg) { return arg; }
+
+#define DO(V) \
+ using V = const V&; \
+ static Untagged##V* Untag##V(V arg) { return arg.ptr().untag(); } \
+ static V##Ptr Get##V##Ptr(V arg) { return arg.ptr(); } \
+ static V Cast##V(const dart::Object& arg) { return dart::V::Cast(arg); }
+ CLASS_LIST_FOR_HANDLES(DO)
+#undef DO
+};
+
+DART_FORCE_INLINE
+static ObjectPtr Marker() {
+ return Object::unknown_constant().ptr();
+}
+
+DART_FORCE_INLINE
+static bool CanShareObject(uword tags) {
+ if ((tags & UntaggedObject::CanonicalBit::mask_in_place()) != 0) {
+ return true;
+ }
+ const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+ if (cid == kOneByteStringCid) return true;
+ if (cid == kTwoByteStringCid) return true;
+ if (cid == kExternalOneByteStringCid) return true;
+ if (cid == kExternalTwoByteStringCid) return true;
+ if (cid == kMintCid) return true;
+ if (cid == kImmutableArrayCid) return true;
+ if (cid == kNeverCid) return true;
+ if (cid == kSentinelCid) return true;
+#if defined(DART_PRECOMPILED_RUNTIME)
+ // In JIT mode we have field guards enabled which means
+ // double/float32x4/float64x2 boxes can be mutable and we therefore cannot
+ // share them.
+ if (cid == kDoubleCid || cid == kFloat32x4Cid || cid == kFloat64x2Cid) {
+ return true;
+ }
+#endif
+ if (cid == kInt32x4Cid) return true; // No field guards here.
+ if (cid == kSendPortCid) return true;
+ if (cid == kCapabilityCid) return true;
+ if (cid == kRegExpCid) return true;
+
+ return false;
+}
+
+// Whether executing `get:hashCode` (possibly in a different isolate) on an
+// object with the given [tags] might return a different answer than the source
+// object (if copying is needed) or on the same object (if the object is
+// shared).
+DART_FORCE_INLINE
+static bool MightNeedReHashing(ObjectPtr object) {
+ const uword tags = TagsFromUntaggedObject(object.untag());
+ const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+ // These use structural hash codes and will therefore always result in the
+ // same hash codes.
+ if (cid == kOneByteStringCid) return false;
+ if (cid == kTwoByteStringCid) return false;
+ if (cid == kExternalOneByteStringCid) return false;
+ if (cid == kExternalTwoByteStringCid) return false;
+ if (cid == kMintCid) return false;
+ if (cid == kDoubleCid) return false;
+ if (cid == kSendPortCid) return false;
+ if (cid == kCapabilityCid) return false;
+
+ // These are shared and use identity hash codes. If they are used as a key in
+ // a map or a value in a set, they will already have the identity hash code
+ // set.
+ if (cid == kImmutableArrayCid) return false;
+ if (cid == kRegExpCid) return false;
+ if (cid == kInt32x4Cid) return false;
+
+ // We copy those (instead of sharing them) - see [CanShareObjct]. They rely
+ // on the default hashCode implementation which uses identity hash codes
+ // (instead of structural hash code).
+ if (cid == kFloat32x4Cid || cid == kFloat64x2Cid) {
+ return !kDartPrecompiledRuntime;
+ }
+
+ // If the [tags] indicates this is a canonical object we'll share it instead
+ // of copying it. That would suggest we don't have to re-hash maps/sets
+ // containing this object on the receiver side.
+ //
+ // Though the object can be a constant of a user-defined class with a
+ // custom hash code that is misbehaving (e.g one that depends on global field
+ // state, ...). To be on the safe side we'll force re-hashing if such objects
+ // are encountered in maps/sets.
+ //
+ // => We might want to consider changing the implementation to avoid rehashing
+ // in such cases in the future and disambiguate the documentation.
+ return true;
+}
+
+DART_FORCE_INLINE
+uword TagsFromUntaggedObject(UntaggedObject* obj) {
+ return obj->tags_;
+}
+
+DART_FORCE_INLINE
+void SetNewSpaceTaggingWord(ObjectPtr to, classid_t cid, uint32_t size) {
+ uword tags = 0;
+
+ tags = UntaggedObject::SizeTag::update(size, tags);
+ tags = UntaggedObject::ClassIdTag::update(cid, tags);
+ tags = UntaggedObject::OldBit::update(false, tags);
+ tags = UntaggedObject::OldAndNotMarkedBit::update(false, tags);
+ tags = UntaggedObject::OldAndNotRememberedBit::update(false, tags);
+ tags = UntaggedObject::CanonicalBit::update(false, tags);
+ tags = UntaggedObject::NewBit::update(true, tags);
+#if defined(HASH_IN_OBJECT_HEADER)
+ tags = UntaggedObject::HashTag::update(0, tags);
+#endif
+ to.untag()->tags_ = tags;
+}
+
+DART_FORCE_INLINE
+ObjectPtr AllocateObject(intptr_t cid, intptr_t size) {
+#if defined(DART_COMPRESSED_POINTERS)
+ const bool compressed = true;
+#else
+ const bool compressed = false;
+#endif
+ return Object::Allocate(cid, size, Heap::kNew, compressed);
+}
+
+DART_FORCE_INLINE
+void UpdateLengthField(intptr_t cid, ObjectPtr from, ObjectPtr to) {
+ // We share these objects - never copy them.
+ ASSERT(!IsStringClassId(cid));
+ ASSERT(cid != kImmutableArrayCid);
+
+ // We update any in-heap variable sized object with the length to keep the
+ // length and the size in the object header in-sync for the GC.
+ if (cid == kArrayCid) {
+ static_cast<UntaggedArray*>(to.untag())->length_ =
+ static_cast<UntaggedArray*>(from.untag())->length_;
+ } else if (IsTypedDataClassId(cid)) {
+ static_cast<UntaggedTypedDataBase*>(to.untag())->length_ =
+ static_cast<UntaggedTypedDataBase*>(from.untag())->length_;
+ }
+}
+
+void InitializeExternalTypedData(intptr_t cid,
+ ExternalTypedDataPtr from,
+ ExternalTypedDataPtr to) {
+ auto raw_from = from.untag();
+ auto raw_to = to.untag();
+ const intptr_t length =
+ TypedData::ElementSizeInBytes(cid) * Smi::Value(raw_from->length_);
+
+ auto buffer = static_cast<uint8_t*>(malloc(length));
+ memmove(buffer, raw_from->data_, length);
+ raw_to->length_ = raw_from->length_;
+ raw_to->data_ = buffer;
+}
+
+void InitializeTypedDataView(TypedDataViewPtr obj) {
+ obj.untag()->typed_data_ = TypedDataBase::null();
+ obj.untag()->offset_in_bytes_ = 0;
+ obj.untag()->length_ = 0;
+}
+
+void FreeExternalTypedData(void* isolate_callback_data, void* buffer) {
+ free(buffer);
+}
+
+void FreeTransferablePeer(void* isolate_callback_data, void* peer) {
+ delete static_cast<TransferableTypedDataPeer*>(peer);
+}
+
+class ForwardMapBase {
+ public:
+ explicit ForwardMapBase(Thread* thread)
+ : thread_(thread), zone_(thread->zone()), isolate_(thread->isolate()) {}
+
+ protected:
+ friend class ObjectGraphCopier;
+
+ intptr_t GetObjectId(ObjectPtr object) {
+ if (object->IsNewObject()) {
+ return isolate_->forward_table_new()->GetValueExclusive(object);
+ } else {
+ return isolate_->forward_table_old()->GetValueExclusive(object);
+ }
+ }
+ void SetObjectId(ObjectPtr object, intptr_t id) {
+ if (object->IsNewObject()) {
+ isolate_->forward_table_new()->SetValueExclusive(object, id);
+ } else {
+ isolate_->forward_table_old()->SetValueExclusive(object, id);
+ }
+ }
+
+ void FinalizeTransferable(const TransferableTypedData& from,
+ const TransferableTypedData& to) {
+ // Get the old peer.
+ auto fpeer = static_cast<TransferableTypedDataPeer*>(
+ thread_->heap()->GetPeer(from.ptr()));
+ ASSERT(fpeer != nullptr && fpeer->data() != nullptr);
+ const intptr_t length = fpeer->length();
+
+ // Allocate new peer object with (data, length).
+ auto tpeer = new TransferableTypedDataPeer(fpeer->data(), length);
+ thread_->heap()->SetPeer(to.ptr(), tpeer);
+
+ // Move the handle itself to the new object.
+ fpeer->handle()->EnsureFreedExternal(thread_->isolate_group());
+ tpeer->set_handle(FinalizablePersistentHandle::New(
+ thread_->isolate_group(), to, tpeer, FreeTransferablePeer, length,
+ /*auto_delete=*/true));
+ fpeer->ClearData();
+ }
+
+ void FinalizeExternalTypedData(const ExternalTypedData& to) {
+ to.AddFinalizer(to.DataAddr(0), &FreeExternalTypedData, to.LengthInBytes());
+ }
+
+ Thread* thread_;
+ Zone* zone_;
+ Isolate* isolate_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ForwardMapBase);
+};
+
+class FastForwardMap : public ForwardMapBase {
+ public:
+ explicit FastForwardMap(Thread* thread)
+ : ForwardMapBase(thread),
+ raw_from_to_(thread->zone(), 20),
+ raw_transferables_from_to_(thread->zone(), 0),
+ raw_objects_to_rehash_(thread->zone(), 0) {
+ raw_from_to_.Resize(2);
+ raw_from_to_[0] = Object::null();
+ raw_from_to_[1] = Object::null();
+ fill_cursor_ = 2;
+ }
+
+ ObjectPtr ForwardedObject(ObjectPtr object) {
+ const intptr_t id = GetObjectId(object);
+ if (id == 0) return Marker();
+ return raw_from_to_[id + 1];
+ }
+
+ void Insert(ObjectPtr from, ObjectPtr to) {
+ ASSERT(ForwardedObject(from) == Marker());
+ ASSERT(raw_from_to_.length() == raw_from_to_.length());
+ const auto id = raw_from_to_.length();
+ SetObjectId(from, id);
+ raw_from_to_.Resize(id + 2);
+ raw_from_to_[id] = from;
+ raw_from_to_[id + 1] = to;
+ }
+
+ void AddTransferable(TransferableTypedDataPtr from,
+ TransferableTypedDataPtr to) {
+ raw_transferables_from_to_.Add(from);
+ raw_transferables_from_to_.Add(to);
+ }
+ void AddExternalTypedData(ExternalTypedDataPtr to) {
+ raw_external_typed_data_to_.Add(to);
+ }
+
+ void AddObjectToRehash(ObjectPtr to) { raw_objects_to_rehash_.Add(to); }
+
+ private:
+ friend class FastObjectCopy;
+ friend class ObjectGraphCopier;
+
+ GrowableArray<ObjectPtr> raw_from_to_;
+ GrowableArray<TransferableTypedDataPtr> raw_transferables_from_to_;
+ GrowableArray<ExternalTypedDataPtr> raw_external_typed_data_to_;
+ GrowableArray<ObjectPtr> raw_objects_to_rehash_;
+ intptr_t fill_cursor_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(FastForwardMap);
+};
+
+class SlowForwardMap : public ForwardMapBase {
+ public:
+ explicit SlowForwardMap(Thread* thread)
+ : ForwardMapBase(thread),
+ from_to_(thread->zone(), 20),
+ transferables_from_to_(thread->zone(), 0) {
+ from_to_.Resize(2);
+ from_to_[0] = &Object::null_object();
+ from_to_[1] = &Object::null_object();
+ fill_cursor_ = 2;
+ }
+
+ ObjectPtr ForwardedObject(ObjectPtr object) {
+ const intptr_t id = GetObjectId(object);
+ if (id == 0) return Marker();
+ return from_to_[id + 1]->ptr();
+ }
+
+ void Insert(ObjectPtr from, ObjectPtr to) {
+ ASSERT(ForwardedObject(from) == Marker());
+ const auto id = from_to_.length();
+ SetObjectId(from, id);
+ from_to_.Resize(id + 2);
+ from_to_[id] = &Object::Handle(Z, from);
+ from_to_[id + 1] = &Object::Handle(Z, to);
+ }
+
+ void AddTransferable(const TransferableTypedData& from,
+ const TransferableTypedData& to) {
+ transferables_from_to_.Add(&TransferableTypedData::Handle(from.ptr()));
+ transferables_from_to_.Add(&TransferableTypedData::Handle(to.ptr()));
+ }
+
+ void AddExternalTypedData(ExternalTypedDataPtr to) {
+ external_typed_data_.Add(&ExternalTypedData::Handle(to));
+ }
+
+ void AddObjectToRehash(const Object& to) {
+ objects_to_rehash_.Add(&Object::Handle(to.ptr()));
+ }
+
+ void FinalizeTransferables() {
+ for (intptr_t i = 0; i < transferables_from_to_.length(); i += 2) {
+ auto from = transferables_from_to_[i];
+ auto to = transferables_from_to_[i + 1];
+ FinalizeTransferable(*from, *to);
+ }
+ }
+
+ void FinalizeExternalTypedData() {
+ for (intptr_t i = 0; i < external_typed_data_.length(); i++) {
+ auto to = external_typed_data_[i];
+ ForwardMapBase::FinalizeExternalTypedData(*to);
+ }
+ }
+
+ private:
+ friend class SlowObjectCopy;
+ friend class ObjectGraphCopier;
+
+ GrowableArray<const Object*> from_to_;
+ GrowableArray<const TransferableTypedData*> transferables_from_to_;
+ GrowableArray<const ExternalTypedData*> external_typed_data_;
+ GrowableArray<const Object*> objects_to_rehash_;
+ intptr_t fill_cursor_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(SlowForwardMap);
+};
+
+class ObjectCopyBase {
+ public:
+ explicit ObjectCopyBase(Thread* thread)
+ : thread_(thread),
+ heap_base_(thread->heap_base()),
+ zone_(thread->zone()),
+ heap_(thread->isolate_group()->heap()),
+ class_table_(thread->isolate_group()->class_table()),
+ new_space_(heap_->new_space()),
+ tmp_(Object::Handle(thread->zone())) {}
+ ~ObjectCopyBase() {}
+
+ protected:
+ static ObjectPtr LoadPointer(ObjectPtr src, intptr_t offset) {
+ return src.untag()->LoadPointer(reinterpret_cast<ObjectPtr*>(
+ reinterpret_cast<uint8_t*>(src.untag()) + offset));
+ }
+ static CompressedObjectPtr LoadCompressedPointer(ObjectPtr src,
+ intptr_t offset) {
+ return src.untag()->LoadPointer(reinterpret_cast<CompressedObjectPtr*>(
+ reinterpret_cast<uint8_t*>(src.untag()) + offset));
+ }
+ static compressed_uword LoadCompressedNonPointerWord(ObjectPtr src,
+ intptr_t offset) {
+ return *reinterpret_cast<compressed_uword*>(
+ reinterpret_cast<uint8_t*>(src.untag()) + offset);
+ }
+ static void StorePointerBarrier(ObjectPtr obj,
+ intptr_t offset,
+ ObjectPtr value) {
+ obj.untag()->StorePointer(
+ reinterpret_cast<ObjectPtr*>(reinterpret_cast<uint8_t*>(obj.untag()) +
+ offset),
+ value);
+ }
+ static void StoreCompressedPointerBarrier(ObjectPtr obj,
+ intptr_t offset,
+ ObjectPtr value) {
+ obj.untag()->StoreCompressedPointer(
+ reinterpret_cast<CompressedObjectPtr*>(
+ reinterpret_cast<uint8_t*>(obj.untag()) + offset),
+ value);
+ }
+ void StoreCompressedLargeArrayPointerBarrier(ObjectPtr obj,
+ intptr_t offset,
+ ObjectPtr value) {
+ obj.untag()->StoreCompressedArrayPointer(
+ reinterpret_cast<CompressedObjectPtr*>(
+ reinterpret_cast<uint8_t*>(obj.untag()) + offset),
+ value, thread_);
+ }
+ static void StorePointerNoBarrier(ObjectPtr obj,
+ intptr_t offset,
+ ObjectPtr value) {
+ *reinterpret_cast<ObjectPtr*>(reinterpret_cast<uint8_t*>(obj.untag()) +
+ offset) = value;
+ }
+ template <typename T = ObjectPtr>
+ static void StoreCompressedPointerNoBarrier(ObjectPtr obj,
+ intptr_t offset,
+ T value) {
+ *reinterpret_cast<CompressedObjectPtr*>(
+ reinterpret_cast<uint8_t*>(obj.untag()) + offset) = value;
+ }
+ static void StoreCompressedNonPointerWord(ObjectPtr obj,
+ intptr_t offset,
+ compressed_uword value) {
+ *reinterpret_cast<compressed_uword*>(
+ reinterpret_cast<uint8_t*>(obj.untag()) + offset) = value;
+ }
+
+ DART_FORCE_INLINE
+ bool CanCopyObject(uword tags, ObjectPtr object) {
+ const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+ if (cid > kNumPredefinedCids) {
+ const bool has_native_fields =
+ Class::NumNativeFieldsOf(class_table_->At(cid)) != 0;
+ if (has_native_fields) {
+ exception_msg_ =
+ "Illegal argument in isolate message: (object has native fields)";
+ return false;
+ }
+ return true;
+ }
+#define HANDLE_ILLEGAL_CASE(Type) \
+ case k##Type##Cid: { \
+ exception_msg_ = \
+ "Illegal argument in isolate message: " \
+ "(object is a" #Type ")"; \
+ return false; \
+ }
+
+ switch (cid) {
+ HANDLE_ILLEGAL_CASE(MirrorReference)
+ HANDLE_ILLEGAL_CASE(ReceivePort)
+ HANDLE_ILLEGAL_CASE(StackTrace)
+ HANDLE_ILLEGAL_CASE(UserTag)
+ HANDLE_ILLEGAL_CASE(DynamicLibrary)
+ HANDLE_ILLEGAL_CASE(Pointer)
+ case kClosureCid: {
+ if (!Function::IsImplicitStaticClosureFunction(
+ Closure::FunctionOf(Closure::RawCast(object)))) {
+ exception_msg_ =
+ "Illegal argument in isolate message: (object is a closure)";
+ return false;
+ }
+ ASSERT(Closure::ContextOf(Closure::RawCast(object)) == Object::null());
+ return true;
+ }
+ default:
+ return true;
+ }
+ }
+
+ Thread* thread_;
+ uword heap_base_;
+ Zone* zone_;
+ Heap* heap_;
+ ClassTable* class_table_;
+ Scavenger* new_space_;
+ Object& tmp_;
+
+ const char* exception_msg_ = nullptr;
+};
+
+class FastObjectCopyBase : public ObjectCopyBase {
+ public:
+ using Types = PtrTypes;
+
+ explicit FastObjectCopyBase(Thread* thread)
+ : ObjectCopyBase(thread), fast_forward_map_(thread) {}
+
+ protected:
+ DART_FORCE_INLINE
+ void ForwardCompressedPointers(ObjectPtr src,
+ ObjectPtr dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ ForwardCompressedPointer(src, dst, offset);
+ }
+ }
+
+ DART_FORCE_INLINE
+ void ForwardCompressedPointers(ObjectPtr src,
+ ObjectPtr dst,
+ intptr_t offset,
+ intptr_t end_offset,
+ UnboxedFieldBitmap bitmap) {
+ if (bitmap.IsEmpty()) {
+ ForwardCompressedPointers(src, dst, offset, end_offset);
+ return;
+ }
+ intptr_t bit = offset >> kCompressedWordSizeLog2;
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ if (bitmap.Get(bit++)) {
+ StoreCompressedNonPointerWord(
+ dst, offset, LoadCompressedNonPointerWord(src, offset));
+ } else {
+ ForwardCompressedPointer(src, dst, offset);
+ }
+ }
+ }
+
+ void ForwardCompressedArrayPointers(intptr_t array_length,
+ ObjectPtr src,
+ ObjectPtr dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ ForwardCompressedPointer(src, dst, offset);
+ }
+ }
+
+ DART_FORCE_INLINE
+ void ForwardCompressedPointer(ObjectPtr src, ObjectPtr dst, intptr_t offset) {
+ auto value = LoadCompressedPointer(src, offset);
+ if (!value.IsHeapObject()) {
+ StoreCompressedPointerNoBarrier(dst, offset, value);
+ return;
+ }
+ auto value_decompressed = value.Decompress(heap_base_);
+ const uword tags = TagsFromUntaggedObject(value_decompressed.untag());
+ if (CanShareObject(tags)) {
+ StoreCompressedPointerNoBarrier(dst, offset, value);
+ return;
+ }
+
+ ObjectPtr existing_to =
+ fast_forward_map_.ForwardedObject(value_decompressed);
+ if (existing_to != Marker()) {
+ StoreCompressedPointerNoBarrier(dst, offset, existing_to);
+ return;
+ }
+
+ if (UNLIKELY(!CanCopyObject(tags, value_decompressed))) {
+ ASSERT(exception_msg_ != nullptr);
+ StoreCompressedPointerNoBarrier(dst, offset, Object::null());
+ return;
+ }
+
+ auto to = Forward(tags, value.Decompress(heap_base_));
+ StoreCompressedPointerNoBarrier(dst, offset, to);
+ }
+
+ ObjectPtr Forward(uword tags, ObjectPtr from) {
+ const intptr_t header_size = UntaggedObject::SizeTag::decode(tags);
+ const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+ const uword size =
+ header_size != 0 ? header_size : from.untag()->HeapSize();
+ if (Heap::IsAllocatableInNewSpace(size)) {
+ const uword alloc = new_space_->TryAllocate(thread_, size);
+ if (alloc != 0) {
+ ObjectPtr to(reinterpret_cast<UntaggedObject*>(alloc));
+ fast_forward_map_.Insert(from, to);
+
+ if (IsExternalTypedDataClassId(cid)) {
+ SetNewSpaceTaggingWord(to, cid, header_size);
+ InitializeExternalTypedData(cid, ExternalTypedData::RawCast(from),
+ ExternalTypedData::RawCast(to));
+ fast_forward_map_.AddExternalTypedData(
+ ExternalTypedData::RawCast(to));
+ } else if (IsTypedDataViewClassId(cid)) {
+ // We set the views backing store to `null` to satisfy an assertion in
+ // GCCompactor::VisitTypedDataViewPointers().
+ SetNewSpaceTaggingWord(to, cid, header_size);
+ InitializeTypedDataView(TypedDataView::RawCast(to));
+ }
+ return to;
+ }
+ }
+ exception_msg_ = kFastAllocationFailed;
+ return Marker();
+ }
+
+ void EnqueueTransferable(TransferableTypedDataPtr from,
+ TransferableTypedDataPtr to) {
+ fast_forward_map_.AddTransferable(from, to);
+ }
+ void EnqueueObjectToRehash(ObjectPtr to) {
+ fast_forward_map_.AddObjectToRehash(to);
+ }
+
+ static void StoreCompressedArrayPointers(intptr_t array_length,
+ ObjectPtr src,
+ ObjectPtr dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ StoreCompressedPointers(src, dst, offset, end_offset);
+ }
+ static void StoreCompressedPointers(ObjectPtr src,
+ ObjectPtr dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ StoreCompressedPointersNoBarrier(src, dst, offset, end_offset);
+ }
+ static void StoreCompressedPointersNoBarrier(ObjectPtr src,
+ ObjectPtr dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ for (; offset <= end_offset; offset += kCompressedWordSize) {
+ StoreCompressedPointerNoBarrier(dst, offset,
+ LoadCompressedPointer(src, offset));
+ }
+ }
+
+ protected:
+ friend class ObjectGraphCopier;
+
+ FastForwardMap fast_forward_map_;
+};
+
+class SlowObjectCopyBase : public ObjectCopyBase {
+ public:
+ using Types = HandleTypes;
+
+ explicit SlowObjectCopyBase(Thread* thread)
+ : ObjectCopyBase(thread), slow_forward_map_(thread) {}
+
+ protected:
+ DART_FORCE_INLINE
+ void ForwardCompressedPointers(const Object& src,
+ const Object& dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ ForwardCompressedPointer(src, dst, offset);
+ }
+ }
+
+ DART_FORCE_INLINE
+ void ForwardCompressedPointers(const Object& src,
+ const Object& dst,
+ intptr_t offset,
+ intptr_t end_offset,
+ UnboxedFieldBitmap bitmap) {
+ intptr_t bit = offset >> kCompressedWordSizeLog2;
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ if (bitmap.Get(bit++)) {
+ StoreCompressedNonPointerWord(
+ dst.ptr(), offset, LoadCompressedNonPointerWord(src.ptr(), offset));
+ } else {
+ ForwardCompressedPointer(src, dst, offset);
+ }
+ }
+ }
+
+ void ForwardCompressedArrayPointers(intptr_t array_length,
+ const Object& src,
+ const Object& dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ if (Array::UseCardMarkingForAllocation(array_length)) {
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ ForwardCompressedLargeArrayPointer(src, dst, offset);
+ }
+ } else {
+ for (; offset < end_offset; offset += kCompressedWordSize) {
+ ForwardCompressedPointer(src, dst, offset);
+ }
+ }
+ }
+
+ DART_FORCE_INLINE
+ void ForwardCompressedLargeArrayPointer(const Object& src,
+ const Object& dst,
+ intptr_t offset) {
+ auto value = LoadCompressedPointer(src.ptr(), offset);
+ if (!value.IsHeapObject()) {
+ StoreCompressedPointerNoBarrier(dst.ptr(), offset, value);
+ return;
+ }
+
+ auto value_decompressed = value.Decompress(heap_base_);
+ const uword tags = TagsFromUntaggedObject(value_decompressed.untag());
+ if (CanShareObject(tags)) {
+ StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset,
+ value_decompressed);
+ return;
+ }
+
+ ObjectPtr existing_to =
+ slow_forward_map_.ForwardedObject(value_decompressed);
+ if (existing_to != Marker()) {
+ StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset, existing_to);
+ return;
+ }
+
+ if (UNLIKELY(!CanCopyObject(tags, value_decompressed))) {
+ ASSERT(exception_msg_ != nullptr);
+ StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset,
+ Object::null());
+ return;
+ }
+
+ tmp_ = value_decompressed;
+ tmp_ = Forward(tags, tmp_); // Only this can cause allocation.
+ StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset, tmp_.ptr());
+ }
+ DART_FORCE_INLINE
+ void ForwardCompressedPointer(const Object& src,
+ const Object& dst,
+ intptr_t offset) {
+ auto value = LoadCompressedPointer(src.ptr(), offset);
+ if (!value.IsHeapObject()) {
+ StoreCompressedPointerNoBarrier(dst.ptr(), offset, value);
+ return;
+ }
+ auto value_decompressed = value.Decompress(heap_base_);
+ const uword tags = TagsFromUntaggedObject(value_decompressed.untag());
+ if (CanShareObject(tags)) {
+ StoreCompressedPointerBarrier(dst.ptr(), offset, value_decompressed);
+ return;
+ }
+
+ ObjectPtr existing_to =
+ slow_forward_map_.ForwardedObject(value_decompressed);
+ if (existing_to != Marker()) {
+ StoreCompressedPointerBarrier(dst.ptr(), offset, existing_to);
+ return;
+ }
+
+ if (UNLIKELY(!CanCopyObject(tags, value_decompressed))) {
+ ASSERT(exception_msg_ != nullptr);
+ StoreCompressedPointerNoBarrier(dst.ptr(), offset, Object::null());
+ return;
+ }
+
+ tmp_ = value_decompressed;
+ tmp_ = Forward(tags, tmp_); // Only this can cause allocation.
+ StoreCompressedPointerBarrier(dst.ptr(), offset, tmp_.ptr());
+ }
+ ObjectPtr Forward(uword tags, const Object& from) {
+ const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
+ intptr_t size = UntaggedObject::SizeTag::decode(tags);
+ if (size == 0) {
+ size = from.ptr().untag()->HeapSize();
+ }
+ ObjectPtr to = AllocateObject(cid, size);
+ slow_forward_map_.Insert(from.ptr(), to);
+ UpdateLengthField(cid, from.ptr(), to);
+ if (cid == kArrayCid && !Heap::IsAllocatableInNewSpace(size)) {
+ to.untag()->SetCardRememberedBitUnsynchronized();
+ }
+ if (IsExternalTypedDataClassId(cid)) {
+ InitializeExternalTypedData(cid, ExternalTypedData::RawCast(from.ptr()),
+ ExternalTypedData::RawCast(to));
+ slow_forward_map_.AddExternalTypedData(ExternalTypedData::RawCast(to));
+ } else if (IsTypedDataViewClassId(cid)) {
+ // We set the views backing store to `null` to satisfy an assertion in
+ // GCCompactor::VisitTypedDataViewPointers().
+ InitializeTypedDataView(TypedDataView::RawCast(to));
+ }
+ return to;
+ }
+ void EnqueueTransferable(const TransferableTypedData& from,
+ const TransferableTypedData& to) {
+ slow_forward_map_.AddTransferable(from, to);
+ }
+ void EnqueueObjectToRehash(const Object& to) {
+ slow_forward_map_.AddObjectToRehash(to);
+ }
+
+ void StoreCompressedArrayPointers(intptr_t array_length,
+ const Object& src,
+ const Object& dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ auto src_ptr = src.ptr();
+ auto dst_ptr = dst.ptr();
+ if (Array::UseCardMarkingForAllocation(array_length)) {
+ for (; offset <= end_offset; offset += kCompressedWordSize) {
+ StoreCompressedLargeArrayPointerBarrier(
+ dst_ptr, offset,
+ LoadCompressedPointer(src_ptr, offset).Decompress(heap_base_));
+ }
+ } else {
+ for (; offset <= end_offset; offset += kCompressedWordSize) {
+ StoreCompressedPointerBarrier(
+ dst_ptr, offset,
+ LoadCompressedPointer(src_ptr, offset).Decompress(heap_base_));
+ }
+ }
+ }
+ void StoreCompressedPointers(const Object& src,
+ const Object& dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ auto src_ptr = src.ptr();
+ auto dst_ptr = dst.ptr();
+ for (; offset <= end_offset; offset += kCompressedWordSize) {
+ StoreCompressedPointerBarrier(
+ dst_ptr, offset,
+ LoadCompressedPointer(src_ptr, offset).Decompress(heap_base_));
+ }
+ }
+ static void StoreCompressedPointersNoBarrier(const Object& src,
+ const Object& dst,
+ intptr_t offset,
+ intptr_t end_offset) {
+ auto src_ptr = src.ptr();
+ auto dst_ptr = dst.ptr();
+ for (; offset <= end_offset; offset += kCompressedWordSize) {
+ StoreCompressedPointerNoBarrier(dst_ptr, offset,
+ LoadCompressedPointer(src_ptr, offset));
+ }
+ }
+
+ protected:
+ friend class ObjectGraphCopier;
+
+ SlowForwardMap slow_forward_map_;
+};
+
+template <typename Base>
+class ObjectCopy : public Base {
+ public:
+ using Types = typename Base::Types;
+
+ explicit ObjectCopy(Thread* thread) : Base(thread) {}
+
+ void CopyPredefinedInstance(typename Types::Object from,
+ typename Types::Object to,
+ intptr_t cid) {
+ if (IsImplicitFieldClassId(cid)) {
+ CopyUserdefinedInstance(from, to);
+ return;
+ }
+ switch (cid) {
+#define COPY_TO(clazz) \
+ case clazz::kClassId: { \
+ typename Types::clazz casted_from = Types::Cast##clazz(from); \
+ typename Types::clazz casted_to = Types::Cast##clazz(to); \
+ Copy##clazz(casted_from, casted_to); \
+ return; \
+ }
+
+ CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(COPY_TO)
+ COPY_TO(Array)
+ COPY_TO(LinkedHashMap)
+ COPY_TO(LinkedHashSet)
+#undef COPY_TO
+
+#define COPY_TO(clazz) case kTypedData##clazz##Cid:
+
+ CLASS_LIST_TYPED_DATA(COPY_TO) {
+ typename Types::TypedData casted_from = Types::CastTypedData(from);
+ typename Types::TypedData casted_to = Types::CastTypedData(to);
+ CopyTypedData(casted_from, casted_to);
+ return;
+ }
+#undef COPY_TO
+
+ case kByteDataViewCid:
+#define COPY_TO(clazz) case kTypedData##clazz##ViewCid:
+ CLASS_LIST_TYPED_DATA(COPY_TO) {
+ typename Types::TypedDataView casted_from =
+ Types::CastTypedDataView(from);
+ typename Types::TypedDataView casted_to =
+ Types::CastTypedDataView(to);
+ CopyTypedDataView(casted_from, casted_to);
+ return;
+ }
+#undef COPY_TO
+
+#define COPY_TO(clazz) case kExternalTypedData##clazz##Cid:
+
+ CLASS_LIST_TYPED_DATA(COPY_TO) {
+ typename Types::ExternalTypedData casted_from =
+ Types::CastExternalTypedData(from);
+ typename Types::ExternalTypedData casted_to =
+ Types::CastExternalTypedData(to);
+ CopyExternalTypedData(casted_from, casted_to);
+ return;
+ }
+#undef COPY_TO
+ default:
+ break;
+ }
+
+ const Object& obj = Types::HandlifyObject(from);
+ FATAL1("Unexpected object: %s\n", obj.ToCString());
+ }
+
+#if defined(DART_PRECOMPILED_RUNTIME)
+ void CopyUserdefinedInstanceAOT(typename Types::Object from,
+ typename Types::Object to,
+ UnboxedFieldBitmap bitmap) {
+ const intptr_t instance_size = UntagObject(from)->HeapSize();
+ Base::ForwardCompressedPointers(from, to, kWordSize, instance_size, bitmap);
+ }
+#endif
+
+ void CopyUserdefinedInstance(typename Types::Object from,
+ typename Types::Object to) {
+ const intptr_t instance_size = UntagObject(from)->HeapSize();
+ Base::ForwardCompressedPointers(from, to, kWordSize, instance_size);
+ }
+
+ void CopyClosure(typename Types::Closure from, typename Types::Closure to) {
+ Base::StoreCompressedPointers(
+ from, to, OFFSET_OF(UntaggedClosure, instantiator_type_arguments_),
+ OFFSET_OF(UntaggedClosure, function_));
+ Base::ForwardCompressedPointer(from, to,
+ OFFSET_OF(UntaggedClosure, context_));
+ Base::StoreCompressedPointersNoBarrier(from, to,
+ OFFSET_OF(UntaggedClosure, hash_),
+ OFFSET_OF(UntaggedClosure, hash_));
+ ONLY_IN_PRECOMPILED(UntagClosure(to)->entry_point_ =
+ UntagClosure(from)->entry_point_);
+ }
+
+ void CopyArray(typename Types::Array from, typename Types::Array to) {
+ const intptr_t length = Smi::Value(UntagArray(from)->length());
+ Base::StoreCompressedArrayPointers(
+ length, from, to, OFFSET_OF(UntaggedArray, type_arguments_),
+ OFFSET_OF(UntaggedArray, type_arguments_));
+ Base::StoreCompressedPointersNoBarrier(from, to,
+ OFFSET_OF(UntaggedArray, length_),
+ OFFSET_OF(UntaggedArray, length_));
+ Base::ForwardCompressedArrayPointers(
+ length, from, to, Array::data_offset(),
+ Array::data_offset() + kCompressedWordSize * length);
+ }
+
+ void CopyGrowableObjectArray(typename Types::GrowableObjectArray from,
+ typename Types::GrowableObjectArray to) {
+ Base::StoreCompressedPointers(
+ from, to, OFFSET_OF(UntaggedGrowableObjectArray, type_arguments_),
+ OFFSET_OF(UntaggedGrowableObjectArray, type_arguments_));
+ Base::StoreCompressedPointersNoBarrier(
+ from, to, OFFSET_OF(UntaggedGrowableObjectArray, length_),
+ OFFSET_OF(UntaggedGrowableObjectArray, length_));
+ Base::ForwardCompressedPointer(
+ from, to, OFFSET_OF(UntaggedGrowableObjectArray, data_));
+ }
+
+ template <intptr_t one_for_set_two_for_map, typename T>
+ void CopyLinkedHashBase(T from,
+ T to,
+ UntaggedLinkedHashBase* from_untagged,
+ UntaggedLinkedHashBase* to_untagged) {
+ // We have to find out whether the map needs re-hashing on the receiver side
+ // due to keys being copied and the keys therefore possibly having different
+ // hash codes (e.g. due to user-defined hashCode implementation or due to
+ // new identity hash codes of the copied objects).
+ bool needs_rehashing = false;
+ ArrayPtr data = from_untagged->data_.Decompress(Base::heap_base_);
+ if (data != Array::null()) {
+ UntaggedArray* untagged_data = data.untag();
+ const intptr_t length = Smi::Value(untagged_data->length_);
+ auto key_value_pairs = untagged_data->data();
+ for (intptr_t i = 0; i < length; i += one_for_set_two_for_map) {
+ ObjectPtr key = key_value_pairs[i].Decompress(Base::heap_base_);
+ if (key->IsHeapObject()) {
+ if (MightNeedReHashing(key)) {
+ needs_rehashing = true;
+ break;
+ }
+ }
+ }
+ }
+
+ Base::StoreCompressedPointers(
+ from, to, OFFSET_OF(UntaggedLinkedHashBase, type_arguments_),
+ OFFSET_OF(UntaggedLinkedHashBase, type_arguments_));
+
+ // Compared with the snapshot-based (de)serializer we do preserve the same
+ // backing store (i.e. used_data/deleted_keys/data) and therefore do not
+ // magically shrink backing store based on usage.
+ //
+ // We do this to avoid making assumptions about the object graph and the
+ // linked hash map (e.g. assuming there's no other references to the data,
+ // assuming the linked hashmap is in a consistent state)
+ if (needs_rehashing) {
+ to_untagged->hash_mask_ = Smi::New(0);
+ to_untagged->index_ = TypedData::RawCast(Object::null());
+ Base::EnqueueObjectToRehash(to);
+ }
+
+ // From this point on we shouldn't use the raw pointers, since GC might
+ // happen when forwarding objects.
+ from_untagged = nullptr;
+ to_untagged = nullptr;
+
+ if (!needs_rehashing) {
+ Base::ForwardCompressedPointer(from, to,
+ OFFSET_OF(UntaggedLinkedHashBase, index_));
+ Base::StoreCompressedPointersNoBarrier(
+ from, to, OFFSET_OF(UntaggedLinkedHashBase, hash_mask_),
+ OFFSET_OF(UntaggedLinkedHashBase, hash_mask_));
+ }
+ Base::ForwardCompressedPointer(from, to,
+ OFFSET_OF(UntaggedLinkedHashBase, data_));
+ Base::StoreCompressedPointersNoBarrier(
+ from, to, OFFSET_OF(UntaggedLinkedHashBase, used_data_),
+ OFFSET_OF(UntaggedLinkedHashBase, used_data_));
+ Base::StoreCompressedPointersNoBarrier(
+ from, to, OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_),
+ OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_));
+ }
+
+ void CopyLinkedHashMap(typename Types::LinkedHashMap from,
+ typename Types::LinkedHashMap to) {
+ CopyLinkedHashBase<2, typename Types::LinkedHashMap>(
+ from, to, UntagLinkedHashMap(from), UntagLinkedHashMap(to));
+ }
+ void CopyLinkedHashSet(typename Types::LinkedHashSet from,
+ typename Types::LinkedHashSet to) {
+ CopyLinkedHashBase<1, typename Types::LinkedHashSet>(
+ from, to, UntagLinkedHashSet(from), UntagLinkedHashSet(to));
+ }
+
+ void CopyDouble(typename Types::Double from, typename Types::Double to) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ auto raw_from = UntagDouble(from);
+ auto raw_to = UntagDouble(to);
+ raw_to->value_ = raw_from->value_;
+#else
+ // Will be shared and not copied.
+ UNREACHABLE();
+#endif
+ }
+
+ void CopyFloat32x4(typename Types::Float32x4 from,
+ typename Types::Float32x4 to) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ auto raw_from = UntagFloat32x4(from);
+ auto raw_to = UntagFloat32x4(to);
+ raw_to->value_[0] = raw_from->value_[0];
+ raw_to->value_[1] = raw_from->value_[1];
+ raw_to->value_[2] = raw_from->value_[2];
+ raw_to->value_[3] = raw_from->value_[3];
+#else
+ // Will be shared and not copied.
+ UNREACHABLE();
+#endif
+ }
+
+ void CopyFloat64x2(typename Types::Float64x2 from,
+ typename Types::Float64x2 to) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ auto raw_from = UntagFloat64x2(from);
+ auto raw_to = UntagFloat64x2(to);
+ raw_to->value_[0] = raw_from->value_[0];
+ raw_to->value_[1] = raw_from->value_[1];
+#else
+ // Will be shared and not copied.
+ UNREACHABLE();
+#endif
+ }
+
+ void CopyTypedData(typename Types::TypedData from,
+ typename Types::TypedData to) {
+ auto raw_from = UntagTypedData(from);
+ auto raw_to = UntagTypedData(to);
+ const intptr_t cid = Types::GetTypedDataPtr(from)->GetClassId();
+ raw_to->length_ = raw_from->length_;
+ raw_to->RecomputeDataField();
+ const intptr_t length =
+ TypedData::ElementSizeInBytes(cid) * Smi::Value(raw_from->length_);
+ memmove(raw_to->data_, raw_from->data_, length);
+ }
+
+ void CopyTypedDataView(typename Types::TypedDataView from,
+ typename Types::TypedDataView to) {
+ // This will forward & initialize the typed data.
+ Base::ForwardCompressedPointer(
+ from, to, OFFSET_OF(UntaggedTypedDataView, typed_data_));
+
+ auto raw_from = UntagTypedDataView(from);
+ auto raw_to = UntagTypedDataView(to);
+ raw_to->length_ = raw_from->length_;
+ raw_to->offset_in_bytes_ = raw_from->offset_in_bytes_;
+ raw_to->data_ = nullptr;
+
+ if (raw_to->typed_data_.Decompress(Base::heap_base_) == Object::null()) {
+ ASSERT(Base::exception_msg_ != nullptr);
+ return;
+ }
+
+ const bool is_external =
+ raw_from->data_ != raw_from->DataFieldForInternalTypedData();
+ if (is_external) {
+ // The raw_to is fully initialized at this point (see handling of external
+ // typed data in [ForwardCompressedPointer])
+ raw_to->RecomputeDataField();
+ } else {
+ // The raw_to isn't initialized yet, but it's address is valid, so we can
+ // compute the data field it would use.
+ raw_to->RecomputeDataFieldForInternalTypedData();
+ }
+ const bool is_external2 =
+ raw_to->data_ != raw_to->DataFieldForInternalTypedData();
+ ASSERT(is_external == is_external2);
+ }
+
+ void CopyExternalTypedData(typename Types::ExternalTypedData from,
+ typename Types::ExternalTypedData to) {
+ // The external typed data is initialized on the forwarding pass (where
+ // normally allocation but not initialization happens), so views on it
+ // can be initialized immediately.
+#if defined(DEBUG)
+ auto raw_from = UntagExternalTypedData(from);
+ auto raw_to = UntagExternalTypedData(to);
+ ASSERT(raw_to->data_ != nullptr);
+ ASSERT(raw_to->length_ == raw_from->length_);
+#endif
+ }
+
+ void CopyTransferableTypedData(typename Types::TransferableTypedData from,
+ typename Types::TransferableTypedData to) {
+ // The [TransferableTypedData] is an empty object with an associated heap
+ // peer object.
+ // -> We'll validate that there's a peer and enqueue the transferable to be
+ // transferred if the transitive copy is successful.
+ auto fpeer = static_cast<TransferableTypedDataPeer*>(
+ Base::heap_->GetPeer(Types::GetTransferableTypedDataPtr(from)));
+ ASSERT(fpeer != nullptr);
+ if (fpeer->data() == nullptr) {
+ Base::exception_msg_ =
+ "Illegal argument in isolate message"
+ " : (TransferableTypedData has been transferred already)";
+ return;
+ }
+ Base::EnqueueTransferable(from, to);
+ }
+
+#define DEFINE_UNSUPPORTED(clazz) \
+ void Copy##clazz(typename Types::clazz from, typename Types::clazz to) { \
+ FATAL("Objects of type " #clazz " should not occur in object graphs"); \
+ }
+
+ FOR_UNSUPPORTED_CLASSES(DEFINE_UNSUPPORTED)
+
+#undef DEFINE_UNSUPPORTED
+
+ UntaggedObject* UntagObject(typename Types::Object obj) {
+ return Types::GetObjectPtr(obj).Decompress(Base::heap_base_).untag();
+ }
+
+#define DO(V) \
+ DART_FORCE_INLINE \
+ Untagged##V* Untag##V(typename Types::V obj) { \
+ return Types::Get##V##Ptr(obj).Decompress(Base::heap_base_).untag(); \
+ }
+ CLASS_LIST_FOR_HANDLES(DO)
+#undef DO
+};
+
+class FastObjectCopy : public ObjectCopy<FastObjectCopyBase> {
+ public:
+ explicit FastObjectCopy(Thread* thread) : ObjectCopy(thread) {}
+ ~FastObjectCopy() {}
+
+ ObjectPtr TryCopyGraphFast(ObjectPtr root) {
+ NoSafepointScope no_safepoint_scope;
+
+ ObjectPtr root_copy = Forward(TagsFromUntaggedObject(root.untag()), root);
+ if (root_copy == Marker()) {
+ return root_copy;
+ }
+ while (fast_forward_map_.fill_cursor_ <
+ fast_forward_map_.raw_from_to_.length()) {
+ const intptr_t index = fast_forward_map_.fill_cursor_;
+ ObjectPtr from = fast_forward_map_.raw_from_to_[index];
+ ObjectPtr to = fast_forward_map_.raw_from_to_[index + 1];
+ FastCopyObject(from, to);
+ if (exception_msg_ != nullptr) {
+ return root_copy;
+ }
+ fast_forward_map_.fill_cursor_ += 2;
+ }
+ if (root_copy != Marker()) {
+ TryBuildArrayOfObjectsToRehash();
+ }
+ return root_copy;
+ }
+
+ void TryBuildArrayOfObjectsToRehash() {
+ const auto& objects_to_rehash = fast_forward_map_.raw_objects_to_rehash_;
+ const intptr_t length = objects_to_rehash.length();
+ if (length == 0) return;
+
+ const intptr_t size = Array::InstanceSize(length);
+ const uword array_addr = new_space_->TryAllocate(thread_, size);
+ if (array_addr == 0) {
+ exception_msg_ = kFastAllocationFailed;
+ return;
+ }
+
+ const uword header_size =
+ UntaggedObject::SizeTag::SizeFits(size) ? size : 0;
+ ArrayPtr array(reinterpret_cast<UntaggedArray*>(array_addr));
+ SetNewSpaceTaggingWord(array, kArrayCid, header_size);
+ StoreCompressedPointerNoBarrier(array, OFFSET_OF(UntaggedArray, length_),
+ Smi::New(length));
+ StoreCompressedPointerNoBarrier(array,
+ OFFSET_OF(UntaggedArray, type_arguments_),
+ TypeArguments::null());
+ auto array_data = array.untag()->data();
+ for (intptr_t i = 0; i < length; ++i) {
+ array_data[i] = objects_to_rehash[i];
+ }
+ raw_objects_to_rehash_ = array;
+ }
+
+ private:
+ friend class ObjectGraphCopier;
+
+ void FastCopyObject(ObjectPtr from, ObjectPtr to) {
+ const uword tags = TagsFromUntaggedObject(from.untag());
+ const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
+ const intptr_t size = UntaggedObject::SizeTag::decode(tags);
+
+ // Ensure the last word is GC-safe (our heap objects are 2-word aligned, the
+ // object header stores the size in multiples of kObjectAlignment, the GC
+ // uses the information from the header and therefore might visit one slot
+ // more than the actual size of the instance).
+ *reinterpret_cast<ObjectPtr*>(UntaggedObject::ToAddr(to) +
+ from.untag()->HeapSize() - kWordSize) = 0;
+ SetNewSpaceTaggingWord(to, cid, size);
+
+ // Fall back to virtual variant for predefined classes
+ if (cid < kNumPredefinedCids && cid != kInstanceCid) {
+ CopyPredefinedInstance(from, to, cid);
+ return;
+ }
+#if defined(DART_PRECOMPILED_RUNTIME)
+ const auto bitmap =
+ class_table_->shared_class_table()->GetUnboxedFieldsMapAt(cid);
+ CopyUserdefinedInstanceAOT(Instance::RawCast(from), Instance::RawCast(to),
+ bitmap);
+#else
+ CopyUserdefinedInstance(Instance::RawCast(from), Instance::RawCast(to));
+#endif
+ }
+
+ ArrayPtr raw_objects_to_rehash_ = Array::null();
+};
+
+class SlowObjectCopy : public ObjectCopy<SlowObjectCopyBase> {
+ public:
+ explicit SlowObjectCopy(Thread* thread)
+ : ObjectCopy(thread), objects_to_rehash_(Array::Handle(thread->zone())) {}
+ ~SlowObjectCopy() {}
+
+ ObjectPtr ContinueCopyGraphSlow(const Object& root,
+ const Object& fast_root_copy) {
+ auto& root_copy = Object::Handle(Z, fast_root_copy.ptr());
+ if (root_copy.ptr() == Marker()) {
+ root_copy = Forward(TagsFromUntaggedObject(root.ptr().untag()), root);
+ }
+
+ Object& from = Object::Handle(Z);
+ Object& to = Object::Handle(Z);
+ while (slow_forward_map_.fill_cursor_ <
+ slow_forward_map_.from_to_.length()) {
+ const intptr_t index = slow_forward_map_.fill_cursor_;
+ from = slow_forward_map_.from_to_[index]->ptr();
+ to = slow_forward_map_.from_to_[index + 1]->ptr();
+ CopyObject(from, to);
+ slow_forward_map_.fill_cursor_ += 2;
+ if (exception_msg_ != nullptr) {
+ return Marker();
+ }
+ }
+ BuildArrayOfObjectsToRehash();
+ return root_copy.ptr();
+ }
+
+ void BuildArrayOfObjectsToRehash() {
+ const auto& objects_to_rehash = slow_forward_map_.objects_to_rehash_;
+ const intptr_t length = objects_to_rehash.length();
+ if (length == 0) return;
+
+ objects_to_rehash_ = Array::New(length);
+ for (intptr_t i = 0; i < length; ++i) {
+ objects_to_rehash_.SetAt(i, *objects_to_rehash[i]);
+ }
+ }
+
+ private:
+ friend class ObjectGraphCopier;
+
+ void CopyObject(const Object& from, const Object& to) {
+ const auto cid = from.GetClassId();
+
+ // Fall back to virtual variant for predefined classes
+ if (cid < kNumPredefinedCids && cid != kInstanceCid) {
+ CopyPredefinedInstance(from, to, cid);
+ return;
+ }
+#if defined(DART_PRECOMPILED_RUNTIME)
+ const auto bitmap =
+ class_table_->shared_class_table()->GetUnboxedFieldsMapAt(cid);
+ CopyUserdefinedInstanceAOT(from, to, bitmap);
+#else
+ CopyUserdefinedInstance(from, to);
+#endif
+ }
+
+ Array& objects_to_rehash_;
+};
+
+class ObjectGraphCopier {
+ public:
+ explicit ObjectGraphCopier(Thread* thread)
+ : thread_(thread),
+ zone_(thread->zone()),
+ fast_object_copy_(thread_),
+ slow_object_copy_(thread_) {
+ thread_->isolate()->set_forward_table_new(new WeakTable());
+ thread_->isolate()->set_forward_table_old(new WeakTable());
+ }
+ ~ObjectGraphCopier() {
+ thread_->isolate()->set_forward_table_new(nullptr);
+ thread_->isolate()->set_forward_table_old(nullptr);
+ }
+
+ // Result will be [<msg>, <objects-in-msg-to-rehash>]
+ ObjectPtr CopyObjectGraph(const Object& root) {
+ const char* exception_msg = nullptr;
+ auto& result = Object::Handle(zone_);
+
+ {
+ LongJumpScope jump; // e.g. for OOMs.
+ if (setjmp(*jump.Set()) == 0) {
+ result = CopyObjectGraphInternal(root, &exception_msg);
+ // Any allocated external typed data must have finalizers attached so
+ // memory will get free()ed.
+ slow_object_copy_.slow_forward_map_.FinalizeExternalTypedData();
+ } else {
+ // Any allocated external typed data must have finalizers attached so
+ // memory will get free()ed.
+ slow_object_copy_.slow_forward_map_.FinalizeExternalTypedData();
+
+ // The copy failed due to non-application error (e.g. OOM error),
+ // propagate this error.
+ result = thread_->StealStickyError();
+ RELEASE_ASSERT(result.IsError());
+ }
+ }
+
+ if (result.IsError()) {
+ Exceptions::PropagateError(Error::Cast(result));
+ UNREACHABLE();
+ }
+ if (result.ptr() == Marker()) {
+ ASSERT(exception_msg != nullptr);
+ ThrowException(exception_msg);
+ UNREACHABLE();
+ }
+
+ // The copy was successful, then detach transferable data from the sender
+ // and attach to the copied graph.
+ slow_object_copy_.slow_forward_map_.FinalizeTransferables();
+ return result.ptr();
+ }
+
+ private:
+ ObjectPtr CopyObjectGraphInternal(const Object& root,
+ const char** exception_msg) {
+ const auto& result_array = Array::Handle(zone_, Array::New(2));
+ if (!root.ptr()->IsHeapObject()) {
+ result_array.SetAt(0, root);
+ return result_array.ptr();
+ }
+ const uword tags = TagsFromUntaggedObject(root.ptr().untag());
+ if (CanShareObject(tags)) {
+ result_array.SetAt(0, root);
+ return result_array.ptr();
+ }
+ if (!fast_object_copy_.CanCopyObject(tags, root.ptr())) {
+ ASSERT(fast_object_copy_.exception_msg_ != nullptr);
+ *exception_msg = fast_object_copy_.exception_msg_;
+ return Marker();
+ }
+
+ // We try a fast new-space only copy first that will not use any barriers.
+ auto& result = Object::Handle(Z, Marker());
+
+ // All allocated but non-initialized heap objects have to be made GC-visible
+ // at this point.
+ if (FLAG_enable_fast_object_copy) {
+ {
+ NoSafepointScope no_safepoint_scope;
+
+ result = fast_object_copy_.TryCopyGraphFast(root.ptr());
+ if (result.ptr() != Marker()) {
+ if (fast_object_copy_.exception_msg_ == nullptr) {
+ result_array.SetAt(0, result);
+ fast_object_copy_.tmp_ = fast_object_copy_.raw_objects_to_rehash_;
+ result_array.SetAt(1, fast_object_copy_.tmp_);
+ HandlifyExternalTypedData();
+ HandlifyTransferables();
+ return result_array.ptr();
+ }
+
+ // There are left-over uninitialized objects we'll have to make GC
+ // visible.
+ SwitchToSlowFowardingList();
+ }
+ }
+
+ if (FLAG_gc_on_foc_slow_path) {
+ // We use kLowMemory to force the GC to compact, which is more likely to
+ // discover untracked pointers (and other issues, like incorrect class
+ // table).
+ thread_->heap()->CollectAllGarbage(Heap::kLowMemory);
+ }
+
+ // Fast copy failed due to
+ // - either failure to allocate into new space
+ // - or failure to copy object which we cannot copy
+ ASSERT(fast_object_copy_.exception_msg_ != nullptr);
+ if (fast_object_copy_.exception_msg_ != kFastAllocationFailed) {
+ *exception_msg = fast_object_copy_.exception_msg_;
+ return Marker();
+ }
+ ASSERT(fast_object_copy_.exception_msg_ == kFastAllocationFailed);
+ }
+
+ // Use the slow copy approach.
+ result = slow_object_copy_.ContinueCopyGraphSlow(root, result);
+ ASSERT((result.ptr() == Marker()) ==
+ (slow_object_copy_.exception_msg_ != nullptr));
+ if (result.ptr() == Marker()) {
+ *exception_msg = slow_object_copy_.exception_msg_;
+ return Marker();
+ }
+
+ result_array.SetAt(0, result);
+ result_array.SetAt(1, slow_object_copy_.objects_to_rehash_);
+ return result_array.ptr();
+ }
+
+ void SwitchToSlowFowardingList() {
+ auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+ auto& slow_forward_map = slow_object_copy_.slow_forward_map_;
+
+ MakeUninitializedNewSpaceObjectsGCSafe();
+ HandlifyTransferables();
+ HandlifyExternalTypedData();
+ HandlifyObjectsToReHash();
+ HandlifyFromToObjects();
+ slow_forward_map.fill_cursor_ = fast_forward_map.fill_cursor_;
+ }
+
+ void MakeUninitializedNewSpaceObjectsGCSafe() {
+ auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+ const auto length = fast_forward_map.raw_from_to_.length();
+ const auto cursor = fast_forward_map.fill_cursor_;
+ for (intptr_t i = cursor; i < length; i += 2) {
+ auto from = fast_forward_map.raw_from_to_[i];
+ auto to = fast_forward_map.raw_from_to_[i + 1];
+ const uword tags = TagsFromUntaggedObject(from.untag());
+ const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
+ const intptr_t size = UntaggedObject::SizeTag::decode(tags);
+ // External typed data is already initialized.
+ if (!IsExternalTypedDataClassId(cid) && !IsTypedDataViewClassId(cid)) {
+ memset(to.untag(), 0x0, from.untag()->HeapSize());
+ SetNewSpaceTaggingWord(to, cid, size);
+ UpdateLengthField(cid, from, to);
+ }
+ }
+ }
+ void HandlifyTransferables() {
+ auto& raw_transferables =
+ fast_object_copy_.fast_forward_map_.raw_transferables_from_to_;
+ const auto length = raw_transferables.length();
+ if (length > 0) {
+ auto& transferables =
+ slow_object_copy_.slow_forward_map_.transferables_from_to_;
+ transferables.Resize(length);
+ for (intptr_t i = 0; i < length; i++) {
+ transferables[i] =
+ &TransferableTypedData::Handle(Z, raw_transferables[i]);
+ }
+ raw_transferables.Clear();
+ }
+ }
+ void HandlifyExternalTypedData() {
+ auto& raw_external_typed_data =
+ fast_object_copy_.fast_forward_map_.raw_external_typed_data_to_;
+ const auto length = raw_external_typed_data.length();
+ if (length > 0) {
+ auto& external_typed_data =
+ slow_object_copy_.slow_forward_map_.external_typed_data_;
+ external_typed_data.Resize(length);
+ for (intptr_t i = 0; i < length; i++) {
+ external_typed_data[i] =
+ &ExternalTypedData::Handle(Z, raw_external_typed_data[i]);
+ }
+ raw_external_typed_data.Clear();
+ }
+ }
+ void HandlifyObjectsToReHash() {
+ auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+ auto& slow_forward_map = slow_object_copy_.slow_forward_map_;
+ const auto length = fast_forward_map.raw_transferables_from_to_.length();
+ if (length > 0) {
+ slow_forward_map.objects_to_rehash_.Resize(length);
+ for (intptr_t i = 0; i < length; i++) {
+ slow_forward_map.objects_to_rehash_[i] =
+ &Object::Handle(Z, fast_forward_map.raw_objects_to_rehash_[i]);
+ }
+ fast_forward_map.raw_objects_to_rehash_.Clear();
+ }
+ }
+ void HandlifyFromToObjects() {
+ auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+ auto& slow_forward_map = slow_object_copy_.slow_forward_map_;
+
+ const intptr_t cursor = fast_forward_map.fill_cursor_;
+ const intptr_t length = fast_forward_map.raw_from_to_.length();
+
+ slow_forward_map.from_to_.Resize(length);
+ for (intptr_t i = 2; i < length; i += 2) {
+ slow_forward_map.from_to_[i] =
+ i < cursor ? nullptr
+ : &Object::Handle(Z, fast_forward_map.raw_from_to_[i]);
+ slow_forward_map.from_to_[i + 1] =
+ &Object::Handle(Z, fast_forward_map.raw_from_to_[i + 1]);
+ }
+ fast_forward_map.raw_from_to_.Clear();
+ }
+ void ThrowException(const char* exception_msg) {
+ const auto& msg_obj = String::Handle(Z, String::New(exception_msg));
+ const auto& args = Array::Handle(Z, Array::New(1));
+ args.SetAt(0, msg_obj);
+ Exceptions::ThrowByType(Exceptions::kArgument, args);
+ UNREACHABLE();
+ }
+
+ Thread* thread_;
+ Zone* zone_;
+ FastObjectCopy fast_object_copy_;
+ SlowObjectCopy slow_object_copy_;
+};
+
+ObjectPtr CopyMutableObjectGraph(const Object& object) {
+ auto thread = Thread::Current();
+ ObjectGraphCopier copier(thread);
+ return copier.CopyObjectGraph(object);
+}
+
+} // namespace dart
diff --git a/runtime/vm/object_graph_copy.h b/runtime/vm/object_graph_copy.h
new file mode 100644
index 0000000..b33c3e4
--- /dev/null
+++ b/runtime/vm/object_graph_copy.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, 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.
+
+#ifndef RUNTIME_VM_OBJECT_GRAPH_COPY_H_
+#define RUNTIME_VM_OBJECT_GRAPH_COPY_H_
+
+namespace dart {
+
+class Object;
+class ObjectPtr;
+
+// Makes a transitive copy of the object graph referenced by [object]. Will not
+// copy objects that can be safely shared - due to being immutable.
+//
+// The result will be an array of length 2 of the format
+//
+// [<copy-of-root>, <array-of-objects-to-rehash / null>]
+//
+// If the array of objects to rehash is not `null` the receiver should re-hash
+// those objects.
+ObjectPtr CopyMutableObjectGraph(const Object& root);
+
+} // namespace dart
+
+#endif // RUNTIME_VM_OBJECT_GRAPH_COPY_H_
diff --git a/runtime/vm/port.cc b/runtime/vm/port.cc
index c7312de..a671373 100644
--- a/runtime/vm/port.cc
+++ b/runtime/vm/port.cc
@@ -243,7 +243,9 @@
MutexLocker ml(mutex_);
auto it = ports_->TryLookup(receiver);
if (it == ports_->end()) return false;
- return (*it).handler->isolate()->group() == group;
+ auto isolate = (*it).handler->isolate();
+ if (isolate == nullptr) return false;
+ return isolate->group() == group;
}
void PortMap::Init() {
diff --git a/runtime/vm/port.h b/runtime/vm/port.h
index 1405362..4999ad4 100644
--- a/runtime/vm/port.h
+++ b/runtime/vm/port.h
@@ -63,6 +63,7 @@
// Returns the owning Isolate for port 'id'.
static Isolate* GetIsolate(Dart_Port id);
+ // Whether the destination port's isolate is a member of [isolate_group].
static bool IsReceiverInThisIsolateGroup(Dart_Port receiver,
IsolateGroup* group);
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index f16ef82..b9fd76f 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -150,6 +150,8 @@
friend class object##DeserializationCluster; \
friend class Serializer; \
friend class Deserializer; \
+ template <typename Base> \
+ friend class ObjectCopy; \
friend class Pass2Visitor;
// RawObject is the base class of all raw objects; even though it carries the
@@ -540,7 +542,7 @@
void StorePointer(type const* addr, type value) {
reinterpret_cast<std::atomic<type>*>(const_cast<type*>(addr))
->store(value, order);
- if (value->IsHeapObject()) {
+ if (value.IsHeapObject()) {
CheckHeapPointerStore(value, Thread::Current());
}
}
@@ -552,7 +554,7 @@
reinterpret_cast<std::atomic<compressed_type>*>(
const_cast<compressed_type*>(addr))
->store(static_cast<compressed_type>(value), order);
- if (value->IsHeapObject()) {
+ if (value.IsHeapObject()) {
CheckHeapPointerStore(value, Thread::Current());
}
}
@@ -560,7 +562,7 @@
template <typename type>
void StorePointer(type const* addr, type value, Thread* thread) {
*const_cast<type*>(addr) = value;
- if (value->IsHeapObject()) {
+ if (value.IsHeapObject()) {
CheckHeapPointerStore(value, thread);
}
}
@@ -570,7 +572,7 @@
type value,
Thread* thread) {
*const_cast<compressed_type*>(addr) = value;
- if (value->IsHeapObject()) {
+ if (value.IsHeapObject()) {
CheckHeapPointerStore(value, thread);
}
}
@@ -772,6 +774,9 @@
friend class WriteBarrierUpdateVisitor; // CheckHeapPointerStore
friend class OffsetsTable;
friend class Object;
+ friend uword TagsFromUntaggedObject(UntaggedObject*); // tags_
+ friend void SetNewSpaceTaggingWord(ObjectPtr, classid_t, uint32_t); // tags_
+ friend class ObjectCopyBase; // LoadPointer/StorePointer
friend void ReportImpossibleNullError(intptr_t cid,
StackFrame* caller_frame,
Thread* thread);
@@ -2808,6 +2813,12 @@
private:
friend class UntaggedTypedDataView;
+ friend void UpdateLengthField(intptr_t, ObjectPtr, ObjectPtr); // length_
+ friend void InitializeExternalTypedData(
+ intptr_t,
+ ExternalTypedDataPtr,
+ ExternalTypedDataPtr); // initialize fields.
+
RAW_HEAP_OBJECT_IMPLEMENTATION(TypedDataBase);
};
@@ -2899,6 +2910,7 @@
VISIT_TO(offset_in_bytes)
CompressedObjectPtr* to_snapshot(Snapshot::Kind kind) { return to(); }
+ friend void InitializeTypedDataView(TypedDataViewPtr);
friend class Api;
friend class Object;
friend class ObjectPoolDeserializationCluster;
@@ -2964,6 +2976,8 @@
template <typename Table, bool kAllCanonicalObjectsAreIncludedIntoSet>
friend class CanonicalSetDeserializationCluster;
friend class OldPage;
+ friend class FastObjectCopy; // For initializing fields.
+ friend void UpdateLengthField(intptr_t, ObjectPtr, ObjectPtr); // length_
};
class UntaggedImmutableArray : public UntaggedArray {
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index 8d6dcb2..12f776e 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -186,6 +186,8 @@
"object.h",
"object_graph.cc",
"object_graph.h",
+ "object_graph_copy.cc",
+ "object_graph_copy.h",
"object_id_ring.cc",
"object_id_ring.h",
"object_reload.cc",
diff --git a/tests/lib/isolate/count_test.dart b/tests/lib/isolate/count_test.dart
index 2315e12..8aeffd5 100644
--- a/tests/lib/isolate/count_test.dart
+++ b/tests/lib/isolate/count_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
library CountTest;
diff --git a/tests/lib/isolate/function_send_test.dart b/tests/lib/isolate/function_send_test.dart
index ea75c63..dc47408 100644
--- a/tests/lib/isolate/function_send_test.dart
+++ b/tests/lib/isolate/function_send_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:isolate";
diff --git a/tests/lib/isolate/isolate_complex_messages_test.dart b/tests/lib/isolate/isolate_complex_messages_test.dart
index 606b3af..998fc7b 100644
--- a/tests/lib/isolate/isolate_complex_messages_test.dart
+++ b/tests/lib/isolate/isolate_complex_messages_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Dart test program for testing isolate communication with
diff --git a/tests/lib/isolate/isolate_current_test.dart b/tests/lib/isolate/isolate_current_test.dart
index b4b0baea..0dc5e59 100644
--- a/tests/lib/isolate/isolate_current_test.dart
+++ b/tests/lib/isolate/isolate_current_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
library isolate_current_test;
diff --git a/tests/lib/isolate/issue_35626_test.dart b/tests/lib/isolate/issue_35626_test.dart
index 26ee9c6..03ecce8 100644
--- a/tests/lib/isolate/issue_35626_test.dart
+++ b/tests/lib/isolate/issue_35626_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Tests that sets of enums can be set through ports.
diff --git a/tests/lib/isolate/large_byte_data_leak_test.dart b/tests/lib/isolate/large_byte_data_leak_test.dart
index fe2fda1..bacd42f 100644
--- a/tests/lib/isolate/large_byte_data_leak_test.dart
+++ b/tests/lib/isolate/large_byte_data_leak_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:async";
diff --git a/tests/lib/isolate/message2_test.dart b/tests/lib/isolate/message2_test.dart
index 422b499..881e9ea 100644
--- a/tests/lib/isolate/message2_test.dart
+++ b/tests/lib/isolate/message2_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Dart test program for testing serialization of messages.
diff --git a/tests/lib/isolate/message3_test.dart b/tests/lib/isolate/message3_test.dart
index bb8a007fa..049499c 100644
--- a/tests/lib/isolate/message3_test.dart
+++ b/tests/lib/isolate/message3_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Dart test program for testing serialization of messages.
diff --git a/tests/lib/isolate/regress_34752_test.dart b/tests/lib/isolate/regress_34752_test.dart
index fc17139..d75ac0d 100644
--- a/tests/lib/isolate/regress_34752_test.dart
+++ b/tests/lib/isolate/regress_34752_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Verifies that large BigInt can be passed through a message port and
diff --git a/tests/lib/isolate/transferable_failed_to_send_test.dart b/tests/lib/isolate/transferable_failed_to_send_test.dart
index a8d3bcd..61297f0 100644
--- a/tests/lib/isolate/transferable_failed_to_send_test.dart
+++ b/tests/lib/isolate/transferable_failed_to_send_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:io" show ServerSocket;
diff --git a/tests/lib/isolate/transferable_test.dart b/tests/lib/isolate/transferable_test.dart
index f8888e7..def39d4 100644
--- a/tests/lib/isolate/transferable_test.dart
+++ b/tests/lib/isolate/transferable_test.dart
@@ -2,7 +2,8 @@
// 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.
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:async";
diff --git a/tests/lib/js/extends_test/extends_subtyping_live_test.dart b/tests/lib/js/extends_test/extends_subtyping_live_test.dart
new file mode 100644
index 0000000..b46818e
--- /dev/null
+++ b/tests/lib/js/extends_test/extends_subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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.
+
+// Tests inheritance subtyping relationships after making package:js types live.
+
+@JS()
+library extends_subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'extends_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+ // Call to foreign function should trigger dart2js to assume types are live.
+ externalGetter;
+ testSubtyping();
+}
diff --git a/tests/lib/js/extends_test/extends_subtyping_not_live_test.dart b/tests/lib/js/extends_test/extends_subtyping_not_live_test.dart
new file mode 100644
index 0000000..3cb0fbb
--- /dev/null
+++ b/tests/lib/js/extends_test/extends_subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.
+
+// Tests inheritance subtyping relationships without making package:js types
+// live.
+
+import 'extends_test_util.dart';
+
+void main() {
+ testSubtyping();
+}
diff --git a/tests/lib/js/extends_test/extends_test_util.dart b/tests/lib/js/extends_test/extends_test_util.dart
index e39e1b9..0698ef9 100644
--- a/tests/lib/js/extends_test/extends_test_util.dart
+++ b/tests/lib/js/extends_test/extends_test_util.dart
@@ -15,6 +15,7 @@
@JS()
class JSClass {
+ external JSClass();
external int get a;
external int getA();
external int getAOrB();
@@ -57,12 +58,10 @@
external int getAOrB();
}
+external AnonymousClass get anon;
external AnonymousExtendAnonymousClass get anonExtendAnon;
external AnonymousExtendJSClass get anonExtendJS;
-void useJSClass(JSClass js) {}
-void useAnonymousClass(AnonymousClass a) {}
-
void setUpWithoutES6Syntax() {
// Use the old way to define inheritance between JS objects.
eval(r"""
@@ -77,6 +76,7 @@
}
}
function JSClass(a) {
+ if (arguments.length == 0) a = 1;
this.a = a;
this.getA = function() {
return this.a;
@@ -109,6 +109,7 @@
return this.getB();
}
}
+ self.anon = new JSClass(1);
self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
self.anonExtendJS = new JSExtendJSClass(1, 2);
""");
@@ -119,6 +120,7 @@
eval(r"""
class JSClass {
constructor(a) {
+ if (arguments.length == 0) a = 1;
this.a = a;
}
getA() {
@@ -128,6 +130,7 @@
return this.getA();
}
}
+ self.JSClass = JSClass;
class JSExtendJSClass extends JSClass {
constructor(a, b) {
super(a);
@@ -157,16 +160,13 @@
}
}
self.JSExtendAnonymousClass = JSExtendAnonymousClass;
+ self.anon = new JSClass(1);
self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
self.anonExtendJS = new JSExtendJSClass(1, 2);
""");
}
void testInheritance() {
- // Note that for the following, there are no meaningful tests for is checks or
- // as casts, since the web compilers should return true and succeed for all JS
- // types.
-
var jsExtendJS = JSExtendJSClass(1, 2);
expect(jsExtendJS.a, 1);
expect(jsExtendJS.b, 2);
@@ -193,18 +193,86 @@
expect(anonExtendJS.getB(), 2);
expect(anonExtendJS.getAOrB(), 2);
expect((anonExtendJS as JSClass).getAOrB(), 2);
+
+ // Test type checking and casts succeeds regardless of type hierarchy.
+
+ // Test type checking at runtime by disabling inlining. We still, however, do
+ // `is` checks directly below to test those optimizations.
+ @pragma('dart2js:noInline')
+ void runtimeIsAndAs<T>(instance) {
+ expect(instance is T, true);
+ expect(() => instance as T, returnsNormally);
+ }
+
+ // Test that base JS type can be used as any subtype.
+ var js = JSClass();
+ expect(js is JSExtendJSClass, true);
+ runtimeIsAndAs<JSExtendJSClass>(js);
+ expect(js is AnonymousExtendJSClass, true);
+ runtimeIsAndAs<AnonymousExtendJSClass>(js);
+
+ // Test that base anonymous type can be use as any subtype.
+ // Conversion from external getter value to a variable is needed to coerce
+ // compile time optimization of type checks. This applies for below as well.
+ var anonVar = anon;
+ expect(anonVar is JSExtendAnonymousClass, true);
+ runtimeIsAndAs<JSExtendAnonymousClass>(anonVar);
+ expect(anonVar is AnonymousExtendAnonymousClass, true);
+ runtimeIsAndAs<AnonymousExtendAnonymousClass>(anonVar);
+
+ // Test that instance of subtypes can be used as their JS supertype.
+ expect(jsExtendJS is JSClass, true);
+ runtimeIsAndAs<JSClass>(jsExtendJS);
+ var anonExtendJSVar = anonExtendJS;
+ expect(anonExtendJSVar is JSClass, true);
+ runtimeIsAndAs<JSClass>(anonExtendJSVar);
+
+ // Test that instance of subtypes can be used as their anonymous supertype.
+ var anonExtendAnonVar = anonExtendAnon;
+ expect(anonExtendAnonVar is AnonymousClass, true);
+ runtimeIsAndAs<AnonymousClass>(anonExtendAnonVar);
+ expect(jsExtendAnon is AnonymousClass, true);
+ runtimeIsAndAs<AnonymousClass>(jsExtendAnon);
+}
+
+JSClass returnJS() => throw '';
+
+AnonymousClass returnAnon() => throw '';
+
+JSExtendJSClass returnJSExtendJS() => throw '';
+
+JSExtendAnonymousClass returnJSExtendAnon() => throw '';
+
+AnonymousExtendJSClass returnAnonExtendJS() => throw '';
+
+AnonymousExtendAnonymousClass returnAnonExtendAnon() => throw '';
+
+@pragma('dart2js:noInline')
+void isRuntimeSubtypeBothWays<T, U>() {
+ // Test T <: U and U <: T. With interop types, type checks should pass
+ // regardless of type hierarchy. Note that dart2js does these type checks at
+ // runtime. Below, we do compile-time checks using top-level functions.
+ T f1() => throw '';
+ U f2() => throw '';
+ expect(f1 is U Function(), true);
+ expect(f2 is T Function(), true);
}
void testSubtyping() {
// Test subtyping for inheritance between JS and anonymous classes.
- expect(useJSClass is void Function(JSExtendJSClass js), true);
- expect(useAnonymousClass is void Function(AnonymousExtendAnonymousClass a),
- true);
- expect(useJSClass is void Function(AnonymousExtendJSClass a), true);
- expect(useAnonymousClass is void Function(JSExtendAnonymousClass js), true);
+ expect(returnJS is JSExtendJSClass Function(), true);
+ expect(returnJSExtendJS is JSClass Function(), true);
+ isRuntimeSubtypeBothWays<JSClass, JSExtendJSClass>();
- expect(useJSClass is void Function(AnonymousExtendAnonymousClass a), false);
- expect(useAnonymousClass is void Function(JSExtendJSClass js), false);
- expect(useJSClass is void Function(JSExtendAnonymousClass js), false);
- expect(useAnonymousClass is void Function(AnonymousExtendJSClass a), false);
+ expect(returnJS is AnonymousExtendJSClass Function(), true);
+ expect(returnAnonExtendJS is JSClass Function(), true);
+ isRuntimeSubtypeBothWays<JSClass, AnonymousExtendJSClass>();
+
+ expect(returnAnon is JSExtendAnonymousClass Function(), true);
+ expect(returnJSExtendAnon is AnonymousClass Function(), true);
+ isRuntimeSubtypeBothWays<AnonymousClass, JSExtendAnonymousClass>();
+
+ expect(returnAnon is AnonymousExtendAnonymousClass Function(), true);
+ expect(returnAnonExtendAnon is AnonymousClass Function(), true);
+ isRuntimeSubtypeBothWays<AnonymousClass, AnonymousExtendAnonymousClass>();
}
diff --git a/tests/lib/js/subtyping_test.dart b/tests/lib/js/subtyping_test.dart
deleted file mode 100644
index 2551fcd..0000000
--- a/tests/lib/js/subtyping_test.dart
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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.
-
-// Tests subtyping relationships between JS and anonymous classes.
-
-@JS()
-library subtyping_test;
-
-import 'package:js/js.dart';
-import 'package:expect/expect.dart' show hasUnsoundNullSafety;
-import 'package:expect/minitest.dart';
-
-@JS()
-class JSClassA {}
-
-@JS()
-class JSClassB {}
-
-@JS()
-@anonymous
-class AnonymousClassA {}
-
-@JS()
-@anonymous
-class AnonymousClassB {}
-
-class DartClass {}
-
-void useJSClassA(JSClassA _) {}
-void useAnonymousClassA(AnonymousClassA _) {}
-void useDartClass(DartClass _) {}
-
-void useNullableJSClassA(JSClassA? _) {}
-void useNullableAnonymousClassA(AnonymousClassA? _) {}
-
-// Avoid static type optimization by running all tests using this.
-@pragma('dart2js:noInline')
-@pragma('dart2js:assumeDynamic')
-confuse(x) => x;
-
-void main() {
- // Checks subtyping with the same type and nullability subtyping.
- expect(useJSClassA is void Function(JSClassA), true);
- expect(useAnonymousClassA is void Function(AnonymousClassA), true);
- expect(useJSClassA is void Function(JSClassA?), hasUnsoundNullSafety);
- expect(useAnonymousClassA is void Function(AnonymousClassA?),
- hasUnsoundNullSafety);
- expect(useNullableJSClassA is void Function(JSClassA?), true);
- expect(useNullableAnonymousClassA is void Function(AnonymousClassA?), true);
- expect(useNullableJSClassA is void Function(JSClassA), true);
- expect(useNullableAnonymousClassA is void Function(AnonymousClassA), true);
-
- expect(confuse(useJSClassA) is void Function(JSClassA), true);
- expect(confuse(useAnonymousClassA) is void Function(AnonymousClassA), true);
- expect(
- confuse(useJSClassA) is void Function(JSClassA?), hasUnsoundNullSafety);
- expect(confuse(useAnonymousClassA) is void Function(AnonymousClassA?),
- hasUnsoundNullSafety);
- expect(confuse(useNullableJSClassA) is void Function(JSClassA?), true);
- expect(confuse(useNullableAnonymousClassA) is void Function(AnonymousClassA?),
- true);
- expect(confuse(useNullableJSClassA) is void Function(JSClassA), true);
- expect(confuse(useNullableAnonymousClassA) is void Function(AnonymousClassA),
- true);
-
- // No subtyping between JS and anonymous classes.
- expect(useJSClassA is void Function(AnonymousClassA), false);
- expect(useAnonymousClassA is void Function(JSClassA), false);
-
- expect(confuse(useJSClassA) is void Function(AnonymousClassA), false);
- expect(confuse(useAnonymousClassA) is void Function(JSClassA), false);
-
- // No subtyping between separate classes even if they're both JS classes or
- // anonymous classes.
- expect(useJSClassA is void Function(JSClassB), false);
- expect(useAnonymousClassA is void Function(AnonymousClassB), false);
-
- expect(confuse(useJSClassA) is void Function(JSClassB), false);
- expect(confuse(useAnonymousClassA) is void Function(AnonymousClassB), false);
-
- // No subtyping between JS/anonymous classes and Dart classes.
- expect(useJSClassA is void Function(DartClass), false);
- expect(useAnonymousClassA is void Function(DartClass), false);
-
- expect(confuse(useJSClassA) is void Function(DartClass), false);
- expect(confuse(useAnonymousClassA) is void Function(DartClass), false);
-}
diff --git a/tests/lib/js/subtyping_test/subtyping_live_test.dart b/tests/lib/js/subtyping_test/subtyping_live_test.dart
new file mode 100644
index 0000000..d137ba6
--- /dev/null
+++ b/tests/lib/js/subtyping_test/subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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.
+
+// Tests subtyping relationships after making package:js types live.
+
+@JS()
+library subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+ // Call to foreign function should trigger dart2js to assume types are live.
+ externalGetter;
+ testSubtyping();
+}
diff --git a/tests/lib/js/subtyping_test/subtyping_not_live_test.dart b/tests/lib/js/subtyping_test/subtyping_not_live_test.dart
new file mode 100644
index 0000000..5344280
--- /dev/null
+++ b/tests/lib/js/subtyping_test/subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.
+
+// Tests subtyping relationships without making package:js types live.
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+void main() {
+ testSubtyping();
+}
diff --git a/tests/lib/js/subtyping_test/subtyping_test_util.dart b/tests/lib/js/subtyping_test/subtyping_test_util.dart
new file mode 100644
index 0000000..c8f7740
--- /dev/null
+++ b/tests/lib/js/subtyping_test/subtyping_test_util.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2021, 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.
+
+// Tests subtyping relationships between JS and anonymous classes.
+
+@JS()
+library subtyping_test_util;
+
+import 'package:js/js.dart';
+import 'package:expect/expect.dart' show hasUnsoundNullSafety;
+import 'package:expect/minitest.dart';
+
+@JS()
+class JSClassA {}
+
+@JS()
+class JSClassB {}
+
+@JS()
+@anonymous
+class AnonymousClassA {}
+
+@JS()
+@anonymous
+class AnonymousClassB {}
+
+class DartClass {}
+
+JSClassA returnJS() => throw '';
+JSClassA? returnNullableJS() => throw '';
+
+AnonymousClassA returnAnon() => throw '';
+AnonymousClassA? returnNullableAnon() => throw '';
+
+DartClass returnDartClass() => throw '';
+
+// Avoid static type optimization by running all tests using this.
+@pragma('dart2js:noInline')
+@pragma('dart2js:assumeDynamic')
+confuse(x) => x;
+
+void testSubtyping() {
+ // Checks subtyping with the same type and nullability subtyping.
+ expect(returnJS is JSClassA Function(), true);
+ expect(returnAnon is AnonymousClassA Function(), true);
+ expect(returnJS is JSClassA? Function(), true);
+ expect(returnAnon is AnonymousClassA? Function(), true);
+ expect(returnNullableJS is JSClassA? Function(), true);
+ expect(returnNullableAnon is AnonymousClassA? Function(), true);
+ expect(returnNullableJS is JSClassA Function(), hasUnsoundNullSafety);
+ expect(
+ returnNullableAnon is AnonymousClassA Function(), hasUnsoundNullSafety);
+
+ // Subtyping between JS and anonymous classes.
+ expect(returnJS is AnonymousClassA Function(), true);
+ expect(returnAnon is JSClassA Function(), true);
+
+ // Subtyping between same type of package:js classes.
+ expect(returnJS is JSClassB Function(), true);
+ expect(returnAnon is AnonymousClassB Function(), true);
+
+ // No subtyping between JS/anonymous classes and Dart classes.
+ expect(returnJS is DartClass Function(), false);
+ expect(returnDartClass is JSClassA Function(), false);
+ expect(returnAnon is DartClass Function(), false);
+ expect(returnDartClass is AnonymousClassA Function(), false);
+
+ // Repeat the checks but using `confuse` to coerce runtime checks instead of
+ // compile-time like above.
+ expect(confuse(returnJS) is JSClassA Function(), true);
+ expect(confuse(returnAnon) is AnonymousClassA Function(), true);
+ expect(confuse(returnJS) is JSClassA? Function(), true);
+ expect(confuse(returnAnon) is AnonymousClassA? Function(), true);
+ expect(confuse(returnNullableJS) is JSClassA? Function(), true);
+ expect(confuse(returnNullableAnon) is AnonymousClassA? Function(), true);
+ expect(
+ confuse(returnNullableJS) is JSClassA Function(), hasUnsoundNullSafety);
+ expect(confuse(returnNullableAnon) is AnonymousClassA Function(),
+ hasUnsoundNullSafety);
+
+ expect(confuse(returnJS) is AnonymousClassA Function(), true);
+ expect(confuse(returnAnon) is JSClassA Function(), true);
+
+ expect(confuse(returnJS) is JSClassB Function(), true);
+ expect(confuse(returnAnon) is AnonymousClassB Function(), true);
+
+ expect(confuse(returnJS) is DartClass Function(), false);
+ expect(confuse(returnDartClass) is JSClassA Function(), false);
+ expect(confuse(returnAnon) is DartClass Function(), false);
+ expect(confuse(returnDartClass) is AnonymousClassA Function(), false);
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index b931e84..412a98c 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -36,6 +36,8 @@
[ $csp ]
html/js_interop_constructor_name/*: SkipByDesign # Issue 42085.
isolate/deferred_in_isolate2_test: Skip # Issue 16898. Deferred loading does not work from an isolate in CSP-mode
+js/extends_test/extends_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/extends_test/extends_with_es6_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/instanceof_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/async_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/jsify_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
diff --git a/tests/lib_2/isolate/count_test.dart b/tests/lib_2/isolate/count_test.dart
index 0d92759..d216e34 100644
--- a/tests/lib_2/isolate/count_test.dart
+++ b/tests/lib_2/isolate/count_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
library CountTest;
diff --git a/tests/lib_2/isolate/function_send_test.dart b/tests/lib_2/isolate/function_send_test.dart
index 8d24312..86eea4a 100644
--- a/tests/lib_2/isolate/function_send_test.dart
+++ b/tests/lib_2/isolate/function_send_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:isolate";
diff --git a/tests/lib_2/isolate/isolate_complex_messages_test.dart b/tests/lib_2/isolate/isolate_complex_messages_test.dart
index 30322a1..2c10be7 100644
--- a/tests/lib_2/isolate/isolate_complex_messages_test.dart
+++ b/tests/lib_2/isolate/isolate_complex_messages_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Dart test program for testing isolate communication with
diff --git a/tests/lib_2/isolate/isolate_current_test.dart b/tests/lib_2/isolate/isolate_current_test.dart
index 3850591..9188c9d 100644
--- a/tests/lib_2/isolate/isolate_current_test.dart
+++ b/tests/lib_2/isolate/isolate_current_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
library isolate_current_test;
diff --git a/tests/lib_2/isolate/issue_35626_test.dart b/tests/lib_2/isolate/issue_35626_test.dart
index 9e9dbfe..cb8fe62 100644
--- a/tests/lib_2/isolate/issue_35626_test.dart
+++ b/tests/lib_2/isolate/issue_35626_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Tests that sets of enums can be set through ports.
diff --git a/tests/lib_2/isolate/large_byte_data_leak_test.dart b/tests/lib_2/isolate/large_byte_data_leak_test.dart
index 7db4ff5..6adcd28 100644
--- a/tests/lib_2/isolate/large_byte_data_leak_test.dart
+++ b/tests/lib_2/isolate/large_byte_data_leak_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:async";
diff --git a/tests/lib_2/isolate/message2_test.dart b/tests/lib_2/isolate/message2_test.dart
index ccb33b33..0ca540d 100644
--- a/tests/lib_2/isolate/message2_test.dart
+++ b/tests/lib_2/isolate/message2_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Dart test program for testing serialization of messages.
diff --git a/tests/lib_2/isolate/message3_test.dart b/tests/lib_2/isolate/message3_test.dart
index b9813bd..0e1483b 100644
--- a/tests/lib_2/isolate/message3_test.dart
+++ b/tests/lib_2/isolate/message3_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Dart test program for testing serialization of messages.
diff --git a/tests/lib_2/isolate/regress_34752_test.dart b/tests/lib_2/isolate/regress_34752_test.dart
index 7390247..c034dbf 100644
--- a/tests/lib_2/isolate/regress_34752_test.dart
+++ b/tests/lib_2/isolate/regress_34752_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
// Verifies that large BigInt can be passed through a message port and
diff --git a/tests/lib_2/isolate/transferable_failed_to_send_test.dart b/tests/lib_2/isolate/transferable_failed_to_send_test.dart
index 8ee6226..d5481df 100644
--- a/tests/lib_2/isolate/transferable_failed_to_send_test.dart
+++ b/tests/lib_2/isolate/transferable_failed_to_send_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:io" show ServerSocket;
diff --git a/tests/lib_2/isolate/transferable_test.dart b/tests/lib_2/isolate/transferable_test.dart
index aef8d97..e00cf75 100644
--- a/tests/lib_2/isolate/transferable_test.dart
+++ b/tests/lib_2/isolate/transferable_test.dart
@@ -4,7 +4,8 @@
// @dart = 2.9
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
// VMOptions=--no-enable-isolate-groups
import "dart:async";
diff --git a/tests/lib_2/js/extends_test/extends_subtyping_live_test.dart b/tests/lib_2/js/extends_test/extends_subtyping_live_test.dart
new file mode 100644
index 0000000..b46818e
--- /dev/null
+++ b/tests/lib_2/js/extends_test/extends_subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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.
+
+// Tests inheritance subtyping relationships after making package:js types live.
+
+@JS()
+library extends_subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'extends_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+ // Call to foreign function should trigger dart2js to assume types are live.
+ externalGetter;
+ testSubtyping();
+}
diff --git a/tests/lib_2/js/extends_test/extends_subtyping_not_live_test.dart b/tests/lib_2/js/extends_test/extends_subtyping_not_live_test.dart
new file mode 100644
index 0000000..3cb0fbb
--- /dev/null
+++ b/tests/lib_2/js/extends_test/extends_subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.
+
+// Tests inheritance subtyping relationships without making package:js types
+// live.
+
+import 'extends_test_util.dart';
+
+void main() {
+ testSubtyping();
+}
diff --git a/tests/lib/js/extends_test/extends_subtyping_test.dart b/tests/lib_2/js/extends_test/extends_test.dart
similarity index 72%
rename from tests/lib/js/extends_test/extends_subtyping_test.dart
rename to tests/lib_2/js/extends_test/extends_test.dart
index 76dc51e..4286838 100644
--- a/tests/lib/js/extends_test/extends_subtyping_test.dart
+++ b/tests/lib_2/js/extends_test/extends_test.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -6,5 +6,5 @@
void main() {
setUpWithoutES6Syntax();
- testSubtyping();
+ testInheritance();
}
diff --git a/tests/lib_2/js/extends_test/extends_test_util.dart b/tests/lib_2/js/extends_test/extends_test_util.dart
new file mode 100644
index 0000000..9a21c51
--- /dev/null
+++ b/tests/lib_2/js/extends_test/extends_test_util.dart
@@ -0,0 +1,278 @@
+// Copyright (c) 2021, 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.
+
+// Tests inheritance relationships between `JS` and `anonymous` classes/objects.
+
+@JS()
+library extends_test;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+@JS()
+class JSClass {
+ external JSClass();
+ external int get a;
+ external int getA();
+ external int getAOrB();
+}
+
+@JS()
+@anonymous
+class AnonymousClass {
+ external int get a;
+ external int getA();
+}
+
+@JS()
+class JSExtendJSClass extends JSClass {
+ external JSExtendJSClass(int a, int b);
+ external int get b;
+ external int getB();
+ external int getAOrB();
+}
+
+@JS()
+class JSExtendAnonymousClass extends AnonymousClass {
+ external JSExtendAnonymousClass(int a, int b);
+ external int get b;
+ external int getB();
+}
+
+@JS()
+@anonymous
+class AnonymousExtendAnonymousClass extends AnonymousClass {
+ external int get b;
+ external int getB();
+}
+
+@JS()
+@anonymous
+class AnonymousExtendJSClass extends JSClass {
+ external int get b;
+ external int getB();
+ external int getAOrB();
+}
+
+external AnonymousClass get anon;
+external AnonymousExtendAnonymousClass get anonExtendAnon;
+external AnonymousExtendJSClass get anonExtendJS;
+
+void setUpWithoutES6Syntax() {
+ // Use the old way to define inheritance between JS objects.
+ eval(r"""
+ function inherits(child, parent) {
+ if (child.prototype.__proto__) {
+ child.prototype.__proto__ = parent.prototype;
+ } else {
+ function tmp() {};
+ tmp.prototype = parent.prototype;
+ child.prototype = new tmp();
+ child.prototype.constructor = child;
+ }
+ }
+ function JSClass(a) {
+ if (arguments.length == 0) a = 1;
+ this.a = a;
+ this.getA = function() {
+ return this.a;
+ }
+ this.getAOrB = function() {
+ return this.getA();
+ }
+ }
+ function JSExtendJSClass(a, b) {
+ JSClass.call(this, a);
+ this.b = b;
+ this.getB = function() {
+ return this.b;
+ }
+ this.getAOrB = function() {
+ return this.getB();
+ }
+ }
+ inherits(JSExtendJSClass, JSClass);
+ function JSExtendAnonymousClass(a, b) {
+ this.a = a;
+ this.b = b;
+ this.getA = function() {
+ return this.a;
+ }
+ this.getB = function() {
+ return this.b;
+ }
+ this.getAOrB = function() {
+ return this.getB();
+ }
+ }
+ self.anon = new JSClass(1);
+ self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
+ self.anonExtendJS = new JSExtendJSClass(1, 2);
+ """);
+}
+
+void setUpWithES6Syntax() {
+ // Use the ES6 syntax for classes to make inheritance easier.
+ eval(r"""
+ class JSClass {
+ constructor(a) {
+ if (arguments.length == 0) a = 1;
+ this.a = a;
+ }
+ getA() {
+ return this.a;
+ }
+ getAOrB() {
+ return this.getA();
+ }
+ }
+ self.JSClass = JSClass;
+ class JSExtendJSClass extends JSClass {
+ constructor(a, b) {
+ super(a);
+ this.b = b;
+ }
+ getB() {
+ return this.b;
+ }
+ getAOrB() {
+ return this.getB();
+ }
+ }
+ self.JSExtendJSClass = JSExtendJSClass;
+ class JSExtendAnonymousClass {
+ constructor(a, b) {
+ this.a = a;
+ this.b = b;
+ }
+ getA() {
+ return this.a;
+ }
+ getB() {
+ return this.b;
+ }
+ getAOrB() {
+ return this.getB();
+ }
+ }
+ self.JSExtendAnonymousClass = JSExtendAnonymousClass;
+ self.anon = new JSClass(1);
+ self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
+ self.anonExtendJS = new JSExtendJSClass(1, 2);
+ """);
+}
+
+void testInheritance() {
+ var jsExtendJS = JSExtendJSClass(1, 2);
+ expect(jsExtendJS.a, 1);
+ expect(jsExtendJS.b, 2);
+ expect(jsExtendJS.getA(), 1);
+ expect(jsExtendJS.getB(), 2);
+ // Test method overrides.
+ expect(jsExtendJS.getAOrB(), 2);
+ expect((jsExtendJS as JSClass).getAOrB(), 2);
+
+ var jsExtendAnon = JSExtendAnonymousClass(1, 2);
+ expect(jsExtendAnon.a, 1);
+ expect(jsExtendAnon.b, 2);
+ expect(jsExtendAnon.getA(), 1);
+ expect(jsExtendAnon.getB(), 2);
+
+ expect(anonExtendAnon.a, 1);
+ expect(anonExtendAnon.b, 2);
+ expect(anonExtendAnon.getA(), 1);
+ expect(anonExtendAnon.getB(), 2);
+
+ expect(anonExtendJS.a, 1);
+ expect(anonExtendJS.b, 2);
+ expect(anonExtendJS.getA(), 1);
+ expect(anonExtendJS.getB(), 2);
+ expect(anonExtendJS.getAOrB(), 2);
+ expect((anonExtendJS as JSClass).getAOrB(), 2);
+
+ // Test type checking and casts succeeds regardless of type hierarchy.
+
+ // Test type checking at runtime by disabling inlining. We still, however, do
+ // `is` checks directly below to test those optimizations.
+ @pragma('dart2js:noInline')
+ void runtimeIsAndAs<T>(instance) {
+ expect(instance is T, true);
+ expect(() => instance as T, returnsNormally);
+ }
+
+ // Test that base JS type can be used as any subtype.
+ var js = JSClass();
+ expect(js is JSExtendJSClass, true);
+ runtimeIsAndAs<JSExtendJSClass>(js);
+ expect(js is AnonymousExtendJSClass, true);
+ runtimeIsAndAs<AnonymousExtendJSClass>(js);
+
+ // Test that base anonymous type can be use as any subtype.
+ // Conversion from external getter value to a variable is needed to coerce
+ // compile time optimization of type checks. This applies for below as well.
+ var anonVar = anon;
+ expect(anonVar is JSExtendAnonymousClass, true);
+ runtimeIsAndAs<JSExtendAnonymousClass>(anonVar);
+ expect(anonVar is AnonymousExtendAnonymousClass, true);
+ runtimeIsAndAs<AnonymousExtendAnonymousClass>(anonVar);
+
+ // Test that instance of subtypes can be used as their JS supertype.
+ expect(jsExtendJS is JSClass, true);
+ runtimeIsAndAs<JSClass>(jsExtendJS);
+ var anonExtendJSVar = anonExtendJS;
+ expect(anonExtendJSVar is JSClass, true);
+ runtimeIsAndAs<JSClass>(anonExtendJSVar);
+
+ // Test that instance of subtypes can be used as their anonymous supertype.
+ var anonExtendAnonVar = anonExtendAnon;
+ expect(anonExtendAnonVar is AnonymousClass, true);
+ runtimeIsAndAs<AnonymousClass>(anonExtendAnonVar);
+ expect(jsExtendAnon is AnonymousClass, true);
+ runtimeIsAndAs<AnonymousClass>(jsExtendAnon);
+}
+
+JSClass returnJS() => throw '';
+
+AnonymousClass returnAnon() => throw '';
+
+JSExtendJSClass returnJSExtendJS() => throw '';
+
+JSExtendAnonymousClass returnJSExtendAnon() => throw '';
+
+AnonymousExtendJSClass returnAnonExtendJS() => throw '';
+
+AnonymousExtendAnonymousClass returnAnonExtendAnon() => throw '';
+
+@pragma('dart2js:noInline')
+void isRuntimeSubtypeBothWays<T, U>() {
+ // Test T <: U and U <: T. With interop types, type checks should pass
+ // regardless of type hierarchy. Note that dart2js does these type checks at
+ // runtime. Below, we do compile-time checks using top-level functions.
+ T f1() => throw '';
+ U f2() => throw '';
+ expect(f1 is U Function(), true);
+ expect(f2 is T Function(), true);
+}
+
+void testSubtyping() {
+ // Test subtyping for inheritance between JS and anonymous classes.
+ expect(returnJS is JSExtendJSClass Function(), true);
+ expect(returnJSExtendJS is JSClass Function(), true);
+ isRuntimeSubtypeBothWays<JSClass, JSExtendJSClass>();
+
+ expect(returnJS is AnonymousExtendJSClass Function(), true);
+ expect(returnAnonExtendJS is JSClass Function(), true);
+ isRuntimeSubtypeBothWays<JSClass, AnonymousExtendJSClass>();
+
+ expect(returnAnon is JSExtendAnonymousClass Function(), true);
+ expect(returnJSExtendAnon is AnonymousClass Function(), true);
+ isRuntimeSubtypeBothWays<AnonymousClass, JSExtendAnonymousClass>();
+
+ expect(returnAnon is AnonymousExtendAnonymousClass Function(), true);
+ expect(returnAnonExtendAnon is AnonymousClass Function(), true);
+ isRuntimeSubtypeBothWays<AnonymousClass, AnonymousExtendAnonymousClass>();
+}
diff --git a/tests/lib/js/extends_test/extends_with_es6_subtyping_test.dart b/tests/lib_2/js/extends_test/extends_with_es6_test.dart
similarity index 72%
rename from tests/lib/js/extends_test/extends_with_es6_subtyping_test.dart
rename to tests/lib_2/js/extends_test/extends_with_es6_test.dart
index e6185da..e0ed10d 100644
--- a/tests/lib/js/extends_test/extends_with_es6_subtyping_test.dart
+++ b/tests/lib_2/js/extends_test/extends_with_es6_test.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -6,5 +6,5 @@
void main() {
setUpWithES6Syntax();
- testSubtyping();
+ testInheritance();
}
diff --git a/tests/lib_2/js/subtyping_test/subtyping_live_test.dart b/tests/lib_2/js/subtyping_test/subtyping_live_test.dart
new file mode 100644
index 0000000..d137ba6
--- /dev/null
+++ b/tests/lib_2/js/subtyping_test/subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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.
+
+// Tests subtyping relationships after making package:js types live.
+
+@JS()
+library subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+ // Call to foreign function should trigger dart2js to assume types are live.
+ externalGetter;
+ testSubtyping();
+}
diff --git a/tests/lib_2/js/subtyping_test/subtyping_not_live_test.dart b/tests/lib_2/js/subtyping_test/subtyping_not_live_test.dart
new file mode 100644
index 0000000..5344280
--- /dev/null
+++ b/tests/lib_2/js/subtyping_test/subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.
+
+// Tests subtyping relationships without making package:js types live.
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+void main() {
+ testSubtyping();
+}
diff --git a/tests/lib_2/js/subtyping_test/subtyping_test_util.dart b/tests/lib_2/js/subtyping_test/subtyping_test_util.dart
new file mode 100644
index 0000000..a39efb9
--- /dev/null
+++ b/tests/lib_2/js/subtyping_test/subtyping_test_util.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, 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.
+
+// Tests subtyping relationships between JS and anonymous classes.
+
+@JS()
+library subtyping_test_util;
+
+import 'package:js/js.dart';
+import 'package:expect/minitest.dart';
+
+@JS()
+class JSClassA {}
+
+@JS()
+class JSClassB {}
+
+@JS()
+@anonymous
+class AnonymousClassA {}
+
+@JS()
+@anonymous
+class AnonymousClassB {}
+
+class DartClass {}
+
+JSClassA returnJS() => throw '';
+
+AnonymousClassA returnAnon() => throw '';
+
+DartClass returnDartClass() => throw '';
+
+// Avoid static type optimization by running all tests using this.
+@pragma('dart2js:noInline')
+@pragma('dart2js:assumeDynamic')
+confuse(x) => x;
+
+void testSubtyping() {
+ // Checks subtyping with the same type.
+ expect(returnJS is JSClassA Function(), true);
+ expect(returnAnon is AnonymousClassA Function(), true);
+
+ // Subtyping between JS and anonymous classes.
+ expect(returnJS is AnonymousClassA Function(), true);
+ expect(returnAnon is JSClassA Function(), true);
+
+ // Subtyping between same type of package:js classes.
+ expect(returnJS is JSClassB Function(), true);
+ expect(returnAnon is AnonymousClassB Function(), true);
+
+ // No subtyping between JS/anonymous classes and Dart classes.
+ expect(returnJS is DartClass Function(), false);
+ expect(returnDartClass is JSClassA Function(), false);
+ expect(returnAnon is DartClass Function(), false);
+ expect(returnDartClass is AnonymousClassA Function(), false);
+
+ // Repeat the checks but using `confuse` to coerce runtime checks instead of
+ // compile-time like above.
+ expect(confuse(returnJS) is JSClassA Function(), true);
+ expect(confuse(returnAnon) is AnonymousClassA Function(), true);
+
+ expect(confuse(returnJS) is AnonymousClassA Function(), true);
+ expect(confuse(returnAnon) is JSClassA Function(), true);
+
+ expect(confuse(returnJS) is JSClassB Function(), true);
+ expect(confuse(returnAnon) is AnonymousClassB Function(), true);
+
+ expect(confuse(returnJS) is DartClass Function(), false);
+ expect(confuse(returnDartClass) is JSClassA Function(), false);
+ expect(confuse(returnAnon) is DartClass Function(), false);
+ expect(confuse(returnDartClass) is AnonymousClassA Function(), false);
+}
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index 7fbd5a0..eb7d1ed 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -36,6 +36,8 @@
[ $csp ]
html/js_interop_constructor_name/*: SkipByDesign # Issue 42085.
isolate/deferred_in_isolate2_test: Skip # Issue 16898. Deferred loading does not work from an isolate in CSP-mode
+js/extends_test/extends_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/extends_test/extends_with_es6_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/instanceof_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/async_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/jsify_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
diff --git a/tools/VERSION b/tools/VERSION
index d136bd2..03401a3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 312
+PRERELEASE 313
PRERELEASE_PATCH 0
\ No newline at end of file