diff options
Diffstat (limited to 'js/src/vm/Xdr.h')
-rw-r--r-- | js/src/vm/Xdr.h | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h new file mode 100644 index 0000000000..8a1de3c6a4 --- /dev/null +++ b/js/src/vm/Xdr.h @@ -0,0 +1,457 @@ +/* -*- 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/Try.h" // MOZ_TRY +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint16_t, uint32_t, uint64_t +#include <string.h> // memcpy +#include <type_traits> // 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 <typename T> +using XDRResultT = mozilla::Result<T, JS::TranscodeResult>; +using XDRResult = XDRResultT<mozilla::Ok>; + +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 <XDRMode mode> +class XDRBuffer; + +template <> +class XDRBuffer<XDR_ENCODE> : 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<XDR_DECODE> : 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 <typename CharT> +using XDRTranscodeString = + mozilla::MaybeOneOf<const CharT*, js::UniquePtr<CharT[], JS::FreePolicy>>; + +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 <XDRMode mode> +class XDRState : public XDRCoderBase { + protected: + XDRBuffer<mode> mainBuf; + XDRBuffer<mode>* buf; + + public: + XDRState(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) + : mainBuf(fc, buffer, cursor), buf(&mainBuf) {} + + template <typename RangeType> + 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 <typename T = mozilla::Ok> + XDRResultT<T> 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 <typename T> + 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<const T*>(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<const uint32_t*>(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 <typename T> + 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 <typename T> + XDRResult codeEnum32(T* val, std::enable_if_t<std::is_enum_v<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 <typename T> + 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<T*>(const_cast<uint8_t*>(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<char>& buffer); + XDRResult codeCharsZ(XDRTranscodeString<char16_t>& buffer); +}; + +} /* namespace js */ + +#endif /* vm_Xdr_h */ |