Dactyloidae/js/src/vm/Xdr.h

441 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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/EndianUtils.h"
#include "mozilla/TypeTraits.h"
#include "jsatom.h"
#include "jsfriendapi.h"
namespace js {
class XDRBuffer {
public:
XDRBuffer(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
: context_(cx), buffer_(buffer), cursor_(cursor) { }
ExclusiveContext* cx() const {
return context_;
}
const uint8_t* read(size_t n) {
MOZ_ASSERT(cursor_ < buffer_.length());
uint8_t* ptr = &buffer_[cursor_];
cursor_ += n;
return ptr;
}
const char* readCString() {
char* ptr = reinterpret_cast<char*>(&buffer_[cursor_]);
uint8_t* end = reinterpret_cast<uint8_t*>(strchr(ptr, '\0')) + 1;
MOZ_ASSERT(buffer_.begin() < end);
MOZ_ASSERT(end <= buffer_.end());
cursor_ = end - buffer_.begin();
return ptr;
}
uint8_t* write(size_t n) {
MOZ_ASSERT(n != 0);
if (!buffer_.growByUninitialized(n)) {
ReportOutOfMemory(cx());
return nullptr;
}
uint8_t* ptr = &buffer_[cursor_];
cursor_ += n;
return ptr;
}
size_t cursor() const {
return cursor_;
}
private:
ExclusiveContext* const context_;
JS::TranscodeBuffer& buffer_;
size_t cursor_;
};
class XDRCoderBase;
class XDRIncrementalEncoder;
// An AutoXDRTree is used to identify section encoded by an XDRIncrementalEncoder.
//
// Its primary goal is to identify functions, such that we can first encode them
// as LazyScript, and later replaced by them by their corresponding bytecode
// once delazified.
//
// As a convenience, this is also used to identify the top-level of the content
// encoded by an XDRIncrementalEncoder.
//
// Sections can be encoded any number of times in an XDRIncrementalEncoder, and
// the latest encoded version would replace all the previous one.
class MOZ_RAII AutoXDRTree
{
public:
// For a JSFunction, a tree key is defined as being:
// script()->begin << 32 | script()->end
//
// Based on the invariant that |begin <= end|, we can make special
// keys, such as the top-level script.
using Key = uint64_t;
AutoXDRTree(XDRCoderBase* xdr, Key key);
~AutoXDRTree();
// Indicate the lack of a key for the current tree.
static constexpr Key noKey = 0;
// Used to end the slices when there is no children.
static constexpr Key noSubTree = Key(1) << 32;
// Used as the root key of the tree in the hash map.
static constexpr Key topLevel = Key(2) << 32;
private:
friend class XDRIncrementalEncoder;
Key key_;
AutoXDRTree* parent_;
XDRCoderBase* xdr_;
};
class XDRCoderBase
{
protected:
XDRCoderBase() {}
public:
virtual AutoXDRTree::Key getTopLevelTreeKey() const { return AutoXDRTree::noKey; }
virtual AutoXDRTree::Key getTreeKey(JSFunction* fun) const { return AutoXDRTree::noKey; }
virtual void createOrReplaceSubTree(AutoXDRTree* child) {};
virtual void endSubTree() {};
};
/*
* XDR serialization state. All data is encoded in little endian.
*/
template <XDRMode mode>
class XDRState : public XDRCoderBase
{
public:
XDRBuffer buf;
private:
JS::TranscodeResult resultCode_;
public:
XDRState(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
: buf(cx, buffer, cursor),
resultCode_(JS::TranscodeResult_Ok)
{
}
virtual ~XDRState() {};
ExclusiveContext* cx() const {
return buf.cx();
}
virtual LifoAlloc& lifoAlloc() const;
virtual bool hasOptions() const { return false; }
virtual const ReadOnlyCompileOptions& options() {
MOZ_CRASH("does not have options");
}
virtual bool hasScriptSourceObjectOut() const { return false; }
virtual ScriptSourceObject** scriptSourceObjectOut() {
MOZ_CRASH("does not have scriptSourceObjectOut.");
}
// Record logical failures of XDR.
void postProcessContextErrors(ExclusiveContext* cx);
JS::TranscodeResult resultCode() const {
return resultCode_;
}
bool fail(JS::TranscodeResult code) {
MOZ_ASSERT(resultCode_ == JS::TranscodeResult_Ok);
resultCode_ = code;
return false;
}
bool 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 {
*n = *buf.read(sizeof(*n));
}
return true;
}
bool codeUint16(uint16_t* n) {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return fail(JS::TranscodeResult_Throw);
mozilla::LittleEndian::writeUint16(ptr, *n);
} else {
const uint8_t* ptr = buf.read(sizeof(*n));
*n = mozilla::LittleEndian::readUint16(ptr);
}
return true;
}
bool codeUint32(uint32_t* n) {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return fail(JS::TranscodeResult_Throw);
mozilla::LittleEndian::writeUint32(ptr, *n);
} else {
const uint8_t* ptr = buf.read(sizeof(*n));
*n = mozilla::LittleEndian::readUint32(ptr);
}
return true;
}
bool codeUint64(uint64_t* n) {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return fail(JS::TranscodeResult_Throw);
mozilla::LittleEndian::writeUint64(ptr, *n);
} else {
const uint8_t* ptr = buf.read(sizeof(*n));
*n = mozilla::LittleEndian::readUint64(ptr);
}
return true;
}
/*
* 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>
bool codeEnum32(T* val, typename mozilla::EnableIf<mozilla::IsEnum<T>::value, T>::Type * = NULL)
{
// 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 = 0xAF647BCE;
uint32_t tmp;
if (mode == XDR_ENCODE)
tmp = uint32_t(*val) ^ MAGIC;
if (!codeUint32(&tmp))
return false;
if (mode == XDR_DECODE)
*val = T(tmp ^ MAGIC);
return true;
}
bool codeDouble(double* dp) {
union DoublePun {
double d;
uint64_t u;
} pun;
if (mode == XDR_ENCODE)
pun.d = *dp;
if (!codeUint64(&pun.u))
return false;
if (mode == XDR_DECODE)
*dp = pun.d;
return true;
}
bool codeMarker(uint32_t magic) {
uint32_t actual = magic;
if (!codeUint32(&actual))
return false;
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 true;
}
bool codeBytes(void* bytes, size_t len) {
if (len == 0)
return true;
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(len);
if (!ptr)
return fail(JS::TranscodeResult_Throw);
memcpy(ptr, bytes, len);
} else {
memcpy(bytes, buf.read(len), len);
}
return true;
}
/*
* During encoding the string is written into the buffer together with its
* terminating '\0'. During decoding the method returns a pointer into the
* decoding buffer and the caller must copy the string if it will outlive
* the decoding buffer.
*/
bool codeCString(const char** sp) {
if (mode == XDR_ENCODE) {
size_t n = strlen(*sp) + 1;
uint8_t* ptr = buf.write(n);
if (!ptr)
return fail(JS::TranscodeResult_Throw);
memcpy(ptr, *sp, n);
} else {
*sp = buf.readCString();
}
return true;
}
bool codeChars(const JS::Latin1Char* chars, size_t nchars);
bool codeChars(char16_t* chars, size_t nchars);
bool codeFunction(JS::MutableHandleFunction objp, HandleScriptSource sourceObject = nullptr);
bool codeScript(MutableHandleScript scriptp);
bool codeConstValue(MutableHandleValue vp);
};
using XDREncoder = XDRState<XDR_ENCODE>;
using XDRDecoder = XDRState<XDR_DECODE>;
class XDROffThreadDecoder : public XDRDecoder
{
const ReadOnlyCompileOptions* options_;
ScriptSourceObject** sourceObjectOut_;
LifoAlloc& alloc_;
public:
// Note, when providing an ExclusiveContext, where isJSContext is false,
// then the initialization of the ScriptSourceObject would remain
// incomplete. Thus, the sourceObjectOut must be used to finish the
// initialization with ScriptSourceObject::initFromOptions after the
// decoding.
//
// When providing a sourceObjectOut pointer, you have to ensure that it is
// marked by the GC to avoid dangling pointers.
XDROffThreadDecoder(ExclusiveContext* cx, LifoAlloc& alloc,
const ReadOnlyCompileOptions* options,
ScriptSourceObject** sourceObjectOut,
JS::TranscodeBuffer& buffer, size_t cursor = 0)
: XDRDecoder(cx, buffer, cursor),
options_(options),
sourceObjectOut_(sourceObjectOut),
alloc_(alloc)
{
MOZ_ASSERT(options);
MOZ_ASSERT(sourceObjectOut);
MOZ_ASSERT(*sourceObjectOut == nullptr);
}
LifoAlloc& lifoAlloc() const override {
return alloc_;
}
bool hasOptions() const override { return true; }
const ReadOnlyCompileOptions& options() override {
return *options_;
}
bool hasScriptSourceObjectOut() const override { return true; }
ScriptSourceObject** scriptSourceObjectOut() override {
return sourceObjectOut_;
}
};
class XDRIncrementalEncoder : public XDREncoder
{
// The incremental encoder encodes the content of scripts and functions in
// the XDRBuffer. It can be used to encode multiple times the same AutoXDRTree,
// and uses its key to identify which part to replace.
//
// Internally, this encoder keeps a tree representation of the scopes. Each
// node is composed of a vector of slices which are interleaved by child
// nodes.
//
// A slice corresponds to an index and a length within the content of the
// slices_ buffer. The index is updated when a slice is created, and the
// length is updated when the slice is ended, either by creating a new scope
// child, or by closing the scope and going back to the parent.
//
// +---+---+---+
// begin | | | |
// length | | | |
// child | . | . | . |
// +-|-+-|-+---+
// | |
// +---------+ +---------+
// | |
// v v
// +---+---+ +---+
// | | | | |
// | | | | |
// | . | . | | . |
// +-|-+---+ +---+
// |
// |
// |
// v
// +---+
// | |
// | |
// | . |
// +---+
//
//
// The tree key is used to identify the child nodes, and to make them
// easily replaceable.
//
// The tree is rooted at the |topLevel| key.
//
struct Slice {
size_t sliceBegin;
size_t sliceLength;
AutoXDRTree::Key child;
};
using SlicesNode = Vector<Slice, 1, SystemAllocPolicy>;
using SlicesTree = HashMap<AutoXDRTree::Key, SlicesNode, DefaultHasher<AutoXDRTree::Key>,
SystemAllocPolicy>;
// Last opened XDR-tree on the stack.
AutoXDRTree* scope_;
// Node corresponding to the opened scope.
SlicesNode* node_;
// Tree of slices.
SlicesTree tree_;
JS::TranscodeBuffer slices_;
bool oom_;
public:
XDRIncrementalEncoder(ExclusiveContext* cx)
: XDREncoder(cx, slices_, 0),
scope_(nullptr),
node_(nullptr),
oom_(false)
{
}
virtual ~XDRIncrementalEncoder() {}
AutoXDRTree::Key getTopLevelTreeKey() const override;
AutoXDRTree::Key getTreeKey(JSFunction* fun) const override;
MOZ_MUST_USE bool init();
void createOrReplaceSubTree(AutoXDRTree* child) override;
void endSubTree() override;
// Append the content collected during the incremental encoding into the
// buffer given as argument.
MOZ_MUST_USE bool linearize(JS::TranscodeBuffer& buffer);
};
} /* namespace js */
#endif /* vm_Xdr_h */