Version 2.15.0-147.0.dev
Merge commit '1d8a9d54cd324d77b3c1a8e76782ae903dbf6e65' into 'dev'
diff --git a/DEPS b/DEPS
index c8e7820..f30b473 100644
--- a/DEPS
+++ b/DEPS
@@ -173,7 +173,7 @@
"WebCore_rev": "bcb10901266c884e7b3740abc597ab95373ab55c",
"webdev_rev": "832b096c0c24798d3df46faa7b7661fe930573c2",
"webkit_inspection_protocol_rev": "dd6fb5d8b536e19cedb384d0bbf1f5631923f1e8",
- "yaml_edit_rev": "ffcbbc22884f590663ec451f48fd8388ba7c49f4",
+ "yaml_edit_rev": "df1452bfe1653286277a1a8f34dddf3e4fbedd9e",
"yaml_rev": "2af44871f684c89e973a96e39026b8b88dda1987",
"zlib_rev": "bf44340d1b6be1af8950bbdf664fec0cf5a831cc",
"crashpad_rev": "bf327d8ceb6a669607b0dbab5a83a275d03f99ed",
diff --git a/pkg/compiler/lib/src/kernel/transformations/clone_mixin_methods_with_super.dart b/pkg/compiler/lib/src/kernel/transformations/clone_mixin_methods_with_super.dart
index 80799f3..abefe2d 100644
--- a/pkg/compiler/lib/src/kernel/transformations/clone_mixin_methods_with_super.dart
+++ b/pkg/compiler/lib/src/kernel/transformations/clone_mixin_methods_with_super.dart
@@ -61,7 +61,7 @@
Procedure? existingGetter = existingNonSetters[field.name];
Procedure? existingSetter = existingSetters[field.name];
cls.addField(cloneVisitor.cloneField(
- field, existingGetter?.reference, existingSetter?.reference));
+ field, null, existingGetter?.reference, existingSetter?.reference));
if (existingGetter != null) {
cls.procedures.remove(existingGetter);
}
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 1343db2..a66f36d 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -67,22 +67,17 @@
/// Pattern for a trailing semicolon.
final _trailingSemicolonPattern = RegExp(r';$');
-/// An implementation of [LaunchRequestArguments] that includes all fields used
+/// An implementation of [AttachRequestArguments] that includes all fields used
/// by the base Dart debug adapter.
///
/// This class represents the data passed from the client editor to the debug
-/// adapter in launchRequest, which is a request to start debugging an
+/// adapter in attachRequest, which is a request to start debugging an
/// application.
///
-/// Specialised adapters (such as Flutter) will likely extend this class with
-/// their own additional fields.
+/// Specialised adapters (such as Flutter) will likely have their own versions
+/// of this class.
class DartAttachRequestArguments extends DartCommonLaunchAttachRequestArguments
implements AttachRequestArguments {
- /// Optional data from the previous, restarted session.
- /// The data is sent as the 'restart' attribute of the 'terminated' event.
- /// The client should leave the data intact.
- final Object? restart;
-
/// The VM Service URI to attach to.
///
/// Either this or [vmServiceInfoFile] must be supplied.
@@ -94,9 +89,9 @@
final String? vmServiceInfoFile;
DartAttachRequestArguments({
- this.restart,
this.vmServiceUri,
this.vmServiceInfoFile,
+ Object? restart,
String? name,
String? cwd,
List<String>? additionalProjectPaths,
@@ -108,6 +103,7 @@
}) : super(
name: name,
cwd: cwd,
+ restart: restart,
additionalProjectPaths: additionalProjectPaths,
debugSdkLibraries: debugSdkLibraries,
debugExternalPackageLibraries: debugExternalPackageLibraries,
@@ -117,15 +113,13 @@
);
DartAttachRequestArguments.fromMap(Map<String, Object?> obj)
- : restart = obj['restart'],
- vmServiceUri = obj['vmServiceUri'] as String?,
+ : vmServiceUri = obj['vmServiceUri'] as String?,
vmServiceInfoFile = obj['vmServiceInfoFile'] as String?,
super.fromMap(obj);
@override
Map<String, Object?> toJson() => {
...super.toJson(),
- if (restart != null) 'restart': restart,
if (vmServiceUri != null) 'vmServiceUri': vmServiceUri,
if (vmServiceInfoFile != null) 'vmServiceInfoFile': vmServiceInfoFile,
};
@@ -137,6 +131,11 @@
/// A common base for [DartLaunchRequestArguments] and
/// [DartAttachRequestArguments] for fields that are common to both.
class DartCommonLaunchAttachRequestArguments extends RequestArguments {
+ /// Optional data from the previous, restarted session.
+ /// The data is sent as the 'restart' attribute of the 'terminated' event.
+ /// The client should leave the data intact.
+ final Object? restart;
+
final String? name;
final String? cwd;
@@ -187,6 +186,7 @@
final bool? sendLogsToClient;
DartCommonLaunchAttachRequestArguments({
+ required this.restart,
required this.name,
required this.cwd,
required this.additionalProjectPaths,
@@ -198,7 +198,8 @@
});
DartCommonLaunchAttachRequestArguments.fromMap(Map<String, Object?> obj)
- : name = obj['name'] as String?,
+ : restart = obj['restart'],
+ name = obj['name'] as String?,
cwd = obj['cwd'] as String?,
additionalProjectPaths =
(obj['additionalProjectPaths'] as List?)?.cast<String>(),
@@ -212,6 +213,7 @@
sendLogsToClient = obj['sendLogsToClient'] as bool?;
Map<String, Object?> toJson() => {
+ if (restart != null) 'restart': restart,
if (name != null) 'name': name,
if (cwd != null) 'cwd': cwd,
if (additionalProjectPaths != null)
@@ -263,8 +265,8 @@
/// an expression into an evaluation console) or to events sent by the server
/// (for example when the server sends a `StoppedEvent` it may cause the client
/// to then send a `stackTraceRequest` or `scopesRequest` to get variables).
-abstract class DartDebugAdapter<TL extends DartLaunchRequestArguments,
- TA extends DartAttachRequestArguments> extends BaseDebugAdapter<TL, TA> {
+abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
+ TA extends AttachRequestArguments> extends BaseDebugAdapter<TL, TA> {
late final DartCommonLaunchAttachRequestArguments args;
final _debuggerInitializedCompleter = Completer<void>();
final _configurationDoneCompleter = Completer<void>();
@@ -378,6 +380,14 @@
/// `null` if the `initialize` request has not yet been made.
InitializeRequestArguments? get initializeArgs => _initializeArgs;
+ /// Whether or not this adapter can handle the restartRequest.
+ ///
+ /// If false, the editor will just terminate the debug session and start a new
+ /// one when the user asks to restart. If true, the adapter must implement
+ /// the [restartRequest] method and handle its own restart (for example the
+ /// Flutter adapter will perform a Hot Restart).
+ bool get supportsRestartRequest => false;
+
/// Whether the VM Service closing should be used as a signal to terminate the
/// debug session.
///
@@ -407,7 +417,7 @@
TA args,
void Function() sendResponse,
) async {
- this.args = args;
+ this.args = args as DartCommonLaunchAttachRequestArguments;
isAttach = true;
_subscribeToOutputStreams = true;
@@ -480,7 +490,7 @@
logger?.call('Starting a DDS instance for $uri');
try {
final dds = await DartDevelopmentService.startDartDevelopmentService(
- uri,
+ vmServiceUriToHttp(uri),
enableAuthCodes: enableAuthCodes,
ipv6: ipv6,
);
@@ -492,13 +502,13 @@
// instance.
if (e.errorCode ==
DartDevelopmentServiceException.existingDdsInstanceError) {
- uri = _cleanVmServiceUri(uri);
+ uri = vmServiceUriToWebSocket(uri);
} else {
rethrow;
}
}
} else {
- uri = _cleanVmServiceUri(uri);
+ uri = vmServiceUriToWebSocket(uri);
}
logger?.call('Connecting to debugger at $uri');
@@ -506,21 +516,25 @@
final vmService = await _vmServiceConnectUri(uri.toString());
logger?.call('Connected to debugger at $uri!');
- // TODO(dantup): VS Code currently depends on a custom dart.debuggerUris
- // event to notify it of VM Services that become available (for example to
- // register with the DevTools server). If this is still required, it will
- // need implementing here (and also documented as a customisation and
- // perhaps gated on a capability/argument).
+ // Send a custom event with the VM Service URI as the editor might want to
+ // know about this (for example so it can connect an embedded DevTools to
+ // this app).
+ sendEvent(
+ RawEventBody({
+ 'vmServiceUri': uri.toString(),
+ }),
+ eventType: 'dart.debuggerUris',
+ );
+
this.vmService = vmService;
unawaited(vmService.onDone.then((_) => _handleVmServiceClosed()));
_subscriptions.addAll([
- vmService.onIsolateEvent.listen(_handleIsolateEvent),
- vmService.onDebugEvent.listen(_handleDebugEvent),
- vmService.onLoggingEvent.listen(_handleLoggingEvent),
- // TODO(dantup): Implement these.
- // vmService.onExtensionEvent.listen(_handleExtensionEvent),
- // vmService.onServiceEvent.listen(_handleServiceEvent),
+ vmService.onIsolateEvent.listen(handleIsolateEvent),
+ vmService.onDebugEvent.listen(handleDebugEvent),
+ vmService.onLoggingEvent.listen(handleLoggingEvent),
+ vmService.onExtensionEvent.listen(handleExtensionEvent),
+ vmService.onServiceEvent.listen(handleServiceEvent),
if (_subscribeToOutputStreams)
vmService.onStdoutEvent.listen(_handleStdoutEvent),
if (_subscribeToOutputStreams)
@@ -530,8 +544,8 @@
vmService.streamListen(vm.EventStreams.kIsolate),
vmService.streamListen(vm.EventStreams.kDebug),
vmService.streamListen(vm.EventStreams.kLogging),
- // vmService.streamListen(vm.EventStreams.kExtension),
- // vmService.streamListen(vm.EventStreams.kService),
+ vmService.streamListen(vm.EventStreams.kExtension),
+ vmService.streamListen(vm.EventStreams.kService),
vmService.streamListen(vm.EventStreams.kStdout),
vmService.streamListen(vm.EventStreams.kStderr),
]);
@@ -633,6 +647,24 @@
sendResponse(null);
break;
+ /// Allows an editor to call a service/service extension that it was told
+ /// about via a custom 'dart.serviceRegistered' or
+ /// 'dart.serviceExtensionAdded' event.
+ case 'callService':
+ final method = args?.args['method'] as String?;
+ if (method == null) {
+ throw DebugAdapterException(
+ 'Method is required to call services/service extensions',
+ );
+ }
+ final params = args?.args['params'] as Map<String, Object?>?;
+ final response = await vmService?.callServiceExtension(
+ method,
+ args: params,
+ );
+ sendResponse(response?.json);
+ break;
+
default:
await super.customRequest(request, args, sendResponse);
}
@@ -848,6 +880,7 @@
supportsDelayedStackTraceLoading: true,
supportsEvaluateForHovers: true,
supportsLogPoints: true,
+ supportsRestartRequest: supportsRestartRequest,
// TODO(dantup): All of these...
// supportsRestartFrame: true,
supportsTerminateRequest: true,
@@ -903,7 +936,7 @@
TL args,
void Function() sendResponse,
) async {
- this.args = args;
+ this.args = args as DartCommonLaunchAttachRequestArguments;
isAttach = false;
// Common setup.
@@ -947,6 +980,23 @@
/// not in the package mapping file.
String? resolvePackageUri(Uri uri) => _converter.resolvePackageUri(uri);
+ /// restart is called by the client when the user invokes a restart (for
+ /// example with the button on the debug toolbar).
+ ///
+ /// The base implementation of this method throws. It is up to a debug adapter
+ /// that advertises `supportsRestartRequest` to override this method.
+ @override
+ Future<void> restartRequest(
+ Request request,
+ RestartArguments? args,
+ void Function() sendResponse,
+ ) async {
+ throw DebugAdapterException(
+ 'restartRequest was called on an adapter that '
+ 'does not provide an implementation',
+ );
+ }
+
/// [scopesRequest] is called by the client to request all of the variables
/// scopes available for a given stack frame.
@override
@@ -1417,11 +1467,25 @@
sendResponse(VariablesResponseBody(variables: variables));
}
+ /// Fixes up a VM Service WebSocket URI to not have a trailing /ws
+ /// and use the HTTP scheme which is what DDS expects.
+ Uri vmServiceUriToHttp(Uri uri) {
+ final isSecure = uri.isScheme('https') || uri.isScheme('wss');
+ uri = uri.replace(scheme: isSecure ? 'https' : 'http');
+
+ final segments = uri.pathSegments;
+ if (segments.isNotEmpty && segments.last == 'ws') {
+ uri = uri.replace(pathSegments: segments.take(segments.length - 1));
+ }
+
+ return uri;
+ }
+
/// Fixes up an Observatory [uri] to a WebSocket URI with a trailing /ws
/// for connecting when not using DDS.
///
/// DDS does its own cleaning up of the URI.
- Uri _cleanVmServiceUri(Uri uri) {
+ Uri vmServiceUriToWebSocket(Uri uri) {
// The VM Service library always expects the WebSockets URI so fix the
// scheme (http -> ws, https -> wss).
final isSecure = uri.isScheme('https') || uri.isScheme('wss');
@@ -1468,7 +1532,9 @@
);
}
- Future<void> _handleDebugEvent(vm.Event event) async {
+ @protected
+ @mustCallSuper
+ Future<void> handleDebugEvent(vm.Event event) async {
// Delay processing any events until the debugger initialization has
// finished running, as events may arrive (for ex. IsolateRunnable) while
// it's doing is own initialization that this may interfere with.
@@ -1477,17 +1543,42 @@
await _isolateManager.handleEvent(event);
}
- Future<void> _handleIsolateEvent(vm.Event event) async {
+ @protected
+ @mustCallSuper
+ Future<void> handleExtensionEvent(vm.Event event) async {
+ await debuggerInitialized;
+
+ // Base Dart does not do anything here, but other DAs (like Flutter) may
+ // override it to do their own handling.
+ }
+
+ @protected
+ @mustCallSuper
+ Future<void> handleIsolateEvent(vm.Event event) async {
// Delay processing any events until the debugger initialization has
// finished running, as events may arrive (for ex. IsolateRunnable) while
// it's doing is own initialization that this may interfere with.
await debuggerInitialized;
+ // Allow IsolateManager to handle any state-related events.
await _isolateManager.handleEvent(event);
+
+ switch (event.kind) {
+ // Pass any Service Extension events on to the client so they can enable
+ // functionality based upon them.
+ case vm.EventKind.kServiceExtensionAdded:
+ this._sendServiceExtensionAdded(
+ event.extensionRPC!,
+ event.isolate!.id!,
+ );
+ break;
+ }
}
/// Handles a dart:developer log() event, sending output to the client.
- Future<void> _handleLoggingEvent(vm.Event event) async {
+ @protected
+ @mustCallSuper
+ Future<void> handleLoggingEvent(vm.Event event) async {
final record = event.logRecord;
final thread = _isolateManager.threadForIsolate(event.isolate);
if (record == null || thread == null) {
@@ -1501,7 +1592,8 @@
if (ref == null || ref.kind == vm.InstanceKind.kNull) {
return null;
}
- return _converter.convertVmInstanceRefToDisplayString(
+ return _converter
+ .convertVmInstanceRefToDisplayString(
thread,
ref,
// Always allow calling toString() here as the user expects the full
@@ -1510,7 +1602,14 @@
allowCallingToString: true,
allowTruncatedValue: false,
includeQuotesAroundString: false,
- );
+ )
+ .catchError((e) {
+ // Fetching strings from the server may throw if they have been
+ // collected since (for example if a Hot Restart occurs while
+ // we're running this). Log the error and just return null so
+ // nothing is shown.
+ logger?.call('$e');
+ });
}
var loggerName = await asString(record.loggerName);
@@ -1534,6 +1633,23 @@
}
}
+ @protected
+ @mustCallSuper
+ Future<void> handleServiceEvent(vm.Event event) async {
+ await debuggerInitialized;
+
+ switch (event.kind) {
+ // Service registrations are passed to the client so they can toggle
+ // behaviour based on their presence.
+ case vm.EventKind.kServiceRegistered:
+ this._sendServiceRegistration(event.service!, event.method!);
+ break;
+ case vm.EventKind.kServiceUnregistered:
+ this._sendServiceUnregistration(event.service!, event.method!);
+ break;
+ }
+ }
+
void _handleStderrEvent(vm.Event event) {
_sendOutputStreamEvent('stderr', event);
}
@@ -1585,6 +1701,27 @@
sendOutput('stdout', message);
}
+ void _sendServiceExtensionAdded(String extensionRPC, String isolateId) {
+ sendEvent(
+ RawEventBody({'extensionRPC': extensionRPC, 'isolateId': isolateId}),
+ eventType: 'dart.serviceExtensionAdded',
+ );
+ }
+
+ void _sendServiceRegistration(String service, String method) {
+ sendEvent(
+ RawEventBody({'service': service, 'method': method}),
+ eventType: 'dart.serviceRegistered',
+ );
+ }
+
+ void _sendServiceUnregistration(String service, String method) {
+ sendEvent(
+ RawEventBody({'service': service, 'method': method}),
+ eventType: 'dart.serviceUnregistered',
+ );
+ }
+
/// Updates the current debug options for the session.
///
/// Clients may not know about all debug options, so anything not included
@@ -1641,15 +1778,10 @@
/// adapter in launchRequest, which is a request to start debugging an
/// application.
///
-/// Specialised adapters (such as Flutter) will likely extend this class with
-/// their own additional fields.
+/// Specialised adapters (such as Flutter) will likely have their own versions
+/// of this class.
class DartLaunchRequestArguments extends DartCommonLaunchAttachRequestArguments
implements LaunchRequestArguments {
- /// Optional data from the previous, restarted session.
- /// The data is sent as the 'restart' attribute of the 'terminated' event.
- /// The client should leave the data intact.
- final Object? restart;
-
/// If noDebug is true the launch request should launch the program without
/// enabling debugging.
final bool? noDebug;
@@ -1681,7 +1813,6 @@
final String? console;
DartLaunchRequestArguments({
- this.restart,
this.noDebug,
required this.program,
this.args,
@@ -1689,6 +1820,7 @@
this.toolArgs,
this.console,
this.enableAsserts,
+ Object? restart,
String? name,
String? cwd,
List<String>? additionalProjectPaths,
@@ -1698,6 +1830,7 @@
bool? evaluateToStringInDebugViews,
bool? sendLogsToClient,
}) : super(
+ restart: restart,
name: name,
cwd: cwd,
additionalProjectPaths: additionalProjectPaths,
@@ -1709,8 +1842,7 @@
);
DartLaunchRequestArguments.fromMap(Map<String, Object?> obj)
- : restart = obj['restart'],
- noDebug = obj['noDebug'] as bool?,
+ : noDebug = obj['noDebug'] as bool?,
program = obj['program'] as String,
args = (obj['args'] as List?)?.cast<String>(),
toolArgs = (obj['toolArgs'] as List?)?.cast<String>(),
@@ -1722,7 +1854,6 @@
@override
Map<String, Object?> toJson() => {
...super.toJson(),
- if (restart != null) 'restart': restart,
if (noDebug != null) 'noDebug': noDebug,
'program': program,
if (args != null) 'args': args,
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 2a3f351..a0d23d8 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -145,6 +145,11 @@
} else {
await launchAsProcess(vmPath, processArgs);
}
+
+ // Delay responding until the debugger is connected.
+ if (debug) {
+ await debuggerInitialized;
+ }
}
/// Called by [attachRequest] to request that we actually connect to the app
@@ -249,7 +254,7 @@
/// Called by [terminateRequest] to request that we gracefully shut down the
/// app being run (or in the case of an attach, disconnect).
Future<void> terminateImpl() async {
- terminatePids(ProcessSignal.sigint);
+ terminatePids(ProcessSignal.sigterm);
await _process?.exitCode;
}
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 534166d..0417245 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -159,7 +159,7 @@
/// Called by [terminateRequest] to request that we gracefully shut down the
/// app being run (or in the case of an attach, disconnect).
Future<void> terminateImpl() async {
- terminatePids(ProcessSignal.sigint);
+ terminatePids(ProcessSignal.sigterm);
await _process?.exitCode;
}
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 3f64d98..2d82bbd 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -77,7 +77,7 @@
RawRequestArguments? args,
void Function(Object?) sendResponse,
) async {
- throw DebugAdapterException('Unknown command ${request.command}');
+ throw DebugAdapterException('Unknown command ${request.command}');
}
Future<void> disconnectRequest(
@@ -167,6 +167,12 @@
void Function() sendResponse,
);
+ Future<void> restartRequest(
+ Request request,
+ RestartArguments? args,
+ void Function() sendResponse,
+ );
+
Future<void> scopesRequest(
Request request,
ScopesArguments args,
@@ -280,6 +286,12 @@
handle(request, _withVoidResponse(launchRequest), parseLaunchArgs);
} else if (request.command == 'attach') {
handle(request, _withVoidResponse(attachRequest), parseAttachArgs);
+ } else if (request.command == 'restart') {
+ handle(
+ request,
+ _withVoidResponse(restartRequest),
+ _allowNullArg(RestartArguments.fromJson),
+ );
} else if (request.command == 'terminate') {
handle(
request,
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 6eb4a24..fd2d342 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -558,11 +558,18 @@
// Set new breakpoints.
final newBreakpoints = _clientBreakpointsByUri[uri] ?? const [];
await Future.forEach<SourceBreakpoint>(newBreakpoints, (bp) async {
- final vmBp = await service.addBreakpointWithScriptUri(
- isolateId, uri, bp.line,
- column: bp.column);
- existingBreakpointsForIsolateAndUri.add(vmBp);
- _clientBreakpointsByVmId[vmBp.id!] = bp;
+ try {
+ final vmBp = await service.addBreakpointWithScriptUri(
+ isolateId, uri, bp.line,
+ column: bp.column);
+ existingBreakpointsForIsolateAndUri.add(vmBp);
+ _clientBreakpointsByVmId[vmBp.id!] = bp;
+ } catch (e) {
+ // Swallow errors setting breakpoints rather than failing the whole
+ // request as it's very easy for editors to send us breakpoints that
+ // aren't valid any more.
+ _adapter.logger?.call('Failed to add breakpoint $e');
+ }
});
}
}
diff --git a/pkg/dds/test/dap/integration/debug_services.dart b/pkg/dds/test/dap/integration/debug_services.dart
new file mode 100644
index 0000000..98f698e
--- /dev/null
+++ b/pkg/dds/test/dap/integration/debug_services.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service_io.dart';
+
+import 'test_client.dart';
+import 'test_scripts.dart';
+import 'test_support.dart';
+
+main() {
+ late DapTestSession dap;
+ setUp(() async {
+ dap = await DapTestSession.setUp();
+ });
+ tearDown(() => dap.tearDown());
+
+ group('debug mode', () {
+ test('reports the VM Service URI to the client', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+
+ await client.hitBreakpoint(testFile, breakpointLine);
+ final vmServiceUri = (await client.vmServiceUri)!;
+ expect(vmServiceUri.scheme, anyOf('ws', 'wss'));
+
+ await client.terminate();
+ });
+
+ test('exposes VM services to the client', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(simpleBreakpointProgram);
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+
+ // Capture our test service registration.
+ final myServiceRegistrationFuture = client.serviceRegisteredEvents
+ .firstWhere((event) => event['service'] == 'myService');
+ await client.hitBreakpoint(testFile, breakpointLine);
+ final vmServiceUri = await client.vmServiceUri;
+
+ // Register a service that echos back its params.
+ final vmService = await vmServiceConnectUri(vmServiceUri.toString());
+ // A service seems mandatory for this to work, even though it's unused.
+ await vmService.registerService('myService', 'myServiceAlias');
+ vmService.registerServiceCallback('myService', (params) async {
+ return {'result': params};
+ });
+
+ // Ensure the service registration event is emitted and includes the
+ // method to call it.
+ final myServiceRegistration = await myServiceRegistrationFuture;
+ final myServiceRegistrationMethod =
+ myServiceRegistration['method'] as String;
+
+ // Call the method and expect it to return the same values.
+ final response = await client.callService(
+ myServiceRegistrationMethod,
+ {'foo': 'bar'},
+ );
+ final result = response.body as Map<String, Object?>;
+ expect(result['foo'], equals('bar'));
+
+ await vmService.dispose();
+ await client.terminate();
+ });
+
+ test('exposes VM service extensions to the client', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile(serviceExtensionProgram);
+
+ // Capture our test service registration.
+ final serviceExtensionAddedFuture = client.serviceExtensionAddedEvents
+ .firstWhere(
+ (event) => event['extensionRPC'] == 'ext.service.extension');
+ await client.start(file: testFile);
+
+ // Ensure the service registration event is emitted and includes the
+ // method to call it.
+ final serviceExtensionAdded = await serviceExtensionAddedFuture;
+ final extensionRPC = serviceExtensionAdded['extensionRPC'] as String;
+ final isolateId = serviceExtensionAdded['isolateId'] as String;
+
+ // Call the method and expect it to return the same values.
+ final response = await client.callService(
+ extensionRPC,
+ {
+ 'isolateId': isolateId,
+ 'foo': 'bar',
+ },
+ );
+ final result = response.body as Map<String, Object?>;
+ expect(result['foo'], equals('bar'));
+ });
+ // These tests can be slow due to starting up the external server process.
+ }, timeout: Timeout.none);
+}
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index 0850b5c4..e4e5010 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -36,11 +36,20 @@
final _serverRequestHandlers =
<String, FutureOr<Object?> Function(Object?)>{};
+ late final Future<Uri?> vmServiceUri;
+
DapTestClient._(
this._channel,
this._logger, {
this.captureVmServiceTraffic = false,
}) {
+ // Set up a future that will complete when the 'dart.debuggerUris' event is
+ // emitted by the debug adapter so tests have easy access to it.
+ vmServiceUri = event('dart.debuggerUris').then<Uri?>((event) {
+ final body = event.body as Map<String, Object?>;
+ return Uri.parse(body['vmServiceUri'] as String);
+ }).catchError((e) => null);
+
_subscription = _channel.listen(
_handleMessage,
onDone: () {
@@ -59,6 +68,16 @@
Stream<OutputEventBody> get outputEvents => events('output')
.map((e) => OutputEventBody.fromJson(e.body as Map<String, Object?>));
+ /// Returns a stream of custom 'dart.serviceExtensionAdded' events.
+ Stream<Map<String, Object?>> get serviceExtensionAddedEvents =>
+ events('dart.serviceExtensionAdded')
+ .map((e) => e.body as Map<String, Object?>);
+
+ /// Returns a stream of custom 'dart.serviceRegistered' events.
+ Stream<Map<String, Object?>> get serviceRegisteredEvents =>
+ events('dart.serviceRegistered')
+ .map((e) => e.body as Map<String, Object?>);
+
/// Returns a stream of 'dart.testNotification' custom events from the
/// package:test JSON reporter.
Stream<Map<String, Object?>> get testNotificationEvents =>
@@ -116,6 +135,11 @@
return attachResponse;
}
+ /// Calls a service method via a custom request.
+ Future<Response> callService(String name, Object? params) {
+ return custom('callService', {'method': name, 'params': params});
+ }
+
/// Sends a continue request for the given thread.
///
/// Returns a Future that completes when the server returns a corresponding
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 0b70626..a382781 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -20,6 +20,28 @@
}
''';
+/// A simple Dart script that registers a simple service extension that returns
+/// its params and waits until it is called before exiting.
+const serviceExtensionProgram = '''
+ import 'dart:async';
+ import 'dart:convert';
+ import 'dart:developer';
+
+ void main(List<String> args) async {
+ // Using a completer here causes the VM to quit when the extension is called
+ // so use a flag.
+ // https://github.com/dart-lang/sdk/issues/47279
+ var wasCalled = false;
+ registerExtension('ext.service.extension', (method, params) async {
+ wasCalled = true;
+ return ServiceExtensionResponse.result(jsonEncode(params));
+ });
+ while (!wasCalled) {
+ await Future.delayed(const Duration(milliseconds: 100));
+ }
+ }
+''';
+
/// A simple Dart script that prints its arguments.
const simpleArgPrintingProgram = r'''
void main(List<String> args) async {
diff --git a/pkg/front_end/lib/src/fasta/builder/enum_builder.dart b/pkg/front_end/lib/src/fasta/builder/enum_builder.dart
index 3c3b87c..5fff739 100644
--- a/pkg/front_end/lib/src/fasta/builder/enum_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/enum_builder.dart
@@ -200,6 +200,7 @@
Constructor? constructorReference;
Reference? toStringReference;
+ Reference? valuesFieldReference;
Reference? valuesGetterReference;
Reference? valuesSetterReference;
if (referencesFromIndexed != null) {
@@ -208,6 +209,8 @@
toStringReference =
referencesFromIndexed.lookupGetterReference(new Name("toString"));
Name valuesName = new Name("values");
+ valuesFieldReference =
+ referencesFromIndexed.lookupFieldReference(valuesName);
valuesGetterReference =
referencesFromIndexed.lookupGetterReference(valuesName);
valuesSetterReference =
@@ -244,6 +247,7 @@
charOffset,
charOffset,
staticFieldNameScheme,
+ fieldReference: valuesFieldReference,
fieldGetterReference: valuesGetterReference,
fieldSetterReference: valuesSetterReference);
members["values"] = valuesBuilder;
@@ -303,10 +307,12 @@
name.length,
parent.fileUri);
}
+ Reference? fieldReference;
Reference? getterReference;
Reference? setterReference;
if (referencesFromIndexed != null) {
Name nameName = new Name(name, referencesFromIndexed.library);
+ fieldReference = referencesFromIndexed.lookupFieldReference(nameName);
getterReference =
referencesFromIndexed.lookupGetterReference(nameName);
setterReference =
@@ -322,6 +328,7 @@
enumConstantInfo.charOffset,
enumConstantInfo.charOffset,
staticFieldNameScheme,
+ fieldReference: fieldReference,
fieldGetterReference: getterReference,
fieldSetterReference: setterReference);
members[name] = fieldBuilder..next = existing;
diff --git a/pkg/front_end/lib/src/fasta/builder/field_builder.dart b/pkg/front_end/lib/src/fasta/builder/field_builder.dart
index ca2aa43..df0d819 100644
--- a/pkg/front_end/lib/src/fasta/builder/field_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/field_builder.dart
@@ -120,8 +120,10 @@
int charOffset,
int charEndOffset,
NameScheme fieldNameScheme,
- {Reference? fieldGetterReference,
+ {Reference? fieldReference,
+ Reference? fieldGetterReference,
Reference? fieldSetterReference,
+ Reference? lateIsSetFieldReference,
Reference? lateIsSetGetterReference,
Reference? lateIsSetSetterReference,
Reference? lateGetterReference,
@@ -138,6 +140,8 @@
late_lowering.computeIsSetStrategy(libraryBuilder);
if (isAbstract || isExternal) {
+ assert(fieldReference == null);
+ assert(lateIsSetFieldReference == null);
assert(lateIsSetGetterReference == null);
assert(lateIsSetSetterReference == null);
assert(lateGetterReference == null);
@@ -169,8 +173,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -184,8 +190,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -201,8 +209,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -216,8 +226,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -238,8 +250,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -253,8 +267,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -263,6 +279,7 @@
isSetStrategy);
}
} else {
+ assert(lateIsSetFieldReference == null);
assert(lateIsSetGetterReference == null);
assert(lateIsSetSetterReference == null);
assert(lateGetterReference == null);
@@ -274,6 +291,7 @@
isLate: isLate,
hasInitializer: hasInitializer,
isNonNullableByDefault: library.isNonNullableByDefault,
+ fieldReference: fieldReference,
getterReference: fieldGetterReference,
setterReference: fieldSetterReference);
}
@@ -599,6 +617,7 @@
required bool isLate,
required bool hasInitializer,
required bool isNonNullableByDefault,
+ required Reference? fieldReference,
required Reference? getterReference,
required Reference? setterReference}) {
// ignore: unnecessary_null_comparison
@@ -619,6 +638,7 @@
isConst: isConst,
isLate: isLate,
fileUri: fileUri,
+ fieldReference: fieldReference,
getterReference: getterReference)
: new Field.mutable(
nameScheme.getFieldName(FieldNameType.Field, name,
@@ -626,6 +646,7 @@
isFinal: isFinal,
isLate: isLate,
fileUri: fileUri,
+ fieldReference: fieldReference,
getterReference: getterReference,
setterReference: setterReference);
_field
@@ -808,8 +829,10 @@
Uri fileUri,
int charOffset,
int charEndOffset,
+ Reference? fieldReference,
Reference? fieldGetterReference,
Reference? fieldSetterReference,
+ Reference? lateIsSetFieldReference,
Reference? lateIsSetGetterReference,
Reference? lateIsSetSetterReference,
Reference? lateGetterReference,
@@ -824,6 +847,7 @@
_field = new Field.mutable(
nameScheme.getFieldName(FieldNameType.Field, name, isSynthesized: true),
fileUri: fileUri,
+ fieldReference: fieldReference,
getterReference: fieldGetterReference,
setterReference: fieldSetterReference)
..fileOffset = charOffset
@@ -841,6 +865,7 @@
nameScheme.getFieldName(FieldNameType.IsSetField, name,
isSynthesized: true),
fileUri: fileUri,
+ fieldReference: lateIsSetFieldReference,
getterReference: lateIsSetGetterReference,
setterReference: lateIsSetSetterReference)
..fileOffset = charOffset
@@ -1191,8 +1216,10 @@
Uri fileUri,
int charOffset,
int charEndOffset,
+ Reference? fieldReference,
Reference? fieldGetterReference,
Reference? fieldSetterReference,
+ Reference? lateIsSetFieldReference,
Reference? lateIsSetGetterReference,
Reference? lateIsSetSetterReference,
Reference? lateGetterReference,
@@ -1205,8 +1232,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -1223,8 +1252,10 @@
Uri fileUri,
int charOffset,
int charEndOffset,
+ Reference? fieldReference,
Reference? fieldGetterReference,
Reference? fieldSetterReference,
+ Reference? lateIsSetFieldReference,
Reference? lateIsSetGetterReference,
Reference? lateIsSetSetterReference,
Reference? lateGetterReference,
@@ -1237,8 +1268,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -1270,8 +1303,10 @@
Uri fileUri,
int charOffset,
int charEndOffset,
+ Reference? fieldReference,
Reference? fieldGetterReference,
Reference? fieldSetterReference,
+ Reference? lateIsSetFieldReference,
Reference? lateIsSetGetterReference,
Reference? lateIsSetSetterReference,
Reference? lateGetterReference,
@@ -1284,8 +1319,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
@@ -1318,8 +1355,10 @@
Uri fileUri,
int charOffset,
int charEndOffset,
+ Reference? fieldReference,
Reference? fieldGetterReference,
Reference? fieldSetterReference,
+ Reference? lateIsSetFieldReference,
Reference? lateIsSetGetterReference,
Reference? lateIsSetSetterReference,
Reference? lateGetterReference,
@@ -1332,8 +1371,10 @@
fileUri,
charOffset,
charEndOffset,
+ fieldReference,
fieldGetterReference,
fieldSetterReference,
+ lateIsSetFieldReference,
lateIsSetGetterReference,
lateIsSetSetterReference,
lateGetterReference,
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index d012efa..13f9ead 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -4083,7 +4083,7 @@
final Map<Reference, Constant> fieldValues = <Reference, Constant>{};
fields.forEach((Field field, Constant value) {
assert(value is! UnevaluatedConstant);
- fieldValues[field.getterReference] = value;
+ fieldValues[field.fieldReference] = value;
});
assert(unusedArguments.isEmpty);
return new InstanceConstant(klass.reference, typeArguments, fieldValues);
@@ -4092,7 +4092,7 @@
InstanceCreation buildUnevaluatedInstance() {
final Map<Reference, Expression> fieldValues = <Reference, Expression>{};
fields.forEach((Field field, Constant value) {
- fieldValues[field.getterReference] = evaluator.extract(value);
+ fieldValues[field.fieldReference] = evaluator.extract(value);
});
return new InstanceCreation(
klass.reference, typeArguments, fieldValues, asserts, unusedArguments);
diff --git a/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart b/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart
index 57d4f7c..4ebcb03 100644
--- a/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart
@@ -331,7 +331,7 @@
if (field.isStatic) continue;
if (!first) result.add(", ");
result.add("${field.name}: ");
- node.fieldValues[field.getterReference]!.accept(this);
+ node.fieldValues[field.fieldReference]!.accept(this);
first = false;
}
result.add("}");
diff --git a/pkg/front_end/lib/src/fasta/source/source_class_builder.dart b/pkg/front_end/lib/src/fasta/source/source_class_builder.dart
index b1ed2fa..448fe67 100644
--- a/pkg/front_end/lib/src/fasta/source/source_class_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_class_builder.dart
@@ -864,8 +864,11 @@
procedure.stubTarget = null;
}
- void _addRedirectingConstructor(SourceFactoryBuilder constructorBuilder,
- SourceLibraryBuilder library, Reference? getterReference) {
+ void _addRedirectingConstructor(
+ SourceFactoryBuilder constructorBuilder,
+ SourceLibraryBuilder library,
+ Reference? fieldReference,
+ Reference? getterReference) {
// Add a new synthetic field to this class for representing factory
// constructors. This is used to support resolving such constructors in
// source code.
@@ -888,6 +891,7 @@
isFinal: true,
initializer: literal,
fileUri: cls.fileUri,
+ fieldReference: fieldReference,
getterReference: getterReference)
..fileOffset = cls.fileOffset;
cls.addField(field);
@@ -930,11 +934,18 @@
// is actually in the kernel tree. This call creates a StaticGet
// to [declaration.target] in a field `_redirecting#` which is
// only legal to do to things in the kernel tree.
- Reference? getterReference =
- referencesFromIndexed?.lookupGetterReference(new Name(
- "_redirecting#", referencesFromIndexed!.library));
+ Reference? fieldReference;
+ Reference? getterReference;
+ if (referencesFromIndexed != null) {
+ Name name =
+ new Name(redirectingName, referencesFromIndexed!.library);
+ fieldReference =
+ referencesFromIndexed!.lookupFieldReference(name);
+ getterReference =
+ referencesFromIndexed!.lookupGetterReference(name);
+ }
_addRedirectingConstructor(
- declaration, library, getterReference);
+ declaration, library, fieldReference, getterReference);
}
Member? targetNode;
if (targetBuilder is FunctionBuilder) {
diff --git a/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart b/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
index 763ce00..4760e58 100644
--- a/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
@@ -175,7 +175,7 @@
Reference memberReference;
if (member is Field) {
libraryBuilder.library.addField(member);
- memberReference = member.getterReference;
+ memberReference = member.fieldReference;
} else if (member is Procedure) {
libraryBuilder.library.addProcedure(member);
memberReference = member.reference;
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index 1c83138..480fb3a 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -1023,12 +1023,15 @@
if (unserializableExports != null) {
Name fieldName = new Name("_exports#", library);
+ Reference? fieldReference =
+ referencesFromIndexed?.lookupFieldReference(fieldName);
Reference? getterReference =
referencesFromIndexed?.lookupGetterReference(fieldName);
library.addField(new Field.immutable(fieldName,
initializer: new StringLiteral(jsonEncode(unserializableExports)),
isStatic: true,
isConst: true,
+ fieldReference: fieldReference,
getterReference: getterReference,
fileUri: library.fileUri));
}
@@ -2246,8 +2249,10 @@
extensionName = currentTypeParameterScopeBuilder.name;
}
+ Reference? fieldReference;
Reference? fieldGetterReference;
Reference? fieldSetterReference;
+ Reference? lateIsSetFieldReference;
Reference? lateIsSetGetterReference;
Reference? lateIsSetSetterReference;
Reference? lateGetterReference;
@@ -2264,18 +2269,21 @@
(_currentClassReferencesFromIndexed ?? referencesFromIndexed)!;
Name nameToLookupName = nameScheme.getFieldName(FieldNameType.Field, name,
isSynthesized: fieldIsLateWithLowering);
+ fieldReference = indexedContainer.lookupFieldReference(nameToLookupName);
fieldGetterReference =
indexedContainer.lookupGetterReference(nameToLookupName);
fieldSetterReference =
indexedContainer.lookupSetterReference(nameToLookupName);
if (fieldIsLateWithLowering) {
- Name lateIsSetNameName = nameScheme.getFieldName(
+ Name lateIsSetName = nameScheme.getFieldName(
FieldNameType.IsSetField, name,
isSynthesized: fieldIsLateWithLowering);
+ lateIsSetFieldReference =
+ indexedContainer.lookupFieldReference(lateIsSetName);
lateIsSetGetterReference =
- indexedContainer.lookupGetterReference(lateIsSetNameName);
+ indexedContainer.lookupGetterReference(lateIsSetName);
lateIsSetSetterReference =
- indexedContainer.lookupSetterReference(lateIsSetNameName);
+ indexedContainer.lookupSetterReference(lateIsSetName);
lateGetterReference = indexedContainer.lookupGetterReference(
nameScheme.getFieldName(FieldNameType.Getter, name,
isSynthesized: fieldIsLateWithLowering));
@@ -2295,8 +2303,10 @@
charOffset,
charEndOffset,
nameScheme,
+ fieldReference: fieldReference,
fieldGetterReference: fieldGetterReference,
fieldSetterReference: fieldSetterReference,
+ lateIsSetFieldReference: lateIsSetFieldReference,
lateIsSetGetterReference: lateIsSetGetterReference,
lateIsSetSetterReference: lateIsSetSetterReference,
lateGetterReference: lateGetterReference,
diff --git a/pkg/front_end/test/incremental_suite.dart b/pkg/front_end/test/incremental_suite.dart
index 4b4f060..30745c3 100644
--- a/pkg/front_end/test/incremental_suite.dart
+++ b/pkg/front_end/test/incremental_suite.dart
@@ -1970,9 +1970,12 @@
Name fieldName = new Name("unique_SimulateTransformer");
Field field = new Field.immutable(fieldName,
isFinal: true,
- getterReference: lib.reference.canonicalName
+ fieldReference: lib.reference.canonicalName
?.getChildFromFieldWithName(fieldName)
.reference,
+ getterReference: lib.reference.canonicalName
+ ?.getChildFromFieldGetterWithName(fieldName)
+ .reference,
fileUri: lib.fileUri);
lib.addField(field);
for (Class c in lib.classes) {
@@ -1983,9 +1986,12 @@
fieldName = new Name("unique_SimulateTransformer");
field = new Field.immutable(fieldName,
isFinal: true,
- getterReference: c.reference.canonicalName
+ fieldReference: lib.reference.canonicalName
?.getChildFromFieldWithName(fieldName)
.reference,
+ getterReference: c.reference.canonicalName
+ ?.getChildFromFieldGetterWithName(fieldName)
+ .reference,
fileUri: c.fileUri);
c.addField(field);
}
diff --git a/pkg/front_end/test/type_labeler_test.dart b/pkg/front_end/test/type_labeler_test.dart
index 8681a9f..ec09139 100644
--- a/pkg/front_end/test/type_labeler_test.dart
+++ b/pkg/front_end/test/type_labeler_test.dart
@@ -240,20 +240,20 @@
check({symConst: "#foo", symLibConst: "#dart:core::bar"}, 0);
Constant fooConst = new InstanceConstant(
- fooClass.reference, [], {booField.getterReference: trueConst});
+ fooClass.reference, [], {booField.fieldReference: trueConst});
check({fooConst: "Foo {boo: true}"}, 1);
Constant foo2Const = new InstanceConstant(foo2Class.reference, [], {
- nextField.getterReference: nullConst,
- valueField.getterReference: intConst
+ nextField.fieldReference: nullConst,
+ valueField.fieldReference: intConst
});
check({foo2Const: "Foo {value: 2, next: null}"}, 1);
Constant foo2nConst = new InstanceConstant(foo2Class.reference, [], {
- valueField.getterReference: intConst,
- nextField.getterReference: new InstanceConstant(foo2Class.reference, [], {
- valueField.getterReference: intConst,
- nextField.getterReference: nullConst
+ valueField.fieldReference: intConst,
+ nextField.fieldReference: new InstanceConstant(foo2Class.reference, [], {
+ valueField.fieldReference: intConst,
+ nextField.fieldReference: nullConst
}),
});
check({foo2nConst: "Foo {value: 2, next: Foo {value: 2, next: null}}"}, 1);
@@ -261,7 +261,7 @@
Constant bazFooFoo2Const = new InstanceConstant(
bazClass.reference,
[foo, foo2],
- {xField.getterReference: fooConst, yField.getterReference: foo2Const});
+ {xField.fieldReference: fooConst, yField.fieldReference: foo2Const});
check({
bazFooFoo2Const: "Baz<Foo/*1*/, Foo/*2*/> "
"{x: Foo/*1*/ {boo: true}, y: Foo/*2*/ {value: 2, next: null}}"
diff --git a/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect
index 08204a3..d87cbdc 100644
--- a/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/nnbd/issue42546.dart.weak.outline.expect
@@ -28,19 +28,19 @@
Extra constant evaluation status:
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:639:13 -> SymbolConstant(#catchError)
-Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:639:13 -> ListConstant(const <Type*>[])
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:639:13 -> SymbolConstant(#test)
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:675:13 -> SymbolConstant(#whenComplete)
-Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:675:13 -> ListConstant(const <Type*>[])
-Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:675:13 -> MapConstant(const <Symbol*, dynamic>{})
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:698:13 -> SymbolConstant(#timeout)
-Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:698:13 -> ListConstant(const <Type*>[])
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:698:13 -> SymbolConstant(#onTimeout)
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:602:13 -> SymbolConstant(#then)
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:602:13 -> SymbolConstant(#onError)
-Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> SymbolConstant(#asStream)
-Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> ListConstant(const <Type*>[])
-Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> ListConstant(const <dynamic>[])
-Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:684:13 -> MapConstant(const <Symbol*, dynamic>{})
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:717:13 -> SymbolConstant(#catchError)
+Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:717:13 -> ListConstant(const <Type*>[])
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:717:13 -> SymbolConstant(#test)
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:753:13 -> SymbolConstant(#whenComplete)
+Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:753:13 -> ListConstant(const <Type*>[])
+Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:753:13 -> MapConstant(const <Symbol*, dynamic>{})
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:776:13 -> SymbolConstant(#timeout)
+Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:776:13 -> ListConstant(const <Type*>[])
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:776:13 -> SymbolConstant(#onTimeout)
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:680:13 -> SymbolConstant(#then)
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:680:13 -> SymbolConstant(#onError)
+Evaluated: SymbolLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> SymbolConstant(#asStream)
+Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> ListConstant(const <Type*>[])
+Evaluated: ListLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> ListConstant(const <dynamic>[])
+Evaluated: MapLiteral @ org-dartlang-sdk:///sdk/lib/async/future.dart:762:13 -> MapConstant(const <Symbol*, dynamic>{})
Extra constant evaluation: evaluated: 61, effectively constant: 15
diff --git a/pkg/front_end/tool/ast_model.dart b/pkg/front_end/tool/ast_model.dart
index 7ea5230..834aa12 100644
--- a/pkg/front_end/tool/ast_model.dart
+++ b/pkg/front_end/tool/ast_model.dart
@@ -115,7 +115,7 @@
'typeParameters': FieldRule(isDeclaration: true),
},
'Field': {
- 'reference': FieldRule(name: 'getterReference'),
+ 'reference': FieldRule(name: 'fieldReference'),
},
'TypeParameter': {
'_variance': FieldRule(name: 'variance'),
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index 1a0a3a2..8c4fccf 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -147,7 +147,7 @@
type ComponentFile {
UInt32 magic = 0x90ABCDEF;
- UInt32 formatVersion = 72;
+ UInt32 formatVersion = 73;
Byte[10] shortSdkHash;
List<String> problemsAsJson; // Described in problems.md.
Library[] libraries;
@@ -376,6 +376,7 @@
type Field extends Member {
Byte tag = 4;
+ CanonicalNameReference canonicalNameField;
CanonicalNameReference canonicalNameGetter;
CanonicalNameReference canonicalNameSetter;
// An absolute path URI to the .dart file from which the field was created.
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index 14dabff..ee30a76 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -216,11 +216,7 @@
NamedNode(Reference? reference)
: this.reference = reference ?? new Reference() {
- if (this is Field) {
- (this as Field).getterReference.node = this;
- } else {
- this.reference.node = this;
- }
+ this.reference.node = this;
}
/// This is an advanced feature.
@@ -493,7 +489,10 @@
}
for (int i = 0; i < fields.length; ++i) {
Field field = fields[i];
- canonicalName.getChildFromField(field).bindTo(field.getterReference);
+ canonicalName.getChildFromField(field).bindTo(field.fieldReference);
+ canonicalName
+ .getChildFromFieldGetter(field)
+ .bindTo(field.getterReference);
if (field.hasSetter) {
canonicalName
.getChildFromFieldSetter(field)
@@ -1262,7 +1261,10 @@
if (!dirty) return;
for (int i = 0; i < fields.length; ++i) {
Field member = fields[i];
- canonicalName.getChildFromField(member).bindTo(member.getterReference);
+ canonicalName.getChildFromField(member).bindTo(member.fieldReference);
+ canonicalName
+ .getChildFromFieldGetter(member)
+ .bindTo(member.getterReference);
if (member.hasSetter) {
canonicalName
.getChildFromFieldSetter(member)
@@ -2049,13 +2051,28 @@
DartType type; // Not null. Defaults to DynamicType.
int flags = 0;
Expression? initializer; // May be null.
+
+ /// Reference used for reading from this field.
+ ///
+ /// This should be used as the target in [StaticGet], [InstanceGet], and
+ /// [SuperPropertyGet].
+ final Reference getterReference;
+
+ /// Reference used for writing to this field.
+ ///
+ /// This should be used as the target in [StaticSet], [InstanceSet], and
+ /// [SuperPropertySet].
final Reference? setterReference;
@override
@Deprecated("Use the specific getterReference/setterReference instead")
Reference get reference => super.reference;
- Reference get getterReference => super.reference;
+ /// Reference used for initializing this field.
+ ///
+ /// This should be used as the target in [FieldInitializer] and as the key
+ /// in the field values of [InstanceConstant].
+ Reference get fieldReference => super.reference;
Field.mutable(Name name,
{this.type: const DynamicType(),
@@ -2066,10 +2083,13 @@
bool isLate: false,
int transformerFlags: 0,
required Uri fileUri,
+ Reference? fieldReference,
Reference? getterReference,
Reference? setterReference})
- : this.setterReference = setterReference ?? new Reference(),
- super(name, fileUri, getterReference) {
+ : this.getterReference = getterReference ?? new Reference(),
+ this.setterReference = setterReference ?? new Reference(),
+ super(name, fileUri, fieldReference) {
+ this.getterReference.node = this;
this.setterReference!.node = this;
// ignore: unnecessary_null_comparison
assert(type != null);
@@ -2091,9 +2111,12 @@
bool isLate: false,
int transformerFlags: 0,
required Uri fileUri,
+ Reference? fieldReference,
Reference? getterReference})
- : this.setterReference = null,
- super(name, fileUri, getterReference) {
+ : this.getterReference = getterReference ?? new Reference(),
+ this.setterReference = null,
+ super(name, fileUri, fieldReference) {
+ this.getterReference.node = this;
// ignore: unnecessary_null_comparison
assert(type != null);
initializer?.parent = this;
@@ -2107,7 +2130,8 @@
@override
void _relinkNode() {
- super._relinkNode();
+ this.fieldReference.node = this;
+ this.getterReference.node = this;
if (hasSetter) {
this.setterReference!.node = this;
}
@@ -2268,7 +2292,7 @@
@override
void toTextInternal(AstPrinter printer) {
- printer.writeMemberName(getterReference);
+ printer.writeMemberName(fieldReference);
}
}
@@ -3162,10 +3186,7 @@
Expression value;
FieldInitializer(Field field, Expression value)
- : this.byReference(
- // getterReference is used since this refers to the field itself
- field.getterReference,
- value);
+ : this.byReference(field.fieldReference, value);
FieldInitializer.byReference(this.fieldReference, this.value) {
value.parent = this;
@@ -3174,7 +3195,7 @@
Field get field => fieldReference.asField;
void set field(Field field) {
- fieldReference = field.getterReference;
+ fieldReference = field.fieldReference;
}
@override
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index 1423d0d..12c33df 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -1578,11 +1578,13 @@
Field readField() {
int tag = readByte();
assert(tag == Tag.Field);
+ CanonicalName fieldCanonicalName = readNonNullCanonicalNameReference();
+ Reference fieldReference = fieldCanonicalName.reference;
CanonicalName getterCanonicalName = readNonNullCanonicalNameReference();
Reference getterReference = getterCanonicalName.reference;
CanonicalName? setterCanonicalName = readNullableCanonicalNameReference();
Reference? setterReference = setterCanonicalName?.reference;
- Field? node = getterReference.node as Field?;
+ Field? node = fieldReference.node as Field?;
if (alwaysCreateNewNamedNodes) {
node = null;
}
@@ -1594,12 +1596,15 @@
if (node == null) {
if (setterReference != null) {
node = new Field.mutable(name,
+ fieldReference: fieldReference,
getterReference: getterReference,
setterReference: setterReference,
fileUri: fileUri);
} else {
node = new Field.immutable(name,
- getterReference: getterReference, fileUri: fileUri);
+ fieldReference: fieldReference,
+ getterReference: getterReference,
+ fileUri: fileUri);
}
}
List<Expression> annotations = readAnnotationList(node);
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index 6cb1bd7..a6d04bd 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.dart
@@ -1326,6 +1326,24 @@
@override
void visitField(Field node) {
+ CanonicalName? fieldCanonicalName = node.fieldReference.canonicalName;
+ if (fieldCanonicalName == null) {
+ throw new ArgumentError('Missing canonical name for $node');
+ }
+ String? fieldOrphancy = node.fieldReference.getOrphancyDescription(node);
+ if (fieldOrphancy != null) {
+ throw new ArgumentError('Trying to serialize orphaned field reference.\n'
+ '${fieldOrphancy}');
+ }
+ fieldOrphancy =
+ fieldCanonicalName.getOrphancyDescription(node, node.fieldReference);
+ if (fieldOrphancy != null) {
+ throw new ArgumentError(
+ 'Trying to serialize orphaned field canonical name.\n'
+ '(${node.runtimeType}:${node.hashCode})\n'
+ '${fieldOrphancy}');
+ }
+
CanonicalName? getterCanonicalName = node.getterReference.canonicalName;
if (getterCanonicalName == null) {
throw new ArgumentError('Missing canonical name for $node');
@@ -1367,6 +1385,7 @@
}
enterScope(memberScope: true);
writeByte(Tag.Field);
+ writeNonNullCanonicalNameReference(fieldCanonicalName);
writeNonNullCanonicalNameReference(getterCanonicalName);
writeNullAllowedCanonicalNameReference(setterCanonicalName);
writeUriReference(node.fileUri);
diff --git a/pkg/kernel/lib/binary/tag.dart b/pkg/kernel/lib/binary/tag.dart
index b628f87..8fb7add 100644
--- a/pkg/kernel/lib/binary/tag.dart
+++ b/pkg/kernel/lib/binary/tag.dart
@@ -176,7 +176,7 @@
/// Internal version of kernel binary format.
/// Bump it when making incompatible changes in kernel binaries.
/// Keep in sync with runtime/vm/kernel_binary.h, pkg/kernel/binary.md.
- static const int BinaryFormatVersion = 72;
+ static const int BinaryFormatVersion = 73;
}
abstract class ConstantTag {
diff --git a/pkg/kernel/lib/canonical_name.dart b/pkg/kernel/lib/canonical_name.dart
index bd151c1..651c2d6 100644
--- a/pkg/kernel/lib/canonical_name.dart
+++ b/pkg/kernel/lib/canonical_name.dart
@@ -30,7 +30,12 @@
/// "@constructors"
/// Qualified name
///
-/// Field or the implicit getter of a field:
+/// Field:
+/// Canonical name of enclosing class or library
+/// "@fields"
+/// Qualified name
+///
+/// Implicit getter of a field:
/// Canonical name of enclosing class or library
/// "@getters"
/// Qualified name
@@ -137,6 +142,10 @@
}
CanonicalName getChildFromField(Field field) {
+ return getChild(fieldsName).getChildFromQualifiedName(field.name);
+ }
+
+ CanonicalName getChildFromFieldGetter(Field field) {
return getChild(gettersName).getChildFromQualifiedName(field.name);
}
@@ -156,6 +165,10 @@
}
CanonicalName getChildFromFieldWithName(Name name) {
+ return getChild(fieldsName).getChildFromQualifiedName(name);
+ }
+
+ CanonicalName getChildFromFieldGetterWithName(Name name) {
return getChild(gettersName).getChildFromQualifiedName(name);
}
@@ -339,6 +352,10 @@
/// within a library or a class.
static const String methodsName = '@methods';
+ /// Symbolic name used for the [CanonicalName] node that holds all fields
+ /// within a library or class.
+ static const String fieldsName = '@fields';
+
/// Symbolic name used for the [CanonicalName] node that holds all getters and
/// readable fields within a library or class.
static const String gettersName = '@getters';
@@ -355,6 +372,7 @@
constructorsName,
factoriesName,
methodsName,
+ fieldsName,
gettersName,
settersName,
typedefsName,
@@ -513,12 +531,15 @@
bool get isConsistent {
NamedNode? node = _node;
if (node != null) {
- if (node.reference != this &&
- (node is! Field || node.setterReference != this)) {
- // The reference of a [NamedNode] must point to this reference, or
- // if the node is a [Field] the setter reference must point to this
- // reference.
- return false;
+ if (node is Field) {
+ // The field, getter or setter reference of the [Field] must point to
+ // this reference.
+ return node.fieldReference == this ||
+ node.getterReference == this ||
+ node.setterReference == this;
+ } else {
+ // The reference of the [NamedNode] must point to this reference.
+ return node.reference == this;
}
}
if (canonicalName != null && canonicalName!._reference != this) {
@@ -529,12 +550,16 @@
String getInconsistency() {
StringBuffer sb = new StringBuffer();
- sb.write('Reference ${this} (${hashCode}):');
+ sb.write('Reference ${toStringInternal()} (${hashCode}):');
NamedNode? node = _node;
if (node != null) {
if (node is Field) {
- if (node.getterReference != this && node.setterReference != this) {
+ if (node.fieldReference != this &&
+ node.getterReference != this &&
+ node.setterReference != this) {
sb.write(' _node=${node} (${node.runtimeType}:${node.hashCode})');
+ sb.write(' _node.fieldReference='
+ '${node.fieldReference} (${node.fieldReference.hashCode})');
sb.write(' _node.getterReference='
'${node.getterReference} (${node.getterReference.hashCode})');
sb.write(' _node.setterReference='
diff --git a/pkg/kernel/lib/clone.dart b/pkg/kernel/lib/clone.dart
index fec13cc..a407a13 100644
--- a/pkg/kernel/lib/clone.dart
+++ b/pkg/kernel/lib/clone.dart
@@ -903,8 +903,8 @@
return result;
}
- Field cloneField(
- Field node, Reference? getterReference, Reference? setterReference) {
+ Field cloneField(Field node, Reference? fieldReference,
+ Reference? getterReference, Reference? setterReference) {
final Uri? activeFileUriSaved = _activeFileUri;
_activeFileUri = node.fileUri;
@@ -915,6 +915,7 @@
initializer: cloneOptional(node.initializer),
transformerFlags: node.transformerFlags,
fileUri: node.fileUri,
+ fieldReference: fieldReference,
getterReference: getterReference,
setterReference: setterReference);
} else {
@@ -927,6 +928,7 @@
initializer: cloneOptional(node.initializer),
transformerFlags: node.transformerFlags,
fileUri: node.fileUri,
+ fieldReference: fieldReference,
getterReference: getterReference);
}
result
diff --git a/pkg/kernel/lib/external_name.dart b/pkg/kernel/lib/external_name.dart
index 367b5d2..c93f975 100644
--- a/pkg/kernel/lib/external_name.dart
+++ b/pkg/kernel/lib/external_name.dart
@@ -44,11 +44,11 @@
return (constant.fieldValues.values.single as StringConstant).value;
} else if (_isPragma(constant.classNode)) {
final String pragmaName =
- (constant.fieldValues[coreTypes.pragmaName.getterReference]
+ (constant.fieldValues[coreTypes.pragmaName.fieldReference]
as StringConstant)
.value;
final Constant? pragmaOptionsValue =
- constant.fieldValues[coreTypes.pragmaOptions.getterReference];
+ constant.fieldValues[coreTypes.pragmaOptions.fieldReference];
final String? pragmaOptions = pragmaOptionsValue is StringConstant
? pragmaOptionsValue.value
: null;
diff --git a/pkg/kernel/lib/reference_from_index.dart b/pkg/kernel/lib/reference_from_index.dart
index 1c93f7c..6182a4b 100644
--- a/pkg/kernel/lib/reference_from_index.dart
+++ b/pkg/kernel/lib/reference_from_index.dart
@@ -31,9 +31,11 @@
}
abstract class IndexedContainer {
+ final Map<Name, Reference> _fieldReferences = new Map<Name, Reference>();
final Map<Name, Reference> _getterReferences = new Map<Name, Reference>();
final Map<Name, Reference> _setterReferences = new Map<Name, Reference>();
+ Reference? lookupFieldReference(Name name) => _fieldReferences[name];
Reference? lookupGetterReference(Name name) => _getterReferences[name];
Reference? lookupSetterReference(Name name) => _setterReferences[name];
@@ -63,6 +65,8 @@
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
Name name = field.name;
+ assert(_fieldReferences[name] == null);
+ _fieldReferences[name] = field.fieldReference;
assert(_getterReferences[name] == null);
_getterReferences[name] = field.getterReference;
if (field.hasSetter) {
diff --git a/pkg/kernel/lib/src/equivalence.dart b/pkg/kernel/lib/src/equivalence.dart
index b307532..4a163b9 100644
--- a/pkg/kernel/lib/src/equivalence.dart
+++ b/pkg/kernel/lib/src/equivalence.dart
@@ -1642,6 +1642,9 @@
if (!checkField_initializer(visitor, node, other)) {
result = visitor.resultOnInequivalence;
}
+ if (!checkField_getterReference(visitor, node, other)) {
+ result = visitor.resultOnInequivalence;
+ }
if (!checkField_setterReference(visitor, node, other)) {
result = visitor.resultOnInequivalence;
}
@@ -1660,7 +1663,7 @@
if (!checkField_transformerFlags(visitor, node, other)) {
result = visitor.resultOnInequivalence;
}
- if (!checkField_getterReference(visitor, node, other)) {
+ if (!checkField_fieldReference(visitor, node, other)) {
result = visitor.resultOnInequivalence;
}
if (!checkField_fileOffset(visitor, node, other)) {
@@ -4915,6 +4918,12 @@
node.initializer, other.initializer, 'initializer');
}
+ bool checkField_getterReference(
+ EquivalenceVisitor visitor, Field node, Field other) {
+ return visitor.checkReferences(
+ node.getterReference, other.getterReference, 'getterReference');
+ }
+
bool checkField_setterReference(
EquivalenceVisitor visitor, Field node, Field other) {
return visitor.checkReferences(
@@ -4971,10 +4980,10 @@
return checkMember_transformerFlags(visitor, node, other);
}
- bool checkField_getterReference(
+ bool checkField_fieldReference(
EquivalenceVisitor visitor, Field node, Field other) {
return visitor.checkReferences(
- node.getterReference, other.getterReference, 'getterReference');
+ node.fieldReference, other.fieldReference, 'fieldReference');
}
bool checkMember_fileOffset(
diff --git a/pkg/kernel/lib/transformations/mixin_full_resolution.dart b/pkg/kernel/lib/transformations/mixin_full_resolution.dart
index d05be90..fee2089 100644
--- a/pkg/kernel/lib/transformations/mixin_full_resolution.dart
+++ b/pkg/kernel/lib/transformations/mixin_full_resolution.dart
@@ -120,6 +120,8 @@
}
for (var field in class_.mixin.fields) {
+ Reference? fieldReference =
+ indexedClass?.lookupFieldReference(field.name);
Reference? getterReference =
indexedClass?.lookupGetterReference(field.name);
Reference? setterReference =
@@ -132,8 +134,8 @@
setterReference = setters[field.name]?.reference;
setterReference?.canonicalName?.unbind();
}
- Field clone =
- cloner.cloneField(field, getterReference, setterReference);
+ Field clone = cloner.cloneField(
+ field, fieldReference, getterReference, setterReference);
Procedure? setter = setters[field.name];
if (setter != null) {
setters.remove(field.name);
diff --git a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart
index cc4d1387..318b720 100644
--- a/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart
+++ b/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart
@@ -352,9 +352,12 @@
type:
new InterfaceType(_locationClass, clazz.enclosingLibrary.nullable),
isFinal: true,
- getterReference: clazz.reference.canonicalName
+ fieldReference: clazz.reference.canonicalName
?.getChildFromFieldWithName(fieldName)
.reference,
+ getterReference: clazz.reference.canonicalName
+ ?.getChildFromFieldGetterWithName(fieldName)
+ .reference,
fileUri: clazz.fileUri);
clazz.addField(locationField);
diff --git a/pkg/kernel/test/clone_test.dart b/pkg/kernel/test/clone_test.dart
index 2a8cfb6..5501551 100644
--- a/pkg/kernel/test/clone_test.dart
+++ b/pkg/kernel/test/clone_test.dart
@@ -96,7 +96,7 @@
void testFields(Iterable<Field> fields) {
testMembers<Field>(
fields,
- (cloner, field) => cloner.cloneField(field, null, null),
+ (cloner, field) => cloner.cloneField(field, null, null, null),
(field) => "${field.runtimeType}(${field.name}):"
"${field.initializer}");
}
@@ -198,6 +198,8 @@
bool checkField(EquivalenceVisitor visitor, Field? node, Object? other) {
if (node is Field && other is Field) {
assumeClonedReferences(
+ visitor, node, node.fieldReference, other, other.fieldReference);
+ assumeClonedReferences(
visitor, node, node.getterReference, other, other.getterReference);
assumeClonedReferences(
visitor, node, node.setterReference, other, other.setterReference);
diff --git a/pkg/nnbd_migration/lib/nnbd_migration.dart b/pkg/nnbd_migration/lib/nnbd_migration.dart
index d184cb0..a780bfa 100644
--- a/pkg/nnbd_migration/lib/nnbd_migration.dart
+++ b/pkg/nnbd_migration/lib/nnbd_migration.dart
@@ -124,7 +124,8 @@
/// Informative message: there is no valid migration for `null` in a
/// non-nullable context.
static const noValidMigrationForNull = NullabilityFixDescription._(
- appliedMessage: 'No valid migration for `null` in a non-nullable context',
+ appliedMessage: 'No valid migration for expression with type `Null` in '
+ 'a non-nullable context',
kind: NullabilityFixKind.noValidMigrationForNull);
/// Informative message: a null-aware access won't be necessary in strong
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 21b38d4..7524133 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -635,7 +635,7 @@
var resultType =
_fixBuilder!._typeSystem.promoteToNonNull(type as TypeImpl);
_flowAnalysis!.nonNullAssert_end(node);
- return node is NullLiteral && hint == null
+ return type.isDartCoreNull && hint == null
? NoValidMigrationChange(resultType)
: NullCheckChange(resultType, hint: hint);
}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 9815198..7e35d77 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -6746,6 +6746,24 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_null_typed_expression_wiithout_valid_migration() async {
+ var content = '''
+void f(int/*!*/ x) {}
+void g() {
+ f(h());
+}
+Null h() => null;
+''';
+ var expected = '''
+void f(int x) {}
+void g() {
+ f(h());
+}
+Null h() => null;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_nullable_use_of_typedef() async {
var content = '''
typedef F<T> = int Function(T);
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index 242c69f..76c2bca 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -2307,6 +2307,22 @@
changes: {findNode.simple('x &&'): isNullCheck});
}
+ Future<void> test_nullExpression_noValidMigration() async {
+ await analyze('''
+int/*!*/ f() => g();
+Null g() => null;
+''');
+ var invocation = findNode.methodInvocation('g();');
+ // Note: in spite of the fact that we leave the method invocation alone, we
+ // analyze it as though it has type `Never`, because it's in a context where
+ // `null` doesn't work.
+ visitSubexpression(invocation, 'Never', changes: {
+ invocation: isNodeChangeForExpression.havingNoValidMigrationWithInfo(
+ isInfo(NullabilityFixDescription.noValidMigrationForNull,
+ {FixReasonTarget.root: TypeMatcher<NullabilityEdge>()}))
+ });
+ }
+
Future<void> test_nullLiteral() async {
await analyze('''
f() => null;
diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart
index 58aae19..25e2ac8 100644
--- a/pkg/vm/lib/transformations/ffi.dart
+++ b/pkg/vm/lib/transformations/ffi.dart
@@ -467,8 +467,8 @@
'dart:ffi',
'DynamicLibraryExtension',
LibraryIndex.tearoffPrefix + 'lookupFunction'),
- getNativeFieldFunction =
- index.getTopLevelProcedure('dart:nativewrappers', 'getNativeField'),
+ getNativeFieldFunction = index.getTopLevelProcedure(
+ 'dart:nativewrappers', '_getNativeField'),
reachabilityFenceFunction =
index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
nativeFieldWrapperClassType =
diff --git a/pkg/vm/lib/transformations/ffi_definitions.dart b/pkg/vm/lib/transformations/ffi_definitions.dart
index 7157845..b0aa9a5 100644
--- a/pkg/vm/lib/transformations/ffi_definitions.dart
+++ b/pkg/vm/lib/transformations/ffi_definitions.dart
@@ -696,9 +696,9 @@
final constant = annotation.constant;
if (constant is InstanceConstant &&
constant.classNode == pragmaClass &&
- constant.fieldValues[pragmaName.getterReference] ==
+ constant.fieldValues[pragmaName.fieldReference] ==
StringConstant(vmFfiStructFields)) {
- return constant.fieldValues[pragmaOptions.getterReference]
+ return constant.fieldValues[pragmaOptions.fieldReference]
as InstanceConstant?;
}
}
@@ -708,7 +708,7 @@
Set<Class> _compoundAnnotatedDependencies(InstanceConstant layoutConstant) {
final fieldTypes = layoutConstant
- .fieldValues[ffiStructLayoutTypesField.getterReference] as ListConstant;
+ .fieldValues[ffiStructLayoutTypesField.fieldReference] as ListConstant;
final result = <Class>{};
for (final fieldType in fieldTypes.entries) {
if (fieldType is TypeLiteralConstant) {
@@ -726,7 +726,7 @@
CompoundNativeTypeCfe _compoundAnnotatedNativeTypeCfe(Class compoundClass) {
final layoutConstant = _compoundAnnotatedFields(compoundClass)!;
final fieldTypes = layoutConstant
- .fieldValues[ffiStructLayoutTypesField.getterReference] as ListConstant;
+ .fieldValues[ffiStructLayoutTypesField.fieldReference] as ListConstant;
final members = <NativeTypeCfe>[];
for (final fieldType in fieldTypes.entries) {
if (fieldType is TypeLiteralConstant) {
@@ -734,14 +734,14 @@
members
.add(NativeTypeCfe(this, dartType, compoundCache: compoundCache));
} else if (fieldType is InstanceConstant) {
- final singleElementConstant = fieldType
- .fieldValues[ffiInlineArrayElementTypeField.getterReference]
- as TypeLiteralConstant;
+ final singleElementConstant =
+ fieldType.fieldValues[ffiInlineArrayElementTypeField.fieldReference]
+ as TypeLiteralConstant;
final singleElementType = NativeTypeCfe(
this, singleElementConstant.type,
compoundCache: compoundCache);
final arrayLengthConstant =
- fieldType.fieldValues[ffiInlineArrayLengthField.getterReference]
+ fieldType.fieldValues[ffiInlineArrayLengthField.fieldReference]
as IntConstant;
final arrayLength = arrayLengthConstant.value;
members.add(ArrayNativeTypeCfe(singleElementType, arrayLength));
@@ -749,7 +749,7 @@
}
if (compoundClass.superclass == structClass) {
final packingConstant = layoutConstant
- .fieldValues[ffiStructLayoutPackingField.getterReference];
+ .fieldValues[ffiStructLayoutPackingField.fieldReference];
if (packingConstant is IntConstant) {
return StructNativeTypeCfe(compoundClass, members,
packing: packingConstant.value);
@@ -767,12 +767,12 @@
node.addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
- pragmaName.getterReference: StringConstant(vmFfiStructFields),
- pragmaOptions.getterReference:
+ pragmaName.fieldReference: StringConstant(vmFfiStructFields),
+ pragmaOptions.fieldReference:
InstanceConstant(ffiStructLayoutClass.reference, [], {
- ffiStructLayoutTypesField.getterReference: ListConstant(
+ ffiStructLayoutTypesField.fieldReference: ListConstant(
InterfaceType(typeClass, Nullability.nonNullable), constants),
- ffiStructLayoutPackingField.getterReference:
+ ffiStructLayoutPackingField.fieldReference:
packing == null ? NullConstant() : IntConstant(packing)
})
}),
@@ -850,8 +850,8 @@
..isNonNullableByDefault = true
..addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, /*type_arguments=*/ [], {
- pragmaName.getterReference: StringConstant("vm:prefer-inline"),
- pragmaOptions.getterReference: NullConstant(),
+ pragmaName.fieldReference: StringConstant("vm:prefer-inline"),
+ pragmaOptions.fieldReference: NullConstant(),
})));
compound.addProcedure(getter);
@@ -887,7 +887,7 @@
List<int> _arraySize(InstanceConstant constant) {
final dimensions =
- constant.fieldValues[arraySizeDimensionsField.getterReference];
+ constant.fieldValues[arraySizeDimensionsField.fieldReference];
if (dimensions != null) {
if (dimensions is ListConstant) {
final result = dimensions.entries
@@ -905,7 +905,7 @@
arraySizeDimension5Field
];
final result = dimensionFields
- .map((f) => constant.fieldValues[f.getterReference])
+ .map((f) => constant.fieldValues[f.fieldReference])
.whereType<IntConstant>()
.map((c) => c.value)
.toList();
@@ -1414,9 +1414,9 @@
@override
Constant generateConstant(FfiTransformer transformer) =>
InstanceConstant(transformer.ffiInlineArrayClass.reference, [], {
- transformer.ffiInlineArrayElementTypeField.getterReference:
+ transformer.ffiInlineArrayElementTypeField.fieldReference:
singleElementType.generateConstant(transformer),
- transformer.ffiInlineArrayLengthField.getterReference:
+ transformer.ffiInlineArrayLengthField.fieldReference:
IntConstant(dimensionsFlattened)
});
diff --git a/pkg/vm/lib/transformations/ffi_native.dart b/pkg/vm/lib/transformations/ffi_native.dart
index 3d44d46..0752ed2 100644
--- a/pkg/vm/lib/transformations/ffi_native.dart
+++ b/pkg/vm/lib/transformations/ffi_native.dart
@@ -94,9 +94,9 @@
assert(currentLibrary != null);
final params = node.function.positionalParameters;
final functionName = annotationConst
- .fieldValues[ffiNativeNameField.getterReference] as StringConstant;
+ .fieldValues[ffiNativeNameField.fieldReference] as StringConstant;
final isLeaf = annotationConst
- .fieldValues[ffiNativeIsLeafField.getterReference] as BoolConstant;
+ .fieldValues[ffiNativeIsLeafField.fieldReference] as BoolConstant;
// double Function(double)
final DartType dartType =
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 312c1d6..a90f1ec 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -372,12 +372,12 @@
// overhead of converting Handles.
// If we find an NFWC1 object being passed to an FfiNative signature
// taking a Pointer, we automatically wrap the argument in a call to
- // `Pointer.fromAddress(getNativeField(obj))`.
+ // `Pointer.fromAddress(_getNativeField(obj))`.
// Example:
// passAsPointer(ClassWithNativeField());
// Becomes, roughly:
// #t0 = PointerClassWithNativeField();
- // passAsPointer(Pointer.fromAddress(getNativeField(#t0)));
+ // passAsPointer(Pointer.fromAddress(_getNativeField(#t0)));
// reachabilityFence(#t0);
final ffiNativeAnn = _tryGetFfiNativeAnnotation(target);
if (ffiNativeAnn != null) {
@@ -404,7 +404,7 @@
isFinal: true);
tmpsArgs.add(tmpPtr);
- // Pointer.fromAddress(getNativeField(#t1)).
+ // Pointer.fromAddress(_getNativeField(#t1)).
final ptr = StaticInvocation(
fromAddressInternal,
Arguments([
@@ -430,7 +430,7 @@
// {
// T #t0;
// final NativeFieldWrapperClass1 #t1 = MyNFWC1();
- // #t0 = foo(Pointer.fromAddress(getNativeField(#t1)));
+ // #t0 = foo(Pointer.fromAddress(_getNativeField(#t1)));
// reachabilityFence(#t1);
// } => #t0
final tmpResult =
diff --git a/pkg/vm/lib/transformations/pragma.dart b/pkg/vm/lib/transformations/pragma.dart
index dfac713..2fabcf4 100644
--- a/pkg/vm/lib/transformations/pragma.dart
+++ b/pkg/vm/lib/transformations/pragma.dart
@@ -78,7 +78,7 @@
String pragmaName;
Constant? name =
- pragmaConstant.fieldValues[coreTypes.pragmaName.getterReference];
+ pragmaConstant.fieldValues[coreTypes.pragmaName.fieldReference];
if (name is StringConstant) {
pragmaName = name.value;
} else {
@@ -86,7 +86,7 @@
}
Constant options =
- pragmaConstant.fieldValues[coreTypes.pragmaOptions.getterReference]!;
+ pragmaConstant.fieldValues[coreTypes.pragmaOptions.fieldReference]!;
switch (pragmaName) {
case kEntryPointPragmaName:
diff --git a/pkg/vm/lib/transformations/type_flow/protobuf_handler.dart b/pkg/vm/lib/transformations/type_flow/protobuf_handler.dart
index c0175a4..ffc4b1e 100644
--- a/pkg/vm/lib/transformations/type_flow/protobuf_handler.dart
+++ b/pkg/vm/lib/transformations/type_flow/protobuf_handler.dart
@@ -116,7 +116,7 @@
if (constant is InstanceConstant &&
constant.classReference == _tagNumberClass.reference) {
if (messageClass._usedTags.add((constant
- .fieldValues[_tagNumberField.getterReference] as IntConstant)
+ .fieldValues[_tagNumberField.fieldReference] as IntConstant)
.value)) {
_invalidatedClasses.add(messageClass);
}
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index f37aa3a..1868a4d 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -1757,6 +1757,11 @@
// write a dangling reference to the deleted member.
if (node is Field) {
assert(
+ node.fieldReference.node == node,
+ "Trying to remove canonical name from field reference on $node "
+ "which has been repurposed for ${node.fieldReference.node}.");
+ node.fieldReference.canonicalName?.unbind();
+ assert(
node.getterReference.node == node,
"Trying to remove canonical name from getter reference on $node "
"which has been repurposed for ${node.getterReference.node}.");
diff --git a/pkg/vm/test/incremental_compiler_test.dart b/pkg/vm/test/incremental_compiler_test.dart
index 1623621..b24cb9e 100644
--- a/pkg/vm/test/incremental_compiler_test.dart
+++ b/pkg/vm/test/incremental_compiler_test.dart
@@ -1045,9 +1045,9 @@
}
});
- /// This test basicaly verifies that components `relink` method is correctly
- /// called when rejecting (i.e. logically going back in time to before a
- /// rejected compilation).
+ /// This test basically verifies that components `relink` method is
+ /// correctly called when rejecting (i.e. logically going back in time to
+ /// before a rejected compilation).
test('check links after reject', () async {
final Uri fooUri = Uri.file('${mytest.path}/foo.dart');
new File.fromUri(fooUri).writeAsStringSync("""
diff --git a/runtime/vm/compiler/frontend/constant_reader.cc b/runtime/vm/compiler/frontend/constant_reader.cc
index 2ed7d14..a6bc6e5 100644
--- a/runtime/vm/compiler/frontend/constant_reader.cc
+++ b/runtime/vm/compiler/frontend/constant_reader.cc
@@ -403,8 +403,7 @@
Field& field = Field::Handle(Z);
Instance& constant = Instance::Handle(Z);
for (intptr_t j = 0; j < number_of_fields; ++j) {
- field = H.LookupFieldByKernelGetterOrSetter(
- reader.ReadCanonicalNameReference());
+ field = H.LookupFieldByKernelField(reader.ReadCanonicalNameReference());
// Recurse into lazily evaluating all "sub" constants
// needed to evaluate the current constant.
const intptr_t entry_index = reader.ReadUInt();
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index d74fc3d..8c1f3bc 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -238,7 +238,7 @@
ReadBool();
const NameIndex field_name = ReadCanonicalNameReference();
const Field& field =
- Field::Handle(Z, H.LookupFieldByKernelGetterOrSetter(field_name));
+ Field::Handle(Z, H.LookupFieldByKernelField(field_name));
initializer_fields[i] = &field;
SkipExpression();
continue;
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index 7406e5a..db4e074 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -316,8 +316,22 @@
return StringEquals(CanonicalNameString(kind), "@factories");
}
+bool TranslationHelper::IsField(NameIndex name) {
+ // Fields with private names have the import URI of the library where they
+ // are visible as the parent and the string "@fields" as the parent's parent.
+ // Fields with non-private names have the string "@fields" as the parent.
+ if (IsRoot(name)) {
+ return false;
+ }
+ NameIndex kind = CanonicalNameParent(name);
+ if (IsPrivate(name)) {
+ kind = CanonicalNameParent(kind);
+ }
+ return StringEquals(CanonicalNameString(kind), "@fields");
+}
+
NameIndex TranslationHelper::EnclosingName(NameIndex name) {
- ASSERT(IsConstructor(name) || IsProcedure(name));
+ ASSERT(IsConstructor(name) || IsProcedure(name) || IsField(name));
NameIndex enclosing = CanonicalNameParent(CanonicalNameParent(name));
if (IsPrivate(name)) {
enclosing = CanonicalNameParent(enclosing);
@@ -574,6 +588,27 @@
return info_.InsertClass(thread_, name_index_handle_, klass);
}
+FieldPtr TranslationHelper::LookupFieldByKernelField(NameIndex kernel_field) {
+ ASSERT(IsField(kernel_field));
+ NameIndex enclosing = EnclosingName(kernel_field);
+
+ Class& klass = Class::Handle(Z);
+ if (IsLibrary(enclosing)) {
+ Library& library =
+ Library::Handle(Z, LookupLibraryByKernelLibrary(enclosing));
+ klass = library.toplevel_class();
+ CheckStaticLookup(klass);
+ } else {
+ ASSERT(IsClass(enclosing));
+ klass = LookupClassByKernelClass(enclosing);
+ }
+ Field& field = Field::Handle(
+ Z, klass.LookupFieldAllowPrivate(
+ DartSymbolObfuscate(CanonicalNameString(kernel_field))));
+ CheckStaticLookup(field);
+ return field.ptr();
+}
+
FieldPtr TranslationHelper::LookupFieldByKernelGetterOrSetter(
NameIndex kernel_field,
bool required) {
@@ -1006,6 +1041,11 @@
if (++next_read_ == field) return;
}
FALL_THROUGH;
+ case kCanonicalNameField:
+ canonical_name_field_ =
+ helper_->ReadCanonicalNameReference(); // read canonical_name_field.
+ if (++next_read_ == field) return;
+ FALL_THROUGH;
case kCanonicalNameGetter:
canonical_name_getter_ =
helper_->ReadCanonicalNameReference(); // read canonical_name_getter.
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index d7e5542..c854d07 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -109,6 +109,7 @@
bool IsGetter(NameIndex name);
bool IsSetter(NameIndex name);
bool IsFactory(NameIndex name);
+ bool IsField(NameIndex name);
// For a member (field, constructor, or procedure) return the canonical name
// of the enclosing class or library.
@@ -165,6 +166,7 @@
virtual LibraryPtr LookupLibraryByKernelLibrary(NameIndex library);
virtual ClassPtr LookupClassByKernelClass(NameIndex klass);
+ FieldPtr LookupFieldByKernelField(NameIndex field);
FieldPtr LookupFieldByKernelGetterOrSetter(NameIndex field,
bool required = true);
FunctionPtr LookupStaticMethodByKernelProcedure(NameIndex procedure,
@@ -444,6 +446,7 @@
public:
enum Field {
kStart, // tag.
+ kCanonicalNameField,
kCanonicalNameGetter,
kCanonicalNameSetter,
kSourceUriIndex,
@@ -491,6 +494,7 @@
bool IsLate() const { return (flags_ & kIsLate) != 0; }
bool IsExtensionMember() const { return (flags_ & kExtensionMember) != 0; }
+ NameIndex canonical_name_field_;
NameIndex canonical_name_getter_;
NameIndex canonical_name_setter_;
TokenPosition position_ = TokenPosition::kNoSource;
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index bc55064..862b338 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -221,7 +221,7 @@
V(::, _storePointer, FfiStorePointer, 0xea6b7751) \
V(::, _fromAddress, FfiFromAddress, 0xfd8cb1cc) \
V(Pointer, get:address, FfiGetAddress, 0x7cde87be) \
- V(::, getNativeField, GetNativeField, 0x95b4ec94) \
+ V(::, _getNativeField, GetNativeField, 0xa0139b85) \
V(::, reachabilityFence, ReachabilityFence, 0x619235c1) \
V(_Utf8Decoder, _scan, Utf8DecoderScan, 0x1dcaf73d) \
V(_Future, timeout, FutureTimeout, 0x73041520) \
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 4d9d1df..dada22e 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -4241,20 +4241,39 @@
return reinterpret_cast<Dart_NativeFunction>(Builtin_SecretKeeper_KeepSecret);
}
+static intptr_t ReturnPtrAsInt(void* ptr) {
+ return reinterpret_cast<intptr_t>(ptr);
+}
+
+static void* SecretKeeperFfiNativeResolver(const char* name, uintptr_t argn) {
+ if (strcmp(name, "returnPtrAsInt") == 0 && argn == 1) {
+ return reinterpret_cast<void*>(&ReturnPtrAsInt);
+ }
+ return nullptr;
+}
+
TEST_CASE(DartAPI_NativeFieldAccess) {
const char* kScriptChars = R"(
+ import 'dart:ffi';
import 'dart:nativewrappers';
class SecretKeeper extends NativeFieldWrapperClass1 {
SecretKeeper(int secret) { _keepSecret(secret); }
@pragma("vm:external-name", "SecretKeeper_KeepSecret")
external void _keepSecret(int secret);
}
- main() => getNativeField(SecretKeeper(321));
+ // Argument auto-conversion will wrap `o` in `_getNativeField()`.
+ @FfiNative<IntPtr Function(Pointer<Void>)>('returnPtrAsInt')
+ external int returnPtrAsInt(NativeFieldWrapperClass1 o);
+ main() => returnPtrAsInt(SecretKeeper(321));
)";
Dart_Handle result;
Dart_Handle lib =
TestCase::LoadTestScript(kScriptChars, SecretKeeperNativeResolver);
+
+ result = Dart_SetFfiNativeResolver(lib, &SecretKeeperFfiNativeResolver);
+ EXPECT_VALID(result);
+
result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
EXPECT_VALID(result);
@@ -4265,19 +4284,28 @@
EXPECT_EQ(321, value);
}
+// Test that trying to access an unset native field (internally through
+// _getNativeField(..)) will result in a Dart exception (and not crash).
TEST_CASE(DartAPI_NativeFieldAccess_Throws) {
const char* kScriptChars = R"(
+ import 'dart:ffi';
import 'dart:nativewrappers';
class ForgetfulSecretKeeper extends NativeFieldWrapperClass1 {
ForgetfulSecretKeeper(int secret) { /* Forget to init. native field. */ }
}
- main() => getNativeField(ForgetfulSecretKeeper(321));
+ // Argument auto-conversion will wrap `o` in `_getNativeField()`.
+ @FfiNative<IntPtr Function(Pointer<Void>)>('returnPtrAsInt')
+ external int returnPtrAsInt(NativeFieldWrapperClass1 o);
+ main() => returnPtrAsInt(ForgetfulSecretKeeper(321));
)";
Dart_Handle result;
Dart_Handle lib =
TestCase::LoadTestScript(kScriptChars, SecretKeeperNativeResolver);
+ result = Dart_SetFfiNativeResolver(lib, &SecretKeeperFfiNativeResolver);
+ EXPECT_VALID(result);
+
result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
EXPECT(Dart_IsError(result));
diff --git a/runtime/vm/kernel_binary.h b/runtime/vm/kernel_binary.h
index 13f777c..65b7bd5 100644
--- a/runtime/vm/kernel_binary.h
+++ b/runtime/vm/kernel_binary.h
@@ -20,8 +20,8 @@
static const uint32_t kMagicProgramFile = 0x90ABCDEFu;
// Both version numbers are inclusive.
-static const uint32_t kMinSupportedKernelFormatVersion = 72;
-static const uint32_t kMaxSupportedKernelFormatVersion = 72;
+static const uint32_t kMinSupportedKernelFormatVersion = 73;
+static const uint32_t kMaxSupportedKernelFormatVersion = 73;
// Keep in sync with package:kernel/lib/binary/tag.dart
#define KERNEL_TAG_LIST(V) \
diff --git a/sdk/lib/async/future.dart b/sdk/lib/async/future.dart
index 555bc3d..e43b92a 100644
--- a/sdk/lib/async/future.dart
+++ b/sdk/lib/async/future.dart
@@ -7,8 +7,8 @@
/// A type representing values that are either `Future<T>` or `T`.
///
/// This class declaration is a public stand-in for an internal
-/// future-or-value generic type. References to this class are resolved to the
-/// internal type.
+/// future-or-value generic type, which is not a class type.
+/// References to this class are resolved to the internal type.
///
/// It is a compile-time error for any class to extend, mix in or implement
/// `FutureOr`.
@@ -43,35 +43,109 @@
}
}
-/// An object representing a delayed computation.
+/// The result of an asynchronous computation.
///
-/// A [Future] is used to represent a potential value, or error,
-/// that will be available at some time in the future.
-/// Receivers of a [Future] can register callbacks
-/// that handle the value or error once it is available.
+/// An _asynchronous computation_ cannot provide a result immediately
+/// when it is started, unlike a synchronous computation which does compute
+/// a result immediately by either returning a value or by throwing.
+/// An asynchronous computation may need to wait for something external
+/// to the program (reading a file, querying a database, fetching a web page)
+/// which takes time.
+/// Instead of blocking all computation until the result is available,
+/// the asynchronous computation immediately returns a `Future`
+/// which will *eventually* "complete" with the result.
+///
+/// ### Asynchronous programming
+///
+/// To perform an asynchronous computation, you use an `async` function
+/// which always produces a future.
+/// Inside such an asynchronous function, you can use the `await` operation
+/// to delay execution until another asyncronous computation has a result.
+/// While execution of the awaiting function is delayed,
+/// the program is not blocked, and can continue doing other things.
+///
+/// Example:
+/// ```dart
+/// import "dart:io";
+/// Future<bool> fileContains(String path, String needle) async {
+/// var haystack = await File(path).readAsString();
+/// return haystack.contains(needle);
+/// }
+/// ```
+/// Here the `File.readAsString` method from `dart:io` is an asychronous
+/// function returning a `Future<String>`.
+/// The `fileContains` function is marked with `async` right before its body,
+/// which means that you can use `await` insider it,
+/// and that it must return a future.
+/// The call to `File(path).readAsString()` initiates reading the file into
+/// a string and produces a `Future<String>` which will eventually contain the
+/// result.
+/// The `await` then waits for that future to complete with a string
+/// (or an error, if reading the file fails).
+/// While waiting, the program can do other things.
+/// When the future completes with a string, the `fileContains` function
+/// computes a boolean and returns it, which then completes the original
+/// future that it returned when first called.
+///
+/// If a future completes with an *error*, awaiting that future will
+/// (re-)throw that error. In the example here, we can add error checking:
+/// ```dart
+/// import "dart:io";
+/// Future<bool> fileContains(String path, String needle) async {
+/// try {
+/// var haystack = await File(path).readAsString();
+/// return haystack.contains(needle);
+/// } on FileSystemException catch (exception, stack) {
+/// _myLog.logError(exception, stack);
+/// return false;
+/// }
+/// }
+/// ```
+/// You use a normal `try`/`catch` to catch the failures of awaited
+/// asynchronous computations.
+///
+/// In general, when writing asynchronous code, you should always await a
+/// future when it is produced, and not wait until after another asynchronous
+/// delay. That ensures that you are ready to receive any error that the
+/// future might produce, which is important because an asynchronous error
+/// that no-one is awaiting is an *uncaught* error and may terminate
+/// the running program.
+///
+/// ### Programming with the `Future` API.
+///
+/// The `Future` class also provides a more direct, low-level functionality
+/// for accessing the result that it completes with.
+/// The `async` and `await` language features are built on top of this
+/// functionality, and it sometimes makes sense to use it directly.
+/// There are things that you cannot do by just `await`ing one future at
+/// a time.
+///
+/// With a [Future], you can manually register callbacks
+/// that handle the value, or error, once it is available.
/// For example:
/// ```dart
/// Future<int> future = getFuture();
/// future.then((value) => handleValue(value))
/// .catchError((error) => handleError(error));
/// ```
-/// A [Future] can be completed in two ways:
-/// with a value ("the future succeeds")
-/// or with an error ("the future fails").
-/// Users can install callbacks for each case.
+/// Since a [Future] can be completed in two ways,
+/// either with a value (if the asynchronous computation succeeded)
+/// or with an error (if the computation failed),
+/// you can install callbacks for either or both cases.
///
-/// In some cases we say that a future is completed with another future.
+/// In some cases we say that a future is completed *with another future*.
/// This is a short way of stating that the future is completed in the same way,
/// with the same value or error,
-/// as the other future once that completes.
-/// Whenever a function in the core library may complete a future
+/// as the other future once that other future itself completes.
+/// Most functions in the platform libraries that complete a future
/// (for example [Completer.complete] or [Future.value]),
-/// then it also accepts another future and does this work for the developer.
+/// also accepts another future, and automatically handles forwarding
+/// the result to the future being completed.
///
-/// The result of registering a pair of callbacks is a Future (the
-/// "successor") which in turn is completed with the result of invoking the
-/// corresponding callback.
-/// The successor is completed with an error if the invoked callback throws.
+/// The result of registering callbacks is itself a `Future`,
+/// which in turn is completed with the result of invoking the
+/// corresponding callback with the original future's result.
+/// The new future is completed with an error if the invoked callback throws.
/// For example:
/// ```dart
/// Future<int> successor = future.then((int value) {
@@ -88,9 +162,9 @@
/// });
/// ```
///
-/// If a future does not have a successor when it completes with an error,
-/// it forwards the error message to an uncaught-error handler.
-/// This behavior makes sure that no error is silently dropped.
+/// If a future does not have any registered handler when it completes
+/// with an error, it forwards the error to an "uncaught-error handler".
+/// This behavior ensures that no error is silently dropped.
/// However, it also means that error handlers should be installed early,
/// so that they are present as soon as a future is completed with an error.
/// The following example demonstrates this potential bug:
@@ -128,9 +202,12 @@
///
/// Equivalent asynchronous code, based on futures:
/// ```dart
-/// Future<int> future = Future(foo); // Result of foo() as a future.
-/// future.then((int value) => bar(value))
-/// .catchError((e) => 499);
+/// Future<int> asyncValue = Future(foo); // Result of foo() as a future.
+/// asyncValue.then((int value) {
+/// return bar(value);
+/// }).catchError((e) {
+/// return 499;
+/// });
/// ```
///
/// Similar to the synchronous code, the error handler (registered with
@@ -142,7 +219,8 @@
/// treated independently and is handled as if it was the only successor.
///
/// A future may also fail to ever complete. In that case, no callbacks are
-/// called.
+/// called. That situation should generally be avoided if possible, unless
+/// it's very clearly documented.
abstract class Future<T> {
/// A `Future<Null>` completed with `null`.
///
diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart
index 7a3c80d..2083510 100644
--- a/sdk/lib/async/stream.dart
+++ b/sdk/lib/async/stream.dart
@@ -18,9 +18,61 @@
/// When a stream has emitted all its event,
/// a single "done" event will notify the listener that the end has been reached.
///
-/// You [listen] on a stream to make it start generating events,
-/// and to set up listeners that receive the events.
-/// When you listen, you receive a [StreamSubscription] object
+/// You produce a stream by calling an `async*` function, which then returns
+/// a stream. Consuming that stream will lead the function to emit events
+/// until it ends, and the stream closes.
+/// You consume a stream either using an `await for` loop, which is available
+/// inside an `async` or `async*` function, or forwards its events directly
+/// using `yield*` inside an `async*` function.
+/// Example:
+/// ```dart
+/// Stream<T> optionalMap<T>(
+/// Stream<T> source , [T Function(T)? convert]) async* {
+/// if (convert == null) {
+/// yield* source;
+/// } else {
+/// await for (var event in source) {
+/// yield convert(event);
+/// }
+/// }
+/// }
+/// ```
+/// When this function is called, it immediately returns a `Stream<T>` object.
+/// Then nothing further happens until someone tries to consume that stream.
+/// At that point, the body of the `async*` function starts running.
+/// If the `convert` function was omitted, the `yield*` will listen to the
+/// `source` stream and forward all events, date and errors, to the returned
+/// stream. When the `source` stream closes, the `yield*` is done,
+/// and the `optionalMap` function body ends too. This closes the returned
+/// stream.
+/// If a `convert` *is* supplied, the function instead listens on the source
+/// stream and enters an `await for` loop which
+/// repeatedly waits for the next data event.
+/// On a data event, it calls `convert` with the value and emits the result
+/// on the returned stream.
+/// If no error events are emitted by the `source` stream,
+/// the loop ends when the `source` stream does,
+/// then the `optionalMap` function body completes,
+/// which closes the returned stream.
+/// On an error event from the `source` stream,
+/// the `await for` that error is (re-)thrown which breaks the loop.
+/// The error then reaches the end of the `optionalMap` function body,
+/// since it's not caught.
+/// That makes the error be emitted on the returned stream, which then closes.
+///
+/// The `Stream` class also provides functionality which allows you to
+/// manually listen for events from a stream, or to convert a stream
+/// into another stream or into a future.
+///
+/// The [forEach] function corresponds to the `await for` loop,
+/// just as [Iterable.forEach] corresponds to a normal `for`/`in` loop.
+/// Like the loop, it will call a function for each data event and break on an
+/// error.
+///
+/// The more low-level [listen] method is what every other method is based on.
+/// You call `listen` on a stream to tell it that you want to receive
+/// events, and to registers the callbacks which will receive those events.
+/// When you call `listen`, you receive a [StreamSubscription] object
/// which is the active object providing the events,
/// and which can be used to stop listening again,
/// or to temporarily pause events from the subscription.
@@ -33,19 +85,21 @@
/// It doesn't start generating events until it has a listener,
/// and it stops sending events when the listener is unsubscribed,
/// even if the source of events could still provide more.
+/// The stream created by an `async*` function is a single-subscription stream,
+/// but each call to the function creates a new such stream.
///
/// Listening twice on a single-subscription stream is not allowed, even after
/// the first subscription has been canceled.
///
/// Single-subscription streams are generally used for streaming chunks of
-/// larger contiguous data like file I/O.
+/// larger contiguous data, like file I/O.
///
/// *A broadcast stream* allows any number of listeners, and it fires
/// its events when they are ready, whether there are listeners or not.
///
/// Broadcast streams are used for independent events/observers.
///
-/// If several listeners want to listen to a single subscription stream,
+/// If several listeners want to listen to a single-subscription stream,
/// use [asBroadcastStream] to create a broadcast stream on top of the
/// non-broadcast stream.
///
@@ -77,7 +131,8 @@
///
/// The default implementation of [isBroadcast] returns false.
/// A broadcast stream inheriting from [Stream] must override [isBroadcast]
-/// to return `true`.
+/// to return `true` if it wants to signal that it behaves like a broadcast
+/// stream.
abstract class Stream<T> {
Stream();
@@ -85,6 +140,7 @@
///
/// If mixins become compatible with const constructors, we may use a
/// stream mixin instead of extending Stream from a const class.
+ /// (They now are compatible. We still consider, but it's not urgent.)
const Stream._internal();
/// Creates an empty broadcast stream.
@@ -93,10 +149,10 @@
/// when it's listened to.
const factory Stream.empty() = _EmptyStream<T>;
- /// Creates a stream which emits a single data event before completing.
+ /// Creates a stream which emits a single data event before closing.
///
/// This stream emits a single data event of [value]
- /// and then completes with a done event.
+ /// and then closes with a done event.
///
/// Example:
/// ```dart
diff --git a/sdk/lib/html/dartium/nativewrappers.dart b/sdk/lib/html/dartium/nativewrappers.dart
index 39004b7..6aa47cd 100644
--- a/sdk/lib/html/dartium/nativewrappers.dart
+++ b/sdk/lib/html/dartium/nativewrappers.dart
@@ -20,4 +20,4 @@
/// future.
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "FullyRecognizedMethod_NoNative")
-external int getNativeField(NativeFieldWrapperClass1 object);
+external int _getNativeField(NativeFieldWrapperClass1 object);
diff --git a/tests/ffi/vmspecific_ffi_native_test.dart b/tests/ffi/vmspecific_ffi_native_test.dart
index 5264901..f9e1e39 100644
--- a/tests/ffi/vmspecific_ffi_native_test.dart
+++ b/tests/ffi/vmspecific_ffi_native_test.dart
@@ -122,7 +122,7 @@
Expect.equals(123456, passAsPointer(cwnf));
// Test that the transform to wrap NativeFieldWrapperClass1 objects in
- // getNativeField(..) doesn't violate the original argument's liveness.
+ // _getNativeField(..) doesn't violate the original argument's liveness.
Expect.equals(
314159,
passAsPointerAndValue(
diff --git a/tests/standalone/check_for_aot_snapshot_jit_test.dart b/tests/standalone/check_for_aot_snapshot_jit_test.dart
index d8b817c..92f39cd 100644
--- a/tests/standalone/check_for_aot_snapshot_jit_test.dart
+++ b/tests/standalone/check_for_aot_snapshot_jit_test.dart
@@ -33,8 +33,8 @@
Expect.isTrue(File(powTest).existsSync(),
"Can't locate dart$_execSuffix on this platform");
final d = Directory.systemTemp.createTempSync('aot_tmp');
- final kernelOutput = d.uri.resolve('pow_test.dill').path;
- final aotOutput = d.uri.resolve('pow_test.aot').path;
+ final kernelOutput = File.fromUri(d.uri.resolve('pow_test.dill')).path;
+ final aotOutput = File.fromUri(d.uri.resolve('pow_test.aot')).path;
final genKernelResult = runAndPrintOutput(
genKernel,
diff --git a/tests/standalone/io/process_run_test.dart b/tests/standalone/io/process_run_test.dart
index a4a470a..c229570 100644
--- a/tests/standalone/io/process_run_test.dart
+++ b/tests/standalone/io/process_run_test.dart
@@ -49,8 +49,7 @@
result = Process.runSync('${processTest.path}', []);
Process.killPid(result.pid);
} catch (e) {
- Expect.fail('System should find process_run_test executable');
- print(e);
+ Expect.fail('System should find process_run_test executable ($e)');
} finally {
// Clean up the temp files and directory
dir.deleteSync(recursive: true);
diff --git a/tests/standalone_2/check_for_aot_snapshot_jit_test.dart b/tests/standalone_2/check_for_aot_snapshot_jit_test.dart
index e9c3e24..d446a7b 100644
--- a/tests/standalone_2/check_for_aot_snapshot_jit_test.dart
+++ b/tests/standalone_2/check_for_aot_snapshot_jit_test.dart
@@ -33,8 +33,8 @@
Expect.isTrue(File(powTest).existsSync(),
"Can't locate dart$_execSuffix on this platform");
final d = Directory.systemTemp.createTempSync('aot_tmp');
- final kernelOutput = d.uri.resolve('pow_test.dill').path;
- final aotOutput = d.uri.resolve('pow_test.aot').path;
+ final kernelOutput = File.fromUri(d.uri.resolve('pow_test.dill')).path;
+ final aotOutput = File.fromUri(d.uri.resolve('pow_test.aot')).path;
final genKernelResult = runAndPrintOutput(
genKernel,
diff --git a/tests/standalone_2/io/process_run_test.dart b/tests/standalone_2/io/process_run_test.dart
index 5dfe13c..342b3c5 100644
--- a/tests/standalone_2/io/process_run_test.dart
+++ b/tests/standalone_2/io/process_run_test.dart
@@ -51,8 +51,7 @@
result = Process.runSync('${processTest.path}', []);
Process.killPid(result.pid);
} catch (e) {
- Expect.fail('System should find process_run_test executable');
- print(e);
+ Expect.fail('System should find process_run_test executable ($e)');
} finally {
// Clean up the temp files and directory
dir.deleteSync(recursive: true);
diff --git a/tools/VERSION b/tools/VERSION
index b5b8743..7776bae 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 146
+PRERELEASE 147
PRERELEASE_PATCH 0
\ No newline at end of file