[io/stacktraces] Ensure websocket connect reports callers stacktrace.

Introduce dwarf-friendly socket connect stacktrace test.

TEST=socket_connect_dwarf_stacktrace_test

Fixes https://github.com/dart-lang/sdk/issues/46304

Change-Id: I428ac4b334d9d80ea06c64283ece3ff411abfa19
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/203160
Reviewed-by: Zach Anderson <zra@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev 2021-06-10 16:16:50 +00:00 committed by commit-bot@chromium.org
parent aea28aba05
commit 52aa8508f1
7 changed files with 132 additions and 6 deletions

View file

@ -1014,6 +1014,8 @@ class _WebSocketImpl extends Stream with _ServiceObject implements WebSocket {
}
String nonce = _CryptoUtils.bytesToBase64(nonceData);
final callerStackTrace = StackTrace.current;
uri = new Uri(
scheme: uri.scheme == "wss" ? "https" : "http",
userInfo: uri.userInfo,
@ -1050,12 +1052,13 @@ class _WebSocketImpl extends Stream with _ServiceObject implements WebSocket {
return request.close();
}).then((response) {
Never error(String message) {
Future<WebSocket> error(String message) {
// Flush data.
response.detachSocket().then((socket) {
socket.destroy();
});
throw new WebSocketException(message);
return Future<WebSocket>.error(
new WebSocketException(message), callerStackTrace);
}
var connectionHeader = response.headers[HttpHeaders.connectionHeader];
@ -1064,22 +1067,24 @@ class _WebSocketImpl extends Stream with _ServiceObject implements WebSocket {
!connectionHeader.any((value) => value.toLowerCase() == "upgrade") ||
response.headers.value(HttpHeaders.upgradeHeader)!.toLowerCase() !=
"websocket") {
error("Connection to '$uri' was not upgraded to websocket");
return error("Connection to '$uri' was not upgraded to websocket");
}
String? accept = response.headers.value("Sec-WebSocket-Accept");
if (accept == null) {
error("Response did not contain a 'Sec-WebSocket-Accept' header");
return error(
"Response did not contain a 'Sec-WebSocket-Accept' header");
}
_SHA1 sha1 = new _SHA1();
sha1.add("$nonce$_webSocketGUID".codeUnits);
List<int> expectedAccept = sha1.close();
List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept);
if (expectedAccept.length != receivedAccept.length) {
error("Response header 'Sec-WebSocket-Accept' is the wrong length");
return error(
"Response header 'Sec-WebSocket-Accept' is the wrong length");
}
for (int i = 0; i < expectedAccept.length; i++) {
if (expectedAccept[i] != receivedAccept[i]) {
error("Bad response 'Sec-WebSocket-Accept' header");
return error("Bad response 'Sec-WebSocket-Accept' header");
}
}
var protocol = response.headers.value('Sec-WebSocket-Protocol');

View file

@ -0,0 +1,50 @@
// 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=--dwarf-stack-traces --save-debugging-info=socket_connect_debug.so
//
// Tests stack trace on socket exceptions.
//
import "dart:async";
import "dart:convert";
import "dart:io";
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
import "package:native_stack_traces/native_stack_traces.dart";
Future<List<String>> findFrames(
Dwarf dwarf, RegExp re, StackTrace stackTrace) async {
final dwarfed = await Stream.value(stackTrace.toString())
.transform(const LineSplitter())
.toList();
return Stream.fromIterable(dwarfed)
.transform(DwarfStackTraceDecoder(dwarf))
.where(re.hasMatch)
.toList();
}
Future<void> main() async {
asyncStart();
final dwarf = Dwarf.fromFile('socket_connect_debug.so')!;
// Test stacktrace when lookup fails
try {
await WebSocket.connect('ws://localhost.tld:0/ws');
} catch (err, stackTrace) {
Expect.contains('Failed host lookup', err.toString());
final decoded = await findFrames(dwarf, RegExp("main"), stackTrace);
Expect.equals(1, decoded.length);
}
// Test stacktrace when connection fails
try {
await WebSocket.connect('ws://localhost:0/ws');
} catch (err, stackTrace) {
Expect.contains('was not upgraded to websocket', err.toString());
final decoded = await findFrames(dwarf, RegExp("main"), stackTrace);
Expect.equals(1, decoded.length);
}
asyncEnd();
}

View file

@ -19,6 +19,7 @@ Future<void> main() async {
try {
await WebSocket.connect('ws://localhost.tld:0/ws');
} catch (err, stackTrace) {
Expect.contains('Failed host lookup', err.toString());
Expect.contains("main ", stackTrace.toString());
}
@ -26,6 +27,7 @@ Future<void> main() async {
try {
await WebSocket.connect('ws://localhost:0/ws');
} catch (err, stackTrace) {
Expect.contains('was not upgraded to websocket', err.toString());
Expect.contains("main ", stackTrace.toString());
}
asyncEnd();

View file

@ -16,6 +16,12 @@ packages_file_test/none: Skip # contains no tests.
[ $builder_tag == asan ]
io/process_detached_test: Slow, Pass
[ $builder_tag == dwarf ]
io/socket_connect_stacktrace_test: SkipByDesign # Assumes stacktrace can be inspected directly, without decoding
[ $builder_tag != dwarf ]
io/socket_connect_dwarf_stacktrace_test: SkipByDesign # Is set up to decode dwarf stack traces
[ $builder_tag == no_ipv6 ]
io/http_ipv6_test: SkipByDesign
io/http_loopback_test: SkipByDesign

View file

@ -0,0 +1,55 @@
// 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=--dwarf-stack-traces --save-debugging-info=socket_connect_debug.so
//
// Tests stack trace on socket exceptions.
//
import "dart:async";
import "dart:convert";
import "dart:io";
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
import "package:native_stack_traces/native_stack_traces.dart";
Future<List<String>> findFrames(
Dwarf dwarf, RegExp re, StackTrace stackTrace) async {
final dwarfed = await Stream.value(stackTrace.toString())
.transform(const LineSplitter())
.toList();
return Stream.fromIterable(dwarfed)
.transform(DwarfStackTraceDecoder(dwarf))
.where(re.hasMatch)
.toList();
}
Future<void> main() async {
asyncStart();
final dwarfFromFile = Dwarf.fromFile('socket_connect_debug.so');
if (dwarfFromFile == null) {
Expect.fail('Debug binary is missing');
return;
}
Dwarf dwarf = dwarfFromFile;
// Test stacktrace when lookup fails
try {
await WebSocket.connect('ws://localhost.tld:0/ws');
} catch (err, stackTrace) {
Expect.contains('Failed host lookup', err.toString());
final decoded = await findFrames(dwarf, RegExp("main"), stackTrace);
Expect.equals(1, decoded.length);
}
// Test stacktrace when connection fails
try {
await WebSocket.connect('ws://localhost:0/ws');
} catch (err, stackTrace) {
Expect.contains('was not upgraded to websocket', err.toString());
final decoded = await findFrames(dwarf, RegExp("main"), stackTrace);
Expect.equals(1, decoded.length);
}
asyncEnd();
}

View file

@ -19,6 +19,7 @@ Future<void> main() async {
try {
await WebSocket.connect('ws://localhost.tld:0/ws');
} catch (err, stackTrace) {
Expect.contains('Failed host lookup', err.toString());
Expect.contains("main ", stackTrace.toString());
}
@ -26,6 +27,7 @@ Future<void> main() async {
try {
await WebSocket.connect('ws://localhost:0/ws');
} catch (err, stackTrace) {
Expect.contains('was not upgraded to websocket', err.toString());
Expect.contains("main ", stackTrace.toString());
}
asyncEnd();

View file

@ -16,6 +16,12 @@ packages_file_test/none: Skip # contains no tests.
[ $builder_tag == asan ]
io/process_detached_test: Slow, Pass
[ $builder_tag == dwarf ]
io/socket_connect_stacktrace_test: SkipByDesign # Assumes stacktrace can be inspected directly, without decoding
[ $builder_tag != dwarf ]
io/socket_connect_dwarf_stacktrace_test: SkipByDesign # Is set up to decode dwarf stack traces
[ $builder_tag == no_ipv6 ]
io/http_ipv6_test: SkipByDesign
io/http_loopback_test: SkipByDesign