Version 2.17.0-195.0.dev
Merge commit '4ffb83acaf3f019f7796cd5966d444db3ecfddcd' into 'dev'
diff --git a/DEPS b/DEPS
index da98049..0258c53 100644
--- a/DEPS
+++ b/DEPS
@@ -83,7 +83,7 @@
"boolean_selector_rev": "437e7f06c7e416bed91e16ae1df453555897e945",
"boringssl_gen_rev": "ced85ef0a00bbca77ce5a91261a5f2ae61b1e62f",
"boringssl_rev" : "87f316d7748268eb56f2dc147bd593254ae93198",
- "browser-compat-data_tag": "v1.0.22",
+ "browser-compat-data_tag": "ac8cae697014da1ff7124fba33b0b4245cc6cd1b", # v1.0.22
"browser_launcher_rev": "c6cc1025d6901926cf022e144ba109677e3548f1",
"characters_rev": "6ec389c4dfa8fce14820dc5cbf6e693202e7e052",
"charcode_rev": "84ea427711e24abf3b832923959caa7dd9a8514b",
@@ -121,10 +121,10 @@
"http_parser_rev": "202391286ddc13c4c3c284ac5b511f04697250ed",
"http_rev": "1e42ffa181b263f7790f276a5465832bff7ce615",
"icu_rev" : "81d656878ec611cb0b42d52c82e9dae93920d9ba",
- "intl_tag": "0.17.0-nullsafety",
+ "intl_tag": "9669926609e7efc17dfd74fbb44ec719a7e573cc", # 0.17.0-nullsafety
"jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
"json_rpc_2_rev": "7e00f893440a72de0637970325e4ea44bd1e8c8e",
- "linter_tag": "1.19.2",
+ "linter_tag": "4eaae25b00f3c29935ab0d804b8711cf9a645519", # 1.19.2
"lints_tag": "f9670df2a66e0ec12eb51554e70c1cbf56c8f5d0",
"logging_rev": "dfbe88b890c3b4f7bc06da5a7b3b43e9e263b688",
"markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
@@ -147,7 +147,7 @@
"rust_revision": "b7856f695d65a8ebc846754f97d15814bcb1c244",
"shelf_static_rev": "202ec1a53c9a830c17cf3b718d089cf7eba568ad",
"shelf_packages_handler_rev": "78302e67c035047e6348e692b0c1182131f0fe35",
- "shelf_proxy_tag": "v1.0.0",
+ "shelf_proxy_tag": "ccd311f64d8689e7a145d703ba267975d6df9e28", # 1.0.0
"shelf_rev": "46483f896cc4308ee3d8e997030ae799b72aa16a",
"shelf_web_socket_rev": "24fb8a04befa75a94ac63a27047b231d1a22aab4",
"source_map_stack_trace_rev": "80709f2d2fe5086ab50d744a28a2d26ea4384a1b",
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index be632ea..c1b807a 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -4777,6 +4777,13 @@
the user edit the variable name after the operation, all occurrences of
the name could be edited simultaneously.
</p>
+ <p>
+ Edit groups may have a length of 0 and function as tabstops where there
+ is no default text, for example, an edit that inserts an <tt>if</tt>
+ statement might provide an empty group between parens where a condition
+ should be typed. For this reason, it's also valid for a group to contain
+ only a single position that is not linked to others.
+ </p>
<dl><dt class="field"><b>positions: List<<a href="#type_Position">Position</a>></b></dt><dd>
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index e2c434e..d4ce078 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -229,8 +229,13 @@
}
/// The list of current analysis sessions in all contexts.
- List<AnalysisSession> get currentSessions {
- return driverMap.values.map((driver) => driver.currentSession).toList();
+ Future<List<AnalysisSession>> get currentSessions async {
+ var sessions = <AnalysisSession>[];
+ for (var driver in driverMap.values) {
+ await driver.applyPendingFileChanges();
+ sessions.add(driver.currentSession);
+ }
+ return sessions;
}
/// A table mapping [Folder]s to the [AnalysisDriver]s associated with them.
@@ -297,6 +302,18 @@
return null;
}
+ /// Return an [AnalysisSession] in which the file with the given [path]
+ /// should be analyzed, preferring the one in which it is analyzed, then
+ /// the one where it is referenced, then the first, otherwise `null`.
+ Future<AnalysisSession?> getAnalysisSession(String path) async {
+ var analysisDriver = getAnalysisDriver(path);
+ if (analysisDriver != null) {
+ await analysisDriver.applyPendingFileChanges();
+ return analysisDriver.currentSession;
+ }
+ return null;
+ }
+
DartdocDirectiveInfo getDartdocDirectiveInfoFor(ResolvedUnitResult result) {
return getDartdocDirectiveInfoForSession(result.session);
}
@@ -395,8 +412,12 @@
return null;
}
- var session = getAnalysisDriver(path)?.currentSession;
- var result = await session?.getParsedUnit2(path);
+ var session = await getAnalysisSession(path);
+ if (session == null) {
+ return null;
+ }
+
+ var result = await session.getParsedUnit2(path);
return result is ParsedUnitResult ? result : null;
}
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 9f3043e..85363b6 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -174,12 +174,11 @@
var id = ++_latestGetSuggestionDetailsId;
while (id == _latestGetSuggestionDetailsId && timer.elapsed < timeout) {
try {
- var analysisDriver = server.getAnalysisDriver(file);
- if (analysisDriver == null) {
+ var session = await server.getAnalysisSession(file);
+ if (session == null) {
server.sendResponse(Response.fileNotAnalyzed(request, 'libraryId'));
return;
}
- var session = analysisDriver.currentSession;
var completion = params.label;
var builder = ChangeBuilder(session: session);
@@ -231,12 +230,11 @@
var id = ++_latestGetSuggestionDetailsId;
while (id == _latestGetSuggestionDetailsId && !budget.isEmpty) {
try {
- var analysisDriver = server.getAnalysisDriver(file);
- if (analysisDriver == null) {
+ var session = await server.getAnalysisSession(file);
+ if (session == null) {
server.sendResponse(Response.fileNotAnalyzed(request, file));
return;
}
- var session = analysisDriver.currentSession;
var completion = params.completion;
var builder = ChangeBuilder(session: session);
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 3949a6f..ba1fbce 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -557,13 +557,14 @@
if (driver == null) {
return errorFixesList;
}
+ await driver.applyPendingFileChanges();
var session = driver.currentSession;
var sourceFactory = driver.sourceFactory;
var errors = analyzeAnalysisOptions(
optionsFile.createSource(),
content,
sourceFactory,
- driver.currentSession.analysisContext.contextRoot.root.path,
+ session.analysisContext.contextRoot.root.path,
);
var options = _getOptions(sourceFactory, content);
if (options == null) {
@@ -602,7 +603,9 @@
for (var error in result.errors) {
var errorLine = lineInfo.getLocation(error.offset).lineNumber;
if (errorLine == requestLine) {
- var workspace = DartChangeWorkspace(server.currentSessions);
+ var workspace = DartChangeWorkspace(
+ await server.currentSessions,
+ );
var context = DartFixContextImpl(
server.instrumentationService, workspace, result, error);
@@ -652,11 +655,10 @@
var document =
parseFragment(content, container: MANIFEST_TAG, generateSpans: true);
var validator = ManifestValidator(manifestFile.createSource());
- var driver = server.getAnalysisDriver(file);
- if (driver == null) {
+ var session = await server.getAnalysisSession(file);
+ if (session == null) {
return errorFixesList;
}
- var session = driver.currentSession;
var errors = validator.validate(content, true);
for (var error in errors) {
var generator = ManifestFixGenerator(error, content, document);
@@ -688,8 +690,8 @@
if (content == null) {
return errorFixesList;
}
- var driver = server.getAnalysisDriver(file);
- if (driver == null) {
+ var session = await server.getAnalysisSession(file);
+ if (session == null) {
return errorFixesList;
}
YamlDocument document;
@@ -704,7 +706,6 @@
}
var validator =
PubspecValidator(resourceProvider, pubspecFile.createSource());
- var session = driver.currentSession;
var errors = validator.validate(yamlContent.nodes);
for (var error in errors) {
var generator =
@@ -736,7 +737,9 @@
if (result != null) {
var context = DartAssistContextImpl(
server.instrumentationService,
- DartChangeWorkspace(server.currentSessions),
+ DartChangeWorkspace(
+ await server.currentSessions,
+ ),
result,
offset,
length,
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart
index 4d4f523..c4d005f 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart
@@ -43,7 +43,9 @@
}
return result.mapResult((result) async {
- final workspace = DartChangeWorkspace(server.currentSessions);
+ final workspace = DartChangeWorkspace(
+ await server.currentSessions,
+ );
final processor =
BulkFixProcessor(server.instrumentationService, workspace);
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 521927d..a1e8437 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -253,7 +253,9 @@
try {
var context = DartAssistContextImpl(
server.instrumentationService,
- DartChangeWorkspace(server.currentSessions),
+ DartChangeWorkspace(
+ await server.currentSessions,
+ ),
unit,
offset,
length,
@@ -344,7 +346,9 @@
if (errorLine < range.start.line || errorLine > range.end.line) {
continue;
}
- var workspace = DartChangeWorkspace(server.currentSessions);
+ var workspace = DartChangeWorkspace(
+ await server.currentSessions,
+ );
var context = DartFixContextImpl(
server.instrumentationService, workspace, unit, error);
final fixes = await fixContributor.computeFixes(context);
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
index fa99c2b..cd60079 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
@@ -66,8 +66,7 @@
_latestCompletionItem = item;
while (item == _latestCompletionItem && timer.elapsed < timeout) {
try {
- final analysisDriver = server.getAnalysisDriver(file);
- final session = analysisDriver?.currentSession;
+ final session = await server.getAnalysisSession(file);
// We shouldn't not get a driver/session, but if we did perhaps the file
// was removed from the analysis set so assume the request is no longer
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
index 06fdd44..ed196a1 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
@@ -123,6 +123,7 @@
SourceRange(editStart.result, editEnd.result - editStart.result);
final sessionHelper = AnalysisSessionHelper(unit.session);
+ final analysisContext = unit.session.analysisContext;
final flutter = Flutter.instance;
final colorType = await sessionHelper.getClass(flutter.widgetsUri, 'Color');
if (colorType == null) {
@@ -132,6 +133,13 @@
return success([]);
}
+ // If this file is outside of analysis roots, we cannot build edits for it
+ // so return null to signal to the client that it should not try to modify
+ // the source.
+ if (!analysisContext.contextRoot.isAnalyzed(unit.path)) {
+ return success([]);
+ }
+
final colorValue = _colorValueForComponents(alpha, red, green, blue);
final colorValueHex =
'0x${colorValue.toRadixString(16).toUpperCase().padLeft(8, '0')}';
diff --git a/pkg/analysis_server/lib/src/services/refactoring/move_file.dart b/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
index caa3706..c8cd8e6 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
@@ -55,6 +55,7 @@
}
driver = drivers.first;
+ await driver.applyPendingFileChanges();
_session = driver.currentSession;
if (!resourceProvider.getResource(oldFile).exists) {
return RefactoringStatus.fatal('$oldFile does not exist.');
@@ -113,8 +114,8 @@
// If this element is a library, update outgoing references inside the file.
if (element == libraryElement.definingCompilationUnit) {
// Handle part-of directives in this library
- var libraryResult = await driver.currentSession
- .getResolvedLibraryByElement(libraryElement);
+ var libraryResult =
+ await _session.getResolvedLibraryByElement(libraryElement);
if (libraryResult is! ResolvedLibraryResult) {
return;
}
diff --git a/pkg/analysis_server/test/abstract_context.dart b/pkg/analysis_server/test/abstract_context.dart
index 51d5c28..58804ac 100644
--- a/pkg/analysis_server/test/abstract_context.dart
+++ b/pkg/analysis_server/test/abstract_context.dart
@@ -63,7 +63,7 @@
Folder get sdkRoot => newFolder('/sdk');
- AnalysisSession get session => contextFor(testPackageRootPath).currentSession;
+ Future<AnalysisSession> get session => sessionFor(testPackageRootPath);
String? get testPackageLanguageVersion => latestLanguageVersion;
@@ -87,6 +87,7 @@
var analysisContext = contextFor(testPackageRootPath);
var files = analysisContext.contextRoot.analyzedFiles().toList();
for (var path in files) {
+ await analysisContext.applyPendingFileChanges();
await analysisContext.currentSession.getResolvedUnit(path);
}
}
@@ -168,10 +169,16 @@
Future<ResolvedUnitResult> resolveFile(String path) async {
path = convertPath(path);
- var session = contextFor(path).currentSession;
+ var session = await sessionFor(path);
return await session.getResolvedUnit(path) as ResolvedUnitResult;
}
+ Future<AnalysisSession> sessionFor(String path) async {
+ var analysisContext = _contextFor(path);
+ await analysisContext.applyPendingFileChanges();
+ return analysisContext.currentSession;
+ }
+
@mustCallSuper
void setUp() {
if (!_lintRulesAreRegistered) {
diff --git a/pkg/analysis_server/test/abstract_single_unit.dart b/pkg/analysis_server/test/abstract_single_unit.dart
index 0c09690..4def5df 100644
--- a/pkg/analysis_server/test/abstract_single_unit.dart
+++ b/pkg/analysis_server/test/abstract_single_unit.dart
@@ -65,7 +65,8 @@
}
Future<void> resolveFile2(String path) async {
- var result = await session.getResolvedUnit(path) as ResolvedUnitResult;
+ var result =
+ await (await session).getResolvedUnit(path) as ResolvedUnitResult;
testAnalysisResult = result;
testCode = result.content;
testUnit = result.unit;
diff --git a/pkg/analysis_server/test/analysis/get_navigation_test.dart b/pkg/analysis_server/test/analysis/get_navigation_test.dart
index 1f81f60..793dad6 100644
--- a/pkg/analysis_server/test/analysis/get_navigation_test.dart
+++ b/pkg/analysis_server/test/analysis/get_navigation_test.dart
@@ -66,6 +66,21 @@
assertHasRegion('String]');
}
+ Future<void> test_comment_toolSeeCodeComment() async {
+ var examplePath = 'examples/api/foo.dart';
+ newFile(convertPath('$testFolder/$examplePath'), content: '');
+ addTestFile('''
+/// {@tool dartpad}
+/// ** See code in $examplePath **
+/// {@end-tool}
+String main() {
+}''');
+ await waitForTasksFinished();
+ await _getNavigation(testFile, testCode.indexOf(examplePath), 1);
+ expect(regions, hasLength(1));
+ assertHasRegion(examplePath, examplePath.length);
+ }
+
Future<void> test_constructorInvocation() async {
// Check that a constructor invocation navigates to the constructor and not
// the class.
diff --git a/pkg/analysis_server/test/analysis/update_content_test.dart b/pkg/analysis_server/test/analysis/update_content_test.dart
index f85f7d4..0f1313f 100644
--- a/pkg/analysis_server/test/analysis/update_content_test.dart
+++ b/pkg/analysis_server/test/analysis/update_content_test.dart
@@ -157,20 +157,20 @@
var driver2 = server.getAnalysisDriver(filePath2)!;
// no sources
- expect(_getUserSources(driver1), isEmpty);
- expect(_getUserSources(driver2), isEmpty);
+ expect(await _getUserSources(driver1), isEmpty);
+ expect(await _getUserSources(driver2), isEmpty);
// add an overlay - new Source in context1
server.updateContent('1', {filePath1: AddContentOverlay('')});
- expect(_getUserSources(driver1), [filePath1]);
- expect(_getUserSources(driver2), isEmpty);
+ expect(await _getUserSources(driver1), [filePath1]);
+ expect(await _getUserSources(driver2), isEmpty);
// remove the overlay - no sources
server.updateContent('2', {filePath1: RemoveContentOverlay()});
// The file isn't removed from the list of added sources.
// expect(_getUserSources(driver1), isEmpty);
- expect(_getUserSources(driver2), isEmpty);
+ expect(await _getUserSources(driver2), isEmpty);
}
@failingTest
@@ -259,8 +259,9 @@
expect(overlay, const TypeMatcher<RemoveContentOverlay>());
}
- List<String> _getUserSources(AnalysisDriver driver) {
+ Future<List<String>> _getUserSources(AnalysisDriver driver) async {
var sources = <String>[];
+ await driver.applyPendingFileChanges();
driver.addedFiles.forEach((path) {
if (path.startsWith(convertPath('/User/'))) {
sources.add(path);
diff --git a/pkg/analysis_server/test/lsp/document_color_test.dart b/pkg/analysis_server/test/lsp/document_color_test.dart
index 55dbf74..755752a 100644
--- a/pkg/analysis_server/test/lsp/document_color_test.dart
+++ b/pkg/analysis_server/test/lsp/document_color_test.dart
@@ -71,6 +71,29 @@
expect(colors, isEmpty);
}
+ Future<void> test_outsideAnalysisRoot() async {
+ const content = '''
+ import 'package:flutter/material.dart';
+
+ const white = [[Color(0xFFFFFFFF)]];
+ ''';
+ colorRange = rangeFromMarkers(content);
+
+ final outsideRootFilePath = convertPath('/home/other/test.dart');
+ newFile(outsideRootFilePath, content: withoutMarkers(content));
+ await initialize();
+
+ final colorPresentations = await getColorPresentation(
+ Uri.file(outsideRootFilePath).toString(),
+ colorRange,
+ Color(alpha: 1, red: 1, green: 0, blue: 0),
+ );
+
+ /// Because this file is not editable (it's outside the analysis roots) an
+ /// empty result will be returned.
+ expect(colorPresentations, isEmpty);
+ }
+
Future<void> test_simpleColor() async {
const content = '''
import 'package:flutter/material.dart';
@@ -106,8 +129,11 @@
String label, {
String? colorCode,
String? importUri,
+ bool withEdits = true,
}) {
- final edit = TextEdit(range: colorRange, newText: colorCode ?? label);
+ final edit = withEdits
+ ? TextEdit(range: colorRange, newText: colorCode ?? label)
+ : null;
final additionalEdits = importUri != null
? [TextEdit(range: importRange, newText: "import '$importUri';\n\n")]
: null;
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart b/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
index c05f315..371d0a0 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
@@ -558,7 +558,8 @@
DartCompletionRequest request);
Future computeSuggestions({int times = 200}) async {
- result = await session.getResolvedUnit(testFile) as ResolvedUnitResult;
+ result =
+ await (await session).getResolvedUnit(testFile) as ResolvedUnitResult;
// Build the request
var request = DartCompletionRequest.forResolvedUnit(
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart b/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart
index be920e7..e1c149a 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart
@@ -51,7 +51,7 @@
// Build the request
var resolvedUnit =
- await session.getResolvedUnit(testFile) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(testFile) as ResolvedUnitResult;
request = DartCompletionRequest.forResolvedUnit(
resolvedUnit: resolvedUnit,
offset: completionOffset,
diff --git a/pkg/analysis_server/test/services/correction/organize_directives_test.dart b/pkg/analysis_server/test/services/correction/organize_directives_test.dart
index 8ecae36..737ad25 100644
--- a/pkg/analysis_server/test/services/correction/organize_directives_test.dart
+++ b/pkg/analysis_server/test/services/correction/organize_directives_test.dart
@@ -911,7 +911,8 @@
Future<void> _computeUnitAndErrors(String code) async {
addTestSource(code);
- var result = await session.getResolvedUnit(testFile) as ResolvedUnitResult;
+ var result =
+ await (await session).getResolvedUnit(testFile) as ResolvedUnitResult;
testUnit = result.unit;
testErrors = result.errors;
}
diff --git a/pkg/analysis_server/test/services/correction/sort_members_test.dart b/pkg/analysis_server/test/services/correction/sort_members_test.dart
index ab012ef..ff4bf5f 100644
--- a/pkg/analysis_server/test/services/correction/sort_members_test.dart
+++ b/pkg/analysis_server/test/services/correction/sort_members_test.dart
@@ -1512,7 +1512,8 @@
Future<void> _parseTestUnit(String code) async {
addTestSource(code);
- var result = await session.getParsedUnit2(testFile) as ParsedUnitResult;
+ var result =
+ await (await session).getParsedUnit2(testFile) as ParsedUnitResult;
lineInfo = result.lineInfo;
testUnit = result.unit;
}
diff --git a/pkg/analysis_server/test/services/refactoring/move_file_test.dart b/pkg/analysis_server/test/services/refactoring/move_file_test.dart
index 983df07..7c2c008 100644
--- a/pkg/analysis_server/test/services/refactoring/move_file_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/move_file_test.dart
@@ -65,7 +65,7 @@
// testAnalysisResult manually here, the path is referenced through the
// referenced File object to run on Windows:
testAnalysisResult =
- await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
_createRefactoring('$testPackageLibPath/222/new_name.dart',
oldFile: file.path);
@@ -89,7 +89,7 @@
// testAnalysisResult manually here, the path is referenced through the
// referenced File object to run on Windows:
testAnalysisResult =
- await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
_createRefactoring('/home/test0/test1/test3/lib/111/name.dart',
oldFile: file.path);
@@ -113,7 +113,7 @@
// testAnalysisResult manually here, the path is referenced through the
// referenced File object to run on Windows:
testAnalysisResult =
- await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
_createRefactoring('/home/test0/test1/test2/test3/lib/111/name.dart',
oldFile: file.path);
@@ -137,7 +137,7 @@
// testAnalysisResult manually here, the path is referenced through the
// referenced File object to run on Windows:
testAnalysisResult =
- await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
_createRefactoring('/home/test0/test1/lib/111/name.dart',
oldFile: file.path);
@@ -159,7 +159,7 @@
// testAnalysisResult manually here, the path is referenced through the
// referenced File object to run on Windows:
testAnalysisResult =
- await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
_createRefactoring('$testPackageLibPath/222/new_name.dart',
oldFile: file.path);
@@ -181,7 +181,7 @@
// testAnalysisResult manually here, the path is referenced through the
// referenced File object to run on Windows:
testAnalysisResult =
- await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
_createRefactoring('$testPackageLibPath/new_name.dart', oldFile: file.path);
await _assertSuccessfulRefactoring();
diff --git a/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart b/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart
index 3c99d77..037bb02 100644
--- a/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart
@@ -398,7 +398,7 @@
Future<List<ClosingLabel>> _computeElements(String sourceContent) async {
newFile(sourcePath, content: sourceContent);
var result =
- await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
var computer = DartUnitClosingLabelsComputer(result.lineInfo, result.unit);
return computer.compute();
}
diff --git a/pkg/analysis_server/test/src/computer/color_computer_test.dart b/pkg/analysis_server/test/src/computer/color_computer_test.dart
index 1178812..c6ec12a 100644
--- a/pkg/analysis_server/test/src/computer/color_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/color_computer_test.dart
@@ -130,12 +130,12 @@
newFile(testPath, content: dartCode);
if (otherCode != null) {
newFile(otherPath, content: otherCode);
- final otherResult =
- await session.getResolvedUnit(otherPath) as ResolvedUnitResult;
+ final otherResult = await (await session).getResolvedUnit(otherPath)
+ as ResolvedUnitResult;
expectNoErrors(otherResult);
}
final result =
- await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(testPath) as ResolvedUnitResult;
expectNoErrors(result);
computer = ColorComputer(result);
diff --git a/pkg/analysis_server/test/src/computer/folding_computer_test.dart b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
index bba4d0b..b0a445f 100644
--- a/pkg/analysis_server/test/src/computer/folding_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
@@ -582,7 +582,7 @@
Future<List<FoldingRegion>> _computeRegions(String sourceContent) async {
newFile(sourcePath, content: sourceContent);
var result =
- await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
var computer = DartUnitFoldingComputer(result.lineInfo, result.unit);
return computer.compute();
}
diff --git a/pkg/analysis_server/test/src/computer/highlights_computer_test.dart b/pkg/analysis_server/test/src/computer/highlights_computer_test.dart
index d1a90d3..0314f65 100644
--- a/pkg/analysis_server/test/src/computer/highlights_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/highlights_computer_test.dart
@@ -114,7 +114,7 @@
this.content = content;
newFile(sourcePath, content: content);
var result =
- await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
if (hasErrors) {
expect(result.errors, isNotEmpty);
diff --git a/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart b/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart
index 133ab24..4cf9605 100644
--- a/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart
@@ -50,7 +50,8 @@
Future<void> createBuilder(String content) async {
originalContent = content;
newFile(path, content: content);
- var result = await session.getResolvedUnit(path) as ResolvedUnitResult;
+ var result =
+ await (await session).getResolvedUnit(path) as ResolvedUnitResult;
computer = ImportElementsComputer(resourceProvider, result);
}
diff --git a/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart b/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart
index cf1cf9a..e3140d3 100644
--- a/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart
@@ -471,7 +471,7 @@
// TODO(brianwilkerson) Automatically extract the selection from the content.
newFile(sourcePath, content: content);
var result =
- await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
var computer = ImportedElementsComputer(
result.unit, content.indexOf(selection), selection.length);
importedElements = computer.compute();
diff --git a/pkg/analysis_server/test/src/computer/outline_computer_test.dart b/pkg/analysis_server/test/src/computer/outline_computer_test.dart
index 79818f0..46d85a7 100644
--- a/pkg/analysis_server/test/src/computer/outline_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/outline_computer_test.dart
@@ -33,7 +33,7 @@
testCode = code;
newFile(testPath, content: code);
var resolveResult =
- await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(testPath) as ResolvedUnitResult;
return DartUnitOutlineComputer(
resolveResult,
withBasicFlutter: true,
diff --git a/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart b/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart
index ee07e86..0a3539b 100644
--- a/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart
@@ -194,7 +194,7 @@
String sourceContent, int offset) async {
newFile(sourcePath, content: sourceContent);
var result =
- await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
var computer = DartSelectionRangeComputer(result.unit, offset);
return computer.compute();
}
diff --git a/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart b/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart
index f59bb45..e8fd170 100644
--- a/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart
+++ b/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart
@@ -586,7 +586,7 @@
testCode = code;
newFile(testPath, content: code);
resolveResult =
- await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+ await (await session).getResolvedUnit(testPath) as ResolvedUnitResult;
computer = FlutterOutlineComputer(resolveResult);
return computer.compute();
}
diff --git a/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart b/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart
index 9dc4e9f..51b9dc0 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart
@@ -31,8 +31,8 @@
AssistKind get kind;
/// The workspace in which fixes contributor operates.
- ChangeWorkspace get workspace {
- return DartChangeWorkspace([session]);
+ Future<ChangeWorkspace> get workspace async {
+ return DartChangeWorkspace([await session]);
}
@override
@@ -194,7 +194,7 @@
Future<List<Assist>> _computeAssists() async {
var context = DartAssistContextImpl(
TestInstrumentationService(),
- workspace,
+ await workspace,
testAnalysisResult,
_offset,
_length,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart
index 2b5f910..d75d912 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart
@@ -161,8 +161,8 @@
ChangeWorkspace? _workspace;
@override
- ChangeWorkspace get workspace {
- return _workspace ?? super.workspace;
+ Future<ChangeWorkspace> get workspace async {
+ return _workspace ?? await super.workspace;
}
Future<void> test_function_inPackage_inWorkspace() async {
@@ -174,8 +174,8 @@
);
_workspace = DartChangeWorkspace([
- session,
- getContext('/home/aaa').currentSession,
+ await session,
+ await sessionFor('/home/aaa'),
]);
await resolveTestCode('''
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
index f6ccc74..ba29ed2 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
@@ -78,7 +78,7 @@
var statement = body.block.statements[0] as ExpressionStatement;
var node = statement.expression;
var template = CodeTemplate(CodeTemplateKind.expression, components, null);
- var builder = ChangeBuilder(session: session);
+ var builder = ChangeBuilder(session: await session);
var context = TemplateContext(node, CorrectionUtils(testAnalysisResult));
await builder.addDartFileEdit(testFile, (builder) {
builder.addInsertion(0, (builder) {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart
index 4205071..e6d1f6c 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart
@@ -35,7 +35,7 @@
var testFile = convertPath('$testPackageLibPath/test.dart');
addSource(testFile, '');
- var result = await session.getResolvedLibraryValid(testFile);
+ var result = await (await session).getResolvedLibraryValid(testFile);
var sets = manager.forLibrary(result.element);
expect(sets, hasLength(2));
}
@@ -46,7 +46,7 @@
addSource('/home/test/pubspec.yaml', '');
var testFile = convertPath('$testPackageLibPath/test.dart');
addSource(testFile, '');
- var result = await session.getResolvedLibraryValid(testFile);
+ var result = await (await session).getResolvedLibraryValid(testFile);
var sets = manager.forLibrary(result.element);
expect(sets, hasLength(0));
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
index 87e18e9..c0f1f6e 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
@@ -33,8 +33,8 @@
late String resultCode;
/// The workspace in which fixes contributor operates.
- ChangeWorkspace get workspace {
- return DartChangeWorkspace([session]);
+ Future<ChangeWorkspace> get workspace async {
+ return DartChangeWorkspace([await session]);
}
/// Find the error that is to be fixed by computing the errors in the file,
@@ -97,8 +97,8 @@
bool get useConfigFiles => false;
/// The workspace in which fixes contributor operates.
- DartChangeWorkspace get workspace {
- return DartChangeWorkspace([session]);
+ Future<DartChangeWorkspace> get workspace async {
+ return DartChangeWorkspace([await session]);
}
Future<void> assertHasFix(String expected) async {
@@ -124,7 +124,8 @@
var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
var analysisContext = contextFor(testFile);
tracker.addContext(analysisContext);
- var processor = BulkFixProcessor(TestInstrumentationService(), workspace,
+ var processor = BulkFixProcessor(
+ TestInstrumentationService(), await workspace,
useConfigFiles: useConfigFiles);
await processor.fixErrors([analysisContext]);
return processor;
@@ -196,7 +197,7 @@
Future<List<Fix>> _computeFixes(AnalysisError error) async {
var context = DartFixContextImpl(
TestInstrumentationService(),
- workspace,
+ await workspace,
testAnalysisResult,
error,
);
@@ -500,7 +501,7 @@
Future<List<Fix>> _computeFixes(AnalysisError error) async {
var context = DartFixContextImpl(
TestInstrumentationService(),
- workspace,
+ await workspace,
testAnalysisResult,
error,
);
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java b/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java
index e858201..2ff6c65 100644
--- a/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java
+++ b/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java
@@ -30,6 +30,11 @@
* wanted to let the user edit the variable name after the operation, all occurrences of the name
* could be edited simultaneously.
*
+ * Edit groups may have a length of 0 and function as tabstops where there is no default text, for
+ * example, an edit that inserts an if statement might provide an empty group between parens where
+ * a condition should be typed. For this reason, it's also valid for a group to contain only a
+ * single position that is not linked to others.
+ *
* @coverage dart.server.generated.types
*/
@SuppressWarnings("unused")
diff --git a/pkg/analyzer/lib/dart/analysis/analysis_context.dart b/pkg/analyzer/lib/dart/analysis/analysis_context.dart
index 124fe2d..b30e1fc 100644
--- a/pkg/analyzer/lib/dart/analysis/analysis_context.dart
+++ b/pkg/analyzer/lib/dart/analysis/analysis_context.dart
@@ -35,4 +35,8 @@
/// Return the workspace for containing the context root.
@Deprecated('Use contextRoot.workspace instead')
Workspace get workspace;
+
+ /// Return a [Future] that completes after pending file changes are applied,
+ /// so that [currentSession] can be used to compute results.
+ Future<void> applyPendingFileChanges();
}
diff --git a/pkg/analyzer/lib/error/listener.dart b/pkg/analyzer/lib/error/listener.dart
index 807499c..113f4bb 100644
--- a/pkg/analyzer/lib/error/listener.dart
+++ b/pkg/analyzer/lib/error/listener.dart
@@ -70,8 +70,9 @@
/// is used to compute the location of the error.
void reportErrorForElement(ErrorCode errorCode, Element element,
[List<Object>? arguments]) {
- reportErrorForOffset(errorCode, element.nonSynthetic.nameOffset,
- element.nameLength, arguments);
+ var nonSynthetic = element.nonSynthetic;
+ reportErrorForOffset(
+ errorCode, nonSynthetic.nameOffset, nonSynthetic.nameLength, arguments);
}
/// Report a diagnostic with the given [code] and [arguments]. The
diff --git a/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart b/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
index bd2a210..2667185 100644
--- a/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
+++ b/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
@@ -77,6 +77,12 @@
return _driver.currentSession;
}
+ /// Return a [Future] that completes after pending file changes are applied,
+ /// so that [currentSession] can be used to compute results.
+ Future<void> applyPendingFileChanges() {
+ return _driver.applyPendingFileChanges();
+ }
+
/// The file with the given [path] might have changed - updated, added or
/// removed. Or not, we don't know. Or it might have, but then changed back.
///
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 8bdb3d8..c1d72a8 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -84,6 +84,8 @@
/// The version of data format, should be incremented on every format change.
static const int DATA_VERSION = 209;
+ static const bool _applyFileChangesSynchronously = true;
+
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.
static int allowedNumberOfContextsToWrite = 10;
@@ -140,6 +142,12 @@
/// The set of priority files, that should be analyzed sooner.
final _priorityFiles = <String>{};
+ /// The file changes that should be applied before processing requests.
+ final List<_FileChange> _pendingFileChanges = [];
+
+ /// The completers to complete after [_pendingFileChanges] are applied.
+ final _pendingFileChangesCompleters = <Completer<void>>[];
+
/// The mapping from the files for which analysis was requested using
/// [getResult] to the [Completer]s to report the result.
final _requestedFiles = <String, List<Completer<ResolvedUnitResult>>>{};
@@ -287,7 +295,8 @@
@override
bool get hasFilesToAnalyze {
- return _fileTracker.hasChangedFiles ||
+ return _pendingFileChanges.isNotEmpty ||
+ _fileTracker.hasChangedFiles ||
_requestedFiles.isNotEmpty ||
_requestedParts.isNotEmpty ||
_fileTracker.hasPendingFiles ||
@@ -407,6 +416,9 @@
}
}
}
+ if (_pendingFileChanges.isNotEmpty) {
+ return AnalysisDriverPriority.general;
+ }
if (_fileTracker.hasChangedFiles) {
return AnalysisDriverPriority.changedFiles;
}
@@ -425,7 +437,8 @@
if (_errorsRequestedParts.isNotEmpty ||
_requestedParts.isNotEmpty ||
_partsToAnalyze.isNotEmpty ||
- _unitElementRequestedParts.isNotEmpty) {
+ _unitElementRequestedParts.isNotEmpty ||
+ _pendingFileChangesCompleters.isNotEmpty) {
return AnalysisDriverPriority.general;
}
return AnalysisDriverPriority.nothing;
@@ -439,12 +452,30 @@
}
if (file_paths.isDart(resourceProvider.pathContext, path)) {
_priorityResults.clear();
- _removePotentiallyAffectedLibraries(path);
- _fileTracker.addFile(path);
+ if (_applyFileChangesSynchronously) {
+ _removePotentiallyAffectedLibraries(path);
+ _fileTracker.addFile(path);
+ } else {
+ _pendingFileChanges.add(
+ _FileChange(path, _FileChangeKind.add),
+ );
+ }
_scheduler.notify(this);
}
}
+ /// Return a [Future] that completes after pending file changes are applied,
+ /// so that [currentSession] can be used to compute results.
+ Future<void> applyPendingFileChanges() {
+ if (_pendingFileChanges.isNotEmpty) {
+ var completer = Completer<void>();
+ _pendingFileChangesCompleters.add(completer);
+ return completer.future;
+ } else {
+ return Future.value();
+ }
+ }
+
/// The file with the given [path] might have changed - updated, added or
/// removed. Or not, we don't know. Or it might have, but then changed back.
///
@@ -468,8 +499,14 @@
}
if (file_paths.isDart(resourceProvider.pathContext, path)) {
_priorityResults.clear();
- _removePotentiallyAffectedLibraries(path);
- _fileTracker.changeFile(path);
+ if (_applyFileChangesSynchronously) {
+ _removePotentiallyAffectedLibraries(path);
+ _fileTracker.changeFile(path);
+ } else {
+ _pendingFileChanges.add(
+ _FileChange(path, _FileChangeKind.change),
+ );
+ }
_scheduler.notify(this);
}
}
@@ -630,6 +667,8 @@
return InvalidPathResult();
}
+ _applyPendingFileChanges();
+
FileState file = _fileTracker.getFile(path);
return FileResultImpl(
currentSession, path, file.uri, file.lineInfo, file.isPart);
@@ -987,6 +1026,8 @@
);
}
+ _applyPendingFileChanges();
+
var completer = Completer<UnitElementResult>();
_unitElementRequestedFiles
.putIfAbsent(path, () => <Completer<UnitElementResult>>[])
@@ -1063,6 +1104,8 @@
return InvalidPathResult();
}
+ _applyPendingFileChanges();
+
FileState file = _fileTracker.getFile(path);
RecordingErrorListener listener = RecordingErrorListener();
CompilationUnit unit = file.parse(listener);
@@ -1342,8 +1385,14 @@
if (file_paths.isDart(resourceProvider.pathContext, path)) {
_lastProducedSignatures.remove(path);
_priorityResults.clear();
- _removePotentiallyAffectedLibraries(path);
- _fileTracker.removeFile(path);
+ if (_applyFileChangesSynchronously) {
+ _removePotentiallyAffectedLibraries(path);
+ _fileTracker.removeFile(path);
+ } else {
+ _pendingFileChanges.add(
+ _FileChange(path, _FileChangeKind.remove),
+ );
+ }
_scheduler.notify(this);
}
}
@@ -1414,6 +1463,33 @@
}
}
+ void _applyPendingFileChanges() {
+ for (var fileChange in _pendingFileChanges) {
+ var path = fileChange.path;
+ _removePotentiallyAffectedLibraries(path);
+ switch (fileChange.kind) {
+ case _FileChangeKind.add:
+ _fileTracker.addFile(path);
+ break;
+ case _FileChangeKind.change:
+ _fileTracker.changeFile(path);
+ break;
+ case _FileChangeKind.remove:
+ _fileTracker.removeFile(path);
+ break;
+ }
+ }
+ _pendingFileChanges.clear();
+
+ if (_pendingFileChangesCompleters.isNotEmpty) {
+ var completers = _pendingFileChangesCompleters.toList();
+ _pendingFileChangesCompleters.clear();
+ for (var completer in completers) {
+ completer.complete();
+ }
+ }
+ }
+
/// There was an exception during a file analysis, we don't know why.
/// But it might have been caused by an inconsistency of files state, and
/// the library context state. Reset the library context, and hope that
@@ -2139,6 +2215,12 @@
await _hasWork.signal;
+ for (var driver in _drivers) {
+ if (driver is AnalysisDriver) {
+ driver._applyPendingFileChanges();
+ }
+ }
+
// Transition to analyzing if there are files to analyze.
if (_hasFilesToAnalyze) {
_statusSupport.transitionToAnalyzing();
@@ -2498,6 +2580,20 @@
String toString() => '$exception\n$stackTrace';
}
+class _FileChange {
+ final String path;
+ final _FileChangeKind kind;
+
+ _FileChange(this.path, this.kind);
+
+ @override
+ String toString() {
+ return '[path: $path][kind: $kind]';
+ }
+}
+
+enum _FileChangeKind { add, change, remove }
+
/// Task that computes the list of files that were added to the driver and
/// declare a class member with the given [name].
class _FilesDefiningClassMemberNameTask {
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
index a83f20f..b00038a 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
@@ -53,4 +53,9 @@
Workspace get workspace {
return contextRoot.workspace;
}
+
+ @override
+ Future<void> applyPendingFileChanges() {
+ return driver.applyPendingFileChanges();
+ }
}
diff --git a/pkg/analyzer/lib/src/dart/analysis/session.dart b/pkg/analyzer/lib/src/dart/analysis/session.dart
index c635635..acdf27e 100644
--- a/pkg/analyzer/lib/src/dart/analysis/session.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/session.dart
@@ -55,41 +55,65 @@
driver.AnalysisDriver getDriver() => _driver;
@override
- Future<SomeErrorsResult> getErrors(String path) {
+ Future<SomeErrorsResult> getErrors(String path) async {
_checkConsistency();
- return _driver.getErrors(path);
+ try {
+ return await _driver.getErrors(path);
+ } finally {
+ _checkConsistency();
+ }
}
@Deprecated('Use getFile2() instead')
@override
SomeFileResult getFile(String path) {
_checkConsistency();
- return _driver.getFileSync(path);
+ try {
+ return _driver.getFileSync(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
Future<SomeFileResult> getFile2(String path) async {
_checkConsistency();
- return _driver.getFile(path);
+ try {
+ return await _driver.getFile(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
- Future<SomeLibraryElementResult> getLibraryByUri(String uri) {
+ Future<SomeLibraryElementResult> getLibraryByUri(String uri) async {
_checkConsistency();
- return _driver.getLibraryByUri(uri);
+ try {
+ return await _driver.getLibraryByUri(uri);
+ } finally {
+ _checkConsistency();
+ }
}
@Deprecated('Use getParsedLibrary2() instead')
@override
SomeParsedLibraryResult getParsedLibrary(String path) {
_checkConsistency();
- return _driver.getParsedLibrary(path);
+ try {
+ return _driver.getParsedLibrary(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
Future<SomeParsedLibraryResult> getParsedLibrary2(String path) async {
_checkConsistency();
- return _driver.getParsedLibrary2(path);
+ try {
+ return await _driver.getParsedLibrary2(path);
+ } finally {
+ _checkConsistency();
+ }
}
@Deprecated('Use getParsedLibraryByElement2() instead')
@@ -101,7 +125,11 @@
return NotElementOfThisSessionResult();
}
- return _driver.getParsedLibraryByUri(element.source.uri);
+ try {
+ return _driver.getParsedLibraryByUri(element.source.uri);
+ } finally {
+ _checkConsistency();
+ }
}
@override
@@ -114,53 +142,79 @@
return NotElementOfThisSessionResult();
}
- return _driver.getParsedLibraryByUri2(element.source.uri);
+ try {
+ return await _driver.getParsedLibraryByUri2(element.source.uri);
+ } finally {
+ _checkConsistency();
+ }
}
@Deprecated('Use getParsedUnit2() instead')
@override
SomeParsedUnitResult getParsedUnit(String path) {
_checkConsistency();
- return _driver.parseFileSync(path);
+ try {
+ return _driver.parseFileSync(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
Future<SomeParsedUnitResult> getParsedUnit2(String path) async {
_checkConsistency();
- return _driver.parseFile(path);
+ try {
+ return await _driver.parseFile(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
- Future<SomeResolvedLibraryResult> getResolvedLibrary(String path) {
+ Future<SomeResolvedLibraryResult> getResolvedLibrary(String path) async {
_checkConsistency();
- return _driver.getResolvedLibrary(path);
+ try {
+ return await _driver.getResolvedLibrary(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
Future<SomeResolvedLibraryResult> getResolvedLibraryByElement(
LibraryElement element,
- ) {
+ ) async {
_checkConsistency();
if (element.session != this) {
- return Future.value(
- NotElementOfThisSessionResult(),
- );
+ return NotElementOfThisSessionResult();
}
- return _driver.getResolvedLibraryByUri(element.source.uri);
+ try {
+ return await _driver.getResolvedLibraryByUri(element.source.uri);
+ } finally {
+ _checkConsistency();
+ }
}
@override
- Future<SomeResolvedUnitResult> getResolvedUnit(String path) {
+ Future<SomeResolvedUnitResult> getResolvedUnit(String path) async {
_checkConsistency();
- return _driver.getResult(path);
+ try {
+ return await _driver.getResult(path);
+ } finally {
+ _checkConsistency();
+ }
}
@override
Future<SomeUnitElementResult> getUnitElement(String path) {
_checkConsistency();
- return _driver.getUnitElement(path);
+ try {
+ return _driver.getUnitElement(path);
+ } finally {
+ _checkConsistency();
+ }
}
/// Check to see that results from this session will be consistent, and throw
diff --git a/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart b/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart
index 623f429..0cd2e14 100644
--- a/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart
+++ b/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart
@@ -93,7 +93,9 @@
member.name,
setterScope: member.isStatic ? staticSetters : instanceSetters,
);
- _checkValuesDeclarationInEnum(member.name);
+ if (!(member.isStatic && member.isSetter)) {
+ _checkValuesDeclarationInEnum(member.name);
+ }
}
}
diff --git a/pkg/analyzer/test/src/dart/analysis/dependency/base.dart b/pkg/analyzer/test/src/dart/analysis/dependency/base.dart
index 0c2aefa..fa356af 100644
--- a/pkg/analyzer/test/src/dart/analysis/dependency/base.dart
+++ b/pkg/analyzer/test/src/dart/analysis/dependency/base.dart
@@ -61,7 +61,10 @@
// }
newFile(path, content: content);
- driverFor(path).changeFile(path);
+
+ var analysisDriver = driverFor(path);
+ analysisDriver.changeFile(path);
+ await analysisDriver.applyPendingFileChanges();
var units = await _resolveLibrary(path);
var uri = units.first.declaredElement!.source.uri;
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
index cabe570..421716f 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
@@ -64,7 +64,10 @@
}
''');
- driverFor(testFilePathPlatform).changeFile(testFilePathPlatform);
+ var analysisDriver = driverFor(testFilePathPlatform);
+ analysisDriver.changeFile(testFilePathPlatform);
+ await analysisDriver.applyPendingFileChanges();
+
await resolveTestCode(r'''
class A {
factory A() =;
@@ -80,7 +83,10 @@
}
''');
- driverFor(testFilePathPlatform).changeFile(testFilePathPlatform);
+ var analysisDriver = driverFor(testFilePathPlatform);
+ analysisDriver.changeFile(testFilePathPlatform);
+ await analysisDriver.applyPendingFileChanges();
+
await resolveTestCode(r'''
class A {
factory A() =
@@ -96,7 +102,10 @@
}
''');
- driverFor(testFilePathPlatform).changeFile(testFilePathPlatform);
+ var analysisDriver = driverFor(testFilePathPlatform);
+ analysisDriver.changeFile(testFilePathPlatform);
+ await analysisDriver.applyPendingFileChanges();
+
await resolveTestCode(r'''
class A {
const
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 6a62c91..1aba4d2 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -407,10 +407,12 @@
var b = convertPath('/test/lib/b.dart');
driver.addFile(a);
+ await driver.applyPendingFileChanges();
expect(driver.addedFiles, contains(a));
expect(driver.addedFiles, isNot(contains(b)));
driver.removeFile(a);
+ await driver.applyPendingFileChanges();
expect(driver.addedFiles, isNot(contains(a)));
expect(driver.addedFiles, isNot(contains(b)));
}
@@ -830,6 +832,12 @@
// Change `b.dart`, also removes `c.dart` and `d.dart` that import it.
// But `a.dart` and `d.dart` is not affected.
driver.changeFile(b.path);
+ await driver.applyPendingFileChanges();
+
+ // We have a new session.
+ var session2 = driver.currentSession;
+ expect(session2, isNot(session1));
+
driver.assertLoadedLibraryUriSet(
excluded: [
'package:test/b.dart',
@@ -842,10 +850,6 @@
],
);
- // We have a new session.
- var session2 = driver.currentSession;
- expect(session2, isNot(session1));
-
// `a.dart` and `e.dart` moved to the new session.
// Invalidated libraries stuck with the old session.
expect(a_element.session, session2);
@@ -896,6 +900,7 @@
// Removes `c.dart` that imports `b.dart`.
// But `d.dart` is not affected.
driver.changeFile(a.path);
+ await driver.applyPendingFileChanges();
driver.assertLoadedLibraryUriSet(
excluded: [
'package:test/b.dart',
@@ -1001,9 +1006,7 @@
// Notify the driver about the change.
driver.changeFile(testFile);
-
- // The file was changed, so it is scheduled for analysis.
- expect(driver.test.fileTracker.isFilePending(testFile), isTrue);
+ await driver.applyPendingFileChanges();
// We get a new result.
{
@@ -1411,6 +1414,7 @@
// Notify the driver that the file was changed.
driver.changeFile(a);
+ await driver.applyPendingFileChanges();
// So, `class A {}` is declared now.
expect((await driver.getFileValid(a)).lineInfo.lineCount, 2);
@@ -2588,6 +2592,7 @@
// Notify the driver that the file was changed.
driver.changeFile(a);
+ await driver.applyPendingFileChanges();
// So, `class A {}` is declared now.
{
diff --git a/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart b/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
index b1a26bd..bcf846f 100644
--- a/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
@@ -25,7 +25,7 @@
String get testFilePathPlatform => convertPath(testFilePath);
test_class__fieldDeclaration_type_namedType_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
var f1 = 0;
doub^ f2 = null;
@@ -37,7 +37,7 @@
}
test_class__fieldDeclaration_type_namedType_typeArgument_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
var f1 = 0;
List<doub^>? f2 = null;
@@ -49,7 +49,7 @@
}
test_class_extends_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A extends foo^ {}
''');
@@ -57,7 +57,7 @@
}
test_class_fieldDeclaration_initializer() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
var f1 = 0;
var f2 = foo^;
@@ -71,7 +71,7 @@
}
test_class_implements_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A implements foo^ {}
''');
@@ -79,7 +79,7 @@
}
test_class_methodDeclaration_body() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {}
class B {
@@ -101,7 +101,7 @@
}
test_class_methodDeclaration_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
void foo^() {
print(0);
@@ -113,7 +113,7 @@
}
test_class_methodDeclaration_returnType_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
doub^ foo() {}
}
@@ -123,7 +123,7 @@
}
test_class_with_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A with foo^ {}
''');
@@ -131,7 +131,7 @@
}
test_constructorDeclaration_body() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {}
class B {
@@ -153,7 +153,7 @@
}
test_constructorDeclaration_fieldFormalParameter_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
final int f;
A(this.^);
@@ -166,7 +166,7 @@
}
test_constructorDeclaration_fieldInitializer_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {}
class B {
@@ -186,7 +186,7 @@
}
test_constructorDeclaration_fieldInitializer_value() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
var f;
@@ -203,7 +203,7 @@
}
test_constructorDeclaration_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
A.foo^() {
print(0);
@@ -215,7 +215,7 @@
}
test_constructorDeclaration_superFormalParameter_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {
A(int first, double second);
A.named(int third);
@@ -232,7 +232,7 @@
}
test_doubleLiteral() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v = 1.2^;
''');
@@ -240,7 +240,7 @@
}
test_extension_methodDeclaration_body() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
extension E on int {
void foo1() {}
@@ -260,7 +260,7 @@
}
test_extension_methodDeclaration_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
extension E on int {
void foo^() {
print(0);
@@ -272,7 +272,7 @@
}
test_extension_methodDeclaration_returnType_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
extension E on int {
doub^ foo() {}
}
@@ -282,7 +282,7 @@
}
test_extension_on_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
extension E on int^ {
void foo() {}
}
@@ -292,7 +292,7 @@
}
test_functionDeclaration_body() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void foo1() {}
void foo2() {
@@ -310,7 +310,7 @@
}
test_functionDeclaration_body_withSemicolon() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void foo1() {}
void foo2() {
@@ -328,7 +328,7 @@
}
test_functionDeclaration_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void foo^() {
print(0);
}
@@ -338,7 +338,7 @@
}
test_functionDeclaration_returnType_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
doub^ f() {}
''');
@@ -346,7 +346,7 @@
}
test_importDirective_show_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
import 'dart:async';
import 'dart:math' show ^;
import 'dart:io';
@@ -358,7 +358,7 @@
}
test_importDirective_uri() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
import 'dart:async';
import 'dart:ma^'
import 'dart:io';
@@ -370,7 +370,7 @@
}
test_integerLiteral() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v = 0^;
''');
@@ -378,7 +378,7 @@
}
test_localVariableDeclaration_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void f() {
var foo^
}
@@ -388,7 +388,7 @@
}
test_localVariableDeclaration_type_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void f() {
doub^ a;
}
@@ -398,7 +398,7 @@
}
test_mixin_implements_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
mixin M implements foo^ {}
''');
@@ -406,7 +406,7 @@
}
test_mixin_methodDeclaration_body() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
class A {}
mixin M {
@@ -428,7 +428,7 @@
}
test_mixin_methodDeclaration_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
mixin M {
void foo^() {
print(0);
@@ -440,7 +440,7 @@
}
test_mixin_methodDeclaration_returnType_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
mixin M {
doub^ foo() {}
}
@@ -450,7 +450,7 @@
}
test_mixin_on_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
mixin M on foo^ {}
''');
@@ -464,7 +464,7 @@
await testDriver.getFile(testFilePathPlatform);
// Should call `changeFile()`, and the driver must re-read the file.
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v1 = 0;
var v2 = v1.^;
''');
@@ -475,7 +475,7 @@
}
test_simpleFormalParameter_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void f(doub^) {}
''');
@@ -483,7 +483,7 @@
}
test_simpleFormalParameter_type_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void f(doub^ a) {}
''');
@@ -491,7 +491,7 @@
}
test_topLevelVariable_initializer() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v1 = 0;
var v2 = foo^;
var v3 = 1;
@@ -503,7 +503,7 @@
}
test_topLevelVariable_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v1 = 0;
var v2^
var v3 = 0;
@@ -513,7 +513,7 @@
}
test_topLevelVariable_type_namedType_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v1 = 0;
doub^ v2 = null;
var v3 = 1;
@@ -523,7 +523,7 @@
}
test_topLevelVariable_type_namedType_typeArgument_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
var v1 = 0;
List<doub^>? v2 = null;
var v3 = 1;
@@ -533,7 +533,7 @@
}
test_typedef_name_nothing() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
typedef F^
''');
@@ -541,7 +541,7 @@
}
test_typeParameter_name() async {
- var result = _resolveTestCode(r'''
+ var result = await _resolveTestCode(r'''
void f<T^>() {
print(0);
}
@@ -564,10 +564,14 @@
return offset;
}
- ResolvedForCompletionResultImpl _resolveTestCode(String content) {
+ Future<ResolvedForCompletionResultImpl> _resolveTestCode(
+ String content,
+ ) async {
var path = testFilePathPlatform;
var offset = _newFileWithOffset(path, content);
+
testDriver.changeFile(path);
+ await testDriver.applyPendingFileChanges();
var performance = OperationPerformanceImpl('<root>');
var result = testDriver.resolveForCompletion(
diff --git a/pkg/analyzer/test/src/dart/analysis/search_test.dart b/pkg/analyzer/test/src/dart/analysis/search_test.dart
index 6c5e71d..69278e4 100644
--- a/pkg/analyzer/test/src/dart/analysis/search_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/search_test.dart
@@ -735,7 +735,6 @@
}
''';
newFile(other, content: otherCode);
- driver.addFile(other);
await resolveTestCode('''
class A {
diff --git a/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart b/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
index f20aedb..249ce8d 100644
--- a/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
@@ -326,6 +326,17 @@
]);
}
+ test_enum_static_generatedGetter_thisSetter_index() async {
+ await assertErrorsInCode('''
+enum E {
+ v;
+ static set values(int _) {}
+}
+''', [
+ error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 5, 1),
+ ]);
+ }
+
test_extension_instance() async {
await assertErrorsInCode('''
extension E on Object {
diff --git a/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart b/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
index ceca9ef..7fb0b41 100644
--- a/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
@@ -53,7 +53,10 @@
// Remove the overlay in the same way as AnalysisServer.
deleteFile(filePath);
- driverFor(testFilePath).removeFile(filePath);
+
+ var analysisDriver = driverFor(testFilePath);
+ analysisDriver.removeFile(filePath);
+ await analysisDriver.applyPendingFileChanges();
await resolveTestFile();
assertErrorsInResult([
diff --git a/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart b/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart
index 3deb2aa..bef10e6 100644
--- a/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart
@@ -115,13 +115,11 @@
}
test_setter_static() async {
- await assertErrorsInCode(r'''
+ await assertNoErrorsInCode(r'''
enum E {
v;
static set values(_) {}
}
-''', [
- error(CompileTimeErrorCode.VALUES_DECLARATION_IN_ENUM, 27, 6),
- ]);
+''');
}
}
diff --git a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
index 317d7c5..18026d6 100644
--- a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
+++ b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
@@ -1333,9 +1333,8 @@
return '{$entriesStr}';
}
- /// TODO(scheglov) Make [type] non-nullable?
- String? _typeStr(DartType? type) {
- return type?.getDisplayString(withNullability: true);
+ String? _typeStr(DartType type) {
+ return type.getDisplayString(withNullability: true);
}
void _withIndent(void Function() f) {
@@ -1535,8 +1534,12 @@
void _writeType(String name, DartType? type) {
if (_withResolution) {
- var typeStr = _typeStr(type);
- _writelnWithIndent('$name: $typeStr');
+ if (type != null) {
+ var typeStr = _typeStr(type);
+ _writelnWithIndent('$name: $typeStr');
+ } else {
+ _writelnWithIndent('$name: null');
+ }
}
}
diff --git a/pkg/analyzer_plugin/doc/api.html b/pkg/analyzer_plugin/doc/api.html
index 0c0d3d7..1df2f8e 100644
--- a/pkg/analyzer_plugin/doc/api.html
+++ b/pkg/analyzer_plugin/doc/api.html
@@ -1507,6 +1507,13 @@
the user edit the variable name after the operation, all occurrences of
the name could be edited simultaneously.
</p>
+ <p>
+ Edit groups may have a length of 0 and function as tabstops where there
+ is no default text, for example, an edit that inserts an <tt>if</tt>
+ statement might provide an empty group between parens where a condition
+ should be typed. For this reason, it's also valid for a group to contain
+ only a single position that is not linked to others.
+ </p>
<dl><dt class="field"><b>positions: List<<a href="#type_Position">Position</a>></b></dt><dd>
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
index be0b7fc..646c18a 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
@@ -359,6 +359,15 @@
SourceEdit get sourceEdit => SourceEdit(offset, length, _buffer.toString());
@override
+ void addEmptyLinkedEdit(String groupName) {
+ var start = offset + _buffer.length;
+ var position = Position(fileEditBuilder.fileEdit.file, start);
+ fileEditBuilder.changeBuilder._lockedPositions.add(position);
+ var group = fileEditBuilder.changeBuilder.getLinkedEditGroup(groupName);
+ group.addPosition(position, 0);
+ }
+
+ @override
void addLinkedEdit(String groupName,
void Function(LinkedEditBuilder builder) buildLinkedEdit) {
var builder = createLinkedEditBuilder();
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
index 804c3c4c..b49c38a 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
@@ -75,6 +75,14 @@
///
/// Clients may not extend, implement or mix-in this class.
abstract class EditBuilder {
+ /// Add an empty region that is part of the linked edit group with the given
+ /// [groupName].
+ ///
+ /// Empty linked edits are locations where the user expects to be able to tab
+ /// to but there is no default (or suggested) text. For example inside the
+ /// parens of an `if` statement.
+ void addEmptyLinkedEdit(String groupName);
+
/// Add a region of text that is part of the linked edit group with the given
/// [groupName]. The [buildLinkedEdit] function is used to write the content
/// of the region of text and to add suggestions for other possible values for
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index 3f13d2a..56ec6a3 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -208,9 +208,11 @@
var parentPath =
_computeParentWithExamplesAPI(node, resourceProvider);
if (parentPath != null) {
+ var start = token.offset + startIndex;
+ var end = token.offset + endIndex;
computer.collector.addRegion(
- token.offset + startIndex,
- token.offset + endIndex,
+ start,
+ end - start,
protocol.ElementKind.LIBRARY,
protocol.Location(
resourceProvider.pathContext.join(parentPath, pathSnippet),
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
index ae38afa..7ff8493 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
@@ -38,7 +38,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a', isRequiredNamed: true);
@@ -69,7 +69,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a', isCovariant: true, isRequiredNamed: true);
@@ -88,7 +88,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a', isRequiredNamed: true);
@@ -110,7 +110,7 @@
''';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a', isRequiredNamed: true);
@@ -137,7 +137,7 @@
addSource(path, 'class A {}');
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C', interfaces: [typeA]);
@@ -152,7 +152,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C', isAbstract: true);
@@ -166,7 +166,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C', membersWriter: () {
@@ -183,7 +183,7 @@
addSource(path, 'class A {}');
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C', mixins: [typeA]);
@@ -200,7 +200,7 @@
DartType typeA = await _getType(path, 'A');
DartType typeB = await _getType(path, 'B');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C', mixins: [typeB], superclass: typeA);
@@ -215,7 +215,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C', nameGroupName: 'name');
@@ -236,7 +236,7 @@
addSource(path, 'class B {}');
DartType typeB = await _getType(path, 'B');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeClassDeclaration('C',
@@ -257,7 +257,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, 'class C {}');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(9, (builder) {
builder.writeConstructorDeclaration('A', bodyWriter: () {
@@ -278,7 +278,7 @@
}
''');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(42, (builder) {
builder.writeConstructorDeclaration('A', fieldNames: ['a', 'bb']);
@@ -292,7 +292,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, 'class C {}');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(9, (builder) {
builder.writeConstructorDeclaration('A', initializerWriter: () {
@@ -308,7 +308,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, 'class C {}');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(9, (builder) {
builder.writeConstructorDeclaration('A', parameterWriter: () {
@@ -320,12 +320,41 @@
expect(edit.replacement, equalsIgnoringWhitespace('A(int a, {this.b});'));
}
+ Future<void> test_writeEmptyLinkedEditGroup() async {
+ var path = convertPath('/home/test/lib/test.dart');
+ addSource(path, '');
+
+ var builder = await newBuilder();
+ await builder.addDartFileEdit(path, (builder) {
+ builder.addInsertion(0, (builder) {
+ builder.write('if (');
+ builder.addEmptyLinkedEdit('condition');
+ builder.writeln(') {');
+ builder.write(' ');
+ builder.addEmptyLinkedEdit('body');
+ builder.writeln();
+ builder.writeln('}');
+ });
+ });
+
+ var linkedEditGroups = builder.sourceChange.linkedEditGroups;
+ expect(linkedEditGroups, hasLength(2));
+ // inside parens at `if ()`
+ expect(linkedEditGroups[0].length, 0);
+ expect(linkedEditGroups[0].positions, hasLength(1));
+ expect(linkedEditGroups[0].positions[0].offset, 4);
+ // after the indent inside the block
+ expect(linkedEditGroups[1].length, 0);
+ expect(linkedEditGroups[1].positions, hasLength(1));
+ expect(linkedEditGroups[1].positions[0].offset, 10);
+ }
+
Future<void> test_writeFieldDeclaration_initializerWriter() async {
var path = convertPath('/home/test/lib/test.dart');
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', initializerWriter: () {
@@ -342,7 +371,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', isConst: true);
@@ -357,7 +386,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', isConst: true, isFinal: true);
@@ -373,7 +402,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', isConst: true, type: typeA);
@@ -388,7 +417,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', isFinal: true);
@@ -404,7 +433,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', isFinal: true, type: typeA);
@@ -419,7 +448,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', isStatic: true);
@@ -434,7 +463,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', nameGroupName: 'name');
@@ -458,7 +487,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeFieldDeclaration('f', type: typeA, typeGroupName: 'type');
@@ -482,7 +511,7 @@
var content = '';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeFunctionDeclaration('fib', bodyWriter: () {
@@ -500,7 +529,7 @@
var content = '';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeFunctionDeclaration('fib', nameGroupName: 'name');
@@ -522,7 +551,7 @@
var content = '';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeFunctionDeclaration('fib', parameterWriter: () {
@@ -542,7 +571,7 @@
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeFunctionDeclaration('fib',
@@ -564,7 +593,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeGetterDeclaration('g', bodyWriter: () {
@@ -581,7 +610,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeGetterDeclaration('g', isStatic: true);
@@ -596,7 +625,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeGetterDeclaration('g', nameGroupName: 'name');
@@ -620,7 +649,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeGetterDeclaration('g',
@@ -646,7 +675,7 @@
import 'foo.dart';
''');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeImportedName([
@@ -665,7 +694,7 @@
import 'bar.dart';
''');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeImportedName([
@@ -683,7 +712,7 @@
var content = '';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeImportedName([
@@ -709,7 +738,7 @@
var content = 'test';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addReplacement(SourceRange(0, 4), (builder) {
builder.writeImportedName([
@@ -739,7 +768,7 @@
addSource(path, content);
await resolveFile(path);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration('foo', initializerWriter: () {
@@ -760,7 +789,7 @@
addSource(path, content);
await resolveFile(path);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration('foo', nameGroupName: 'name');
@@ -786,7 +815,7 @@
addSource(path, content);
await resolveFile(path);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration('foo', isConst: true);
@@ -806,7 +835,7 @@
addSource(path, content);
await resolveFile(path);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration('foo', isFinal: true);
@@ -828,7 +857,7 @@
var A = unit.declarations[1] as ClassDeclaration;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration(
@@ -859,7 +888,7 @@
var A = unit.declarations[1] as ClassDeclaration;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration(
@@ -895,7 +924,7 @@
var A = unit.declarations[1] as ClassDeclaration;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(11, (builder) {
builder.writeLocalVariableDeclaration(
@@ -924,7 +953,7 @@
addSource(path, 'class A {}');
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeMixinDeclaration('M', interfaces: [typeA]);
@@ -942,7 +971,7 @@
DartType typeA = await _getType(path, 'A');
DartType typeB = await _getType(path, 'B');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeMixinDeclaration('M',
@@ -958,7 +987,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeMixinDeclaration('M', membersWriter: () {
@@ -974,7 +1003,7 @@
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeMixinDeclaration('M', nameGroupName: 'name');
@@ -995,7 +1024,7 @@
addSource(path, 'class A {}');
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeMixinDeclaration('M', superclassConstraints: [typeA]);
@@ -1010,7 +1039,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a');
@@ -1025,7 +1054,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a', isCovariant: true);
@@ -1041,7 +1070,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameter('a', type: typeA);
@@ -1068,7 +1097,7 @@
var invocation = statement.expression as MethodInvocation;
var argument = invocation.argumentList.arguments[0];
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(2, (builder) {
builder.writeParameterMatchingArgument(argument, 0, <String>{});
@@ -1088,7 +1117,7 @@
var parameters = f.functionExpression.parameters;
var elements = parameters?.parameters.map((p) => p.declaredElement!);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameters(elements!);
@@ -1108,7 +1137,7 @@
var parameters = f.functionExpression.parameters;
var elements = parameters?.parameters.map((p) => p.declaredElement!);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameters(elements!);
@@ -1128,7 +1157,7 @@
var parameters = f.functionExpression.parameters;
var elements = parameters?.parameters.map((p) => p.declaredElement!);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameters(elements!);
@@ -1147,7 +1176,7 @@
var parameters = f.functionExpression.parameters;
var elements = parameters?.parameters.map((p) => p.declaredElement!);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParameters(elements!, requiredTypes: true);
@@ -1170,7 +1199,7 @@
var statement = body.block.statements[0] as ExpressionStatement;
var invocation = statement.expression as MethodInvocation;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParametersMatchingArguments(invocation.argumentList);
@@ -1196,7 +1225,7 @@
var statement = body.block.statements[0] as ExpressionStatement;
var invocation = statement.expression as MethodInvocation;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeParametersMatchingArguments(invocation.argumentList);
@@ -1223,7 +1252,7 @@
var aElement = await _getClassElement(aPath, 'A');
var fooElement = aElement.methods[0];
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeReference(fooElement);
@@ -1245,7 +1274,7 @@
var aElement = await _getTopLevelAccessorElement(aPath, 'a');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeReference(aElement);
@@ -1267,7 +1296,7 @@
var aElement = await _getTopLevelAccessorElement(aPath, 'a');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeReference(aElement);
@@ -1287,7 +1316,7 @@
var aElement = await _getTopLevelAccessorElement(aPath, 'a');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeReference(aElement);
@@ -1309,7 +1338,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeSetterDeclaration('s', bodyWriter: () {
@@ -1326,7 +1355,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeSetterDeclaration('s', isStatic: true);
@@ -1341,7 +1370,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeSetterDeclaration('s', nameGroupName: 'name');
@@ -1365,7 +1394,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeSetterDeclaration('s',
@@ -1390,7 +1419,7 @@
addSource(path, content);
var unit = (await resolveFile(path)).unit;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
var typeProvider = unit.declaredElement!.library.typeProvider;
@@ -1432,7 +1461,7 @@
var typeA = await _getType(path, 'A');
var typeBofA = await _getType(path, 'B', typeArguments: [typeA]);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(typeBofA);
@@ -1448,7 +1477,7 @@
addSource(path, content);
DartType typeC = await _getType(path, 'C');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(typeC, groupName: 'type');
@@ -1469,7 +1498,7 @@
addSource(path, content);
DartType typeC = await _getType(path, 'C');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(typeC,
@@ -1504,7 +1533,7 @@
nullabilitySuffix: NullabilitySuffix.star,
);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length, (builder) {
// "T" cannot be written, because we are outside of "A".
@@ -1528,7 +1557,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(null);
@@ -1563,7 +1592,7 @@
return '_prefix${nextPrefixIndex++}';
}
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(0, (builder) {
builder.writeType(a1.instantiate(
@@ -1610,7 +1639,7 @@
addSource(path, content);
var unit = (await resolveFile(path)).unit;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
var typeProvider = unit.declaredElement!.library.typeProvider;
@@ -1627,7 +1656,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(typeA, required: true);
@@ -1642,7 +1671,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(null, required: true);
@@ -1658,7 +1687,7 @@
addSource(path, content);
DartType typeA = await _getType(path, 'A');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(typeA);
@@ -1682,7 +1711,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeTypes([]);
@@ -1699,7 +1728,7 @@
DartType typeA = await _getType(path, 'A');
DartType typeB = await _getType(path, 'B');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeTypes([typeA, typeB]);
@@ -1714,7 +1743,7 @@
var content = 'class A {}';
addSource(path, content);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeTypes(null);
@@ -1731,7 +1760,7 @@
DartType typeA = await _getType(path, 'A');
DartType typeB = await _getType(path, 'B');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeTypes([typeA, typeB], prefix: 'implements ');
@@ -1748,7 +1777,7 @@
var f = await _getTopLevelAccessorElement(path, 'v');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 1, (builder) {
builder.writeType(f.returnType);
@@ -1793,7 +1822,7 @@
var findNode = FindNode(resolvedUnit.content, resolvedUnit.unit);
var body = findNode.functionBody('{}');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.convertFunctionFromSyncToAsync(body, resolvedUnit.typeProvider);
});
@@ -1810,7 +1839,7 @@
var findNode = FindNode(resolvedUnit.content, resolvedUnit.unit);
var body = findNode.functionBody('{}');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.convertFunctionFromSyncToAsync(body, resolvedUnit.typeProvider);
});
@@ -1839,7 +1868,7 @@
var path = convertPath('/home/test/lib/test.dart');
newFile(path, content: initialCode);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(34, (builder) {
builder.writeln(' 3 + 4;');
@@ -1891,7 +1920,7 @@
var path = convertPath('/home/test/lib/test.dart');
newFile(path, content: initialCode);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.format(SourceRange(37, 39));
});
@@ -1920,7 +1949,7 @@
var path = convertPath('/home/test/lib/test.dart');
newFile(path, content: initialCode);
- var builder = newBuilder();
+ var builder = await newBuilder();
var future = Future.wait([
builder.addDartFileEdit(path, (builder) {
builder.addSimpleInsertion(0, '11');
@@ -1938,7 +1967,7 @@
var path = convertPath('/home/test/lib/test.dart');
newFile(path, content: initialCode);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addSimpleInsertion(0, '11');
});
@@ -1959,7 +1988,7 @@
var findNode = FindNode(resolvedUnit.content, resolvedUnit.unit);
var type = findNode.typeAnnotation('String');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.replaceTypeWithFuture(type, resolvedUnit.typeProvider);
});
@@ -2474,7 +2503,7 @@
}) async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, initialCode);
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
for (var i = 0; i < uriList.length; ++i) {
var uri = Uri.parse(uriList[i]);
@@ -3049,7 +3078,7 @@
var displayBuffer = displayText != null ? StringBuffer() : null;
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
builder.addInsertion(content.length - 2, (builder) {
builder.writeOverride(
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart
index 9bff4bb..10af9f1 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart
@@ -28,5 +28,7 @@
}
/// Return a newly created Dart change builder.
- ChangeBuilder newBuilder() => ChangeBuilder(session: session);
+ Future<ChangeBuilder> newBuilder() async {
+ return ChangeBuilder(session: await session);
+ }
}
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart
index cd2eefa..80fcd16 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart
@@ -469,12 +469,12 @@
newFile(path, content: initialCode);
var requestedResult =
- await session.getLibraryByUri(uriStr) as LibraryElementResult;
+ await (await session).getLibraryByUri(uriStr) as LibraryElementResult;
var requestedLibrary = requestedResult.element;
var requestedElement = requestedLibrary.exportNamespace.get(name);
expect(requestedElement, isNotNull, reason: '`$name` in $uriStr');
- var builder = newBuilder();
+ var builder = await newBuilder();
await builder.addDartFileEdit(path, (builder) {
var uri = Uri.parse(uriStr);
var result = builder.importLibraryElement(uri);
diff --git a/pkg/analyzer_plugin/test/support/abstract_context.dart b/pkg/analyzer_plugin/test/support/abstract_context.dart
index 426644f..9a8066b 100644
--- a/pkg/analyzer_plugin/test/support/abstract_context.dart
+++ b/pkg/analyzer_plugin/test/support/abstract_context.dart
@@ -46,7 +46,7 @@
Folder get sdkRoot => newFolder('/sdk');
- AnalysisSession get session => contextFor(testPackageRootPath).currentSession;
+ Future<AnalysisSession> get session async => sessionFor(testPackageRootPath);
/// The file system-specific `analysis_options.yaml` path.
String get testPackageAnalysisOptionsPath =>
@@ -98,10 +98,16 @@
}
Future<ResolvedUnitResult> resolveFile(String path) async {
- var session = contextFor(path).currentSession;
+ var session = await sessionFor(path);
return await session.getResolvedUnit(path) as ResolvedUnitResult;
}
+ Future<AnalysisSession> sessionFor(String path) async {
+ var analysisContext = contextFor(path);
+ await analysisContext.applyPendingFileChanges();
+ return analysisContext.currentSession;
+ }
+
void setUp() {
createMockSdk(
resourceProvider: resourceProvider,
diff --git a/pkg/analyzer_plugin/tool/spec/common_types_spec.html b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
index 9a3d257..a12e921 100644
--- a/pkg/analyzer_plugin/tool/spec/common_types_spec.html
+++ b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
@@ -1008,6 +1008,13 @@
the user edit the variable name after the operation, all occurrences of
the name could be edited simultaneously.
</p>
+ <p>
+ Edit groups may have a length of 0 and function as tabstops where there
+ is no default text, for example, an edit that inserts an <tt>if</tt>
+ statement might provide an empty group between parens where a condition
+ should be typed. For this reason, it's also valid for a group to contain
+ only a single position that is not linked to others.
+ </p>
<object>
<field name="positions">
<list>
diff --git a/pkg/compiler/lib/compiler.dart b/pkg/compiler/lib/compiler.dart
index b9ade95..a57731b 100644
--- a/pkg/compiler/lib/compiler.dart
+++ b/pkg/compiler/lib/compiler.dart
@@ -247,7 +247,6 @@
return compiler.run().then((bool success) {
return new CompilationResult(compiler,
isSuccess: success,
- kernelInitializedCompilerState:
- compiler.kernelLoader.initializedCompilerState);
+ kernelInitializedCompilerState: compiler.initializedCompilerState);
});
}
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index d1ddb50..918c9fa 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -7,6 +7,7 @@
import 'dart:async' show Future;
import 'dart:convert' show jsonEncode;
+import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
import 'package:kernel/ast.dart' as ir;
import '../compiler.dart' as api;
@@ -46,9 +47,9 @@
import 'kernel/front_end_adapter.dart' show CompilerFileSystem;
import 'kernel/kernel_strategy.dart';
import 'kernel/kernel_world.dart';
-import 'kernel/loader.dart' show KernelLoaderTask, KernelResult;
import 'null_compiler_output.dart' show NullCompilerOutput;
import 'options.dart' show CompilerOptions;
+import 'phase/load_kernel.dart' as load_kernel;
import 'serialization/task.dart';
import 'serialization/serialization.dart';
import 'serialization/strategies.dart';
@@ -105,7 +106,9 @@
Entity get currentElement => _reporter.currentElement;
List<CompilerTask> tasks;
- KernelLoaderTask kernelLoader;
+ GenericTask loadKernelTask;
+ fe.InitializedCompilerState initializedCompilerState;
+ bool forceSerializationForTesting = false;
GlobalTypeInferenceTask globalInference;
CodegenWorldBuilder _codegenWorldBuilder;
@@ -179,7 +182,7 @@
// [enqueueTask] is created earlier because it contains the resolution
// world objects needed by other tasks.
enqueueTask = GenericTask('Enqueue', measurer),
- kernelLoader = KernelLoaderTask(options, provider, reporter, measurer),
+ loadKernelTask = GenericTask('kernel loader', measurer),
kernelFrontEndTask,
globalInference = GlobalTypeInferenceTask(this),
deferredLoadTask = frontendStrategy.createDeferredLoadTask(this),
@@ -191,6 +194,8 @@
userHandlerTask = GenericTask('Diagnostic handler', measurer),
userProviderTask = GenericTask('Input provider', measurer)
];
+
+ initializedCompilerState = options.kernelInitializedCompilerState;
}
/// Creates the backend strategy.
@@ -259,15 +264,15 @@
/// Dumps a list of unused [ir.Library]'s in the [KernelResult]. This *must*
/// be called before [setMainAndTrimComponent], because that method will
/// discard the unused [ir.Library]s.
- void dumpUnusedLibraries(KernelResult result) {
- var usedUris = result.libraries.toSet();
+ void dumpUnusedLibraries(ir.Component component, List<Uri> libraries) {
+ var usedUris = libraries.toSet();
bool isUnused(ir.Library l) => !usedUris.contains(l.importUri);
String libraryString(ir.Library library) {
return '${library.importUri}(${library.fileUri})';
}
var unusedLibraries =
- result.component.libraries.where(isUnused).map(libraryString).toList();
+ component.libraries.where(isUnused).map(libraryString).toList();
unusedLibraries.sort();
var jsonLibraries = jsonEncode(unusedLibraries);
outputProvider.createOutputSink(options.outputUri.pathSegments.last,
@@ -277,10 +282,31 @@
reporter.reportInfo(
reporter.createMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
'text': "${unusedLibraries.length} unused libraries out of "
- "${result.component.libraries.length}. Dumping to JSON."
+ "${component.libraries.length}. Dumping to JSON."
}));
}
+ /// Trims a component down to only the provided library uris.
+ ir.Component trimComponent(
+ ir.Component component, List<Uri> librariesToInclude) {
+ var irLibraryMap = <Uri, ir.Library>{};
+ var irLibraries = <ir.Library>[];
+ for (var library in component.libraries) {
+ irLibraryMap[library.importUri] = library;
+ }
+ for (var library in librariesToInclude) {
+ irLibraries.add(irLibraryMap[library]);
+ }
+ var mainMethod = component.mainMethodName;
+ var componentMode = component.mode;
+ final trimmedComponent = ir.Component(
+ libraries: irLibraries,
+ uriToSource: component.uriToSource,
+ nameRoot: component.root);
+ trimmedComponent.setMainMethodAndMode(mainMethod, true, componentMode);
+ return trimmedComponent;
+ }
+
Future runInternal() async {
clearState();
var compilationTarget = options.compilationTarget;
@@ -331,25 +357,26 @@
await generateJavaScriptCode(globalTypeInferenceResults,
indices: closedWorldAndIndices.indices);
} else {
- KernelResult result = await kernelLoader.load();
+ final input = load_kernel.Input(options, provider, reporter,
+ initializedCompilerState, forceSerializationForTesting);
+ load_kernel.Output output =
+ await loadKernelTask.measure(() async => load_kernel.run(input));
+ if (output == null || compilationFailed) return;
reporter.log("Kernel load complete");
- if (result == null) return;
- if (compilationFailed) {
- return;
- }
if (retainDataForTesting) {
- componentForTesting = result.component;
+ componentForTesting = output.component;
}
- frontendStrategy.registerLoadedLibraries(result);
+ ir.Component component = output.component;
+ List<Uri> libraries = output.libraries;
+ frontendStrategy.registerLoadedLibraries(component, libraries);
if (options.modularMode) {
- await runModularAnalysis(result);
+ await runModularAnalysis(component, output.moduleLibraries);
} else {
List<ModuleData> data;
if (options.hasModularAnalysisInputs) {
- data =
- await serializationTask.deserializeModuleData(result.component);
+ data = await serializationTask.deserializeModuleData(component);
}
frontendStrategy.registerModuleData(data);
@@ -360,16 +387,16 @@
// 'trimmed' elements.
if (options.fromDill) {
if (options.dumpUnusedLibraries) {
- dumpUnusedLibraries(result);
+ dumpUnusedLibraries(component, libraries);
}
if (options.entryUri != null) {
- result.trimComponent();
+ component = trimComponent(component, libraries);
}
}
if (options.cfeOnly) {
- await serializationTask.serializeComponent(result.component);
+ await serializationTask.serializeComponent(component);
} else {
- await compileFromKernel(result.rootLibraryUri, result.libraries);
+ await compileFromKernel(output.rootLibraryUri, libraries);
}
}
}
@@ -471,18 +498,18 @@
return closedWorld;
}
- void runModularAnalysis(KernelResult result) {
+ void runModularAnalysis(
+ ir.Component component, Iterable<Uri> moduleLibraries) {
_userCodeLocations
- .addAll(result.moduleLibraries.map((module) => CodeLocation(module)));
+ .addAll(moduleLibraries.map((module) => CodeLocation(module)));
selfTask.measureSubtask('runModularAnalysis', () {
- var included = result.moduleLibraries.toSet();
+ var included = moduleLibraries.toSet();
var elementMap = frontendStrategy.elementMap;
- var moduleData = computeModuleData(result.component, included, options,
- reporter, environment, elementMap);
+ var moduleData = computeModuleData(
+ component, included, options, reporter, environment, elementMap);
if (compilationFailed) return;
- serializationTask.testModuleSerialization(moduleData, result.component);
- serializationTask.serializeModuleData(
- moduleData, result.component, included);
+ serializationTask.testModuleSerialization(moduleData, component);
+ serializationTask.serializeModuleData(moduleData, component, included);
});
}
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index f1a2641..1a6b1bb 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -44,7 +44,7 @@
import '../universe/world_impact.dart';
import '../util/enumset.dart';
import 'element_map.dart';
-import 'loader.dart';
+import 'element_map_impl.dart';
import 'native_basic_data.dart';
/// Front end strategy that loads '.dill' files and builds a resolved element
@@ -229,13 +229,13 @@
}
/// Registers a set of loaded libraries with this strategy.
- void registerLoadedLibraries(KernelResult kernelResult) {
- _elementMap.addComponent(kernelResult.component);
+ void registerLoadedLibraries(ir.Component component, List<Uri> libraries) {
+ _elementMap.addComponent(component);
_irAnnotationData = processAnnotations(
- ModularCore(kernelResult.component, _elementMap.constantEvaluator));
+ ModularCore(component, _elementMap.constantEvaluator));
_annotationProcessor = KernelAnnotationProcessor(
elementMap, nativeBasicDataBuilder, _irAnnotationData);
- for (Uri uri in kernelResult.libraries) {
+ for (Uri uri in libraries) {
LibraryEntity library = elementEnvironment.lookupLibrary(uri);
if (maybeEnableNative(library.canonicalUri)) {
_annotationProcessor.extractNativeAnnotations(library);
diff --git a/pkg/compiler/lib/src/kernel/loader.dart b/pkg/compiler/lib/src/kernel/loader.dart
deleted file mode 100644
index 8e7520e..0000000
--- a/pkg/compiler/lib/src/kernel/loader.dart
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-library dart2js.library_loader;
-
-import 'dart:async';
-
-import 'package:front_end/src/fasta/kernel/utils.dart';
-import 'package:kernel/ast.dart' as ir;
-import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
-
-import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
-import 'package:kernel/kernel.dart' hide LibraryDependency, Combinator;
-import 'package:kernel/target/targets.dart' hide DiagnosticReporter;
-
-import '../../compiler.dart' as api;
-import '../commandline_options.dart' show Flags;
-import '../common/tasks.dart' show CompilerTask, Measurer;
-import '../common.dart';
-import '../options.dart';
-
-import 'front_end_adapter.dart';
-import 'dart2js_target.dart' show Dart2jsTarget;
-
-/// A task that produces the kernel IR representation of the application.
-///
-/// It supports loading both .dart source files or pre-compiled .dill files.
-/// When given .dart source files, it invokes the common front-end (CFE)
-/// to produce the corresponding kernel IR representation.
-class KernelLoaderTask extends CompilerTask {
- final DiagnosticReporter _reporter;
-
- final api.CompilerInput _compilerInput;
-
- final CompilerOptions _options;
-
- /// Shared state between compilations.
- fe.InitializedCompilerState initializedCompilerState;
-
- // TODO(johnniwinther): Remove this when #34942 is fixed.
- /// Force in-memory serialization/deserialization of the loaded component.
- ///
- /// This is used for testing.
- bool forceSerialization = false;
-
- KernelLoaderTask(
- this._options, this._compilerInput, this._reporter, Measurer measurer)
- : initializedCompilerState = _options.kernelInitializedCompilerState,
- super(measurer);
-
- @override
- String get name => 'kernel loader';
-
- static Library _findEntryLibrary(Component component, Uri entryUri) {
- var entryLibrary = component.libraries
- .firstWhere((l) => l.fileUri == entryUri, orElse: () => null);
- if (entryLibrary == null) {
- throw ArgumentError('Entry uri $entryUri not found in dill.');
- }
- return entryLibrary;
- }
-
- static ir.Reference _findMainMethod(Library entryLibrary) {
- var mainMethod = entryLibrary.procedures
- .firstWhere((p) => p.name.text == 'main', orElse: () => null);
-
- // In some cases, a main method is defined in another file, and then
- // exported. In these cases, we search for the main method in
- // [additionalExports].
- ir.Reference mainMethodReference;
- if (mainMethod == null) {
- mainMethodReference = entryLibrary.additionalExports.firstWhere(
- (p) => p.canonicalName.name == 'main',
- orElse: () => null);
- } else {
- mainMethodReference = mainMethod.reference;
- }
- if (mainMethodReference == null) {
- throw ArgumentError(
- 'Entry uri ${entryLibrary.fileUri} has no main method.');
- }
- return mainMethodReference;
- }
-
- /// Loads an entire Kernel [Component] from a file on disk.
- Future<KernelResult> load() {
- return measure(() async {
- String targetName =
- _options.compileForServer ? "dart2js_server" : "dart2js";
-
- // We defer selecting the platform until we've resolved the null safety
- // mode.
- String getPlatformFilename() {
- String unsoundMarker = _options.useLegacySubtyping ? "_unsound" : "";
- return "${targetName}_platform$unsoundMarker.dill";
- }
-
- // If we are passed an [entryUri] and building from dill, then we lookup
- // the [entryLibrary] in the built component.
- Library entryLibrary;
- var resolvedUri = _options.compilationTarget;
- ir.Component component;
- List<Uri> moduleLibraries = const [];
-
- void inferNullSafetyMode(bool isSound) {
- if (_options.nullSafetyMode == NullSafetyMode.unspecified) {
- _options.nullSafetyMode =
- isSound ? NullSafetyMode.sound : NullSafetyMode.unsound;
- }
- }
-
- void validateNullSafetyMode() {
- assert(_options.nullSafetyMode != NullSafetyMode.unspecified);
- }
-
- if (_options.fromDill) {
- component = ir.Component();
- Future<void> read(Uri uri) async {
- api.Input input = await _compilerInput.readFromUri(uri,
- inputKind: api.InputKind.binary);
- BinaryBuilder(input.data).readComponent(component);
- }
-
- await read(resolvedUri);
-
- if (_options.modularMode) {
- moduleLibraries =
- component.libraries.map((lib) => lib.importUri).toList();
- }
-
- var isStrongDill =
- component.mode == ir.NonNullableByDefaultCompiledMode.Strong;
- var incompatibleNullSafetyMode =
- isStrongDill ? NullSafetyMode.unsound : NullSafetyMode.sound;
- if (_options.nullSafetyMode == incompatibleNullSafetyMode) {
- var dillMode = isStrongDill ? 'sound' : 'unsound';
- var option =
- isStrongDill ? Flags.noSoundNullSafety : Flags.soundNullSafety;
- throw ArgumentError("$resolvedUri was compiled with $dillMode null "
- "safety and is incompatible with the '$option' option");
- }
- inferNullSafetyMode(isStrongDill);
- validateNullSafetyMode();
-
- // Modular compiles do not include the platform on the input dill
- // either.
- if (_options.platformBinaries != null) {
- var platformUri =
- _options.platformBinaries.resolve(getPlatformFilename());
- // Modular analysis can be run on the sdk by providing directly the
- // path to the platform.dill file. In that case, we do not load the
- // platform file implicitly.
- // TODO(joshualitt): Change how we detect this case so it is less
- // brittle.
- if (platformUri != resolvedUri) await read(platformUri);
- }
-
- // Concatenate dills.
- if (_options.dillDependencies != null) {
- for (Uri dependency in _options.dillDependencies) {
- await read(dependency);
- }
- }
-
- if (_options.entryUri != null) {
- entryLibrary = _findEntryLibrary(component, _options.entryUri);
- var mainMethod = _findMainMethod(entryLibrary);
- component.setMainMethodAndMode(mainMethod, true, component.mode);
- }
- } else {
- bool verbose = false;
- Target target =
- Dart2jsTarget(targetName, TargetFlags(), options: _options);
- fe.FileSystem fileSystem = CompilerFileSystem(_compilerInput);
- fe.Verbosity verbosity = _options.verbosity;
- fe.DiagnosticMessageHandler onDiagnostic =
- (fe.DiagnosticMessage message) {
- if (fe.Verbosity.shouldPrint(verbosity, message)) {
- reportFrontEndMessage(_reporter, message);
- }
- };
- fe.CompilerOptions options = fe.CompilerOptions()
- ..target = target
- ..librariesSpecificationUri = _options.librariesSpecificationUri
- ..packagesFileUri = _options.packageConfig
- ..explicitExperimentalFlags = _options.explicitExperimentalFlags
- ..verbose = verbose
- ..fileSystem = fileSystem
- ..onDiagnostic = onDiagnostic
- ..verbosity = verbosity;
- bool isLegacy =
- await fe.uriUsesLegacyLanguageVersion(resolvedUri, options);
- inferNullSafetyMode(!isLegacy);
-
- List<Uri> dependencies = [];
- if (_options.platformBinaries != null) {
- dependencies
- .add(_options.platformBinaries.resolve(getPlatformFilename()));
- }
- if (_options.dillDependencies != null) {
- dependencies.addAll(_options.dillDependencies);
- }
-
- initializedCompilerState = fe.initializeCompiler(
- initializedCompilerState,
- target,
- _options.librariesSpecificationUri,
- dependencies,
- _options.packageConfig,
- explicitExperimentalFlags: _options.explicitExperimentalFlags,
- nnbdMode: _options.useLegacySubtyping
- ? fe.NnbdMode.Weak
- : fe.NnbdMode.Strong,
- invocationModes: _options.cfeInvocationModes,
- verbosity: verbosity);
- component = await fe.compile(initializedCompilerState, verbose,
- fileSystem, onDiagnostic, resolvedUri);
- if (component == null) return null;
- validateNullSafetyMode();
- }
-
- if (forceSerialization) {
- // TODO(johnniwinther): Remove this when #34942 is fixed.
- List<int> data = serializeComponent(component);
- component = ir.Component();
- BinaryBuilder(data).readComponent(component);
- }
- return _toResult(entryLibrary, component, moduleLibraries);
- });
- }
-
- KernelResult _toResult(
- Library entryLibrary, ir.Component component, List<Uri> moduleLibraries) {
- Uri rootLibraryUri = null;
- Iterable<ir.Library> libraries = component.libraries;
- if (!_options.modularMode) {
- // For non-modular builds we should always have a [mainMethod] at this
- // point.
- if (component.mainMethod == null) {
- // TODO(sigmund): move this so that we use the same error template
- // from the CFE.
- _reporter.reportError(_reporter.createMessage(NO_LOCATION_SPANNABLE,
- MessageKind.GENERIC, {'text': "No 'main' method found."}));
- }
-
- // If we are building from dill and are passed an [entryUri], then we use
- // that to find the appropriate [entryLibrary]. Otherwise, we fallback to
- // the [enclosingLibrary] of the [mainMethod].
- // NOTE: Under some circumstances, the [entryLibrary] exports the
- // [mainMethod] from another library, and thus the [enclosingLibrary] of
- // the [mainMethod] may not be the same as the [entryLibrary].
- var root = entryLibrary ?? component.mainMethod.enclosingLibrary;
- rootLibraryUri = root.importUri;
-
- // Filter unreachable libraries: [Component] was built by linking in the
- // entire SDK libraries, not all of them are used. We include anything
- // that is reachable from `main`. Note that all internal libraries that
- // the compiler relies on are reachable from `dart:core`.
- var seen = Set<Library>();
- search(ir.Library current) {
- if (!seen.add(current)) return;
- for (ir.LibraryDependency dep in current.dependencies) {
- search(dep.targetLibrary);
- }
- }
-
- search(root);
-
- // Libraries dependencies do not show implicit imports to `dart:core`.
- var dartCore = component.libraries.firstWhere((lib) {
- return lib.importUri.isScheme('dart') && lib.importUri.path == 'core';
- });
- search(dartCore);
-
- libraries = libraries.where(seen.contains);
- }
- return KernelResult(component, rootLibraryUri,
- libraries.map((lib) => lib.importUri).toList(), moduleLibraries);
- }
-}
-
-/// Result of invoking the CFE to produce the kernel IR.
-class KernelResult {
- ir.Component component;
-
- /// The [Uri] of the root library containing main.
- /// Note: rootLibraryUri will be null for some modules, for example in the
- /// case of dependent libraries processed modularly.
- final Uri rootLibraryUri;
-
- /// Returns the [Uri]s of all libraries that have been loaded that are
- /// reachable from the [rootLibraryUri].
- ///
- /// Note that [component] may contain some libraries that are excluded here.
- final Iterable<Uri> libraries;
-
- /// When running only dart2js modular analysis, returns the [Uri]s for
- /// libraries loaded in the input module.
- ///
- /// This excludes other libraries reachable from them that were loaded as
- /// dependencies. The result of [moduleLibraries] is always a subset of
- /// [libraries].
- final Iterable<Uri> moduleLibraries;
-
- KernelResult(this.component, this.rootLibraryUri, this.libraries,
- this.moduleLibraries);
-
- void trimComponent() {
- var irLibraryMap = <Uri, Library>{};
- var irLibraries = <Library>[];
- for (var library in component.libraries) {
- irLibraryMap[library.importUri] = library;
- }
- for (var library in libraries) {
- irLibraries.add(irLibraryMap[library]);
- }
- var mainMethod = component.mainMethodName;
- var componentMode = component.mode;
- component = ir.Component(
- libraries: irLibraries,
- uriToSource: component.uriToSource,
- nameRoot: component.root);
- component.setMainMethodAndMode(mainMethod, true, componentMode);
- }
-
- @override
- String toString() =>
- 'root=$rootLibraryUri,libraries=$libraries,module=$moduleLibraries';
-}
diff --git a/pkg/compiler/lib/src/phase/load_kernel.dart b/pkg/compiler/lib/src/phase/load_kernel.dart
new file mode 100644
index 0000000..1db4a15
--- /dev/null
+++ b/pkg/compiler/lib/src/phase/load_kernel.dart
@@ -0,0 +1,337 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:front_end/src/fasta/kernel/utils.dart';
+import 'package:kernel/ast.dart' as ir;
+import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
+
+import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
+import 'package:kernel/kernel.dart' hide LibraryDependency, Combinator;
+import 'package:kernel/target/targets.dart' hide DiagnosticReporter;
+
+import '../../compiler.dart' as api;
+import '../commandline_options.dart';
+import '../common.dart';
+import '../kernel/front_end_adapter.dart';
+import '../kernel/dart2js_target.dart' show Dart2jsTarget;
+import '../options.dart';
+
+class Input {
+ final CompilerOptions options;
+ final api.CompilerInput compilerInput;
+ final DiagnosticReporter reporter;
+
+ /// Shared state between compilations. Only used when loading from source.
+ final fe.InitializedCompilerState initializedCompilerState;
+
+ // TODO(johnniwinther): Remove this when #34942 is fixed.
+ /// Force in-memory serialization/deserialization of the loaded component.
+ ///
+ /// This is used for testing.
+ final bool forceSerialization;
+
+ Input(this.options, this.compilerInput, this.reporter,
+ this.initializedCompilerState, this.forceSerialization);
+}
+
+/// Result of invoking the CFE to produce the kernel IR.
+class Output {
+ final ir.Component component;
+
+ /// The [Uri] of the root library containing main.
+ /// Note: rootLibraryUri will be null for some modules, for example in the
+ /// case of dependent libraries processed modularly.
+ final Uri rootLibraryUri;
+
+ /// Returns the [Uri]s of all libraries that have been loaded that are
+ /// reachable from the [rootLibraryUri].
+ ///
+ /// Note that [component] may contain some libraries that are excluded here.
+ final Iterable<Uri> libraries;
+
+ /// When running only dart2js modular analysis, returns the [Uri]s for
+ /// libraries loaded in the input module.
+ ///
+ /// This excludes other libraries reachable from them that were loaded as
+ /// dependencies. The result of [moduleLibraries] is always a subset of
+ /// [libraries].
+ final Iterable<Uri> moduleLibraries;
+
+ final fe.InitializedCompilerState initializedCompilerState;
+
+ Output(this.component, this.rootLibraryUri, this.libraries,
+ this.moduleLibraries, this.initializedCompilerState);
+}
+
+Library _findEntryLibrary(Component component, Uri entryUri) {
+ var entryLibrary = component.libraries
+ .firstWhere((l) => l.fileUri == entryUri, orElse: () => null);
+ if (entryLibrary == null) {
+ throw ArgumentError('Entry uri $entryUri not found in dill.');
+ }
+ return entryLibrary;
+}
+
+ir.Reference _findMainMethod(Library entryLibrary) {
+ var mainMethod = entryLibrary.procedures
+ .firstWhere((p) => p.name.text == 'main', orElse: () => null);
+
+ // In some cases, a main method is defined in another file, and then
+ // exported. In these cases, we search for the main method in
+ // [additionalExports].
+ ir.Reference mainMethodReference;
+ if (mainMethod == null) {
+ mainMethodReference = entryLibrary.additionalExports
+ .firstWhere((p) => p.canonicalName.name == 'main', orElse: () => null);
+ } else {
+ mainMethodReference = mainMethod.reference;
+ }
+ if (mainMethodReference == null) {
+ throw ArgumentError(
+ 'Entry uri ${entryLibrary.fileUri} has no main method.');
+ }
+ return mainMethodReference;
+}
+
+String _getPlatformFilename(CompilerOptions options, String targetName) {
+ String unsoundMarker = options.useLegacySubtyping ? "_unsound" : "";
+ return "${targetName}_platform$unsoundMarker.dill";
+}
+
+void _inferNullSafetyMode(CompilerOptions options, bool isSound) {
+ if (options.nullSafetyMode == NullSafetyMode.unspecified) {
+ options.nullSafetyMode =
+ isSound ? NullSafetyMode.sound : NullSafetyMode.unsound;
+ }
+}
+
+void _validateNullSafetyMode(CompilerOptions options) {
+ assert(options.nullSafetyMode != NullSafetyMode.unspecified);
+}
+
+class _LoadFromKernelResult {
+ final ir.Component component;
+ final Library entryLibrary;
+ final List<Uri> moduleLibraries;
+
+ _LoadFromKernelResult(
+ this.component, this.entryLibrary, this.moduleLibraries);
+}
+
+Future<_LoadFromKernelResult> _loadFromKernel(CompilerOptions options,
+ api.CompilerInput compilerInput, String targetName) async {
+ Library entryLibrary;
+ var resolvedUri = options.compilationTarget;
+ ir.Component component = ir.Component();
+ List<Uri> moduleLibraries = [];
+
+ Future<void> read(Uri uri) async {
+ api.Input input =
+ await compilerInput.readFromUri(uri, inputKind: api.InputKind.binary);
+ BinaryBuilder(input.data).readComponent(component);
+ }
+
+ await read(resolvedUri);
+
+ if (options.modularMode) {
+ moduleLibraries = component.libraries.map((lib) => lib.importUri).toList();
+ }
+
+ var isStrongDill =
+ component.mode == ir.NonNullableByDefaultCompiledMode.Strong;
+ var incompatibleNullSafetyMode =
+ isStrongDill ? NullSafetyMode.unsound : NullSafetyMode.sound;
+ if (options.nullSafetyMode == incompatibleNullSafetyMode) {
+ var dillMode = isStrongDill ? 'sound' : 'unsound';
+ var option = isStrongDill ? Flags.noSoundNullSafety : Flags.soundNullSafety;
+ throw ArgumentError("$resolvedUri was compiled with $dillMode null "
+ "safety and is incompatible with the '$option' option");
+ }
+ _inferNullSafetyMode(options, isStrongDill);
+ _validateNullSafetyMode(options);
+
+ // Modular compiles do not include the platform on the input dill
+ // either.
+ if (options.platformBinaries != null) {
+ var platformUri = options.platformBinaries
+ .resolve(_getPlatformFilename(options, targetName));
+ // Modular analysis can be run on the sdk by providing directly the
+ // path to the platform.dill file. In that case, we do not load the
+ // platform file implicitly.
+ // TODO(joshualitt): Change how we detect this case so it is less
+ // brittle.
+ if (platformUri != resolvedUri) await read(platformUri);
+ }
+
+ // Concatenate dills.
+ if (options.dillDependencies != null) {
+ for (Uri dependency in options.dillDependencies) {
+ await read(dependency);
+ }
+ }
+
+ if (options.entryUri != null) {
+ entryLibrary = _findEntryLibrary(component, options.entryUri);
+ var mainMethod = _findMainMethod(entryLibrary);
+ component.setMainMethodAndMode(mainMethod, true, component.mode);
+ }
+ return _LoadFromKernelResult(component, entryLibrary, moduleLibraries);
+}
+
+class _LoadFromSourceResult {
+ final ir.Component component;
+ final fe.InitializedCompilerState initializedCompilerState;
+
+ _LoadFromSourceResult(this.component, this.initializedCompilerState);
+}
+
+Future<_LoadFromSourceResult> _loadFromSource(
+ CompilerOptions options,
+ api.CompilerInput compilerInput,
+ DiagnosticReporter reporter,
+ fe.InitializedCompilerState initializedCompilerState,
+ String targetName) async {
+ bool verbose = false;
+ Target target = Dart2jsTarget(targetName, TargetFlags(), options: options);
+ fe.FileSystem fileSystem = CompilerFileSystem(compilerInput);
+ fe.Verbosity verbosity = options.verbosity;
+ fe.DiagnosticMessageHandler onDiagnostic = (fe.DiagnosticMessage message) {
+ if (fe.Verbosity.shouldPrint(verbosity, message)) {
+ reportFrontEndMessage(reporter, message);
+ }
+ };
+ fe.CompilerOptions feOptions = fe.CompilerOptions()
+ ..target = target
+ ..librariesSpecificationUri = options.librariesSpecificationUri
+ ..packagesFileUri = options.packageConfig
+ ..explicitExperimentalFlags = options.explicitExperimentalFlags
+ ..verbose = verbose
+ ..fileSystem = fileSystem
+ ..onDiagnostic = onDiagnostic
+ ..verbosity = verbosity;
+ Uri resolvedUri = options.compilationTarget;
+ bool isLegacy = await fe.uriUsesLegacyLanguageVersion(resolvedUri, feOptions);
+ _inferNullSafetyMode(options, !isLegacy);
+
+ List<Uri> dependencies = [];
+ if (options.platformBinaries != null) {
+ dependencies.add(options.platformBinaries
+ .resolve(_getPlatformFilename(options, targetName)));
+ }
+ if (options.dillDependencies != null) {
+ dependencies.addAll(options.dillDependencies);
+ }
+
+ initializedCompilerState = fe.initializeCompiler(
+ initializedCompilerState,
+ target,
+ options.librariesSpecificationUri,
+ dependencies,
+ options.packageConfig,
+ explicitExperimentalFlags: options.explicitExperimentalFlags,
+ nnbdMode:
+ options.useLegacySubtyping ? fe.NnbdMode.Weak : fe.NnbdMode.Strong,
+ invocationModes: options.cfeInvocationModes,
+ verbosity: verbosity);
+ ir.Component component = await fe.compile(
+ initializedCompilerState, verbose, fileSystem, onDiagnostic, resolvedUri);
+ _validateNullSafetyMode(options);
+ return _LoadFromSourceResult(component, initializedCompilerState);
+}
+
+Output _createOutput(
+ CompilerOptions options,
+ DiagnosticReporter reporter,
+ Library entryLibrary,
+ ir.Component component,
+ List<Uri> moduleLibraries,
+ fe.InitializedCompilerState initializedCompilerState) {
+ Uri rootLibraryUri = null;
+ Iterable<ir.Library> libraries = component.libraries;
+ if (!options.modularMode) {
+ // For non-modular builds we should always have a [mainMethod] at this
+ // point.
+ if (component.mainMethod == null) {
+ // TODO(sigmund): move this so that we use the same error template
+ // from the CFE.
+ reporter.reportError(reporter.createMessage(NO_LOCATION_SPANNABLE,
+ MessageKind.GENERIC, {'text': "No 'main' method found."}));
+ }
+
+ // If we are building from dill and are passed an [entryUri], then we use
+ // that to find the appropriate [entryLibrary]. Otherwise, we fallback to
+ // the [enclosingLibrary] of the [mainMethod].
+ // NOTE: Under some circumstances, the [entryLibrary] exports the
+ // [mainMethod] from another library, and thus the [enclosingLibrary] of
+ // the [mainMethod] may not be the same as the [entryLibrary].
+ var root = entryLibrary ?? component.mainMethod.enclosingLibrary;
+ rootLibraryUri = root.importUri;
+
+ // Filter unreachable libraries: [Component] was built by linking in the
+ // entire SDK libraries, not all of them are used. We include anything
+ // that is reachable from `main`. Note that all internal libraries that
+ // the compiler relies on are reachable from `dart:core`.
+ var seen = Set<Library>();
+ search(ir.Library current) {
+ if (!seen.add(current)) return;
+ for (ir.LibraryDependency dep in current.dependencies) {
+ search(dep.targetLibrary);
+ }
+ }
+
+ search(root);
+
+ // Libraries dependencies do not show implicit imports to `dart:core`.
+ var dartCore = component.libraries.firstWhere((lib) {
+ return lib.importUri.isScheme('dart') && lib.importUri.path == 'core';
+ });
+ search(dartCore);
+
+ libraries = libraries.where(seen.contains);
+ }
+ return Output(
+ component,
+ rootLibraryUri,
+ libraries.map((lib) => lib.importUri).toList(),
+ moduleLibraries,
+ initializedCompilerState);
+}
+
+/// Loads an entire Kernel [Component] from a file on disk.
+Future<Output> run(Input input) async {
+ CompilerOptions options = input.options;
+ api.CompilerInput compilerInput = input.compilerInput;
+ DiagnosticReporter reporter = input.reporter;
+
+ String targetName = options.compileForServer ? "dart2js_server" : "dart2js";
+
+ Library entryLibrary;
+ ir.Component component;
+ List<Uri> moduleLibraries = const [];
+ fe.InitializedCompilerState initializedCompilerState =
+ input.initializedCompilerState;
+ if (options.fromDill) {
+ _LoadFromKernelResult result =
+ await _loadFromKernel(options, compilerInput, targetName);
+ component = result.component;
+ entryLibrary = result.entryLibrary;
+ moduleLibraries = result.moduleLibraries;
+ } else {
+ _LoadFromSourceResult result = await _loadFromSource(options, compilerInput,
+ reporter, input.initializedCompilerState, targetName);
+ component = result.component;
+ initializedCompilerState = result.initializedCompilerState;
+ }
+ if (component == null) return null;
+ if (input.forceSerialization) {
+ // TODO(johnniwinther): Remove this when #34942 is fixed.
+ List<int> data = serializeComponent(component);
+ component = ir.Component();
+ BinaryBuilder(data).readComponent(component);
+ }
+ return _createOutput(options, reporter, entryLibrary, component,
+ moduleLibraries, initializedCompilerState);
+}
diff --git a/pkg/compiler/test/analyses/analysis_helper.dart b/pkg/compiler/test/analyses/analysis_helper.dart
index 763818b..fecbbef 100644
--- a/pkg/compiler/test/analyses/analysis_helper.dart
+++ b/pkg/compiler/test/analyses/analysis_helper.dart
@@ -15,7 +15,7 @@
import 'package:compiler/src/ir/scope.dart';
import 'package:compiler/src/ir/static_type.dart';
import 'package:compiler/src/ir/util.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
import 'package:expect/expect.dart';
import 'package:front_end/src/api_prototype/constant_evaluator.dart' as ir;
import 'package:front_end/src/api_unstable/dart2js.dart'
@@ -61,8 +61,15 @@
packageConfig: packageConfig,
entryPoint: entryPoint,
options: options);
- KernelResult result = await compiler.kernelLoader.load();
- new DynamicVisitor(compiler.reporter, result.component, allowedListPath,
+ load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+ compiler.options,
+ compiler.provider,
+ compiler.reporter,
+ compiler.initializedCompilerState,
+ false));
+ compiler.frontendStrategy
+ .registerLoadedLibraries(result.component, result.libraries);
+ DynamicVisitor(compiler.reporter, result.component, allowedListPath,
analyzedUrisFilter)
.run(verbose: verbose, generate: generate);
});
diff --git a/pkg/compiler/test/analyses/static_type_visitor_test.dart b/pkg/compiler/test/analyses/static_type_visitor_test.dart
index 4ec5093..3c504c0 100644
--- a/pkg/compiler/test/analyses/static_type_visitor_test.dart
+++ b/pkg/compiler/test/analyses/static_type_visitor_test.dart
@@ -7,7 +7,7 @@
import 'package:async_helper/async_helper.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/ir/static_type.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/class_hierarchy.dart' as ir;
import 'package:kernel/core_types.dart' as ir;
@@ -26,7 +26,12 @@
Compiler compiler = await compilerFor(
memorySourceFiles: {'main.dart': source},
entryPoint: Uri.parse('memory:main.dart'));
- KernelResult result = await compiler.kernelLoader.load();
+ load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+ compiler.options,
+ compiler.provider,
+ compiler.reporter,
+ compiler.initializedCompilerState,
+ false));
ir.Component component = result.component;
StaticTypeVisitor visitor = new Visitor(component);
component.accept(visitor);
diff --git a/pkg/compiler/test/end_to_end/dill_loader_test.dart b/pkg/compiler/test/end_to_end/dill_loader_test.dart
index 6e22f60..fe24e88 100644
--- a/pkg/compiler/test/end_to_end/dill_loader_test.dart
+++ b/pkg/compiler/test/end_to_end/dill_loader_test.dart
@@ -10,7 +10,7 @@
import 'package:compiler/src/elements/entities.dart'
show LibraryEntity, ClassEntity;
import 'package:compiler/src/kernel/dart2js_target.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
import 'package:expect/expect.dart';
import 'package:front_end/src/api_unstable/dart2js.dart';
import 'package:front_end/src/fasta/kernel/utils.dart' show serializeComponent;
@@ -42,8 +42,14 @@
memorySourceFiles: {'main.dill': kernelBinary},
diagnosticHandler: diagnostics,
outputProvider: output);
- KernelResult result = await compiler.kernelLoader.load();
- compiler.frontendStrategy.registerLoadedLibraries(result);
+ load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+ compiler.options,
+ compiler.provider,
+ compiler.reporter,
+ compiler.initializedCompilerState,
+ false));
+ compiler.frontendStrategy
+ .registerLoadedLibraries(result.component, result.libraries);
Expect.equals(0, diagnostics.errors.length);
Expect.equals(0, diagnostics.warnings.length);
diff --git a/pkg/compiler/test/end_to_end/modular_loader_test.dart b/pkg/compiler/test/end_to_end/modular_loader_test.dart
index 00abbf1..33fc298 100644
--- a/pkg/compiler/test/end_to_end/modular_loader_test.dart
+++ b/pkg/compiler/test/end_to_end/modular_loader_test.dart
@@ -10,7 +10,7 @@
import 'package:compiler/src/elements/entities.dart'
show LibraryEntity, ClassEntity;
import 'package:compiler/src/kernel/dart2js_target.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
import 'package:expect/expect.dart';
import 'package:front_end/src/api_prototype/front_end.dart';
import 'package:front_end/src/api_prototype/memory_file_system.dart';
@@ -43,8 +43,14 @@
memorySourceFiles: {'a.dill': aDill, 'b.dill': bDill, 'c.dill': cDill},
diagnosticHandler: diagnostics,
outputProvider: output);
- KernelResult result = await compiler.kernelLoader.load();
- compiler.frontendStrategy.registerLoadedLibraries(result);
+ load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+ compiler.options,
+ compiler.provider,
+ compiler.reporter,
+ compiler.initializedCompilerState,
+ false));
+ compiler.frontendStrategy
+ .registerLoadedLibraries(result.component, result.libraries);
Expect.equals(0, diagnostics.errors.length);
Expect.equals(0, diagnostics.warnings.length);
diff --git a/pkg/compiler/test/helpers/memory_compiler.dart b/pkg/compiler/test/helpers/memory_compiler.dart
index 7131e00..31866ca 100644
--- a/pkg/compiler/test/helpers/memory_compiler.dart
+++ b/pkg/compiler/test/helpers/memory_compiler.dart
@@ -109,8 +109,8 @@
beforeRun(compiler);
}
bool isSuccess = await compiler.run();
- fe.InitializedCompilerState compilerState = kernelInitializedCompilerState =
- compiler.kernelLoader.initializedCompilerState;
+ fe.InitializedCompilerState compilerState =
+ kernelInitializedCompilerState = compiler.initializedCompilerState;
return new CompilationResult(compiler,
isSuccess: isSuccess, kernelInitializedCompilerState: compilerState);
}
diff --git a/pkg/compiler/test/serialization/serialization_test_helper.dart b/pkg/compiler/test/serialization/serialization_test_helper.dart
index a29a79c..2df1808 100644
--- a/pkg/compiler/test/serialization/serialization_test_helper.dart
+++ b/pkg/compiler/test/serialization/serialization_test_helper.dart
@@ -105,7 +105,7 @@
options: commonOptions,
outputProvider: collector,
beforeRun: (Compiler compiler) {
- compiler.kernelLoader.forceSerialization = true;
+ compiler.forceSerializationForTesting = true;
});
Expect.isTrue(result.isSuccess);
Map<OutputType, Map<String, String>> expectedOutput = collector.clear();
@@ -119,7 +119,7 @@
options: commonOptions,
outputProvider: collector2,
beforeRun: (Compiler compiler) {
- compiler.kernelLoader.forceSerialization = true;
+ compiler.forceSerializationForTesting = true;
compiler.stopAfterClosedWorld = true;
});
Expect.isTrue(result2.isSuccess);
@@ -136,7 +136,7 @@
['--out=$dillUri', '${Flags.writeClosedWorld}=$closedWorldUri'],
outputProvider: collector3a,
beforeRun: (Compiler compiler) {
- compiler.kernelLoader.forceSerialization = true;
+ compiler.forceSerializationForTesting = true;
});
Expect.isTrue(result3a.isSuccess);
Expect.isTrue(collector3a.binaryOutputMap.containsKey(dillUri));
@@ -164,7 +164,7 @@
],
outputProvider: collector3b,
beforeRun: (Compiler compiler) {
- compiler.kernelLoader.forceSerialization = true;
+ compiler.forceSerializationForTesting = true;
compiler.stopAfterTypeInference = true;
});
Expect.isTrue(result3b.isSuccess);
diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart
index be47b4c..8b6b46e 100644
--- a/pkg/dart2native/lib/dart2native.dart
+++ b/pkg/dart2native/lib/dart2native.dart
@@ -5,6 +5,9 @@
import 'dart:io';
import 'dart:typed_data';
+import 'package:dart2native/dart2native_macho.dart'
+ show writeAppendedMachOExecutable;
+
// Maximum page size across all supported architectures (arm64 macOS has 16K
// pages, some arm64 Linux distributions have 64K pages).
const elfPageSize = 65536;
@@ -14,6 +17,11 @@
Future writeAppendedExecutable(
String dartaotruntimePath, String payloadPath, String outputPath) async {
+ if (Platform.isMacOS) {
+ return await writeAppendedMachOExecutable(
+ dartaotruntimePath, payloadPath, outputPath);
+ }
+
final dartaotruntime = File(dartaotruntimePath);
final int dartaotruntimeLength = dartaotruntime.lengthSync();
diff --git a/pkg/dart2native/lib/dart2native_macho.dart b/pkg/dart2native/lib/dart2native_macho.dart
new file mode 100644
index 0000000..7f3b786
--- /dev/null
+++ b/pkg/dart2native/lib/dart2native_macho.dart
@@ -0,0 +1,334 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:dart2native/macho.dart';
+import 'package:dart2native/macho_parser.dart';
+
+const String kSnapshotSegmentName = "__CUSTOM";
+const String kSnapshotSectionName = "__dart_app_snap";
+const int kMinimumSegmentSize = 0x4000;
+// Since arm64 macOS has 16K pages, which is larger than the 4K pages on x64
+// macOS, we use this larger page size to ensure the MachO file is aligned
+// properly on all architectures.
+const int kSegmentAlignment = 0x4000;
+
+int align(int size, int base) {
+ final int over = size % base;
+ if (over != 0) {
+ return size + (base - over);
+ }
+ return size;
+}
+
+// Utility for aligning parts of MachO headers to the defined sizes.
+int vmSizeAlign(int size) {
+ return align(max(size, kMinimumSegmentSize), kSegmentAlignment);
+}
+
+// Returns value + amount only if the original value is within the bounds
+// defined by [withinStart, withinStart + withinSize).
+Uint32 addIfWithin(
+ Uint32 value, Uint64 amount, Uint64 withinStart, Uint64 withinSize) {
+ final intWithinStart = withinStart.asInt();
+ final intWithinSize = withinSize.asInt();
+
+ if (value >= intWithinStart && value < (intWithinStart + intWithinSize)) {
+ return (value.asUint64() + amount).asUint32();
+ } else {
+ return value;
+ }
+}
+
+// Trims a bytestring that an arbitrary number of null characters on the end of
+// it.
+String trimmedBytestring(Uint8List bytestring) {
+ return String.fromCharCodes(bytestring.takeWhile((value) => value != 0));
+}
+
+// Simplifies casting so we get null values back instead of exceptions.
+T? cast<T>(x) => x is T ? x : null;
+
+// Inserts a segment definition into a MachOFile. This does NOT insert the
+// actual segment into the file. It only inserts the definition of that segment
+// into the MachO header.
+//
+// In addition to simply specifying the definition for the segment, this
+// function also moves the existing __LINKEDIT segment to the end of the header
+// definition as is required by the MachO specification (or at least MacOS's
+// implementation of it). In doing so there are several offsets in the original
+// __LINKEDIT segment that must be updated to point to their new location
+// because the __LINKEDIT segment and sections are now in a different
+// place. This function takes care of those shifts as well.
+//
+// Returns the original, unmodified, __LINKEDIT segment.
+Future<MachOSegmentCommand64> insertSegmentDefinition(MachOFile file,
+ File segment, String segmentName, String sectionName) async {
+ // Load in the data to be inserted.
+ final segmentData = await segment.readAsBytes();
+
+ // Find the existing __LINKEDIT segment
+ final linkedit = cast<MachOSegmentCommand64>(file.commands
+ .where((segment) =>
+ segment.asType() is MachOSegmentCommand64 &&
+ MachOConstants.SEG_LINKEDIT ==
+ trimmedBytestring((segment as MachOSegmentCommand64).segname))
+ .first);
+
+ final linkeditIndex = file.commands.indexWhere((segment) =>
+ segment.asType() is MachOSegmentCommand64 &&
+ MachOConstants.SEG_LINKEDIT ==
+ trimmedBytestring((segment as MachOSegmentCommand64).segname));
+
+ if (linkedit == null) {
+ throw FormatException(
+ "Could not find a __LINKEDIT section in the specified binary.");
+ } else {
+ // Create the new segment.
+ final Uint8List segname = Uint8List(16);
+ segname.setRange(0, segmentName.length, ascii.encode(segmentName));
+ segname.fillRange(segmentName.length, 16, 0);
+
+ final Uint64 vmaddr = linkedit.vmaddr;
+ final Uint64 vmsize = Uint64(vmSizeAlign(segmentData.length));
+ final Uint64 fileoff = linkedit.fileoff;
+ final Uint64 filesize = vmsize;
+ final Int32 maxprot = MachOConstants.VM_PROT_READ;
+ final Int32 initprot = maxprot;
+ final Uint32 nsects = Uint32(1);
+
+ final Uint8List sectname = Uint8List(16);
+ sectname.setRange(0, sectionName.length, ascii.encode(sectionName));
+ sectname.fillRange(sectionName.length, 16, 0);
+
+ final Uint64 addr = vmaddr;
+ final Uint64 size = Uint64(segmentData.length);
+ final Uint32 offset = fileoff.asUint32();
+ final Uint32 flags = MachOConstants.S_REGULAR;
+
+ final Uint32 zero = Uint32(0);
+
+ final loadCommandDefinitionSize = 4 * 2;
+ final sectionDefinitionSize = 16 * 2 + 8 * 2 + 4 * 8;
+ final segmentDefinitionSize = 16 + 8 * 4 + 4 * 4;
+ final commandSize = loadCommandDefinitionSize +
+ segmentDefinitionSize +
+ sectionDefinitionSize;
+
+ final loadCommand =
+ MachOLoadCommand(MachOConstants.LC_SEGMENT_64, Uint32(commandSize));
+
+ final section = MachOSection64(sectname, segname, addr, size, offset, zero,
+ zero, zero, flags, zero, zero, zero);
+
+ final segment = MachOSegmentCommand64(Uint32(commandSize), segname, vmaddr,
+ vmsize, fileoff, filesize, maxprot, initprot, nsects, zero, [section]);
+
+ // Setup the new linkedit command.
+ final shiftedLinkeditVmaddr = linkedit.vmaddr + segment.vmsize;
+ final shiftedLinkeditFileoff = linkedit.fileoff + segment.filesize;
+ final shiftedLinkedit = MachOSegmentCommand64(
+ linkedit.cmdsize,
+ linkedit.segname,
+ shiftedLinkeditVmaddr,
+ linkedit.vmsize,
+ shiftedLinkeditFileoff,
+ linkedit.filesize,
+ linkedit.maxprot,
+ linkedit.initprot,
+ linkedit.nsects,
+ linkedit.flags,
+ linkedit.sections);
+
+ // Shift all of the related commands that need to reference the new file
+ // position of the linkedit segment.
+ for (var i = 0; i < file.commands.length; i++) {
+ final command = file.commands[i];
+
+ final offsetAmount = segment.filesize;
+ final withinStart = linkedit.fileoff;
+ final withinSize = linkedit.filesize;
+
+ // For the specific command that we need to adjust, we need to move the
+ // commands' various offsets forward by the new segment's size in the file
+ // (segment.filesize). However, we need to ensure that when we move the
+ // offset forward, we exclude cases where the offset was originally
+ // outside of the linkedit segment (i.e. offset < linkedit.fileoff or
+ // offset >= linkedit.fileoff + linkedit.filesize). The DRY-ing function
+ // addIfWithin takes care of that repeated logic.
+ if (command is MachODyldInfoCommand) {
+ file.commands[i] = MachODyldInfoCommand(
+ command.cmd,
+ command.cmdsize,
+ addIfWithin(
+ command.rebase_off, offsetAmount, withinStart, withinSize),
+ command.rebase_size,
+ addIfWithin(
+ command.bind_off, offsetAmount, withinStart, withinSize),
+ command.bind_size,
+ addIfWithin(
+ command.weak_bind_off, offsetAmount, withinStart, withinSize),
+ command.weak_bind_size,
+ addIfWithin(
+ command.lazy_bind_off, offsetAmount, withinStart, withinSize),
+ command.lazy_bind_size,
+ addIfWithin(
+ command.export_off, offsetAmount, withinStart, withinSize),
+ command.export_size);
+ } else if (command is MachOSymtabCommand) {
+ file.commands[i] = MachOSymtabCommand(
+ command.cmdsize,
+ addIfWithin(command.symoff, offsetAmount, withinStart, withinSize),
+ command.nsyms,
+ addIfWithin(command.stroff, offsetAmount, withinStart, withinSize),
+ command.strsize);
+ } else if (command is MachODysymtabCommand) {
+ file.commands[i] = MachODysymtabCommand(
+ command.cmdsize,
+ command.ilocalsym,
+ command.nlocalsym,
+ command.iextdefsym,
+ command.nextdefsym,
+ command.iundefsym,
+ command.nundefsym,
+ addIfWithin(command.tocoff, offsetAmount, withinStart, withinSize),
+ command.ntoc,
+ addIfWithin(
+ command.modtaboff, offsetAmount, withinStart, withinSize),
+ command.nmodtab,
+ addIfWithin(
+ command.extrefsymoff, offsetAmount, withinStart, withinSize),
+ command.nextrefsyms,
+ addIfWithin(
+ command.indirectsymoff, offsetAmount, withinStart, withinSize),
+ command.nindirectsyms,
+ addIfWithin(
+ command.extreloff, offsetAmount, withinStart, withinSize),
+ command.nextrel,
+ addIfWithin(
+ command.locreloff, offsetAmount, withinStart, withinSize),
+ command.nlocrel);
+ } else if (command is MachOLinkeditDataCommand) {
+ file.commands[i] = MachOLinkeditDataCommand(
+ command.cmd,
+ command.cmdsize,
+ addIfWithin(command.dataoff, offsetAmount, withinStart, withinSize),
+ command.datasize);
+ }
+ }
+
+ // Now we need to build the new header from these modified pieces.
+ file.header = MachOHeader(
+ file.header!.magic,
+ file.header!.cputype,
+ file.header!.cpusubtype,
+ file.header!.filetype,
+ file.header!.ncmds + Uint32(1),
+ file.header!.sizeofcmds + loadCommand.cmdsize,
+ file.header!.flags,
+ file.header!.reserved);
+
+ file.commands[linkeditIndex] = shiftedLinkedit;
+ file.commands.insert(linkeditIndex, segment);
+ }
+
+ return linkedit;
+}
+
+// Pipe from one file stream into another. We do this in chunks to avoid
+// excessive memory load.
+Future<int> pipeStream(RandomAccessFile from, RandomAccessFile to,
+ {int? numToWrite, int chunkSize = 1 << 30}) async {
+ int numWritten = 0;
+ final int fileLength = from.lengthSync();
+ while (from.positionSync() != fileLength) {
+ final int availableBytes = fileLength - from.positionSync();
+ final int numToRead = numToWrite == null
+ ? min(availableBytes, chunkSize)
+ : min(numToWrite - numWritten, min(availableBytes, chunkSize));
+
+ final buffer = await from.read(numToRead);
+ await to.writeFrom(buffer);
+
+ numWritten += numToRead;
+
+ if (numToWrite != null && numWritten >= numToWrite) {
+ break;
+ }
+ }
+
+ return numWritten;
+}
+
+// Writes an "appended" dart runtime + script snapshot file in a format
+// compatible with MachO executables.
+Future writeAppendedMachOExecutable(
+ String dartaotruntimePath, String payloadPath, String outputPath) async {
+ File originalExecutableFile = File(dartaotruntimePath);
+
+ MachOFile machOFile = MachOFile();
+ await machOFile.loadFromFile(originalExecutableFile);
+
+ // Insert the new segment that contains our snapshot data.
+ File newSegmentFile = File(payloadPath);
+
+ // Note that these two values MUST match the ones in
+ // runtime/bin/snapshot_utils.cc, which looks specifically for the snapshot in
+ // this segment/section.
+ final linkeditCommand = await insertSegmentDefinition(
+ machOFile, newSegmentFile, kSnapshotSegmentName, kSnapshotSectionName);
+
+ // Write out the new executable, with the same contents except the new header.
+ File outputFile = File(outputPath);
+ RandomAccessFile stream = await outputFile.open(mode: FileMode.write);
+
+ // Write the MachO header.
+ machOFile.writeSync(stream);
+ final int headerBytesWritten = stream.positionSync();
+
+ RandomAccessFile newSegmentFileStream = await newSegmentFile.open();
+ RandomAccessFile originalFileStream = await originalExecutableFile.open();
+ await originalFileStream.setPosition(headerBytesWritten);
+
+ // Write the unchanged data from the original file.
+ await pipeStream(originalFileStream, stream,
+ numToWrite: linkeditCommand.fileoff.asInt() - headerBytesWritten);
+
+ // Write the inserted section data, ensuring that the data is padded to the
+ // segment size.
+ await pipeStream(newSegmentFileStream, stream);
+ final int newSegmentLength = newSegmentFileStream.lengthSync();
+ final int alignedSegmentSize = vmSizeAlign(newSegmentLength);
+ await stream.writeFrom(List.filled(alignedSegmentSize - newSegmentLength, 0));
+
+ // Copy the rest of the file from the original to the new one.
+ await pipeStream(originalFileStream, stream);
+
+ await stream.close();
+
+ if (machOFile.hasCodeSignature) {
+ // After writing the modified file, we perform ad-hoc signing (no identity)
+ // similar to the linker (the linker-signed option flag) to ensure that any
+ // LC_CODE_SIGNATURE block has the correct CD hashes. This is necessary for
+ // platforms where signature verification is always on (e.g., OS X on M1).
+ final signingProcess = await Process.run(
+ 'codesign', ['-o', 'linker-signed', '-s', '-', outputPath]);
+ if (signingProcess.exitCode != 0) {
+ print('Subcommand terminated with exit code ${signingProcess.exitCode}.');
+ if (signingProcess.stdout.isNotEmpty) {
+ print('Subcommand stdout:');
+ print(signingProcess.stdout);
+ }
+ if (signingProcess.stderr.isNotEmpty) {
+ print('Subcommand stderr:');
+ print(signingProcess.stderr);
+ }
+ throw 'Could not sign the new executable';
+ }
+ }
+}
diff --git a/pkg/dart2native/lib/macho.dart b/pkg/dart2native/lib/macho.dart
new file mode 100644
index 0000000..7aaccb8
--- /dev/null
+++ b/pkg/dart2native/lib/macho.dart
@@ -0,0 +1,2416 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This file is a reimplementation of the header file mach-o/loader.h, which is
+// part of the Apple system headers. All comments, which detail the format of
+// Mach-O files, have been reproduced from the orginal header.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+// Extensions for writing the custom byte types (defined below) to streams
+// (RandomAccessFile).
+extension ByteWriter on RandomAccessFile {
+ void writeUint32(Uint32 value) {
+ final intValue = value.asInt();
+ for (int i = 0; i < 4; i++) {
+ writeByteSync((intValue >> (8 * i)) & 0xff);
+ }
+ }
+
+ void writeUint64(Uint64 value) {
+ final intValue = value.asInt();
+ for (int i = 0; i < 8; i++) {
+ writeByteSync((intValue >> (8 * i)) & 0xff);
+ }
+ }
+
+ void writeInt32(Int32 value) {
+ final intValue = value.asInt();
+ for (int i = 0; i < 4; i++) {
+ writeByteSync((intValue >> (8 * i)) & 0xff);
+ }
+ }
+}
+
+// The dart ffi library doesn't have definitions for integer operations (among
+// others) on ffi types. Since we use those operations prolifically in handling
+// MachO files, these are here as a convenience to avoid polluting the code with
+// casts and operations.
+abstract class IntLike {
+ final int _data;
+ const IntLike(this._data);
+
+ int asInt() => _data;
+
+ @override
+ String toString() => asInt().toString();
+}
+
+class Uint64 extends IntLike {
+ const Uint64(int data) : super(data);
+
+ Uint64 operator +(Uint64 other) {
+ return Uint64(_data + other._data);
+ }
+
+ Uint32 asUint32() {
+ if (_data < 0 || _data >= (1 << 32)) {
+ throw FormatException(
+ "Attempted to cast a Uint64 to a Uint32, but the value will not fit in "
+ "32bits: $_data");
+ }
+
+ return Uint32(_data);
+ }
+
+ bool operator <(int other) {
+ // All positively encoded integers are less than negatively encoded ones.
+ if (_data < 0 && other > 0) {
+ return false;
+ }
+ if (other < 0) {
+ return true;
+ }
+ return _data < other;
+ }
+
+ bool operator >(int other) {
+ // All negatively encoded integers are greater than positively encoded ones.
+ if (_data < 0 && other > 0) {
+ return true;
+ }
+ if (other < 0) {
+ return false;
+ }
+ return _data > other;
+ }
+
+ bool operator ==(other) {
+ if (other is Uint64) {
+ return _data == other._data;
+ } else {
+ return false;
+ }
+ }
+}
+
+class Int32 extends IntLike {
+ const Int32(int data) : super(data);
+
+ Int32 operator |(Int32 other) {
+ return Int32(_data | other._data);
+ }
+
+ bool operator ==(other) {
+ if (other is Int32) {
+ return _data == other._data;
+ } else {
+ return false;
+ }
+ }
+}
+
+class Uint16 extends IntLike {
+ const Uint16(int data) : super(data);
+}
+
+class Uint32 extends IntLike {
+ const Uint32(int data) : super(data);
+
+ Uint32 operator |(Uint32 other) {
+ return Uint32(_data | other._data);
+ }
+
+ Uint32 operator +(Uint32 other) {
+ return Uint32(_data + other._data);
+ }
+
+ Uint32 operator &(Uint32 other) {
+ return Uint32(_data & other._data);
+ }
+
+ Uint32 operator >>(Uint32 other) {
+ return Uint32(_data >> other._data);
+ }
+
+ bool operator <(int other) {
+ return _data < other;
+ }
+
+ bool operator >(int other) {
+ return _data > other;
+ }
+
+ bool operator >=(int other) {
+ return _data >= other;
+ }
+
+ bool operator ==(other) {
+ if (other is Uint32) {
+ return _data == other._data;
+ } else {
+ return false;
+ }
+ }
+
+ Uint64 asUint64() {
+ return Uint64(_data);
+ }
+}
+
+// A load command is simply a part of the MachO header that indicates there is
+// typed schema that a consumer of the headers can use to understand how to load
+// and run various parts of the file (e.g. where to find the TEXT and DATA
+// sections). Every load command with a known schema in a MachO header should
+// extend this abstract class. This class does not appear in the original MachO
+// definitions, but is useful for the object-oriented nature of this
+// implementation.
+abstract class IMachOLoadCommand<T> {
+ /* type of load command (uint32_t) */
+ final Uint32 cmd;
+ /* total size of command in bytes (uint32_t) */
+ final Uint32 cmdsize;
+
+ IMachOLoadCommand(this.cmd, this.cmdsize);
+ T asType();
+
+ void writeSync(RandomAccessFile stream) {
+ stream.writeUint32(cmd);
+ stream.writeUint32(cmdsize);
+ writeContentsSync(stream);
+ }
+
+ // Subclasses need to implement this serializer, which should NOT
+ // attempt to serialize the cmd and the cmdsize to the stream. That
+ // logic is handled by the parent class automatically.
+ void writeContentsSync(RandomAccessFile stream);
+}
+
+// In cases where it's not necessary to actually deserialize a load command into
+// its schema, we use this catch-all class.
+class MachOGenericLoadCommand
+ extends IMachOLoadCommand<MachOGenericLoadCommand> {
+ final Uint8List contents;
+
+ MachOGenericLoadCommand(cmd, cmdsize, this.contents) : super(cmd, cmdsize);
+
+ @override
+ MachOGenericLoadCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeFromSync(contents);
+ }
+}
+
+// There are two types of headers: 32bit and 64bit. The only difference is that
+// 64bit headers have a reserved field. This class does not appear in the
+// original header definitions, but is useful for the object-oriented nature of
+// this implementation.
+abstract class IMachOHeader {
+ /* mach magic number identifier (uint32_t) */
+ final Uint32 magic;
+ /* cpu specifier (uint32_t) */
+ final Uint32 cputype;
+ /* machine specifier (uint32_t) */
+ final Uint32 cpusubtype;
+ /* type of file (uint32_t) */
+ final Uint32 filetype;
+ /* number of load commands (uint32_t) */
+ final Uint32 ncmds;
+ /* the size of all the load commands (uint32_t) */
+ final Uint32 sizeofcmds;
+ /* flags (uint32_t) */
+ final Uint32 flags;
+
+ /* reserved (uint32_t) */
+ final Uint32 reserved; // Only used in 64bit MachO files
+
+ IMachOHeader(
+ this.magic,
+ this.cputype,
+ this.cpusubtype,
+ this.filetype,
+ this.ncmds,
+ this.sizeofcmds,
+ this.flags,
+ this.reserved,
+ );
+}
+
+/*
+* The 32-bit mach header appears at the very beginning of the object file for
+* 32-bit architectures.
+*/
+class MachOHeader32 extends IMachOHeader {
+ MachOHeader32(
+ Uint32 magic,
+ Uint32 cputype,
+ Uint32 cpusubtype,
+ Uint32 filetype,
+ Uint32 ncmds,
+ Uint32 sizeofcmds,
+ Uint32 flags,
+ ) : super(
+ magic,
+ cputype,
+ cpusubtype,
+ filetype,
+ ncmds,
+ sizeofcmds,
+ flags,
+ Uint32(0),
+ );
+}
+
+/*
+* The 64-bit mach header appears at the very beginning of object files for
+* 64-bit architectures.
+*/
+class MachOHeader extends IMachOHeader {
+ MachOHeader(
+ Uint32 magic,
+ Uint32 cputype,
+ Uint32 cpusubtype,
+ Uint32 filetype,
+ Uint32 ncmds,
+ Uint32 sizeofcmds,
+ Uint32 flags,
+ Uint32 reserved,
+ ) : super(
+ magic,
+ cputype,
+ cpusubtype,
+ filetype,
+ ncmds,
+ sizeofcmds,
+ flags,
+ reserved,
+ );
+}
+
+/*
+* The load commands directly follow the mach_header. The total size of all
+* of the commands is given by the sizeofcmds field in the mach_header. All
+* load commands must have as their first two fields cmd and cmdsize. The cmd
+* field is filled in with a constant for that command type. Each command type
+* has a structure specifically for it. The cmdsize field is the size in bytes
+* of the particular load command structure plus anything that follows it that
+* is a part of the load command (i.e. section structures, strings, etc.). To
+* advance to the next load command the cmdsize can be added to the offset or
+* pointer of the current load command. The cmdsize for 32-bit architectures
+* MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple
+* of 8 bytes (these are forever the maximum alignment of any load commands).
+* The padded bytes must be zero. All tables in the object file must also
+* follow these rules so the file can be memory mapped. Otherwise the pointers
+* to these tables will not work well or at all on some machines. With all
+* padding zeroed like objects will compare byte for byte.
+*/
+class MachOLoadCommand {
+ /* type of load command (uint32_t) */
+ final Uint32 cmd;
+ /* total size of command in bytes (uint32_t) */
+ final Uint32 cmdsize;
+
+ MachOLoadCommand(this.cmd, this.cmdsize);
+}
+
+/*
+* The segment load command indicates that a part of this file is to be
+* mapped into the task's address space. The size of this segment in memory,
+* vmsize, maybe equal to or larger than the amount to map from this file,
+* filesize. The file is mapped starting at fileoff to the beginning of
+* the segment in memory, vmaddr. The rest of the memory of the segment,
+* if any, is allocated zero fill on demand. The segment's maximum virtual
+* memory protection and initial virtual memory protection are specified
+* by the maxprot and initprot fields. If the segment has sections then the
+* section structures directly follow the segment command and their size is
+* reflected in cmdsize.
+*/
+class MachOSegmentCommand extends IMachOLoadCommand<MachOSegmentCommand> {
+ /* For 32Bit Architectures */
+ final Uint8List segname; /* segment name */
+ final Uint32 vmaddr; /* memory address of this segment (uint32_t) */
+ final Uint32 vmsize; /* memory size of this segment (uint32_t) */
+ final Uint32 fileoff; /* file offset of this segment (uint32_t) */
+ final Uint32 filesize; /* amount to map from the file (uint32_t) */
+ final Int32 maxprot; /* maximum VM protection (int32) */
+ final Int32 initprot; /* initial VM protection (int32) */
+ final Uint32 nsects; /* number of sections in segment (uint32_t) */
+ final Uint32 flags; /* flags (uint32_t) */
+
+ MachOSegmentCommand(
+ Uint32 cmdsize,
+ this.segname,
+ this.vmaddr,
+ this.vmsize,
+ this.fileoff,
+ this.filesize,
+ this.maxprot,
+ this.initprot,
+ this.nsects,
+ this.flags,
+ ) : super(MachOConstants.LC_SEGMENT, cmdsize);
+
+ @override
+ MachOSegmentCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeFromSync(segname);
+ stream.writeUint32(vmaddr);
+ stream.writeUint32(vmsize);
+ stream.writeUint32(fileoff);
+ stream.writeUint32(filesize);
+ stream.writeInt32(maxprot);
+ stream.writeInt32(initprot);
+ stream.writeUint32(nsects);
+ stream.writeUint32(flags);
+ }
+}
+
+/*
+* The 64-bit segment load command indicates that a part of this file is to be
+* mapped into a 64-bit task's address space. If the 64-bit segment has
+* sections then section_64 structures directly follow the 64-bit segment
+* command and their size is reflected in cmdsize.
+*/
+class MachOSegmentCommand64 extends IMachOLoadCommand<MachOSegmentCommand64> {
+ /* For 64Bit Architectures */
+ final Uint8List segname; //[16] /* segment name */
+ final Uint64 vmaddr; /* memory address of this segment (uint64_t) */
+ final Uint64 vmsize; /* memory size of this segment (uint64_t) */
+ final Uint64 fileoff; /* file offset of this segment (uint64_t) */
+ final Uint64 filesize; /* amount to map from the file (uint64_t) */
+ final Int32 maxprot; /* maximum VM protection (int32) */
+ final Int32 initprot; /* initial VM protection (int32) */
+ final Uint32 nsects; /* number of sections in segment (uint32_t) */
+ final Uint32 flags; /* flags (uint32_t) */
+
+ final List<MachOSection64> sections;
+
+ MachOSegmentCommand64(
+ Uint32 cmdsize,
+ this.segname,
+ this.vmaddr,
+ this.vmsize,
+ this.fileoff,
+ this.filesize,
+ this.maxprot,
+ this.initprot,
+ this.nsects,
+ this.flags,
+ this.sections,
+ ) : super(MachOConstants.LC_SEGMENT_64, cmdsize);
+
+ @override
+ MachOSegmentCommand64 asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeFromSync(segname);
+ stream.writeUint64(vmaddr);
+ stream.writeUint64(vmsize);
+ stream.writeUint64(fileoff);
+ stream.writeUint64(filesize);
+ stream.writeInt32(maxprot);
+ stream.writeInt32(initprot);
+ stream.writeUint32(nsects);
+ stream.writeUint32(flags);
+
+ sections.forEach((section) {
+ section.writeContentsSync(stream);
+ });
+ }
+}
+
+/*
+* A segment is made up of zero or more sections. Non-MH_OBJECT files have
+* all of their segments with the proper sections in each, and padded to the
+* specified segment alignment when produced by the link editor. The first
+* segment of a MH_EXECUTE and MH_FVMLIB format file contains the mach_header
+* and load commands of the object file before its first section. The zero
+* fill sections are always last in their segment (in all formats). This
+* allows the zeroed segment padding to be mapped into memory where zero fill
+* sections might be. The gigabyte zero fill sections, those with the section
+* type S_GB_ZEROFILL, can only be in a segment with sections of this type.
+* These segments are then placed after all other segments.
+*
+* The MH_OBJECT format has all of its sections in one segment for
+* compactness. There is no padding to a specified segment boundary and the
+* mach_header and load commands are not part of the segment.
+*
+* Sections with the same section name, sectname, going into the same segment,
+* segname, are combined by the link editor. The resulting section is aligned
+* to the maximum alignment of the combined sections and is the new section's
+* alignment. The combined sections are aligned to their original alignment in
+* the combined section. Any padded bytes to get the specified alignment are
+* zeroed.
+*
+* The format of the relocation entries referenced by the reloff and nreloc
+* fields of the section structure for mach object files is described in the
+* header file <reloc.h>.
+*/
+class MachOSection {
+ /* for 32-bit architectures */
+ final Uint8List sectname; /* name of this section */
+ final Uint8List segname; /* segment this section goes in */
+ final Uint32 addr; /* memory address of this section (uint32_t) */
+ final Uint32 size; /* size in bytes of this section (uint32_t) */
+ final Uint32 offset; /* file offset of this section (uint32_t) */
+ final Uint32 align; /* section alignment (power of 2) (uint32_t) */
+ final Uint32 reloff; /* file offset of relocation entries (uint32_t) */
+ final Uint32 nreloc; /* number of relocation entries (uint32_t) */
+ final Uint32 flags; /* flags (section type and attributes)(uint32_t) */
+ final Uint32 reserved1; /* reserved (for offset or index) (uint32_t) */
+ final Uint32 reserved2; /* reserved (for count or sizeof) (uint32_t) */
+
+ MachOSection(
+ this.sectname,
+ this.segname,
+ this.addr,
+ this.size,
+ this.offset,
+ this.align,
+ this.reloff,
+ this.nreloc,
+ this.flags,
+ this.reserved1,
+ this.reserved2,
+ ) {
+ if (segname.length != 16) {
+ throw ArgumentError("segname must be 16 bytes exactly");
+ }
+ }
+}
+
+class MachOSection64 {
+ /* for 64-bit architectures */
+ final Uint8List sectname; //[16] /* name of this section */
+ final Uint8List segname; //[16] /* segment this section goes in */
+ final Uint64 addr; /* memory address of this section (uint64_t) */
+ final Uint64 size; /* size in bytes of this section (uint64_t) */
+ final Uint32 offset; /* file offset of this section (uint32_t) */
+ final Uint32 align; /* section alignment (power of 2) (uint32_t) */
+ final Uint32 reloff; /* file offset of relocation entries (uint32_t) */
+ final Uint32 nreloc; /* number of relocation entries (uint32_t) */
+ final Uint32 flags; /* flags (section type and attributes)(uint32_t) */
+ final Uint32 reserved1; /* reserved (for offset or index) (uint32_t) */
+ final Uint32 reserved2; /* reserved (for count or sizeof) (uint32_t) */
+ final Uint32 reserved3; /* reserved (uint32_t) */
+
+ MachOSection64(
+ this.sectname,
+ this.segname,
+ this.addr,
+ this.size,
+ this.offset,
+ this.align,
+ this.reloff,
+ this.nreloc,
+ this.flags,
+ this.reserved1,
+ this.reserved2,
+ this.reserved3,
+ ) {
+ if (segname.length != 16) {
+ throw ArgumentError("segname must be 16 bytes exactly");
+ }
+ }
+
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeFromSync(sectname);
+ stream.writeFromSync(segname);
+ stream.writeUint64(addr);
+ stream.writeUint64(size);
+ stream.writeUint32(offset);
+ stream.writeUint32(align);
+ stream.writeUint32(reloff);
+ stream.writeUint32(nreloc);
+ stream.writeUint32(flags);
+ stream.writeUint32(reserved1);
+ stream.writeUint32(reserved2);
+ stream.writeUint32(reserved3);
+ }
+}
+
+// This is a stand-in for the lc_str union in the MachO header.
+class MachOStr {
+ final int offset;
+ final Uint8List ptr;
+
+ MachOStr(this.offset, this.ptr);
+ // part of the schema so it doesn't contribute to
+ // the size of this schema.
+
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeInt32(Int32(offset));
+ stream.writeFromSync(ptr);
+ }
+}
+
+/*
+ * Fixed virtual memory shared libraries are identified by two things. The
+ * target pathname (the name of the library as found for execution), and the
+ * minor version number. The address of where the headers are loaded is in
+ * header_addr. (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOFvmlib {
+ final MachOStr name; /* library's target pathname */
+ final Uint32 minor_version; /* library's minor version number (uint32_t) */
+ final Uint32 header_addr; /* library's header address (uint32_t) */
+
+ MachOFvmlib(
+ this.name,
+ this.minor_version,
+ this.header_addr,
+ );
+
+ void writeContentsSync(RandomAccessFile stream) {
+ name.writeContentsSync(stream);
+ stream.writeUint32(minor_version);
+ stream.writeUint32(header_addr);
+ }
+}
+
+/*
+ * A fixed virtual shared library (filetype == MH_FVMLIB in the mach header)
+ * contains a fvmlib_command (cmd == LC_IDFVMLIB) to identify the library.
+ * An object that uses a fixed virtual shared library also contains a
+ * fvmlib_command (cmd == LC_LOADFVMLIB) for each library it uses.
+ * (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOFvmlibCommand extends IMachOLoadCommand<MachOFvmlibCommand> {
+ final MachOFvmlib fvmlib; /* the library identification */
+
+ MachOFvmlibCommand(
+ Uint32 cmdsize,
+ this.fvmlib,
+ ) : super(MachOConstants.LC_IDFVMLIB, cmdsize);
+
+ @override
+ MachOFvmlibCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ fvmlib.writeContentsSync(stream);
+ }
+}
+
+/*
+ * Dynamicly linked shared libraries are identified by two things. The
+ * pathname (the name of the library as found for execution), and the
+ * compatibility version number. The pathname must match and the compatibility
+ * number in the user of the library must be greater than or equal to the
+ * library being used. The time stamp is used to record the time a library was
+ * built and copied into user so it can be use to determined if the library used
+ * at runtime is exactly the same as used to built the program.
+ */
+class MachODylib {
+ final MachOStr name; /* library's path name */
+ final Uint32 timestamp; /* library's build time stamp (uint32_t) */
+ final Uint32
+ current_version; /* library's current version number (uint32_t) */
+ final Uint32
+ compatibility_version; /* library's compatibility vers number(uint32_t) */
+
+ MachODylib(
+ this.name,
+ this.timestamp,
+ this.current_version,
+ this.compatibility_version,
+ );
+
+ void writeContentsSync(RandomAccessFile stream) {
+ name.writeContentsSync(stream);
+ stream.writeUint32(timestamp);
+ stream.writeUint32(current_version);
+ stream.writeUint32(compatibility_version);
+ }
+}
+
+/*
+ * A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
+ * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
+ * An object that uses a dynamically linked shared library also contains a
+ * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
+ * LC_REEXPORT_DYLIB) for each library it uses.
+ */
+class MachODylibCommand extends IMachOLoadCommand<MachODylibCommand> {
+ final MachODylib dylib; /* the library identification */
+
+ MachODylibCommand(
+ Uint32 cmd,
+ Uint32 cmdsize,
+ this.dylib,
+ ) : super(cmd, cmdsize) {
+ if (this.cmd != MachOConstants.LC_ID_DYLIB &&
+ this.cmd != MachOConstants.LC_LOAD_WEAK_DYLIB &&
+ this.cmd != MachOConstants.LC_REEXPORT_DYLIB) {
+ throw ArgumentError(
+ "cmd was not one of LC_ID_DYLIB (${MachOConstants.LC_ID_DYLIB}), "
+ "LC_LOAD_WEAK_DYLIB (${MachOConstants.LC_LOAD_WEAK_DYLIB}), "
+ "LC_REEXPORT_DYLIB (${MachOConstants.LC_REEXPORT_DYLIB}): $cmd");
+ }
+ }
+
+ @override
+ MachODylibCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ dylib.writeContentsSync(stream);
+ }
+}
+
+/*
+ * A dynamically linked shared library may be a subframework of an umbrella
+ * framework. If so it will be linked with "-umbrella umbrella_name" where
+ * Where "umbrella_name" is the name of the umbrella framework. A subframework
+ * can only be linked against by its umbrella framework or other subframeworks
+ * that are part of the same umbrella framework. Otherwise the static link
+ * editor produces an error and states to link against the umbrella framework.
+ * The name of the umbrella framework for subframeworks is recorded in the
+ * following structure.
+ */
+class MachOSubFrameworkCommand
+ extends IMachOLoadCommand<MachOSubFrameworkCommand> {
+ final MachOStr umbrella; /* the umbrella framework name */
+
+ MachOSubFrameworkCommand(
+ Uint32 cmdsize,
+ this.umbrella,
+ ) : super(MachOConstants.LC_SUB_FRAMEWORK, cmdsize);
+
+ @override
+ MachOSubFrameworkCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ umbrella.writeContentsSync(stream);
+ }
+}
+
+/*
+ * For dynamically linked shared libraries that are subframework of an umbrella
+ * framework they can allow clients other than the umbrella framework or other
+ * subframeworks in the same umbrella framework. To do this the subframework
+ * is built with "-allowable_client client_name" and an LC_SUB_CLIENT load
+ * command is created for each -allowable_client flag. The client_name is
+ * usually a framework name. It can also be a name used for bundles clients
+ * where the bundle is built with "-client_name client_name".
+ */
+class MachOSubClientCommand extends IMachOLoadCommand<MachOSubClientCommand> {
+ final MachOStr client; /* the client name */
+
+ MachOSubClientCommand(
+ Uint32 cmdsize,
+ this.client,
+ ) : super(MachOConstants.LC_SUB_CLIENT, cmdsize);
+
+ @override
+ MachOSubClientCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ client.writeContentsSync(stream);
+ }
+}
+
+/*
+ * A dynamically linked shared library may be a sub_umbrella of an umbrella
+ * framework. If so it will be linked with "-sub_umbrella umbrella_name" where
+ * Where "umbrella_name" is the name of the sub_umbrella framework. When
+ * staticly linking when -twolevel_namespace is in effect a twolevel namespace
+ * umbrella framework will only cause its subframeworks and those frameworks
+ * listed as sub_umbrella frameworks to be implicited linked in. Any other
+ * dependent dynamic libraries will not be linked it when -twolevel_namespace
+ * is in effect. The primary library recorded by the static linker when
+ * resolving a symbol in these libraries will be the umbrella framework.
+ * Zero or more sub_umbrella frameworks may be use by an umbrella framework.
+ * The name of a sub_umbrella framework is recorded in the following structure.
+ */
+class MachOSubUmbrellaCommand
+ extends IMachOLoadCommand<MachOSubUmbrellaCommand> {
+ final MachOStr sub_umbrella; /* the sub_umbrella framework name */
+
+ MachOSubUmbrellaCommand(
+ Uint32 cmdsize,
+ this.sub_umbrella,
+ ) : super(
+ MachOConstants.LC_SUB_UMBRELLA,
+ cmdsize,
+ );
+
+ @override
+ MachOSubUmbrellaCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ sub_umbrella.writeContentsSync(stream);
+ }
+}
+
+/*
+ * A dynamically linked shared library may be a sub_library of another shared
+ * library. If so it will be linked with "-sub_library library_name" where
+ * Where "library_name" is the name of the sub_library shared library. When
+ * staticly linking when -twolevel_namespace is in effect a twolevel namespace
+ * shared library will only cause its subframeworks and those frameworks
+ * listed as sub_umbrella frameworks and libraries listed as sub_libraries to
+ * be implicited linked in. Any other dependent dynamic libraries will not be
+ * linked it when -twolevel_namespace is in effect. The primary library
+ * recorded by the static linker when resolving a symbol in these libraries
+ * will be the umbrella framework (or dynamic library). Zero or more sub_library
+ * shared libraries may be use by an umbrella framework or (or dynamic library).
+ * The name of a sub_library framework is recorded in the following structure.
+ * For example /usr/lib/libobjc_profile.A.dylib would be recorded as "libobjc".
+ */
+class MachOSubLibraryCommand extends IMachOLoadCommand<MachOSubLibraryCommand> {
+ final MachOStr sub_library; /* the sub_library name */
+
+ MachOSubLibraryCommand(
+ Uint32 cmdsize,
+ this.sub_library,
+ ) : super(
+ MachOConstants.LC_SUB_LIBRARY,
+ cmdsize,
+ );
+
+ @override
+ MachOSubLibraryCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ sub_library.writeContentsSync(stream);
+ }
+}
+
+/*
+ * A program (filetype == MH_EXECUTE) that is
+ * prebound to its dynamic libraries has one of these for each library that
+ * the static linker used in prebinding. It contains a bit vector for the
+ * modules in the library. The bits indicate which modules are bound (1) and
+ * which are not (0) from the library. The bit for module 0 is the low bit
+ * of the first byte. So the bit for the Nth module is:
+ * (linked_modules[N/8] >> N%8) & 1
+ */
+class MachOPreboundDylibCommand
+ extends IMachOLoadCommand<MachOPreboundDylibCommand> {
+ final MachOStr name; /* library's path name */
+ final Uint32 nmodules; /* number of modules in library (uint32_t) */
+ final MachOStr linked_modules; /* bit vector of linked modules */
+
+ MachOPreboundDylibCommand(
+ Uint32 cmdsize,
+ this.name,
+ this.nmodules,
+ this.linked_modules,
+ ) : super(
+ MachOConstants.LC_PREBOUND_DYLIB,
+ cmdsize,
+ );
+
+ @override
+ MachOPreboundDylibCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ name.writeContentsSync(stream);
+ stream.writeUint32(nmodules);
+ linked_modules.writeContentsSync(stream);
+ }
+}
+
+/*
+ * A program that uses a dynamic linker contains a dylinker_command to identify
+ * the name of the dynamic linker (LC_LOAD_DYLINKER). And a dynamic linker
+ * contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER).
+ * A file can have at most one of these.
+ * This struct is also used for the LC_DYLD_ENVIRONMENT load command and
+ * contains string for dyld to treat like environment variable.
+ */
+class MachODylinkerCommand extends IMachOLoadCommand<MachODylinkerCommand> {
+ final MachOStr name; /* dynamic linker's path name */
+
+ MachODylinkerCommand(
+ Uint32 cmd,
+ Uint32 cmdsize,
+ this.name,
+ ) : super(
+ cmd,
+ cmdsize,
+ ) {
+ if (this.cmd != MachOConstants.LC_ID_DYLINKER &&
+ this.cmd != MachOConstants.LC_LOAD_DYLINKER &&
+ this.cmd != MachOConstants.LC_DYLD_ENVIRONMENT) {
+ throw ArgumentError(
+ "cmd was not one of LC_ID_DYLINKER (${MachOConstants.LC_ID_DYLINKER}), "
+ "LC_LOAD_DYLINKER (${MachOConstants.LC_LOAD_DYLINKER}), "
+ "LC_DYLD_ENVIRONMENT (${MachOConstants.LC_DYLD_ENVIRONMENT}): $cmd");
+ }
+ }
+
+ @override
+ MachODylinkerCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ name.writeContentsSync(stream);
+ }
+}
+
+/*
+ * Thread commands contain machine-specific data structures suitable for
+ * use in the thread state primitives. The machine specific data structures
+ * follow the struct thread_command as follows.
+ * Each flavor of machine specific data structure is preceded by an uint32_t
+ * constant for the flavor of that data structure, an uint32_t that is the
+ * count of uint32_t's of the size of the state data structure and then
+ * the state data structure follows. This triple may be repeated for many
+ * flavors. The constants for the flavors, counts and state data structure
+ * definitions are expected to be in the header file <machine/thread_status.h>.
+ * These machine specific data structures sizes must be multiples of
+ * 4 bytes. The cmdsize reflects the total size of the thread_command
+ * and all of the sizes of the constants for the flavors, counts and state
+ * data structures.
+ *
+ * For executable objects that are unix processes there will be one
+ * thread_command (cmd == LC_UNIXTHREAD) created for it by the link-editor.
+ * This is the same as a LC_THREAD, except that a stack is automatically
+ * created (based on the shell's limit for the stack size). Command arguments
+ * and environment variables are copied onto that stack.
+ */
+class MachOThreadCommand extends IMachOLoadCommand<MachOThreadCommand> {
+ /* final int flavor flavor of thread state (uint32_t) */
+ /* final int count count of longs in thread state (uint32_t) */
+ /* struct XXX_thread_state state thread state for this flavor */
+ /* ... */
+
+ MachOThreadCommand(
+ Uint32 cmd,
+ Uint32 cmdsize,
+ /* final int flavor flavor of thread state (uint32_t) */
+ /* final int count count of longs in thread state (uint32_t) */
+ /* struct XXX_thread_state state thread state for this flavor */
+ /* ... */
+ ) : super(
+ cmd,
+ cmdsize,
+ ) {
+ if (this.cmd != MachOConstants.LC_THREAD &&
+ this.cmd != MachOConstants.LC_UNIXTHREAD) {
+ throw ArgumentError(
+ "cmd was not one of LC_THREAD (${MachOConstants.LC_THREAD}), "
+ "LC_UNIXTHREAD (${MachOConstants.LC_UNIXTHREAD}): $cmd");
+ }
+ }
+
+ @override
+ MachOThreadCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {}
+}
+
+/*
+ * The routines command contains the address of the dynamic shared library
+ * initialization routine and an index into the module table for the module
+ * that defines the routine. Before any modules are used from the library the
+ * dynamic linker fully binds the module that defines the initialization routine
+ * and then calls it. This gets called before any module initialization
+ * routines (used for C++ static constructors) in the library.
+ */
+class MachORoutinesCommand extends IMachOLoadCommand<MachORoutinesCommand> {
+ final Uint32 init_address; /* address of initialization routine (uint32_t) */
+ final Uint32 init_module; /* index into the module table that (uint32_t) */
+ /* the init routine is defined in */
+ final Uint32 reserved1; /* (uint32_t) */
+ final Uint32 reserved2; /* (uint32_t) */
+ final Uint32 reserved3; /* (uint32_t) */
+ final Uint32 reserved4; /* (uint32_t) */
+ final Uint32 reserved5; /* (uint32_t) */
+ final Uint32 reserved6; /* (uint32_t) */
+
+ MachORoutinesCommand(
+ Uint32 cmdsize,
+ this.init_address,
+ this.init_module,
+ this.reserved1,
+ this.reserved2,
+ this.reserved3,
+ this.reserved4,
+ this.reserved5,
+ this.reserved6,
+ ) : super(
+ MachOConstants.LC_ROUTINES,
+ cmdsize,
+ );
+
+ @override
+ MachORoutinesCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(init_address);
+ stream.writeUint32(init_module);
+ stream.writeUint32(reserved1);
+ stream.writeUint32(reserved2);
+ stream.writeUint32(reserved3);
+ stream.writeUint32(reserved4);
+ stream.writeUint32(reserved5);
+ stream.writeUint32(reserved6);
+ }
+}
+
+/*
+ * The 64-bit routines command. Same use as above.
+ */
+class MachORoutinesCommand64 extends IMachOLoadCommand<MachORoutinesCommand64> {
+ final Uint64 init_address; /* address of initialization routine (uint64_t) */
+ final Uint64 init_module; /* index into the module table that (uint64_t) */
+ /* the init routine is defined in */
+ final Uint64 reserved1; /* (uint64_t) */
+ final Uint64 reserved2; /* (uint64_t) */
+ final Uint64 reserved3; /* (uint64_t) */
+ final Uint64 reserved4; /* (uint64_t) */
+ final Uint64 reserved5; /* (uint64_t) */
+ final Uint64 reserved6; /* (uint64_t) */
+
+ MachORoutinesCommand64(
+ Uint32 cmdsize,
+ this.init_address,
+ this.init_module,
+ this.reserved1,
+ this.reserved2,
+ this.reserved3,
+ this.reserved4,
+ this.reserved5,
+ this.reserved6,
+ ) : super(
+ MachOConstants.LC_ROUTINES_64,
+ cmdsize,
+ );
+
+ @override
+ MachORoutinesCommand64 asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint64(init_address);
+ stream.writeUint64(init_module);
+ stream.writeUint64(reserved1);
+ stream.writeUint64(reserved2);
+ stream.writeUint64(reserved3);
+ stream.writeUint64(reserved4);
+ stream.writeUint64(reserved5);
+ stream.writeUint64(reserved6);
+ }
+}
+
+/*
+ * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
+ * "stab" style symbol table information as described in the header files
+ * <nlist.h> and <stab.h>.
+ */
+class MachOSymtabCommand extends IMachOLoadCommand<MachOSymtabCommand> {
+ final Uint32 symoff; /* symbol table offset (uint32_t) */
+ final Uint32 nsyms; /* number of symbol table entries (uint32_t) */
+ final Uint32 stroff; /* string table offset (uint32_t) */
+ final Uint32 strsize; /* string table size in bytes (uint32_t) */
+
+ MachOSymtabCommand(
+ Uint32 cmdsize,
+ this.symoff,
+ this.nsyms,
+ this.stroff,
+ this.strsize,
+ ) : super(
+ MachOConstants.LC_SYMTAB,
+ cmdsize,
+ );
+
+ @override
+ MachOSymtabCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(symoff);
+ stream.writeUint32(nsyms);
+ stream.writeUint32(stroff);
+ stream.writeUint32(strsize);
+ }
+}
+
+/*
+ * This is the second set of the symbolic information which is used to support
+ * the data structures for the dynamically link editor.
+ *
+ * The original set of symbolic information in the symtab_command which contains
+ * the symbol and string tables must also be present when this load command is
+ * present. When this load command is present the symbol table is organized
+ * into three groups of symbols:
+ * local symbols (static and debugging symbols) - grouped by module
+ * defined external symbols - grouped by module (sorted by name if not lib)
+ * undefined external symbols (sorted by name if MH_BINDATLOAD is not set,
+ * and in order the were seen by the static
+ * linker if MH_BINDATLOAD is set)
+ * In this load command there are offsets and counts to each of the three groups
+ * of symbols.
+ *
+ * This load command contains a the offsets and sizes of the following new
+ * symbolic information tables:
+ * table of contents
+ * module table
+ * reference symbol table
+ * indirect symbol table
+ * The first three tables above (the table of contents, module table and
+ * reference symbol table) are only present if the file is a dynamically linked
+ * shared library. For executable and object modules, which are files
+ * containing only one module, the information that would be in these three
+ * tables is determined as follows:
+ * table of contents - the defined external symbols are sorted by name
+ * module table - the file contains only one module so everything in the
+ * file is part of the module.
+ * reference symbol table - is the defined and undefined external symbols
+ *
+ * For dynamically linked shared library files this load command also contains
+ * offsets and sizes to the pool of relocation entries for all sections
+ * separated into two groups:
+ * external relocation entries
+ * local relocation entries
+ * For executable and object modules the relocation entries continue to hang
+ * off the section structures.
+ */
+class MachODysymtabCommand extends IMachOLoadCommand<MachODysymtabCommand> {
+ /*
+ * The symbols indicated by symoff and nsyms of the LC_SYMTAB load command
+ * are grouped into the following three groups:
+ * local symbols (further grouped by the module they are from)
+ * defined external symbols (further grouped by the module they are from)
+ * undefined symbols
+ *
+ * The local symbols are used only for debugging. The dynamic binding
+ * process may have to use them to indicate to the debugger the local
+ * symbols for a module that is being bound.
+ *
+ * The last two groups are used by the dynamic binding process to do the
+ * binding (indirectly through the module table and the reference symbol
+ * table when this is a dynamically linked shared library file).
+ */
+ final Uint32 ilocalsym; /* index to local symbols (uint32_t) */
+ final Uint32 nlocalsym; /* number of local symbols (uint32_t) */
+
+ final Uint32 iextdefsym; /* index to externally defined symbols (uint32_t) */
+ final Uint32 nextdefsym; /* number of externally defined symbols (uint32_t) */
+
+ final Uint32 iundefsym; /* index to undefined symbols (uint32_t) */
+ final Uint32 nundefsym; /* number of undefined symbols (uint32_t) */
+
+ /*
+ * For the for the dynamic binding process to find which module a symbol
+ * is defined in the table of contents is used (analogous to the ranlib
+ * structure in an archive) which maps defined external symbols to modules
+ * they are defined in. This exists only in a dynamically linked shared
+ * library file. For executable and object modules the defined external
+ * symbols are sorted by name and is use as the table of contents.
+ */
+ final Uint32 tocoff; /* file offset to table of contents (uint32_t) */
+ final Uint32 ntoc; /* number of entries in table of contents (uint32_t) */
+
+ /*
+ * To support dynamic binding of "modules" (whole object files) the symbol
+ * table must reflect the modules that the file was created from. This is
+ * done by having a module table that has indexes and counts into the merged
+ * tables for each module. The module structure that these two entries
+ * refer to is described below. This exists only in a dynamically linked
+ * shared library file. For executable and object modules the file only
+ * contains one module so everything in the file belongs to the module.
+ */
+ final Uint32 modtaboff; /* file offset to module table (uint32_t) */
+ final Uint32 nmodtab; /* number of module table entries (uint32_t) */
+
+ /*
+ * To support dynamic module binding the module structure for each module
+ * indicates the external references (defined and undefined) each module
+ * makes. For each module there is an offset and a count into the
+ * reference symbol table for the symbols that the module references.
+ * This exists only in a dynamically linked shared library file. For
+ * executable and object modules the defined external symbols and the
+ * undefined external symbols indicates the external references.
+ */
+ final Uint32 extrefsymoff; /* offset to referenced symbol table (uint32_t) */
+ final Uint32
+ nextrefsyms; /* number of referenced symbol table entries (uint32_t) */
+
+ /*
+ * The sections that contain "symbol pointers" and "routine stubs" have
+ * indexes and (implied counts based on the size of the section and fixed
+ * size of the entry) into the "indirect symbol" table for each pointer
+ * and stub. For every section of these two types the index into the
+ * indirect symbol table is stored in the section header in the field
+ * reserved1. An indirect symbol table entry is simply a 32bit index into
+ * the symbol table to the symbol that the pointer or stub is referring to.
+ * The indirect symbol table is ordered to match the entries in the section.
+ */
+ final Uint32
+ indirectsymoff; /* file offset to the indirect symbol table (uint32_t) */
+ final Uint32
+ nindirectsyms; /* number of indirect symbol table entries (uint32_t) */
+
+ /*
+ * To support relocating an individual module in a library file quickly the
+ * external relocation entries for each module in the library need to be
+ * accessed efficiently. Since the relocation entries can't be accessed
+ * through the section headers for a library file they are separated into
+ * groups of local and external entries further grouped by module. In this
+ * case the presents of this load command who's extreloff, nextrel,
+ * locreloff and nlocrel fields are non-zero indicates that the relocation
+ * entries of non-merged sections are not referenced through the section
+ * structures (and the reloff and nreloc fields in the section headers are
+ * set to zero).
+ *
+ * Since the relocation entries are not accessed through the section headers
+ * this requires the r_address field to be something other than a section
+ * offset to identify the item to be relocated. In this case r_address is
+ * set to the offset from the vmaddr of the first LC_SEGMENT command.
+ * For MH_SPLIT_SEGS images r_address is set to the the offset from the
+ * vmaddr of the first read-write LC_SEGMENT command.
+ *
+ * The relocation entries are grouped by module and the module table
+ * entries have indexes and counts into them for the group of external
+ * relocation entries for that the module.
+ *
+ * For sections that are merged across modules there must not be any
+ * remaining external relocation entries for them (for merged sections
+ * remaining relocation entries must be local).
+ */
+ final Uint32 extreloff; /* offset to external relocation entries (uint32_t) */
+ final Uint32 nextrel; /* number of external relocation entries (uint32_t) */
+
+ /*
+ * All the local relocation entries are grouped together (they are not
+ * grouped by their module since they are only used if the object is moved
+ * from it staticly link edited address).
+ */
+ final Uint32 locreloff; /* offset to local relocation entries (uint32_t) */
+ final Uint32 nlocrel; /* number of local relocation entries (uint32_t) */
+
+ MachODysymtabCommand(
+ Uint32 cmdsize,
+ this.ilocalsym,
+ this.nlocalsym,
+ this.iextdefsym,
+ this.nextdefsym,
+ this.iundefsym,
+ this.nundefsym,
+ this.tocoff,
+ this.ntoc,
+ this.modtaboff,
+ this.nmodtab,
+ this.extrefsymoff,
+ this.nextrefsyms,
+ this.indirectsymoff,
+ this.nindirectsyms,
+ this.extreloff,
+ this.nextrel,
+ this.locreloff,
+ this.nlocrel,
+ ) : super(
+ MachOConstants.LC_DYSYMTAB,
+ cmdsize,
+ );
+
+ @override
+ MachODysymtabCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(ilocalsym);
+ stream.writeUint32(nlocalsym);
+ stream.writeUint32(iextdefsym);
+ stream.writeUint32(nextdefsym);
+ stream.writeUint32(iundefsym);
+ stream.writeUint32(nundefsym);
+ stream.writeUint32(tocoff);
+ stream.writeUint32(ntoc);
+ stream.writeUint32(modtaboff);
+ stream.writeUint32(nmodtab);
+ stream.writeUint32(extrefsymoff);
+ stream.writeUint32(nextrefsyms);
+ stream.writeUint32(indirectsymoff);
+ stream.writeUint32(nindirectsyms);
+ stream.writeUint32(extreloff);
+ stream.writeUint32(nextrel);
+ stream.writeUint32(locreloff);
+ stream.writeUint32(nlocrel);
+ }
+}
+
+/* a table of contents entry */
+class MachODylibTableOfContents {
+ final Uint32
+ symbol_index; /* the defined external symb(uint32_t) ol
+ (index into the symbol table) */
+ final Uint32
+ module_index; /* index into the module table this symb(uint32_t) ol
+ is defined in */
+
+ MachODylibTableOfContents(
+ this.symbol_index,
+ this.module_index,
+ );
+}
+
+/* a module table entry */
+class MachODylibModule {
+ final Uint32
+ module_name; /* the module name (index into string table) (uint32_t) */
+
+ final Uint32
+ iextdefsym; /* index into externally defined symbols (uint32_t) */
+ final Uint32 nextdefsym; /* number of externally defined symbols (uint32_t) */
+ final Uint32 irefsym; /* index into reference symbol table (uint32_t) */
+ final Uint32
+ nrefsym; /* number of reference symbol table entries (uint32_t) */
+ final Uint32 ilocalsym; /* index into symbols for local symbols (uint32_t) */
+ final Uint32 nlocalsym; /* number of local symbols (uint32_t) */
+
+ final Uint32 iextrel; /* index into external relocation entries (uint32_t) */
+ final Uint32 nextrel; /* number of external relocation entries (uint32_t) */
+
+ final Uint32
+ iinit_iterm; /* low 16 bits are the index into the in(uint32_t) it
+ section, high 16 bits are the index into
+ the term section */
+ final Uint32
+ ninit_nterm; /* low 16 bits are the number of init secti(uint32_t) on
+ entries, high 16 bits are the number of
+ term section entries */
+
+ final Uint32 /* for this module address of the start of (uint32_t) */
+ objc_module_info_addr; /* the (__OBJC,__module_info) section */
+ final Uint32 /* for this module size of (uint32_t) */
+ objc_module_info_size; /* the (__OBJC,__module_info) section */
+
+ MachODylibModule(
+ this.module_name,
+ this.iextdefsym,
+ this.nextdefsym,
+ this.irefsym,
+ this.nrefsym,
+ this.ilocalsym,
+ this.nlocalsym,
+ this.iextrel,
+ this.nextrel,
+ this.iinit_iterm,
+ this.ninit_nterm,
+ this.objc_module_info_addr,
+ this.objc_module_info_size,
+ );
+}
+
+/* a 64-bit module table entry */
+class MachODylibModule64 {
+ final Uint32
+ module_name; /* the module name (index into string table) (uint32_t) */
+
+ final Uint32
+ iextdefsym; /* index into externally defined symbols (uint32_t) */
+ final Uint32 nextdefsym; /* number of externally defined symbols (uint32_t) */
+ final Uint32 irefsym; /* index into reference symbol table (uint32_t) */
+ final Uint32
+ nrefsym; /* number of reference symbol table entries (uint32_t) */
+ final Uint32 ilocalsym; /* index into symbols for local symbols (uint32_t) */
+ final Uint32 nlocalsym; /* number of local symbols (uint32_t) */
+
+ final Uint32 iextrel; /* index into external relocation entries (uint32_t) */
+ final Uint32 nextrel; /* number of external relocation entries (uint32_t) */
+
+ final Uint32
+ iinit_iterm; /* low 16 bits are the index into the in(uint32_t) it
+ section, high 16 bits are the index into
+ the term section */
+ final Uint32
+ ninit_nterm; /* low 16 bits are the number of init secti(uint32_t) on
+ entries, high 16 bits are the number of
+ term section entries */
+
+ final Uint32 /* for this module size of (uint32_t) */
+ objc_module_info_size; /* the (__OBJC,__module_info) section */
+ final Uint64 /* for this module address of the start of (uint64_t) */
+ objc_module_info_addr; /* the (__OBJC,__module_info) section */
+
+ MachODylibModule64(
+ this.module_name,
+ this.iextdefsym,
+ this.nextdefsym,
+ this.irefsym,
+ this.nrefsym,
+ this.ilocalsym,
+ this.nlocalsym,
+ this.iextrel,
+ this.nextrel,
+ this.iinit_iterm,
+ this.ninit_nterm,
+ this.objc_module_info_size,
+ this.objc_module_info_addr,
+ );
+}
+
+/*
+ * The entries in the reference symbol table are used when loading the module
+ * (both by the static and dynamic link editors) and if the module is unloaded
+ * or replaced. Therefore all external symbols (defined and undefined) are
+ * listed in the module's reference table. The flags describe the type of
+ * reference that is being made. The constants for the flags are defined in
+ * <mach-o/nlist.h> as they are also used for symbol table entries.
+ */
+class MachODylibReference {
+ final Uint32 isym; //:24, /* index into the symbol table (uint32_t) */
+ final Uint32 flags; //:8; /* flags to indicate the type of reference */
+
+ MachODylibReference(Uint32 value)
+ : isym = value & Uint32(0xffffff),
+ flags = value >> Uint32(24);
+}
+
+/*
+ * The twolevel_hints_command contains the offset and number of hints in the
+ * two-level namespace lookup hints table.
+ */
+class MachOTwolevelHintsCommand
+ extends IMachOLoadCommand<MachOTwolevelHintsCommand> {
+ final Uint32 offset; /* offset to the hint table (uint32_t) */
+ final Uint32 nhints; /* number of hints in the hint table (uint32_t) */
+
+ MachOTwolevelHintsCommand(
+ Uint32 cmdsize,
+ this.offset,
+ this.nhints,
+ ) : super(
+ MachOConstants.LC_TWOLEVEL_HINTS,
+ cmdsize,
+ );
+
+ @override
+ MachOTwolevelHintsCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(offset);
+ stream.writeUint32(nhints);
+ }
+}
+
+/*
+ * The entries in the two-level namespace lookup hints table are twolevel_hint
+ * structs. These provide hints to the dynamic link editor where to start
+ * looking for an undefined symbol in a two-level namespace image. The
+ * isub_image field is an index into the sub-images (sub-frameworks and
+ * sub-umbrellas list) that made up the two-level image that the undefined
+ * symbol was found in when it was built by the static link editor. If
+ * isub-image is 0 the the symbol is expected to be defined in library and not
+ * in the sub-images. If isub-image is non-zero it is an index into the array
+ * of sub-images for the umbrella with the first index in the sub-images being
+ * 1. The array of sub-images is the ordered list of sub-images of the umbrella
+ * that would be searched for a symbol that has the umbrella recorded as its
+ * primary library. The table of contents index is an index into the
+ * library's table of contents. This is used as the starting point of the
+ * binary search or a directed linear search.
+ */
+class MachOTwolevelHint {
+ final int isub_image; //:8, /* index into the sub images */
+ final int itoc; //:24; /* index into the table of contents */
+
+ MachOTwolevelHint(int value)
+ : isub_image = value & 0xff,
+ itoc = value >> 8;
+}
+
+/*
+ * The prebind_cksum_command contains the value of the original check sum for
+ * prebound files or zero. When a prebound file is first created or modified
+ * for other than updating its prebinding information the value of the check sum
+ * is set to zero. When the file has it prebinding re-done and if the value of
+ * the check sum is zero the original check sum is calculated and stored in
+ * cksum field of this load command in the output file. If when the prebinding
+ * is re-done and the cksum field is non-zero it is left unchanged from the
+ * input file.
+ */
+class MachOPrebindCksumCommand
+ extends IMachOLoadCommand<MachOPrebindCksumCommand> {
+ final Uint32 cksum; /* the check sum or zero (uint32_t) */
+
+ MachOPrebindCksumCommand(
+ Uint32 cmdsize,
+ this.cksum,
+ ) : super(
+ MachOConstants.LC_PREBIND_CKSUM,
+ cmdsize,
+ );
+
+ @override
+ MachOPrebindCksumCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(cksum);
+ }
+}
+
+/*
+ * The uuid load command contains a single 128-bit unique random number that
+ * identifies an object produced by the static link editor.
+ */
+class MachOUuidCommand extends IMachOLoadCommand<MachOUuidCommand> {
+ final Uint8List uuid; //[16]; /* the 128-bit uuid */
+
+ MachOUuidCommand(
+ Uint32 cmdsize,
+ this.uuid,
+ ) : super(
+ MachOConstants.LC_UUID,
+ cmdsize,
+ );
+
+ @override
+ MachOUuidCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeFromSync(uuid);
+ }
+}
+
+/*
+ * The rpath_command contains a path which at runtime should be added to
+ * the current run path used to find @rpath prefixed dylibs.
+ */
+class MachORpathCommand extends IMachOLoadCommand<MachORpathCommand> {
+ final MachOStr path; /* path to add to run path */
+
+ MachORpathCommand(
+ Uint32 cmdsize,
+ this.path,
+ ) : super(
+ MachOConstants.LC_RPATH,
+ cmdsize,
+ );
+
+ @override
+ MachORpathCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ path.writeContentsSync(stream);
+ }
+}
+
+/*
+ * The linkedit_data_command contains the offsets and sizes of a blob
+ * of data in the __LINKEDIT segment.
+ */
+class MachOLinkeditDataCommand
+ extends IMachOLoadCommand<MachOLinkeditDataCommand> {
+ final Uint32
+ dataoff; /* file offset of data in __LINKEDIT segment (uint32_t) */
+ final Uint32
+ datasize; /* file size of data in __LINKEDIT segment (uint32_t) */
+
+ MachOLinkeditDataCommand(
+ Uint32 cmd,
+ Uint32 cmdsize,
+ this.dataoff,
+ this.datasize,
+ ) : super(cmd, cmdsize) {
+ if (this.cmd != MachOConstants.LC_CODE_SIGNATURE &&
+ this.cmd != MachOConstants.LC_SEGMENT_SPLIT_INFO &&
+ this.cmd != MachOConstants.LC_FUNCTION_STARTS &&
+ this.cmd != MachOConstants.LC_DATA_IN_CODE &&
+ this.cmd != MachOConstants.LC_DYLIB_CODE_SIGN_DRS) {
+ throw ArgumentError("cmd was not one of LC_CODE_SIGNATURE "
+ "(${MachOConstants.LC_CODE_SIGNATURE}), LC_SEGMENT_SPLIT_INFO "
+ "(${MachOConstants.LC_SEGMENT_SPLIT_INFO}), LC_FUNCTION_STARTS "
+ "(${MachOConstants.LC_FUNCTION_STARTS}), LC_DATA_IN_CODE "
+ "(${MachOConstants.LC_DATA_IN_CODE}), LC_DYLIB_CODE_SIGN_DRS "
+ "(${MachOConstants.LC_DYLIB_CODE_SIGN_DRS}): $cmd");
+ }
+ }
+
+ @override
+ MachOLinkeditDataCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(dataoff);
+ stream.writeUint32(datasize);
+ }
+}
+
+/*
+ * The encryption_info_command contains the file offset and size of an
+ * of an encrypted segment.
+ */
+class MachOEncryptionInfoCommand
+ extends IMachOLoadCommand<MachOEncryptionInfoCommand> {
+ final Uint32 cryptoff; /* file offset of encrypted range (uint32_t) */
+ final Uint32 cryptsize; /* file size of encrypted range (uint32_t) */
+ final Uint32
+ cryptid; /* which enryption syste(uint32_t) m,
+ 0 means not-encrypted yet */
+
+ MachOEncryptionInfoCommand(
+ Uint32 cmdsize,
+ this.cryptoff,
+ this.cryptsize,
+ this.cryptid,
+ ) : super(
+ MachOConstants.LC_ENCRYPTION_INFO,
+ cmdsize,
+ );
+
+ @override
+ MachOEncryptionInfoCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(cryptoff);
+ stream.writeUint32(cryptsize);
+ stream.writeUint32(cryptid);
+ }
+}
+
+/*
+ * The version_min_command contains the min OS version on which this
+ * binary was built to run.
+ */
+class MachOVersionMinCommand extends IMachOLoadCommand<MachOVersionMinCommand> {
+ final Uint32 version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz (uint32_t) */
+ final Uint32 sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz (uint32_t) */
+
+ MachOVersionMinCommand(
+ Uint32 cmd,
+ Uint32 cmdsize,
+ this.version,
+ this.sdk,
+ ) : super(cmd, cmdsize) {
+ if (this.cmd != MachOConstants.LC_VERSION_MIN_MACOSX &&
+ this.cmd != MachOConstants.LC_VERSION_MIN_IPHONEOS) {
+ throw ArgumentError("cmd was not one of: LC_VERSION_MIN_MACOSX "
+ "(${MachOConstants.LC_VERSION_MIN_MACOSX}), LC_VERSION_MIN_IPHONEOS "
+ "(${MachOConstants.LC_VERSION_MIN_IPHONEOS}): $cmd");
+ }
+ }
+
+ @override
+ MachOVersionMinCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(version);
+ stream.writeUint32(sdk);
+ }
+}
+
+/*
+ * The dyld_info_command contains the file offsets and sizes of
+ * the new compressed form of the information dyld needs to
+ * load the image. This information is used by dyld on Mac OS X
+ * 10.6 and later. All information pointed to by this command
+ * is encoded using byte streams, so no endian swapping is needed
+ * to interpret it.
+ */
+class MachODyldInfoCommand extends IMachOLoadCommand<MachODyldInfoCommand> {
+ /*
+ * Dyld rebases an image whenever dyld loads it at an address different
+ * from its preferred address. The rebase information is a stream
+ * of byte sized opcodes whose symbolic names start with REBASE_OPCODE_.
+ * Conceptually the rebase information is a table of tuples:
+ * <seg-index, seg-offset, type>
+ * The opcodes are a compressed way to encode the table by only
+ * encoding when a column changes. In addition simple patterns
+ * like "every n'th offset for m times" can be encoded in a few
+ * bytes.
+ */
+ final Uint32 rebase_off; /* file offset to rebase info (uint32_t) */
+ final Uint32 rebase_size; /* size of rebase info (uint32_t) */
+
+ /*
+ * Dyld binds an image during the loading process, if the image
+ * requires any pointers to be initialized to symbols in other images.
+ * The bind information is a stream of byte sized
+ * opcodes whose symbolic names start with BIND_OPCODE_.
+ * Conceptually the bind information is a table of tuples:
+ * <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>
+ * The opcodes are a compressed way to encode the table by only
+ * encoding when a column changes. In addition simple patterns
+ * like for runs of pointers initialzed to the same value can be
+ * encoded in a few bytes.
+ */
+ final Uint32 bind_off; /* file offset to binding info (uint32_t) */
+ final Uint32 bind_size; /* size of binding info (uint32_t) */
+
+ /*
+ * Some C++ programs require dyld to unique symbols so that all
+ * images in the process use the same copy of some code/data.
+ * This step is done after binding. The content of the weak_bind
+ * info is an opcode stream like the bind_info. But it is sorted
+ * alphabetically by symbol name. This enable dyld to walk
+ * all images with weak binding information in order and look
+ * for collisions. If there are no collisions, dyld does
+ * no updating. That means that some fixups are also encoded
+ * in the bind_info. For instance, all calls to "operator new"
+ * are first bound to libstdc++.dylib using the information
+ * in bind_info. Then if some image overrides operator new
+ * that is detected when the weak_bind information is processed
+ * and the call to operator new is then rebound.
+ */
+ final Uint32
+ weak_bind_off; /* file offset to weak binding info (uint32_t) */
+ final Uint32 weak_bind_size; /* size of weak binding info (uint32_t) */
+
+ /*
+ * Some uses of external symbols do not need to be bound immediately.
+ * Instead they can be lazily bound on first use. The lazy_bind
+ * are contains a stream of BIND opcodes to bind all lazy symbols.
+ * Normal use is that dyld ignores the lazy_bind section when
+ * loading an image. Instead the static linker arranged for the
+ * lazy pointer to initially point to a helper function which
+ * pushes the offset into the lazy_bind area for the symbol
+ * needing to be bound, then jumps to dyld which simply adds
+ * the offset to lazy_bind_off to get the information on what
+ * to bind.
+ */
+ final Uint32 lazy_bind_off; /* file offset to lazy binding info (uint32_t) */
+ final Uint32 lazy_bind_size; /* size of lazy binding infs (uint32_t) */
+
+ /*
+ * The symbols exported by a dylib are encoded in a trie. This
+ * is a compact representation that factors out common prefixes.
+ * It also reduces LINKEDIT pages in RAM because it encodes all
+ * information (name, address, flags) in one small, contiguous range.
+ * The export area is a stream of nodes. The first node sequentially
+ * is the start node for the trie.
+ *
+ * Nodes for a symbol start with a uleb128 that is the length of
+ * the exported symbol information for the string so far.
+ * If there is no exported symbol, the node starts with a zero byte.
+ * If there is exported info, it follows the length.
+ *
+ * First is a uleb128 containing flags. Normally, it is followed by
+ * a uleb128 encoded offset which is location of the content named
+ * by the symbol from the mach_header for the image. If the flags
+ * is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is
+ * a uleb128 encoded library ordinal, then a zero terminated
+ * UTF8 string. If the string is zero length, then the symbol
+ * is re-export from the specified dylib with the same name.
+ * If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following
+ * the flags is two uleb128s: the stub offset and the resolver offset.
+ * The stub is used by non-lazy pointers. The resolver is used
+ * by lazy pointers and must be called to get the actual address to use.
+ *
+ * After the optional exported symbol information is a byte of
+ * how many edges (0-255) that this node has leaving it,
+ * followed by each edge.
+ * Each edge is a zero terminated UTF8 of the addition chars
+ * in the symbol, followed by a uleb128 offset for the node that
+ * edge points to.
+ *
+ */
+ final Uint32 export_off; /* file offset to lazy binding info (uint32_t) */
+ final Uint32 export_size; /* size of lazy binding infs (uint32_t) */
+
+ MachODyldInfoCommand(
+ Uint32 cmd,
+ Uint32 cmdsize,
+ this.rebase_off,
+ this.rebase_size,
+ this.bind_off,
+ this.bind_size,
+ this.weak_bind_off,
+ this.weak_bind_size,
+ this.lazy_bind_off,
+ this.lazy_bind_size,
+ this.export_off,
+ this.export_size,
+ ) : super(
+ cmd,
+ cmdsize,
+ ) {
+ if (this.cmd != MachOConstants.LC_DYLD_INFO &&
+ this.cmd != MachOConstants.LC_DYLD_INFO_ONLY) {
+ throw ArgumentError(
+ "cmd was not one of LC_DYLD_INFO (${MachOConstants.LC_DYLD_INFO}), "
+ "LC_DYLD_INFO_ONLY (${MachOConstants.LC_DYLD_INFO_ONLY}): $cmd");
+ }
+ }
+
+ @override
+ MachODyldInfoCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(rebase_off);
+ stream.writeUint32(rebase_size);
+ stream.writeUint32(bind_off);
+ stream.writeUint32(bind_size);
+ stream.writeUint32(weak_bind_off);
+ stream.writeUint32(weak_bind_size);
+ stream.writeUint32(lazy_bind_off);
+ stream.writeUint32(lazy_bind_size);
+ stream.writeUint32(export_off);
+ stream.writeUint32(export_size);
+ }
+}
+
+/*
+ * The symseg_command contains the offset and size of the GNU style
+ * symbol table information as described in the header file <symseg.h>.
+ * The symbol roots of the symbol segments must also be aligned properly
+ * in the file. So the requirement of keeping the offsets aligned to a
+ * multiple of a 4 bytes translates to the length field of the symbol
+ * roots also being a multiple of a long. Also the padding must again be
+ * zeroed. (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOSymsegCommand extends IMachOLoadCommand<MachOSymsegCommand> {
+ final Uint32 offset; /* symbol segment offset (uint32_t) */
+ final Uint32 size; /* symbol segment size in bytes (uint32_t) */
+
+ MachOSymsegCommand(
+ Uint32 cmdsize,
+ this.offset,
+ this.size,
+ ) : super(
+ MachOConstants.LC_SYMSEG,
+ cmdsize,
+ );
+
+ @override
+ MachOSymsegCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint32(offset);
+ stream.writeUint32(size);
+ }
+}
+
+/*
+ * The ident_command contains a free format string table following the
+ * ident_command structure. The strings are null terminated and the size of
+ * the command is padded out with zero bytes to a multiple of 4 bytes/
+ * (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOIdentCommand extends IMachOLoadCommand<MachOIdentCommand> {
+ MachOIdentCommand(
+ Uint32 cmdsize,
+ ) : super(
+ MachOConstants.LC_IDENT,
+ cmdsize,
+ );
+
+ @override
+ MachOIdentCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {}
+}
+
+/*
+ * The fvmfile_command contains a reference to a file to be loaded at the
+ * specified virtual address. (Presently, this command is reserved for
+ * internal use. The kernel ignores this command when loading a program into
+ * memory).
+ */
+class MachOFvmfileCommand extends IMachOLoadCommand<MachOFvmfileCommand> {
+ final MachOStr name; /* files pathname */
+ final Uint32 header_addr; /* files virtual address (uint32_t) */
+
+ MachOFvmfileCommand(
+ Uint32 cmdsize,
+ this.name,
+ this.header_addr,
+ ) : super(
+ MachOConstants.LC_FVMFILE,
+ cmdsize,
+ );
+
+ @override
+ MachOFvmfileCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ name.writeContentsSync(stream);
+ stream.writeUint32(header_addr);
+ }
+}
+
+/*
+ * The entry_point_command is a replacement for thread_command.
+ * It is used for main executables to specify the location (file offset)
+ * of main(). If -stack_size was used at link time, the stacksize
+ * field will contain the stack size need for the main thread.
+ */
+class MachOEntryPointCommand extends IMachOLoadCommand<MachOEntryPointCommand> {
+ final Uint64 entryoff; /* file (__TEXT) offset of main (uint64_t)() */
+ final Uint64 stacksize; /* if not zero, initial stack size (uint64_t) */
+
+ MachOEntryPointCommand(
+ Uint32 cmdsize,
+ this.entryoff,
+ this.stacksize,
+ ) : super(
+ MachOConstants.LC_MAIN,
+ cmdsize,
+ );
+
+ @override
+ MachOEntryPointCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint64(entryoff);
+ stream.writeUint64(stacksize);
+ }
+}
+
+/*
+ * The source_version_command is an optional load command containing
+ * the version of the sources used to build the binary.
+ */
+class MachOSourceVersionCommand
+ extends IMachOLoadCommand<MachOSourceVersionCommand> {
+ final Uint64 version; /* A.B.C.D.E packed as a24.b10.c10.d10.e10 (uint64_t) */
+
+ MachOSourceVersionCommand(
+ Uint32 cmdsize,
+ this.version,
+ ) : super(
+ MachOConstants.LC_SOURCE_VERSION,
+ cmdsize,
+ );
+
+ @override
+ MachOSourceVersionCommand asType() => this;
+
+ @override
+ void writeContentsSync(RandomAccessFile stream) {
+ stream.writeUint64(version);
+ }
+}
+
+/*
+ * The LC_DATA_IN_CODE load commands uses a linkedit_data_command
+ * to point to an array of data_in_code_entry entries. Each entry
+ * describes a range of data in a code section. This load command
+ * is only used in final linked images.
+ */
+class MachODataInCodeEntry {
+ final Uint32 offset; /* from mach_header to start of data range(uint32_t) */
+ final Uint16 length; /* number of bytes in data range (uint16_t) */
+ final Uint16 kind; /* a DICE_KIND_* value (uint16_t) */
+
+ MachODataInCodeEntry(
+ this.offset,
+ this.length,
+ this.kind,
+ );
+}
+
+/*
+ * Sections of type S_THREAD_LOCAL_VARIABLES contain an array
+ * of tlv_descriptor structures.
+ */
+// class MachOTlvDescriptor {
+// void* (*thunk)(struct tlv_descriptor*);
+// unsigned long key;
+// unsigned long offset;
+
+// MachOTlvDescriptor(
+// void* (*thunk)(struct tlv_descriptor*);
+// unsigned long key;
+// unsigned long offset;
+// );
+
+// }
+
+class MachOConstants {
+ /* Constant for the magic field of the mach_header (32-bit architectures) */
+ static const Uint32 MH_MAGIC = Uint32(0xfeedface); /* the mach magic number */
+ static const Uint32 MH_CIGAM = Uint32(0xcefaedfe); /* NXSwapInt(MH_MAGIC) */
+
+ /* Constant for the magic field of the mach_header_64 (64-bit architectures) */
+ static const Uint32 MH_MAGIC_64 =
+ Uint32(0xfeedfacf); /* the 64-bit mach magic number */
+ static const Uint32 MH_CIGAM_64 =
+ Uint32(0xcffaedfe); /* NXSwapInt(MH_MAGIC_64) */
+
+ /*
+ * After MacOS X 10.1 when a new load command is added that is required to be
+ * understood by the dynamic linker for the image to execute properly the
+ * LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic
+ * linker sees such a load command it it does not understand will issue a
+ * "unknown load command required for execution" error and refuse to use the
+ * image. Other load commands without this bit that are not understood will
+ * simply be ignored.
+ */
+ static const Uint32 LC_REQ_DYLD = Uint32(0x80000000);
+ // This one is a convenience so we can define other constants in this class as
+ // actual const.
+ static const int _LC_REQ_DYLD = 0x80000000;
+
+ /*; Constants for the cmd field of all load commands, the type */
+ static const Uint32 LC_SEGMENT =
+ Uint32(0x1); /* segment of this file to be mapped */
+ static const Uint32 LC_SYMTAB =
+ Uint32(0x2); /* link-edit stab symbol table info */
+ static const Uint32 LC_SYMSEG =
+ Uint32(0x3); /* link-edit gdb symbol table info (obsolete) */
+ static const Uint32 LC_THREAD = Uint32(0x4); /* thread */
+ static const Uint32 LC_UNIXTHREAD =
+ Uint32(0x5); /* unix thread (includes a stack) */
+ static const Uint32 LC_LOADFVMLIB =
+ Uint32(0x6); /* load a specified fixed VM shared library */
+ static const Uint32 LC_IDFVMLIB =
+ Uint32(0x7); /* fixed VM shared library identification */
+ static const Uint32 LC_IDENT =
+ Uint32(0x8); /* object identification info (obsolete) */
+ static const Uint32 LC_FVMFILE =
+ Uint32(0x9); /* fixed VM file inclusion (internal use) */
+ static const Uint32 LC_PREPAGE =
+ Uint32(0xa); /* prepage command (internal use) */
+ static const Uint32 LC_DYSYMTAB =
+ Uint32(0xb); /* dynamic link-edit symbol table info */
+ static const Uint32 LC_LOAD_DYLIB =
+ Uint32(0xc); /* load a dynamically linked shared library */
+ static const Uint32 LC_ID_DYLIB =
+ Uint32(0xd); /* dynamically linked shared lib ident */
+ static const Uint32 LC_LOAD_DYLINKER =
+ Uint32(0xe); /* load a dynamic linker */
+ static const Uint32 LC_ID_DYLINKER =
+ Uint32(0xf); /* dynamic linker identification */
+ static const Uint32 LC_PREBOUND_DYLIB =
+ Uint32(0x10); /* modules prebound for a dynamically */
+ /* linked shared library */
+ static const Uint32 LC_ROUTINES = Uint32(0x11); /* image routines */
+ static const Uint32 LC_SUB_FRAMEWORK = Uint32(0x12); /* sub framework */
+ static const Uint32 LC_SUB_UMBRELLA = Uint32(0x13); /* sub umbrella */
+ static const Uint32 LC_SUB_CLIENT = Uint32(0x14); /* sub client */
+ static const Uint32 LC_SUB_LIBRARY = Uint32(0x15); /* sub library */
+ static const Uint32 LC_TWOLEVEL_HINTS =
+ Uint32(0x16); /* two-level namespace lookup hints */
+ static const Uint32 LC_PREBIND_CKSUM = Uint32(0x17); /* prebind checksum */
+
+ /*
+ * load a dynamically linked shared library that is allowed to be missing
+ * (all symbols are weak imported).
+ */
+ static const Uint32 LC_LOAD_WEAK_DYLIB = Uint32(0x18 | _LC_REQ_DYLD);
+
+ static const Uint32 LC_SEGMENT_64 = Uint32(0x19);
+ /* 64-bit segment of this file to be
+ mapped */
+ static const Uint32 LC_ROUTINES_64 = Uint32(0x1a); /* 64-bit image routines */
+ static const Uint32 LC_UUID = Uint32(0x1b); /* the uuid */
+ static const Uint32 LC_RPATH =
+ Uint32(0x1c | _LC_REQ_DYLD); /* runpath additions */
+ static const Uint32 LC_CODE_SIGNATURE =
+ Uint32(0x1d); /* local of code signature */
+ static const Uint32 LC_SEGMENT_SPLIT_INFO =
+ Uint32(0x1e); /* local of info to split segments */
+ static const Uint32 LC_REEXPORT_DYLIB =
+ Uint32(0x1f | _LC_REQ_DYLD); /* load and re-export dylib */
+ static const Uint32 LC_LAZY_LOAD_DYLIB =
+ Uint32(0x20); /* delay load of dylib until first use */
+ static const Uint32 LC_ENCRYPTION_INFO =
+ Uint32(0x21); /* encrypted segment information */
+ static const Uint32 LC_DYLD_INFO =
+ Uint32(0x22); /* compressed dyld information */
+ static const Uint32 LC_DYLD_INFO_ONLY =
+ Uint32(0x22 | _LC_REQ_DYLD); /* compressed dyld information only */
+ static const Uint32 LC_LOAD_UPWARD_DYLIB =
+ Uint32(0x23 | _LC_REQ_DYLD); /* load upward dylib */
+ static const Uint32 LC_VERSION_MIN_MACOSX =
+ Uint32(0x24); /* build for MacOSX min OS version */
+ static const Uint32 LC_VERSION_MIN_IPHONEOS =
+ Uint32(0x25); /* build for iPhoneOS min OS version */
+ static const Uint32 LC_FUNCTION_STARTS =
+ Uint32(0x26); /* compressed table of function start addresses */
+ static const Uint32 LC_DYLD_ENVIRONMENT = Uint32(0x27);
+ /* string for dyld to treat
+ like environment variable */
+ static const Uint32 LC_MAIN =
+ Uint32(0x28 | _LC_REQ_DYLD); /* replacement for LC_UNIXTHREAD */
+ static const Uint32 LC_DATA_IN_CODE =
+ Uint32(0x29); /* table of non-instructions in __text */
+ static const Uint32 LC_SOURCE_VERSION =
+ Uint32(0x2A); /* source version used to build binary */
+ static const Uint32 LC_DYLIB_CODE_SIGN_DRS =
+ Uint32(0x2B); /* Code signing DRs copied from linked dylibs */
+ static const Uint32 LC_BUILD_VERSION =
+ Uint32(0x32); /* Platform min OS version */
+
+ /* Constants for the flags field of the segment_command */
+
+ /* the file contents for this segment is for the high part of the VM space,
+ the low part is zero filled (for stacks in core files) */
+ static const Uint32 SG_HIGHVM = Uint32(0x1);
+ /* this segment is the VM that is allocated by a fixed VM library, for overlap
+ checking in the link editor */
+ static const Uint32 SG_FVMLIB = Uint32(0x2);
+ /* this segment has nothing that was relocated in it and nothing relocated to
+ it, that is it maybe safely replaced without relocation */
+ static const Uint32 SG_NORELOC = Uint32(0x4);
+ /* This segment is protected. If the segment starts at file offset 0, the
+ first page of the segment is not protected. All other pages of the segment
+ are protected. */
+ static const Uint32 SG_PROTECTED_VERSION_1 = Uint32(0x8);
+
+/*
+ * The flags field of a section structure is separated into two parts a section
+ * type and section attributes. The section types are mutually exclusive (it
+ * can only have one type) but the section attributes are not (it may have more
+ * than one attribute).
+ */
+ static const Uint32 SECTION_TYPE = Uint32(0x000000ff); /* 256 section types */
+ static const Uint32 SECTION_ATTRIBUTES =
+ Uint32(0xffffff00); /* 24 section attributes */
+
+/* Constants for the type of a section */
+ static const Uint32 S_REGULAR = Uint32(0x0); /* regular section */
+ static const Uint32 S_ZEROFILL =
+ Uint32(0x1); /* zero fill on demand section */
+ static const Uint32 S_CSTRING_LITERALS =
+ Uint32(0x2); /* section with only literal C strings*/
+ static const Uint32 S_4BYTE_LITERALS =
+ Uint32(0x3); /* section with only 4 byte literals */
+ static const Uint32 S_8BYTE_LITERALS =
+ Uint32(0x4); /* section with only 8 byte literals */
+ static const Uint32 S_LITERAL_POINTERS =
+ Uint32(0x5); /* section with only pointers to */
+ /* literals */
+/*
+ * For the two types of symbol pointers sections and the symbol stubs section
+ * they have indirect symbol table entries. For each of the entries in the
+ * section the indirect symbol table entries, in corresponding order in the
+ * indirect symbol table, start at the index stored in the reserved1 field
+ * of the section structure. Since the indirect symbol table entries
+ * correspond to the entries in the section the number of indirect symbol table
+ * entries is inferred from the size of the section divided by the size of the
+ * entries in the section. For symbol pointers sections the size of the entries
+ * in the section is 4 bytes and for symbol stubs sections the byte size of the
+ * stubs is stored in the reserved2 field of the section structure.
+ */
+ static const Uint32 S_NON_LAZY_SYMBOL_POINTERS =
+ Uint32(0x6); /* section with only non-lazy
+ symbol pointers */
+ static const Uint32 S_LAZY_SYMBOL_POINTERS =
+ Uint32(0x7); /* section with only lazy symbol
+ pointers */
+ static const Uint32 S_SYMBOL_STUBS = Uint32(
+ 0x8); /* section with only symbol
+ stubs, byte size of stub in
+ the reserved2 field */
+ static const Uint32 S_MOD_INIT_FUNC_POINTERS =
+ Uint32(0x9); /* section with only function
+ pointers for initialization*/
+ static const Uint32 S_MOD_TERM_FUNC_POINTERS =
+ Uint32(0xa); /* section with only function
+ pointers for termination */
+ static const Uint32 S_COALESCED =
+ Uint32(0xb); /* section contains symbols that
+ are to be coalesced */
+ static const Uint32 S_GB_ZEROFILL = Uint32(
+ 0xc); /* zero fill on demand section
+ (that can be larger than 4
+ gigabytes) */
+ static const Uint32 S_INTERPOSING = Uint32(
+ 0xd); /* section with only pairs of
+ function pointers for
+ interposing */
+ static const Uint32 S_16BYTE_LITERALS =
+ Uint32(0xe); /* section with only 16 byte
+ literals */
+ static const Uint32 S_DTRACE_DOF =
+ Uint32(0xf); /* section contains
+ DTrace Object Format */
+ static const Uint32 S_LAZY_DYLIB_SYMBOL_POINTERS = Uint32(
+ 0x10); /* section with only lazy
+ symbol pointers to lazy
+ loaded dylibs */
+ /*
+ * Section types to support thread local variables
+ */
+ static const Uint32 S_THREAD_LOCAL_REGULAR =
+ Uint32(0x11); /* template of initial
+ values for TLVs */
+ static const Uint32 S_THREAD_LOCAL_ZEROFILL =
+ Uint32(0x12); /* template of initial
+ values for TLVs */
+ static const Uint32 S_THREAD_LOCAL_VARIABLES =
+ Uint32(0x13); /* TLV descriptors */
+ static const Uint32 S_THREAD_LOCAL_VARIABLE_POINTERS =
+ Uint32(0x14); /* pointers to TLV
+ descriptors */
+ static const Uint32 S_THREAD_LOCAL_INIT_FUNCTION_POINTERS =
+ Uint32(0x15); /* functions to call
+ to initialize TLV
+ values */
+
+ /*
+ * Constants for the section attributes part of the flags field of a section
+ * structure.
+ */
+ static const Uint32 SECTION_ATTRIBUTES_USR =
+ Uint32(0xff000000); /* User setable attributes */
+ static const Uint32 S_ATTR_PURE_INSTRUCTIONS =
+ Uint32(0x80000000); /* section contains only true
+ machine instructions */
+ static const Uint32 S_ATTR_NO_TOC = Uint32(
+ 0x40000000); /* section contains coalesced
+ symbols that are not to be
+ in a ranlib table of
+ contents */
+ static const Uint32 S_ATTR_STRIP_STATIC_SYMS = Uint32(
+ 0x20000000); /* ok to strip static symbols
+ in this section in files
+ with the MH_DYLDLINK flag */
+ static const Uint32 S_ATTR_NO_DEAD_STRIP =
+ Uint32(0x10000000); /* no dead stripping */
+ static const Uint32 S_ATTR_LIVE_SUPPORT =
+ Uint32(0x08000000); /* blocks are live if they
+ reference live blocks */
+ static const Uint32 S_ATTR_SELF_MODIFYING_CODE =
+ Uint32(0x04000000); /* Used with i386 code stubs
+ written on by dyld */
+ /*
+ * If a segment contains any sections marked with S_ATTR_DEBUG then all
+ * sections in that segment must have this attribute. No section other than
+ * a section marked with this attribute may reference the contents of this
+ * section. A section with this attribute may contain no symbols and must have
+ * a section type S_REGULAR. The static linker will not copy section contents
+ * from sections with this attribute into its output file. These sections
+ * generally contain DWARF debugging info.
+ */
+ static const Uint32 S_ATTR_DEBUG = Uint32(0x02000000); /* a debug section */
+ static const Uint32 SECTION_ATTRIBUTES_SYS =
+ Uint32(0x00ffff00); /* system setable attributes */
+ static const Uint32 S_ATTR_SOME_INSTRUCTIONS =
+ Uint32(0x00000400); /* section contains some
+ machine instructions */
+ static const Uint32 S_ATTR_EXT_RELOC =
+ Uint32(0x00000200); /* section has external
+ relocation entries */
+ static const Uint32 S_ATTR_LOC_RELOC =
+ Uint32(0x00000100); /* section has local
+ relocation entries */
+
+ /*
+ * The names of segments and sections in them are mostly meaningless to the
+ * link-editor. But there are few things to support traditional UNIX
+ * executables that require the link-editor and assembler to use some names
+ * agreed upon by convention.
+ *
+ * The initial protection of the "__TEXT" segment has write protection turned
+ * off (not writeable).
+ *
+ * The link-editor will allocate common symbols at the end of the "__common"
+ * section in the "__DATA" segment. It will create the section and segment
+ * if needed.
+ */
+
+/* The currently known segment names and the section names in those segments */
+
+ static final String SEG_PAGEZERO =
+ "__PAGEZERO"; /* the pagezero segment which has no */
+ /* protections and catches NULL */
+ /* references for MH_EXECUTE files */
+
+ static final String SEG_TEXT = "__TEXT"; /* the tradition UNIX text segment */
+ static final String SECT_TEXT = "__text"; /* the real text part of the text */
+ /* section no headers, and no padding */
+ static final String SECT_FVMLIB_INIT0 =
+ "__fvmlib_init0"; /* the fvmlib initialization */
+ /* section */
+ static final String SECT_FVMLIB_INIT1 =
+ "__fvmlib_init1"; /* the section following the */
+ /* fvmlib initialization */
+ /* section */
+
+ static final String SEG_DATA = "__DATA"; /* the tradition UNIX data segment */
+ static final String SECT_DATA =
+ "__data"; /* the real initialized data section */
+ /* no padding, no bss overlap */
+ static final String SECT_BSS =
+ "__bss"; /* the real uninitialized data section*/
+ /* no padding */
+ static final String SECT_COMMON =
+ "__common"; /* the section common symbols are */
+ /* allocated in by the link editor */
+
+ static final String SEG_OBJC = "__OBJC"; /* objective-C runtime segment */
+ static final String SECT_OBJC_SYMBOLS = "__symbol_table"; /* symbol table */
+ static final String SECT_OBJC_MODULES =
+ "__module_info"; /* module information */
+ static final String SECT_OBJC_STRINGS = "__selector_strs"; /* string table */
+ static final String SECT_OBJC_REFS = "__selector_refs"; /* string table */
+
+ static final String SEG_ICON = "__ICON"; /* the icon segment */
+ static final String SECT_ICON_HEADER = "__header"; /* the icon headers */
+ static final String SECT_ICON_TIFF = "__tiff"; /* the icons in tiff format */
+
+ static final String SEG_LINKEDIT =
+ "__LINKEDIT"; /* the segment containing all structs */
+ /* created and maintained by the link */
+ /* editor. Created with -seglinkedit */
+ /* option to ld(1) for MH_EXECUTE and */
+ /* FVMLIB file types only */
+
+ static final String SEG_UNIXSTACK =
+ "__UNIXSTACK"; /* the unix stack segment */
+
+ static final String SEG_IMPORT =
+ "__IMPORT"; /* the segment for the self (dyld) */
+ /* modifing code stubs that has read, */
+ /* write and execute permissions */
+
+ /*
+ * An indirect symbol table entry is simply a 32bit index into the symbol table
+ * to the symbol that the pointer or stub is refering to. Unless it is for a
+ * non-lazy symbol pointer section for a defined symbol which strip(1) as
+ * removed. In which case it has the value INDIRECT_SYMBOL_LOCAL. If the
+ * symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that.
+ */
+ static const Uint32 INDIRECT_SYMBOL_LOCAL = Uint32(0x80000000);
+ static const Uint32 INDIRECT_SYMBOL_ABS = Uint32(0x40000000);
+
+ /*
+ * The following are used to encode rebasing information
+ */
+ static const Uint32 REBASE_TYPE_POINTER = Uint32(1);
+ static const Uint32 REBASE_TYPE_TEXT_ABSOLUTE32 = Uint32(2);
+ static const Uint32 REBASE_TYPE_TEXT_PCREL32 = Uint32(3);
+
+ static const Uint32 REBASE_OPCODE_MASK = Uint32(0xF0);
+ static const Uint32 REBASE_IMMEDIATE_MASK = Uint32(0x0F);
+ static const Uint32 REBASE_OPCODE_DONE = Uint32(0x00);
+ static const Uint32 REBASE_OPCODE_SET_TYPE_IMM = Uint32(0x10);
+ static const Uint32 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = Uint32(0x20);
+ static const Uint32 REBASE_OPCODE_ADD_ADDR_ULEB = Uint32(0x30);
+ static const Uint32 REBASE_OPCODE_ADD_ADDR_IMM_SCALED = Uint32(0x40);
+ static const Uint32 REBASE_OPCODE_DO_REBASE_IMM_TIMES = Uint32(0x50);
+ static const Uint32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES = Uint32(0x60);
+ static const Uint32 REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = Uint32(0x70);
+ static const Uint32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB =
+ Uint32(0x80);
+
+/*
+ * The following are used to encode binding information
+ */
+ static const Uint32 BIND_TYPE_POINTER = Uint32(1);
+ static const Uint32 BIND_TYPE_TEXT_ABSOLUTE32 = Uint32(2);
+ static const Uint32 BIND_TYPE_TEXT_PCREL32 = Uint32(3);
+
+ static const Uint32 BIND_SPECIAL_DYLIB_SELF = Uint32(0);
+ static const Uint32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = Uint32(-1);
+ static const Uint32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = Uint32(-2);
+
+ static const Uint32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = Uint32(0x1);
+ static const Uint32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = Uint32(0x8);
+
+ static const Uint32 BIND_OPCODE_MASK = Uint32(0xF0);
+ static const Uint32 BIND_IMMEDIATE_MASK = Uint32(0x0F);
+ static const Uint32 BIND_OPCODE_DONE = Uint32(0x00);
+ static const Uint32 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = Uint32(0x10);
+ static const Uint32 BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = Uint32(0x20);
+ static const Uint32 BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = Uint32(0x30);
+ static const Uint32 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = Uint32(0x40);
+ static const Uint32 BIND_OPCODE_SET_TYPE_IMM = Uint32(0x50);
+ static const Uint32 BIND_OPCODE_SET_ADDEND_SLEB = Uint32(0x60);
+ static const Uint32 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = Uint32(0x70);
+ static const Uint32 BIND_OPCODE_ADD_ADDR_ULEB = Uint32(0x80);
+ static const Uint32 BIND_OPCODE_DO_BIND = Uint32(0x90);
+ static const Uint32 BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = Uint32(0xA0);
+ static const Uint32 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = Uint32(0xB0);
+ static const Uint32 BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB =
+ Uint32(0xC0);
+
+/*
+ * The following are used on the flags byte of a terminal node
+ * in the export information.
+ */
+ static const Uint32 EXPORT_SYMBOL_FLAGS_KIND_MASK = Uint32(0x03);
+ static const Uint32 EXPORT_SYMBOL_FLAGS_KIND_REGULAR = Uint32(0x00);
+ static const Uint32 EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL = Uint32(0x01);
+ static const Uint32 EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION = Uint32(0x04);
+ static const Uint32 EXPORT_SYMBOL_FLAGS_REEXPORT = Uint32(0x08);
+ static const Uint32 EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER = Uint32(0x10);
+
+ static const Uint32 DICE_KIND_DATA =
+ Uint32(0x0001); /* L$start$data$... label */
+ static const Uint32 DICE_KIND_JUMP_TABLE8 =
+ Uint32(0x0002); /* L$start$jt8$... label */
+ static const Uint32 DICE_KIND_JUMP_TABLE16 =
+ Uint32(0x0003); /* L$start$jt16$... label */
+ static const Uint32 DICE_KIND_JUMP_TABLE32 =
+ Uint32(0x0004); /* L$start$jt32$... label */
+ static const Uint32 DICE_KIND_ABS_JUMP_TABLE32 =
+ Uint32(0x0005); /* L$start$jta32$... label */
+
+/*
+ * Protection values, defined as bits within the vm_prot_t type
+ */
+
+ static const Int32 VM_PROT_NONE = Int32(0x00);
+ static const Int32 VM_PROT_READ = Int32(0x01); /* read permission */
+ static const Int32 VM_PROT_WRITE = Int32(0x02); /* write permission */
+ static const Int32 VM_PROT_EXECUTE = Int32(0x04); /* execute permission */
+
+/*
+ * The default protection for newly-created virtual memory
+ */
+
+ static final Int32 VM_PROT_DEFAULT = VM_PROT_READ | VM_PROT_WRITE;
+
+/*
+ * The maximum privileges possible, for parameter checking.
+ */
+
+ static final Int32 VM_PROT_ALL =
+ VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE;
+
+/*
+ * An invalid protection value.
+ * Used only by memory_object_lock_request to indicate no change
+ * to page locks. Using -1 here is a bad idea because it
+ * looks like VM_PROT_ALL and then some.
+ */
+
+ static const Int32 VM_PROT_NO_CHANGE = Int32(0x08);
+
+/*
+ * When a caller finds that he cannot obtain write permission on a
+ * mapped entry, the following flag can be used. The entry will
+ * be made "needs copy" effectively copying the object (using COW),
+ * and write permission will be added to the maximum protections
+ * for the associated entry.
+ */
+
+ static const Int32 VM_PROT_COPY = Int32(0x10);
+
+/*
+ * Another invalid protection value.
+ * Used only by memory_object_data_request upon an object
+ * which has specified a copy_call copy strategy. It is used
+ * when the kernel wants a page belonging to a copy of the
+ * object, and is only asking the object as a result of
+ * following a shadow chain. This solves the race between pages
+ * being pushed up by the memory manager and the kernel
+ * walking down the shadow chain.
+ */
+
+ static const Int32 VM_PROT_WANTS_COPY = Int32(0x10);
+}
diff --git a/pkg/dart2native/lib/macho_parser.dart b/pkg/dart2native/lib/macho_parser.dart
new file mode 100644
index 0000000..20deb1d
--- /dev/null
+++ b/pkg/dart2native/lib/macho_parser.dart
@@ -0,0 +1,376 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:math';
+import 'dart:typed_data';
+
+import './macho.dart';
+
+extension ByteReader on RandomAccessFile {
+ Uint32 readUint32() {
+ Uint8List rawBytes = readSync(4);
+ var byteView = ByteData.view(rawBytes.buffer);
+ return Uint32(byteView.getUint32(0, Endian.little));
+ }
+
+ Uint64 readUint64() {
+ Uint8List rawBytes = readSync(8);
+ var byteView = ByteData.view(rawBytes.buffer);
+ return Uint64(byteView.getUint64(0, Endian.little));
+ }
+
+ Int32 readInt32() {
+ Uint8List rawBytes = readSync(4);
+ var byteView = ByteData.view(rawBytes.buffer);
+ return Int32(byteView.getInt32(0, Endian.little));
+ }
+}
+
+class MachOFile {
+ IMachOHeader? header;
+ // The headerMaxOffset is set during parsing based on the maximum offset for
+ // segment offsets. Assuming the header start at byte 0 (that seems to always
+ // be the case), this number represents the total size of the header, which
+ // often includes a significant amount of zero-padding.
+ int headerMaxOffset = 0;
+ // We keep track on whether a code signature was seen so we can recreate it
+ // in the case that the binary has a CD hash that nededs updating.
+ bool hasCodeSignature = false;
+
+ // This wil contain all of the "load commands" in this MachO file. A load
+ // command is really a typed schema that indicates various parts of the MachO
+ // file (e.g. where to find the TEXT and DATA sections).
+ List<IMachOLoadCommand> commands =
+ List<IMachOLoadCommand>.empty(growable: true);
+
+ MachOFile();
+
+ // Returns the number of bytes read from the file.
+ Future<int> loadFromFile(File file) async {
+ // Ensure the file is long enough to contain the magic bytes.
+ final int fileLength = await file.length();
+ if (fileLength < 4) {
+ throw FormatException(
+ "File was not formatted properly. Length was too short: $fileLength");
+ }
+
+ // Read the first 4 bytes to see what type of MachO file this is.
+ var stream = await file.open();
+ var magic = stream.readUint32();
+
+ bool is64Bit = magic == MachOConstants.MH_MAGIC_64 ||
+ magic == MachOConstants.MH_CIGAM_64;
+
+ await stream.setPosition(0);
+
+ // Set the max header offset to the maximum file size so that when we read
+ // in the header we can correctly set the total header size.
+ headerMaxOffset = (1 << 63) - 1;
+
+ header = await _headerFromStream(stream, is64Bit);
+ if (header == null) {
+ throw FormatException(
+ "Could not parse a MachO header from the file: ${file.path}");
+ } else {
+ commands = await _commandsFromStream(stream, header!);
+ }
+
+ return stream.positionSync();
+ }
+
+ Future<MachOSymtabCommand> parseSymtabFromStream(
+ final Uint32 cmdsize, RandomAccessFile stream) async {
+ final symoff = stream.readUint32();
+ final nsyms = stream.readUint32();
+ final stroff = stream.readUint32();
+ final strsize = stream.readUint32();
+
+ return MachOSymtabCommand(cmdsize, symoff, nsyms, stroff, strsize);
+ }
+
+ Future<MachODysymtabCommand> parseDysymtabFromStream(
+ final Uint32 cmdsize, RandomAccessFile stream) async {
+ final ilocalsym = stream.readUint32();
+ final nlocalsym = stream.readUint32();
+ final iextdefsym = stream.readUint32();
+ final nextdefsym = stream.readUint32();
+ final iundefsym = stream.readUint32();
+ final nundefsym = stream.readUint32();
+ final tocoff = stream.readUint32();
+ final ntoc = stream.readUint32();
+ final modtaboff = stream.readUint32();
+ final nmodtab = stream.readUint32();
+ final extrefsymoff = stream.readUint32();
+ final nextrefsyms = stream.readUint32();
+ final indirectsymoff = stream.readUint32();
+ final nindirectsyms = stream.readUint32();
+ final extreloff = stream.readUint32();
+ final nextrel = stream.readUint32();
+ final locreloff = stream.readUint32();
+ final nlocrel = stream.readUint32();
+
+ return MachODysymtabCommand(
+ cmdsize,
+ ilocalsym,
+ nlocalsym,
+ iextdefsym,
+ nextdefsym,
+ iundefsym,
+ nundefsym,
+ tocoff,
+ ntoc,
+ modtaboff,
+ nmodtab,
+ extrefsymoff,
+ nextrefsyms,
+ indirectsymoff,
+ nindirectsyms,
+ extreloff,
+ nextrel,
+ locreloff,
+ nlocrel);
+ }
+
+ Future<MachOLinkeditDataCommand> parseLinkeditDataCommand(
+ final Uint32 cmd, final Uint32 cmdsize, RandomAccessFile stream) async {
+ final dataoff = stream.readUint32();
+ final datasize = stream.readUint32();
+
+ return MachOLinkeditDataCommand(
+ cmd,
+ cmdsize,
+ dataoff,
+ datasize,
+ );
+ }
+
+ Future<MachODyldInfoCommand> parseDyldInfoFromStream(
+ final Uint32 cmd, final Uint32 cmdsize, RandomAccessFile stream) async {
+ // Note that we're relying on the fact that the mirror returns the list of
+ // fields in the same order they're defined ni the class definition.
+
+ final rebaseOff = stream.readUint32();
+ final rebaseSize = stream.readUint32();
+ final bindOff = stream.readUint32();
+ final bindSize = stream.readUint32();
+ final weakBindOff = stream.readUint32();
+ final weakBindSize = stream.readUint32();
+ final lazyBindOff = stream.readUint32();
+ final lazyBindSize = stream.readUint32();
+ final exportOff = stream.readUint32();
+ final exportSize = stream.readUint32();
+
+ return MachODyldInfoCommand(
+ cmd,
+ cmdsize,
+ rebaseOff,
+ rebaseSize,
+ bindOff,
+ bindSize,
+ weakBindOff,
+ weakBindSize,
+ lazyBindOff,
+ lazyBindSize,
+ exportOff,
+ exportSize);
+ }
+
+ Future<MachOSegmentCommand64> parseSegmentCommand64FromStream(
+ final Uint32 cmdsize, RandomAccessFile stream) async {
+ final Uint8List segname = await stream.read(16);
+ final vmaddr = stream.readUint64();
+ final vmsize = stream.readUint64();
+ final fileoff = stream.readUint64();
+ final filesize = stream.readUint64();
+ final maxprot = stream.readInt32();
+ final initprot = stream.readInt32();
+ final nsects = stream.readUint32();
+ final flags = stream.readUint32();
+
+ if (nsects.asInt() == 0 && filesize.asInt() != 0) {
+ headerMaxOffset = min(headerMaxOffset, fileoff.asInt());
+ }
+
+ final sections = List.filled(nsects.asInt(), 0).map((_) {
+ final Uint8List sectname = stream.readSync(16);
+ final Uint8List segname = stream.readSync(16);
+ final addr = stream.readUint64();
+ final size = stream.readUint64();
+ final offset = stream.readUint32();
+ final align = stream.readUint32();
+ final reloff = stream.readUint32();
+ final nreloc = stream.readUint32();
+ final flags = stream.readUint32();
+ final reserved1 = stream.readUint32();
+ final reserved2 = stream.readUint32();
+ final reserved3 = stream.readUint32();
+
+ final notZerofill =
+ (flags & MachOConstants.S_ZEROFILL) != MachOConstants.S_ZEROFILL;
+ if (offset > 0 && size > 0 && notZerofill) {
+ headerMaxOffset = min(headerMaxOffset, offset.asInt());
+ }
+
+ return MachOSection64(sectname, segname, addr, size, offset, align,
+ reloff, nreloc, flags, reserved1, reserved2, reserved3);
+ }).toList();
+
+ return MachOSegmentCommand64(cmdsize, segname, vmaddr, vmsize, fileoff,
+ filesize, maxprot, initprot, nsects, flags, sections);
+ }
+
+ Future<IMachOHeader> _headerFromStream(
+ RandomAccessFile stream, bool is64Bit) async {
+ final magic = stream.readUint32();
+ final cputype = stream.readUint32();
+ final cpusubtype = stream.readUint32();
+ final filetype = stream.readUint32();
+ final ncmds = stream.readUint32();
+ final sizeofcmds = stream.readUint32();
+ final flags = stream.readUint32();
+
+ if (is64Bit) {
+ final reserved = stream.readUint32();
+ return MachOHeader(magic, cputype, cpusubtype, filetype, ncmds,
+ sizeofcmds, flags, reserved);
+ } else {
+ return MachOHeader32(
+ magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags);
+ }
+ }
+
+ void writeLoadCommandToStream(
+ IMachOLoadCommand command, RandomAccessFile stream) {
+ command.writeSync(stream);
+ }
+
+ void writeSync(RandomAccessFile stream) {
+ // Write the header.
+ stream.writeUint32(header!.magic);
+ stream.writeUint32(header!.cputype);
+ stream.writeUint32(header!.cpusubtype);
+ stream.writeUint32(header!.filetype);
+ stream.writeUint32(header!.ncmds);
+ stream.writeUint32(header!.sizeofcmds);
+ stream.writeUint32(header!.flags);
+
+ if (header is MachOHeader) {
+ stream.writeUint32(header!.reserved);
+ }
+
+ // Write all of the commands.
+ commands.forEach((command) {
+ writeLoadCommandToStream(command, stream);
+ });
+
+ // Pad the header according to the offset.
+ final int paddingAmount = headerMaxOffset - stream.positionSync();
+ if (paddingAmount > 0) {
+ stream.writeFromSync(List.filled(paddingAmount, 0));
+ }
+ }
+
+ Future<List<IMachOLoadCommand>> _commandsFromStream(
+ RandomAccessFile stream, IMachOHeader header) async {
+ final loadCommands = List<MachOLoadCommand>.empty(growable: true);
+ for (int i = 0; i < header.ncmds.asInt(); i++) {
+ final cmd = stream.readUint32();
+ final cmdsize = stream.readUint32();
+
+ // We need to read cmdsize bytes to get to the next command definition,
+ // but the cmdsize does includes the 2 bytes we just read (cmd +
+ // cmdsize) so we need to subtract those.
+ await stream
+ .setPosition((await stream.position()) + cmdsize.asInt() - 2 * 4);
+
+ loadCommands.add(MachOLoadCommand(cmd, cmdsize));
+ }
+
+ // Un-read all the bytes we just read.
+ var loadCommandsOffset = loadCommands
+ .map((command) => command.cmdsize)
+ .reduce((value, element) => value + element);
+ await stream
+ .setPosition((await stream.position()) - loadCommandsOffset.asInt());
+
+ final commands = List<IMachOLoadCommand>.empty(growable: true);
+ for (int i = 0; i < header.ncmds.asInt(); i++) {
+ final cmd = stream.readUint32();
+ final cmdsize = stream.readUint32();
+
+ // TODO(sarietta): Handle all MachO load command types. For now, since
+ // this implementation is exclusively being used to handle generating
+ // MacOS-compatible MachO executables for compiled dart scripts, only the
+ // load commands that are currently implemented are strictly necessary. It
+ // may be useful to handle all cases and pull this functionality out to a
+ // separate MachO library.
+ if (cmd == MachOConstants.LC_SEGMENT_64) {
+ commands.add(await parseSegmentCommand64FromStream(cmdsize, stream));
+ } else if (cmd == MachOConstants.LC_DYLD_INFO_ONLY ||
+ cmd == MachOConstants.LC_DYLD_INFO) {
+ commands.add(await parseDyldInfoFromStream(cmd, cmdsize, stream));
+ } else if (cmd == MachOConstants.LC_SYMTAB) {
+ commands.add(await parseSymtabFromStream(cmdsize, stream));
+ } else if (cmd == MachOConstants.LC_DYSYMTAB) {
+ commands.add(await parseDysymtabFromStream(cmdsize, stream));
+ } else if (cmd == MachOConstants.LC_CODE_SIGNATURE ||
+ cmd == MachOConstants.LC_SEGMENT_SPLIT_INFO ||
+ cmd == MachOConstants.LC_FUNCTION_STARTS ||
+ cmd == MachOConstants.LC_DATA_IN_CODE ||
+ cmd == MachOConstants.LC_DYLIB_CODE_SIGN_DRS) {
+ if (cmd == MachOConstants.LC_CODE_SIGNATURE) {
+ hasCodeSignature = true;
+ }
+ commands.add(await parseLinkeditDataCommand(cmd, cmdsize, stream));
+ } else if (cmd == MachOConstants.LC_SEGMENT ||
+ cmd == MachOConstants.LC_SYMSEG ||
+ cmd == MachOConstants.LC_THREAD ||
+ cmd == MachOConstants.LC_UNIXTHREAD ||
+ cmd == MachOConstants.LC_LOADFVMLIB ||
+ cmd == MachOConstants.LC_IDFVMLIB ||
+ cmd == MachOConstants.LC_IDENT ||
+ cmd == MachOConstants.LC_FVMFILE ||
+ cmd == MachOConstants.LC_PREPAGE ||
+ cmd == MachOConstants.LC_LOAD_DYLIB ||
+ cmd == MachOConstants.LC_ID_DYLIB ||
+ cmd == MachOConstants.LC_LOAD_DYLINKER ||
+ cmd == MachOConstants.LC_ID_DYLINKER ||
+ cmd == MachOConstants.LC_PREBOUND_DYLIB ||
+ cmd == MachOConstants.LC_ROUTINES ||
+ cmd == MachOConstants.LC_SUB_FRAMEWORK ||
+ cmd == MachOConstants.LC_SUB_UMBRELLA ||
+ cmd == MachOConstants.LC_SUB_CLIENT ||
+ cmd == MachOConstants.LC_SUB_LIBRARY ||
+ cmd == MachOConstants.LC_TWOLEVEL_HINTS ||
+ cmd == MachOConstants.LC_PREBIND_CKSUM ||
+ cmd == MachOConstants.LC_LOAD_WEAK_DYLIB ||
+ cmd == MachOConstants.LC_ROUTINES_64 ||
+ cmd == MachOConstants.LC_UUID ||
+ cmd == MachOConstants.LC_RPATH ||
+ cmd == MachOConstants.LC_REEXPORT_DYLIB ||
+ cmd == MachOConstants.LC_LAZY_LOAD_DYLIB ||
+ cmd == MachOConstants.LC_ENCRYPTION_INFO ||
+ cmd == MachOConstants.LC_LOAD_UPWARD_DYLIB ||
+ cmd == MachOConstants.LC_VERSION_MIN_MACOSX ||
+ cmd == MachOConstants.LC_VERSION_MIN_IPHONEOS ||
+ cmd == MachOConstants.LC_DYLD_ENVIRONMENT ||
+ cmd == MachOConstants.LC_MAIN ||
+ cmd == MachOConstants.LC_SOURCE_VERSION ||
+ cmd == MachOConstants.LC_BUILD_VERSION) {
+ // cmdsize includes the size of the contents + cmd + cmdsize
+ final contents = await stream.read(cmdsize.asInt() - 2 * 4);
+ commands.add(MachOGenericLoadCommand(cmd, cmdsize, contents));
+ } else {
+ // cmdsize includes the size of the contents + cmd + cmdsize
+ final contents = await stream.read(cmdsize.asInt() - 2 * 4);
+ commands.add(MachOGenericLoadCommand(cmd, cmdsize, contents));
+ final cmdString = "0x${cmd.asInt().toRadixString(16)}";
+ print("Found unknown MachO load command: $cmdString");
+ }
+ }
+
+ return commands;
+ }
+}
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index bac5e34..e5c15b0 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -4,6 +4,8 @@
import 'dart:io';
+import 'package:dart2native/macho.dart';
+import 'package:dart2native/macho_parser.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
@@ -21,6 +23,61 @@
void defineCompileTests() {
final isRunningOnIA32 = Platform.version.contains('ia32');
+
+ if (Platform.isMacOS) {
+ test('Compile exe for MacOS signing', () async {
+ final p = project(mainSrc: '''void main() {}''');
+ final inFile =
+ path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+ final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+ var result = await p.run(
+ [
+ 'compile',
+ 'exe',
+ '-o',
+ outFile,
+ inFile,
+ ],
+ );
+
+ expect(result.stdout, contains(soundNullSafetyMessage));
+ expect(result.stderr, isEmpty);
+ expect(result.exitCode, 0);
+ expect(File(outFile).existsSync(), true,
+ reason: 'File not found: $outFile');
+
+ // Ensure the file contains the __CUSTOM segment.
+ final machOFile = MachOFile();
+ await machOFile.loadFromFile(File(outFile));
+
+ // Throws an exception (and thus the test fails) if the segment doesn't
+ // exist.
+ machOFile.commands.where((segment) {
+ if (segment.asType() is MachOSegmentCommand64) {
+ final segmentName = (segment as MachOSegmentCommand64).segname;
+ final segmentNameTrimmed = String.fromCharCodes(
+ segmentName.takeWhile((value) => value != 0));
+ return segmentNameTrimmed == '__CUSTOM';
+ } else {
+ return false;
+ }
+ }).first;
+
+ // Ensure that the exe can be signed.
+ final codeSigningProcess = await Process.start('codesign', [
+ '-o',
+ 'runtime',
+ '-s',
+ '-',
+ outFile,
+ ]);
+
+ final signingResult = await codeSigningProcess.exitCode;
+ expect(signingResult, 0);
+ }, skip: isRunningOnIA32);
+ }
+
// *** NOTE ***: These tests *must* be run with the `--use-sdk` option
// as they depend on a fully built SDK to resolve various snapshot files
// used by compilation.
@@ -153,9 +210,9 @@
[],
);
- expect(result.stdout, contains('I love executables'));
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
+ expect(result.stdout, contains('I love executables'));
}, skip: isRunningOnIA32);
test('Compile to executable disabled on IA32', () async {
@@ -220,9 +277,9 @@
[],
);
- expect(result.stdout, contains('42'));
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
+ expect(result.stdout, contains('42'));
}, skip: isRunningOnIA32);
test('Compile and run aot snapshot', () async {
@@ -443,9 +500,9 @@
[],
);
- expect(result.stdout, contains('sound'));
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
+ expect(result.stdout, contains('sound'));
}, skip: isRunningOnIA32);
test('Compile and run exe with --no-sound-null-safety', () async {
diff --git a/pkg/dds/lib/devtools_server.dart b/pkg/dds/lib/devtools_server.dart
index 0a267d6..299044c 100644
--- a/pkg/dds/lib/devtools_server.dart
+++ b/pkg/dds/lib/devtools_server.dart
@@ -489,11 +489,12 @@
}
Future<Map<String, dynamic>> launchDevTools(
- Map<String, dynamic> params,
- Uri vmServiceUri,
- String devToolsUrl,
- bool headlessMode,
- bool machineMode) async {
+ Map<String, dynamic> params,
+ Uri vmServiceUri,
+ String devToolsUrl,
+ bool headlessMode,
+ bool machineMode,
+ ) async {
// First see if we have an existing DevTools client open that we can
// reuse.
final canReuse =
@@ -508,10 +509,11 @@
shouldNotify,
)) {
_emitLaunchEvent(
- reused: true,
- notified: shouldNotify,
- pid: null,
- machineMode: machineMode);
+ reused: true,
+ notified: shouldNotify,
+ pid: null,
+ machineMode: machineMode,
+ );
return {
'reused': true,
'notified': shouldNotify,
@@ -582,7 +584,7 @@
bool _tryReuseExistingDevToolsInstance(
Uri vmServiceUri,
- String page,
+ String? page,
bool notifyUser,
) {
// First try to find a client that's already connected to this VM service,
@@ -591,7 +593,9 @@
clientManager.findExistingConnectedReusableClient(vmServiceUri);
if (existingClient != null) {
try {
- existingClient.showPage(page);
+ if (page != null) {
+ existingClient.showPage(page);
+ }
if (notifyUser) {
existingClient.notify();
}
@@ -617,7 +621,7 @@
String _buildUriToLaunch(
Map<String, dynamic> uriParams,
- page,
+ String? page,
Uri devToolsUri,
) {
final queryStringNameValues = [];
diff --git a/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart b/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
index 5b65d4c..e98f23e 100644
--- a/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
+++ b/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
@@ -18,7 +18,7 @@
class MachineModeCommandHandler {
static const launchDevToolsService = 'launchDevTools';
static const copyAndCreateDevToolsFile = 'copyAndCreateDevToolsFile';
- static const restoreDevToolsFile = 'restoreDevToolsFiles';
+ static const restoreDevToolsFile = 'restoreDevToolsFile';
static const errorLaunchingBrowserCode = 500;
static const bool machineMode = true;
@@ -67,7 +67,7 @@
_stdinCommandStream.listen((Map<String, dynamic> json) async {
// ID can be String, int or null
final dynamic id = json['id'];
- final Map<String, dynamic> params = json['params'];
+ final Map<String, dynamic> params = json['params'] ?? <String, dynamic>{};
final method = json['method'];
switch (method) {
case 'vm.register':
@@ -200,7 +200,7 @@
void _handleDevToolsSurvey(dynamic id, Map<String, dynamic> params) {
_devToolsUsage ??= DevToolsUsage();
final String surveyRequest = params['surveyRequest'];
- final String value = params['value'];
+ final String value = params['value'] ?? '';
switch (surveyRequest) {
case copyAndCreateDevToolsFile:
@@ -212,8 +212,6 @@
{
'id': id,
'result': {
- // TODO(bkonyi): fix incorrect spelling of "success" here and
- // below once we figure out the impact of changing this key.
'success': true,
},
},
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index cb3837e..68a6e019 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -16,7 +16,7 @@
browser_launcher: ^1.0.0
collection: ^1.15.0
dds_service_extensions: ^1.3.0
- devtools_shared: ^2.3.0
+ devtools_shared: ^2.11.4
http_multi_server: ^3.0.0
json_rpc_2: ^3.0.0
meta: ^1.1.8
diff --git a/pkg/dds/test/common/test_helper.dart b/pkg/dds/test/common/test_helper.dart
index a9473ea..7325d97 100644
--- a/pkg/dds/test/common/test_helper.dart
+++ b/pkg/dds/test/common/test_helper.dart
@@ -61,3 +61,12 @@
await service.resume(isolate.id!);
await completer.future;
}
+
+/// Returns the resolved URI to the pre-built devtools app.
+///
+/// The method caller is responsible for providing the relative [prefix] that
+/// will resolve to the sdk/ directory (e.g. '../../../').
+Uri devtoolsAppUri({required String prefix}) {
+ const pathFromSdkDirectory = 'third_party/devtools/web';
+ return Platform.script.resolve('$prefix$pathFromSdkDirectory');
+}
diff --git a/pkg/dds/test/devtools_observatory_connection_test.dart b/pkg/dds/test/devtools_observatory_connection_test.dart
index 3dcc6cc..ae3eb38 100644
--- a/pkg/dds/test/devtools_observatory_connection_test.dart
+++ b/pkg/dds/test/devtools_observatory_connection_test.dart
@@ -43,9 +43,7 @@
remoteVmServiceUri,
devToolsConfiguration: DevToolsConfiguration(
enable: true,
- customBuildDirectoryPath: Platform.script.resolve(
- '../../../third_party/devtools/web',
- ),
+ customBuildDirectoryPath: devtoolsAppUri(prefix: '../../../'),
),
);
expect(dds.isRunning, true);
diff --git a/pkg/dds/test/devtools_server/devtools_server_driver.dart b/pkg/dds/test/devtools_server/devtools_server_driver.dart
new file mode 100644
index 0000000..99e9191
--- /dev/null
+++ b/pkg/dds/test/devtools_server/devtools_server_driver.dart
@@ -0,0 +1,93 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:devtools_shared/devtools_test_utils.dart';
+
+const verbose = true;
+
+class DevToolsServerDriver {
+ DevToolsServerDriver._(
+ this._process,
+ this._stdin,
+ Stream<String> _stdout,
+ Stream<String> _stderr,
+ ) : stdout = _convertToMapStream(_stdout),
+ stderr = _stderr.map((line) {
+ _trace('<== STDERR $line');
+ return line;
+ });
+
+ final Process _process;
+ final Stream<Map<String, dynamic>?> stdout;
+ final Stream<String> stderr;
+ final StringSink _stdin;
+
+ void write(Map<String, dynamic> request) {
+ final line = jsonEncode(request);
+ _trace('==> $line');
+ _stdin.writeln(line);
+ }
+
+ static Stream<Map<String, dynamic>?> _convertToMapStream(
+ Stream<String> stream,
+ ) {
+ return stream.map((line) {
+ _trace('<== $line');
+ return line;
+ }).map((line) {
+ try {
+ return jsonDecode(line) as Map<String, dynamic>;
+ } catch (e) {
+ return null;
+ }
+ }).where((item) => item != null);
+ }
+
+ static void _trace(String message) {
+ if (verbose) {
+ print(message);
+ }
+ }
+
+ bool kill() => _process.kill();
+
+ static Future<DevToolsServerDriver> create({
+ int port = 0,
+ int? tryPorts,
+ List<String> additionalArgs = const [],
+ }) async {
+ final script =
+ Platform.script.resolveUri(Uri.parse('./serve_devtools.dart'));
+ final args = [
+ script.path,
+ '--machine',
+ '--port',
+ '$port',
+ ...additionalArgs,
+ ];
+
+ if (tryPorts != null) {
+ args.addAll(['--try-ports', '$tryPorts']);
+ }
+
+ if (useChromeHeadless && headlessModeIsSupported) {
+ args.add('--headless');
+ }
+ final Process process = await Process.start(
+ Platform.resolvedExecutable,
+ args,
+ );
+
+ return DevToolsServerDriver._(
+ process,
+ process.stdin,
+ process.stdout.transform(utf8.decoder).transform(const LineSplitter()),
+ process.stderr.transform(utf8.decoder).transform(const LineSplitter()),
+ );
+ }
+}
diff --git a/pkg/dds/test/devtools_server/devtools_server_test.dart b/pkg/dds/test/devtools_server/devtools_server_test.dart
new file mode 100644
index 0000000..2f34765
--- /dev/null
+++ b/pkg/dds/test/devtools_server/devtools_server_test.dart
@@ -0,0 +1,569 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:dds/devtools_server.dart';
+import 'package:dds/src/devtools/machine_mode_command_handler.dart';
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:devtools_shared/devtools_test_utils.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'devtools_server_driver.dart';
+
+late CliAppFixture appFixture;
+late DevToolsServerDriver server;
+final completers = <String, Completer<Map<String, dynamic>>>{};
+
+/// A broadcast stream controller for streaming events from the server.
+late StreamController<Map<String, dynamic>> eventController;
+
+/// A broadcast stream of events from the server.
+///
+/// Listening for "server.started" events on this stream may be unreliable
+/// because it may have occurred before the test starts. Use the
+/// [serverStartedEvent] instead.
+Stream<Map<String, dynamic>> get events => eventController.stream;
+
+/// Completer that signals when the server started event has been received.
+late Completer<Map<String, dynamic>> serverStartedEvent;
+
+final Map<String, String> registeredServices = {};
+
+// A list of PIDs for Chrome instances spawned by tests that should be
+// cleaned up.
+final List<int> browserPids = [];
+
+void main() {
+ late StreamSubscription<String> stderrSub;
+ late StreamSubscription<Map<String, dynamic>?> stdoutSub;
+
+ setUp(() async {
+ serverStartedEvent = Completer<Map<String, dynamic>>();
+ eventController = StreamController<Map<String, dynamic>>.broadcast();
+
+ // Start the command-line server.
+ server = await DevToolsServerDriver.create();
+
+ // Fail tests on any stderr.
+ stderrSub = server.stderr.listen((text) => throw 'STDERR: $text');
+ stdoutSub = server.stdout.listen((map) {
+ if (map!.containsKey('id')) {
+ if (map.containsKey('result')) {
+ completers[map['id']]!.complete(map['result']);
+ } else {
+ completers[map['id']]!.completeError(map['error']);
+ }
+ } else if (map.containsKey('event')) {
+ if (map['event'] == 'server.started') {
+ serverStartedEvent.complete(map);
+ }
+ eventController.add(map);
+ }
+ });
+
+ await serverStartedEvent.future;
+ await _startApp();
+ });
+
+ tearDown(() async {
+ browserPids
+ ..forEach((pid) => Process.killPid(pid, ProcessSignal.sigkill))
+ ..clear();
+ await stdoutSub.cancel();
+ await stderrSub.cancel();
+ server.kill();
+ await appFixture.teardown();
+ });
+
+ test('registers service', () async {
+ final serverResponse = await _send(
+ 'vm.register',
+ {'uri': appFixture.serviceUri.toString()},
+ );
+ expect(serverResponse['success'], isTrue);
+
+ // Expect the VM service to see the launchDevTools service registered.
+ expect(registeredServices, contains(DevToolsServer.launchDevToolsService));
+ }, timeout: const Timeout.factor(10));
+
+ test('can bind to next available port', () async {
+ final server1 = await DevToolsServerDriver.create(port: 8855);
+ try {
+ // Wait for the first server to start up and ensure it got the
+ // expected port.
+ final event1 = (await server1.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+ expect(event1['params']['port'], 8855);
+
+ // Now spawn another requesting the same port and ensure it got the next
+ // port number.
+ final server2 = await DevToolsServerDriver.create(
+ port: 8855,
+ tryPorts: 2,
+ );
+ try {
+ final event2 = (await server2.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+
+ expect(event2['params']['port'], 8856);
+ } finally {
+ server2.kill();
+ }
+ } finally {
+ server1.kill();
+ }
+ }, timeout: const Timeout.factor(10));
+
+ test('allows embedding without flag', () async {
+ final server = await DevToolsServerDriver.create();
+ final httpClient = HttpClient();
+ late HttpClientResponse resp;
+ try {
+ final startedEvent = (await server.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+ final host = startedEvent['params']['host'];
+ final port = startedEvent['params']['port'];
+
+ final req = await httpClient.get(host, port, '/');
+ resp = await req.close();
+ expect(resp.headers.value('x-frame-options'), isNull);
+ } finally {
+ httpClient.close();
+ await resp.drain();
+ server.kill();
+ }
+ }, timeout: const Timeout.factor(10));
+
+ test('does not allow embedding with flag', () async {
+ final server = await DevToolsServerDriver.create(
+ additionalArgs: ['--no-allow-embedding'],
+ );
+ final httpClient = HttpClient();
+ late HttpClientResponse resp;
+ try {
+ final startedEvent = (await server.stdout.firstWhere(
+ (map) => map!['event'] == 'server.started',
+ ))!;
+ final host = startedEvent['params']['host'];
+ final port = startedEvent['params']['port'];
+
+ final req = await httpClient.get(host, port, '/');
+ resp = await req.close();
+ expect(resp.headers.value('x-frame-options'), 'SAMEORIGIN');
+ } finally {
+ httpClient.close();
+ await resp.drain();
+ server.kill();
+ }
+ }, timeout: const Timeout.factor(10));
+
+ test('Analytics Survey', () async {
+ var serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': 'copyAndCreateDevToolsFile',
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['success'], isTrue);
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': apiSetActiveSurvey,
+ 'value': 'Q3-2019',
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['success'], isTrue);
+ expect(serverResponse['activeSurvey'], 'Q3-2019');
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': apiIncrementSurveyShownCount,
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['activeSurvey'], 'Q3-2019');
+ expect(serverResponse['surveyShownCount'], 1);
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': apiIncrementSurveyShownCount,
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['activeSurvey'], 'Q3-2019');
+ expect(serverResponse['surveyShownCount'], 2);
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': apiGetSurveyShownCount,
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['activeSurvey'], 'Q3-2019');
+ expect(serverResponse['surveyShownCount'], 2);
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': apiGetSurveyActionTaken,
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['activeSurvey'], 'Q3-2019');
+ expect(serverResponse['surveyActionTaken'], isFalse);
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': apiSetSurveyActionTaken,
+ 'value': json.encode(true),
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['activeSurvey'], 'Q3-2019');
+ expect(serverResponse['surveyActionTaken'], isTrue);
+
+ serverResponse = await _send('devTools.survey', {
+ 'surveyRequest': MachineModeCommandHandler.restoreDevToolsFile,
+ });
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['success'], isTrue);
+ expect(
+ serverResponse['content'],
+ '{\n'
+ ' \"Q3-2019\": {\n'
+ ' \"surveyActionTaken\": true,\n'
+ ' \"surveyShownCount\": 2\n'
+ ' }\n'
+ '}\n',
+ );
+ }, timeout: const Timeout.factor(10));
+
+ for (final bool useVmService in [true, false]) {
+ group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+ test(
+ 'DevTools connects back to server API and registers that it is connected',
+ () async {
+ // Register the VM.
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Send a request to launch DevTools in a browser.
+ await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+ final serverResponse =
+ await _waitForClients(requiredConnectionState: true);
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ appFixture.serviceUri.toString(),
+ );
+ }, timeout: const Timeout.factor(10));
+
+ test('can launch on a specific page', () async {
+ // Register the VM.
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Send a request to launch at a certain page.
+ await _sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ page: 'memory',
+ );
+
+ final serverResponse = await _waitForClients(requiredPage: 'memory');
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ appFixture.serviceUri.toString(),
+ );
+ expect(serverResponse['clients'][0]['currentPage'], 'memory');
+ }, timeout: const Timeout.factor(10));
+
+ test('can switch page', () async {
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Launch on the memory page and wait for the connection.
+ await _sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ page: 'memory',
+ );
+ await _waitForClients(requiredPage: 'memory');
+
+ // Re-launch, allowing reuse and with a different page.
+ await _sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ page: 'logging',
+ );
+
+ final serverResponse = await _waitForClients(requiredPage: 'logging');
+ expect(serverResponse, isNotNull);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ appFixture.serviceUri.toString(),
+ );
+ expect(serverResponse['clients'][0]['currentPage'], 'logging');
+ }, timeout: const Timeout.factor(20));
+
+ test('DevTools reports disconnects from a VM', () async {
+ // Register the VM.
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Send a request to launch DevTools in a browser.
+ await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+ // Wait for the DevTools to inform server that it's connected.
+ await _waitForClients(requiredConnectionState: true);
+
+ // Terminate the VM.
+ await appFixture.teardown();
+
+ // Ensure the client is marked as disconnected.
+ final serverResponse =
+ await _waitForClients(requiredConnectionState: false);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isFalse);
+ expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
+ }, timeout: const Timeout.factor(20));
+
+ test('server removes clients that disconnect from the API', () async {
+ final event = await serverStartedEvent.future;
+
+ // Spawn our own Chrome process so we can terminate it.
+ final devToolsUri =
+ 'http://${event['params']['host']}:${event['params']['port']}';
+ final chrome = await Chrome.locate()!.start(url: devToolsUri);
+
+ // Wait for DevTools to inform server that it's connected.
+ await _waitForClients();
+
+ // Close the browser, which will disconnect DevTools SSE connection
+ // back to the server.
+ chrome.kill();
+
+ // Await a long delay to wait for the SSE client to close.
+ await delay(duration: const Duration(seconds: 20));
+
+ // Ensure the client is completely removed from the list.
+ await _waitForClients(expectNone: true, useLongTimeout: true);
+ }, timeout: const Timeout.factor(20));
+
+ test('Server reuses DevTools instance if already connected to same VM',
+ () async {
+ // Register the VM.
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Send a request to launch DevTools in a browser.
+ await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+ {
+ final serverResponse = await _waitForClients(
+ requiredConnectionState: true,
+ );
+ expect(serverResponse['clients'], hasLength(1));
+ }
+
+ // Request again, allowing reuse, and server emits an event saying the
+ // window was reused.
+ final launchResponse = await _sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ );
+ expect(launchResponse['reused'], isTrue);
+
+ // Ensure there's still only one connection (eg. we didn't spawn a new one
+ // we reused the existing one).
+ final serverResponse =
+ await _waitForClients(requiredConnectionState: true);
+ expect(serverResponse['clients'], hasLength(1));
+ }, timeout: const Timeout.factor(20));
+
+ test('Server does not reuse DevTools instance if embedded', () async {
+ // Register the VM.
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Spawn an embedded version of DevTools in a browser.
+ final event = await serverStartedEvent.future;
+ final devToolsUri =
+ 'http://${event['params']['host']}:${event['params']['port']}';
+ final launchUrl = '$devToolsUri/?embed=true&page=logging'
+ '&uri=${Uri.encodeQueryComponent(appFixture.serviceUri.toString())}';
+ final chrome = await Chrome.locate()!.start(url: launchUrl);
+ try {
+ {
+ final serverResponse =
+ await _waitForClients(requiredConnectionState: true);
+ expect(serverResponse['clients'], hasLength(1));
+ }
+
+ // Send a request to the server to launch and ensure it did
+ // not reuse the existing connection. Launch it on a different page
+ // so we can easily tell once this one has connected.
+ final launchResponse = await _sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ page: 'memory',
+ );
+ expect(launchResponse['reused'], isFalse);
+
+ // Ensure there's now two connections.
+ final serverResponse = await _waitForClients(
+ requiredConnectionState: true,
+ requiredPage: 'memory',
+ );
+ expect(serverResponse['clients'], hasLength(2));
+ } finally {
+ chrome.kill();
+ }
+ }, timeout: const Timeout.factor(20));
+
+ test('Server reuses DevTools instance if not connected to a VM',
+ () async {
+ // Register the VM.
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Send a request to launch DevTools in a browser.
+ await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+ // Wait for the DevTools to inform server that it's connected.
+ await _waitForClients(requiredConnectionState: true);
+
+ // Terminate the VM.
+ await appFixture.teardown();
+
+ // Ensure the client is marked as disconnected.
+ await _waitForClients(requiredConnectionState: false);
+
+ // Start up a new app.
+ await _startApp();
+ await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+ // Send a new request to launch.
+ await _sendLaunchDevToolsRequest(
+ useVmService: useVmService,
+ reuseWindows: true,
+ notify: true,
+ );
+
+ // Ensure we now have a single connected client.
+ final serverResponse =
+ await _waitForClients(requiredConnectionState: true);
+ expect(serverResponse['clients'], hasLength(1));
+ expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+ expect(
+ serverResponse['clients'][0]['vmServiceUri'],
+ appFixture.serviceUri.toString(),
+ );
+ }, timeout: const Timeout.factor(20));
+ });
+ }
+}
+
+Future<Map<String, dynamic>> _sendLaunchDevToolsRequest({
+ required bool useVmService,
+ String? page,
+ bool notify = false,
+ bool reuseWindows = false,
+}) async {
+ print('grabbing client.launch event');
+ final launchEvent = events.where((e) => e['event'] == 'client.launch').first;
+ if (useVmService) {
+ await appFixture.serviceConnection.callMethod(
+ registeredServices[DevToolsServer.launchDevToolsService]!,
+ args: {
+ 'reuseWindows': reuseWindows,
+ 'page': page,
+ 'notify': notify,
+ },
+ );
+ } else {
+ await _send(
+ 'devTools.launch',
+ {
+ 'vmServiceUri': appFixture.serviceUri.toString(),
+ 'reuseWindows': reuseWindows,
+ 'page': page,
+ },
+ );
+ }
+ final response = await launchEvent;
+ final pid = response['params']['pid'];
+ if (pid != null) {
+ browserPids.add(pid);
+ }
+ return response['params'];
+}
+
+Future<void> _startApp() async {
+ final appUri =
+ Platform.script.resolveUri(Uri.parse('../fixtures/empty_dart_app.dart'));
+ appFixture = await CliAppFixture.create(appUri.path);
+
+ // Track services method names as they're registered.
+ appFixture.serviceConnection
+ .onEvent(EventStreams.kService)
+ .where((e) => e.kind == EventKind.kServiceRegistered)
+ .listen((e) => registeredServices[e.service!] = e.method!);
+ await appFixture.serviceConnection.streamListen(EventStreams.kService);
+}
+
+int nextId = 0;
+Future<Map<String, dynamic>> _send(
+ String method, [
+ Map<String, dynamic>? params,
+]) {
+ final id = (nextId++).toString();
+ completers[id] = Completer<Map<String, dynamic>>();
+ server.write({'id': id.toString(), 'method': method, 'params': params});
+ return completers[id]!.future;
+}
+
+// It may take time for the servers client list to be updated as the web app
+// connects, so this helper just polls waiting for the expected state and
+// then returns the client list.
+Future<Map<String, dynamic>> _waitForClients({
+ bool? requiredConnectionState,
+ String? requiredPage,
+ bool expectNone = false,
+ bool useLongTimeout = false,
+ Duration delayDuration = defaultDelay,
+}) async {
+ late Map<String, dynamic> serverResponse;
+
+ final isOnPage = (client) => client['currentPage'] == requiredPage;
+ final hasConnectionState = (client) => requiredConnectionState ?? false
+ // If we require a connected client, also require a non-null page. This
+ // avoids a race in tests where we may proceed to send messages to a client
+ // that is not fully initialised.
+ ? (client['hasConnection'] && client['currentPage'] != null)
+ : !client['hasConnection'];
+
+ await _waitFor(
+ () async {
+ // Await a short delay to give the client time to connect.
+ await delay();
+
+ serverResponse = await _send('client.list');
+ final clients = serverResponse['clients'];
+ return clients is List &&
+ (clients.isEmpty == expectNone) &&
+ (requiredPage == null || clients.any(isOnPage)) &&
+ (requiredConnectionState == null || clients.any(hasConnectionState));
+ },
+ delayDuration: delayDuration,
+ );
+
+ return serverResponse;
+}
+
+Future<void> _waitFor(
+ Future<bool> condition(), {
+ Duration delayDuration = defaultDelay,
+}) async {
+ while (true) {
+ if (await condition()) {
+ return;
+ }
+ await delay(duration: delayDuration);
+ }
+}
+
+const defaultDelay = Duration(milliseconds: 500);
diff --git a/pkg/dds/test/devtools_server/serve_devtools.dart b/pkg/dds/test/devtools_server/serve_devtools.dart
new file mode 100644
index 0000000..e44c4f8
--- /dev/null
+++ b/pkg/dds/test/devtools_server/serve_devtools.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:dds/devtools_server.dart';
+
+import '../common/test_helper.dart';
+
+void main(List<String> args) async {
+ unawaited(
+ DevToolsServer().serveDevToolsWithArgs(
+ args,
+ customDevToolsPath: devtoolsAppUri(prefix: '../../../../').path,
+ ),
+ );
+}
diff --git a/pkg/dds/test/fixtures/empty_dart_app.dart b/pkg/dds/test/fixtures/empty_dart_app.dart
new file mode 100644
index 0000000..e96e4fe
--- /dev/null
+++ b/pkg/dds/test/fixtures/empty_dart_app.dart
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+void main() async {
+ print('starting empty app');
+
+ var myVar = 0;
+ while (true) {
+ myVar++;
+ print(myVar);
+ await (Future.delayed(const Duration(seconds: 2)));
+ }
+}
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_dart_2_17_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_dart_2_17_test.dart
new file mode 100644
index 0000000..644f54d
--- /dev/null
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_dart_2_17_test.dart
@@ -0,0 +1,303 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.9
+
+import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
+import 'package:test/test.dart';
+
+import 'expression_compiler_e2e_suite.dart';
+
+void main() async {
+ var driver = await TestDriver.init();
+
+ group('Dart 2.17 language features', () {
+ tearDownAll(() async {
+ await driver.finish();
+ });
+
+ group('(Unsound null safety)', () {
+ group('(AMD module system)', () {
+ var setup = SetupCompilerOptions(
+ soundNullSafety: false,
+ legacyCode: false,
+ moduleFormat: ModuleFormat.amd);
+ runSharedTests(setup, driver);
+ });
+
+ group('(DDC module system)', () {
+ var setup = SetupCompilerOptions(
+ soundNullSafety: false,
+ legacyCode: false,
+ moduleFormat: ModuleFormat.ddc);
+ runSharedTests(setup, driver);
+ });
+ });
+
+ group('(Sound null safety)', () {
+ group('(AMD module system)', () {
+ var setup = SetupCompilerOptions(
+ soundNullSafety: true,
+ legacyCode: false,
+ moduleFormat: ModuleFormat.amd);
+ runSharedTests(setup, driver);
+ });
+
+ group('(DDC module system)', () {
+ var setup = SetupCompilerOptions(
+ soundNullSafety: true,
+ legacyCode: false,
+ moduleFormat: ModuleFormat.ddc);
+ runSharedTests(setup, driver);
+ });
+ });
+ });
+}
+
+/// Shared tests for language features introduced in version 2.17.0.
+void runSharedTests(SetupCompilerOptions setup, TestDriver driver) {
+ group('Named arguments anywhere', () {
+ var source = r'''
+ String topLevelMethod(int param1, String param2,
+ {int param3 = -1, String param4 = 'default'}) =>
+ '$param1, $param2, $param3, $param4';
+
+ class C {
+ int param1;
+ String param2;
+ int param3;
+ String param4;
+ C(this.param1, this.param2,
+ {this.param3 = -1, this.param4 = 'default'});
+
+ static String staticMethod(int param1, String param2,
+ {int param3 = -1, String param4 = 'default'}) =>
+ '$param1, $param2, $param3, $param4';
+
+ String instanceMethod(int param1, String param2,
+ {int param3 = -1, String param4 = 'default'}) =>
+ '$param1, $param2, $param3, $param4';
+
+ String toString() => '$param1, $param2, $param3, $param4';
+ }
+
+ main() {
+ String localMethod(int param1, String param2,
+ {int param3 = -1, String param4 = 'default'}) =>
+ '$param1, $param2, $param3, $param4';
+ var c = C(1, 'two');
+ // Breakpoint: bp
+ print('hello world');
+ }
+ ''';
+
+ setUpAll(() async {
+ await driver.initSource(setup, source,
+ experiments: {'named-arguments-anywhere': true});
+ });
+
+ tearDownAll(() async {
+ await driver.cleanupTest();
+ });
+
+ test('in top level method', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
+ expectedResult: '1, two, 3, four');
+ });
+ test('in local method', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
+ expectedResult: '1, two, 3, four');
+ });
+ test('in class constructor', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'C(param3: 3, 1, param4: "four", "two").toString()',
+ expectedResult: '1, two, 3, four');
+ });
+ test('in class static method', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'C.staticMethod(param3: 3, 1, param4: "four", "two")',
+ expectedResult: '1, two, 3, four');
+ });
+ test('in class instance method', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'c.instanceMethod(param3: 3, 1, param4: "four", "two")',
+ expectedResult: '1, two, 3, four');
+ });
+ });
+
+ group('Super parameters', () {
+ var source = r'''
+ class S {
+ final int i;
+ final String? s;
+ final double d;
+
+ S(this.i, [this.s]): d = 3.14;
+
+ S.named(this.i, {this.d = 3.14}): s = 'default';
+ }
+
+ class C extends S {
+ final int i1;
+ final int i2;
+
+ C(this.i1, super.i, this.i2, [super.s]);
+
+ C.named({super.d}): i1 = 10, i2 = 30, super.named(20);
+ }
+
+ main() {
+ var c = C(1, 2, 3, 'bar');
+ var c2 = C.named(d: 2.71);
+ // Breakpoint: bp
+ print('hello world');
+ }
+ ''';
+
+ setUpAll(() async {
+ await driver
+ .initSource(setup, source, experiments: {'super-parameters': true});
+ });
+
+ tearDownAll(() async {
+ await driver.cleanupTest();
+ });
+
+ test('in constructor mixed with regular parameters', () async {
+ await driver.check(
+ breakpointId: 'bp', expression: 'c.i1', expectedResult: '1');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c.i', expectedResult: '2');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c.i2', expectedResult: '3');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c.s', expectedResult: 'bar');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c.d', expectedResult: '3.14');
+ });
+ test('in named constructor mixed with regular parameters', () async {
+ await driver.check(
+ breakpointId: 'bp', expression: 'c2.i1', expectedResult: '10');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c2.i', expectedResult: '20');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c2.i2', expectedResult: '30');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c2.s', expectedResult: 'default');
+ await driver.check(
+ breakpointId: 'bp', expression: 'c2.d', expectedResult: '2.71');
+ });
+ });
+
+ group('Enhanced enums', () {
+ var source = r'''
+ enum E<T> with M {
+ id_int<int>(0),
+ id_bool<bool>(true),
+ id_string<String>('hello world', n: 13);
+
+ final T field;
+ final num n;
+ static const constStaticField = id_string;
+
+ const E(T arg0, {num? n}) : this.field = arg0, this.n = n ?? 42;
+
+ T get fieldGetter => field;
+ num instanceMethod() => n;
+ }
+
+ enum E2 with M {
+ v1, v2, id_string;
+ int get index => 10;
+ }
+
+ mixin M on Enum {
+ int mixinMethod() => index * 100;
+ }
+
+ main() {
+ var e = E.id_string;
+ // Breakpoint: bp
+ print('hello world');
+ }
+ ''';
+
+ setUpAll(() async {
+ await driver
+ .initSource(setup, source, experiments: {'enhanced-enums': true});
+ });
+
+ tearDownAll(() async {
+ await driver.cleanupTest();
+ });
+
+ test('evaluate to the correct string', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'E.id_string.toString()',
+ expectedResult: 'E.id_string');
+ });
+ test('evaluate to the correct index', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'E.id_string.index',
+ expectedResult: '2');
+ });
+ test('compare properly against themselves', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'e == E.id_string && E.id_string == E.id_string',
+ expectedResult: 'true');
+ });
+ test('compare properly against other enums', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'e != E2.id_string && E.id_string != E2.id_string',
+ expectedResult: 'true');
+ });
+ test('with instance methods', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'E.id_bool.instanceMethod()',
+ expectedResult: '42');
+ });
+ test('with instance methods from local instance', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'e.instanceMethod()',
+ expectedResult: '13');
+ });
+ test('with getters', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'E.id_int.fieldGetter',
+ expectedResult: '0');
+ });
+ test('with getters from local instance', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'e.fieldGetter',
+ expectedResult: 'hello world');
+ });
+ test('with mixin calls', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'E.id_string.mixinMethod()',
+ expectedResult: '200');
+ });
+ test('with mixin calls through overridden indices', () async {
+ await driver.check(
+ breakpointId: 'bp',
+ expression: 'E2.v2.mixinMethod()',
+ expectedResult: '1000');
+ });
+ });
+}
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
index 7db89ce..a1916b8 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
@@ -61,10 +61,7 @@
}
''';
-/// Shared tests that require a language version greater than 2.12.
-///
-/// Tests that exercise language features introduced with 2.12 or after are
-/// valid here.
+/// Shared tests that require a language version >=2.12.0 <2.17.0.
// TODO(nshahan) Merge with [runAgnosticSharedTests] after we no longer need to
// test support for evaluation in legacy (pre-null safety) code.
void runNullSafeSharedTests(SetupCompilerOptions setup, TestDriver driver) {
@@ -316,82 +313,6 @@
});
});
- group('Named arguments anywhere', () {
- var source = r'''
- String topLevelMethod(int param1, String param2,
- {int param3 = -1, String param4 = 'default'}) =>
- '$param1, $param2, $param3, $param4';
-
- class C {
- int param1;
- String param2;
- int param3;
- String param4;
- C(this.param1, this.param2,
- {this.param3 = -1, this.param4 = 'default'});
-
- static String staticMethod(int param1, String param2,
- {int param3 = -1, String param4 = 'default'}) =>
- '$param1, $param2, $param3, $param4';
-
- String instanceMethod(int param1, String param2,
- {int param3 = -1, String param4 = 'default'}) =>
- '$param1, $param2, $param3, $param4';
-
- String toString() => '$param1, $param2, $param3, $param4';
- }
-
- main() {
- String localMethod(int param1, String param2,
- {int param3 = -1, String param4 = 'default'}) =>
- '$param1, $param2, $param3, $param4';
- var c = C(1, 'two');
- // Breakpoint: bp
- print('hello world');
- }
- ''';
-
- setUpAll(() async {
- await driver.initSource(setup, source,
- experiments: {'named-arguments-anywhere': true});
- });
-
- tearDownAll(() async {
- await driver.cleanupTest();
- });
-
- test('in top level method', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
- expectedResult: '1, two, 3, four');
- });
- test('in local method', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
- expectedResult: '1, two, 3, four');
- });
- test('in class constructor', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'C(param3: 3, 1, param4: "four", "two").toString()',
- expectedResult: '1, two, 3, four');
- });
- test('in class static method', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'C.staticMethod(param3: 3, 1, param4: "four", "two")',
- expectedResult: '1, two, 3, four');
- });
- test('in class instance method', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'c.instanceMethod(param3: 3, 1, param4: "four", "two")',
- expectedResult: '1, two, 3, four');
- });
- });
-
group('Enums', () {
var source = r'''
enum E {id1, id2, id3}
@@ -436,110 +357,6 @@
expectedResult: 'true');
});
});
-
- group('Enhanced enums', () {
- var source = r'''
- enum E<T> with M {
- id_int<int>(0),
- id_bool<bool>(true),
- id_string<String>('hello world', n: 13);
-
- final T field;
- final num n;
- static const constStaticField = id_string;
-
- const E(T arg0, {num? n}) : this.field = arg0, this.n = n ?? 42;
-
- T get fieldGetter => field;
- num instanceMethod() => n;
- }
-
- enum E2 with M {
- v1, v2, id_string;
- int get index => 10;
- }
-
- mixin M on Enum {
- int mixinMethod() => index * 100;
- }
-
- main() {
- var e = E.id_string;
- // Breakpoint: bp
- print('hello world');
- }
- ''';
-
- setUpAll(() async {
- await driver
- .initSource(setup, source, experiments: {'enhanced-enums': true});
- });
-
- tearDownAll(() async {
- await driver.cleanupTest();
- });
-
- test('evaluate to the correct string', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'E.id_string.toString()',
- expectedResult: 'E.id_string');
- });
- test('evaluate to the correct index', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'E.id_string.index',
- expectedResult: '2');
- });
- test('compare properly against themselves', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'e == E.id_string && E.id_string == E.id_string',
- expectedResult: 'true');
- });
- test('compare properly against other enums', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'e != E2.id_string && E.id_string != E2.id_string',
- expectedResult: 'true');
- });
- test('with instance methods', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'E.id_bool.instanceMethod()',
- expectedResult: '42');
- });
- test('with instance methods from local instance', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'e.instanceMethod()',
- expectedResult: '13');
- });
- test('with getters', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'E.id_int.fieldGetter',
- expectedResult: '0');
- });
- test('with getters from local instance', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'e.fieldGetter',
- expectedResult: 'hello world');
- });
- test('with mixin calls', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'E.id_string.mixinMethod()',
- expectedResult: '200');
- });
- test('with mixin calls through overridden indices', () async {
- await driver.check(
- breakpointId: 'bp',
- expression: 'E2.v2.mixinMethod()',
- expectedResult: '1000');
- });
- });
}
/// Shared tests that are valid in legacy (before 2.12) and are agnostic to
diff --git a/pkg/nnbd_migration/lib/migration_cli.dart b/pkg/nnbd_migration/lib/migration_cli.dart
index 2a30f55..a6bd912 100644
--- a/pkg/nnbd_migration/lib/migration_cli.dart
+++ b/pkg/nnbd_migration/lib/migration_cli.dart
@@ -896,7 +896,7 @@
logger.stdout(ansi.emphasized('Re-analyzing project...'));
_dartFixListener!.reset();
- _fixCodeProcessor!.prepareToRerun();
+ await _fixCodeProcessor!.prepareToRerun();
var analysisResult = await _fixCodeProcessor!.runFirstPhase();
if (analysisResult.hasErrors && !options.ignoreErrors!) {
_logErrors(analysisResult);
@@ -997,10 +997,11 @@
bool get isPreviewServerRunning => _task?.isPreviewServerRunning ?? false;
- void prepareToRerun() {
+ Future<void> prepareToRerun() async {
var driver = context.driver;
pathsToProcess = _migrationCli.computePathsToProcess(context);
pathsToProcess.forEach(driver.changeFile);
+ await driver.applyPendingFileChanges();
}
/// Call the supplied [process] function to process each compilation unit.
diff --git a/pkg/nnbd_migration/test/abstract_context.dart b/pkg/nnbd_migration/test/abstract_context.dart
index 2965a8a..b096215 100644
--- a/pkg/nnbd_migration/test/abstract_context.dart
+++ b/pkg/nnbd_migration/test/abstract_context.dart
@@ -126,10 +126,7 @@
Source addSource(String path, String content, [Uri? uri]) {
File file = newFile(path, content: content);
- Source source = file.createSource(uri);
- driver!.addFile(file.path);
- driver!.changeFile(file.path);
- return source;
+ return file.createSource(uri);
}
/// Add the test_core package and a library with URI,
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 2aef55f..185ab05 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -47,6 +47,7 @@
dartdev/test/commands/analyze_test: Slow, Pass
dartdev/test/commands/help_test: Slow, Pass
dartdev/test/smoke/*: Slow, Pass
+dds/test/devtools_server/devtools_server_test: Slow, Pass
dev_compiler/test/modular/*: Slow, Pass
dev_compiler/test/options/*: Skip # test needs fixes
dev_compiler/test/sourcemap/*: SkipByDesign # Skip sourcemap tests
@@ -145,6 +146,7 @@
vm_snapshot_analysis/test/*: SkipByDesign # Only meant to run on vm
[ $system == windows ]
+dds/test/devtools_server/devtools_server_test: Skip # Issue 48528
front_end/test/fasta/bootstrap_test: Skip # Issue 31902
front_end/test/fasta/strong_test: Pass, Slow, Timeout
front_end/test/fasta/text_serialization_test: Pass, Slow, Timeout
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 6f14afa..52ee6d0 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## 8.2.1
+- Remove `example/vm_service_asserts.dart'
+
## 8.2.0
- Update to version `3.56` of the spec.
- Added optional `line` and `column` properties to `SourceLocation`.
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
deleted file mode 100644
index 2955af8..0000000
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ /dev/null
@@ -1,1209 +0,0 @@
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// This is a generated file.
-
-/// A library for asserting correct responses from the VM Service.
-
-import 'package:vm_service/vm_service.dart' as vms;
-
-dynamic assertNotNull(dynamic obj) {
- if (obj == null) throw 'assert failed';
- return obj;
-}
-
-bool assertBool(bool obj) {
- return obj;
-}
-
-int assertInt(int obj) {
- return obj;
-}
-
-double assertDouble(double obj) {
- return obj;
-}
-
-dynamic assertDynamic(dynamic obj) {
- assertNotNull(obj);
- return obj;
-}
-
-List<dynamic> assertListOfDynamic(List<dynamic> list) {
- return list;
-}
-
-List<int> assertListOfInt(List<int> list) {
- for (int elem in list) {
- assertInt(elem);
- }
- return list;
-}
-
-List<String> assertListOfString(List<String> list) {
- for (String elem in list) {
- assertString(elem);
- }
- return list;
-}
-
-List<vms.IsolateFlag> assertListOfIsolateFlag(List<vms.IsolateFlag> list) {
- for (vms.IsolateFlag elem in list) {
- assertIsolateFlag(elem);
- }
- return list;
-}
-
-String assertString(String obj) {
- if (obj.isEmpty) throw 'expected non-zero length string';
- return obj;
-}
-
-vms.Success assertSuccess(vms.Success obj) {
- if (obj.type != 'Success') throw 'expected Success';
- return obj;
-}
-
-/// Assert PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
-/// PauseException, Resume, BreakpointAdded, BreakpointResolved,
-/// BreakpointRemoved, and Inspect events.
-vms.Event assertDebugEvent(vms.Event event) {
- assertEvent(event);
- if (event.kind == vms.EventKind.kPauseBreakpoint ||
- event.kind == vms.EventKind.kBreakpointAdded ||
- event.kind == vms.EventKind.kBreakpointRemoved ||
- event.kind == vms.EventKind.kBreakpointResolved) {
- assertBreakpoint(event.breakpoint!);
- }
- if (event.kind == vms.EventKind.kPauseBreakpoint) {
- for (vms.Breakpoint elem in event.pauseBreakpoints!) {
- assertBreakpoint(elem);
- }
- }
- if (event.kind == vms.EventKind.kPauseBreakpoint ||
- event.kind == vms.EventKind.kPauseInterrupted ||
- event.kind == vms.EventKind.kPauseException ||
- event.kind == vms.EventKind.kResume) {
- // For PauseInterrupted events, there will be no top frame if the isolate is
- // idle (waiting in the message loop).
- // For the Resume event, the top frame is provided at all times except for
- // the initial resume event that is delivered when an isolate begins
- // execution.
- if (event.topFrame != null ||
- (event.kind != vms.EventKind.kPauseInterrupted &&
- event.kind != vms.EventKind.kResume)) {
- assertFrame(event.topFrame!);
- }
- }
- if (event.kind == vms.EventKind.kPauseException) {
- assertInstanceRef(event.exception!);
- }
- if (event.kind == vms.EventKind.kPauseBreakpoint ||
- event.kind == vms.EventKind.kPauseInterrupted) {
- assertBool(event.atAsyncSuspension!);
- }
- if (event.kind == vms.EventKind.kInspect) {
- assertInstanceRef(event.inspectee!);
- }
- return event;
-}
-
-/// Assert IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate,
-/// and ServiceExtensionAdded events.
-vms.Event assertIsolateEvent(vms.Event event) {
- assertEvent(event);
- if (event.kind == vms.EventKind.kServiceExtensionAdded) {
- assertString(event.extensionRPC!);
- }
- return event;
-}
-
-String assertCodeKind(String obj) {
- if (obj == "Collected") return obj;
- if (obj == "Dart") return obj;
- if (obj == "Native") return obj;
- if (obj == "Stub") return obj;
- if (obj == "Tag") return obj;
- throw "invalid CodeKind: $obj";
-}
-
-String assertErrorKind(String obj) {
- if (obj == "InternalError") return obj;
- if (obj == "LanguageError") return obj;
- if (obj == "TerminationError") return obj;
- if (obj == "UnhandledException") return obj;
- throw "invalid ErrorKind: $obj";
-}
-
-String assertEventKind(String obj) {
- if (obj == "BreakpointAdded") return obj;
- if (obj == "BreakpointRemoved") return obj;
- if (obj == "BreakpointResolved") return obj;
- if (obj == "BreakpointUpdated") return obj;
- if (obj == "CpuSamples") return obj;
- if (obj == "Extension") return obj;
- if (obj == "GC") return obj;
- if (obj == "Inspect") return obj;
- if (obj == "IsolateExit") return obj;
- if (obj == "IsolateReload") return obj;
- if (obj == "IsolateRunnable") return obj;
- if (obj == "IsolateStart") return obj;
- if (obj == "IsolateUpdate") return obj;
- if (obj == "Logging") return obj;
- if (obj == "None") return obj;
- if (obj == "PauseBreakpoint") return obj;
- if (obj == "PauseException") return obj;
- if (obj == "PauseExit") return obj;
- if (obj == "PauseInterrupted") return obj;
- if (obj == "PausePostRequest") return obj;
- if (obj == "PauseStart") return obj;
- if (obj == "Resume") return obj;
- if (obj == "ServiceExtensionAdded") return obj;
- if (obj == "ServiceRegistered") return obj;
- if (obj == "ServiceUnregistered") return obj;
- if (obj == "TimelineEvents") return obj;
- if (obj == "TimelineStreamSubscriptionsUpdate") return obj;
- if (obj == "UserTagChanged") return obj;
- if (obj == "VMFlagUpdate") return obj;
- if (obj == "VMUpdate") return obj;
- if (obj == "WriteEvent") return obj;
- throw "invalid EventKind: $obj";
-}
-
-String assertInstanceKind(String obj) {
- if (obj == "Bool") return obj;
- if (obj == "BoundedType") return obj;
- if (obj == "Closure") return obj;
- if (obj == "Double") return obj;
- if (obj == "Float32List") return obj;
- if (obj == "Float32x4") return obj;
- if (obj == "Float32x4List") return obj;
- if (obj == "Float64List") return obj;
- if (obj == "Float64x2") return obj;
- if (obj == "Float64x2List") return obj;
- if (obj == "FunctionType") return obj;
- if (obj == "Int") return obj;
- if (obj == "Int16List") return obj;
- if (obj == "Int32List") return obj;
- if (obj == "Int32x4") return obj;
- if (obj == "Int32x4List") return obj;
- if (obj == "Int64List") return obj;
- if (obj == "Int8List") return obj;
- if (obj == "List") return obj;
- if (obj == "Map") return obj;
- if (obj == "MirrorReference") return obj;
- if (obj == "Null") return obj;
- if (obj == "PlainInstance") return obj;
- if (obj == "ReceivePort") return obj;
- if (obj == "RegExp") return obj;
- if (obj == "StackTrace") return obj;
- if (obj == "String") return obj;
- if (obj == "Type") return obj;
- if (obj == "TypeParameter") return obj;
- if (obj == "TypeRef") return obj;
- if (obj == "Uint16List") return obj;
- if (obj == "Uint32List") return obj;
- if (obj == "Uint64List") return obj;
- if (obj == "Uint8ClampedList") return obj;
- if (obj == "Uint8List") return obj;
- if (obj == "WeakProperty") return obj;
- throw "invalid InstanceKind: $obj";
-}
-
-String assertSentinelKind(String obj) {
- if (obj == "BeingInitialized") return obj;
- if (obj == "Collected") return obj;
- if (obj == "Expired") return obj;
- if (obj == "Free") return obj;
- if (obj == "NotInitialized") return obj;
- if (obj == "OptimizedOut") return obj;
- throw "invalid SentinelKind: $obj";
-}
-
-String assertFrameKind(String obj) {
- if (obj == "AsyncActivation") return obj;
- if (obj == "AsyncCausal") return obj;
- if (obj == "AsyncSuspensionMarker") return obj;
- if (obj == "Regular") return obj;
- throw "invalid FrameKind: $obj";
-}
-
-String assertSourceReportKind(String obj) {
- if (obj == "BranchCoverage") return obj;
- if (obj == "Coverage") return obj;
- if (obj == "PossibleBreakpoints") return obj;
- throw "invalid SourceReportKind: $obj";
-}
-
-String assertExceptionPauseMode(String obj) {
- if (obj == "All") return obj;
- if (obj == "None") return obj;
- if (obj == "Unhandled") return obj;
- throw "invalid ExceptionPauseMode: $obj";
-}
-
-String assertStepOption(String obj) {
- if (obj == "Into") return obj;
- if (obj == "Out") return obj;
- if (obj == "Over") return obj;
- if (obj == "OverAsyncSuspension") return obj;
- if (obj == "Rewind") return obj;
- throw "invalid StepOption: $obj";
-}
-
-vms.AllocationProfile assertAllocationProfile(vms.AllocationProfile obj) {
- assertNotNull(obj);
- assertListOfClassHeapStats(obj.members!);
- assertMemoryUsage(obj.memoryUsage!);
- return obj;
-}
-
-vms.BoundField assertBoundField(vms.BoundField obj) {
- assertNotNull(obj);
- assertFieldRef(obj.decl!);
- if (obj.value is vms.InstanceRef) {
- assertInstanceRef(obj.value!);
- } else if (obj.value is vms.Sentinel) {
- assertSentinel(obj.value!);
- } else {
- throw "Unexpected value: ${obj.value}";
- }
- return obj;
-}
-
-vms.BoundVariable assertBoundVariable(vms.BoundVariable obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- if (obj.value is vms.InstanceRef) {
- assertInstanceRef(obj.value!);
- } else if (obj.value is vms.TypeArgumentsRef) {
- assertTypeArgumentsRef(obj.value!);
- } else if (obj.value is vms.Sentinel) {
- assertSentinel(obj.value!);
- } else {
- throw "Unexpected value: ${obj.value}";
- }
- assertInt(obj.declarationTokenPos!);
- assertInt(obj.scopeStartTokenPos!);
- assertInt(obj.scopeEndTokenPos!);
- return obj;
-}
-
-List<vms.BoundVariable> assertListOfBoundVariable(
- List<vms.BoundVariable> list) {
- for (vms.BoundVariable elem in list) {
- assertBoundVariable(elem);
- }
- return list;
-}
-
-vms.Breakpoint assertBreakpoint(vms.Breakpoint obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInt(obj.breakpointNumber!);
- assertBool(obj.enabled!);
- assertBool(obj.resolved!);
- if (obj.location is vms.SourceLocation) {
- assertSourceLocation(obj.location!);
- } else if (obj.location is vms.UnresolvedSourceLocation) {
- assertUnresolvedSourceLocation(obj.location!);
- } else {
- throw "Unexpected value: ${obj.location}";
- }
- return obj;
-}
-
-List<vms.Breakpoint> assertListOfBreakpoint(List<vms.Breakpoint> list) {
- for (vms.Breakpoint elem in list) {
- assertBreakpoint(elem);
- }
- return list;
-}
-
-vms.ClassRef assertClassRef(vms.ClassRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertLibraryRef(obj.library!);
- return obj;
-}
-
-List<vms.ClassRef> assertListOfClassRef(List<vms.ClassRef> list) {
- for (vms.ClassRef elem in list) {
- assertClassRef(elem);
- }
- return list;
-}
-
-vms.Class assertClass(vms.Class obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertLibraryRef(obj.library!);
- assertBool(obj.isAbstract!);
- assertBool(obj.isConst!);
- assertBool(obj.traceAllocations!);
- assertListOfInstanceRef(obj.interfaces!);
- assertListOfFieldRef(obj.fields!);
- assertListOfFuncRef(obj.functions!);
- assertListOfClassRef(obj.subclasses!);
- return obj;
-}
-
-vms.ClassHeapStats assertClassHeapStats(vms.ClassHeapStats obj) {
- assertNotNull(obj);
- assertClassRef(obj.classRef!);
- assertInt(obj.accumulatedSize!);
- assertInt(obj.bytesCurrent!);
- assertInt(obj.instancesAccumulated!);
- assertInt(obj.instancesCurrent!);
- return obj;
-}
-
-List<vms.ClassHeapStats> assertListOfClassHeapStats(
- List<vms.ClassHeapStats> list) {
- for (vms.ClassHeapStats elem in list) {
- assertClassHeapStats(elem);
- }
- return list;
-}
-
-vms.ClassList assertClassList(vms.ClassList obj) {
- assertNotNull(obj);
- assertListOfClassRef(obj.classes!);
- return obj;
-}
-
-vms.CodeRef assertCodeRef(vms.CodeRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertCodeKind(obj.kind!);
- return obj;
-}
-
-List<vms.CodeRef> assertListOfCodeRef(List<vms.CodeRef> list) {
- for (vms.CodeRef elem in list) {
- assertCodeRef(elem);
- }
- return list;
-}
-
-vms.Code assertCode(vms.Code obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertCodeKind(obj.kind!);
- return obj;
-}
-
-vms.ContextRef assertContextRef(vms.ContextRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInt(obj.length!);
- return obj;
-}
-
-List<vms.ContextRef> assertListOfContextRef(List<vms.ContextRef> list) {
- for (vms.ContextRef elem in list) {
- assertContextRef(elem);
- }
- return list;
-}
-
-vms.Context assertContext(vms.Context obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInt(obj.length!);
- assertListOfContextElement(obj.variables!);
- return obj;
-}
-
-vms.ContextElement assertContextElement(vms.ContextElement obj) {
- assertNotNull(obj);
- if (obj.value is vms.InstanceRef) {
- assertInstanceRef(obj.value!);
- } else if (obj.value is vms.Sentinel) {
- assertSentinel(obj.value!);
- } else {
- throw "Unexpected value: ${obj.value}";
- }
- return obj;
-}
-
-List<vms.ContextElement> assertListOfContextElement(
- List<vms.ContextElement> list) {
- for (vms.ContextElement elem in list) {
- assertContextElement(elem);
- }
- return list;
-}
-
-vms.CpuSamples assertCpuSamples(vms.CpuSamples obj) {
- assertNotNull(obj);
- assertInt(obj.samplePeriod!);
- assertInt(obj.maxStackDepth!);
- assertInt(obj.sampleCount!);
- assertInt(obj.timeSpan!);
- assertInt(obj.timeOriginMicros!);
- assertInt(obj.timeExtentMicros!);
- assertInt(obj.pid!);
- assertListOfProfileFunction(obj.functions!);
- assertListOfCpuSample(obj.samples!);
- return obj;
-}
-
-vms.CpuSamplesEvent assertCpuSamplesEvent(vms.CpuSamplesEvent obj) {
- assertNotNull(obj);
- assertInt(obj.samplePeriod!);
- assertInt(obj.maxStackDepth!);
- assertInt(obj.sampleCount!);
- assertInt(obj.timeSpan!);
- assertInt(obj.timeOriginMicros!);
- assertInt(obj.timeExtentMicros!);
- assertInt(obj.pid!);
- assertListOfDynamic(obj.functions!);
- assertListOfCpuSample(obj.samples!);
- return obj;
-}
-
-vms.CpuSample assertCpuSample(vms.CpuSample obj) {
- assertNotNull(obj);
- assertInt(obj.tid!);
- assertInt(obj.timestamp!);
- assertListOfInt(obj.stack!);
- return obj;
-}
-
-List<vms.CpuSample> assertListOfCpuSample(List<vms.CpuSample> list) {
- for (vms.CpuSample elem in list) {
- assertCpuSample(elem);
- }
- return list;
-}
-
-vms.ErrorRef assertErrorRef(vms.ErrorRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertErrorKind(obj.kind!);
- assertString(obj.message!);
- return obj;
-}
-
-List<vms.ErrorRef> assertListOfErrorRef(List<vms.ErrorRef> list) {
- for (vms.ErrorRef elem in list) {
- assertErrorRef(elem);
- }
- return list;
-}
-
-vms.Error assertError(vms.Error obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertErrorKind(obj.kind!);
- assertString(obj.message!);
- return obj;
-}
-
-vms.Event assertEvent(vms.Event obj) {
- assertNotNull(obj);
- assertEventKind(obj.kind!);
- assertInt(obj.timestamp!);
- return obj;
-}
-
-vms.ExtensionData assertExtensionData(vms.ExtensionData obj) {
- assertNotNull(obj);
- return obj;
-}
-
-vms.FieldRef assertFieldRef(vms.FieldRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertObjRef(obj.owner!);
- assertInstanceRef(obj.declaredType!);
- assertBool(obj.isConst!);
- assertBool(obj.isFinal!);
- assertBool(obj.isStatic!);
- return obj;
-}
-
-List<vms.FieldRef> assertListOfFieldRef(List<vms.FieldRef> list) {
- for (vms.FieldRef elem in list) {
- assertFieldRef(elem);
- }
- return list;
-}
-
-vms.Field assertField(vms.Field obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertObjRef(obj.owner!);
- assertInstanceRef(obj.declaredType!);
- assertBool(obj.isConst!);
- assertBool(obj.isFinal!);
- assertBool(obj.isStatic!);
- return obj;
-}
-
-vms.Flag assertFlag(vms.Flag obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- assertString(obj.comment!);
- assertBool(obj.modified!);
- return obj;
-}
-
-List<vms.Flag> assertListOfFlag(List<vms.Flag> list) {
- for (vms.Flag elem in list) {
- assertFlag(elem);
- }
- return list;
-}
-
-vms.FlagList assertFlagList(vms.FlagList obj) {
- assertNotNull(obj);
- assertListOfFlag(obj.flags!);
- return obj;
-}
-
-vms.Frame assertFrame(vms.Frame obj) {
- assertNotNull(obj);
- assertInt(obj.index!);
- return obj;
-}
-
-List<vms.Frame> assertListOfFrame(List<vms.Frame> list) {
- for (vms.Frame elem in list) {
- assertFrame(elem);
- }
- return list;
-}
-
-vms.FuncRef assertFuncRef(vms.FuncRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- if (obj.owner is vms.LibraryRef) {
- assertLibraryRef(obj.owner!);
- } else if (obj.owner is vms.ClassRef) {
- assertClassRef(obj.owner!);
- } else if (obj.owner is vms.FuncRef) {
- assertFuncRef(obj.owner!);
- } else {
- throw "Unexpected value: ${obj.owner}";
- }
- assertBool(obj.isStatic!);
- assertBool(obj.isConst!);
- assertBool(obj.implicit!);
- return obj;
-}
-
-List<vms.FuncRef> assertListOfFuncRef(List<vms.FuncRef> list) {
- for (vms.FuncRef elem in list) {
- assertFuncRef(elem);
- }
- return list;
-}
-
-vms.Func assertFunc(vms.Func obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- if (obj.owner is vms.LibraryRef) {
- assertLibraryRef(obj.owner!);
- } else if (obj.owner is vms.ClassRef) {
- assertClassRef(obj.owner!);
- } else if (obj.owner is vms.FuncRef) {
- assertFuncRef(obj.owner!);
- } else {
- throw "Unexpected value: ${obj.owner}";
- }
- assertBool(obj.isStatic!);
- assertBool(obj.isConst!);
- assertBool(obj.implicit!);
- assertInstanceRef(obj.signature!);
- return obj;
-}
-
-vms.InstanceRef assertInstanceRef(vms.InstanceRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInstanceKind(obj.kind!);
- assertInt(obj.identityHashCode!);
- assertClassRef(obj.classRef!);
- return obj;
-}
-
-List<vms.InstanceRef> assertListOfInstanceRef(List<vms.InstanceRef> list) {
- for (vms.InstanceRef elem in list) {
- assertInstanceRef(elem);
- }
- return list;
-}
-
-vms.Instance assertInstance(vms.Instance obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInstanceKind(obj.kind!);
- assertInt(obj.identityHashCode!);
- assertClassRef(obj.classRef!);
- return obj;
-}
-
-vms.IsolateRef assertIsolateRef(vms.IsolateRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.number!);
- assertString(obj.name!);
- assertBool(obj.isSystemIsolate!);
- return obj;
-}
-
-List<vms.IsolateRef> assertListOfIsolateRef(List<vms.IsolateRef> list) {
- for (vms.IsolateRef elem in list) {
- assertIsolateRef(elem);
- }
- return list;
-}
-
-vms.Isolate assertIsolate(vms.Isolate obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.number!);
- assertString(obj.name!);
- assertBool(obj.isSystemIsolate!);
- assertListOfIsolateFlag(obj.isolateFlags!);
- assertInt(obj.startTime!);
- assertBool(obj.runnable!);
- assertInt(obj.livePorts!);
- assertBool(obj.pauseOnExit!);
- assertEvent(obj.pauseEvent!);
- assertListOfLibraryRef(obj.libraries!);
- assertListOfBreakpoint(obj.breakpoints!);
- assertExceptionPauseMode(obj.exceptionPauseMode!);
- return obj;
-}
-
-vms.IsolateFlag assertIsolateFlag(vms.IsolateFlag obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- assertString(obj.valueAsString!);
- return obj;
-}
-
-vms.IsolateGroupRef assertIsolateGroupRef(vms.IsolateGroupRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.number!);
- assertString(obj.name!);
- assertBool(obj.isSystemIsolateGroup!);
- return obj;
-}
-
-List<vms.IsolateGroupRef> assertListOfIsolateGroupRef(
- List<vms.IsolateGroupRef> list) {
- for (vms.IsolateGroupRef elem in list) {
- assertIsolateGroupRef(elem);
- }
- return list;
-}
-
-vms.IsolateGroup assertIsolateGroup(vms.IsolateGroup obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.number!);
- assertString(obj.name!);
- assertBool(obj.isSystemIsolateGroup!);
- assertListOfIsolateRef(obj.isolates!);
- return obj;
-}
-
-vms.InboundReferences assertInboundReferences(vms.InboundReferences obj) {
- assertNotNull(obj);
- assertListOfInboundReference(obj.references!);
- return obj;
-}
-
-vms.InboundReference assertInboundReference(vms.InboundReference obj) {
- assertNotNull(obj);
- assertObjRef(obj.source!);
- return obj;
-}
-
-List<vms.InboundReference> assertListOfInboundReference(
- List<vms.InboundReference> list) {
- for (vms.InboundReference elem in list) {
- assertInboundReference(elem);
- }
- return list;
-}
-
-vms.InstanceSet assertInstanceSet(vms.InstanceSet obj) {
- assertNotNull(obj);
- assertInt(obj.totalCount!);
- assertListOfObjRef(obj.instances!);
- return obj;
-}
-
-vms.LibraryRef assertLibraryRef(vms.LibraryRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertString(obj.uri!);
- return obj;
-}
-
-List<vms.LibraryRef> assertListOfLibraryRef(List<vms.LibraryRef> list) {
- for (vms.LibraryRef elem in list) {
- assertLibraryRef(elem);
- }
- return list;
-}
-
-vms.Library assertLibrary(vms.Library obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertString(obj.uri!);
- assertBool(obj.debuggable!);
- assertListOfLibraryDependency(obj.dependencies!);
- assertListOfScriptRef(obj.scripts!);
- assertListOfFieldRef(obj.variables!);
- assertListOfFuncRef(obj.functions!);
- assertListOfClassRef(obj.classes!);
- return obj;
-}
-
-vms.LibraryDependency assertLibraryDependency(vms.LibraryDependency obj) {
- assertNotNull(obj);
- assertBool(obj.isImport!);
- assertBool(obj.isDeferred!);
- assertString(obj.prefix!);
- assertLibraryRef(obj.target!);
- return obj;
-}
-
-List<vms.LibraryDependency> assertListOfLibraryDependency(
- List<vms.LibraryDependency> list) {
- for (vms.LibraryDependency elem in list) {
- assertLibraryDependency(elem);
- }
- return list;
-}
-
-vms.LogRecord assertLogRecord(vms.LogRecord obj) {
- assertNotNull(obj);
- assertInstanceRef(obj.message!);
- assertInt(obj.time!);
- assertInt(obj.level!);
- assertInt(obj.sequenceNumber!);
- assertInstanceRef(obj.loggerName!);
- assertInstanceRef(obj.zone!);
- assertInstanceRef(obj.error!);
- assertInstanceRef(obj.stackTrace!);
- return obj;
-}
-
-vms.MapAssociation assertMapAssociation(vms.MapAssociation obj) {
- assertNotNull(obj);
- if (obj.key is vms.InstanceRef) {
- assertInstanceRef(obj.key!);
- } else if (obj.key is vms.Sentinel) {
- assertSentinel(obj.key!);
- } else {
- throw "Unexpected value: ${obj.key}";
- }
- if (obj.value is vms.InstanceRef) {
- assertInstanceRef(obj.value!);
- } else if (obj.value is vms.Sentinel) {
- assertSentinel(obj.value!);
- } else {
- throw "Unexpected value: ${obj.value}";
- }
- return obj;
-}
-
-vms.MemoryUsage assertMemoryUsage(vms.MemoryUsage obj) {
- assertNotNull(obj);
- assertInt(obj.externalUsage!);
- assertInt(obj.heapCapacity!);
- assertInt(obj.heapUsage!);
- return obj;
-}
-
-vms.Message assertMessage(vms.Message obj) {
- assertNotNull(obj);
- assertInt(obj.index!);
- assertString(obj.name!);
- assertString(obj.messageObjectId!);
- assertInt(obj.size!);
- return obj;
-}
-
-List<vms.Message> assertListOfMessage(List<vms.Message> list) {
- for (vms.Message elem in list) {
- assertMessage(elem);
- }
- return list;
-}
-
-vms.NativeFunction assertNativeFunction(vms.NativeFunction obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- return obj;
-}
-
-vms.NullValRef assertNullValRef(vms.NullValRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInstanceKind(obj.kind!);
- assertInt(obj.identityHashCode!);
- assertClassRef(obj.classRef!);
- assertString(obj.valueAsString!);
- return obj;
-}
-
-List<vms.NullValRef> assertListOfNullValRef(List<vms.NullValRef> list) {
- for (vms.NullValRef elem in list) {
- assertNullValRef(elem);
- }
- return list;
-}
-
-vms.NullVal assertNullVal(vms.NullVal obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertInstanceKind(obj.kind!);
- assertInt(obj.identityHashCode!);
- assertClassRef(obj.classRef!);
- assertString(obj.valueAsString!);
- return obj;
-}
-
-vms.ObjRef assertObjRef(vms.ObjRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- return obj;
-}
-
-List<vms.ObjRef> assertListOfObjRef(List<vms.ObjRef> list) {
- for (vms.ObjRef elem in list) {
- assertObjRef(elem);
- }
- return list;
-}
-
-vms.Obj assertObj(vms.Obj obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- return obj;
-}
-
-vms.Parameter assertParameter(vms.Parameter obj) {
- assertNotNull(obj);
- assertInstanceRef(obj.parameterType!);
- assertBool(obj.fixed!);
- return obj;
-}
-
-vms.PortList assertPortList(vms.PortList obj) {
- assertNotNull(obj);
- assertListOfInstanceRef(obj.ports!);
- return obj;
-}
-
-vms.ProfileFunction assertProfileFunction(vms.ProfileFunction obj) {
- assertNotNull(obj);
- assertString(obj.kind!);
- assertInt(obj.inclusiveTicks!);
- assertInt(obj.exclusiveTicks!);
- assertString(obj.resolvedUrl!);
- assertDynamic(obj.function!);
- return obj;
-}
-
-List<vms.ProfileFunction> assertListOfProfileFunction(
- List<vms.ProfileFunction> list) {
- for (vms.ProfileFunction elem in list) {
- assertProfileFunction(elem);
- }
- return list;
-}
-
-vms.ProtocolList assertProtocolList(vms.ProtocolList obj) {
- assertNotNull(obj);
- assertListOfProtocol(obj.protocols!);
- return obj;
-}
-
-vms.Protocol assertProtocol(vms.Protocol obj) {
- assertNotNull(obj);
- assertString(obj.protocolName!);
- assertInt(obj.major!);
- assertInt(obj.minor!);
- return obj;
-}
-
-List<vms.Protocol> assertListOfProtocol(List<vms.Protocol> list) {
- for (vms.Protocol elem in list) {
- assertProtocol(elem);
- }
- return list;
-}
-
-vms.ProcessMemoryUsage assertProcessMemoryUsage(vms.ProcessMemoryUsage obj) {
- assertNotNull(obj);
- assertProcessMemoryItem(obj.root!);
- return obj;
-}
-
-vms.ProcessMemoryItem assertProcessMemoryItem(vms.ProcessMemoryItem obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- assertString(obj.description!);
- assertInt(obj.size!);
- assertListOfProcessMemoryItem(obj.children!);
- return obj;
-}
-
-List<vms.ProcessMemoryItem> assertListOfProcessMemoryItem(
- List<vms.ProcessMemoryItem> list) {
- for (vms.ProcessMemoryItem elem in list) {
- assertProcessMemoryItem(elem);
- }
- return list;
-}
-
-vms.ReloadReport assertReloadReport(vms.ReloadReport obj) {
- assertNotNull(obj);
- assertBool(obj.success!);
- return obj;
-}
-
-vms.RetainingObject assertRetainingObject(vms.RetainingObject obj) {
- assertNotNull(obj);
- assertObjRef(obj.value!);
- return obj;
-}
-
-List<vms.RetainingObject> assertListOfRetainingObject(
- List<vms.RetainingObject> list) {
- for (vms.RetainingObject elem in list) {
- assertRetainingObject(elem);
- }
- return list;
-}
-
-vms.RetainingPath assertRetainingPath(vms.RetainingPath obj) {
- assertNotNull(obj);
- assertInt(obj.length!);
- assertString(obj.gcRootType!);
- assertListOfRetainingObject(obj.elements!);
- return obj;
-}
-
-vms.Response assertResponse(vms.Response obj) {
- assertNotNull(obj);
- return obj;
-}
-
-vms.Sentinel assertSentinel(vms.Sentinel obj) {
- assertNotNull(obj);
- assertSentinelKind(obj.kind!);
- assertString(obj.valueAsString!);
- return obj;
-}
-
-vms.ScriptRef assertScriptRef(vms.ScriptRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.uri!);
- return obj;
-}
-
-List<vms.ScriptRef> assertListOfScriptRef(List<vms.ScriptRef> list) {
- for (vms.ScriptRef elem in list) {
- assertScriptRef(elem);
- }
- return list;
-}
-
-vms.Script assertScript(vms.Script obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.uri!);
- assertLibraryRef(obj.library!);
- return obj;
-}
-
-vms.ScriptList assertScriptList(vms.ScriptList obj) {
- assertNotNull(obj);
- assertListOfScriptRef(obj.scripts!);
- return obj;
-}
-
-vms.SourceLocation assertSourceLocation(vms.SourceLocation obj) {
- assertNotNull(obj);
- assertScriptRef(obj.script!);
- assertInt(obj.tokenPos!);
- return obj;
-}
-
-vms.SourceReport assertSourceReport(vms.SourceReport obj) {
- assertNotNull(obj);
- assertListOfSourceReportRange(obj.ranges!);
- assertListOfScriptRef(obj.scripts!);
- return obj;
-}
-
-vms.SourceReportCoverage assertSourceReportCoverage(
- vms.SourceReportCoverage obj) {
- assertNotNull(obj);
- assertListOfInt(obj.hits!);
- assertListOfInt(obj.misses!);
- return obj;
-}
-
-vms.SourceReportRange assertSourceReportRange(vms.SourceReportRange obj) {
- assertNotNull(obj);
- assertInt(obj.scriptIndex!);
- assertInt(obj.startPos!);
- assertInt(obj.endPos!);
- assertBool(obj.compiled!);
- return obj;
-}
-
-List<vms.SourceReportRange> assertListOfSourceReportRange(
- List<vms.SourceReportRange> list) {
- for (vms.SourceReportRange elem in list) {
- assertSourceReportRange(elem);
- }
- return list;
-}
-
-vms.Stack assertStack(vms.Stack obj) {
- assertNotNull(obj);
- assertListOfFrame(obj.frames!);
- assertListOfMessage(obj.messages!);
- assertBool(obj.truncated!);
- return obj;
-}
-
-vms.Timeline assertTimeline(vms.Timeline obj) {
- assertNotNull(obj);
- assertListOfTimelineEvent(obj.traceEvents!);
- assertInt(obj.timeOriginMicros!);
- assertInt(obj.timeExtentMicros!);
- return obj;
-}
-
-vms.TimelineEvent assertTimelineEvent(vms.TimelineEvent obj) {
- assertNotNull(obj);
- return obj;
-}
-
-List<vms.TimelineEvent> assertListOfTimelineEvent(
- List<vms.TimelineEvent> list) {
- for (vms.TimelineEvent elem in list) {
- assertTimelineEvent(elem);
- }
- return list;
-}
-
-vms.TimelineFlags assertTimelineFlags(vms.TimelineFlags obj) {
- assertNotNull(obj);
- assertString(obj.recorderName!);
- assertListOfString(obj.availableStreams!);
- assertListOfString(obj.recordedStreams!);
- return obj;
-}
-
-vms.Timestamp assertTimestamp(vms.Timestamp obj) {
- assertNotNull(obj);
- assertInt(obj.timestamp!);
- return obj;
-}
-
-vms.TypeArgumentsRef assertTypeArgumentsRef(vms.TypeArgumentsRef obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- return obj;
-}
-
-List<vms.TypeArgumentsRef> assertListOfTypeArgumentsRef(
- List<vms.TypeArgumentsRef> list) {
- for (vms.TypeArgumentsRef elem in list) {
- assertTypeArgumentsRef(elem);
- }
- return list;
-}
-
-vms.TypeArguments assertTypeArguments(vms.TypeArguments obj) {
- assertNotNull(obj);
- assertString(obj.id!);
- assertString(obj.name!);
- assertListOfInstanceRef(obj.types!);
- return obj;
-}
-
-vms.TypeParameters assertTypeParameters(vms.TypeParameters obj) {
- assertNotNull(obj);
- assertListOfString(obj.names!);
- assertTypeArgumentsRef(obj.bounds!);
- assertTypeArgumentsRef(obj.defaults!);
- return obj;
-}
-
-vms.UnresolvedSourceLocation assertUnresolvedSourceLocation(
- vms.UnresolvedSourceLocation obj) {
- assertNotNull(obj);
- return obj;
-}
-
-vms.UriList assertUriList(vms.UriList obj) {
- assertNotNull(obj);
- assertListOfDynamic(obj.uris!);
- return obj;
-}
-
-vms.Version assertVersion(vms.Version obj) {
- assertNotNull(obj);
- assertInt(obj.major!);
- assertInt(obj.minor!);
- return obj;
-}
-
-vms.VMRef assertVMRef(vms.VMRef obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- return obj;
-}
-
-List<vms.VMRef> assertListOfVMRef(List<vms.VMRef> list) {
- for (vms.VMRef elem in list) {
- assertVMRef(elem);
- }
- return list;
-}
-
-vms.VM assertVM(vms.VM obj) {
- assertNotNull(obj);
- assertString(obj.name!);
- assertInt(obj.architectureBits!);
- assertString(obj.hostCPU!);
- assertString(obj.operatingSystem!);
- assertString(obj.targetCPU!);
- assertString(obj.version!);
- assertInt(obj.pid!);
- assertInt(obj.startTime!);
- assertListOfIsolateRef(obj.isolates!);
- assertListOfIsolateGroupRef(obj.isolateGroups!);
- assertListOfIsolateRef(obj.systemIsolates!);
- assertListOfIsolateGroupRef(obj.systemIsolateGroups!);
- return obj;
-}
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 8240e95..19d91ff 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
A library to communicate with a service implementing the Dart VM
service protocol.
-version: 8.2.0
+version: 8.2.1
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
diff --git a/pkg/vm_service/tool/dart/generate_dart.dart b/pkg/vm_service/tool/dart/generate_dart.dart
index d59b1e7..c907438 100644
--- a/pkg/vm_service/tool/dart/generate_dart.dart
+++ b/pkg/vm_service/tool/dart/generate_dart.dart
@@ -901,162 +901,6 @@
types.where((t) => !t!.skip).forEach((t) => t!.generate(gen));
}
- void generateAsserts(DartGenerator gen) {
- gen.out(r'''
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// This is a generated file.
-
-/// A library for asserting correct responses from the VM Service.
-
-import 'package:vm_service/vm_service.dart' as vms;
-
-dynamic assertNotNull(dynamic obj) {
- if (obj == null) throw 'assert failed';
- return obj;
-}
-
-bool assertBool(bool obj) {
- return obj;
-}
-
-int assertInt(int obj) {
- return obj;
-}
-
-double assertDouble(double obj) {
- return obj;
-}
-
-dynamic assertDynamic(dynamic obj) {
- assertNotNull(obj);
- return obj;
-}
-
-List<dynamic> assertListOfDynamic(List<dynamic> list) {
- return list;
-}
-
-List<int> assertListOfInt(List<int> list) {
- for (int elem in list) {
- assertInt(elem);
- }
- return list;
-}
-
-List<String> assertListOfString(List<String> list) {
- for (String elem in list) {
- assertString(elem);
- }
- return list;
-}
-
-List<vms.IsolateFlag> assertListOfIsolateFlag(List<vms.IsolateFlag> list) {
- for (vms.IsolateFlag elem in list) {
- assertIsolateFlag(elem);
- }
- return list;
-}
-
-String assertString(String obj) {
- if (obj.isEmpty) throw 'expected non-zero length string';
- return obj;
-}
-
-vms.Success assertSuccess(vms.Success obj) {
- if (obj.type != 'Success') throw 'expected Success';
- return obj;
-}
-
-/// Assert PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
-/// PauseException, Resume, BreakpointAdded, BreakpointResolved,
-/// BreakpointRemoved, and Inspect events.
-vms.Event assertDebugEvent(vms.Event event) {
- assertEvent(event);
- if (event.kind == vms.EventKind.kPauseBreakpoint ||
- event.kind == vms.EventKind.kBreakpointAdded ||
- event.kind == vms.EventKind.kBreakpointRemoved ||
- event.kind == vms.EventKind.kBreakpointResolved) {
- assertBreakpoint(event.breakpoint!);
- }
- if (event.kind == vms.EventKind.kPauseBreakpoint) {
- for (vms.Breakpoint elem in event.pauseBreakpoints!) {
- assertBreakpoint(elem);
- }
- }
- if (event.kind == vms.EventKind.kPauseBreakpoint ||
- event.kind == vms.EventKind.kPauseInterrupted ||
- event.kind == vms.EventKind.kPauseException ||
- event.kind == vms.EventKind.kResume) {
- // For PauseInterrupted events, there will be no top frame if the isolate is
- // idle (waiting in the message loop).
- // For the Resume event, the top frame is provided at all times except for
- // the initial resume event that is delivered when an isolate begins
- // execution.
- if (event.topFrame != null ||
- (event.kind != vms.EventKind.kPauseInterrupted &&
- event.kind != vms.EventKind.kResume)) {
- assertFrame(event.topFrame!);
- }
- }
- if (event.kind == vms.EventKind.kPauseException) {
- assertInstanceRef(event.exception!);
- }
- if (event.kind == vms.EventKind.kPauseBreakpoint ||
- event.kind == vms.EventKind.kPauseInterrupted) {
- assertBool(event.atAsyncSuspension!);
- }
- if (event.kind == vms.EventKind.kInspect) {
- assertInstanceRef(event.inspectee!);
- }
- return event;
-}
-
-/// Assert IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate,
-/// and ServiceExtensionAdded events.
-vms.Event assertIsolateEvent(vms.Event event) {
- assertEvent(event);
- if (event.kind == vms.EventKind.kServiceExtensionAdded) {
- assertString(event.extensionRPC!);
- }
- return event;
-}
-
-''');
- for (Enum e in enums) {
- e.generateAssert(gen);
- }
- for (Type? type in types) {
- if (type!.name == 'Success') continue;
- type.generateAssert(gen);
- if (type.name!.endsWith('Ref') ||
- [
- 'BoundVariable',
- 'Breakpoint',
- 'ClassHeapStats',
- 'CodeRegion',
- 'ContextElement',
- 'CpuSample',
- 'Flag',
- 'Frame',
- 'InboundReference',
- 'LibraryDependency',
- 'Message',
- 'ProcessMemoryItem',
- 'ProfileFunction',
- 'ProcessMemoryItem',
- 'Protocol',
- 'RetainingObject',
- 'SourceReportRange',
- 'TimelineEvent',
- ].contains(type.name)) {
- type.generateListAssert(gen);
- }
- }
- }
-
void setDefaultValue(String typeName, String fieldName, String defaultValue) {
types
.firstWhere((t) => t!.name == typeName)!
diff --git a/pkg/vm_service/tool/generate.dart b/pkg/vm_service/tool/generate.dart
index e91c0f3..2f5f65e 100644
--- a/pkg/vm_service/tool/generate.dart
+++ b/pkg/vm_service/tool/generate.dart
@@ -20,11 +20,12 @@
String appDirPath = dirname(Platform.script.toFilePath());
// Parse service.md into a model.
- var file = File(
- normalize(join(appDirPath, '../../../runtime/vm/service/service.md')));
- var document = Document();
- StringBuffer buf = StringBuffer(file.readAsStringSync());
- var nodes = document.parseLines(buf.toString().split('\n'));
+ final file = File(
+ normalize(join(appDirPath, '../../../runtime/vm/service/service.md')),
+ );
+ final document = Document();
+ final buf = StringBuffer(file.readAsStringSync());
+ final nodes = document.parseLines(buf.toString().split('\n'));
print('Parsed ${file.path}.');
print('Service protocol version ${ApiParseUtil.parseVersionString(nodes)}.');
@@ -33,7 +34,6 @@
await _generateDart(appDirPath, nodes);
await _generateJava(appDirPath, nodes);
- await _generateAsserts(appDirPath, nodes);
}
Future _generateDart(String appDirPath, List<Node> nodes) async {
@@ -98,34 +98,6 @@
print('Wrote Java to $srcDirPath.');
}
-Future _generateAsserts(String appDirPath, List<Node> nodes) async {
- var outDirPath = normalize(join(appDirPath, '..', 'example'));
- var outDir = Directory(outDirPath);
- if (!outDir.existsSync()) outDir.createSync(recursive: true);
- var outputFile = File(join(outDirPath, 'vm_service_assert.dart'));
- var generator = dart.DartGenerator();
- dart.api = dart.Api();
- dart.api.parse(nodes);
- dart.api.generateAsserts(generator);
- outputFile.writeAsStringSync(generator.toString());
- ProcessResult result = Process.runSync('dart', ['format', outDirPath]);
- if (result.exitCode != 0) {
- print('dart format: ${result.stdout}\n${result.stderr}');
- throw result.exitCode;
- }
-
- if (_stampPubspecVersion) {
- // Update the pubspec file.
- Version version = ApiParseUtil.parseVersionSemVer(nodes);
- _stampPubspec(version);
-
- // Validate that the changelog contains an entry for the current version.
- _checkUpdateChangelog(version);
- }
-
- print('Wrote Dart to ${outputFile.path}.');
-}
-
// Push the major and minor versions into the pubspec.
void _stampPubspec(Version version) {
final String pattern = 'version: ';
diff --git a/runtime/bin/builtin_impl_sources.gni b/runtime/bin/builtin_impl_sources.gni
index 470f0b5..ff6a6c3 100644
--- a/runtime/bin/builtin_impl_sources.gni
+++ b/runtime/bin/builtin_impl_sources.gni
@@ -72,4 +72,6 @@
"file_test.cc",
"hashmap_test.cc",
"priority_heap_test.cc",
+ "snapshot_utils_test.cc",
+ "test_utils.cc",
]
diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc
index 894884a..92ca3a4 100644
--- a/runtime/bin/file_macos.cc
+++ b/runtime/bin/file_macos.cc
@@ -88,6 +88,7 @@
switch (type) {
case kReadOnly:
prot = PROT_READ;
+ map_flags |= MAP_RESILIENT_CODESIGN;
break;
case kReadExecute:
// Try to allocate near the VM's binary.
diff --git a/runtime/bin/file_test.cc b/runtime/bin/file_test.cc
index 60adf31..91e53e4 100644
--- a/runtime/bin/file_test.cc
+++ b/runtime/bin/file_test.cc
@@ -2,28 +2,18 @@
// 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 "bin/file.h"
#include "bin/dartutils.h"
#include "bin/directory.h"
-#include "bin/file.h"
+#include "bin/test_utils.h"
#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/unit_test.h"
namespace dart {
-// Helper method to be able to run the test from the runtime
-// directory, or the top directory.
-static const char* GetFileName(const char* name) {
- if (bin::File::Exists(NULL, name)) {
- return name;
- } else {
- static const int kRuntimeLength = strlen("runtime/");
- return name + kRuntimeLength;
- }
-}
-
TEST_CASE(Read) {
- const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
bin::File* file = bin::File::Open(NULL, kFilename, bin::File::kRead);
EXPECT(file != NULL);
char buffer[16];
@@ -36,7 +26,7 @@
}
TEST_CASE(OpenUri_RelativeFilename) {
- const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
strlen(kFilename) * 3 + 1));
char* t = encoded;
@@ -63,7 +53,8 @@
}
TEST_CASE(OpenUri_AbsoluteFilename) {
- const char* kRelativeFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kRelativeFilename =
+ bin::test::GetFileName("runtime/bin/file_test.cc");
const char* kFilename = bin::File::GetCanonicalPath(NULL, kRelativeFilename);
EXPECT_NOTNULL(kFilename);
char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
@@ -100,7 +91,8 @@
}
TEST_CASE(OpenUri_ValidUri) {
- const char* kRelativeFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kRelativeFilename =
+ bin::test::GetFileName("runtime/bin/file_test.cc");
const char* kAbsoluteFilename = bin::File::GetCanonicalPath(NULL,
kRelativeFilename);
EXPECT_NOTNULL(kAbsoluteFilename);
@@ -132,7 +124,8 @@
}
TEST_CASE(OpenUri_UriWithSpaces) {
- const char* kRelativeFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kRelativeFilename =
+ bin::test::GetFileName("runtime/bin/file_test.cc");
const char* strSystemTemp = bin::Directory::SystemTemp(NULL);
EXPECT_NOTNULL(strSystemTemp);
const char* kTempDir = Concat(strSystemTemp, "/foo bar");
@@ -175,7 +168,7 @@
}
TEST_CASE(OpenUri_InvalidUriPercentEncoding) {
- const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
strlen(kFilename) * 3 + 1));
char* t = encoded;
@@ -195,7 +188,7 @@
}
TEST_CASE(OpenUri_TruncatedUriPercentEncoding) {
- const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+ const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
strlen(kFilename) * 3 + 1));
char* t = encoded;
@@ -216,7 +209,7 @@
TEST_CASE(FileLength) {
const char* kFilename =
- GetFileName("runtime/tests/vm/data/fixed_length_file");
+ bin::test::GetFileName("runtime/tests/vm/data/fixed_length_file");
bin::File* file = bin::File::Open(NULL, kFilename, bin::File::kRead);
EXPECT(file != NULL);
EXPECT_EQ(42, file->Length());
@@ -226,7 +219,7 @@
TEST_CASE(FilePosition) {
char buf[42];
const char* kFilename =
- GetFileName("runtime/tests/vm/data/fixed_length_file");
+ bin::test::GetFileName("runtime/tests/vm/data/fixed_length_file");
bin::File* file = bin::File::Open(NULL, kFilename, bin::File::kRead);
EXPECT(file != NULL);
EXPECT(file->ReadFully(buf, 12));
diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc
index 4650da4..11b0ead 100644
--- a/runtime/bin/snapshot_utils.cc
+++ b/runtime/bin/snapshot_utils.cc
@@ -13,6 +13,9 @@
#include "bin/file.h"
#include "bin/platform.h"
#include "include/dart_api.h"
+#if defined(DART_TARGET_OS_MACOS)
+#include <platform/mach_o.h>
+#endif
#include "platform/utils.h"
#define LOG_SECTION_BOUNDARIES false
@@ -23,6 +26,11 @@
static const int64_t kAppSnapshotHeaderSize = 5 * kInt64Size;
static const int64_t kAppSnapshotPageSize = 16 * KB;
+static const char kMachOAppSnapshotSegmentName[] __attribute__((unused)) =
+ "__CUSTOM";
+static const char kMachOAppSnapshotSectionName[] __attribute__((unused)) =
+ "__dart_app_snap";
+
class MappedAppSnapshot : public AppSnapshot {
public:
MappedAppSnapshot(MappedMemory* vm_snapshot_data,
@@ -233,8 +241,121 @@
return nullptr;
}
+#if defined(DART_TARGET_OS_MACOS)
+AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
+ const char* container_path) {
+ File* file = File::Open(NULL, container_path, File::kRead);
+ if (file == nullptr) {
+ return nullptr;
+ }
+ RefCntReleaseScope<File> rs(file);
+
+ // Ensure file is actually MachO-formatted.
+ if (!IsMachOFormattedBinary(container_path)) {
+ Syslog::PrintErr(
+ "Attempted load target was not formatted as expected: "
+ "expected Mach-O binary.\n");
+ return nullptr;
+ }
+
+ // Parse the first 4bytes and extract the magic number.
+ uint32_t magic;
+ file->SetPosition(0);
+ file->Read(&magic, sizeof(uint32_t));
+
+ const bool is64Bit =
+ magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64;
+ const bool isByteSwapped =
+ magic == mach_o::MH_CIGAM || magic == mach_o::MH_CIGAM_64;
+
+ if (isByteSwapped) {
+ Syslog::PrintErr(
+ "Dart snapshot contained an unexpected binary file layout. "
+ "Expected non-byte swapped header but found a byte-swapped header.\n");
+ return nullptr;
+ }
+
+ file->SetPosition(0);
+
+ // Read in the Mach-O header, which will contain information about all of the
+ // segments in the binary.
+ //
+ // From the header we determine where our special segment is located. This
+ // segment must be named according to the convention captured by
+ // kMachOAppSnapshotSegmentType and kMachOAppSnapshotSegmentName.
+ if (!is64Bit) {
+ Syslog::PrintErr(
+ "Dart snapshot compiled with 32bit architecture. "
+ "Currently only 64bit architectures are supported.\n");
+ return nullptr;
+ } else {
+ mach_o::mach_header_64 header;
+ file->Read(&header, sizeof(header));
+
+ for (uint32_t i = 0; i < header.ncmds; ++i) {
+ mach_o::load_command command;
+ file->Read(&command, sizeof(mach_o::load_command));
+
+ file->SetPosition(file->Position() - sizeof(command));
+ if (command.cmd != mach_o::LC_SEGMENT &&
+ command.cmd != mach_o::LC_SEGMENT_64) {
+ file->SetPosition(file->Position() + command.cmdsize);
+ continue;
+ }
+
+ mach_o::segment_command_64 segment;
+ file->Read(&segment, sizeof(segment));
+
+ for (uint32_t j = 0; j < segment.nsects; ++j) {
+ mach_o::section_64 section;
+ file->Read(§ion, sizeof(section));
+
+ if (segment.cmd == mach_o::LC_SEGMENT_64 &&
+ strcmp(section.segname, kMachOAppSnapshotSegmentName) == 0 &&
+ strcmp(section.sectname, kMachOAppSnapshotSectionName) == 0) {
+ // We have to do the loading "by-hand" because we need to set the
+ // snapshot length to a specific length instead of the "rest of the
+ // file", which is the assumption that TryReadAppSnapshotElf makes.
+ const char* error = nullptr;
+ const uint8_t* vm_data_buffer = nullptr;
+ const uint8_t* vm_instructions_buffer = nullptr;
+ const uint8_t* isolate_data_buffer = nullptr;
+ const uint8_t* isolate_instructions_buffer = nullptr;
+
+ std::unique_ptr<uint8_t[]> snapshot(new uint8_t[section.size]);
+ file->SetPosition(section.offset);
+ file->Read(snapshot.get(), sizeof(uint8_t) * section.size);
+
+ Dart_LoadedElf* handle = Dart_LoadELF_Memory(
+ snapshot.get(), section.size, &error, &vm_data_buffer,
+ &vm_instructions_buffer, &isolate_data_buffer,
+ &isolate_instructions_buffer);
+
+ if (handle == nullptr) {
+ Syslog::PrintErr("Loading failed: %s\n", error);
+ return nullptr;
+ }
+
+ return new ElfAppSnapshot(handle, vm_data_buffer,
+ vm_instructions_buffer, isolate_data_buffer,
+ isolate_instructions_buffer);
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+#endif // defined(DART_TARGET_OS_MACOS)
+
AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf(
const char* container_path) {
+#if defined(DART_TARGET_OS_MACOS)
+ if (IsMachOFormattedBinary(container_path)) {
+ return TryReadAppendedAppSnapshotElfFromMachO(container_path);
+ }
+#endif
+
File* file = File::Open(NULL, container_path, File::kRead);
if (file == nullptr) {
return nullptr;
@@ -329,6 +450,29 @@
#endif // defined(DART_PRECOMPILED_RUNTIME)
+#if defined(DART_TARGET_OS_MACOS)
+bool Snapshot::IsMachOFormattedBinary(const char* filename) {
+ File* file = File::Open(NULL, filename, File::kRead);
+ if (file == nullptr) {
+ return false;
+ }
+ RefCntReleaseScope<File> rs(file);
+
+ // Ensure the file is long enough to even contain the magic bytes.
+ if (file->Length() < 4) {
+ return false;
+ }
+
+ // Parse the first 4bytes and check the magic numbers.
+ uint32_t magic;
+ file->SetPosition(0);
+ file->Read(&magic, sizeof(uint32_t));
+
+ return magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64 ||
+ magic == mach_o::MH_MAGIC || magic == mach_o::MH_CIGAM;
+}
+#endif // defined(DART_TARGET_OS_MACOS)
+
AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri,
bool force_load_elf_from_memory,
bool decode_uri) {
diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h
index ed96fb1..f26f7b2 100644
--- a/runtime/bin/snapshot_utils.h
+++ b/runtime/bin/snapshot_utils.h
@@ -38,6 +38,10 @@
// an ELF binary). May report false negatives.
static bool IsAOTSnapshot(const char* snapshot_filename);
+#if defined(DART_TARGET_OS_MACOS)
+ static bool IsMachOFormattedBinary(const char* container_path);
+#endif
+
static AppSnapshot* TryReadAppendedAppSnapshotElf(const char* container_path);
static AppSnapshot* TryReadAppSnapshot(
const char* script_uri,
@@ -54,6 +58,11 @@
intptr_t isolate_instructions_size);
private:
+#if defined(DART_TARGET_OS_MACOS)
+ static AppSnapshot* TryReadAppendedAppSnapshotElfFromMachO(
+ const char* container_path);
+#endif
+
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(Snapshot);
};
diff --git a/runtime/bin/snapshot_utils_test.cc b/runtime/bin/snapshot_utils_test.cc
new file mode 100644
index 0000000..7b726fd
--- /dev/null
+++ b/runtime/bin/snapshot_utils_test.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "bin/snapshot_utils.h"
+#include "bin/file.h"
+#include "bin/test_utils.h"
+#include "platform/assert.h"
+#include "platform/globals.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+#if defined(DART_TARGET_OS_MACOS)
+TEST_CASE(CanDetectMachOFiles) {
+ const char* kMachO32BitLittleEndianFilename =
+ bin::test::GetFileName("runtime/tests/vm/data/macho_32bit_little_endian");
+ const char* kMachO64BitLittleEndianFilename =
+ bin::test::GetFileName("runtime/tests/vm/data/macho_64bit_little_endian");
+ const char* kMachO32BitBigEndianFilename =
+ bin::test::GetFileName("runtime/tests/vm/data/macho_32bit_big_endian");
+ const char* kMachO64BitBigEndianFilename =
+ bin::test::GetFileName("runtime/tests/vm/data/macho_64bit_big_endian");
+
+ EXPECT(
+ bin::Snapshot::IsMachOFormattedBinary(kMachO32BitLittleEndianFilename));
+ EXPECT(
+ bin::Snapshot::IsMachOFormattedBinary(kMachO64BitLittleEndianFilename));
+ EXPECT(bin::Snapshot::IsMachOFormattedBinary(kMachO32BitBigEndianFilename));
+ EXPECT(bin::Snapshot::IsMachOFormattedBinary(kMachO64BitBigEndianFilename));
+
+ const char* kFilename =
+ bin::test::GetFileName("runtime/bin/snapshot_utils_test.cc");
+ EXPECT(!bin::Snapshot::IsMachOFormattedBinary(kFilename));
+}
+#endif
+
+} // namespace dart
diff --git a/runtime/bin/test_utils.cc b/runtime/bin/test_utils.cc
new file mode 100644
index 0000000..66d7fd9
--- /dev/null
+++ b/runtime/bin/test_utils.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "bin/test_utils.h"
+#include "bin/file.h"
+
+namespace dart {
+namespace bin {
+namespace test {
+
+const char* GetFileName(const char* name) {
+ if (bin::File::Exists(NULL, name)) {
+ return name;
+ } else {
+ static const int kRuntimeLength = strlen("runtime/");
+ return name + kRuntimeLength;
+ }
+}
+
+} // namespace test
+} // namespace bin
+} // namespace dart
diff --git a/runtime/bin/test_utils.h b/runtime/bin/test_utils.h
new file mode 100644
index 0000000..9291819
--- /dev/null
+++ b/runtime/bin/test_utils.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_BIN_TEST_UTILS_H_
+#define RUNTIME_BIN_TEST_UTILS_H_
+
+namespace dart {
+namespace bin {
+namespace test {
+
+// Helper method to be able to run the test from the runtime
+// directory, or the top directory.
+const char* GetFileName(const char* name);
+
+} // namespace test
+} // namespace bin
+} // namespace dart
+
+#endif // RUNTIME_BIN_TEST_UTILS_H_
diff --git a/runtime/platform/mach_o.h b/runtime/platform/mach_o.h
new file mode 100644
index 0000000..d5bf913
--- /dev/null
+++ b/runtime/platform/mach_o.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_PLATFORM_MACH_O_H_
+#define RUNTIME_PLATFORM_MACH_O_H_
+
+#include <platform/globals.h>
+
+namespace dart {
+
+namespace mach_o {
+
+#pragma pack(push, 1)
+
+typedef int cpu_type_t;
+typedef int cpu_subtype_t;
+typedef int vm_prot_t;
+
+struct mach_header {
+ uint32_t magic;
+ cpu_type_t cputype;
+ cpu_subtype_t cpusubtype;
+ uint32_t filetype;
+ uint32_t ncmds;
+ uint32_t sizeofcmds;
+ uint32_t flags;
+};
+
+static const uint32_t MH_MAGIC = 0xfeedface;
+static const uint32_t MH_CIGAM = 0xcefaedfe;
+
+struct mach_header_64 {
+ uint32_t magic;
+ cpu_type_t cputype;
+ cpu_subtype_t cpusubtype;
+ uint32_t filetype;
+ uint32_t ncmds;
+ uint32_t sizeofcmds;
+ uint32_t flags;
+ uint32_t reserved;
+};
+
+static const uint32_t MH_MAGIC_64 = 0xfeedfacf;
+static const uint32_t MH_CIGAM_64 = 0xcffaedfe;
+
+struct load_command {
+ uint32_t cmd;
+ uint32_t cmdsize;
+};
+
+static const uint32_t LC_SEGMENT = 0x1;
+static const uint32_t LC_SEGMENT_64 = 0x19;
+
+struct section {
+ char sectname[16];
+ char segname[16];
+ uint32_t addr;
+ uint32_t size;
+ uint32_t offset;
+ uint32_t align;
+ uint32_t reloff;
+ uint32_t nreloc;
+ uint32_t flags;
+ uint32_t reserved1;
+ uint32_t reserved2;
+};
+
+struct section_64 {
+ char sectname[16];
+ char segname[16];
+ uint64_t addr;
+ uint64_t size;
+ uint32_t offset;
+ uint32_t align;
+ uint32_t reloff;
+ uint32_t nreloc;
+ uint32_t flags;
+ uint32_t reserved1;
+ uint32_t reserved2;
+ uint32_t reserved3;
+};
+
+struct segment_command_64 {
+ uint32_t cmd;
+ uint32_t cmdsize;
+ char segname[16];
+ uint64_t vmaddr;
+ uint64_t vmsize;
+ uint64_t fileoff;
+ uint64_t filesize;
+ vm_prot_t maxprot;
+ vm_prot_t initprot;
+ uint32_t nsects;
+ uint32_t flags;
+};
+
+#pragma pack(pop)
+
+} // namespace mach_o
+
+} // namespace dart
+
+#endif // RUNTIME_PLATFORM_MACH_O_H_
diff --git a/runtime/tests/vm/data/macho_32bit_big_endian b/runtime/tests/vm/data/macho_32bit_big_endian
new file mode 100644
index 0000000..c271589
--- /dev/null
+++ b/runtime/tests/vm/data/macho_32bit_big_endian
Binary files differ
diff --git a/runtime/tests/vm/data/macho_32bit_little_endian b/runtime/tests/vm/data/macho_32bit_little_endian
new file mode 100644
index 0000000..7c7003f
--- /dev/null
+++ b/runtime/tests/vm/data/macho_32bit_little_endian
Binary files differ
diff --git a/runtime/tests/vm/data/macho_64bit_big_endian b/runtime/tests/vm/data/macho_64bit_big_endian
new file mode 100644
index 0000000..95b3475
--- /dev/null
+++ b/runtime/tests/vm/data/macho_64bit_big_endian
Binary files differ
diff --git a/runtime/tests/vm/data/macho_64bit_little_endian b/runtime/tests/vm/data/macho_64bit_little_endian
new file mode 100644
index 0000000..fcd8a04c
--- /dev/null
+++ b/runtime/tests/vm/data/macho_64bit_little_endian
Binary files differ
diff --git a/tools/VERSION b/tools/VERSION
index cafe4e0..d7d6b9c 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 194
+PRERELEASE 195
PRERELEASE_PATCH 0
\ No newline at end of file