diff --git a/net/websockets/websocket_basic_stream_adapters.cc b/net/websockets/websocket_basic_stream_adapters.cc index 8f3fb3e32b902..854c6ff499ddf 100644 --- a/net/websockets/websocket_basic_stream_adapters.cc +++ b/net/websockets/websocket_basic_stream_adapters.cc @@ -165,8 +165,31 @@ void WebSocketSpdyStreamAdapter::OnDataReceived( } read_data_.Enqueue(std::move(buffer)); - if (read_callback_) - std::move(read_callback_).Run(CopySavedReadDataIntoBuffer()); + if (read_callback_) { + // Move the callback into a local *before* evaluating the argument. + // Per C++17 [expr.call]/8, in `std::move(read_callback_).Run(arg)` the + // postfix-expression is sequenced before `arg`, but `std::move` is only + // an xvalue cast and `OnceCallback::Run() &&` only clears the holder + // inside its body. So `read_callback_` stays populated throughout `arg` + // evaluation. CopySavedReadDataIntoBuffer() -> SpdyReadQueue::Dequeue -> + // pop_front() -> ~SpdyBuffer can re-enter OnClose() (e.g. via a + // DoDrainSession triggered from a SpdyBuffer consume callback when the + // session write queue overflows). OnClose() then sees `read_callback_` + // non-null, runs it, and synchronously destroys `this` via + // network::WebSocket::Reset() -> channel_.reset(). pop_front() then + // resumes on freed memory at `begin_++`. + // + // Hoisting the move into a separate full-expression clears + // `read_callback_` immediately via the OnceCallback move ctor, so a + // re-entrant OnClose() finds it null and takes the no-callback path, + // leaving `this` alive when pop_front() resumes. + // + // `cb` is on the stack: even if `this` is later destroyed inside + // `cb.Run(rv)`, returning from this scope only touches the stack frame. + CompletionOnceCallback cb = std::move(read_callback_); + int rv = CopySavedReadDataIntoBuffer(); + std::move(cb).Run(rv); + } } void WebSocketSpdyStreamAdapter::OnDataSent() {