diff --git a/src/base/http/http_server.cc b/src/base/http/http_server.cc index d2d3d3fe550..1afce3e3eca 100644 --- a/src/base/http/http_server.cc +++ b/src/base/http/http_server.cc @@ -449,9 +449,23 @@ size_t HttpServer::ParseOneWebsocketFrame(HttpServerConnection* conn) { return 0; // Not enough data to read the payload. uint8_t* const payload_start = rd; - // Unmask the payload. - for (uint32_t i = 0; i < payload_len; ++i) - payload_start[i] ^= mask[i % sizeof(mask)]; + // Unmask the payload. Use explicit 32-bit XOR to avoid a compiler + // vectorization bug (observed in Apple Clang 21 / Xcode 26) where the + // mask[i % 4] pattern produces an incorrect 8-byte vector with the second + // half zeroed, causing every other 4-byte group to be left unmasked. + { + uint32_t mask32; + memcpy(&mask32, mask, sizeof(mask32)); + uint32_t i = 0; + for (; i + 4 <= payload_len; i += 4) { + uint32_t tmp; + memcpy(&tmp, &payload_start[i], sizeof(tmp)); + tmp ^= mask32; + memcpy(&payload_start[i], &tmp, sizeof(tmp)); + } + for (; i < payload_len; ++i) + payload_start[i] ^= mask[i & 3]; + } if (opcode == kOpcodePing) { PERFETTO_DLOG("[HTTP] Websocket PING"); diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts index 91609592237..79e24c508fd 100644 --- a/ui/src/trace_processor/engine.ts +++ b/ui/src/trace_processor/engine.ts @@ -233,6 +233,19 @@ export abstract class EngineBase implements Engine, Disposable { this.rxSeqId = rpc.seq; + // The protobufjs oneof discriminant for `type` is the string field `rpc.type`. + // When the TraceProcessor rejects an unknown request it sets the `invalid_request` + // oneof case, leaving `rpc.response` as null/0 so the switch below would fall + // through to `default` without ever resolving the pending promise — causing an + // indefinite hang. Detect and fail fast here instead. + if (rpc.type === 'invalidRequest') { + this.fail( + `TraceProcessor rejected RPC request (method=${rpc.invalidRequest}). ` + + `Ensure the trace_processor_shell binary is compatible with this ` + + `version of the UI (ERR:rpc_invalid_request)`, + ); + } + let isFinalResponse = true; switch (rpc.response) { @@ -675,7 +688,42 @@ export abstract class EngineBase implements Engine, Disposable { protected fail(reason: string) { this._failed = reason; - throw new Error(reason); + const error = new Error(reason); + // Reject all pending operations so callers don't hang forever when the + // engine enters a failed state (e.g. after receiving an invalid_request). + for (const p of this.pendingParses) p.reject(error); + this.pendingParses = []; + for (const p of this.pendingEOFs) p.reject(error); + this.pendingEOFs = []; + for (const p of this.pendingResetTraceProcessors) p.reject(error); + this.pendingResetTraceProcessors = []; + for (const p of this.pendingRestoreTables) p.reject(error); + this.pendingRestoreTables = []; + for (const p of this.pendingComputeMetrics) p.reject(error); + this.pendingComputeMetrics = []; + this.pendingReadMetatrace?.reject(error); + this.pendingReadMetatrace = undefined; + this.pendingRegisterSqlPackage?.reject(error); + this.pendingRegisterSqlPackage = undefined; + this.pendingAnalyzeStructuredQueries?.reject(error); + this.pendingAnalyzeStructuredQueries = undefined; + this.pendingTraceSummary?.reject(error); + this.pendingTraceSummary = undefined; + // Complete any pending streaming queries with an error result. + // WritableQueryResult has no reject() method — errors are signalled by + // calling appendResultBatch() with a QueryResult proto that has `error` + // set and a final batch with `is_last_batch=true`. + if (this.pendingQueries.length > 0) { + const errBatch = protos.QueryResult.encode( + protos.QueryResult.create({ + error: reason, + batch: [protos.QueryResult.CellsBatch.create({isLastBatch: true})], + }), + ).finish(); + for (const q of this.pendingQueries) q.appendResultBatch(errBatch); + this.pendingQueries = []; + } + throw error; } get failed(): string | undefined {