websocket client: remove the remaining unsafe from the module

Merged
JA
3mo ago
+1190-1306

What

Follow-up to #40002 / #40018. Takes the whole websocket client module to zero unsafe blocks by fixing the layers underneath instead of annotating call sites:

filemainthis PR
websocket_client.rs710
WebSocketUpgradeClient.rs530
WebSocketProxyTunnel.rs150
CppWebSocket.rs102*
WebSocketProxy.rs10

* the unsafe extern "C" { block keyword for the C++ imports (all now safe fn) and one comment.

  • Ownership. The refs held on behalf of the socket's userdata pointer and of C++'s m_upgradeClient/m_connectedWebSocket are RefPtr<Self> fields released with RefPtr::deref; no more hand-paired Self::deref(ptr). Custom deinit destructors become Drop.
  • Back-references. Tunnel ↔ client links are BackRef<_, Mut> built from the dispatch-time ThisPtr and cleared by the pointee before it can be freed; the tunnel dispatches into either transport through UpgradeClientRef. SslWrapper handlers are typed ThisPtr<WebSocketProxyTunnel> (the wrapper was already generic over its ctx).
  • C++ boundary. Every Bun__WebSocket* entry point is a plain HOST_EXPORT fn; generate-host-exports.ts now scans src/http_jsc and learns &[T] (C passes ptr, len) and ThisPtr<T> params. C++ passes the header arrays as (ptr, len) pairs, the handshake-overflow bytes as one opaque InitialData box, the custom SSL_CTX as Option<OwnedSslCtx>, and setConnectedWebSocket passes nullptr once an open handler has already closed the client. The WebSocket__* imports are safe fns over a new by-value bun_core::ffi::FfiSlice<'a, T> ({ptr,len} carrying the borrow's lifetime).
  • Initial-data microtask. JSGlobalObject::queue_microtask_boxed<T: MicrotaskCallback> owns the one raw trampoline; the task and the client hold detachable back-references to each other; the C++ pending-activity ref is still released at VM shutdown if the queue never drains.
  • Small safe accessors replacing per-site raw handling: Socket::ssl_mut() (and ssl() no longer returns the connecting-socket -1 sentinel), Socket::take_ext_owner, SSL::servername(), VirtualMachine::{default_client_ssl_ctx, ssl_ctx_cache_get_or_create} (the two RuntimeHooks entries become safe fns returning OwnedSslCtx), RefPtr::from_this, BackRef<_, Mut>::this_ptr.

Testing

Debug+ASAN: all 22 test/js/web/websocket/*.test.ts files (178 tests incl. proxy, proxy re-entrancy/leak, permessage-deflate, autobahn) pass; hand-driven ws/wss echo, wss verify-rejection, close-during-connect / during-handshake, non-101 response; BUN_DEBUG_alloc=1 shows 50/50 new/destroy for both HTTPClient and WebSocket across 50 connect/echo/close cycles.