/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef vm_Xdr_h #define vm_Xdr_h #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH #include "mozilla/MaybeOneOf.h" // mozilla::MaybeOneOf #include "mozilla/Result.h" // mozilla::{Result, Ok, Err}, MOZ_TRY #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t #include // memcpy #include // std::enable_if_t #include "js/AllocPolicy.h" // ReportOutOfMemory #include "js/Transcoding.h" // JS::TranscodeResult, JS::TranscodeBuffer, JS::TranscodeRange, IsTranscodingBytecodeAligned, IsTranscodingBytecodeOffsetAligned #include "js/TypeDecls.h" // JS::Latin1Char #include "js/UniquePtr.h" // UniquePtr #include "js/Utility.h" // JS::FreePolicy struct JSContext; namespace js { enum XDRMode { XDR_ENCODE, XDR_DECODE }; template using XDRResultT = mozilla::Result; using XDRResult = XDRResultT; class XDRBufferBase { public: explicit XDRBufferBase(FrontendContext* fc, size_t cursor = 0) : fc_(fc), cursor_(cursor) {} FrontendContext* fc() const { return fc_; } size_t cursor() const { return cursor_; } protected: FrontendContext* const fc_; size_t cursor_; }; template class XDRBuffer; template <> class XDRBuffer : public XDRBufferBase { public: XDRBuffer(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) : XDRBufferBase(fc, cursor), buffer_(buffer) {} uint8_t* write(size_t n) { MOZ_ASSERT(n != 0); if (!buffer_.growByUninitialized(n)) { ReportOutOfMemory(fc()); return nullptr; } uint8_t* ptr = &buffer_[cursor_]; cursor_ += n; return ptr; } bool align32() { size_t extra = cursor_ % 4; if (extra) { size_t padding = 4 - extra; if (!buffer_.appendN(0, padding)) { ReportOutOfMemory(fc()); return false; } cursor_ += padding; } return true; } bool isAligned32() { return cursor_ % 4 == 0; } const uint8_t* read(size_t n) { MOZ_CRASH("Should never read in encode mode"); return nullptr; } const uint8_t* peek(size_t n) { MOZ_CRASH("Should never read in encode mode"); return nullptr; } uint8_t* bufferAt(size_t cursor) { MOZ_ASSERT(cursor < buffer_.length()); return &buffer_[cursor]; } private: JS::TranscodeBuffer& buffer_; }; template <> class XDRBuffer : public XDRBufferBase { public: XDRBuffer(FrontendContext* fc, const JS::TranscodeRange& range) : XDRBufferBase(fc), buffer_(range) {} // This isn't used by XDRStencilDecoder. // Defined just for XDRState, shared with XDRStencilEncoder. XDRBuffer(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) : XDRBufferBase(fc, cursor), buffer_(buffer.begin(), buffer.length()) {} bool align32() { size_t extra = cursor_ % 4; if (extra) { size_t padding = 4 - extra; cursor_ += padding; // Don't let buggy code read past our buffer if (cursor_ > buffer_.length()) { return false; } } return true; } bool isAligned32() { return cursor_ % 4 == 0; } const uint8_t* read(size_t n) { MOZ_ASSERT(cursor_ < buffer_.length()); const uint8_t* ptr = &buffer_[cursor_]; cursor_ += n; // Don't let buggy code read past our buffer if (cursor_ > buffer_.length()) { return nullptr; } return ptr; } const uint8_t* peek(size_t n) { MOZ_ASSERT(cursor_ < buffer_.length()); const uint8_t* ptr = &buffer_[cursor_]; // Don't let buggy code read past our buffer if (cursor_ + n > buffer_.length()) { return nullptr; } return ptr; } uint8_t* write(size_t n) { MOZ_CRASH("Should never write in decode mode"); return nullptr; } private: const JS::TranscodeRange buffer_; }; template using XDRTranscodeString = mozilla::MaybeOneOf>; class XDRCoderBase { private: #ifdef DEBUG JS::TranscodeResult resultCode_; #endif protected: XDRCoderBase() #ifdef DEBUG : resultCode_(JS::TranscodeResult::Ok) #endif { } public: #ifdef DEBUG // Record logical failures of XDR. JS::TranscodeResult resultCode() const { return resultCode_; } void setResultCode(JS::TranscodeResult code) { MOZ_ASSERT(resultCode() == JS::TranscodeResult::Ok); resultCode_ = code; } bool validateResultCode(FrontendContext* fc, JS::TranscodeResult code) const; #endif }; /* * XDR serialization state. All data is encoded in native endian, except * bytecode. */ template class XDRState : public XDRCoderBase { protected: XDRBuffer mainBuf; XDRBuffer* buf; public: XDRState(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) : mainBuf(fc, buffer, cursor), buf(&mainBuf) {} template XDRState(FrontendContext* fc, const RangeType& range) : mainBuf(fc, range), buf(&mainBuf) {} // No default copy constructor or copying assignment, because |buf| // is an internal pointer. XDRState(const XDRState&) = delete; XDRState& operator=(const XDRState&) = delete; ~XDRState() = default; FrontendContext* fc() const { return mainBuf.fc(); } template XDRResultT fail(JS::TranscodeResult code) { #ifdef DEBUG MOZ_ASSERT(code != JS::TranscodeResult::Ok); MOZ_ASSERT(validateResultCode(fc(), code)); setResultCode(code); #endif return mozilla::Err(code); } XDRResult align32() { if (!buf->align32()) { return fail(JS::TranscodeResult::Throw); } return mozilla::Ok(); } bool isAligned32() { return buf->isAligned32(); } XDRResult readData(const uint8_t** pptr, size_t length) { const uint8_t* ptr = buf->read(length); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } *pptr = ptr; return mozilla::Ok(); } // Peek the `sizeof(T)` bytes and return the pointer to `*pptr`. // The caller is responsible for aligning the buffer by calling `align32`. template XDRResult peekData(const T** pptr) { static_assert(alignof(T) <= 4); MOZ_ASSERT(isAligned32()); const uint8_t* ptr = buf->peek(sizeof(T)); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } *pptr = reinterpret_cast(ptr); return mozilla::Ok(); } // Peek uint32_t data. XDRResult peekUint32(uint32_t* n) { MOZ_ASSERT(mode == XDR_DECODE); const uint8_t* ptr = buf->peek(sizeof(*n)); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } *n = *reinterpret_cast(ptr); return mozilla::Ok(); } XDRResult codeUint8(uint8_t* n) { if (mode == XDR_ENCODE) { uint8_t* ptr = buf->write(sizeof(*n)); if (!ptr) { return fail(JS::TranscodeResult::Throw); } *ptr = *n; } else { const uint8_t* ptr = buf->read(sizeof(*n)); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } *n = *ptr; } return mozilla::Ok(); } private: template XDRResult codeUintImpl(T* n) { if (mode == XDR_ENCODE) { uint8_t* ptr = buf->write(sizeof(T)); if (!ptr) { return fail(JS::TranscodeResult::Throw); } memcpy(ptr, n, sizeof(T)); } else { const uint8_t* ptr = buf->read(sizeof(T)); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } memcpy(n, ptr, sizeof(T)); } return mozilla::Ok(); } public: XDRResult codeUint16(uint16_t* n) { return codeUintImpl(n); } XDRResult codeUint32(uint32_t* n) { return codeUintImpl(n); } XDRResult codeUint64(uint64_t* n) { return codeUintImpl(n); } void codeUint32At(uint32_t* n, size_t cursor) { if constexpr (mode == XDR_ENCODE) { uint8_t* ptr = buf->bufferAt(cursor); memcpy(ptr, n, sizeof(uint32_t)); } else { MOZ_CRASH("not supported."); } } const uint8_t* bufferAt(size_t cursor) const { if constexpr (mode == XDR_ENCODE) { return buf->bufferAt(cursor); } MOZ_CRASH("not supported."); } XDRResult peekArray(size_t n, const uint8_t** p) { if constexpr (mode == XDR_DECODE) { const uint8_t* ptr = buf->peek(n); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } *p = ptr; return mozilla::Ok(); } MOZ_CRASH("not supported."); } /* * Use SFINAE to refuse any specialization which is not an enum. Uses of * this function do not have to specialize the type of the enumerated field * as C++ will extract the parameterized from the argument list. */ template XDRResult codeEnum32(T* val, std::enable_if_t>* = nullptr) { // Mix the enumeration value with a random magic number, such that a // corruption with a low-ranged value (like 0) is less likely to cause a // miss-interpretation of the XDR content and instead cause a failure. const uint32_t MAGIC = 0x21AB218C; uint32_t tmp; if (mode == XDR_ENCODE) { tmp = uint32_t(*val) ^ MAGIC; } MOZ_TRY(codeUint32(&tmp)); if (mode == XDR_DECODE) { *val = T(tmp ^ MAGIC); } return mozilla::Ok(); } XDRResult codeDouble(double* dp) { union DoublePun { double d; uint64_t u; } pun; if (mode == XDR_ENCODE) { pun.d = *dp; } MOZ_TRY(codeUint64(&pun.u)); if (mode == XDR_DECODE) { *dp = pun.d; } return mozilla::Ok(); } XDRResult codeMarker(uint32_t magic) { uint32_t actual = magic; MOZ_TRY(codeUint32(&actual)); if (actual != magic) { // Fail in debug, but only soft-fail in release MOZ_ASSERT(false, "Bad XDR marker"); return fail(JS::TranscodeResult::Failure_BadDecode); } return mozilla::Ok(); } XDRResult codeBytes(void* bytes, size_t len) { if (len == 0) { return mozilla::Ok(); } if (mode == XDR_ENCODE) { uint8_t* ptr = buf->write(len); if (!ptr) { return fail(JS::TranscodeResult::Throw); } memcpy(ptr, bytes, len); } else { const uint8_t* ptr = buf->read(len); if (!ptr) { return fail(JS::TranscodeResult::Failure_BadDecode); } memcpy(bytes, ptr, len); } return mozilla::Ok(); } // While encoding, code the given data to the buffer. // While decoding, borrow the buffer and return it to `*data`. // // The data can have extra bytes after `sizeof(T)`, and the caller should // provide the entire data length as `length`. // // The caller is responsible for aligning the buffer by calling `align32`. template XDRResult borrowedData(T** data, uint32_t length) { static_assert(alignof(T) <= 4); MOZ_ASSERT(isAligned32()); if (mode == XDR_ENCODE) { MOZ_TRY(codeBytes(*data, length)); } else { const uint8_t* cursor = nullptr; MOZ_TRY(readData(&cursor, length)); *data = reinterpret_cast(const_cast(cursor)); } return mozilla::Ok(); } // Prefer using a variant below that is encoding aware. XDRResult codeChars(char* chars, size_t nchars); XDRResult codeChars(JS::Latin1Char* chars, size_t nchars); XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars); XDRResult codeChars(char16_t* chars, size_t nchars); // Transcode null-terminated strings. When decoding, a new buffer is // allocated and ownership is returned to caller. // // NOTE: Throws if string longer than JSString::MAX_LENGTH. XDRResult codeCharsZ(XDRTranscodeString& buffer); XDRResult codeCharsZ(XDRTranscodeString& buffer); }; } /* namespace js */ #endif /* vm_Xdr_h */