Version 2.17.0-120.0.dev
Merge commit '30195a437b689cbb99bce81f891d340e2e5e2cd5' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 012897a..225f1ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,19 @@
throw UnsupportedError("connectionFactory not implemented");
```
+- **Breaking Change** [#48093](https://github.com/dart-lang/sdk/issues/48093):
+ `HttpClient` has a new `keyLog` property, which allows TLS keys to be logged
+ for debugging purposes. Classes that `implement HttpClient` may be broken by
+ this change. Add the following method to your classes to fix them:
+
+ ```dart
+ void set keyLog(Function(String line)? callback) =>
+ throw UnsupportedError("keyLog not implemented");
+ ```
+
+- Add a optional `keyLog` parameter to `SecureSocket.connect` and
+ `SecureSocket.startConnect`.
+
### Tools
#### Dart command line
diff --git a/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart b/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart
index 550e718..01cc969 100644
--- a/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart
+++ b/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart
@@ -8,7 +8,7 @@
import 'dart:isolate';
import 'dart:typed_data';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
void main() async {
for (var serializationMode in [
@@ -188,7 +188,7 @@
import 'dart:isolate';
import 'dart:typed_data';
- import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+ import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
void main(_, [SendPort? sendPort]) {
var mode = $mode;
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart b/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart
index 99c2ae5..ed29787 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'executor_shared/serialization.dart'
+import 'executor/serialization.dart'
show SerializationMode, SerializationModeHelpers;
/// Generates a Dart program for a given set of macros, which can be compiled
@@ -49,72 +49,106 @@
const String template = '''
import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/execute_macro.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/protocol.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/execute_macro.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/message_grouper.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/protocol.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
$_importMarker
-/// Entrypoint to be spawned with [Isolate.spawnUri].
+/// Entrypoint to be spawned with [Isolate.spawnUri] or [Process.start].
///
/// Supports the client side of the macro expansion protocol.
-void main(_, SendPort sendPort) {
- /// Local function that sends requests and returns responses using [sendPort].
- Future<Response> sendRequest(Request request) => _sendRequest(request, sendPort);
+void main(_, [SendPort? sendPort]) {
+ // Function that sends the result of a [Serializer] using either [sendPort]
+ // or [stdout].
+ void Function(Serializer) sendResult;
- /// TODO: More directly support customizable serialization types.
+ // The stream for incoming messages, could be either a ReceivePort or stdin.
+ Stream<Object?> messageStream;
+
withSerializationMode($_modeMarker, () {
- ReceivePort receivePort = new ReceivePort();
- sendPort.send(receivePort.sendPort);
+ if (sendPort != null) {
+ ReceivePort receivePort = new ReceivePort();
+ messageStream = receivePort;
+ sendResult = (Serializer serializer) =>
+ _sendIsolateResult(serializer, sendPort);
+ // If using isolate communication, first send a sendPort to the parent
+ // isolate.
+ sendPort.send(receivePort.sendPort);
+ } else {
+ sendResult = _sendStdoutResult;
+ if (serializationMode == SerializationMode.byteDataClient) {
+ messageStream = MessageGrouper(stdin).messageStream;
+ } else if (serializationMode == SerializationMode.jsonClient) {
+ messageStream = stdin
+ .transform(const Utf8Decoder())
+ .transform(const LineSplitter())
+ .map((line) => jsonDecode(line)!);
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported serialization mode \$serializationMode for '
+ 'ProcessExecutor');
+ }
+ }
- receivePort.listen((message) async {
- if (serializationMode == SerializationMode.byteDataClient
- && message is TransferableTypedData) {
- message = message.materialize().asUint8List();
- }
- var deserializer = deserializerFactory(message)
- ..moveNext();
- int zoneId = deserializer.expectInt();
- deserializer..moveNext();
- var type = MessageType.values[deserializer.expectInt()];
- var serializer = serializerFactory();
- switch (type) {
- case MessageType.instantiateMacroRequest:
- var request = new InstantiateMacroRequest.deserialize(deserializer, zoneId);
- (await _instantiateMacro(request)).serialize(serializer);
- break;
- case MessageType.executeDeclarationsPhaseRequest:
- var request = new ExecuteDeclarationsPhaseRequest.deserialize(deserializer, zoneId);
- (await _executeDeclarationsPhase(request, sendRequest)).serialize(serializer);
- break;
- case MessageType.executeDefinitionsPhaseRequest:
- var request = new ExecuteDefinitionsPhaseRequest.deserialize(deserializer, zoneId);
- (await _executeDefinitionsPhase(request, sendRequest)).serialize(serializer);
- break;
- case MessageType.executeTypesPhaseRequest:
- var request = new ExecuteTypesPhaseRequest.deserialize(deserializer, zoneId);
- (await _executeTypesPhase(request, sendRequest)).serialize(serializer);
- break;
- case MessageType.response:
- var response = new SerializableResponse.deserialize(deserializer, zoneId);
- _responseCompleters.remove(response.requestId)!.complete(response);
- return;
- default:
- throw new StateError('Unhandled event type \$type');
- }
- _sendResult(serializer, sendPort);
- });
+ messageStream.listen((message) => _handleMessage(message, sendResult));
});
}
+void _handleMessage(
+ Object? message, void Function(Serializer) sendResult) async {
+ // Serializes `request` and send it using `sendResult`.
+ Future<Response> sendRequest(Request request) =>
+ _sendRequest(request, sendResult);
+
+ if (serializationMode == SerializationMode.byteDataClient
+ && message is TransferableTypedData) {
+ message = message.materialize().asUint8List();
+ }
+ var deserializer = deserializerFactory(message)
+ ..moveNext();
+ int zoneId = deserializer.expectInt();
+ deserializer..moveNext();
+ var type = MessageType.values[deserializer.expectInt()];
+ var serializer = serializerFactory();
+ switch (type) {
+ case MessageType.instantiateMacroRequest:
+ var request = new InstantiateMacroRequest.deserialize(deserializer, zoneId);
+ (await _instantiateMacro(request)).serialize(serializer);
+ break;
+ case MessageType.executeDeclarationsPhaseRequest:
+ var request = new ExecuteDeclarationsPhaseRequest.deserialize(deserializer, zoneId);
+ (await _executeDeclarationsPhase(request, sendRequest)).serialize(serializer);
+ break;
+ case MessageType.executeDefinitionsPhaseRequest:
+ var request = new ExecuteDefinitionsPhaseRequest.deserialize(deserializer, zoneId);
+ (await _executeDefinitionsPhase(request, sendRequest)).serialize(serializer);
+ break;
+ case MessageType.executeTypesPhaseRequest:
+ var request = new ExecuteTypesPhaseRequest.deserialize(deserializer, zoneId);
+ (await _executeTypesPhase(request, sendRequest)).serialize(serializer);
+ break;
+ case MessageType.response:
+ var response = new SerializableResponse.deserialize(deserializer, zoneId);
+ _responseCompleters.remove(response.requestId)!.complete(response);
+ return;
+ default:
+ throw new StateError('Unhandled event type \$type');
+ }
+ sendResult(serializer);
+}
+
/// Maps macro identifiers to constructors.
final _macroConstructors = <MacroClassIdentifierImpl, Map<String, Macro Function()>>{
$_macroConstructorEntriesMarker
@@ -263,21 +297,22 @@
/// Holds on to response completers by request id.
final _responseCompleters = <int, Completer<Response>>{};
-/// Serializes [request], sends it to [sendPort], and sets up a [Completer] in
-/// [_responseCompleters] to handle the response.
-Future<Response> _sendRequest(Request request, SendPort sendPort) {
+/// Serializes [request], passes it to [sendResult], and sets up a [Completer]
+/// in [_responseCompleters] to handle the response.
+Future<Response> _sendRequest(
+ Request request, void Function(Serializer serializer) sendResult) {
Completer<Response> completer = Completer();
_responseCompleters[request.id] = completer;
Serializer serializer = serializerFactory();
serializer.addInt(request.serializationZoneId);
request.serialize(serializer);
- _sendResult(serializer, sendPort);
+ sendResult(serializer);
return completer.future;
}
/// Sends [serializer.result] to [sendPort], possibly wrapping it in a
/// [TransferableTypedData] object.
-void _sendResult(Serializer serializer, SendPort sendPort) {
+void _sendIsolateResult(Serializer serializer, SendPort sendPort) {
if (serializationMode == SerializationMode.byteDataClient) {
sendPort.send(
TransferableTypedData.fromList([serializer.result as Uint8List]));
@@ -285,4 +320,27 @@
sendPort.send(serializer.result);
}
}
+
+/// Sends [serializer.result] to [stdout].
+///
+/// Serializes the result to a string if using JSON.
+void _sendStdoutResult(Serializer serializer) {
+ if (serializationMode == SerializationMode.jsonClient) {
+ stdout.writeln(jsonEncode(serializer.result));
+ } else if (serializationMode == SerializationMode.byteDataClient) {
+ Uint8List result = (serializer as ByteDataSerializer).result;
+ int length = result.lengthInBytes;
+ stdout.add([
+ length >> 24 & 0xff,
+ length >> 16 & 0xff,
+ length >> 8 & 0xff,
+ length & 0xff,
+ ]);
+ stdout.add(result);
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported serialization mode \$serializationMode for '
+ 'ProcessExecutor');
+ }
+}
''';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
index ade1e6d..3ac7616d 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
@@ -4,7 +4,7 @@
import 'api.dart';
import 'bootstrap.dart'; // For doc comments only.
-import 'executor_shared/serialization.dart';
+import 'executor/serialization.dart';
/// The interface used by Dart language implementations, in order to load
/// and execute macros, as well as produce library augmentations from those
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/augmentation_library.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/augmentation_library.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/augmentation_library.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/augmentation_library.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/builder_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/builder_impls.dart
similarity index 98%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/builder_impls.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/builder_impls.dart
index 6f70c92..7acddb2 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/builder_impls.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/builder_impls.dart
@@ -4,7 +4,7 @@
import 'dart:async';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
import '../executor.dart';
import '../api.dart';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/execute_macro.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/execute_macro.dart
similarity index 98%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/execute_macro.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/execute_macro.dart
index 7488445..13ec76e 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/execute_macro.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/execute_macro.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/builder_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/builder_impls.dart';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
/// Runs [macro] in the types phase and returns a [MacroExecutionResult].
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/executor_base.dart
similarity index 68%
rename from pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/executor_base.dart
index 1efa621..018b47a 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/executor_base.dart
@@ -4,157 +4,49 @@
import 'dart:async';
import 'dart:isolate';
-import 'dart:typed_data';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
import '../api.dart';
-import '../executor_shared/augmentation_library.dart';
-import '../executor_shared/introspection_impls.dart';
-import '../executor_shared/protocol.dart';
-import '../executor_shared/response_impls.dart';
-import '../executor_shared/serialization.dart';
+import '../executor/introspection_impls.dart';
+import '../executor/protocol.dart';
+import '../executor/serialization.dart';
import '../executor.dart';
-/// Returns an instance of [_IsolatedMacroExecutor] using [serializationMode].
+/// Base implementation for macro executors which communicate with some external
+/// process to run macros.
///
-/// The [serializationMode] must be a `server` variant;
-///
-/// This is the only public api exposed by this library.
-Future<MacroExecutor> start(SerializationMode serializationMode) async =>
- new _IsolatedMacroExecutor(serializationMode);
-
-/// A [MacroExecutor] implementation which spawns a separate isolate for each
-/// macro that is loaded. Each of these is wrapped in its own
-/// [_SingleIsolatedMacroExecutor] which requests are delegated to.
-///
-/// This implementation requires precompiled kernel files when loading macros,
-/// (you must pass a `precompiledKernelUri` to [loadMacro]).
-///
-/// Spawned isolates are not ran in the same isolate group, so objects are
-/// serialized between isolates.
-class _IsolatedMacroExecutor extends MacroExecutor
- with AugmentationLibraryBuilder {
- /// Individual executors indexed by [MacroClassIdentifier] or
- /// [MacroInstanceIdentifier].
- final _executors = <Object, _SingleIsolatedMacroExecutor>{};
-
- /// The mode to use for serialization - must be a `server` variant.
- final SerializationMode _serializationMode;
-
- _IsolatedMacroExecutor(this._serializationMode) {
- if (_serializationMode.isClient) {
- throw new ArgumentError(
- 'Got $serializationMode but expected a server version.');
- }
- }
-
- @override
- void close() {
- for (_SingleIsolatedMacroExecutor executor in _executors.values) {
- executor.close();
- }
- }
-
- @override
- Future<MacroExecutionResult> executeDeclarationsPhase(
- MacroInstanceIdentifier macro,
- DeclarationImpl declaration,
- TypeResolver typeResolver,
- ClassIntrospector classIntrospector) =>
- _executors[macro]!.executeDeclarationsPhase(
- macro, declaration, typeResolver, classIntrospector);
-
- @override
- Future<MacroExecutionResult> executeDefinitionsPhase(
- MacroInstanceIdentifier macro,
- DeclarationImpl declaration,
- TypeResolver typeResolver,
- ClassIntrospector classIntrospector,
- TypeDeclarationResolver typeDeclarationResolver) =>
- _executors[macro]!.executeDefinitionsPhase(macro, declaration,
- typeResolver, classIntrospector, typeDeclarationResolver);
-
- @override
- Future<MacroExecutionResult> executeTypesPhase(
- MacroInstanceIdentifier macro, DeclarationImpl declaration) =>
- _executors[macro]!.executeTypesPhase(macro, declaration);
-
- @override
- Future<MacroInstanceIdentifier> instantiateMacro(
- MacroClassIdentifier macroClass,
- String constructor,
- Arguments arguments) async {
- _SingleIsolatedMacroExecutor executor = _executors[macroClass]!;
- MacroInstanceIdentifier instance =
- await executor.instantiateMacro(macroClass, constructor, arguments);
- _executors[instance] = executor;
- return instance;
- }
-
- @override
- Future<MacroClassIdentifier> loadMacro(Uri library, String name,
- {Uri? precompiledKernelUri}) async {
- if (precompiledKernelUri == null) {
- throw new UnsupportedError(
- 'This environment requires a non-null `precompiledKernelUri` to be '
- 'passed when loading macros.');
- }
- MacroClassIdentifier identifier =
- new MacroClassIdentifierImpl(library, name);
- _executors.remove(identifier)?.close();
-
- _SingleIsolatedMacroExecutor executor =
- await _SingleIsolatedMacroExecutor.start(
- library, name, precompiledKernelUri, _serializationMode);
- _executors[identifier] = executor;
- return identifier;
- }
-}
-
-class _SingleIsolatedMacroExecutor extends MacroExecutor {
- /// The stream on which we receive responses.
+/// Subtypes must extend this class and implement the [close] and [sendResult]
+/// apis to handle communication with the external macro program.
+abstract class ExternalMacroExecutorBase extends MacroExecutor {
+ /// The stream on which we receive messages from the external macro executor.
final Stream<Object> messageStream;
- /// The send port where we should send requests.
- final SendPort sendPort;
-
- /// A function that should be invoked when shutting down this executor
- /// to perform any necessary cleanup.
- final void Function() onClose;
-
- /// A map of response completers by request id.
- final responseCompleters = <int, Completer<Response>>{};
-
/// The mode to use for serialization - must be a `server` variant.
final SerializationMode serializationMode;
+ /// A map of response completers by request id.
+ final _responseCompleters = <int, Completer<Response>>{};
+
/// We need to know which serialization zone to deserialize objects in, so
- /// that we read them from the correct cache. Each request creates its own
- /// zone which it stores here by ID and then responses are deserialized in
- /// the same zone.
- static final serializationZones = <int, Zone>{};
+ /// that we read them from the correct cache. Each macro execution creates its
+ /// own zone which it stores here by ID and then responses are deserialized in
+ /// that same zone.
+ static final _serializationZones = <int, Zone>{};
/// Incrementing identifier for the serialization zone ids.
static int _nextSerializationZoneId = 0;
- _SingleIsolatedMacroExecutor(
- {required this.onClose,
- required this.messageStream,
- required this.sendPort,
- required this.serializationMode}) {
+ ExternalMacroExecutorBase(
+ {required this.messageStream, required this.serializationMode}) {
messageStream.listen((message) {
- if (serializationMode == SerializationMode.byteDataServer &&
- message is TransferableTypedData) {
- message = message.materialize().asUint8List();
- }
withSerializationMode(serializationMode, () {
Deserializer deserializer = deserializerFactory(message);
// Every object starts with a zone ID which dictates the zone in which
// we should deserialize the message.
deserializer.moveNext();
int zoneId = deserializer.expectInt();
- Zone zone = serializationZones[zoneId]!;
+ Zone zone = _serializationZones[zoneId]!;
zone.run(() async {
deserializer.moveNext();
MessageType messageType =
@@ -164,7 +56,7 @@
SerializableResponse response =
new SerializableResponse.deserialize(deserializer, zoneId);
Completer<Response>? completer =
- responseCompleters.remove(response.requestId);
+ _responseCompleters.remove(response.requestId);
if (completer == null) {
throw new StateError(
'Got a response for an unrecognized request id '
@@ -192,7 +84,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.isExactlyTypeRequest:
IsExactlyTypeRequest request =
@@ -207,7 +99,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.isSubtypeOfRequest:
IsSubtypeOfRequest request =
@@ -222,7 +114,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.declarationOfRequest:
DeclarationOfRequest request =
@@ -238,7 +130,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.constructorsOfRequest:
ClassIntrospectionRequest request =
@@ -256,7 +148,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.fieldsOfRequest:
ClassIntrospectionRequest request =
@@ -274,7 +166,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.interfacesOfRequest:
ClassIntrospectionRequest request =
@@ -292,7 +184,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.methodsOfRequest:
ClassIntrospectionRequest request =
@@ -310,7 +202,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.mixinsOfRequest:
ClassIntrospectionRequest request =
@@ -328,7 +220,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
case MessageType.superclassOfRequest:
ClassIntrospectionRequest request =
@@ -346,7 +238,7 @@
serializationZoneId: zoneId);
Serializer serializer = serializerFactory();
response.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
break;
default:
throw new StateError('Unexpected message type $messageType');
@@ -356,35 +248,6 @@
});
}
- static Future<_SingleIsolatedMacroExecutor> start(Uri library, String name,
- Uri precompiledKernelUri, SerializationMode serializationMode) async {
- ReceivePort receivePort = new ReceivePort();
- Isolate isolate =
- await Isolate.spawnUri(precompiledKernelUri, [], receivePort.sendPort);
- Completer<SendPort> sendPortCompleter = new Completer();
- StreamController<Object> messageStreamController =
- new StreamController(sync: true);
- receivePort.listen((message) {
- if (!sendPortCompleter.isCompleted) {
- sendPortCompleter.complete(message as SendPort);
- } else {
- messageStreamController.add(message);
- }
- }).onDone(messageStreamController.close);
-
- return new _SingleIsolatedMacroExecutor(
- onClose: () {
- receivePort.close();
- isolate.kill();
- },
- messageStream: messageStreamController.stream,
- sendPort: await sendPortCompleter.future,
- serializationMode: serializationMode);
- }
-
- @override
- void close() => onClose();
-
/// These calls are handled by the higher level executor.
@override
String buildAugmentationLibrary(Iterable<MacroExecutionResult> macroResults,
@@ -453,22 +316,28 @@
@override
Future<MacroClassIdentifier> loadMacro(Uri library, String name,
{Uri? precompiledKernelUri}) =>
- throw new StateError('Unreachable');
+ throw new StateError(
+ 'This executor should be wrapped in a MultiMacroExecutor which will '
+ 'handle load requests.');
+
+ /// Sends [serializer.result] to [sendPort], possibly wrapping it in a
+ /// [TransferableTypedData] object.
+ void sendResult(Serializer serializer);
/// Creates a [Request] with a given serialization zone ID, and handles the
/// response, casting it to the expected type or throwing the error provided.
Future<T> _sendRequest<T>(Request Function(int) requestFactory) =>
withSerializationMode(serializationMode, () async {
int zoneId = _nextSerializationZoneId++;
- serializationZones[zoneId] = Zone.current;
+ _serializationZones[zoneId] = Zone.current;
Request request = requestFactory(zoneId);
Serializer serializer = serializerFactory();
// It is our responsibility to add the zone ID header.
serializer.addInt(zoneId);
request.serialize(serializer);
- _sendResult(serializer);
+ sendResult(serializer);
Completer<Response> completer = new Completer<Response>();
- responseCompleters[request.id] = completer;
+ _responseCompleters[request.id] = completer;
try {
Response response = await completer.future;
T? result = response.response as T?;
@@ -477,18 +346,7 @@
response.error!.toString(), response.stackTrace);
} finally {
// Clean up the zone after the request is done.
- serializationZones.remove(zoneId);
+ _serializationZones.remove(zoneId);
}
});
-
- /// Sends [serializer.result] to [sendPort], possibly wrapping it in a
- /// [TransferableTypedData] object.
- void _sendResult(Serializer serializer) {
- if (serializationMode == SerializationMode.byteDataServer) {
- sendPort.send(
- new TransferableTypedData.fromList([serializer.result as Uint8List]));
- } else {
- sendPort.send(serializer.result);
- }
- }
}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/isolated_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/isolated_executor.dart
new file mode 100644
index 0000000..ae15038
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/isolated_executor.dart
@@ -0,0 +1,94 @@
+// 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 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import '../executor/multi_executor.dart';
+import '../executor/executor_base.dart';
+import '../executor/serialization.dart';
+import '../executor.dart';
+
+/// Returns a [MacroExecutor] which loads macros into isolates using precompiled
+/// kernel files and communicates with that isolate using [serializationMode].
+///
+/// The [serializationMode] must be a `server` variant, and any precompiled
+/// programs must use the corresponding `client` variant.
+///
+/// This is the only public api exposed by this library.
+Future<MacroExecutor> start(SerializationMode serializationMode) async =>
+ new MultiMacroExecutor((Uri library, String name,
+ {Uri? precompiledKernelUri}) {
+ if (precompiledKernelUri == null) {
+ throw new UnsupportedError(
+ 'This environment requires a non-null `precompiledKernelUri` to be '
+ 'passed when loading macros.');
+ }
+
+ return _SingleIsolatedMacroExecutor.start(
+ library, name, precompiledKernelUri, serializationMode);
+ });
+
+/// Actual implementation of the isolate based macro executor.
+class _SingleIsolatedMacroExecutor extends ExternalMacroExecutorBase {
+ /// The send port where we should send requests.
+ final SendPort sendPort;
+
+ /// A function that should be invoked when shutting down this executor
+ /// to perform any necessary cleanup.
+ final void Function() onClose;
+
+ _SingleIsolatedMacroExecutor(
+ {required Stream<Object> messageStream,
+ required this.onClose,
+ required this.sendPort,
+ required SerializationMode serializationMode})
+ : super(
+ messageStream: messageStream, serializationMode: serializationMode);
+
+ static Future<_SingleIsolatedMacroExecutor> start(Uri library, String name,
+ Uri precompiledKernelUri, SerializationMode serializationMode) async {
+ ReceivePort receivePort = new ReceivePort();
+ Isolate isolate =
+ await Isolate.spawnUri(precompiledKernelUri, [], receivePort.sendPort);
+ Completer<SendPort> sendPortCompleter = new Completer();
+ StreamController<Object> messageStreamController =
+ new StreamController(sync: true);
+ receivePort.listen((message) {
+ if (!sendPortCompleter.isCompleted) {
+ sendPortCompleter.complete(message as SendPort);
+ } else {
+ if (serializationMode == SerializationMode.byteDataServer) {
+ message =
+ (message as TransferableTypedData).materialize().asUint8List();
+ }
+ messageStreamController.add(message);
+ }
+ }).onDone(messageStreamController.close);
+
+ return new _SingleIsolatedMacroExecutor(
+ onClose: () {
+ receivePort.close();
+ isolate.kill();
+ },
+ messageStream: messageStreamController.stream,
+ sendPort: await sendPortCompleter.future,
+ serializationMode: serializationMode);
+ }
+
+ @override
+ void close() => onClose();
+
+ /// Sends the [Serializer.result] to [sendPort], possibly wrapping it in a
+ /// [TransferableTypedData] object.
+ void sendResult(Serializer serializer) {
+ if (serializationMode == SerializationMode.byteDataServer) {
+ sendPort.send(
+ new TransferableTypedData.fromList([serializer.result as Uint8List]));
+ } else {
+ sendPort.send(serializer.result);
+ }
+ }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/message_grouper.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/message_grouper.dart
new file mode 100644
index 0000000..7e2d390
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/message_grouper.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:math';
+import 'dart:typed_data';
+
+/// Collects messages from an input stream of bytes.
+///
+/// Each message should start with a 32 bit big endian uint indicating its size,
+/// followed by that many bytes.
+class MessageGrouper {
+ /// The input bytes stream subscription.
+ late final StreamSubscription _inputStreamSubscription;
+
+ /// The length of the current message to read, or `-1` if we are currently
+ /// reading the length.
+ int _length = -1;
+
+ /// The buffer to store the length bytes in.
+ BytesBuilder _lengthBuffer = new BytesBuilder();
+
+ /// If reading raw data, buffer for the data.
+ Uint8List _messageBuffer = new Uint8List(0);
+
+ /// The position to write the next byte in [_messageBuffer].
+ int _messagePos = 0;
+
+ late StreamController<Uint8List> _messageStreamController =
+ new StreamController<Uint8List>(onCancel: () {
+ _inputStreamSubscription.cancel();
+ });
+ Stream<Uint8List> get messageStream => _messageStreamController.stream;
+
+ MessageGrouper(Stream<List<int>> inputStream) {
+ _inputStreamSubscription = inputStream.listen(_handleBytes, onDone: cancel);
+ }
+
+ void _handleBytes(List<int> bytes, [int offset = 0]) {
+ if (_length == -1) {
+ while (_lengthBuffer.length < 4 && offset < bytes.length) {
+ _lengthBuffer.addByte(bytes[offset++]);
+ }
+ if (_lengthBuffer.length >= 4) {
+ Uint8List lengthBytes = _lengthBuffer.takeBytes();
+ _length = lengthBytes[0] << 24 |
+ lengthBytes[1] << 16 |
+ lengthBytes[2] << 8 |
+ lengthBytes[3];
+ }
+ }
+
+ // Just pass along `bytes` without a copy if we can, and reset our state
+ if (offset == 0 && bytes.length == _length && bytes is Uint8List) {
+ _length = -1;
+ _messageStreamController.add(bytes);
+ return;
+ }
+
+ // Initialize a new buffer.
+ if (_messagePos == 0) {
+ _messageBuffer = new Uint8List(_length);
+ }
+
+ // Read the data from `bytes`.
+ int lenToRead = min(_length - _messagePos, bytes.length - offset);
+ while (lenToRead-- > 0) {
+ _messageBuffer[_messagePos++] = bytes[offset++];
+ }
+
+ // If we completed a message, add it to the output stream, reset our state,
+ // and call ourselves again if we have more data to read.
+ if (_messagePos >= _length) {
+ _messageStreamController.add(_messageBuffer);
+ _length = -1;
+ _messagePos = 0;
+
+ if (offset < bytes.length) {
+ _handleBytes(bytes, offset);
+ }
+ }
+ }
+
+ /// Stop listening to the input stream for further updates, and close the
+ /// output stream.
+ void cancel() {
+ _inputStreamSubscription.cancel();
+ _messageStreamController.close();
+ }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/multi_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/multi_executor.dart
new file mode 100644
index 0000000..81b8107
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/multi_executor.dart
@@ -0,0 +1,83 @@
+// 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 'dart:async';
+
+import '../api.dart';
+import '../executor/augmentation_library.dart';
+import '../executor/introspection_impls.dart';
+import '../executor/response_impls.dart';
+import '../executor.dart';
+
+/// A [MacroExecutor] implementation which delegates most work to other
+/// executors which are spawned through a provided callback.
+class MultiMacroExecutor extends MacroExecutor with AugmentationLibraryBuilder {
+ /// Individual executors indexed by [MacroClassIdentifier] or
+ /// [MacroInstanceIdentifier].
+ final _executors = <Object, MacroExecutor>{};
+
+ /// The function to spawn an actual macro executor for a given [loadMacro]
+ /// request.
+ final Future<MacroExecutor> Function(Uri library, String name,
+ {Uri? precompiledKernelUri}) _spawnExecutor;
+
+ MultiMacroExecutor(this._spawnExecutor);
+
+ @override
+ void close() {
+ for (MacroExecutor executor in _executors.values) {
+ executor.close();
+ }
+ _executors.clear();
+ }
+
+ @override
+ Future<MacroExecutionResult> executeDeclarationsPhase(
+ MacroInstanceIdentifier macro,
+ DeclarationImpl declaration,
+ TypeResolver typeResolver,
+ ClassIntrospector classIntrospector) =>
+ _executors[macro]!.executeDeclarationsPhase(
+ macro, declaration, typeResolver, classIntrospector);
+
+ @override
+ Future<MacroExecutionResult> executeDefinitionsPhase(
+ MacroInstanceIdentifier macro,
+ DeclarationImpl declaration,
+ TypeResolver typeResolver,
+ ClassIntrospector classIntrospector,
+ TypeDeclarationResolver typeDeclarationResolver) =>
+ _executors[macro]!.executeDefinitionsPhase(macro, declaration,
+ typeResolver, classIntrospector, typeDeclarationResolver);
+
+ @override
+ Future<MacroExecutionResult> executeTypesPhase(
+ MacroInstanceIdentifier macro, DeclarationImpl declaration) =>
+ _executors[macro]!.executeTypesPhase(macro, declaration);
+
+ @override
+ Future<MacroInstanceIdentifier> instantiateMacro(
+ MacroClassIdentifier macroClass,
+ String constructor,
+ Arguments arguments) async {
+ MacroExecutor executor = _executors[macroClass]!;
+ MacroInstanceIdentifier instance =
+ await executor.instantiateMacro(macroClass, constructor, arguments);
+ _executors[instance] = executor;
+ return instance;
+ }
+
+ @override
+ Future<MacroClassIdentifier> loadMacro(Uri library, String name,
+ {Uri? precompiledKernelUri}) async {
+ MacroClassIdentifier identifier =
+ new MacroClassIdentifierImpl(library, name);
+ _executors.remove(identifier)?.close();
+
+ MacroExecutor executor = await _spawnExecutor(library, name,
+ precompiledKernelUri: precompiledKernelUri);
+ _executors[identifier] = executor;
+ return identifier;
+ }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/process_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/process_executor.dart
new file mode 100644
index 0000000..ed67219
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/process_executor.dart
@@ -0,0 +1,117 @@
+// 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/executor/protocol.dart';
+
+import '../executor/message_grouper.dart';
+import '../executor/multi_executor.dart';
+import '../executor/executor_base.dart';
+import '../executor/serialization.dart';
+import '../executor.dart';
+
+/// Returns a [MacroExecutor] which loads macros as separate processes using
+/// precompiled binaries and communicates with that program using
+/// [serializationMode].
+///
+/// The [serializationMode] must be a `server` variant, and any precompiled
+/// programs spawned must use the corresponding `client` variant.
+///
+/// This is the only public api exposed by this library.
+Future<MacroExecutor> start(SerializationMode serializationMode) async =>
+ new MultiMacroExecutor((Uri library, String name,
+ {Uri? precompiledKernelUri}) {
+ if (precompiledKernelUri == null) {
+ throw new UnsupportedError(
+ 'This environment requires a non-null `precompiledKernelUri` to be '
+ 'passed when loading macros.');
+ }
+
+ // TODO: We actually assume this is a full precompiled AOT binary, and not
+ // a kernel file. We launch it directly using `Process.start`.
+ return _SingleProcessMacroExecutor.start(
+ library, name, serializationMode, precompiledKernelUri.toFilePath());
+ });
+
+/// Actual implementation of the separate process based macro executor.
+class _SingleProcessMacroExecutor extends ExternalMacroExecutorBase {
+ /// The IOSink that writes to stdin of the external process.
+ final IOSink outSink;
+
+ /// A function that should be invoked when shutting down this executor
+ /// to perform any necessary cleanup.
+ final void Function() onClose;
+
+ _SingleProcessMacroExecutor(
+ {required Stream<Object> messageStream,
+ required this.onClose,
+ required this.outSink,
+ required SerializationMode serializationMode})
+ : super(
+ messageStream: messageStream, serializationMode: serializationMode);
+
+ static Future<_SingleProcessMacroExecutor> start(Uri library, String name,
+ SerializationMode serializationMode, String programPath) async {
+ Process process = await Process.start(programPath, []);
+ process.stderr
+ .transform(const Utf8Decoder())
+ .listen((content) => throw new RemoteException(content));
+
+ Stream<Object> messageStream;
+
+ if (serializationMode == SerializationMode.byteDataServer) {
+ messageStream = new MessageGrouper(process.stdout).messageStream;
+ } else if (serializationMode == SerializationMode.jsonServer) {
+ messageStream = process.stdout
+ .transform(const Utf8Decoder())
+ .transform(const LineSplitter())
+ .map((line) => jsonDecode(line)!);
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported serialization mode \$serializationMode for '
+ 'ProcessExecutor');
+ }
+
+ return new _SingleProcessMacroExecutor(
+ onClose: () {
+ process.kill();
+ },
+ messageStream: messageStream,
+ outSink: process.stdin,
+ serializationMode: serializationMode);
+ }
+
+ @override
+ void close() => onClose();
+
+ /// Sends the [Serializer.result] to [stdin].
+ ///
+ /// Json results are serialized to a `String`, and separated by newlines.
+ void sendResult(Serializer serializer) {
+ if (serializationMode == SerializationMode.jsonServer) {
+ outSink.writeln(jsonEncode(serializer.result));
+ } else if (serializationMode == SerializationMode.byteDataServer) {
+ Uint8List result = (serializer as ByteDataSerializer).result;
+ int length = result.lengthInBytes;
+ if (length > 0xffffffff) {
+ throw new StateError('Message was larger than the allowed size!');
+ }
+ outSink.add([
+ length >> 24 & 0xff,
+ length >> 16 & 0xff,
+ length >> 8 & 0xff,
+ length & 0xff
+ ]);
+ outSink.add(result);
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported serialization mode $serializationMode for '
+ 'ProcessExecutor');
+ }
+ }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/protocol.dart
similarity index 99%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/protocol.dart
index 50a6743..fa6d608 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/protocol.dart
@@ -10,7 +10,7 @@
import '../executor.dart';
import '../api.dart';
-import '../executor_shared/response_impls.dart';
+import '../executor/response_impls.dart';
import 'introspection_impls.dart';
import 'remote_instance.dart';
import 'serialization.dart';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/remote_instance.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/remote_instance.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/response_impls.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/response_impls.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
similarity index 99%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
index 267a46f..50d98fd 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
@@ -68,7 +68,7 @@
void endList();
/// Returns the resulting serialized object.
- Object? get result;
+ Object get result;
}
/// A pull based object deserialization interface.
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
similarity index 99%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
index cc7e8cb..cf3ff21 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
@@ -1,4 +1,4 @@
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
import 'remote_instance.dart';
import 'serialization.dart';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart
deleted file mode 100644
index e45d120..0000000
--- a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart
+++ /dev/null
@@ -1,164 +0,0 @@
-// 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 'dart:async';
-import 'dart:isolate';
-import 'dart:mirrors';
-
-import 'isolate_mirrors_impl.dart';
-import '../executor_shared/augmentation_library.dart';
-import '../executor_shared/introspection_impls.dart';
-import '../executor_shared/protocol.dart';
-import '../executor_shared/remote_instance.dart';
-import '../executor.dart';
-import '../api.dart';
-
-/// Returns an instance of [_IsolateMirrorMacroExecutor].
-///
-/// This is the only public api exposed by this library.
-Future<MacroExecutor> start() => _IsolateMirrorMacroExecutor.start();
-
-/// A [MacroExecutor] implementation which relies on [IsolateMirror.loadUri]
-/// in order to load macros libraries.
-///
-/// All actual work happens in a separate [Isolate], and this class serves as
-/// a bridge between that isolate and the language frontends.
-class _IsolateMirrorMacroExecutor extends MacroExecutor
- with AugmentationLibraryBuilder {
- /// The actual isolate doing macro loading and execution.
- final Isolate _macroIsolate;
-
- /// The channel used to send requests to the [_macroIsolate].
- final SendPort _sendPort;
-
- /// The stream of responses from the [_macroIsolate].
- final Stream<Response> _responseStream;
-
- /// A map of response completers by request id.
- final _responseCompleters = <int, Completer<Response>>{};
-
- /// A function that should be invoked when shutting down this executor
- /// to perform any necessary cleanup.
- final void Function() _onClose;
-
- _IsolateMirrorMacroExecutor._(
- this._macroIsolate, this._sendPort, this._responseStream, this._onClose) {
- _responseStream.listen((event) {
- Completer<Response>? completer =
- _responseCompleters.remove(event.requestId);
- if (completer == null) {
- throw new StateError(
- 'Got a response for an unrecognized request id ${event.requestId}');
- }
- completer.complete(event);
- });
- }
-
- /// Initialize an [IsolateMirrorMacroExecutor] and return it once ready.
- ///
- /// Spawns the macro isolate and sets up a communication channel.
- static Future<MacroExecutor> start() async {
- ReceivePort receivePort = new ReceivePort();
- Completer<SendPort> sendPortCompleter = new Completer<SendPort>();
- StreamController<Response> responseStreamController =
- new StreamController<Response>(sync: true);
- receivePort.listen((message) {
- if (!sendPortCompleter.isCompleted) {
- sendPortCompleter.complete(message as SendPort);
- } else {
- responseStreamController.add(message as Response);
- }
- }).onDone(responseStreamController.close);
- Isolate macroIsolate = await Isolate.spawn(spawn, receivePort.sendPort);
-
- return new _IsolateMirrorMacroExecutor._(
- macroIsolate,
- await sendPortCompleter.future,
- responseStreamController.stream,
- receivePort.close);
- }
-
- @override
- void close() {
- _onClose();
- _macroIsolate.kill();
- }
-
- @override
- Future<MacroExecutionResult> executeDeclarationsPhase(
- MacroInstanceIdentifier macro,
- Declaration declaration,
- TypeResolver typeResolver,
- ClassIntrospector classIntrospector) {
- // TODO: implement executeDeclarationsPhase
- throw new UnimplementedError();
- }
-
- @override
- Future<MacroExecutionResult> executeDefinitionsPhase(
- MacroInstanceIdentifier macro,
- DeclarationImpl declaration,
- TypeResolver typeResolver,
- ClassIntrospector classIntrospector,
- TypeDeclarationResolver typeDeclarationResolver) =>
- _sendRequest(new ExecuteDefinitionsPhaseRequest(
- macro,
- declaration,
- new RemoteInstanceImpl(
- instance: typeResolver,
- id: RemoteInstance.uniqueId,
- kind: RemoteInstanceKind.typeResolver),
- new RemoteInstanceImpl(
- instance: classIntrospector,
- id: RemoteInstance.uniqueId,
- kind: RemoteInstanceKind.classIntrospector),
- new RemoteInstanceImpl(
- instance: typeDeclarationResolver,
- id: RemoteInstance.uniqueId,
- kind: RemoteInstanceKind.typeDeclarationResolver),
- // Serialization zones are not necessary in this executor.
- serializationZoneId: -1));
-
- @override
- Future<MacroExecutionResult> executeTypesPhase(
- MacroInstanceIdentifier macro, Declaration declaration) {
- // TODO: implement executeTypesPhase
- throw new UnimplementedError();
- }
-
- @override
- Future<MacroInstanceIdentifier> instantiateMacro(
- MacroClassIdentifier macroClass,
- String constructor,
- Arguments arguments) =>
- _sendRequest(new InstantiateMacroRequest(
- macroClass, constructor, arguments, RemoteInstance.uniqueId,
- // Serialization zones are not necessary in this executor.
- serializationZoneId: -1));
-
- @override
- Future<MacroClassIdentifier> loadMacro(Uri library, String name,
- {Uri? precompiledKernelUri}) {
- if (precompiledKernelUri != null) {
- // TODO: Implement support?
- throw new UnsupportedError(
- 'The IsolateMirrorsExecutor does not support precompiled dill files');
- }
- return _sendRequest(new LoadMacroRequest(library, name,
- // Serialization zones are not necessary in this executor.
- serializationZoneId: -1));
- }
-
- /// Sends a request and returns the response, casting it to the expected
- /// type.
- Future<T> _sendRequest<T>(Request request) async {
- _sendPort.send(request);
- Completer<Response> completer = new Completer<Response>();
- _responseCompleters[request.id] = completer;
- Response response = await completer.future;
- T? result = response.response as T?;
- if (result != null) return result;
- throw response.error!;
- }
-}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart
deleted file mode 100644
index e6494f0..0000000
--- a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-// 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 'dart:async';
-import 'dart:isolate';
-import 'dart:mirrors';
-
-import '../executor_shared/builder_impls.dart';
-import '../executor_shared/introspection_impls.dart';
-import '../executor_shared/response_impls.dart';
-import '../executor_shared/protocol.dart';
-import '../api.dart';
-
-/// Spawns a new isolate for loading and executing macros.
-void spawn(SendPort sendPort) {
- ReceivePort receivePort = new ReceivePort();
- sendPort.send(receivePort.sendPort);
- receivePort.listen((message) async {
- Response response;
- if (message is LoadMacroRequest) {
- response = await _loadMacro(message);
- } else if (message is InstantiateMacroRequest) {
- response = await _instantiateMacro(message);
- } else if (message is ExecuteDefinitionsPhaseRequest) {
- response = await _executeDefinitionsPhase(message);
- } else {
- throw new StateError('Unrecognized event type $message');
- }
- sendPort.send(response);
- });
-}
-
-/// Maps macro identifiers to class mirrors.
-final _macroClasses = <MacroClassIdentifierImpl, ClassMirror>{};
-
-/// Handles [LoadMacroRequest]s.
-Future<Response> _loadMacro(LoadMacroRequest request) async {
- try {
- MacroClassIdentifierImpl identifier =
- new MacroClassIdentifierImpl(request.library, request.name);
- if (_macroClasses.containsKey(identifier)) {
- throw new UnsupportedError(
- 'Reloading macros is not supported by this implementation');
- }
- LibraryMirror libMirror =
- await currentMirrorSystem().isolate.loadUri(request.library);
- ClassMirror macroClass =
- libMirror.declarations[new Symbol(request.name)] as ClassMirror;
- _macroClasses[identifier] = macroClass;
- return new Response(
- response: identifier,
- requestId: request.id,
- responseType: MessageType.macroClassIdentifier);
- } catch (e) {
- return new Response(
- error: e, requestId: request.id, responseType: MessageType.error);
- }
-}
-
-/// Maps macro instance identifiers to instances.
-final _macroInstances = <MacroInstanceIdentifierImpl, Macro>{};
-
-/// Handles [InstantiateMacroRequest]s.
-Future<Response> _instantiateMacro(InstantiateMacroRequest request) async {
- try {
- ClassMirror? clazz = _macroClasses[request.macroClass];
- if (clazz == null) {
- throw new ArgumentError('Unrecognized macro class ${request.macroClass}');
- }
- Macro instance = clazz.newInstance(
- new Symbol(request.constructorName), request.arguments.positional, {
- for (MapEntry<String, Object?> entry in request.arguments.named.entries)
- new Symbol(entry.key): entry.value,
- }).reflectee as Macro;
- MacroInstanceIdentifierImpl identifier =
- new MacroInstanceIdentifierImpl(instance, request.instanceId);
- _macroInstances[identifier] = instance;
- return new Response(
- response: identifier,
- requestId: request.id,
- responseType: MessageType.macroInstanceIdentifier);
- } catch (e) {
- return new Response(
- error: e, requestId: request.id, responseType: MessageType.error);
- }
-}
-
-Future<Response> _executeDefinitionsPhase(
- ExecuteDefinitionsPhaseRequest request) async {
- try {
- Macro? instance = _macroInstances[request.macro];
- if (instance == null) {
- throw new StateError('Unrecognized macro instance ${request.macro}\n'
- 'Known instances: $_macroInstances)');
- }
- DeclarationImpl declaration = request.declaration;
- if (instance is FunctionDefinitionMacro &&
- declaration is FunctionDeclarationImpl) {
- FunctionDefinitionBuilderImpl builder = new FunctionDefinitionBuilderImpl(
- declaration,
- request.classIntrospector.instance as ClassIntrospector,
- request.typeResolver.instance as TypeResolver,
- request.typeDeclarationResolver.instance as TypeDeclarationResolver);
- await instance.buildDefinitionForFunction(declaration, builder);
- return new Response(
- response: builder.result,
- requestId: request.id,
- responseType: MessageType.macroExecutionResult);
- } else if (instance is MethodDefinitionMacro &&
- declaration is MethodDeclarationImpl) {
- FunctionDefinitionBuilderImpl builder = new FunctionDefinitionBuilderImpl(
- declaration,
- request.classIntrospector.instance as ClassIntrospector,
- request.typeResolver.instance as TypeResolver,
- request.typeDeclarationResolver.instance as TypeDeclarationResolver);
- await instance.buildDefinitionForMethod(declaration, builder);
- return new SerializableResponse(
- responseType: MessageType.macroExecutionResult,
- response: builder.result,
- requestId: request.id,
- serializationZoneId: request.serializationZoneId);
- } else {
- throw new UnsupportedError(
- 'Only Method and Function Definition Macros are supported currently');
- }
- } catch (e) {
- return new Response(
- error: e, requestId: request.id, responseType: MessageType.error);
- }
-}
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/augmentation_library_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/augmentation_library_test.dart
similarity index 94%
rename from pkg/_fe_analyzer_shared/test/macros/executor_shared/augmentation_library_test.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/augmentation_library_test.dart
index 704898a..87e6bc3 100644
--- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/augmentation_library_test.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/augmentation_library_test.dart
@@ -2,14 +2,14 @@
// 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:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/augmentation_library.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/augmentation_library.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
import '../util.dart';
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor/executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/executor_test.dart
new file mode 100644
index 0000000..9f35c52
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/executor_test.dart
@@ -0,0 +1,496 @@
+// 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 'dart:io';
+import 'dart:isolate';
+
+import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
+ as isolatedExecutor;
+import 'package:_fe_analyzer_shared/src/macros/executor/process_executor.dart'
+ as processExecutor;
+
+import 'package:test/test.dart';
+
+import '../util.dart';
+
+void main() {
+ late MacroExecutor executor;
+ late File kernelOutputFile;
+ final macroName = 'SimpleMacro';
+ late MacroInstanceIdentifier instanceId;
+ late Uri macroUri;
+ late File simpleMacroFile;
+ late Directory tmpDir;
+
+ for (var executorKind in ['Isolated', 'Process']) {
+ group('$executorKind executor', () {
+ for (var mode in [
+ SerializationMode.byteDataServer,
+ SerializationMode.jsonServer
+ ]) {
+ final clientMode = mode == SerializationMode.byteDataServer
+ ? SerializationMode.byteDataClient
+ : SerializationMode.jsonClient;
+
+ group('$mode', () {
+ setUpAll(() async {
+ simpleMacroFile =
+ File(Platform.script.resolve('simple_macro.dart').toFilePath());
+ executor = executorKind == 'Isolated'
+ ? await isolatedExecutor.start(mode)
+ : await processExecutor.start(mode);
+ tmpDir = Directory.systemTemp.createTempSync('executor_test');
+ macroUri = simpleMacroFile.absolute.uri;
+
+ var bootstrapContent = bootstrapMacroIsolate({
+ macroUri.toString(): {
+ macroName: ['', 'named']
+ }
+ }, clientMode);
+ var bootstrapFile =
+ File(tmpDir.uri.resolve('main.dart').toFilePath())
+ ..writeAsStringSync(bootstrapContent);
+ kernelOutputFile =
+ File(tmpDir.uri.resolve('main.dart.dill').toFilePath());
+ var packageConfigPath = (await Isolate.packageConfig)!.toFilePath();
+ var buildSnapshotResult =
+ await Process.run(Platform.resolvedExecutable, [
+ if (executorKind == 'Isolated') ...[
+ '--snapshot=${kernelOutputFile.uri.toFilePath()}',
+ '--snapshot-kind=kernel',
+ ] else ...[
+ 'compile',
+ 'exe',
+ '-o',
+ kernelOutputFile.uri.toFilePath(),
+ ],
+ '--packages=${packageConfigPath}',
+ bootstrapFile.uri.toFilePath(),
+ ]);
+ expect(buildSnapshotResult.exitCode, 0,
+ reason: 'stdout: ${buildSnapshotResult.stdout}\n'
+ 'stderr: ${buildSnapshotResult.stderr}');
+
+ var clazzId = await executor.loadMacro(macroUri, macroName,
+ precompiledKernelUri: kernelOutputFile.uri);
+ expect(clazzId, isNotNull, reason: 'Can load a macro.');
+
+ instanceId =
+ await executor.instantiateMacro(clazzId, '', Arguments([], {}));
+ expect(instanceId, isNotNull,
+ reason: 'Can create an instance with no arguments.');
+
+ instanceId = await executor.instantiateMacro(
+ clazzId, '', Arguments([1, 2], {}));
+ expect(instanceId, isNotNull,
+ reason: 'Can create an instance with positional arguments.');
+
+ instanceId = await executor.instantiateMacro(
+ clazzId, 'named', Arguments([], {'x': 1, 'y': 2}));
+ expect(instanceId, isNotNull,
+ reason: 'Can create an instance with named arguments.');
+ });
+
+ tearDownAll(() {
+ if (tmpDir.existsSync()) {
+ try {
+ // Fails flakily on windows if a process still has the file open
+ tmpDir.deleteSync(recursive: true);
+ } catch (_) {}
+ }
+ executor.close();
+ });
+
+ group('run macros', () {
+ group('in the types phase', () {
+ test('on functions', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId, Fixtures.myFunction);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('class GeneratedByMyFunction {}'));
+ });
+
+ test('on methods', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId, Fixtures.myMethod);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('class GeneratedByMyMethod {}'));
+ });
+
+ test('on getters', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId,
+ Fixtures.myVariableGetter,
+ );
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'class GeneratedByMyVariableGetter {}'));
+ });
+
+ test('on setters', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId,
+ Fixtures.myVariableSetter,
+ );
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'class GeneratedByMyVariableSetter {}'));
+ });
+
+ test('on variables', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId,
+ Fixtures.myVariable,
+ );
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'class GeneratedBy_myVariable {}'));
+ });
+
+ test('on constructors', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId, Fixtures.myConstructor);
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'class GeneratedByMyConstructor {}'));
+ });
+
+ test('on fields', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId, Fixtures.myField);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('class GeneratedByMyField {}'));
+ });
+
+ test('on classes', () async {
+ var result = await executor.executeTypesPhase(
+ instanceId, Fixtures.myClass);
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'class MyClassBuilder implements Builder<MyClass> {}'));
+ });
+ });
+
+ group('in the declaration phase', () {
+ test('on functions', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myFunction,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'String delegateMyFunction() => myFunction();'));
+ });
+
+ test('on methods', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myMethod,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(
+ result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace(
+ 'String delegateMemberMyMethod() => myMethod();'));
+ });
+
+ test('on constructors', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myConstructor,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ augment class MyClass {
+ factory MyClass.myConstructorDelegate() => MyClass.myConstructor();
+ }'''));
+ });
+
+ test('on getters', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myVariableGetter,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ String get delegateMyVariable => myVariable;'''));
+ });
+
+ test('on setters', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myVariableSetter,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ void set delegateMyVariable(String value) => myVariable = value;'''));
+ });
+
+ test('on variables', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myVariable,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ String get delegate_myVariable => _myVariable;'''));
+ });
+
+ test('on fields', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myField,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ augment class MyClass {
+ String get delegateMyField => myField;
+ }'''));
+ });
+
+ test('on classes', () async {
+ var result = await executor.executeDeclarationsPhase(
+ instanceId,
+ Fixtures.myClass,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ augment class MyClass {
+ static const List<String> fieldNames = ['myField',];
+ }'''));
+ });
+ });
+
+ group('in the definition phase', () {
+ test('on functions', () async {
+ var result = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myFunction,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ augment String myFunction() {
+ print('isAbstract: false');
+ print('isExternal: false');
+ print('isGetter: false');
+ print('isSetter: false');
+ print('returnType: String');
+ return augment super();
+ }'''));
+ });
+
+ test('on methods', () async {
+ var definitionResult = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myMethod,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(definitionResult.augmentations, hasLength(2));
+ var augmentationStrings = definitionResult.augmentations
+ .map((a) => a.debugString().toString())
+ .toList();
+ expect(augmentationStrings,
+ unorderedEquals(methodDefinitionMatchers));
+ });
+
+ test('on constructors', () async {
+ var definitionResult = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myConstructor,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(definitionResult.augmentations, hasLength(1));
+ expect(
+ definitionResult.augmentations.first
+ .debugString()
+ .toString(),
+ constructorDefinitionMatcher);
+ });
+
+ test('on getters', () async {
+ var result = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myVariableGetter,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ augment String myVariable() {
+ print('isAbstract: false');
+ print('isExternal: false');
+ print('isGetter: true');
+ print('isSetter: false');
+ print('returnType: String');
+ return augment super;
+ }'''));
+ });
+
+ test('on setters', () async {
+ var result = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myVariableSetter,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(result.augmentations.single.debugString().toString(),
+ equalsIgnoringWhitespace('''
+ augment void myVariable(String value, ) {
+ print('isAbstract: false');
+ print('isExternal: false');
+ print('isGetter: false');
+ print('isSetter: true');
+ print('returnType: void');
+ print('positionalParam: String value');
+ return augment super = value;
+ }'''));
+ });
+
+ test('on variables', () async {
+ var result = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myVariable,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(
+ result.augmentations.map((a) => a.debugString().toString()),
+ unorderedEquals([
+ equalsIgnoringWhitespace('''
+ augment String get _myVariable {
+ print('parentClass: ');
+ print('isExternal: false');
+ print('isFinal: true');
+ print('isLate: false');
+ return augment super;
+ }'''),
+ equalsIgnoringWhitespace('''
+ augment set _myVariable(String value) {
+ augment super = value;
+ }'''),
+ equalsIgnoringWhitespace('''
+ augment final String _myVariable = 'new initial value' + augment super;
+ '''),
+ ]));
+ });
+
+ test('on fields', () async {
+ var definitionResult = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myField,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ expect(definitionResult.augmentations, hasLength(1));
+ expect(
+ definitionResult.augmentations.first
+ .debugString()
+ .toString(),
+ fieldDefinitionMatcher);
+ });
+
+ test('on classes', () async {
+ var definitionResult = await executor.executeDefinitionsPhase(
+ instanceId,
+ Fixtures.myClass,
+ Fixtures.testTypeResolver,
+ Fixtures.testClassIntrospector,
+ Fixtures.testTypeDeclarationResolver);
+ var augmentationStrings = definitionResult.augmentations
+ .map((a) => a.debugString().toString())
+ .toList();
+ expect(
+ augmentationStrings,
+ unorderedEquals([
+ ...methodDefinitionMatchers,
+ constructorDefinitionMatcher,
+ fieldDefinitionMatcher
+ ]));
+ });
+ });
+ });
+ });
+ }
+ });
+ }
+}
+
+final constructorDefinitionMatcher = equalsIgnoringWhitespace('''
+augment class MyClass {
+ augment MyClass.myConstructor() {
+ print('definingClass: MyClass');
+ print('isFactory: false');
+ print('isAbstract: false');
+ print('isExternal: false');
+ print('isGetter: false');
+ print('isSetter: false');
+ print('returnType: MyClass');
+ return augment super();
+ }
+}''');
+
+final fieldDefinitionMatcher = equalsIgnoringWhitespace('''
+augment class MyClass {
+ augment String get myField {
+ print('parentClass: MyClass');
+ print('isExternal: false');
+ print('isFinal: false');
+ print('isLate: false');
+ return augment super;
+ }
+ augment set myField(String value) {
+ augment super = value;
+ }
+ augment String myField = \'new initial value\' + augment super;
+}''');
+
+final methodDefinitionMatchers = [
+ equalsIgnoringWhitespace('''
+ augment class MyClass {
+ augment String myMethod() {
+ print('definingClass: MyClass');
+ print('isAbstract: false');
+ print('isExternal: false');
+ print('isGetter: false');
+ print('isSetter: false');
+ print('returnType: String');
+ return augment super();
+ }
+ }
+ '''),
+ equalsIgnoringWhitespace('''
+ augment class MyClass {
+ augment String myMethod() {
+ print('x: 1, y: 2');
+ print('parentClass: MyClass');
+ print('superClass: MySuperclass');
+ print('interface: MyInterface');
+ print('mixin: MyMixin');
+ print('field: myField');
+ print('method: myMethod');
+ print('constructor: myConstructor');
+ return augment super();
+ }
+ }'''),
+];
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/response_impls_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/response_impls_test.dart
similarity index 97%
rename from pkg/_fe_analyzer_shared/test/macros/executor_shared/response_impls_test.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/response_impls_test.dart
index 88ef6fb..c64b7c7 100644
--- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/response_impls_test.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/response_impls_test.dart
@@ -2,11 +2,11 @@
// 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:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/serialization_test.dart
similarity index 97%
rename from pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/serialization_test.dart
index 5a9842e..eb74a8e 100644
--- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/serialization_test.dart
@@ -3,10 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:_fe_analyzer_shared/src/macros/api.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization_extensions.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization_extensions.dart';
import 'package:test/test.dart';
import '../util.dart';
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/simple_macro.dart b/pkg/_fe_analyzer_shared/test/macros/executor/simple_macro.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/test/macros/isolated_executor/simple_macro.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/simple_macro.dart
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart
deleted file mode 100644
index a2223c9..0000000
--- a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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 'dart:io';
-
-import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart'
- as mirrorExecutor;
-
-import 'package:test/test.dart';
-
-import '../util.dart';
-
-void main() {
- late MacroExecutor executor;
- late File simpleMacroFile;
-
- setUpAll(() {
- // We support running from either the root of the SDK or the package root.
- simpleMacroFile = File(
- 'pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart');
- if (!simpleMacroFile.existsSync()) {
- simpleMacroFile =
- File('test/macros/isolate_mirror_executor/simple_macro.dart');
- }
- });
-
- setUp(() async {
- executor = await mirrorExecutor.start();
- });
-
- tearDown(() {
- executor.close();
- });
-
- test('can load macros and create instances', () async {
- var clazzId =
- await executor.loadMacro(simpleMacroFile.absolute.uri, 'SimpleMacro');
- expect(clazzId, isNotNull, reason: 'Can load a macro.');
-
- var instanceId =
- await executor.instantiateMacro(clazzId, '', Arguments([], {}));
- expect(instanceId, isNotNull,
- reason: 'Can create an instance with no arguments.');
-
- instanceId =
- await executor.instantiateMacro(clazzId, '', Arguments([1, 2], {}));
- expect(instanceId, isNotNull,
- reason: 'Can create an instance with positional arguments.');
-
- instanceId = await executor.instantiateMacro(
- clazzId, 'named', Arguments([], {'x': 1, 'y': 2}));
- expect(instanceId, isNotNull,
- reason: 'Can create an instance with named arguments.');
-
- var returnType = NamedTypeAnnotationImpl(
- id: RemoteInstance.uniqueId,
- identifier: IdentifierImpl(id: RemoteInstance.uniqueId, name: 'String'),
- isNullable: false,
- typeArguments: const []);
- var definitionResult = await executor.executeDefinitionsPhase(
- instanceId,
- FunctionDeclarationImpl(
- id: RemoteInstance.uniqueId,
- isAbstract: false,
- isExternal: false,
- isGetter: false,
- isOperator: false,
- isSetter: false,
- identifier: IdentifierImpl(id: RemoteInstance.uniqueId, name: 'foo'),
- namedParameters: [],
- positionalParameters: [],
- returnType: returnType,
- typeParameters: [],
- ),
- TestTypeResolver({
- returnType.identifier:
- TestNamedStaticType(returnType.identifier, 'dart:core', [])
- }),
- FakeClassIntrospector(),
- FakeTypeDeclarationResolver());
- expect(definitionResult.augmentations, hasLength(1));
- expect(definitionResult.augmentations.first.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment String foo() {
- print('x: 1, y: 2');
- return augment super();
- }'''));
- });
-}
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart b/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart
deleted file mode 100644
index f23aa43..0000000
--- a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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 'dart:async';
-
-import 'package:_fe_analyzer_shared/src/macros/api.dart';
-
-/// A very simple macro that annotates functions (or getters) with no arguments
-/// and adds a print statement to the top of them.
-class SimpleMacro implements FunctionDefinitionMacro {
- final int? x;
- final int? y;
-
- SimpleMacro([this.x, this.y]);
-
- SimpleMacro.named({this.x, this.y});
-
- @override
- FutureOr<void> buildDefinitionForFunction(
- FunctionDeclaration method, FunctionDefinitionBuilder builder) async {
- if (method.namedParameters
- .followedBy(method.positionalParameters)
- .isNotEmpty) {
- throw ArgumentError(
- 'This macro can only be run on functions with no arguments!');
- }
-
- // Test the type resolver and static type interfaces
- var staticReturnType = await builder.resolve(method.returnType.code);
- if (!(await staticReturnType.isExactly(staticReturnType))) {
- throw StateError('The return type should be exactly equal to itself!');
- }
- if (!(await staticReturnType.isSubtypeOf(staticReturnType))) {
- throw StateError('The return type should be a subtype of itself!');
- }
-
- builder.augment(FunctionBodyCode.fromString('''{
- print('x: $x, y: $y');
- return augment super();
- }'''));
- }
-}
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart
deleted file mode 100644
index ec4408f..0000000
--- a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart
+++ /dev/null
@@ -1,466 +0,0 @@
-// 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 'dart:io';
-import 'dart:isolate';
-
-import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
- as isolatedExecutor;
-
-import 'package:test/test.dart';
-
-import '../util.dart';
-
-void main() {
- late MacroExecutor executor;
- late File kernelOutputFile;
- final macroName = 'SimpleMacro';
- late MacroInstanceIdentifier instanceId;
- late Uri macroUri;
- late File simpleMacroFile;
- late Directory tmpDir;
-
- for (var mode in [
- SerializationMode.byteDataServer,
- SerializationMode.jsonServer
- ]) {
- final clientMode = mode == SerializationMode.byteDataServer
- ? SerializationMode.byteDataClient
- : SerializationMode.jsonClient;
-
- group('$mode', () {
- setUpAll(() async {
- simpleMacroFile =
- File(Platform.script.resolve('simple_macro.dart').toFilePath());
- executor = await isolatedExecutor.start(mode);
- tmpDir = Directory.systemTemp.createTempSync('isolated_executor_test');
- macroUri = simpleMacroFile.absolute.uri;
-
- var bootstrapContent = bootstrapMacroIsolate({
- macroUri.toString(): {
- macroName: ['', 'named']
- }
- }, clientMode);
- var bootstrapFile = File(tmpDir.uri.resolve('main.dart').toFilePath())
- ..writeAsStringSync(bootstrapContent);
- kernelOutputFile =
- File(tmpDir.uri.resolve('main.dart.dill').toFilePath());
- var buildSnapshotResult =
- await Process.run(Platform.resolvedExecutable, [
- '--snapshot=${kernelOutputFile.uri.toFilePath()}',
- '--snapshot-kind=kernel',
- '--packages=${(await Isolate.packageConfig)!}',
- bootstrapFile.uri.toFilePath(),
- ]);
- expect(buildSnapshotResult.exitCode, 0,
- reason: 'stdout: ${buildSnapshotResult.stdout}\n'
- 'stderr: ${buildSnapshotResult.stderr}');
-
- var clazzId = await executor.loadMacro(macroUri, macroName,
- precompiledKernelUri: kernelOutputFile.uri);
- expect(clazzId, isNotNull, reason: 'Can load a macro.');
-
- instanceId =
- await executor.instantiateMacro(clazzId, '', Arguments([], {}));
- expect(instanceId, isNotNull,
- reason: 'Can create an instance with no arguments.');
-
- instanceId =
- await executor.instantiateMacro(clazzId, '', Arguments([1, 2], {}));
- expect(instanceId, isNotNull,
- reason: 'Can create an instance with positional arguments.');
-
- instanceId = await executor.instantiateMacro(
- clazzId, 'named', Arguments([], {'x': 1, 'y': 2}));
- expect(instanceId, isNotNull,
- reason: 'Can create an instance with named arguments.');
- });
-
- tearDownAll(() {
- if (tmpDir.existsSync()) tmpDir.deleteSync(recursive: true);
- executor.close();
- });
-
- group('run macros', () {
- group('in the types phase', () {
- test('on functions', () async {
- var result = await executor.executeTypesPhase(
- instanceId, Fixtures.myFunction);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('class GeneratedByMyFunction {}'));
- });
-
- test('on methods', () async {
- var result =
- await executor.executeTypesPhase(instanceId, Fixtures.myMethod);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('class GeneratedByMyMethod {}'));
- });
-
- test('on getters', () async {
- var result = await executor.executeTypesPhase(
- instanceId,
- Fixtures.myVariableGetter,
- );
- expect(
- result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace(
- 'class GeneratedByMyVariableGetter {}'));
- });
-
- test('on setters', () async {
- var result = await executor.executeTypesPhase(
- instanceId,
- Fixtures.myVariableSetter,
- );
- expect(
- result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace(
- 'class GeneratedByMyVariableSetter {}'));
- });
-
- test('on variables', () async {
- var result = await executor.executeTypesPhase(
- instanceId,
- Fixtures.myVariable,
- );
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('class GeneratedBy_myVariable {}'));
- });
-
- test('on constructors', () async {
- var result = await executor.executeTypesPhase(
- instanceId, Fixtures.myConstructor);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('class GeneratedByMyConstructor {}'));
- });
-
- test('on fields', () async {
- var result =
- await executor.executeTypesPhase(instanceId, Fixtures.myField);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('class GeneratedByMyField {}'));
- });
-
- test('on classes', () async {
- var result =
- await executor.executeTypesPhase(instanceId, Fixtures.myClass);
- expect(
- result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace(
- 'class MyClassBuilder implements Builder<MyClass> {}'));
- });
- });
-
- group('in the declaration phase', () {
- test('on functions', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myFunction,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(
- result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace(
- 'String delegateMyFunction() => myFunction();'));
- });
-
- test('on methods', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myMethod,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(
- result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace(
- 'String delegateMemberMyMethod() => myMethod();'));
- });
-
- test('on constructors', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myConstructor,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment class MyClass {
- factory MyClass.myConstructorDelegate() => MyClass.myConstructor();
- }'''));
- });
-
- test('on getters', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myVariableGetter,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- String get delegateMyVariable => myVariable;'''));
- });
-
- test('on setters', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myVariableSetter,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- void set delegateMyVariable(String value) => myVariable = value;'''));
- });
-
- test('on variables', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myVariable,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- String get delegate_myVariable => _myVariable;'''));
- });
-
- test('on fields', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myField,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment class MyClass {
- String get delegateMyField => myField;
- }'''));
- });
-
- test('on classes', () async {
- var result = await executor.executeDeclarationsPhase(
- instanceId,
- Fixtures.myClass,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment class MyClass {
- static const List<String> fieldNames = ['myField',];
- }'''));
- });
- });
-
- group('in the definition phase', () {
- test('on functions', () async {
- var result = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myFunction,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment String myFunction() {
- print('isAbstract: false');
- print('isExternal: false');
- print('isGetter: false');
- print('isSetter: false');
- print('returnType: String');
- return augment super();
- }'''));
- });
-
- test('on methods', () async {
- var definitionResult = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myMethod,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(definitionResult.augmentations, hasLength(2));
- var augmentationStrings = definitionResult.augmentations
- .map((a) => a.debugString().toString())
- .toList();
- expect(
- augmentationStrings, unorderedEquals(methodDefinitionMatchers));
- });
-
- test('on constructors', () async {
- var definitionResult = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myConstructor,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(definitionResult.augmentations, hasLength(1));
- expect(
- definitionResult.augmentations.first.debugString().toString(),
- constructorDefinitionMatcher);
- });
-
- test('on getters', () async {
- var result = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myVariableGetter,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment String myVariable() {
- print('isAbstract: false');
- print('isExternal: false');
- print('isGetter: true');
- print('isSetter: false');
- print('returnType: String');
- return augment super;
- }'''));
- });
-
- test('on setters', () async {
- var result = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myVariableSetter,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(result.augmentations.single.debugString().toString(),
- equalsIgnoringWhitespace('''
- augment void myVariable(String value, ) {
- print('isAbstract: false');
- print('isExternal: false');
- print('isGetter: false');
- print('isSetter: true');
- print('returnType: void');
- print('positionalParam: String value');
- return augment super = value;
- }'''));
- });
-
- test('on variables', () async {
- var result = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myVariable,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(
- result.augmentations.map((a) => a.debugString().toString()),
- unorderedEquals([
- equalsIgnoringWhitespace('''
- augment String get _myVariable {
- print('parentClass: ');
- print('isExternal: false');
- print('isFinal: true');
- print('isLate: false');
- return augment super;
- }'''),
- equalsIgnoringWhitespace('''
- augment set _myVariable(String value) {
- augment super = value;
- }'''),
- equalsIgnoringWhitespace('''
- augment final String _myVariable = 'new initial value' + augment super;
- '''),
- ]));
- });
-
- test('on fields', () async {
- var definitionResult = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myField,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- expect(definitionResult.augmentations, hasLength(1));
- expect(
- definitionResult.augmentations.first.debugString().toString(),
- fieldDefinitionMatcher);
- });
-
- test('on classes', () async {
- var definitionResult = await executor.executeDefinitionsPhase(
- instanceId,
- Fixtures.myClass,
- Fixtures.testTypeResolver,
- Fixtures.testClassIntrospector,
- Fixtures.testTypeDeclarationResolver);
- var augmentationStrings = definitionResult.augmentations
- .map((a) => a.debugString().toString())
- .toList();
- expect(
- augmentationStrings,
- unorderedEquals([
- ...methodDefinitionMatchers,
- constructorDefinitionMatcher,
- fieldDefinitionMatcher
- ]));
- });
- });
- });
- });
- }
-}
-
-final constructorDefinitionMatcher = equalsIgnoringWhitespace('''
-augment class MyClass {
- augment MyClass.myConstructor() {
- print('definingClass: MyClass');
- print('isFactory: false');
- print('isAbstract: false');
- print('isExternal: false');
- print('isGetter: false');
- print('isSetter: false');
- print('returnType: MyClass');
- return augment super();
- }
-}''');
-
-final fieldDefinitionMatcher = equalsIgnoringWhitespace('''
-augment class MyClass {
- augment String get myField {
- print('parentClass: MyClass');
- print('isExternal: false');
- print('isFinal: false');
- print('isLate: false');
- return augment super;
- }
- augment set myField(String value) {
- augment super = value;
- }
- augment String myField = \'new initial value\' + augment super;
-}''');
-
-final methodDefinitionMatchers = [
- equalsIgnoringWhitespace('''
- augment class MyClass {
- augment String myMethod() {
- print('definingClass: MyClass');
- print('isAbstract: false');
- print('isExternal: false');
- print('isGetter: false');
- print('isSetter: false');
- print('returnType: String');
- return augment super();
- }
- }
- '''),
- equalsIgnoringWhitespace('''
- augment class MyClass {
- augment String myMethod() {
- print('x: 1, y: 2');
- print('parentClass: MyClass');
- print('superClass: MySuperclass');
- print('interface: MyInterface');
- print('mixin: MyMixin');
- print('field: myField');
- print('method: myMethod');
- print('constructor: myConstructor');
- return augment super();
- }
- }'''),
-];
diff --git a/pkg/_fe_analyzer_shared/test/macros/util.dart b/pkg/_fe_analyzer_shared/test/macros/util.dart
index 88f02d6..0907c70 100644
--- a/pkg/_fe_analyzer_shared/test/macros/util.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/util.dart
@@ -6,8 +6,8 @@
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';
diff --git a/pkg/compiler/lib/src/common/backend_api.dart b/pkg/compiler/lib/src/common/backend_api.dart
deleted file mode 100644
index 5c38952..0000000
--- a/pkg/compiler/lib/src/common/backend_api.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-library dart2js.backend_api;
-
-import '../common/resolution.dart' show ResolutionImpact;
-import '../universe/world_impact.dart' show WorldImpact;
-
-/// Target-specific transformation for resolution world impacts.
-///
-/// This processes target-agnostic [ResolutionImpact]s and creates [WorldImpact]
-/// in which backend/target specific impact data is added, for example: if
-/// certain feature is used that requires some helper code from the backend
-/// libraries, this will be included by the impact transformer.
-class ImpactTransformer {
- /// Transform the [ResolutionImpact] into a [WorldImpact] adding the
- /// backend dependencies for features used in [worldImpact].
- WorldImpact transformResolutionImpact(ResolutionImpact worldImpact) {
- return worldImpact;
- }
-}
diff --git a/pkg/compiler/lib/src/js_backend/impact_transformer.dart b/pkg/compiler/lib/src/js_backend/impact_transformer.dart
index 3a92051..e340b31 100644
--- a/pkg/compiler/lib/src/js_backend/impact_transformer.dart
+++ b/pkg/compiler/lib/src/js_backend/impact_transformer.dart
@@ -8,7 +8,6 @@
import '../common.dart';
import '../common/elements.dart';
-import '../common/backend_api.dart' show ImpactTransformer;
import '../common/codegen.dart' show CodegenImpact;
import '../common/resolution.dart' show ResolutionImpact;
import '../constants/values.dart';
@@ -34,7 +33,13 @@
import 'runtime_types.dart';
import 'runtime_types_resolution.dart';
-class JavaScriptImpactTransformer extends ImpactTransformer {
+/// JavaScript specific transformation for resolution world impacts.
+///
+/// This processes target-agnostic [ResolutionImpact]s and creates [WorldImpact]
+/// in which JavaScript specific impact data is added, for example: if
+/// a certain feature is used that requires some helper code from the backend
+/// libraries, this will be included by the impact transformer.
+class JavaScriptImpactTransformer {
final ElementEnvironment _elementEnvironment;
final CommonElements _commonElements;
final BackendImpacts _impacts;
@@ -60,7 +65,8 @@
DartTypes get _dartTypes => _commonElements.dartTypes;
- @override
+ /// Transform the [ResolutionImpact] into a [WorldImpact] adding the
+ /// backend dependencies for features used in [worldImpact].
WorldImpact transformResolutionImpact(ResolutionImpact worldImpact) {
TransformedWorldImpact transformed = TransformedWorldImpact(worldImpact);
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index dcd7fb2..ef176a1 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -7,7 +7,6 @@
import 'package:kernel/ast.dart' as ir;
import '../common.dart';
-import '../common/backend_api.dart';
import '../common/elements.dart';
import '../common/names.dart' show Uris;
import '../common/resolution.dart';
@@ -159,7 +158,7 @@
// before creating the resolution enqueuer.
AnnotationsData annotationsData = AnnotationsDataImpl(
compiler.options, annotationsDataBuilder.pragmaAnnotations);
- ImpactTransformer impactTransformer = JavaScriptImpactTransformer(
+ final impactTransformer = JavaScriptImpactTransformer(
elementEnvironment,
commonElements,
impacts,
@@ -303,7 +302,7 @@
class KernelWorkItemBuilder implements WorkItemBuilder {
final CompilerTask _compilerTask;
final KernelToElementMapImpl _elementMap;
- final ImpactTransformer _impactTransformer;
+ final JavaScriptImpactTransformer _impactTransformer;
final NativeMemberResolver _nativeMemberResolver;
final AnnotationsDataBuilder _annotationsDataBuilder;
final Map<MemberEntity, ClosureScopeModel> _closureModels;
@@ -347,7 +346,7 @@
class KernelWorkItem implements WorkItem {
final CompilerTask _compilerTask;
final KernelToElementMapImpl _elementMap;
- final ImpactTransformer _impactTransformer;
+ final JavaScriptImpactTransformer _impactTransformer;
final NativeMemberResolver _nativeMemberResolver;
final AnnotationsDataBuilder _annotationsDataBuilder;
@override
diff --git a/pkg/front_end/lib/src/fasta/kernel/macro.dart b/pkg/front_end/lib/src/fasta/kernel/macro.dart
index 6f6abea..c4a34b3 100644
--- a/pkg/front_end/lib/src/fasta/kernel/macro.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/macro.dart
@@ -4,9 +4,9 @@
import 'package:_fe_analyzer_shared/src/macros/api.dart' as macro;
import 'package:_fe_analyzer_shared/src/macros/executor.dart' as macro;
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart'
as macro;
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart'
as macro;
import 'package:front_end/src/base/common.dart';
import 'package:kernel/ast.dart' show DartType;
diff --git a/pkg/front_end/lib/src/kernel_generator_impl.dart b/pkg/front_end/lib/src/kernel_generator_impl.dart
index a20e586..8801b72 100644
--- a/pkg/front_end/lib/src/kernel_generator_impl.dart
+++ b/pkg/front_end/lib/src/kernel_generator_impl.dart
@@ -6,7 +6,7 @@
library front_end.kernel_generator_impl;
import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
diff --git a/pkg/front_end/test/macro_api_test.dart b/pkg/front_end/test/macro_api_test.dart
index 0938f4e..c096d36 100644
--- a/pkg/front_end/test/macro_api_test.dart
+++ b/pkg/front_end/test/macro_api_test.dart
@@ -4,8 +4,8 @@
import 'dart:io' show Directory, Platform;
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
as isolatedExecutor;
import 'package:expect/expect.dart';
import 'package:front_end/src/api_prototype/experimental_flags.dart';
diff --git a/pkg/front_end/test/macro_application/macro_application_test.dart b/pkg/front_end/test/macro_application/macro_application_test.dart
index 9282bac..fd317c7 100644
--- a/pkg/front_end/test/macro_application/macro_application_test.dart
+++ b/pkg/front_end/test/macro_application/macro_application_test.dart
@@ -6,8 +6,8 @@
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
as isolatedExecutor;
import 'package:_fe_analyzer_shared/src/testing/id.dart'
show ActualData, ClassId, Id, LibraryId;
diff --git a/pkg/front_end/test/macros/macro_test.dart b/pkg/front_end/test/macros/macro_test.dart
index 514d77b..e583b6b 100644
--- a/pkg/front_end/test/macros/macro_test.dart
+++ b/pkg/front_end/test/macros/macro_test.dart
@@ -6,7 +6,7 @@
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
import 'package:_fe_analyzer_shared/src/testing/features.dart';
import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index a8d2705..5669280 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -211,6 +211,8 @@
combinations
combinator
combiner
+communicate
+communicates
communication
compared
compares
@@ -522,6 +524,7 @@
gradually
granted
graphs
+grouper
growability
gt
guarantee
@@ -676,6 +679,7 @@
lang
largest
lattice
+launch
launched
launcher
layer
@@ -705,6 +709,7 @@
linebreak
linter
linux
+listening
lives
ll
llub
@@ -839,6 +844,7 @@
orphans
ors
os
+ourselves
outlined
outputs
outputting
@@ -924,6 +930,7 @@
println
prioritization
proc
+processes
processor
producers
product
diff --git a/runtime/bin/dartutils.cc b/runtime/bin/dartutils.cc
index 05a500e..5f2e056 100644
--- a/runtime/bin/dartutils.cc
+++ b/runtime/bin/dartutils.cc
@@ -658,6 +658,11 @@
return Dart_PostCObject(port_id, &object);
}
+bool DartUtils::PostString(Dart_Port port_id, const char* value) {
+ Dart_CObject* object = CObject::NewString(value);
+ return Dart_PostCObject(port_id, object);
+}
+
Dart_Handle DartUtils::GetDartType(const char* library_url,
const char* class_name) {
return Dart_GetNonNullableType(Dart_LookupLibrary(NewString(library_url)),
diff --git a/runtime/bin/dartutils.h b/runtime/bin/dartutils.h
index bbe0a1f..ca20354 100644
--- a/runtime/bin/dartutils.h
+++ b/runtime/bin/dartutils.h
@@ -173,6 +173,7 @@
static bool PostNull(Dart_Port port_id);
static bool PostInt32(Dart_Port port_id, int32_t value);
static bool PostInt64(Dart_Port port_id, int64_t value);
+ static bool PostString(Dart_Port port_id, const char* value);
static Dart_Handle GetDartType(const char* library_url,
const char* class_name);
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index 17b9404..ba4c459 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -129,6 +129,7 @@
V(SecureSocket_Init, 1) \
V(SecureSocket_PeerCertificate, 1) \
V(SecureSocket_RegisterBadCertificateCallback, 2) \
+ V(SecureSocket_RegisterKeyLogPort, 2) \
V(SecureSocket_RegisterHandshakeCompleteCallback, 2) \
V(SecureSocket_Renegotiate, 4) \
V(SecurityContext_Allocate, 1) \
diff --git a/runtime/bin/secure_socket_filter.cc b/runtime/bin/secure_socket_filter.cc
index 57c5829..43e3a9d 100644
--- a/runtime/bin/secure_socket_filter.cc
+++ b/runtime/bin/secure_socket_filter.cc
@@ -207,6 +207,15 @@
GetFilter(args)->RegisterBadCertificateCallback(callback);
}
+void FUNCTION_NAME(SecureSocket_RegisterKeyLogPort)(Dart_NativeArguments args) {
+ Dart_Handle port = ThrowIfError(Dart_GetNativeArgument(args, 1));
+ ASSERT(!Dart_IsNull(port));
+
+ Dart_Port port_id;
+ ThrowIfError(Dart_SendPortGetId(port, &port_id));
+ GetFilter(args)->RegisterKeyLogPort(port_id);
+}
+
void FUNCTION_NAME(SecureSocket_PeerCertificate)(Dart_NativeArguments args) {
Dart_Handle cert = ThrowIfError(GetFilter(args)->PeerCertificate());
Dart_SetReturnValue(args, cert);
@@ -465,6 +474,10 @@
return X509Helper::WrappedX509Certificate(ca);
}
+void SSLFilter::RegisterKeyLogPort(Dart_Port key_log_port) {
+ key_log_port_ = key_log_port;
+}
+
void SSLFilter::InitializeLibrary() {
MutexLocker locker(mutex_);
if (!library_initialized_) {
@@ -595,10 +608,14 @@
return SSL_ERROR_WANT_CERTIFICATE_VERIFY;
}
if (callback_error != NULL) {
- // The SSL_do_handshake will try performing a handshake and might call
- // a CertificateCallback. If the certificate validation
- // failed the 'callback_error" will be set by the certificateCallback
- // logic and we propagate the error"
+ // The SSL_do_handshake will try performing a handshake and might call one
+ // or both of:
+ // SSLCertContext::KeyLogCallback
+ // SSLCertContext::CertificateCallback
+ //
+ // If either of those functions fail, and this.callback_error has not
+ // already been set, then they will set this.callback_error to an error
+ // handle i.e. only the first error will be captured and propogated.
Dart_PropagateError(callback_error);
}
if (SSL_want_write(ssl_) || SSL_want_read(ssl_)) {
diff --git a/runtime/bin/secure_socket_filter.h b/runtime/bin/secure_socket_filter.h
index 826e28f..56f7f58 100644
--- a/runtime/bin/secure_socket_filter.h
+++ b/runtime/bin/secure_socket_filter.h
@@ -90,6 +90,8 @@
bool require_client_certificate);
void RegisterHandshakeCompleteCallback(Dart_Handle handshake_complete);
void RegisterBadCertificateCallback(Dart_Handle callback);
+ void RegisterKeyLogPort(Dart_Port key_log_port);
+ Dart_Port key_log_port() { return key_log_port_; }
Dart_Handle bad_certificate_callback() {
return Dart_HandleFromPersistent(bad_certificate_callback_);
}
@@ -145,6 +147,7 @@
Dart_Port reply_port_ = ILLEGAL_PORT;
Dart_Port trust_evaluate_reply_port_ = ILLEGAL_PORT;
+ Dart_Port key_log_port_ = ILLEGAL_PORT;
static bool IsBufferEncrypted(int i) {
return static_cast<BufferIndex>(i) >= kFirstEncrypted;
diff --git a/runtime/bin/secure_socket_unsupported.cc b/runtime/bin/secure_socket_unsupported.cc
index 8d2b2fc..37c2972 100644
--- a/runtime/bin/secure_socket_unsupported.cc
+++ b/runtime/bin/secure_socket_unsupported.cc
@@ -65,6 +65,11 @@
"Secure Sockets unsupported on this platform"));
}
+void FUNCTION_NAME(SecureSocket_RegisterKeyLogPort)(Dart_NativeArguments args) {
+ Dart_ThrowException(DartUtils::NewDartArgumentError(
+ "Secure Sockets unsupported on this platform"));
+}
+
void FUNCTION_NAME(SecureSocket_ProcessBuffer)(Dart_NativeArguments args) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Secure Sockets unsupported on this platform"));
diff --git a/runtime/bin/security_context.cc b/runtime/bin/security_context.cc
index 77e7ad1..8ccc6a3 100644
--- a/runtime/bin/security_context.cc
+++ b/runtime/bin/security_context.cc
@@ -74,13 +74,24 @@
"BadCertificateCallback returned a value that was not a boolean",
Dart_Null()));
}
- if (Dart_IsError(result)) {
+ // See SSLFilter::Handshake for the semantics of filter->callback_error.
+ if (Dart_IsError(result) && filter->callback_error == nullptr) {
filter->callback_error = result;
return 0;
}
return static_cast<int>(DartUtils::GetBooleanValue(result));
}
+void SSLCertContext::KeyLogCallback(const SSL* ssl, const char* line) {
+ SSLFilter* filter = static_cast<SSLFilter*>(
+ SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));
+
+ Dart_Port port = filter->key_log_port();
+ if (port != ILLEGAL_PORT) {
+ DartUtils::PostString(port, line);
+ }
+}
+
SSLCertContext* SSLCertContext::GetSecurityContext(Dart_NativeArguments args) {
SSLCertContext* context;
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
@@ -807,6 +818,7 @@
SSLFilter::InitializeLibrary();
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLCertContext::CertificateCallback);
+ SSL_CTX_set_keylog_callback(ctx, SSLCertContext::KeyLogCallback);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM");
SSLCertContext* context = new SSLCertContext(ctx);
diff --git a/runtime/bin/security_context.h b/runtime/bin/security_context.h
index e8800e2..b5bc66b 100644
--- a/runtime/bin/security_context.h
+++ b/runtime/bin/security_context.h
@@ -30,17 +30,16 @@
explicit SSLCertContext(SSL_CTX* context)
: ReferenceCounted(),
context_(context),
- alpn_protocol_string_(NULL),
+ alpn_protocol_string_(nullptr),
trust_builtin_(false) {}
~SSLCertContext() {
SSL_CTX_free(context_);
- if (alpn_protocol_string_ != NULL) {
- free(alpn_protocol_string_);
- }
+ free(alpn_protocol_string_);
}
static int CertificateCallback(int preverify_ok, X509_STORE_CTX* store_ctx);
+ static void KeyLogCallback(const SSL* ssl, const char* line);
static SSLCertContext* GetSecurityContext(Dart_NativeArguments args);
static const char* GetPasswordArgument(Dart_NativeArguments args,
diff --git a/runtime/tests/vm/dart/regress_big_regexp_test.dart b/runtime/tests/vm/dart/regress_big_regexp_test.dart
index 09a7aa4..8a3530d 100644
--- a/runtime/tests/vm/dart/regress_big_regexp_test.dart
+++ b/runtime/tests/vm/dart/regress_big_regexp_test.dart
@@ -2,6 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// VMOptions=--intrinsify
+// VMOptions=--no_intrinsify
+
// Verifies that RegExp compilation doesn't crash on a huge source string.
import 'package:expect/expect.dart';
diff --git a/runtime/tests/vm/dart_2/regress_big_regexp_test.dart b/runtime/tests/vm/dart_2/regress_big_regexp_test.dart
index 1ef005d..69729d1 100644
--- a/runtime/tests/vm/dart_2/regress_big_regexp_test.dart
+++ b/runtime/tests/vm/dart_2/regress_big_regexp_test.dart
@@ -2,6 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// VMOptions=--intrinsify
+// VMOptions=--no_intrinsify
+
// @dart = 2.9
// Verifies that RegExp compilation doesn't crash on a huge source string.
diff --git a/runtime/vm/compiler/asm_intrinsifier_riscv.cc b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
index a800323..0e2da82 100644
--- a/runtime/vm/compiler/asm_intrinsifier_riscv.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
@@ -670,8 +670,34 @@
bool sticky) {
if (FLAG_interpret_irregexp) return;
- // TODO(riscv)
- __ Bind(normal_ir_body);
+ static const intptr_t kRegExpParamOffset = 2 * target::kWordSize;
+ static const intptr_t kStringParamOffset = 1 * target::kWordSize;
+ // start_index smi is located at offset 0.
+
+ // Incoming registers:
+ // T0: Function. (Will be reloaded with the specialized matcher function.)
+ // S4: Arguments descriptor. (Will be preserved.)
+ // S5: Unknown. (Must be GC safe on tail call.)
+
+ // Load the specialized function pointer into R0. Leverage the fact the
+ // string CIDs as well as stored function pointers are in sequence.
+ __ lx(T2, Address(SP, kRegExpParamOffset));
+ __ lx(T1, Address(SP, kStringParamOffset));
+ __ LoadClassId(T1, T1);
+ __ AddImmediate(T1, -kOneByteStringCid);
+ __ slli(T1, T1, target::kWordSizeLog2);
+ __ add(T1, T1, T2);
+ __ lx(T0, FieldAddress(T1, target::RegExp::function_offset(kOneByteStringCid,
+ sticky)));
+
+ // Registers are now set up for the lazy compile stub. It expects the function
+ // in R0, the argument descriptor in R4, and IC-Data in R5.
+ __ li(S5, 0);
+
+ // Tail-call the function.
+ __ lx(CODE_REG, FieldAddress(T0, target::Function::code_offset()));
+ __ lx(T1, FieldAddress(T0, target::Function::entry_point_offset()));
+ __ jr(T1);
}
void AsmIntrinsifier::UserTag_defaultTag(Assembler* assembler,
diff --git a/runtime/vm/regexp_assembler_ir.cc b/runtime/vm/regexp_assembler_ir.cc
index 60dc2c6..36c5414 100644
--- a/runtime/vm/regexp_assembler_ir.cc
+++ b/runtime/vm/regexp_assembler_ir.cc
@@ -299,14 +299,12 @@
const Object& retval =
Object::Handle(zone, DartEntry::InvokeFunction(fun, args));
- if (retval.IsUnwindError()) {
- Exceptions::PropagateError(Error::Cast(retval));
+ if (retval.IsLanguageError()) {
+ Exceptions::ThrowCompileTimeError(LanguageError::Cast(retval));
+ UNREACHABLE();
}
if (retval.IsError()) {
- const Error& error = Error::Cast(retval);
- OS::PrintErr("%s\n", error.ToErrorCString());
- // Should never happen.
- UNREACHABLE();
+ Exceptions::PropagateError(Error::Cast(retval));
}
if (retval.IsNull()) {
diff --git a/sdk/lib/_http/http.dart b/sdk/lib/_http/http.dart
index 5503db6..6ea7e64 100644
--- a/sdk/lib/_http/http.dart
+++ b/sdk/lib/_http/http.dart
@@ -1688,6 +1688,21 @@
void set badCertificateCallback(
bool Function(X509Certificate cert, String host, int port)? callback);
+ /// Sets a callback that will be called when new TLS keys are exchanged with
+ /// the server. It will receive one line of text in
+ /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+ /// for each call. Writing these lines to a file will allow tools (such as
+ /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+ /// to decrypt communication between the this client and the server. This is
+ /// meant to allow network-level debugging of secure sockets and should not
+ /// be used in production code. For example:
+ ///
+ /// final log = File('keylog.txt');
+ /// final client = HttpClient();
+ /// client.keyLog = (line) => log.writeAsStringSync(line,
+ /// mode: FileMode.append);
+ void set keyLog(Function(String line)? callback);
+
/// Shuts down the HTTP client.
///
/// If [force] is `false` (the default) the [HttpClient] will be kept alive
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index bb38ebe..e1c0d5c 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -2447,7 +2447,9 @@
} else {
connectionTask = (isSecure && proxy.isDirect
? SecureSocket.startConnect(host, port,
- context: context, onBadCertificate: callback)
+ context: context,
+ onBadCertificate: callback,
+ keyLog: client._keyLog)
: Socket.startConnect(host, port));
}
_connecting++;
@@ -2526,6 +2528,7 @@
String Function(Uri)? _findProxy = HttpClient.findProxyFromEnvironment;
Duration _idleTimeout = const Duration(seconds: 15);
BadCertificateCallback? _badCertificateCallback;
+ Function(String line)? _keyLog;
Duration get idleTimeout => _idleTimeout;
@@ -2555,6 +2558,10 @@
_badCertificateCallback = callback;
}
+ void set keyLog(Function(String line)? callback) {
+ _keyLog = callback;
+ }
+
Future<HttpClientRequest> open(
String method, String host, int port, String path) {
const int hashMark = 0x23;
diff --git a/sdk/lib/_internal/vm/bin/secure_socket_patch.dart b/sdk/lib/_internal/vm/bin/secure_socket_patch.dart
index cfd1d6b..a2ce0f3 100644
--- a/sdk/lib/_internal/vm/bin/secure_socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/secure_socket_patch.dart
@@ -189,6 +189,9 @@
external void registerHandshakeCompleteCallback(
Function handshakeCompleteHandler);
+ @pragma("vm:external-name", "SecureSocket_RegisterKeyLogPort")
+ external void registerKeyLogPort(SendPort port);
+
// This is a security issue, as it exposes a raw pointer to Dart code.
@pragma("vm:external-name", "SecureSocket_FilterPointer")
external int _pointer();
diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart
index 6150653..de267bb 100644
--- a/sdk/lib/io/secure_socket.dart
+++ b/sdk/lib/io/secure_socket.dart
@@ -27,6 +27,20 @@
/// the connection or not. The handler should return true
/// to continue the [SecureSocket] connection.
///
+ /// [keyLog] is an optional callback that will be called when new TLS keys
+ /// are exchanged with the server. [keyLog] will receive one line of text in
+ /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+ /// for each call. Writing these lines to a file will allow tools (such as
+ /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+ /// to decrypt content sent through this socket. This is meant to allow
+ /// network-level debugging of secure sockets and should not be used in
+ /// production code. For example:
+ /// ```dart
+ /// final log = File('keylog.txt');
+ /// final socket = await SecureSocket.connect('www.example.com', 443,
+ /// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+ /// ```
+ ///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@@ -40,11 +54,13 @@
static Future<SecureSocket> connect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
List<String>? supportedProtocols,
Duration? timeout}) {
return RawSecureSocket.connect(host, port,
context: context,
onBadCertificate: onBadCertificate,
+ keyLog: keyLog,
supportedProtocols: supportedProtocols,
timeout: timeout)
.then((rawSocket) => new SecureSocket._(rawSocket));
@@ -56,10 +72,12 @@
static Future<ConnectionTask<SecureSocket>> startConnect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
List<String>? supportedProtocols}) {
return RawSecureSocket.startConnect(host, port,
context: context,
onBadCertificate: onBadCertificate,
+ keyLog: keyLog,
supportedProtocols: supportedProtocols)
.then((rawState) {
Future<SecureSocket> socket =
@@ -88,6 +106,26 @@
/// the [socket] will be used. The [host] can be either a [String] or
/// an [InternetAddress].
///
+ /// [onBadCertificate] is an optional handler for unverifiable certificates.
+ /// The handler receives the [X509Certificate], and can inspect it and
+ /// decide (or let the user decide) whether to accept
+ /// the connection or not. The handler should return true
+ /// to continue the [SecureSocket] connection.
+ ///
+ /// [keyLog] is an optional callback that will be called when new TLS keys
+ /// are exchanged with the server. [keyLog] will receive one line of text in
+ /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+ /// for each call. Writing these lines to a file will allow tools (such as
+ /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+ /// to decrypt content sent through this socket. This is meant to allow
+ /// network-level debugging of secure sockets and should not be used in
+ /// production code. For example:
+ /// ```dart
+ /// final log = File('keylog.txt');
+ /// final socket = await SecureSocket.connect('www.example.com', 443,
+ /// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+ /// ```
+ ///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@@ -104,6 +142,7 @@
{host,
SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
@Since("2.6") List<String>? supportedProtocols}) {
return ((socket as dynamic /*_Socket*/)._detachRaw() as Future)
.then<RawSecureSocket>((detachedRaw) {
@@ -112,6 +151,7 @@
host: host,
context: context,
onBadCertificate: onBadCertificate,
+ keyLog: keyLog,
supportedProtocols: supportedProtocols);
}).then<SecureSocket>((raw) => new SecureSocket._(raw));
}
@@ -209,6 +249,26 @@
/// the connection or not. The handler should return true
/// to continue the [RawSecureSocket] connection.
///
+ /// [onBadCertificate] is an optional handler for unverifiable certificates.
+ /// The handler receives the [X509Certificate], and can inspect it and
+ /// decide (or let the user decide) whether to accept
+ /// the connection or not. The handler should return true
+ /// to continue the [SecureSocket] connection.
+ ///
+ /// [keyLog] is an optional callback that will be called when new TLS keys
+ /// are exchanged with the server. [keyLog] will receive one line of text in
+ /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+ /// for each call. Writing these lines to a file will allow tools (such as
+ /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+ /// to decrypt content sent through this socket. This is meant to allow
+ /// network-level debugging of secure sockets and should not be used in
+ /// production code. For example:
+ /// ```dart
+ /// final log = File('keylog.txt');
+ /// final socket = await SecureSocket.connect('www.example.com', 443,
+ /// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+ /// ```
+ ///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@@ -216,6 +276,7 @@
static Future<RawSecureSocket> connect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
List<String>? supportedProtocols,
Duration? timeout}) {
_RawSecureSocket._verifyFields(host, port, false, false);
@@ -223,6 +284,7 @@
return secure(socket,
context: context,
onBadCertificate: onBadCertificate,
+ keyLog: keyLog,
supportedProtocols: supportedProtocols);
});
}
@@ -233,6 +295,7 @@
static Future<ConnectionTask<RawSecureSocket>> startConnect(host, int port,
{SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
List<String>? supportedProtocols}) {
return RawSocket.startConnect(host, port)
.then((ConnectionTask<RawSocket> rawState) {
@@ -240,6 +303,7 @@
return secure(rawSocket,
context: context,
onBadCertificate: onBadCertificate,
+ keyLog: keyLog,
supportedProtocols: supportedProtocols);
});
return new ConnectionTask<RawSecureSocket>._(socket, rawState._onCancel);
@@ -266,6 +330,26 @@
/// the [socket] will be used. The [host] can be either a [String] or
/// an [InternetAddress].
///
+ /// [onBadCertificate] is an optional handler for unverifiable certificates.
+ /// The handler receives the [X509Certificate], and can inspect it and
+ /// decide (or let the user decide) whether to accept
+ /// the connection or not. The handler should return true
+ /// to continue the [SecureSocket] connection.
+ ///
+ /// [keyLog] is an optional callback that will be called when new TLS keys
+ /// are exchanged with the server. [keyLog] will receive one line of text in
+ /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+ /// for each call. Writing these lines to a file will allow tools (such as
+ /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+ /// to decrypt content sent through this socket. This is meant to allow
+ /// network-level debugging of secure sockets and should not be used in
+ /// production code. For example:
+ /// ```dart
+ /// final log = File('keylog.txt');
+ /// final socket = await SecureSocket.connect('www.example.com', 443,
+ /// keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+ /// ```
+ ///
/// [supportedProtocols] is an optional list of protocols (in decreasing
/// order of preference) to use during the ALPN protocol negotiation with the
/// server. Example values are "http/1.1" or "h2". The selected protocol
@@ -283,6 +367,7 @@
host,
SecurityContext? context,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
List<String>? supportedProtocols}) {
socket.readEventsEnabled = false;
socket.writeEventsEnabled = false;
@@ -291,6 +376,7 @@
subscription: subscription,
context: context,
onBadCertificate: onBadCertificate,
+ keyLog: keyLog,
supportedProtocols: supportedProtocols);
}
@@ -427,6 +513,8 @@
final bool requestClientCertificate;
final bool requireClientCertificate;
final bool Function(X509Certificate certificate)? onBadCertificate;
+ final void Function(String line)? keyLog;
+ ReceivePort? keyLogPort;
var _status = handshakeStatus;
bool _writeEventsEnabled = true;
@@ -458,6 +546,7 @@
bool requestClientCertificate = false,
bool requireClientCertificate = false,
bool onBadCertificate(X509Certificate certificate)?,
+ void keyLog(String line)?,
List<String>? supportedProtocols}) {
_verifyFields(host, requestedPort, requestClientCertificate,
requireClientCertificate);
@@ -477,6 +566,7 @@
requestClientCertificate,
requireClientCertificate,
onBadCertificate,
+ keyLog,
supportedProtocols)
._handshakeComplete
.future;
@@ -493,6 +583,7 @@
this.requestClientCertificate,
this.requireClientCertificate,
this.onBadCertificate,
+ this.keyLog,
List<String>? supportedProtocols) {
_controller
..onListen = _onSubscriptionStateChange
@@ -505,6 +596,23 @@
secureFilter.init();
secureFilter
.registerHandshakeCompleteCallback(_secureHandshakeCompleteHandler);
+
+ if (keyLog != null) {
+ final port = ReceivePort();
+ port.listen((line) {
+ try {
+ keyLog!((line as String) + '\n');
+ } catch (e, s) {
+ // There is no obvious place to surface exceptions from the keyLog
+ // callback so write the details to stderr.
+ stderr.writeln("Failure in keyLog callback:");
+ stderr.writeln(s);
+ }
+ });
+ secureFilter.registerKeyLogPort(port.sendPort);
+ keyLogPort = port;
+ }
+
if (onBadCertificate != null) {
secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper);
}
@@ -607,6 +715,7 @@
_secureFilter!.destroy();
_secureFilter = null;
}
+ keyLogPort?.close();
if (_socketSubscription != null) {
_socketSubscription.cancel();
}
@@ -1240,6 +1349,7 @@
int processBuffer(int bufferIndex);
void registerBadCertificateCallback(Function callback);
void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
+ void registerKeyLogPort(SendPort port);
// This call may cause a reference counted pointer in the native
// implementation to be retained. It should only be called when the resulting
diff --git a/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart b/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart
index a7db2d5..29544d8 100644
--- a/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart
+++ b/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart
@@ -225,29 +225,89 @@
}
}
- @JSName('decodeAudioData')
- Future<AudioBuffer> _decodeAudioData(ByteBuffer audioData,
- [DecodeSuccessCallback? successCallback,
- DecodeErrorCallback? errorCallback]) native;
-
Future<AudioBuffer> decodeAudioData(ByteBuffer audioData,
[DecodeSuccessCallback? successCallback,
DecodeErrorCallback? errorCallback]) {
- if (successCallback != null && errorCallback != null) {
- return _decodeAudioData(audioData, successCallback, errorCallback);
+ // Both callbacks need to be provided if they're being used.
+ assert((successCallback == null) == (errorCallback == null));
+ // `decodeAudioData` can exist either in the older callback syntax or the
+ // newer `Promise`-based syntax that also accepts callbacks. In the former,
+ // we synthesize a `Future` to be consistent.
+ // For more details:
+ // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData
+ // https://www.w3.org/TR/webaudio/#dom-baseaudiocontext-decodeaudiodata
+ final completer = Completer<Object>();
+ var errorInCallbackIsNull = false;
+
+ void success(AudioBuffer decodedData) {
+ completer.complete(decodedData);
+ successCallback!.call(decodedData);
}
- var completer = new Completer<AudioBuffer>();
- _decodeAudioData(audioData, (value) {
- completer.complete(value);
- }, (error) {
- if (error == null) {
- completer.completeError('');
+ final nullErrorString =
+ '[AudioContext.decodeAudioData] completed with a null error.';
+
+ void error(DomException? error) {
+ // Safari has a bug where it may return null for the error callback. In
+ // the case where the Safari version still returns a `Promise` and the
+ // error is not null after the `Promise` is finished, the error callback
+ // is called instead in the `Promise`'s `catch` block. Otherwise, and in
+ // the case where a `Promise` is not returned by the API at all, the
+ // callback never gets called (for backwards compatibility, it can not
+ // accept null). Instead, the `Future` completes with a custom string,
+ // indicating that null was given.
+ // https://github.com/mdn/webaudio-examples/issues/5
+ if (error != null) {
+ // Note that we `complete` and not `completeError`. This is to make sure
+ // that errors in the `Completer` are not thrown if the call gets back
+ // a `Promise`.
+ completer.complete(error);
+ errorCallback!.call(error);
} else {
- completer.completeError(error);
+ completer.complete(nullErrorString);
+ errorInCallbackIsNull = true;
}
+ }
+
+ var decodeResult;
+ if (successCallback == null) {
+ decodeResult =
+ JS("creates:AudioBuffer;", "#.decodeAudioData(#)", this, audioData);
+ } else {
+ decodeResult = JS(
+ "creates:AudioBuffer;",
+ "#.decodeAudioData(#, #, #)",
+ this,
+ audioData,
+ convertDartClosureToJS(success, 1),
+ convertDartClosureToJS(error, 1));
+ }
+
+ if (decodeResult != null) {
+ // Promise-based syntax.
+ return promiseToFuture<AudioBuffer>(decodeResult).catchError((error) {
+ // If the error was null in the callback, but no longer is now that the
+ // `Promise` is finished, call the error callback. If it's still null,
+ // throw the error string. This is to handle the aforementioned bug in
+ // Safari.
+ if (errorInCallbackIsNull) {
+ if (error != null) {
+ errorCallback?.call(error);
+ } else {
+ throw nullErrorString;
+ }
+ }
+ throw error;
+ });
+ }
+
+ // Callback-based syntax. We use the above completer to synthesize a
+ // `Future` from the callback values. Since we don't use `completeError`
+ // above, `then` is used to simulate an error.
+ return completer.future.then((value) {
+ if (value is AudioBuffer) return value;
+ throw value;
});
- return completer.future;
}
}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
diff --git a/tests/lib/html/audiocontext_test.dart b/tests/lib/html/audiocontext_test.dart
index 8316130..a091790 100644
--- a/tests/lib/html/audiocontext_test.dart
+++ b/tests/lib/html/audiocontext_test.dart
@@ -7,6 +7,7 @@
import 'dart:typed_data';
import 'dart:web_audio';
+import 'package:async_helper/async_helper.dart';
import 'package:expect/minitest.dart';
main() {
@@ -95,5 +96,74 @@
expect(oscillator.type, equals('triangle'));
}
});
+
+ asyncTest(() async {
+ if (AudioContext.supported) {
+ final audioSourceUrl = "/root_dart/tests/lib/html/small.mp3";
+
+ Future<void> requestAudioDecode(
+ {bool triggerDecodeError: false,
+ DecodeSuccessCallback? successCallback,
+ DecodeErrorCallback? errorCallback}) async {
+ HttpRequest audioRequest = HttpRequest();
+ audioRequest.open("GET", audioSourceUrl, async: true);
+ audioRequest.responseType = "arraybuffer";
+ var completer = new Completer<void>();
+ audioRequest.onLoad.listen((_) {
+ ByteBuffer audioData = audioRequest.response;
+ if (triggerDecodeError) audioData = Uint8List.fromList([]).buffer;
+ context
+ .decodeAudioData(audioData, successCallback, errorCallback)
+ .then((_) {
+ completer.complete();
+ }).catchError((e) {
+ completer.completeError(e);
+ });
+ });
+ audioRequest.send();
+ return completer.future;
+ }
+
+ // Decode successfully without callback.
+ await requestAudioDecode();
+
+ // Decode successfully with callback. Use counter to make sure it's only
+ // called once.
+ var successCallbackCalled = 0;
+ await requestAudioDecode(
+ successCallback: (_) {
+ successCallbackCalled += 1;
+ },
+ errorCallback: (_) {});
+ expect(successCallbackCalled, 1);
+
+ // Fail decode without callback.
+ try {
+ await requestAudioDecode(triggerDecodeError: true);
+ fail('Expected decode failure.');
+ } catch (_) {}
+
+ // Fail decode with callback.
+ var errorCallbackCalled = 0;
+ try {
+ await requestAudioDecode(
+ triggerDecodeError: true,
+ successCallback: (_) {},
+ errorCallback: (_) {
+ errorCallbackCalled += 1;
+ });
+ fail('Expected decode failure.');
+ } catch (e) {
+ // Safari may return a null error. Assuming Safari is version >= 14.1,
+ // the Future should complete with a string error if the error
+ // callback never gets called.
+ if (errorCallbackCalled == 0) {
+ expect(e is String, true);
+ } else {
+ expect(errorCallbackCalled, 1);
+ }
+ }
+ }
+ });
});
}
diff --git a/tests/lib/html/small.mp3 b/tests/lib/html/small.mp3
new file mode 100644
index 0000000..3fcc88b
--- /dev/null
+++ b/tests/lib/html/small.mp3
Binary files differ
diff --git a/tests/lib_2/html/audiocontext_test.dart b/tests/lib_2/html/audiocontext_test.dart
index b66e54b..396cf39 100644
--- a/tests/lib_2/html/audiocontext_test.dart
+++ b/tests/lib_2/html/audiocontext_test.dart
@@ -1,3 +1,6 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
import 'dart:async';
@@ -5,6 +8,7 @@
import 'dart:typed_data';
import 'dart:web_audio';
+import 'package:async_helper/async_helper.dart';
import 'package:expect/minitest.dart';
main() {
@@ -93,5 +97,74 @@
expect(oscillator.type, equals('triangle'));
}
});
+
+ asyncTest(() async {
+ if (AudioContext.supported) {
+ final audioSourceUrl = "/root_dart/tests/lib_2/html/small.mp3";
+
+ Future<void> requestAudioDecode(
+ {bool triggerDecodeError: false,
+ DecodeSuccessCallback successCallback,
+ DecodeErrorCallback errorCallback}) async {
+ HttpRequest audioRequest = HttpRequest();
+ audioRequest.open("GET", audioSourceUrl, async: true);
+ audioRequest.responseType = "arraybuffer";
+ var completer = new Completer<void>();
+ audioRequest.onLoad.listen((_) {
+ ByteBuffer audioData = audioRequest.response;
+ if (triggerDecodeError) audioData = Uint8List.fromList([]).buffer;
+ context
+ .decodeAudioData(audioData, successCallback, errorCallback)
+ .then((_) {
+ completer.complete();
+ }).catchError((e) {
+ completer.completeError(e);
+ });
+ });
+ audioRequest.send();
+ return completer.future;
+ }
+
+ // Decode successfully without callback.
+ await requestAudioDecode();
+
+ // Decode successfully with callback. Use counter to make sure it's only
+ // called once.
+ var successCallbackCalled = 0;
+ await requestAudioDecode(
+ successCallback: (_) {
+ successCallbackCalled += 1;
+ },
+ errorCallback: (_) {});
+ expect(successCallbackCalled, 1);
+
+ // Fail decode without callback.
+ try {
+ await requestAudioDecode(triggerDecodeError: true);
+ fail('Expected decode failure.');
+ } catch (_) {}
+
+ // Fail decode with callback.
+ var errorCallbackCalled = 0;
+ try {
+ await requestAudioDecode(
+ triggerDecodeError: true,
+ successCallback: (_) {},
+ errorCallback: (_) {
+ errorCallbackCalled += 1;
+ });
+ fail('Expected decode failure.');
+ } catch (e) {
+ // Safari may return a null error. Assuming Safari is version >= 14.1,
+ // the Future should complete with a string error if the error
+ // callback never gets called.
+ if (errorCallbackCalled == 0) {
+ expect(e is String, true);
+ } else {
+ expect(errorCallbackCalled, 1);
+ }
+ }
+ }
+ });
});
}
diff --git a/tests/lib_2/html/small.mp3 b/tests/lib_2/html/small.mp3
new file mode 100644
index 0000000..3fcc88b
--- /dev/null
+++ b/tests/lib_2/html/small.mp3
Binary files differ
diff --git a/tests/standalone/io/http_key_log_test.dart b/tests/standalone/io/http_key_log_test.dart
new file mode 100644
index 0000000..0b948a2
--- /dev/null
+++ b/tests/standalone/io/http_key_log_test.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+late InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+ ..useCertificateChain(localFile('certificates/server_chain.pem'))
+ ..usePrivateKey(localFile('certificates/server_key.pem'),
+ password: 'dartdart');
+
+Future<HttpServer> startEchoServer() {
+ return HttpServer.bindSecure(HOST, 0, serverContext).then((server) {
+ server.listen((HttpRequest req) {
+ final res = req.response;
+ res.write("Test");
+ res.close();
+ });
+ return server;
+ });
+}
+
+testSuccess(HttpServer server) async {
+ var log = "";
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ final client = HttpClient(context: clientContext);
+ client.keyLog = (String line) {
+ log += line;
+ };
+ final request =
+ await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+ final response = await request.close();
+ await response.drain();
+
+ Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+}
+
+testExceptionInKeyLogFunction(HttpServer server) async {
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ final client = HttpClient(context: clientContext);
+ var numCalls = 0;
+ client.keyLog = (String line) {
+ ++numCalls;
+ throw FileSystemException("Something bad happened");
+ };
+ final request =
+ await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+ final response = await request.close();
+ await response.drain();
+
+ Expect.notEquals(0, numCalls);
+}
+
+void main() async {
+ asyncStart();
+ await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+ final server = await startEchoServer();
+
+ await testSuccess(server);
+ await testExceptionInKeyLogFunction(server);
+
+ await server.close();
+ asyncEnd();
+}
diff --git a/tests/standalone/io/http_override_test.dart b/tests/standalone/io/http_override_test.dart
index 92270d0..fccbbe4 100644
--- a/tests/standalone/io/http_override_test.dart
+++ b/tests/standalone/io/http_override_test.dart
@@ -52,6 +52,7 @@
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)?) {}
+ void set keyLog(Function(String line)? callback) {}
void close({bool force: false}) {}
}
@@ -100,6 +101,7 @@
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)?) {}
+ void set keyLog(Function(String line)? callback) {}
void close({bool force: false}) {}
}
diff --git a/tests/standalone/io/secure_key_log_test.dart b/tests/standalone/io/secure_key_log_test.dart
new file mode 100644
index 0000000..aec2270
--- /dev/null
+++ b/tests/standalone/io/secure_key_log_test.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+late InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+ ..useCertificateChain(localFile('certificates/server_chain.pem'))
+ ..usePrivateKey(localFile('certificates/server_key.pem'),
+ password: 'dartdart');
+
+Future<SecureServerSocket> startEchoServer() {
+ return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
+ server.listen((SecureSocket client) {
+ client.fold<List<int>>(
+ <int>[], (message, data) => message..addAll(data)).then((message) {
+ client.add(message);
+ client.close();
+ });
+ });
+ return server;
+ });
+}
+
+testSuccess(SecureServerSocket server) async {
+ var log = "";
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ await SecureSocket.connect(HOST, server.port, context: clientContext,
+ keyLog: (line) {
+ log += line;
+ }).then((socket) {
+ socket.write("Hello server.");
+ socket.close();
+ return socket.drain().then((value) {
+ Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+ return server;
+ });
+ });
+}
+
+testExceptionInKeyLogFunction(SecureServerSocket server) async {
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ var numCalls = 0;
+ await SecureSocket.connect(HOST, server.port, context: clientContext,
+ keyLog: (line) {
+ ++numCalls;
+ throw FileSystemException("Something bad happened");
+ }).then((socket) {
+ socket.close();
+ return socket.drain().then((value) {
+ Expect.notEquals(0, numCalls);
+ return server;
+ });
+ });
+}
+
+void main() async {
+ asyncStart();
+ await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+ final server = await startEchoServer();
+
+ await testSuccess(server);
+ await testExceptionInKeyLogFunction(server);
+
+ await server.close();
+ asyncEnd();
+}
diff --git a/tests/standalone_2/io/http_key_log_test.dart b/tests/standalone_2/io/http_key_log_test.dart
new file mode 100644
index 0000000..ecfc916
--- /dev/null
+++ b/tests/standalone_2/io/http_key_log_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+
+// @dart = 2.9
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+ ..useCertificateChain(localFile('certificates/server_chain.pem'))
+ ..usePrivateKey(localFile('certificates/server_key.pem'),
+ password: 'dartdart');
+
+Future<HttpServer> startEchoServer() {
+ return HttpServer.bindSecure(HOST, 0, serverContext).then((server) {
+ server.listen((HttpRequest req) {
+ final res = req.response;
+ res.write("Test");
+ res.close();
+ });
+ return server;
+ });
+}
+
+testSuccess(HttpServer server) async {
+ var log = "";
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ final client = HttpClient(context: clientContext);
+ client.keyLog = (String line) {
+ log += line;
+ };
+ final request =
+ await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+ final response = await request.close();
+ await response.drain();
+
+ Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+}
+
+testExceptionInKeyLogFunction(HttpServer server) async {
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ final client = HttpClient(context: clientContext);
+ var numCalls = 0;
+ client.keyLog = (String line) {
+ ++numCalls;
+ throw FileSystemException("Something bad happened");
+ };
+ final request =
+ await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+ final response = await request.close();
+ await response.drain();
+
+ Expect.notEquals(0, numCalls);
+}
+
+void main() async {
+ asyncStart();
+ await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+ final server = await startEchoServer();
+
+ await testSuccess(server);
+ await testExceptionInKeyLogFunction(server);
+
+ await server.close();
+ asyncEnd();
+}
diff --git a/tests/standalone_2/io/http_override_test.dart b/tests/standalone_2/io/http_override_test.dart
index 72b4967..b3a8255 100644
--- a/tests/standalone_2/io/http_override_test.dart
+++ b/tests/standalone_2/io/http_override_test.dart
@@ -50,6 +50,8 @@
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)) {}
+ void set keyLog(Function(String line) callback) =>
+ throw UnsupportedError("keyLog not implemented");
void close({bool force: false}) {}
}
@@ -94,6 +96,8 @@
String host, int port, String realm, HttpClientCredentials credentials) {}
set badCertificateCallback(
bool callback(X509Certificate cert, String host, int port)) {}
+ void set keyLog(Function(String line) callback) =>
+ throw UnsupportedError("keyLog not implemented");
void close({bool force: false}) {}
}
diff --git a/tests/standalone_2/io/secure_key_log_test.dart b/tests/standalone_2/io/secure_key_log_test.dart
new file mode 100644
index 0000000..d596122
--- /dev/null
+++ b/tests/standalone_2/io/secure_key_log_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+//
+// @dart = 2.9
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+ ..useCertificateChain(localFile('certificates/server_chain.pem'))
+ ..usePrivateKey(localFile('certificates/server_key.pem'),
+ password: 'dartdart');
+
+Future<SecureServerSocket> startEchoServer() {
+ return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
+ server.listen((SecureSocket client) {
+ client.fold<List<int>>(
+ <int>[], (message, data) => message..addAll(data)).then((message) {
+ client.add(message);
+ client.close();
+ });
+ });
+ return server;
+ });
+}
+
+testSuccess(SecureServerSocket server) async {
+ var log = "";
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ await SecureSocket.connect(HOST, server.port, context: clientContext,
+ keyLog: (line) {
+ log += line;
+ }).then((socket) {
+ socket.write("Hello server.");
+ socket.close();
+ return socket.drain().then((value) {
+ Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+ return server;
+ });
+ });
+}
+
+testExceptionInKeyLogFunction(SecureServerSocket server) async {
+ SecurityContext clientContext = new SecurityContext()
+ ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+ var numCalls = 0;
+ await SecureSocket.connect(HOST, server.port, context: clientContext,
+ keyLog: (line) {
+ ++numCalls;
+ throw FileSystemException("Something bad happened");
+ }).then((socket) {
+ socket.close();
+ return socket.drain().then((value) {
+ Expect.notEquals(0, numCalls);
+ return server;
+ });
+ });
+}
+
+void main() async {
+ asyncStart();
+ await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+ final server = await startEchoServer();
+
+ await testSuccess(server);
+ await testExceptionInKeyLogFunction(server);
+
+ await server.close();
+ asyncEnd();
+}
diff --git a/tools/VERSION b/tools/VERSION
index b6083b2..cff630a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 119
+PRERELEASE 120
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/templates/html/impl/impl_AudioContext.darttemplate b/tools/dom/templates/html/impl/impl_AudioContext.darttemplate
index d716afd..77f860d 100644
--- a/tools/dom/templates/html/impl/impl_AudioContext.darttemplate
+++ b/tools/dom/templates/html/impl/impl_AudioContext.darttemplate
@@ -41,28 +41,88 @@
}
}
- @JSName('decodeAudioData')
- Future$#NULLSAFECAST(<AudioBuffer>) _decodeAudioData(ByteBuffer audioData,
- [DecodeSuccessCallback$NULLABLE successCallback,
- DecodeErrorCallback$NULLABLE errorCallback]) native;
-
Future<AudioBuffer> decodeAudioData(ByteBuffer audioData,
[DecodeSuccessCallback$NULLABLE successCallback,
DecodeErrorCallback$NULLABLE errorCallback]) {
- if (successCallback != null && errorCallback != null) {
- return _decodeAudioData(audioData, successCallback, errorCallback);
+ // Both callbacks need to be provided if they're being used.
+ assert((successCallback == null) == (errorCallback == null));
+ // `decodeAudioData` can exist either in the older callback syntax or the
+ // newer `Promise`-based syntax that also accepts callbacks. In the former,
+ // we synthesize a `Future` to be consistent.
+ // For more details:
+ // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData
+ // https://www.w3.org/TR/webaudio/#dom-baseaudiocontext-decodeaudiodata
+ final completer = Completer<Object>();
+ var errorInCallbackIsNull = false;
+
+ void success(AudioBuffer decodedData) {
+ completer.complete(decodedData);
+ successCallback$NULLASSERT.call(decodedData);
}
- var completer = new Completer<AudioBuffer>();
- _decodeAudioData(audioData, (value) {
- completer.complete(value);
- }, (error) {
- if (error == null) {
- completer.completeError('');
+ final nullErrorString =
+ '[AudioContext.decodeAudioData] completed with a null error.';
+
+ void error(DomException$NULLABLE error) {
+ // Safari has a bug where it may return null for the error callback. In
+ // the case where the Safari version still returns a `Promise` and the
+ // error is not null after the `Promise` is finished, the error callback
+ // is called instead in the `Promise`'s `catch` block. Otherwise, and in
+ // the case where a `Promise` is not returned by the API at all, the
+ // callback never gets called (for backwards compatibility, it can not
+ // accept null). Instead, the `Future` completes with a custom string,
+ // indicating that null was given.
+ // https://github.com/mdn/webaudio-examples/issues/5
+ if (error != null) {
+ // Note that we `complete` and not `completeError`. This is to make sure
+ // that errors in the `Completer` are not thrown if the call gets back
+ // a `Promise`.
+ completer.complete(error);
+ errorCallback$NULLASSERT.call(error);
} else {
- completer.completeError(error);
+ completer.complete(nullErrorString);
+ errorInCallbackIsNull = true;
}
+ }
+
+ var decodeResult;
+ if (successCallback == null) {
+ decodeResult =
+ JS("creates:AudioBuffer;", "#.decodeAudioData(#)", this, audioData);
+ } else {
+ decodeResult = JS(
+ "creates:AudioBuffer;",
+ "#.decodeAudioData(#, #, #)",
+ this,
+ audioData,
+ convertDartClosureToJS(success, 1),
+ convertDartClosureToJS(error, 1));
+ }
+
+ if (decodeResult != null) {
+ // Promise-based syntax.
+ return promiseToFuture<AudioBuffer>(decodeResult).catchError((error) {
+ // If the error was null in the callback, but no longer is now that the
+ // `Promise` is finished, call the error callback. If it's still null,
+ // throw the error string. This is to handle the aforementioned bug in
+ // Safari.
+ if (errorInCallbackIsNull) {
+ if (error != null) {
+ errorCallback?.call(error);
+ } else {
+ throw nullErrorString;
+ }
+ }
+ throw error;
+ });
+ }
+
+ // Callback-based syntax. We use the above completer to synthesize a
+ // `Future` from the callback values. Since we don't use `completeError`
+ // above, `then` is used to simulate an error.
+ return completer.future.then((value) {
+ if (value is AudioBuffer) return value;
+ throw value;
});
- return completer.future;
}
}