From 0e9d801353b699db721cfd5fe611f39eba75ff91 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 16 Mar 2026 11:14:58 -0700 Subject: [PATCH] [ Service ] Fix dart:developer service extensions, add getSupportedProtocols Brings package:vm_service test suite pass rate up to ~93% Change-Id: I108a24e1acf37eb69c9de25a2f7334eb4c56dbe6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487600 Reviewed-by: Nicholas Shahan Commit-Queue: Ben Konyi --- .../lib/dart_runtime_service.dart | 5 ++ pkg/dart_runtime_service/lib/src/clients.dart | 9 ++-- .../lib/src/dart_runtime_service_backend.dart | 16 ++++--- .../lib/src/dart_runtime_service_rpcs.dart | 28 +++++++++-- .../test/utils/mocks.dart | 9 ++-- .../lib/dart_runtime_service_vm.dart | 28 ++++++----- .../lib/src/dart_runtime_service_vm_rpcs.dart | 46 +++++++++++++++++++ .../lib/src/native_bindings.dart | 10 +++- 8 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart diff --git a/pkg/dart_runtime_service/lib/dart_runtime_service.dart b/pkg/dart_runtime_service/lib/dart_runtime_service.dart index 4f2dd0c9471..03064b127d2 100644 --- a/pkg/dart_runtime_service/lib/dart_runtime_service.dart +++ b/pkg/dart_runtime_service/lib/dart_runtime_service.dart @@ -6,6 +6,11 @@ export 'src/clients.dart'; export 'src/dart_runtime_service.dart'; export 'src/dart_runtime_service_backend.dart'; export 'src/dart_runtime_service_options.dart'; +export 'src/dart_runtime_service_rpcs.dart' + show + RpcHandlerWithNoParameters, + RpcHandlerWithParameters, + ServiceRpcHandler; export 'src/event_streams.dart'; export 'src/exceptions.dart'; export 'src/expression_evaluator.dart'; diff --git a/pkg/dart_runtime_service/lib/src/clients.dart b/pkg/dart_runtime_service/lib/src/clients.dart index f7561c9fe67..fc1768adf5f 100644 --- a/pkg/dart_runtime_service/lib/src/clients.dart +++ b/pkg/dart_runtime_service/lib/src/clients.dart @@ -76,10 +76,11 @@ base class Client { @mustCallSuper void registerRpcHandlers() { - _internalRpcs.registerRpcsWithPeer(_clientPeer); - backend.registerRpcs(_clientPeer); - _internalRpcs.registerServiceExtensionForwarder(_clientPeer); - backend.registerFallbacks(_clientPeer); + _internalRpcs + ..addBackendRpcs(backend: backend) + ..registerRpcsWithPeer(_clientPeer) + ..registerServiceExtensionForwarder(_clientPeer) + ..registerBackendFallbacks(_clientPeer); } /// Attempts to register a [service] to be provided by this client. diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart index 519220a9ebb..ec04c62d40a 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart @@ -2,10 +2,12 @@ // 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:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'dart:collection'; + import 'package:meta/meta.dart'; import 'dart_runtime_service.dart'; +import 'dart_runtime_service_rpcs.dart'; import 'event_streams.dart'; import 'expression_evaluator.dart'; import 'isolate_manager.dart'; @@ -72,14 +74,14 @@ abstract class DartRuntimeServiceBackend { /// more clients listening to [streamId]. void onStreamCancel({required String streamId}) {} - /// Invoked by the [DartRuntimeService] to register handlers for the RPCs - /// provided by the backend. - void registerRpcs(json_rpc.Peer clientPeer); + /// RPCs to be registered with the [DartRuntimeService]. + UnmodifiableListView get rpcs => + UnmodifiableListView(const []); - /// Invoked by the [DartRuntimeService] to register fallback handlers - /// provided by the backend. + /// Fallbacks to be registered with the [DartRuntimeService]. /// /// Backend fallbacks are executed after incoming RPC requests fail to match /// any registered RPCs or service extensions provided by other clients. - void registerFallbacks(json_rpc.Peer clientPeer); + UnmodifiableListView get fallbacks => + UnmodifiableListView(const []); } diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart index 547c27011a1..c357a60cdfb 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart @@ -11,6 +11,7 @@ import 'package:vm_service/vm_service.dart'; import 'clients.dart'; import 'dart_runtime_service.dart'; +import 'dart_runtime_service_backend.dart'; import 'event_streams.dart'; import 'expression_evaluator.dart'; import 'rpc_exceptions.dart'; @@ -20,6 +21,8 @@ typedef RpcHandlerWithNoParameters = FutureOr Function(); typedef RpcHandlerWithParameters = FutureOr Function(json_rpc.Parameters); +typedef ServiceRpcHandler = (String, Function?); + /// Manages requests made to platform-agnostic RPCs provided by /// [DartRuntimeService] by a single [Client]. final class DartRuntimeServiceRpcs { @@ -49,7 +52,7 @@ final class DartRuntimeServiceRpcs { // Parameters for streamListen static const _kStreamId = 'streamId'; - late final _commonRpcs = <(String, Function?)>[ + late final _commonRpcs = [ ('getClientName', getClientName), ('registerService', registerService), ('setClientName', setClientName), @@ -59,9 +62,22 @@ final class DartRuntimeServiceRpcs { ('evaluateInFrame', expressionEvaluator?.evaluateInFrame), ]; - /// Registers the set of platform-agnostic RPCs for use by [client]. + final _backendRpcs = []; + final _backendFallbacks = []; + + void addBackendRpcs({required DartRuntimeServiceBackend backend}) { + _backendRpcs.addAll(backend.rpcs); + _backendFallbacks.addAll(backend.fallbacks); + } + + void addBackendFallbacks({ + required List fallbacks, + }) => _backendFallbacks.addAll(fallbacks); + + /// Registers the set of platform-agnostic and backend RPCs for use by + /// [client]. void registerRpcsWithPeer(json_rpc.Peer clientPeer) { - for (final (method, callback) in _commonRpcs) { + for (final (method, callback) in [..._commonRpcs, ..._backendRpcs]) { if (callback == null) continue; if (callback is! RpcHandlerWithNoParameters && callback is! RpcHandlerWithParameters) { @@ -92,6 +108,12 @@ final class DartRuntimeServiceRpcs { clientPeer.registerFallback(serviceExtensionForwarderFallback); } + void registerBackendFallbacks(json_rpc.Peer clientPeer) { + for (final fallback in _backendFallbacks) { + clientPeer.registerFallback(fallback); + } + } + /// Attempts to [parse] [parameters] into an instance of [T]. /// /// If [parameters] can't be parsed, a [json_rpc.RpcException] is thrown diff --git a/pkg/dart_runtime_service/test/utils/mocks.dart b/pkg/dart_runtime_service/test/utils/mocks.dart index 4c62762e208..e4613613064 100644 --- a/pkg/dart_runtime_service/test/utils/mocks.dart +++ b/pkg/dart_runtime_service/test/utils/mocks.dart @@ -2,8 +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. +import 'dart:collection'; + import 'package:dart_runtime_service/dart_runtime_service.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:test/fake.dart'; /// Fake implementation of [DartRuntimeServiceBackend] that throws when @@ -32,10 +33,12 @@ base class FakeDartRuntimeServiceBackend extends Fake }) async {} @override - void registerRpcs(json_rpc.Peer clientPeer) {} + UnmodifiableListView get rpcs => + UnmodifiableListView(const []); @override - void registerFallbacks(json_rpc.Peer clientPeer) {} + UnmodifiableListView get fallbacks => + UnmodifiableListView(const []); @override DartRuntimeService get frontend => throw UnimplementedError(); diff --git a/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart index 5f44d33558c..10eec9ecd59 100644 --- a/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart +++ b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; @@ -13,6 +14,7 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:logging/logging.dart'; import 'package:stream_channel/stream_channel.dart'; +import 'src/dart_runtime_service_vm_rpcs.dart'; import 'src/native_bindings.dart'; import 'src/vm_expression_evaluator.dart'; import 'src/vm_isolate_manager.dart'; @@ -73,6 +75,20 @@ class DartRuntimeServiceVMBackend @override late final VmExpressionEvaluator expressionEvaluator; + final _vmServiceRpcs = DartRuntimeServiceVmRpcs(); + + @override + UnmodifiableListView get rpcs => + UnmodifiableListView(_vmServiceRpcs.rpcs); + + @override + UnmodifiableListView + get fallbacks => UnmodifiableListView([ + // If the registered Dart RPC handlers can't handle a request, forward it + // it to the native VM service implementation for processing. + sendToRuntime, + ]); + @override Future initialize() async { _logger.info('Initializing...'); @@ -148,18 +164,6 @@ class DartRuntimeServiceVMBackend _nativeBindings.streamCancel(streamId: streamId); } - @override - void registerRpcs(json_rpc.Peer clientPeer) { - // The VM service handles its service requests in service.cc. - } - - @override - void registerFallbacks(json_rpc.Peer clientPeer) { - // If the registered Dart RPC handlers can't handle a request, forward it - // it to the native VM service implementation for processing. - clientPeer.registerFallback(sendToRuntime); - } - /// Sends service requests to the Dart VM runtime for processing. Future sendToRuntime(json_rpc.Parameters request) async { final method = request.method; diff --git a/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart b/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart new file mode 100644 index 00000000000..f5f9bc6854e --- /dev/null +++ b/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart @@ -0,0 +1,46 @@ +// Copyright (c) 2026, 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:collection'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:logging/logging.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'native_bindings.dart'; + +/// Implementations of RPCs specific to the VM service that are not handled +/// in runtime/vm/service.cc. +final class DartRuntimeServiceVmRpcs { + final _logger = Logger('$DartRuntimeServiceVmRpcs'); + final _nativeBindings = NativeBindings(); + + late final rpcs = UnmodifiableListView([ + ('getSupportedProtocols', getSupportedProtocols), + ]); + + /// Returns the list of protocols implemented by the service. + /// + /// VM service middleware like DDS should intercept this RPC and add their + /// own information to the response. + Future getSupportedProtocols() async { + final version = Version.parse( + await _nativeBindings.sendToVM(method: 'getVersion', params: const {}), + ); + if (version == null) { + _logger.warning('Unable to retrieve version for getSupportedProtocols.'); + RpcException.internalError.throwException(); + } + + return ProtocolList( + protocols: [ + Protocol( + protocolName: 'VM Service', + major: version.major, + minor: version.minor, + ), + ], + ).toJson(); + } +} diff --git a/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart b/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart index 4aee454ac1a..28f2ac46161 100644 --- a/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart +++ b/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart @@ -133,10 +133,16 @@ class NativeBindings { const kData = 'data'; final Object? converted; - if (value case [final Uint8List utf8String]) { + if (value case final String string) { + converted = json.decode(string); + } else if (value case [final Uint8List utf8String]) { converted = jsonUtf8Decoder.decode(utf8String); } else { - RpcException.internalError.throwException(); + RpcException.internalError.throwException( + data: { + 'details': 'Unknown response type (${value.runtimeType}: $value)', + }, + ); } if (converted case {kResult: final Map result}) { return result;