[vm_service] Use empty lists instead of null for non-optional fields when parsing JSON
Change 64060a8ddf accidentally lost `?? []` for non-optional fields when parsing JSON. This change restores that (in `generate_dart_common.dart`), along with some minor tweaks to get the codegen to work on Windows.
For reasons I don't understand, the generated files were formatted differently to how the formatter formats them on my machine today, which unfortunately makes the diff larger than the intended change.
Change-Id: Ibadaa3ec4c6af4e636c1e5ffd2c7e792bc1e8a14
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497600
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
b715374b28
commit
4d07f9ace4
@@ -1,6 +1,9 @@
|
||||
## 15.2.0
|
||||
- Update to version `4.22` of the spec.
|
||||
- Deprecate `Message` type and `messages` field of `Stack` type.
|
||||
- Fix an issue introduced in 15.1.0 that could cause `parse()` methods (such as
|
||||
`VM.parse()`) to leave collections as `null` instead of empty lists when they
|
||||
were not present in the JSON.
|
||||
|
||||
## 15.1.0
|
||||
- Update to version `4.21` of the spec.
|
||||
|
||||
+2065
-2038
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
name: vm_service
|
||||
version: 15.2.0
|
||||
version: 15.2.0-wip
|
||||
description: >-
|
||||
A library to communicate with a service implementing the Dart VM
|
||||
service protocol.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
// Regression test for failure to roll into Flutter found in
|
||||
// https://github.com/flutter/flutter/pull/185274#discussion_r3116379311
|
||||
|
||||
void main() {
|
||||
test('VM.parse() never results in null lists', () {
|
||||
final vmService = VM.parse({})!;
|
||||
expect(vmService.systemIsolateGroups, allOf(isNotNull, isEmpty));
|
||||
expect(vmService.systemIsolates, allOf(isNotNull, isEmpty));
|
||||
expect(vmService.isolateGroups, allOf(isNotNull, isEmpty));
|
||||
expect(vmService.isolates, allOf(isNotNull, isEmpty));
|
||||
});
|
||||
}
|
||||
@@ -34,6 +34,8 @@ class Tokenizer {
|
||||
Token? _head;
|
||||
Token? _last;
|
||||
|
||||
final _newLineRegExp = RegExp('\r?\n');
|
||||
|
||||
Tokenizer(this.text);
|
||||
|
||||
Token? tokenize() {
|
||||
@@ -45,7 +47,7 @@ class Tokenizer {
|
||||
if (whitespace.contains(c)) {
|
||||
// skip
|
||||
} else if (c == '/' && _peek(i) == '/') {
|
||||
int index = text.indexOf('\n', i);
|
||||
int index = text.indexOf(_newLineRegExp, i);
|
||||
if (index == -1) index = text.length;
|
||||
_emit(text.substring(i, index));
|
||||
i = index;
|
||||
|
||||
@@ -754,7 +754,7 @@ class Type extends Member {
|
||||
} else {
|
||||
gen.writeln(
|
||||
'${field.generatableName} = _createServiceObjectListOrNull'
|
||||
'<${fieldType.listTypeArg}>($ref, $typesList)');
|
||||
'<${fieldType.listTypeArg}>($ref, $typesList) ?? []');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ Future<void> main(List<String> args) async {
|
||||
);
|
||||
final document = Document();
|
||||
final buf = StringBuffer(file.readAsStringSync());
|
||||
final nodes = document.parseLines(buf.toString().split('\n'));
|
||||
final nodes = document.parseLines(buf.toString().split(RegExp('\r?\n')));
|
||||
print('Parsed ${file.path}.');
|
||||
print('Service protocol version ${ApiParseUtil.parseVersionString(nodes)}.');
|
||||
|
||||
@@ -111,7 +111,8 @@ Future<String> _generateDartCommon({
|
||||
}
|
||||
|
||||
Future<void> _runDartFormat(String outDirPath) async {
|
||||
ProcessResult result = Process.runSync('dart', ['format', outDirPath]);
|
||||
ProcessResult result =
|
||||
Process.runSync(Platform.resolvedExecutable, ['format', outDirPath]);
|
||||
if (result.exitCode != 0) {
|
||||
print('dart format: ${result.stdout}\n${result.stderr}');
|
||||
throw result.exitCode;
|
||||
@@ -122,10 +123,13 @@ Future<void> _generateJava(String codeGeneratorDir, List<Node> nodes) async {
|
||||
var srcDirPath = normalize(join(codeGeneratorDir, '..', 'java', 'src'));
|
||||
var generator = java.JavaGenerator(srcDirPath);
|
||||
|
||||
// We might be on Windows, but we always write paths with forward slashes.
|
||||
final scriptPath = Platform.script.toFilePath();
|
||||
final kSdk = '/sdk/';
|
||||
final scriptLocation =
|
||||
scriptPath.substring(scriptPath.indexOf(kSdk) + kSdk.length);
|
||||
final kSdk = [Platform.pathSeparator, 'sdk', Platform.pathSeparator].join();
|
||||
final scriptLocation = scriptPath
|
||||
.substring(scriptPath.indexOf(kSdk) + kSdk.length)
|
||||
.replaceAll(Platform.pathSeparator, '/');
|
||||
|
||||
java.api = java.Api(scriptLocation);
|
||||
java.api.parse(nodes);
|
||||
java.api.generate(generator);
|
||||
@@ -135,6 +139,7 @@ Future<void> _generateJava(String codeGeneratorDir, List<Node> nodes) async {
|
||||
// directory are).
|
||||
List<String> generatedPaths = generator.allWrittenFiles
|
||||
.map((path) => relative(path, from: 'java'))
|
||||
.map((path) => path.replaceAll(Platform.pathSeparator, '/'))
|
||||
.toList();
|
||||
generatedPaths.sort();
|
||||
File gitignoreFile = File(join(codeGeneratorDir, '..', 'java', '.gitignore'));
|
||||
|
||||
@@ -28,11 +28,8 @@ abstract interface class VmServiceInterface {
|
||||
Stream<Event> onEvent(String streamId);
|
||||
|
||||
/// Handler for calling extra service extensions.
|
||||
Future<Response> callServiceExtension(
|
||||
String method, {
|
||||
String? isolateId,
|
||||
Map<String, dynamic>? args,
|
||||
});
|
||||
Future<Response> callServiceExtension(String method,
|
||||
{String? isolateId, Map<String, dynamic>? args});
|
||||
|
||||
/// Invoked by the Dart Development Service (DDS) immediately after it
|
||||
/// connects.
|
||||
@@ -363,11 +360,8 @@ abstract interface class VmServiceInterface {
|
||||
///
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<AllocationProfile> getAllocationProfile(
|
||||
String isolateId, {
|
||||
bool? reset,
|
||||
bool? gc,
|
||||
});
|
||||
Future<AllocationProfile> getAllocationProfile(String isolateId,
|
||||
{bool? reset, bool? gc});
|
||||
|
||||
/// The `getAllocationTraces` RPC allows for the retrieval of allocation
|
||||
/// traces for objects of a specific set of types (see
|
||||
@@ -427,10 +421,7 @@ abstract interface class VmServiceInterface {
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<CpuSamples> getCpuSamples(
|
||||
String isolateId,
|
||||
int timeOriginMicros,
|
||||
int timeExtentMicros,
|
||||
);
|
||||
String isolateId, int timeOriginMicros, int timeExtentMicros);
|
||||
|
||||
/// The `getFlagList` RPC returns a list of all command line flags in the VM
|
||||
/// along with their current values.
|
||||
@@ -706,11 +697,8 @@ abstract interface class VmServiceInterface {
|
||||
///
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<PerfettoCpuSamples> getPerfettoCpuSamples(
|
||||
String isolateId, {
|
||||
int? timeOriginMicros,
|
||||
int? timeExtentMicros,
|
||||
});
|
||||
Future<PerfettoCpuSamples> getPerfettoCpuSamples(String isolateId,
|
||||
{int? timeOriginMicros, int? timeExtentMicros});
|
||||
|
||||
/// The `getPerfettoVMTimeline` RPC is used to retrieve an object which
|
||||
/// contains a VM timeline trace represented in Perfetto's proto format. See
|
||||
@@ -743,10 +731,8 @@ abstract interface class VmServiceInterface {
|
||||
/// or Perfettofile, an [RPCError] with error code `114`, `invalid timeline
|
||||
/// request`, will be returned as timeline events are written directly to a
|
||||
/// file, and thus cannot be retrieved through the VM Service, in these modes.
|
||||
Future<PerfettoTimeline> getPerfettoVMTimeline({
|
||||
int? timeOriginMicros,
|
||||
int? timeExtentMicros,
|
||||
});
|
||||
Future<PerfettoTimeline> getPerfettoVMTimeline(
|
||||
{int? timeOriginMicros, int? timeExtentMicros});
|
||||
|
||||
/// The `getPorts` RPC is used to retrieve the list of `ReceivePort` instances
|
||||
/// for a given isolate.
|
||||
@@ -966,10 +952,8 @@ abstract interface class VmServiceInterface {
|
||||
/// Perfettofile, an [RPCError] with error code `114`, `invalid timeline
|
||||
/// request`, will be returned as timeline events are written directly to a
|
||||
/// file, and thus cannot be retrieved through the VM Service, in these modes.
|
||||
Future<Timeline> getVMTimeline({
|
||||
int? timeOriginMicros,
|
||||
int? timeExtentMicros,
|
||||
});
|
||||
Future<Timeline> getVMTimeline(
|
||||
{int? timeOriginMicros, int? timeExtentMicros});
|
||||
|
||||
/// The `getVMTimelineFlags` RPC returns information about the current VM
|
||||
/// timeline configuration.
|
||||
@@ -1032,11 +1016,8 @@ abstract interface class VmServiceInterface {
|
||||
/// of relative paths, but this is not guaranteed.
|
||||
///
|
||||
/// See [UriList].
|
||||
Future<UriList> lookupResolvedPackageUris(
|
||||
String isolateId,
|
||||
List<String> uris, {
|
||||
bool? local,
|
||||
});
|
||||
Future<UriList> lookupResolvedPackageUris(String isolateId, List<String> uris,
|
||||
{bool? local});
|
||||
|
||||
/// The `lookupPackageUris` RPC is used to convert a list of URIs to their
|
||||
/// unresolved paths. For example, URIs passed to this RPC are mapped in the
|
||||
@@ -1149,11 +1130,8 @@ abstract interface class VmServiceInterface {
|
||||
///
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<Success> resume(
|
||||
String isolateId, {
|
||||
/*StepOption*/ String? step,
|
||||
int? frameIndex,
|
||||
});
|
||||
Future<Success> resume(String isolateId,
|
||||
{/*StepOption*/ String? step, int? frameIndex});
|
||||
|
||||
/// The `setBreakpointState` RPC allows for breakpoints to be enabled or
|
||||
/// disabled, without requiring for the breakpoint to be completely removed.
|
||||
@@ -1165,10 +1143,7 @@ abstract interface class VmServiceInterface {
|
||||
///
|
||||
/// See [Breakpoint].
|
||||
Future<Breakpoint> setBreakpointState(
|
||||
String isolateId,
|
||||
String breakpointId,
|
||||
bool enable,
|
||||
);
|
||||
String isolateId, String breakpointId, bool enable);
|
||||
|
||||
/// The `setExceptionPauseMode` RPC is used to control if an isolate pauses
|
||||
/// when an exception is thrown.
|
||||
@@ -1186,9 +1161,7 @@ abstract interface class VmServiceInterface {
|
||||
/// returned.
|
||||
@Deprecated('Use setIsolatePauseMode instead')
|
||||
Future<Success> setExceptionPauseMode(
|
||||
String isolateId,
|
||||
/*ExceptionPauseMode*/ String mode,
|
||||
);
|
||||
String isolateId, /*ExceptionPauseMode*/ String mode);
|
||||
|
||||
/// The `setIsolatePauseMode` RPC is used to control if or when an isolate
|
||||
/// will pause due to a change in execution state.
|
||||
@@ -1207,11 +1180,9 @@ abstract interface class VmServiceInterface {
|
||||
///
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<Success> setIsolatePauseMode(
|
||||
String isolateId, {
|
||||
/*ExceptionPauseMode*/ String? exceptionPauseMode,
|
||||
bool? shouldPauseOnExit,
|
||||
});
|
||||
Future<Success> setIsolatePauseMode(String isolateId,
|
||||
{/*ExceptionPauseMode*/ String? exceptionPauseMode,
|
||||
bool? shouldPauseOnExit});
|
||||
|
||||
/// The `setFlag` RPC is used to set a VM flag at runtime. Returns an error if
|
||||
/// the named flag does not exist, the flag may not be set at runtime, or the
|
||||
@@ -1251,10 +1222,7 @@ abstract interface class VmServiceInterface {
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<Success> setLibraryDebuggable(
|
||||
String isolateId,
|
||||
String libraryId,
|
||||
bool isDebuggable,
|
||||
);
|
||||
String isolateId, String libraryId, bool isDebuggable);
|
||||
|
||||
/// The `setName` RPC is used to change the debugging name for an isolate.
|
||||
///
|
||||
@@ -1282,10 +1250,7 @@ abstract interface class VmServiceInterface {
|
||||
/// This method will throw a [SentinelException] in the case a [Sentinel] is
|
||||
/// returned.
|
||||
Future<Success> setTraceClassAllocation(
|
||||
String isolateId,
|
||||
String classId,
|
||||
bool enable,
|
||||
);
|
||||
String isolateId, String classId, bool enable);
|
||||
|
||||
/// The `setVMName` RPC is used to change the debugging name for the vm.
|
||||
///
|
||||
@@ -1406,12 +1371,8 @@ class VmServerConnection {
|
||||
/// Pending service extension requests to this client by id.
|
||||
final _pendingServiceExtensionRequests = <dynamic, _PendingServiceRequest>{};
|
||||
|
||||
VmServerConnection(
|
||||
this._requestStream,
|
||||
this._responseSink,
|
||||
this._serviceExtensionRegistry,
|
||||
this._serviceImplementation,
|
||||
) {
|
||||
VmServerConnection(this._requestStream, this._responseSink,
|
||||
this._serviceExtensionRegistry, this._serviceImplementation) {
|
||||
_requestStream.listen(_delegateRequest, onDone: _doneCompleter.complete);
|
||||
done.then((_) {
|
||||
for (var sub in _streamSubscriptions.values) {
|
||||
@@ -1426,8 +1387,7 @@ class VmServerConnection {
|
||||
/// We don't attempt to do any serialization or deserialization of the
|
||||
/// request or response in this case
|
||||
Future<Map<String, Object?>> _forwardServiceExtensionRequest(
|
||||
Map<String, Object?> request,
|
||||
) {
|
||||
Map<String, Object?> request) {
|
||||
final originalId = request['id'];
|
||||
request = Map<String, Object?>.of(request);
|
||||
// Modify the request ID to ensure we don't have conflicts between
|
||||
@@ -1451,12 +1411,8 @@ class VmServerConnection {
|
||||
}
|
||||
final method = request['method'] as String?;
|
||||
if (method == null) {
|
||||
throw RPCError(
|
||||
null,
|
||||
RPCErrorKind.kInvalidRequest.code,
|
||||
'Invalid Request',
|
||||
request,
|
||||
);
|
||||
throw RPCError(null, RPCErrorKind.kInvalidRequest.code,
|
||||
'Invalid Request', request);
|
||||
}
|
||||
final params = request['params'] as Map<String, dynamic>?;
|
||||
late Response response;
|
||||
@@ -1718,10 +1674,14 @@ class VmServerConnection {
|
||||
response = await _serviceImplementation.getVMTimelineMicros();
|
||||
break;
|
||||
case 'pause':
|
||||
response = await _serviceImplementation.pause(params!['isolateId']);
|
||||
response = await _serviceImplementation.pause(
|
||||
params!['isolateId'],
|
||||
);
|
||||
break;
|
||||
case 'kill':
|
||||
response = await _serviceImplementation.kill(params!['isolateId']);
|
||||
response = await _serviceImplementation.kill(
|
||||
params!['isolateId'],
|
||||
);
|
||||
break;
|
||||
case 'lookupResolvedPackageUris':
|
||||
response = await _serviceImplementation.lookupResolvedPackageUris(
|
||||
@@ -1811,7 +1771,9 @@ class VmServerConnection {
|
||||
);
|
||||
break;
|
||||
case 'setVMName':
|
||||
response = await _serviceImplementation.setVMName(params!['name']);
|
||||
response = await _serviceImplementation.setVMName(
|
||||
params!['name'],
|
||||
);
|
||||
break;
|
||||
case 'setVMTimelineFlags':
|
||||
response = await _serviceImplementation.setVMTimelineFlags(
|
||||
@@ -1856,7 +1818,10 @@ class VmServerConnection {
|
||||
_responseSink.add({
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'streamNotify',
|
||||
'params': {'streamId': id, 'event': e.toJson()},
|
||||
'params': {
|
||||
'streamId': id,
|
||||
'event': e.toJson(),
|
||||
},
|
||||
});
|
||||
});
|
||||
response = Success();
|
||||
@@ -1870,31 +1835,22 @@ class VmServerConnection {
|
||||
if (registeredClient != null) {
|
||||
// Check for any client which has registered this extension, if we
|
||||
// have one then delegate the request to that client.
|
||||
_responseSink.add(
|
||||
await registeredClient._forwardServiceExtensionRequest(request),
|
||||
);
|
||||
_responseSink.add(await registeredClient
|
||||
._forwardServiceExtensionRequest(request));
|
||||
// Bail out early in this case, we are just acting as a proxy and
|
||||
// never get a `Response` instance.
|
||||
return;
|
||||
} else if (method.startsWith('ext.')) {
|
||||
// Remaining methods with `ext.` are assumed to be registered via
|
||||
// dart:developer, which the service implementation handles.
|
||||
final args = params == null
|
||||
? null
|
||||
: Map<String, dynamic>.of(params);
|
||||
final args =
|
||||
params == null ? null : Map<String, dynamic>.of(params);
|
||||
final isolateId = args?.remove('isolateId');
|
||||
response = await _serviceImplementation.callServiceExtension(
|
||||
method,
|
||||
isolateId: isolateId,
|
||||
args: args,
|
||||
);
|
||||
response = await _serviceImplementation.callServiceExtension(method,
|
||||
isolateId: isolateId, args: args);
|
||||
} else {
|
||||
throw RPCError(
|
||||
method,
|
||||
RPCErrorKind.kMethodNotFound.code,
|
||||
'Method not found',
|
||||
request,
|
||||
);
|
||||
throw RPCError(method, RPCErrorKind.kMethodNotFound.code,
|
||||
'Method not found', request);
|
||||
}
|
||||
}
|
||||
_responseSink.add({
|
||||
|
||||
Reference in New Issue
Block a user