Version 2.18.0-66.0.dev
Merge commit 'f8d011ebb4adafe7f12a50ccf7b8282a14938568' into 'dev'
diff --git a/pkg/analysis_server/test/lsp/initialization_test.dart b/pkg/analysis_server/test/lsp/initialization_test.dart
index 68fc852..94d60fa 100644
--- a/pkg/analysis_server/test/lsp/initialization_test.dart
+++ b/pkg/analysis_server/test/lsp/initialization_test.dart
@@ -25,6 +25,16 @@
@reflectiveTest
class InitializationTest extends AbstractLspAnalysisServerTest {
+ /// Waits for any in-progress analysis context rebuild.
+ ///
+ /// Pumps the event queue before and after, to ensure any server code that
+ /// runs after the rebuild has had chance to run.
+ Future<void> get contextRebuildComplete async {
+ await pumpEventQueue(times: 5000);
+ await server.analysisContextsRebuilt;
+ await pumpEventQueue(times: 5000);
+ }
+
TextDocumentRegistrationOptions registrationOptionsFor(
List<Registration> registrations,
Method method,
@@ -561,7 +571,7 @@
// Opening both files should only add the project folder once.
await openFile(file1Uri, '');
await openFile(file2Uri, '');
- await pumpEventQueue(times: 5000);
+ await contextRebuildComplete;
expect(server.contextManager.includedPaths, equals([projectFolderPath]));
// Closing only one of the files should not remove the project folder
@@ -576,7 +586,7 @@
// the context.
resetContextBuildCounter();
await closeFile(file2Uri);
- await pumpEventQueue(times: 5000);
+ await contextRebuildComplete;
expect(server.contextManager.includedPaths, equals([]));
expect(server.contextManager.driverMap, hasLength(0));
expectContextBuilds();
@@ -611,7 +621,7 @@
// Opening a file nested within the project should add the project folder.
await openFile(nestedFileUri, '');
- await pumpEventQueue(times: 500);
+ await contextRebuildComplete;
expect(server.contextManager.includedPaths, equals([projectFolderPath]));
// Ensure the file was cached in each driver. This happens as a result of
@@ -851,7 +861,7 @@
// Opening both files should only add the project folder once.
await openFile(file1Uri, '');
await openFile(file2Uri, '');
- await pumpEventQueue(times: 5000);
+ await contextRebuildComplete;
expect(server.contextManager.includedPaths, equals([projectFolderPath]));
expect(server.contextManager.driverMap, hasLength(1));
@@ -867,7 +877,7 @@
// the context.
resetContextBuildCounter();
await closeFile(file2Uri);
- await pumpEventQueue(times: 5000);
+ await contextRebuildComplete;
expect(server.contextManager.includedPaths, equals([]));
expect(server.contextManager.driverMap, hasLength(0));
expectContextBuilds();
@@ -909,6 +919,7 @@
// Opening a file nested within the project should cause the project folder
// to be added
await openFile(nestedFileUri, '');
+ await contextRebuildComplete;
expect(server.contextManager.includedPaths, equals([projectFolderPath]));
// Ensure the file was cached in each driver. This happens as a result of
diff --git a/pkg/dartdev/lib/src/commands/debug_adapter.dart b/pkg/dartdev/lib/src/commands/debug_adapter.dart
index 817f91b..e3fcf99 100644
--- a/pkg/dartdev/lib/src/commands/debug_adapter.dart
+++ b/pkg/dartdev/lib/src/commands/debug_adapter.dart
@@ -63,6 +63,14 @@
enableDds: args[argDds],
enableAuthCodes: args[argAuthCodes],
test: args[argTest],
+ // Protocol errors should be written to stderr to help debug (or in the
+ // case of a user running this command to explain it's for tools).
+ onError: (e) => stderr.writeln(
+ 'Input could not be parsed as a Debug Adapter Protocol message.\n'
+ 'The "dart debug_adapter" command is intended for use by tooling that '
+ 'communicates using the Debug Adapter Protocol.\n\n'
+ '$e',
+ ),
);
await server.channel.closed;
diff --git a/pkg/dartdev/test/commands/debug_adapter_test.dart b/pkg/dartdev/test/commands/debug_adapter_test.dart
index 00a7e8c..f6d33ff2 100644
--- a/pkg/dartdev/test/commands/debug_adapter_test.dart
+++ b/pkg/dartdev/test/commands/debug_adapter_test.dart
@@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'dart:convert';
+
import 'package:test/test.dart';
import '../utils.dart';
@@ -26,4 +28,25 @@
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
});
+
+ test('invalid input provides a suitable message', () async {
+ final p = project();
+ final process = await p.start(['debug_adapter']);
+
+ // Capture stderr
+ final errorOutput = StringBuffer();
+ process.stderr.transform(utf8.decoder).listen(errorOutput.write);
+
+ // Write invalid headers and await quit.
+ process.stdin.write('foo\r\nbar\r\n\r\n');
+ await process.exitCode;
+
+ expect(
+ errorOutput.toString(),
+ allOf(
+ contains('Input could not be parsed'),
+ contains('is intended for use by tooling'),
+ contains('foo\r\nbar'),
+ ));
+ });
}
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index d095e3b..209649a 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -419,7 +419,8 @@
this.enableDds = true,
this.enableAuthCodes = true,
this.logger,
- }) : super(channel) {
+ Function? onError,
+ }) : super(channel, onError: onError) {
channel.closed.then((_) => shutdown());
_isolateManager = IsolateManager(this);
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index eecc025..b10813e 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -34,12 +34,14 @@
bool enableDds = true,
bool enableAuthCodes = true,
Logger? logger,
+ Function? onError,
}) : super(
channel,
ipv6: ipv6,
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger,
+ onError: onError,
);
/// Whether the VM Service closing should be used as a signal to terminate the
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 468ea09..e80f061 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -33,12 +33,14 @@
bool enableDds = true,
bool enableAuthCodes = true,
Logger? logger,
+ Function? onError,
}) : super(
channel,
ipv6: ipv6,
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger,
+ onError: onError,
);
/// Whether the VM Service closing should be used as a signal to terminate the
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 215a975..92fcf2e 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -35,8 +35,8 @@
/// such as `runInTerminal`.
final _serverToClientRequestCompleters = <int, Completer<Object?>>{};
- BaseDebugAdapter(this._channel) {
- _channel.listen(_handleIncomingMessage);
+ BaseDebugAdapter(this._channel, {Function? onError}) {
+ _channel.listen(_handleIncomingMessage, onError: onError);
}
/// Parses arguments for [attachRequest] into a type of [TAttachArgs].
diff --git a/pkg/dds/lib/src/dap/protocol_stream_transformers.dart b/pkg/dds/lib/src/dap/protocol_stream_transformers.dart
index f166e55..7d70edb 100644
--- a/pkg/dds/lib/src/dap/protocol_stream_transformers.dart
+++ b/pkg/dds/lib/src/dap/protocol_stream_transformers.dart
@@ -5,13 +5,16 @@
import 'dart:async';
import 'dart:convert';
-class InvalidEncodingError {
- final String headers;
- InvalidEncodingError(this.headers);
+import 'exceptions.dart';
- @override
- String toString() =>
- 'Encoding in supplied headers is not supported.\n\nHeaders:\n$headers';
+class InvalidEncodingException extends InvalidHeadersException {
+ InvalidEncodingException(String headers)
+ : super('Encoding in supplied headers is not supported.', headers);
+}
+
+class InvalidHeadersException extends DebugAdapterException {
+ InvalidHeadersException(String message, String headers)
+ : super('$message\n\nHeaders:\n$headers');
}
/// Transforms a stream of LSP/DAP data in the form:
@@ -36,23 +39,30 @@
input = stream.expand((b) => b).listen(
(codeUnit) {
buffer.add(codeUnit);
- if (isParsingHeaders && _endsWithCrLfCrLf(buffer)) {
- headers = _parseHeaders(buffer);
- buffer.clear();
- isParsingHeaders = false;
- } else if (!isParsingHeaders &&
- buffer.length >= headers!.contentLength) {
- // UTF-8 is the default - and only supported - encoding for LSP.
- // The string 'utf8' is valid since it was published in the original spec.
- // Any other encodings should be rejected with an error.
- if ([null, 'utf-8', 'utf8']
- .contains(headers?.encoding?.toLowerCase())) {
- _output.add(utf8.decode(buffer));
- } else {
- _output.addError(InvalidEncodingError(headers!.rawHeaders));
+ try {
+ if (isParsingHeaders && _endsWithCrLfCrLf(buffer)) {
+ headers = _parseHeaders(buffer);
+ buffer.clear();
+ isParsingHeaders = false;
+ } else if (!isParsingHeaders &&
+ buffer.length >= headers!.contentLength) {
+ // UTF-8 is the default - and only supported - encoding for LSP.
+ // The string 'utf8' is valid since it was published in the original spec.
+ // Any other encodings should be rejected with an error.
+ if ([null, 'utf-8', 'utf8']
+ .contains(headers?.encoding?.toLowerCase())) {
+ _output.add(utf8.decode(buffer));
+ } else {
+ _output.addError(
+ InvalidEncodingException(headers!.rawHeaders),
+ );
+ }
+ buffer.clear();
+ isParsingHeaders = true;
}
- buffer.clear();
- isParsingHeaders = true;
+ } on DebugAdapterException catch (e) {
+ _output.addError(e);
+ _output.close();
}
},
onError: _output.addError,
@@ -96,8 +106,10 @@
'The stream has utf8 content:\n${utf8.decode(buffer)}');
}
final headers = asString.split('\r\n');
- final lengthHeader =
- headers.firstWhere((h) => h.startsWith('Content-Length'));
+ final lengthHeader = headers.firstWhere(
+ (h) => h.startsWith('Content-Length'),
+ orElse: () => throw InvalidHeadersException(
+ 'No Content-Length header was supplied', asString));
final length = lengthHeader.split(':').last.trim();
final contentTypeHeader = headers
.firstWhere((h) => h.startsWith('Content-Type'), orElse: () => '');
diff --git a/pkg/dds/lib/src/dap/server.dart b/pkg/dds/lib/src/dap/server.dart
index 6de1eaa..9b208a6 100644
--- a/pkg/dds/lib/src/dap/server.dart
+++ b/pkg/dds/lib/src/dap/server.dart
@@ -33,6 +33,7 @@
this.enableAuthCodes = true,
this.test = false,
this.logger,
+ Function? onError,
}) : channel = ByteStreamServerChannel(_input, _output, logger) {
adapter = test
? DartTestDebugAdapter(
@@ -41,6 +42,7 @@
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger,
+ onError: onError,
)
: DartCliDebugAdapter(
channel,
@@ -48,6 +50,7 @@
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger,
+ onError: onError,
);
}
diff --git a/pkg/dds/test/dap/integration/protocol_test.dart b/pkg/dds/test/dap/integration/protocol_test.dart
new file mode 100644
index 0000000..06f5fd9
--- /dev/null
+++ b/pkg/dds/test/dap/integration/protocol_test.dart
@@ -0,0 +1,29 @@
+// 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:convert';
+
+import 'package:test/test.dart';
+
+import 'test_support.dart';
+
+main() {
+ group('dap protocol', () {
+ test('prints a suitable error if the server receives malformed input',
+ () async {
+ final errorOutput = Completer<String>();
+ final server = await DapTestSession.startServer(
+ onError: (e) => errorOutput.complete('$e'),
+ );
+ addTearDown(() => server.stop());
+ server.sink.add(utf8.encode('not\r\n\r\nvalid'));
+ expect(
+ await errorOutput.future,
+ contains('No Content-Length header was supplied'),
+ );
+ });
+ // These tests can be slow due to starting up the external server process.
+ }, timeout: Timeout.none);
+}
diff --git a/pkg/dds/test/dap/integration/test_server.dart b/pkg/dds/test/dap/integration/test_server.dart
index 519f5e1..2da9a3a 100644
--- a/pkg/dds/test/dap/integration/test_server.dart
+++ b/pkg/dds/test/dap/integration/test_server.dart
@@ -33,7 +33,10 @@
StreamSink<List<int>> get sink => stdinController.sink;
Stream<List<int>> get stream => stdoutController.stream;
- InProcessDapTestServer._(List<String> args) {
+ InProcessDapTestServer._(
+ List<String> args, {
+ Function? onError,
+ }) {
_server = DapServer(
stdinController.stream,
stdoutController.sink,
@@ -42,6 +45,7 @@
ipv6: args.contains('--ipv6'),
enableAuthCodes: !args.contains('--no-auth-codes'),
test: args.contains('--test'),
+ onError: onError,
);
}
@@ -52,11 +56,15 @@
static Future<InProcessDapTestServer> create({
Logger? logger,
+ Function? onError,
List<String>? additionalArgs,
}) async {
- return InProcessDapTestServer._([
- ...?additionalArgs,
- ]);
+ return InProcessDapTestServer._(
+ [
+ ...?additionalArgs,
+ ],
+ onError: onError,
+ );
}
}
@@ -74,12 +82,19 @@
OutOfProcessDapTestServer._(
this._process,
- Logger? logger,
- ) {
- // Treat anything written to stderr as the DAP crashing and fail the test.
+ Logger? logger, {
+ Function? onError,
+ }) {
+ // Handle any stderr from the process. If an error handler was provided by
+ // the test, call it. Otherwise throw to fail the test as it's likely
+ // unexpected.
_process.stderr.transform(utf8.decoder).listen((error) {
logger?.call(error);
- throw error;
+ if (onError != null) {
+ onError(error);
+ } else {
+ throw error;
+ }
});
unawaited(_process.exitCode.then((code) {
final message = 'Out-of-process DAP server terminated with code $code';
@@ -99,6 +114,7 @@
static Future<OutOfProcessDapTestServer> create({
Logger? logger,
+ Function? onError,
List<String>? additionalArgs,
}) async {
final ddsEntryScript =
@@ -115,8 +131,8 @@
...?additionalArgs,
];
- final _process = await Process.start(Platform.resolvedExecutable, args);
+ final process = await Process.start(Platform.resolvedExecutable, args);
- return OutOfProcessDapTestServer._(_process, logger);
+ return OutOfProcessDapTestServer._(process, logger, onError: onError);
}
}
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index 1c0dc21..1b1dc25 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -269,7 +269,7 @@
}
static Future<DapTestSession> setUp({List<String>? additionalArgs}) async {
- final server = await _startServer(additionalArgs: additionalArgs);
+ final server = await startServer(additionalArgs: additionalArgs);
final client = await DapTestClient.connect(
server,
captureVmServiceTraffic: verboseLogging,
@@ -279,17 +279,20 @@
}
/// Starts a DAP server that can be shared across tests.
- static Future<DapTestServer> _startServer({
+ static Future<DapTestServer> startServer({
Logger? logger,
+ Function? onError,
List<String>? additionalArgs,
}) async {
return useInProcessDap
? await InProcessDapTestServer.create(
logger: logger,
+ onError: onError,
additionalArgs: additionalArgs,
)
: await OutOfProcessDapTestServer.create(
logger: logger,
+ onError: onError,
additionalArgs: additionalArgs,
);
}
diff --git a/runtime/vm/dwarf.cc b/runtime/vm/dwarf.cc
index d661bd7..1296ad0 100644
--- a/runtime/vm/dwarf.cc
+++ b/runtime/vm/dwarf.cc
@@ -815,6 +815,9 @@
static constexpr intptr_t kResolvedFileRootLen = sizeof(kResolvedFileRoot) - 1;
static constexpr char kResolvedSdkRoot[] = "org-dartlang-sdk:///sdk/";
static constexpr intptr_t kResolvedSdkRootLen = sizeof(kResolvedSdkRoot) - 1;
+static constexpr char kResolvedGoogle3Root[] = "google3:///";
+static constexpr intptr_t kResolvedGoogle3RootLen =
+ sizeof(kResolvedGoogle3Root) - 1;
static const char* ConvertResolvedURI(const char* str) {
const intptr_t len = strlen(str);
@@ -831,6 +834,10 @@
// Leave "sdk/" as a prefix in the returned path.
return str + (kResolvedSdkRootLen - 4);
}
+ if (len > kResolvedGoogle3RootLen &&
+ strncmp(str, kResolvedGoogle3Root, kResolvedGoogle3RootLen) == 0) {
+ return str + kResolvedGoogle3RootLen; // Strip off the entire prefix.
+ }
return nullptr;
}
diff --git a/tools/VERSION b/tools/VERSION
index 09f482f..8165b14 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 18
PATCH 0
-PRERELEASE 65
+PRERELEASE 66
PRERELEASE_PATCH 0
\ No newline at end of file