1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef vm_Xdr_h 8 #define vm_Xdr_h 9 10 #include "mozilla/MaybeOneOf.h" 11 #include "mozilla/Utf8.h" 12 13 #include <type_traits> 14 15 #include "jsapi.h" 16 #include "jsfriendapi.h" 17 #include "NamespaceImports.h" 18 19 #include "js/CompileOptions.h" 20 #include "js/Transcoding.h" 21 #include "js/TypeDecls.h" 22 #include "vm/JSAtom.h" 23 24 namespace js { 25 26 struct SourceExtent; 27 28 namespace frontend { 29 struct CompilationStencil; 30 struct ExtensibleCompilationStencil; 31 struct CompilationStencilMerger; 32 struct CompilationInput; 33 } // namespace frontend 34 35 class LifoAlloc; 36 37 enum XDRMode { XDR_ENCODE, XDR_DECODE }; 38 39 template <typename T> 40 using XDRResultT = mozilla::Result<T, JS::TranscodeResult>; 41 using XDRResult = XDRResultT<mozilla::Ok>; 42 43 class XDRBufferBase { 44 public: 45 explicit XDRBufferBase(JSContext* cx, size_t cursor = 0) context_(cx)46 : context_(cx), cursor_(cursor) {} 47 cx()48 JSContext* cx() const { return context_; } 49 cursor()50 size_t cursor() const { return cursor_; } 51 52 protected: 53 JSContext* const context_; 54 size_t cursor_; 55 }; 56 57 template <XDRMode mode> 58 class XDRBuffer; 59 60 template <> 61 class XDRBuffer<XDR_ENCODE> : public XDRBufferBase { 62 public: 63 XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) XDRBufferBase(cx,cursor)64 : XDRBufferBase(cx, cursor), buffer_(buffer) {} 65 write(size_t n)66 uint8_t* write(size_t n) { 67 MOZ_ASSERT(n != 0); 68 if (!buffer_.growByUninitialized(n)) { 69 ReportOutOfMemory(cx()); 70 return nullptr; 71 } 72 uint8_t* ptr = &buffer_[cursor_]; 73 cursor_ += n; 74 return ptr; 75 } 76 align32()77 bool align32() { 78 size_t extra = cursor_ % 4; 79 if (extra) { 80 size_t padding = 4 - extra; 81 if (!buffer_.appendN(0, padding)) { 82 ReportOutOfMemory(cx()); 83 return false; 84 } 85 cursor_ += padding; 86 } 87 return true; 88 } 89 90 #ifdef DEBUG isAligned32()91 bool isAligned32() { return cursor_ % 4 == 0; } 92 #endif 93 read(size_t n)94 const uint8_t* read(size_t n) { 95 MOZ_CRASH("Should never read in encode mode"); 96 return nullptr; 97 } 98 peek(size_t n)99 const uint8_t* peek(size_t n) { 100 MOZ_CRASH("Should never read in encode mode"); 101 return nullptr; 102 } 103 104 private: 105 JS::TranscodeBuffer& buffer_; 106 }; 107 108 template <> 109 class XDRBuffer<XDR_DECODE> : public XDRBufferBase { 110 public: XDRBuffer(JSContext * cx,const JS::TranscodeRange & range)111 XDRBuffer(JSContext* cx, const JS::TranscodeRange& range) 112 : XDRBufferBase(cx), buffer_(range) {} 113 114 XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) XDRBufferBase(cx,cursor)115 : XDRBufferBase(cx, cursor), buffer_(buffer.begin(), buffer.length()) {} 116 align32()117 bool align32() { 118 size_t extra = cursor_ % 4; 119 if (extra) { 120 size_t padding = 4 - extra; 121 cursor_ += padding; 122 123 // Don't let buggy code read past our buffer 124 if (cursor_ > buffer_.length()) { 125 return false; 126 } 127 } 128 return true; 129 } 130 131 #ifdef DEBUG isAligned32()132 bool isAligned32() { return cursor_ % 4 == 0; } 133 #endif 134 read(size_t n)135 const uint8_t* read(size_t n) { 136 MOZ_ASSERT(cursor_ < buffer_.length()); 137 const uint8_t* ptr = &buffer_[cursor_]; 138 cursor_ += n; 139 140 // Don't let buggy code read past our buffer 141 if (cursor_ > buffer_.length()) { 142 return nullptr; 143 } 144 145 return ptr; 146 } 147 peek(size_t n)148 const uint8_t* peek(size_t n) { 149 MOZ_ASSERT(cursor_ < buffer_.length()); 150 const uint8_t* ptr = &buffer_[cursor_]; 151 152 // Don't let buggy code read past our buffer 153 if (cursor_ + n > buffer_.length()) { 154 return nullptr; 155 } 156 157 return ptr; 158 } 159 write(size_t n)160 uint8_t* write(size_t n) { 161 MOZ_CRASH("Should never write in decode mode"); 162 return nullptr; 163 } 164 165 private: 166 const JS::TranscodeRange buffer_; 167 }; 168 169 template <typename CharT> 170 using XDRTranscodeString = 171 mozilla::MaybeOneOf<const CharT*, js::UniquePtr<CharT[], JS::FreePolicy>>; 172 173 class XDRCoderBase { 174 private: 175 #ifdef DEBUG 176 JS::TranscodeResult resultCode_; 177 #endif 178 179 protected: XDRCoderBase()180 XDRCoderBase() 181 #ifdef DEBUG 182 : resultCode_(JS::TranscodeResult::Ok) 183 #endif 184 { 185 } 186 187 public: 188 #ifdef DEBUG 189 // Record logical failures of XDR. resultCode()190 JS::TranscodeResult resultCode() const { return resultCode_; } setResultCode(JS::TranscodeResult code)191 void setResultCode(JS::TranscodeResult code) { 192 MOZ_ASSERT(resultCode() == JS::TranscodeResult::Ok); 193 resultCode_ = code; 194 } 195 bool validateResultCode(JSContext* cx, JS::TranscodeResult code) const; 196 #endif 197 }; 198 199 /* 200 * XDR serialization state. All data is encoded in native endian, except 201 * bytecode. 202 */ 203 template <XDRMode mode> 204 class XDRState : public XDRCoderBase { 205 protected: 206 XDRBuffer<mode> mainBuf; 207 XDRBuffer<mode>* buf; 208 209 public: 210 XDRState(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0) mainBuf(cx,buffer,cursor)211 : mainBuf(cx, buffer, cursor), buf(&mainBuf) {} 212 213 template <typename RangeType> XDRState(JSContext * cx,const RangeType & range)214 XDRState(JSContext* cx, const RangeType& range) 215 : mainBuf(cx, range), buf(&mainBuf) {} 216 217 // No default copy constructor or copying assignment, because |buf| 218 // is an internal pointer. 219 XDRState(const XDRState&) = delete; 220 XDRState& operator=(const XDRState&) = delete; 221 222 virtual ~XDRState() = default; 223 cx()224 JSContext* cx() const { return mainBuf.cx(); } 225 isMultiDecode()226 virtual bool isMultiDecode() const { return false; } 227 hasOptions()228 virtual bool hasOptions() const { return false; } options()229 virtual const JS::ReadOnlyCompileOptions& options() { 230 MOZ_CRASH("does not have options"); 231 } hasScriptSourceObjectOut()232 virtual bool hasScriptSourceObjectOut() const { return false; } scriptSourceObjectOut()233 virtual ScriptSourceObject** scriptSourceObjectOut() { 234 MOZ_CRASH("does not have scriptSourceObjectOut."); 235 } 236 237 template <typename T = mozilla::Ok> fail(JS::TranscodeResult code)238 XDRResultT<T> fail(JS::TranscodeResult code) { 239 #ifdef DEBUG 240 MOZ_ASSERT(code != JS::TranscodeResult::Ok); 241 MOZ_ASSERT(validateResultCode(cx(), code)); 242 setResultCode(code); 243 #endif 244 return mozilla::Err(code); 245 } 246 align32()247 XDRResult align32() { 248 if (!buf->align32()) { 249 return fail(JS::TranscodeResult::Throw); 250 } 251 return Ok(); 252 } 253 254 #ifdef DEBUG isAligned32()255 bool isAligned32() { return buf->isAligned32(); } 256 #endif 257 readData(const uint8_t ** pptr,size_t length)258 XDRResult readData(const uint8_t** pptr, size_t length) { 259 const uint8_t* ptr = buf->read(length); 260 if (!ptr) { 261 return fail(JS::TranscodeResult::Failure_BadDecode); 262 } 263 *pptr = ptr; 264 return Ok(); 265 } 266 267 // Peek the `sizeof(T)` bytes and return the pointer to `*pptr`. 268 // The caller is responsible for aligning the buffer by calling `align32`. 269 template <typename T> peekData(const T ** pptr)270 XDRResult peekData(const T** pptr) { 271 static_assert(alignof(T) <= 4); 272 MOZ_ASSERT(isAligned32()); 273 const uint8_t* ptr = buf->peek(sizeof(T)); 274 if (!ptr) { 275 return fail(JS::TranscodeResult::Failure_BadDecode); 276 } 277 *pptr = reinterpret_cast<const T*>(ptr); 278 return Ok(); 279 } 280 281 // Peek uint32_t data. peekUint32(uint32_t * n)282 XDRResult peekUint32(uint32_t* n) { 283 MOZ_ASSERT(mode == XDR_DECODE); 284 const uint8_t* ptr = buf->peek(sizeof(*n)); 285 if (!ptr) { 286 return fail(JS::TranscodeResult::Failure_BadDecode); 287 } 288 *n = *reinterpret_cast<const uint32_t*>(ptr); 289 return Ok(); 290 } 291 codeUint8(uint8_t * n)292 XDRResult codeUint8(uint8_t* n) { 293 if (mode == XDR_ENCODE) { 294 uint8_t* ptr = buf->write(sizeof(*n)); 295 if (!ptr) { 296 return fail(JS::TranscodeResult::Throw); 297 } 298 *ptr = *n; 299 } else { 300 const uint8_t* ptr = buf->read(sizeof(*n)); 301 if (!ptr) { 302 return fail(JS::TranscodeResult::Failure_BadDecode); 303 } 304 *n = *ptr; 305 } 306 return Ok(); 307 } 308 309 private: 310 template <typename T> codeUintImpl(T * n)311 XDRResult codeUintImpl(T* n) { 312 if (mode == XDR_ENCODE) { 313 uint8_t* ptr = buf->write(sizeof(T)); 314 if (!ptr) { 315 return fail(JS::TranscodeResult::Throw); 316 } 317 memcpy(ptr, n, sizeof(T)); 318 } else { 319 const uint8_t* ptr = buf->read(sizeof(T)); 320 if (!ptr) { 321 return fail(JS::TranscodeResult::Failure_BadDecode); 322 } 323 memcpy(n, ptr, sizeof(T)); 324 } 325 return Ok(); 326 } 327 328 public: codeUint16(uint16_t * n)329 XDRResult codeUint16(uint16_t* n) { return codeUintImpl(n); } 330 codeUint32(uint32_t * n)331 XDRResult codeUint32(uint32_t* n) { return codeUintImpl(n); } 332 codeUint64(uint64_t * n)333 XDRResult codeUint64(uint64_t* n) { return codeUintImpl(n); } 334 335 /* 336 * Use SFINAE to refuse any specialization which is not an enum. Uses of 337 * this function do not have to specialize the type of the enumerated field 338 * as C++ will extract the parameterized from the argument list. 339 */ 340 template <typename T> 341 XDRResult codeEnum32(T* val, std::enable_if_t<std::is_enum_v<T>>* = nullptr) { 342 // Mix the enumeration value with a random magic number, such that a 343 // corruption with a low-ranged value (like 0) is less likely to cause a 344 // miss-interpretation of the XDR content and instead cause a failure. 345 const uint32_t MAGIC = 0x21AB218C; 346 uint32_t tmp; 347 if (mode == XDR_ENCODE) { 348 tmp = uint32_t(*val) ^ MAGIC; 349 } 350 MOZ_TRY(codeUint32(&tmp)); 351 if (mode == XDR_DECODE) { 352 *val = T(tmp ^ MAGIC); 353 } 354 return Ok(); 355 } 356 codeDouble(double * dp)357 XDRResult codeDouble(double* dp) { 358 union DoublePun { 359 double d; 360 uint64_t u; 361 } pun; 362 if (mode == XDR_ENCODE) { 363 pun.d = *dp; 364 } 365 MOZ_TRY(codeUint64(&pun.u)); 366 if (mode == XDR_DECODE) { 367 *dp = pun.d; 368 } 369 return Ok(); 370 } 371 codeMarker(uint32_t magic)372 XDRResult codeMarker(uint32_t magic) { 373 uint32_t actual = magic; 374 MOZ_TRY(codeUint32(&actual)); 375 if (actual != magic) { 376 // Fail in debug, but only soft-fail in release 377 MOZ_ASSERT(false, "Bad XDR marker"); 378 return fail(JS::TranscodeResult::Failure_BadDecode); 379 } 380 return Ok(); 381 } 382 codeBytes(void * bytes,size_t len)383 XDRResult codeBytes(void* bytes, size_t len) { 384 if (len == 0) { 385 return Ok(); 386 } 387 if (mode == XDR_ENCODE) { 388 uint8_t* ptr = buf->write(len); 389 if (!ptr) { 390 return fail(JS::TranscodeResult::Throw); 391 } 392 memcpy(ptr, bytes, len); 393 } else { 394 const uint8_t* ptr = buf->read(len); 395 if (!ptr) { 396 return fail(JS::TranscodeResult::Failure_BadDecode); 397 } 398 memcpy(bytes, ptr, len); 399 } 400 return Ok(); 401 } 402 403 // While encoding, code the given data to the buffer. 404 // While decoding, borrow the buffer and return it to `*data`. 405 // 406 // The data can have extra bytes after `sizeof(T)`, and the caller should 407 // provide the entire data length as `length`. 408 // 409 // The caller is responsible for aligning the buffer by calling `align32`. 410 template <typename T> borrowedData(T ** data,uint32_t length)411 XDRResult borrowedData(T** data, uint32_t length) { 412 static_assert(alignof(T) <= 4); 413 MOZ_ASSERT(isAligned32()); 414 415 if (mode == XDR_ENCODE) { 416 MOZ_TRY(codeBytes(*data, length)); 417 } else { 418 const uint8_t* cursor = nullptr; 419 MOZ_TRY(readData(&cursor, length)); 420 *data = reinterpret_cast<T*>(const_cast<uint8_t*>(cursor)); 421 } 422 return Ok(); 423 } 424 425 // Prefer using a variant below that is encoding aware. 426 XDRResult codeChars(char* chars, size_t nchars); 427 428 XDRResult codeChars(JS::Latin1Char* chars, size_t nchars); 429 XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars); 430 XDRResult codeChars(char16_t* chars, size_t nchars); 431 432 // Transcode null-terminated strings. When decoding, a new buffer is 433 // allocated and ownership is returned to caller. 434 // 435 // NOTE: Throws if string longer than JSString::MAX_LENGTH. 436 XDRResult codeCharsZ(XDRTranscodeString<char>& buffer); 437 XDRResult codeCharsZ(XDRTranscodeString<char16_t>& buffer); 438 439 XDRResult codeModuleObject(MutableHandleModuleObject modp); 440 XDRResult codeScript(MutableHandleScript scriptp); 441 }; 442 443 using XDREncoder = XDRState<XDR_ENCODE>; 444 using XDRDecoderBase = XDRState<XDR_DECODE>; 445 446 class XDRDecoder : public XDRDecoderBase { 447 public: 448 XDRDecoder(JSContext* cx, const JS::ReadOnlyCompileOptions* options, 449 JS::TranscodeBuffer& buffer, size_t cursor = 0) XDRDecoderBase(cx,buffer,cursor)450 : XDRDecoderBase(cx, buffer, cursor), options_(options) { 451 MOZ_ASSERT(options); 452 } 453 454 template <typename RangeType> XDRDecoder(JSContext * cx,const JS::ReadOnlyCompileOptions * options,const RangeType & range)455 XDRDecoder(JSContext* cx, const JS::ReadOnlyCompileOptions* options, 456 const RangeType& range) 457 : XDRDecoderBase(cx, range), options_(options) { 458 MOZ_ASSERT(options); 459 } 460 hasOptions()461 bool hasOptions() const override { return true; } options()462 const JS::ReadOnlyCompileOptions& options() override { return *options_; } 463 464 private: 465 const JS::ReadOnlyCompileOptions* options_; 466 }; 467 468 class XDROffThreadDecoder : public XDRDecoder { 469 ScriptSourceObject** sourceObjectOut_; 470 bool isMultiDecode_; 471 472 public: 473 enum class Type { 474 Single, 475 Multi, 476 }; 477 478 // Note, when providing an JSContext, where isJSContext is false, 479 // then the initialization of the ScriptSourceObject would remain 480 // incomplete. Thus, the sourceObjectOut must be used to finish the 481 // initialization with ScriptSourceObject::initFromOptions after the 482 // decoding. 483 // 484 // When providing a sourceObjectOut pointer, you have to ensure that it is 485 // marked by the GC to avoid dangling pointers. XDROffThreadDecoder(JSContext * cx,const JS::ReadOnlyCompileOptions * options,Type type,ScriptSourceObject ** sourceObjectOut,const JS::TranscodeRange & range)486 XDROffThreadDecoder(JSContext* cx, const JS::ReadOnlyCompileOptions* options, 487 Type type, ScriptSourceObject** sourceObjectOut, 488 const JS::TranscodeRange& range) 489 : XDRDecoder(cx, options, range), 490 sourceObjectOut_(sourceObjectOut), 491 isMultiDecode_(type == Type::Multi) { 492 MOZ_ASSERT(sourceObjectOut); 493 MOZ_ASSERT(*sourceObjectOut == nullptr); 494 } 495 isMultiDecode()496 bool isMultiDecode() const override { return isMultiDecode_; } 497 hasScriptSourceObjectOut()498 bool hasScriptSourceObjectOut() const override { return true; } scriptSourceObjectOut()499 ScriptSourceObject** scriptSourceObjectOut() override { 500 return sourceObjectOut_; 501 } 502 }; 503 504 /* 505 * The structure of the Stencil XDR buffer is: 506 * 507 * 1. Header 508 * a. Version 509 * b. ScriptSource 510 * d. Alignment padding 511 * 2. Stencil 512 * a. CompilationStencil 513 */ 514 515 /* 516 * The stencil decoder accepts `range` as input. 517 * 518 * The decoded stencils are outputted to the default-initialized 519 * `stencil` parameter of `codeStencil` method. 520 * 521 * The decoded stencils borrow the input `buffer`/`range`, and the consumer 522 * has to keep the buffer alive while the decoded stencils are alive. 523 */ 524 class XDRStencilDecoder : public XDRDecoderBase { 525 public: XDRStencilDecoder(JSContext * cx,JS::TranscodeBuffer & buffer,size_t cursor)526 XDRStencilDecoder(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor) 527 : XDRDecoderBase(cx, buffer, cursor) { 528 MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(buffer.begin())); 529 MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(cursor)); 530 } 531 XDRStencilDecoder(JSContext * cx,const JS::TranscodeRange & range)532 XDRStencilDecoder(JSContext* cx, const JS::TranscodeRange& range) 533 : XDRDecoderBase(cx, range) { 534 MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(range.begin().get())); 535 } 536 537 XDRResult codeStencil(frontend::CompilationInput& input, 538 frontend::CompilationStencil& stencil); 539 hasOptions()540 bool hasOptions() const override { return !!options_; } options()541 const JS::ReadOnlyCompileOptions& options() override { 542 MOZ_ASSERT(options_); 543 return *options_; 544 } 545 546 private: 547 const JS::ReadOnlyCompileOptions* options_ = nullptr; 548 }; 549 550 class XDRStencilEncoder : public XDREncoder { 551 public: XDRStencilEncoder(JSContext * cx,JS::TranscodeBuffer & buffer)552 XDRStencilEncoder(JSContext* cx, JS::TranscodeBuffer& buffer) 553 : XDREncoder(cx, buffer, buffer.length()) { 554 // NOTE: If buffer is empty, buffer.begin() doesn't point valid buffer. 555 MOZ_ASSERT_IF(!buffer.empty(), 556 JS::IsTranscodingBytecodeAligned(buffer.begin())); 557 MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(buffer.length())); 558 } 559 560 private: 561 XDRResult codeStencil(const JS::ReadOnlyCompileOptions* options, 562 const RefPtr<ScriptSource>& source, 563 const frontend::CompilationStencil& stencil); 564 565 public: 566 XDRResult codeStencil(const frontend::CompilationInput& input, 567 const frontend::CompilationStencil& stencil); 568 569 XDRResult codeStencil(const RefPtr<ScriptSource>& source, 570 const frontend::CompilationStencil& stencil); 571 }; 572 573 class XDRIncrementalStencilEncoder { 574 frontend::CompilationStencilMerger* merger_ = nullptr; 575 576 public: 577 XDRIncrementalStencilEncoder() = default; 578 579 ~XDRIncrementalStencilEncoder(); 580 581 XDRResult linearize(JSContext* cx, JS::TranscodeBuffer& buffer, 582 js::ScriptSource* ss); 583 584 XDRResult setInitial( 585 JSContext* cx, const JS::ReadOnlyCompileOptions& options, 586 UniquePtr<frontend::ExtensibleCompilationStencil>&& initial); 587 XDRResult addDelazification( 588 JSContext* cx, const frontend::CompilationStencil& delazification); 589 }; 590 591 template <XDRMode mode> 592 XDRResult XDRAtomOrNull(XDRState<mode>* xdr, js::MutableHandleAtom atomp); 593 594 template <XDRMode mode> 595 XDRResult XDRAtom(XDRState<mode>* xdr, js::MutableHandleAtom atomp); 596 597 } /* namespace js */ 598 599 #endif /* vm_Xdr_h */ 600