mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 15:17:07 +00:00
[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:
parent
aea28aba05
commit
52aa8508f1
7 changed files with 132 additions and 6 deletions
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue