Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  StructuredClone.cpp   Sprache: C

 
/* -*- 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/. */


/*
 * This file implements the structured data algorithms of
 * https://html.spec.whatwg.org/multipage/structured-data.html
 *
 * The spec is in two parts:
 *
 * -   StructuredSerialize examines a JS value and produces a graph of Records.
 * -   StructuredDeserialize walks the Records and produces a new JS value.
 *
 * The differences between our implementation and the spec are minor:
 *
 * -   We call the two phases "write" and "read".
 * -   Our algorithms use an explicit work stack, rather than recursion.
 * -   Serialized data is a flat array of bytes, not a (possibly cyclic) graph
 *     of "Records".
 * -   As a consequence, we handle non-treelike object graphs differently.
 *     We serialize objects that appear in multiple places in the input as
 *     backreferences, using sequential integer indexes.
 *     See `JSStructuredCloneReader::allObjs`, our take on the "memory" map
 *     in the spec's StructuredDeserialize.
 */


#include "js/StructuredClone.h"

#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "jsdate.h"

#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "gc/GC.h"           // AutoSelectGCHeap
#include "js/Array.h"        // JS::GetArrayLength, JS::IsArrayObject
#include "js/ArrayBuffer.h"  // JS::{ArrayBufferHasData,DetachArrayBuffer,IsArrayBufferObject,New{,Mapped}ArrayBufferWithContents,ReleaseMappedArrayBufferContents}
#include "js/ColumnNumber.h"  // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
#include "js/Date.h"
#include "js/experimental/TypedData.h"  // JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped,Big{Ui,I}nt64}ArrayWithBuffer
#include "js/friend/ErrorMessages.h"    // js::GetErrorMessage, JSMSG_*
#include "js/GCAPI.h"
#include "js/GCHashTable.h"
#include "js/Object.h"              // JS::GetBuiltinClass
#include "js/PropertyAndElement.h"  // JS_GetElement
#include "js/RegExpFlags.h"         // JS::RegExpFlag, JS::RegExpFlags
#include "js/ScalarType.h"          // js::Scalar::Type
#include "js/SharedArrayBuffer.h"   // JS::IsSharedArrayBufferObject
#include "js/Wrapper.h"
#include "util/DifferentialTesting.h"
#include "vm/BigIntType.h"
#include "vm/ErrorObject.h"
#include "vm/JSContext.h"
#include "vm/PlainObject.h"  // js::PlainObject
#include "vm/RegExpObject.h"
#include "vm/SavedFrame.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmJS.h"

#include "vm/ArrayObject-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/ErrorObject-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectOperations-inl.h"
#include "vm/Realm-inl.h"
#include "vm/StringType-inl.h"

using namespace js;

using JS::CanonicalizeNaN;
using JS::GetBuiltinClass;
using JS::RegExpFlag;
using JS::RegExpFlags;
using JS::RootedValueVector;
using mozilla::AssertedCast;
using mozilla::BitwiseCast;
using mozilla::Maybe;
using mozilla::NativeEndian;
using mozilla::NumbersAreIdentical;

// When you make updates here, make sure you consider whether you need to bump
// the value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h.  You
// will likely need to increment the version if anything at all changes in the
// serialization format.
//
// Note that SCTAG_END_OF_KEYS is written into the serialized form and should
// have a stable ID, it need not be at the end of the list and should not be
// used for sizing data structures.

enum StructuredDataType : uint32_t {
  // Structured data types provided by the engine
  SCTAG_FLOAT_MAX = 0xFFF00000,
  SCTAG_HEADER = 0xFFF10000,
  SCTAG_NULL = 0xFFFF0000,
  SCTAG_UNDEFINED,
  SCTAG_BOOLEAN,
  SCTAG_INT32,
  SCTAG_STRING,
  SCTAG_DATE_OBJECT,
  SCTAG_REGEXP_OBJECT,
  SCTAG_ARRAY_OBJECT,
  SCTAG_OBJECT_OBJECT,
  SCTAG_ARRAY_BUFFER_OBJECT_V2,  // Old version, for backwards compatibility.
  SCTAG_BOOLEAN_OBJECT,
  SCTAG_STRING_OBJECT,
  SCTAG_NUMBER_OBJECT,
  SCTAG_BACK_REFERENCE_OBJECT,
  SCTAG_DO_NOT_USE_1,           // Required for backwards compatibility
  SCTAG_DO_NOT_USE_2,           // Required for backwards compatibility
  SCTAG_TYPED_ARRAY_OBJECT_V2,  // Old version, for backwards compatibility.
  SCTAG_MAP_OBJECT,
  SCTAG_SET_OBJECT,
  SCTAG_END_OF_KEYS,
  SCTAG_DO_NOT_USE_3,         // Required for backwards compatibility
  SCTAG_DATA_VIEW_OBJECT_V2,  // Old version, for backwards compatibility.
  SCTAG_SAVED_FRAME_OBJECT,

  // No new tags before principals.
  SCTAG_JSPRINCIPALS,
  SCTAG_NULL_JSPRINCIPALS,
  SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
  SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,

  SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
  SCTAG_SHARED_WASM_MEMORY_OBJECT,

  SCTAG_BIGINT,
  SCTAG_BIGINT_OBJECT,

  SCTAG_ARRAY_BUFFER_OBJECT,
  SCTAG_TYPED_ARRAY_OBJECT,
  SCTAG_DATA_VIEW_OBJECT,

  SCTAG_ERROR_OBJECT,

  SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT,
  SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT,

  SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
  SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
  SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
  SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
  SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
  SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
  SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
  SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
  SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
  SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED =
      SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
  // BigInt64 and BigUint64 are not supported in the v1 format.
  SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED,

  // Define a separate range of numbers for Transferable-only tags, since
  // they are not used for persistent clone buffers and therefore do not
  // require bumping JS_STRUCTURED_CLONE_VERSION.
  SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
  SCTAG_TRANSFER_MAP_PENDING_ENTRY,
  SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
  SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER,
  SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,

  SCTAG_END_OF_BUILTIN_TYPES
};

/*
 * Format of transfer map:
 *   - <SCTAG_TRANSFER_MAP_HEADER, UNREAD|TRANSFERRING|TRANSFERRED>
 *   - numTransferables (64 bits)
 *   - array of:
 *     - <SCTAG_TRANSFER_MAP_*, TransferableOwnership> pointer (64
 *       bits)
 *     - extraData (64 bits), eg byte length for ArrayBuffers
 *     - any data written for custom transferables
 */


// Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
// contents have been read out yet or not. TRANSFERRING is for the case where we
// have started but not completed reading, which due to errors could mean that
// there are things still owned by the clone buffer that need to be released, so
// discarding should not just be skipped.
enum TransferableMapHeader {
  SCTAG_TM_UNREAD = 0,
  SCTAG_TM_TRANSFERRING,
  SCTAG_TM_TRANSFERRED,

  SCTAG_TM_END
};

static inline uint64_t PairToUInt64(uint32_t tag, uint32_t data) {
  return uint64_t(data) | (uint64_t(tag) << 32);
}

namespace js {

template <typename T, typename AllocPolicy>
struct BufferIterator {
  using BufferList = mozilla::BufferList<AllocPolicy>;

  explicit BufferIterator(const BufferList& buffer)
      : mBuffer(buffer), mIter(buffer.Iter()) {
    static_assert(8 % sizeof(T) == 0);
  }

  explicit BufferIterator(const JSStructuredCloneData& data)
      : mBuffer(data.bufList_), mIter(data.Start()) {}

  BufferIterator& operator=(const BufferIterator& other) {
    MOZ_ASSERT(&mBuffer == &other.mBuffer);
    mIter = other.mIter;
    return *this;
  }

  [[nodiscard]] bool advance(size_t size = sizeof(T)) {
    return mIter.AdvanceAcrossSegments(mBuffer, size);
  }

  BufferIterator operator++(int) {
    BufferIterator ret = *this;
    if (!advance(sizeof(T))) {
      MOZ_ASSERT(false"Failed to read StructuredCloneData. Data incomplete");
    }
    return ret;
  }

  BufferIterator& operator+=(size_t size) {
    if (!advance(size)) {
      MOZ_ASSERT(false"Failed to read StructuredCloneData. Data incomplete");
    }
    return *this;
  }

  size_t operator-(const BufferIterator& other) const {
    MOZ_ASSERT(&mBuffer == &other.mBuffer);
    return mBuffer.RangeLength(other.mIter, mIter);
  }

  bool operator==(const BufferIterator& other) const {
    return mBuffer.Start() == other.mBuffer.Start() && mIter == other.mIter;
  }
  bool operator!=(const BufferIterator& other) const {
    return !(*this == other);
  }

  bool done() const { return mIter.Done(); }

  [[nodiscard]] bool readBytes(char* outData, size_t size) {
    return mBuffer.ReadBytes(mIter, outData, size);
  }

  void write(const T& data) {
    MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
    *reinterpret_cast<T*>(mIter.Data()) = data;
  }

  T peek() const {
    MOZ_ASSERT(mIter.HasRoomFor(sizeof(T)));
    return *reinterpret_cast<T*>(mIter.Data());
  }

  bool canPeek() const { return mIter.HasRoomFor(sizeof(T)); }

  const BufferList& mBuffer;
  typename BufferList::IterImpl mIter;
};

SharedArrayRawBufferRefs& SharedArrayRawBufferRefs::operator=(
    SharedArrayRawBufferRefs&& other) {
  takeOwnership(std::move(other));
  return *this;
}

SharedArrayRawBufferRefs::~SharedArrayRawBufferRefs() { releaseAll(); }

bool SharedArrayRawBufferRefs::acquire(JSContext* cx,
                                       SharedArrayRawBuffer* rawbuf) {
  if (!refs_.append(rawbuf)) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (!rawbuf->addReference()) {
    refs_.popBack();
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_SC_SAB_REFCNT_OFLO);
    return false;
  }

  return true;
}

bool SharedArrayRawBufferRefs::acquireAll(
    JSContext* cx, const SharedArrayRawBufferRefs& that) {
  if (!refs_.reserve(refs_.length() + that.refs_.length())) {
    ReportOutOfMemory(cx);
    return false;
  }

  for (auto ref : that.refs_) {
    if (!ref->addReference()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_SC_SAB_REFCNT_OFLO);
      return false;
    }
    MOZ_ALWAYS_TRUE(refs_.append(ref));
  }

  return true;
}

void SharedArrayRawBufferRefs::takeOwnership(SharedArrayRawBufferRefs&& other) {
  MOZ_ASSERT(refs_.empty());
  refs_ = std::move(other.refs_);
}

void SharedArrayRawBufferRefs::releaseAll() {
  for (auto ref : refs_) {
    ref->dropReference();
  }
  refs_.clear();
}

// SCOutput provides an interface to write raw data -- eg uint64_ts, doubles,
// arrays of bytes -- into a structured clone data output stream. It also knows
// how to free any transferable data within that stream.
//
// Note that it contains a full JSStructuredCloneData object, which holds the
// callbacks necessary to read/write/transfer/free the data. For the purpose of
// this class, only the freeTransfer callback is relevant; the rest of the
// callbacks are used by the higher-level JSStructuredCloneWriter interface.
struct SCOutput {
 public:
  using Iter = BufferIterator<uint64_t, SystemAllocPolicy>;

  SCOutput(JSContext* cx, JS::StructuredCloneScope scope);

  JSContext* context() const { return cx; }
  JS::StructuredCloneScope scope() const { return buf.scope(); }
  void sameProcessScopeRequired() { buf.sameProcessScopeRequired(); }

  [[nodiscard]] bool write(uint64_t u);
  [[nodiscard]] bool writePair(uint32_t tag, uint32_t data);
  [[nodiscard]] bool writeDouble(double d);
  [[nodiscard]] bool writeBytes(const void* p, size_t nbytes);
  [[nodiscard]] bool writeChars(const Latin1Char* p, size_t nchars);
  [[nodiscard]] bool writeChars(const char16_t* p, size_t nchars);

  template <class T>
  [[nodiscard]] bool writeArray(const T* p, size_t nelems);

  void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure,
                    OwnTransferablePolicy policy) {
    buf.setCallbacks(callbacks, closure, policy);
  }
  void extractBuffer(JSStructuredCloneData* data) { *data = std::move(buf); }

  uint64_t tell() const { return buf.Size(); }
  uint64_t count() const { return buf.Size() / sizeof(uint64_t); }
  Iter iter() { return Iter(buf); }

  size_t offset(Iter dest) { return dest - iter(); }

  JSContext* cx;
  JSStructuredCloneData buf;
};

class SCInput {
 public:
  using BufferIterator = js::BufferIterator<uint64_t, SystemAllocPolicy>;

  SCInput(JSContext* cx, const JSStructuredCloneData& data);

  JSContext* context() const { return cx; }

  static void getPtr(uint64_t data, void** ptr);
  static void getPair(uint64_t data, uint32_t* tagp, uint32_t* datap);

  [[nodiscard]] bool read(uint64_t* p);
  [[nodiscard]] bool readPair(uint32_t* tagp, uint32_t* datap);
  [[nodiscard]] bool readDouble(double* p);
  [[nodiscard]] bool readBytes(void* p, size_t nbytes);
  [[nodiscard]] bool readChars(Latin1Char* p, size_t nchars);
  [[nodiscard]] bool readChars(char16_t* p, size_t nchars);
  [[nodiscard]] bool readPtr(void**);

  [[nodiscard]] bool get(uint64_t* p);
  [[nodiscard]] bool getPair(uint32_t* tagp, uint32_t* datap);

  const BufferIterator& tell() const { return point; }
  void seekTo(const BufferIterator& pos) { point = pos; }
  [[nodiscard]] bool seekBy(size_t pos) {
    if (!point.advance(pos)) {
      reportTruncated();
      return false;
    }
    return true;
  }

  template <class T>
  [[nodiscard]] bool readArray(T* p, size_t nelems);

  bool reportTruncated() {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
    return false;
  }

 private:
  void staticAssertions() {
    static_assert(sizeof(char16_t) == 2);
    static_assert(sizeof(uint32_t) == 4);
  }

  JSContext* cx;
  BufferIterator point;
};

}  // namespace js

struct JSStructuredCloneReader {
 public:
  explicit JSStructuredCloneReader(SCInput& in, JS::StructuredCloneScope scope,
                                   const JS::CloneDataPolicy& cloneDataPolicy,
                                   const JSStructuredCloneCallbacks* cb,
                                   void* cbClosure);

  SCInput& input() { return in; }
  bool read(MutableHandleValue vp, size_t nbytes);

 private:
  JSContext* context() { return in.context(); }

  bool readHeader();
  bool readTransferMap();

  [[nodiscard]] bool readUint32(uint32_t* num);

  enum ShouldAtomizeStrings : bool {
    DontAtomizeStrings = false,
    AtomizeStrings = true
  };

  template <typename CharT>
  JSString* readStringImpl(uint32_t nchars, ShouldAtomizeStrings atomize);
  JSString* readString(uint32_t data, ShouldAtomizeStrings atomize);

  BigInt* readBigInt(uint32_t data);

  [[nodiscard]] bool readTypedArray(uint32_t arrayType, uint64_t nelems,
                                    MutableHandleValue vp, bool v1Read = false);

  [[nodiscard]] bool readDataView(uint64_t byteLength, MutableHandleValue vp);

  [[nodiscard]] bool readArrayBuffer(StructuredDataType type, uint32_t data,
                                     MutableHandleValue vp);
  [[nodiscard]] bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
                                       MutableHandleValue vp);

  [[nodiscard]] bool readSharedArrayBuffer(StructuredDataType type,
                                           MutableHandleValue vp);

  [[nodiscard]] bool readSharedWasmMemory(uint32_t nbytes,
                                          MutableHandleValue vp);

  // A serialized SavedFrame contains primitive values in a header followed by
  // an optional parent frame that is read recursively.
  [[nodiscard]] JSObject* readSavedFrameHeader(uint32_t principalsTag);
  [[nodiscard]] bool readSavedFrameFields(Handle<SavedFrame*> frameObj,
                                          HandleValue parent, bool* state);

  // A serialized Error contains primitive values in a header followed by
  // 'cause', 'errors', and 'stack' fields that are read recursively.
  [[nodiscard]] JSObject* readErrorHeader(uint32_t type);
  [[nodiscard]] bool readErrorFields(Handle<ErrorObject*> errorObj,
                                     HandleValue cause, bool* state);

  [[nodiscard]] bool readMapField(Handle<MapObject*> mapObj, HandleValue key);

  [[nodiscard]] bool readObjectField(HandleObject obj, HandleValue key);

  [[nodiscard]] bool startRead(
      MutableHandleValue vp,
      ShouldAtomizeStrings atomizeStrings = DontAtomizeStrings);

  SCInput& in;

  // The widest scope that the caller will accept, where
  // SameProcess is the widest (it can store anything it wants)
  // and DifferentProcess is the narrowest (it cannot contain pointers and must
  // be valid cross-process.)
  //
  // Although this can be initially set to other "unresolved" values (eg
  // Unassigned), by the end of a successful readHeader() this will be resolved
  // to SameProcess or DifferentProcess.
  JS::StructuredCloneScope allowedScope;

  const JS::CloneDataPolicy cloneDataPolicy;

  // Stack of objects with properties remaining to be read.
  RootedValueVector objs;

  // Maintain a stack of state values for the `objs` stack. Since this is only
  // needed for a very small subset of objects (those with a known set of
  // object children), the state information is stored as a stack of
  // <object, state> pairs where the object determines which element of the
  // `objs` stack that it corresponds to. So when reading from the `objs` stack,
  // the state will be retrieved only if the top object on `objState` matches
  // the top object of `objs`.
  //
  // Currently, the only state needed is a boolean indicating whether the fields
  // have been read yet.
  Rooted<GCVector<std::pair<HeapPtr<JSObject*>, bool>, 8>> objState;

  // Array of all objects read during this deserialization, for resolving
  // backreferences.
  //
  // For backreferences to work correctly, objects must be added to this
  // array in exactly the order expected by the version of the Writer that
  // created the serialized data, even across years and format versions. This
  // is usually no problem, since both algorithms do a single linear pass
  // over the serialized data. There is one hitch; see readTypedArray.
  //
  // The values in this vector are objects, except it can temporarily have
  // one `undefined` placeholder value (the readTypedArray hack).
  RootedValueVector allObjs;

  size_t numItemsRead;

  // The user defined callbacks that will be used for cloning.
  const JSStructuredCloneCallbacks* callbacks;

  // Any value passed to JS_ReadStructuredClone.
  void* closure;

  // The heap to use for allocating common GC things. This starts out as the
  // nursery (the default) but may switch to the tenured heap if nursery
  // collection occurs, as nursery allocation is pointless after the
  // deserialized root object is tenured.
  //
  // This is only used for the most common kind, e.g. plain objects, strings
  // and a couple of others.
  AutoSelectGCHeap gcHeap;

  friend bool JS_ReadString(JSStructuredCloneReader* r,
                            JS::MutableHandleString str);
  friend bool JS_ReadTypedArray(JSStructuredCloneReader* r,
                                MutableHandleValue vp);

  // Provide a way to detect whether any of the clone data is never used. When
  // "tail" data (currently, this is only stored data for Transferred
  // ArrayBuffers in the DifferentProcess scope) is read, record the first and
  // last positions. At the end of deserialization, make sure there's nothing
  // between the end of the main data and the beginning of the tail, nor after
  // the end of the tail.
  mozilla::Maybe<SCInput::BufferIterator> tailStartPos;
  mozilla::Maybe<SCInput::BufferIterator> tailEndPos;
};

struct JSStructuredCloneWriter {
 public:
  explicit JSStructuredCloneWriter(JSContext* cx,
                                   JS::StructuredCloneScope scope,
                                   const JS::CloneDataPolicy& cloneDataPolicy,
                                   const JSStructuredCloneCallbacks* cb,
                                   void* cbClosure, const Value& tVal)
      : out(cx, scope),
        callbacks(cb),
        closure(cbClosure),
        objs(cx),
        counts(cx),
        objectEntries(cx),
        otherEntries(cx),
        memory(cx),
        transferable(cx, tVal),
        transferableObjects(cx, TransferableObjectsList(cx)),
        cloneDataPolicy(cloneDataPolicy) {
    out.setCallbacks(cb, cbClosure,
                     OwnTransferablePolicy::OwnsTransferablesIfAny);
  }

  bool init() {
    return parseTransferable() && writeHeader() && writeTransferMap();
  }

  bool write(HandleValue v);

  SCOutput& output() { return out; }

  void extractBuffer(JSStructuredCloneData* newData) {
    out.extractBuffer(newData);
  }

 private:
  JSStructuredCloneWriter() = delete;
  JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;

  JSContext* context() { return out.context(); }

  bool writeHeader();
  bool writeTransferMap();

  bool writeString(uint32_t tag, JSString* str);
  bool writeBigInt(uint32_t tag, BigInt* bi);
  bool writeArrayBuffer(HandleObject obj);
  bool writeTypedArray(HandleObject obj);
  bool writeDataView(HandleObject obj);
  bool writeSharedArrayBuffer(HandleObject obj);
  bool writeSharedWasmMemory(HandleObject obj);
  bool startObject(HandleObject obj, bool* backref);
  bool writePrimitive(HandleValue v);
  bool startWrite(HandleValue v);
  bool traverseObject(HandleObject obj, ESClass cls);
  bool traverseMap(HandleObject obj);
  bool traverseSet(HandleObject obj);
  bool traverseSavedFrame(HandleObject obj);
  bool traverseError(HandleObject obj);

  template <typename... Args>
  bool reportDataCloneError(uint32_t errorId, Args&&... aArgs);

  bool parseTransferable();
  bool transferOwnership();

  inline void checkStack();

  SCOutput out;

  // The user defined callbacks that will be used to signal cloning, in some
  // cases.
  const JSStructuredCloneCallbacks* callbacks;

  // Any value passed to the callbacks.
  void* closure;

  // Vector of objects with properties remaining to be written.
  //
  // NB: These can span multiple compartments, so the compartment must be
  // entered before any manipulation is performed.
  RootedValueVector objs;

  // counts[i] is the number of entries of objs[i] remaining to be written.
  // counts.length() == objs.length() and sum(counts) == entries.length().
  Vector<size_t> counts;

  // For JSObject: Property IDs as value
  RootedIdVector objectEntries;

  // For Map: Key followed by value
  // For Set: Key
  // For SavedFrame: parent SavedFrame
  // For Error: cause, errors, stack
  RootedValueVector otherEntries;

  // The "memory" list described in the HTML5 internal structured cloning
  // algorithm.  memory is a superset of objs; items are never removed from
  // Memory until a serialization operation is finished
  using CloneMemory = GCHashMap<JSObject*, uint32_t,
                                StableCellHasher<JSObject*>, SystemAllocPolicy>;
  Rooted<CloneMemory> memory;

  // Set of transferable objects
  RootedValue transferable;
  using TransferableObjectsList = GCVector<JSObject*>;
  Rooted<TransferableObjectsList> transferableObjects;

  const JS::CloneDataPolicy cloneDataPolicy;

  friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
  friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
  friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj);
};

JS_PUBLIC_API uint64_t js::GetSCOffset(JSStructuredCloneWriter* writer) {
  MOZ_ASSERT(writer);
  return writer->output().count() * sizeof(uint64_t);
}

static_assert(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
static_assert(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
static_assert(Scalar::Int8 == 0);

template <typename... Args>
static void ReportDataCloneError(JSContext* cx,
                                 const JSStructuredCloneCallbacks* callbacks,
                                 uint32_t errorId, void* closure,
                                 Args&&... aArgs) {
  unsigned errorNumber;
  switch (errorId) {
    case JS_SCERR_DUP_TRANSFERABLE:
      errorNumber = JSMSG_SC_DUP_TRANSFERABLE;
      break;

    case JS_SCERR_TRANSFERABLE:
      errorNumber = JSMSG_SC_NOT_TRANSFERABLE;
      break;

    case JS_SCERR_UNSUPPORTED_TYPE:
      errorNumber = JSMSG_SC_UNSUPPORTED_TYPE;
      break;

    case JS_SCERR_SHMEM_TRANSFERABLE:
      errorNumber = JSMSG_SC_SHMEM_TRANSFERABLE;
      break;

    case JS_SCERR_TRANSFERABLE_TWICE:
      errorNumber = JSMSG_SC_TRANSFERABLE_TWICE;
      break;

    case JS_SCERR_TYPED_ARRAY_DETACHED:
      errorNumber = JSMSG_TYPED_ARRAY_DETACHED;
      break;

    case JS_SCERR_WASM_NO_TRANSFER:
      errorNumber = JSMSG_WASM_NO_TRANSFER;
      break;

    case JS_SCERR_NOT_CLONABLE:
      errorNumber = JSMSG_SC_NOT_CLONABLE;
      break;

    case JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP:
      errorNumber = JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP;
      break;

    default:
      MOZ_CRASH("Unkown errorId");
      break;
  }

  if (callbacks && callbacks->reportError) {
    MOZ_RELEASE_ASSERT(!cx->isExceptionPending());

    JSErrorReport report;
    report.errorNumber = errorNumber;
    // Get js error message if it's possible and propagate it through callback.
    if (JS_ExpandErrorArgumentsASCII(cx, GetErrorMessage, errorNumber, &report,
                                     std::forward<Args>(aArgs)...) &&
        report.message()) {
      callbacks->reportError(cx, errorId, closure, report.message().c_str());
    } else {
      ReportOutOfMemory(cx);

      callbacks->reportError(cx, errorId, closure, "");
    }

    return;
  }

  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber,
                            std::forward<Args>(aArgs)...);
}

bool WriteStructuredClone(JSContext* cx, HandleValue v,
                          JSStructuredCloneData* bufp,
                          JS::StructuredCloneScope scope,
                          const JS::CloneDataPolicy& cloneDataPolicy,
                          const JSStructuredCloneCallbacks* cb, void* cbClosure,
                          const Value& transferable) {
  JSStructuredCloneWriter w(cx, scope, cloneDataPolicy, cb, cbClosure,
                            transferable);
  if (!w.init()) {
    return false;
  }
  if (!w.write(v)) {
    return false;
  }
  w.extractBuffer(bufp);
  return true;
}

bool ReadStructuredClone(JSContext* cx, const JSStructuredCloneData& data,
                         JS::StructuredCloneScope scope, MutableHandleValue vp,
                         const JS::CloneDataPolicy& cloneDataPolicy,
                         const JSStructuredCloneCallbacks* cb,
                         void* cbClosure) {
  if (data.Size() % 8) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_SC_BAD_SERIALIZED_DATA, "misaligned");
    return false;
  }
  SCInput in(cx, data);
  JSStructuredCloneReader r(in, scope, cloneDataPolicy, cb, cbClosure);
  return r.read(vp, data.Size());
}

static bool StructuredCloneHasTransferObjects(
    const JSStructuredCloneData& data) {
  if (data.Size() < sizeof(uint64_t)) {
    return false;
  }

  uint64_t u;
  BufferIterator<uint64_t, SystemAllocPolicy> iter(data);
  MOZ_ALWAYS_TRUE(iter.readBytes(reinterpret_cast<char*>(&u), sizeof(u)));
  uint32_t tag = uint32_t(u >> 32);
  return (tag == SCTAG_TRANSFER_MAP_HEADER);
}

namespace js {

SCInput::SCInput(JSContext* cx, const JSStructuredCloneData& data)
    : cx(cx), point(data) {
  static_assert(JSStructuredCloneData::BufferList::kSegmentAlignment % 8 == 0,
                "structured clone buffer reads should be aligned");
  MOZ_ASSERT(data.Size() % 8 == 0);
}

bool SCInput::read(uint64_t* p) {
  if (!point.canPeek()) {
    *p = 0;  // initialize to shut GCC up
    return reportTruncated();
  }
  *p = NativeEndian::swapFromLittleEndian(point.peek());
  MOZ_ALWAYS_TRUE(point.advance());
  return true;
}

bool SCInput::readPair(uint32_t* tagp, uint32_t* datap) {
  uint64_t u;
  bool ok = read(&u);
  if (ok) {
    *tagp = uint32_t(u >> 32);
    *datap = uint32_t(u);
  }
  return ok;
}

bool SCInput::get(uint64_t* p) {
  if (!point.canPeek()) {
    return reportTruncated();
  }
  *p = NativeEndian::swapFromLittleEndian(point.peek());
  return true;
}

bool SCInput::getPair(uint32_t* tagp, uint32_t* datap) {
  uint64_t u = 0;
  if (!get(&u)) {
    return false;
  }

  *tagp = uint32_t(u >> 32);
  *datap = uint32_t(u);
  return true;
}

void SCInput::getPair(uint64_t data, uint32_t* tagp, uint32_t* datap) {
  uint64_t u = NativeEndian::swapFromLittleEndian(data);
  *tagp = uint32_t(u >> 32);
  *datap = uint32_t(u);
}

bool SCInput::readDouble(double* p) {
  uint64_t u;
  if (!read(&u)) {
    return false;
  }
  *p = CanonicalizeNaN(mozilla::BitwiseCast<double>(u));
  return true;
}

template <typename T>
static void swapFromLittleEndianInPlace(T* ptr, size_t nelems) {
  if (nelems > 0) {
    NativeEndian::swapFromLittleEndianInPlace(ptr, nelems);
  }
}

template <>
void swapFromLittleEndianInPlace(uint8_t* ptr, size_t nelems) {}

// Data is packed into an integral number of uint64_t words. Compute the
// padding required to finish off the final word.
static size_t ComputePadding(size_t nelems, size_t elemSize) {
  // We want total length mod 8, where total length is nelems * sizeof(T),
  // but that might overflow. So reduce nelems to nelems mod 8, since we are
  // going to be doing a mod 8 later anyway.
  size_t leftoverLength = (nelems % sizeof(uint64_t)) * elemSize;
  return (-leftoverLength) & (sizeof(uint64_t) - 1);
}

template <class T>
bool SCInput::readArray(T* p, size_t nelems) {
  if (!nelems) {
    return true;
  }

  static_assert(sizeof(uint64_t) % sizeof(T) == 0);

  // Fail if nelems is so huge that computing the full size will overflow.
  mozilla::CheckedInt<size_t> size =
      mozilla::CheckedInt<size_t>(nelems) * sizeof(T);
  if (!size.isValid()) {
    return reportTruncated();
  }

  if (!point.readBytes(reinterpret_cast<char*>(p), size.value())) {
    // To avoid any way in which uninitialized data could escape, zero the array
    // if filling it failed.
    std::uninitialized_fill_n(p, nelems, 0);
    return reportTruncated();
  }

  swapFromLittleEndianInPlace(p, nelems);

  point += ComputePadding(nelems, sizeof(T));

  return true;
}

bool SCInput::readBytes(void* p, size_t nbytes) {
  return readArray((uint8_t*)p, nbytes);
}

bool SCInput::readChars(Latin1Char* p, size_t nchars) {
  static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
                "Latin1Char must fit in 1 byte");
  return readBytes(p, nchars);
}

bool SCInput::readChars(char16_t* p, size_t nchars) {
  MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
  return readArray((uint16_t*)p, nchars);
}

void SCInput::getPtr(uint64_t data, void** ptr) {
  *ptr = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(data));
}

bool SCInput::readPtr(void** p) {
  uint64_t u;
  if (!read(&u)) {
    return false;
  }
  *p = reinterpret_cast<void*>(u);
  return true;
}

SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope)
    : cx(cx), buf(scope) {}

bool SCOutput::write(uint64_t u) {
  uint64_t v = NativeEndian::swapToLittleEndian(u);
  if (!buf.AppendBytes(reinterpret_cast<char*>(&v), sizeof(u))) {
    ReportOutOfMemory(context());
    return false;
  }
  return true;
}

bool SCOutput::writePair(uint32_t tag, uint32_t data) {
  // As it happens, the tag word appears after the data word in the output.
  // This is because exponents occupy the last 2 bytes of doubles on the
  // little-endian platforms we care most about.
  //
  // For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
  // PairToUInt64 produces the number 0xFFFF000200000001.
  // That is written out as the bytes 01 00 00 00 02 00 FF FF.
  return write(PairToUInt64(tag, data));
}

static inline double ReinterpretPairAsDouble(uint32_t tag, uint32_t data) {
  return BitwiseCast<double>(PairToUInt64(tag, data));
}

bool SCOutput::writeDouble(double d) {
  return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
}

template <class T>
bool SCOutput::writeArray(const T* p, size_t nelems) {
  static_assert(8 % sizeof(T) == 0);
  static_assert(sizeof(uint64_t) % sizeof(T) == 0);

  if (nelems == 0) {
    return true;
  }

  for (size_t i = 0; i < nelems; i++) {
    T value = NativeEndian::swapToLittleEndian(p[i]);
    if (!buf.AppendBytes(reinterpret_cast<char*>(&value), sizeof(value))) {
      ReportOutOfMemory(context());
      return false;
    }
  }

  // Zero-pad to 8 bytes boundary.
  size_t padbytes = ComputePadding(nelems, sizeof(T));
  char zeroes[sizeof(uint64_t)] = {0};
  if (!buf.AppendBytes(zeroes, padbytes)) {
    ReportOutOfMemory(context());
    return false;
  }

  return true;
}

template <>
bool SCOutput::writeArray<uint8_t>(const uint8_t* p, size_t nelems) {
  if (nelems == 0) {
    return true;
  }

  if (!buf.AppendBytes(reinterpret_cast<const char*>(p), nelems)) {
    ReportOutOfMemory(context());
    return false;
  }

  // zero-pad to 8 bytes boundary
  size_t padbytes = ComputePadding(nelems, 1);
  char zeroes[sizeof(uint64_t)] = {0};
  if (!buf.AppendBytes(zeroes, padbytes)) {
    ReportOutOfMemory(context());
    return false;
  }

  return true;
}

bool SCOutput::writeBytes(const void* p, size_t nbytes) {
  return writeArray((const uint8_t*)p, nbytes);
}

bool SCOutput::writeChars(const char16_t* p, size_t nchars) {
  static_assert(sizeof(char16_t) == sizeof(uint16_t),
                "required so that treating char16_t[] memory as uint16_t[] "
                "memory is permissible");
  return writeArray((const uint16_t*)p, nchars);
}

bool SCOutput::writeChars(const Latin1Char* p, size_t nchars) {
  static_assert(sizeof(Latin1Char) == sizeof(uint8_t),
                "Latin1Char must fit in 1 byte");
  return writeBytes(p, nchars);
}

}  // namespace js

JSStructuredCloneData::~JSStructuredCloneData() { discardTransferables(); }

// If the buffer contains Transferables, free them. Note that custom
// Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
// delete their transferables.
void JSStructuredCloneData::discardTransferables() {
  if (!Size()) {
    return;
  }

  if (ownTransferables_ != OwnTransferablePolicy::OwnsTransferablesIfAny) {
    return;
  }

  // DifferentProcess clones cannot contain pointers, so nothing needs to be
  // released.
  if (scope() == JS::StructuredCloneScope::DifferentProcess) {
    return;
  }

  FreeTransferStructuredCloneOp freeTransfer = nullptr;
  if (callbacks_) {
    freeTransfer = callbacks_->freeTransfer;
  }

  auto point = BufferIterator<uint64_t, SystemAllocPolicy>(*this);
  if (point.done()) {
    return;  // Empty buffer
  }

  uint32_t tag, data;
  MOZ_RELEASE_ASSERT(point.canPeek());
  SCInput::getPair(point.peek(), &tag, &data);
  MOZ_ALWAYS_TRUE(point.advance());

  if (tag == SCTAG_HEADER) {
    if (point.done()) {
      return;
    }

    MOZ_RELEASE_ASSERT(point.canPeek());
    SCInput::getPair(point.peek(), &tag, &data);
    MOZ_ALWAYS_TRUE(point.advance());
  }

  if (tag != SCTAG_TRANSFER_MAP_HEADER) {
    return;
  }

  if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) {
    return;
  }

  // freeTransfer should not GC
  JS::AutoSuppressGCAnalysis nogc;

  if (point.done()) {
    return;
  }

  MOZ_RELEASE_ASSERT(point.canPeek());
  uint64_t numTransferables = NativeEndian::swapFromLittleEndian(point.peek());
  MOZ_ALWAYS_TRUE(point.advance());
  while (numTransferables--) {
    if (!point.canPeek()) {
      return;
    }

    uint32_t ownership;
    SCInput::getPair(point.peek(), &tag, &ownership);
    MOZ_ALWAYS_TRUE(point.advance());
    MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
    if (!point.canPeek()) {
      return;
    }

    void* content;
    SCInput::getPtr(point.peek(), &content);
    MOZ_ALWAYS_TRUE(point.advance());
    if (!point.canPeek()) {
      return;
    }

    uint64_t extraData = NativeEndian::swapFromLittleEndian(point.peek());
    MOZ_ALWAYS_TRUE(point.advance());

    if (ownership < JS::SCTAG_TMO_FIRST_OWNED) {
      continue;
    }

    if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
      js_free(content);
    } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
      JS::ReleaseMappedArrayBufferContents(content, extraData);
    } else if (freeTransfer) {
      freeTransfer(tag, JS::TransferableOwnership(ownership), content,
                   extraData, closure_);
    } else {
      MOZ_ASSERT(false"unknown ownership");
    }
  }
}

static_assert(JSString::MAX_LENGTH < UINT32_MAX);

bool JSStructuredCloneWriter::parseTransferable() {
  // NOTE: The transferables set is tested for non-emptiness at various
  //       junctures in structured cloning, so this set must be initialized
  //       by this method in all non-error cases.
  MOZ_ASSERT(transferableObjects.empty(),
             "parseTransferable called with stale data");

  if (transferable.isNull() || transferable.isUndefined()) {
    return true;
  }

  if (!transferable.isObject()) {
    return reportDataCloneError(JS_SCERR_TRANSFERABLE);
  }

  JSContext* cx = context();
  RootedObject array(cx, &transferable.toObject());
  bool isArray;
  if (!JS::IsArrayObject(cx, array, &isArray)) {
    return false;
  }
  if (!isArray) {
    return reportDataCloneError(JS_SCERR_TRANSFERABLE);
  }

  uint32_t length;
  if (!JS::GetArrayLength(cx, array, &length)) {
    return false;
  }

  // Initialize the set for the provided array's length.
  if (!transferableObjects.reserve(length)) {
    return false;
  }

  if (length == 0) {
    return true;
  }

  RootedValue v(context());
  RootedObject tObj(context());

  for (uint32_t i = 0; i < length; ++i) {
    if (!CheckForInterrupt(cx)) {
      return false;
    }

    if (!JS_GetElement(cx, array, i, &v)) {
      return false;
    }

    if (!v.isObject()) {
      return reportDataCloneError(JS_SCERR_TRANSFERABLE);
    }
    tObj = &v.toObject();

    RootedObject unwrappedObj(cx, CheckedUnwrapStatic(tObj));
    if (!unwrappedObj) {
      ReportAccessDenied(cx);
      return false;
    }

    // Shared memory cannot be transferred because it is not possible (nor
    // desirable) to detach the memory in agents that already hold a
    // reference to it.

    if (unwrappedObj->is<SharedArrayBufferObject>()) {
      return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
    }

    else if (unwrappedObj->is<WasmMemoryObject>()) {
      if (unwrappedObj->as<WasmMemoryObject>().isShared()) {
        return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
      }
    }

    // External array buffers may be able to be transferred in the future,
    // but that is not currently implemented.

    else if (unwrappedObj->is<ArrayBufferObject>()) {
      if (unwrappedObj->as<ArrayBufferObject>().isExternal()) {
        return reportDataCloneError(JS_SCERR_TRANSFERABLE);
      }
    }

    else {
      if (!out.buf.callbacks_ || !out.buf.callbacks_->canTransfer) {
        return reportDataCloneError(JS_SCERR_TRANSFERABLE);
      }

      JSAutoRealm ar(cx, unwrappedObj);
      bool sameProcessScopeRequired = false;
      if (!out.buf.callbacks_->canTransfer(
              cx, unwrappedObj, &sameProcessScopeRequired, out.buf.closure_)) {
        return reportDataCloneError(JS_SCERR_TRANSFERABLE);
      }

      if (sameProcessScopeRequired) {
        output().sameProcessScopeRequired();
      }
    }

    // No duplicates allowed
    if (std::find(transferableObjects.begin(), transferableObjects.end(),
                  tObj) != transferableObjects.end()) {
      return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
    }

    if (!transferableObjects.append(tObj)) {
      return false;
    }
  }

  return true;
}

template <typename... Args>
bool JSStructuredCloneWriter::reportDataCloneError(uint32_t errorId,
                                                   Args&&... aArgs) {
  ReportDataCloneError(context(), out.buf.callbacks_, errorId, out.buf.closure_,
                       std::forward<Args>(aArgs)...);
  return false;
}

bool JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) {
  JSLinearString* linear = str->ensureLinear(context());
  if (!linear) {
    return false;
  }

#if FUZZING_JS_FUZZILLI
  if (js::SupportDifferentialTesting()) {
    // TODO we could always output a twoByteChar string
    return true;
  }
#endif

  static_assert(JSString::MAX_LENGTH < (1 << 30),
                "String length must fit in 30 bits");

  // Try to share the underlying StringBuffer without copying the contents.
  bool useBuffer = linear->hasStringBuffer() &&
                   output().scope() == JS::StructuredCloneScope::SameProcess;

  uint32_t length = linear->length();
  bool isLatin1 = linear->hasLatin1Chars();
  uint32_t lengthAndBits =
      length | (uint32_t(isLatin1) << 31) | (uint32_t(useBuffer) << 30);
  if (!out.writePair(tag, lengthAndBits)) {
    return false;
  }

  if (useBuffer) {
    mozilla::StringBuffer* buffer = linear->stringBuffer();
    if (!out.buf.stringBufferRefsHeld_.emplaceBack(buffer)) {
      ReportOutOfMemory(context());
      return false;
    }
    uintptr_t p = reinterpret_cast<uintptr_t>(buffer);
    return out.writeBytes(&p, sizeof(p));
  }

  JS::AutoCheckCannotGC nogc;
  return linear->hasLatin1Chars()
             ? out.writeChars(linear->latin1Chars(nogc), length)
             : out.writeChars(linear->twoByteChars(nogc), length);
}

bool JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) {
  bool signBit = bi->isNegative();
  size_t length = bi->digitLength();
  // The length must fit in 31 bits to leave room for a sign bit.
  if (length > size_t(INT32_MAX)) {
    return false;
  }
  uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31);

  if (!out.writePair(tag, lengthAndSign)) {
    return false;
  }
  return out.writeArray(bi->digits().data(), length);
}

inline void JSStructuredCloneWriter::checkStack() {
#ifdef DEBUG
  // To avoid making serialization O(n^2), limit stack-checking at 10.
  const size_t MAX = 10;

  size_t limit = std::min(counts.length(), MAX);
  MOZ_ASSERT(objs.length() == counts.length());
  size_t total = 0;
  for (size_t i = 0; i < limit; i++) {
    MOZ_ASSERT(total + counts[i] >= total);
    total += counts[i];
  }
  if (counts.length() <= MAX) {
    MOZ_ASSERT(total == objectEntries.length() + otherEntries.length());
  } else {
    MOZ_ASSERT(total <= objectEntries.length() + otherEntries.length());
  }

  size_t j = objs.length();
  for (size_t i = 0; i < limit; i++) {
    --j;
    MOZ_ASSERT(memory.has(&objs[j].toObject()));
  }
#endif
}

/*
 * Write out a typed array. Note that post-v1 structured clone buffers do not
 * perform endianness conversion on stored data, so multibyte typed arrays
 * cannot be deserialized into a different endianness machine. Endianness
 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
 * Int16Array views of the same ArrayBuffer, should the data bytes be
 * byte-swapped when writing or not? The Int8Array requires them to not be
 * swapped; the Int16Array requires that they are.
 */

bool JSStructuredCloneWriter::writeTypedArray(HandleObject obj) {
  Rooted<TypedArrayObject*> tarr(context(),
                                 obj->maybeUnwrapAs<TypedArrayObject>());
  JSAutoRealm ar(context(), tarr);

#ifdef FUZZING_JS_FUZZILLI
  if (js::SupportDifferentialTesting() && !tarr->hasBuffer()) {
    // fake oom because differential testing will fail
    fprintf(stderr, "[unhandlable oom]");
    _exit(-1);
    return false;
  }
#endif

  if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) {
    return false;
  }

  if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, uint32_t(tarr->type()))) {
    return false;
  }

  mozilla::Maybe<size_t> nelems = tarr->length();
  if (!nelems) {
    return reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED);
  }

  // Auto-length TypedArrays are tagged by storing `-1` for the length. We still
  // need to query the length to check for detached or out-of-bounds lengths.
  bool isAutoLength = tarr->is<ResizableTypedArrayObject>() &&
                      tarr->as<ResizableTypedArrayObject>().isAutoLength();
  uint64_t length = isAutoLength ? uint64_t(-1) : uint64_t(*nelems);
  if (!out.write(length)) {
    return false;
  }

  // Write out the ArrayBuffer tag and contents
  RootedValue val(context(), tarr->bufferValue());
  if (!startWrite(val)) {
    return false;
  }

  uint64_t byteOffset = tarr->byteOffset().valueOr(0);
  return out.write(byteOffset);
}

bool JSStructuredCloneWriter::writeDataView(HandleObject obj) {
  Rooted<DataViewObject*> view(context(), obj->maybeUnwrapAs<DataViewObject>());
  JSAutoRealm ar(context(), view);

  if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, 0)) {
    return false;
  }

  mozilla::Maybe<size_t> byteLength = view->byteLength();
  if (!byteLength) {
    return reportDataCloneError(JS_SCERR_TYPED_ARRAY_DETACHED);
  }

  // Auto-length DataViews are tagged by storing `-1` for the length. We still
  // need to query the length to check for detached or out-of-bounds lengths.
  bool isAutoLength = view->is<ResizableDataViewObject>() &&
                      view->as<ResizableDataViewObject>().isAutoLength();
  uint64_t length = isAutoLength ? uint64_t(-1) : uint64_t(*byteLength);
  if (!out.write(length)) {
    return false;
  }

  // Write out the ArrayBuffer tag and contents
  RootedValue val(context(), view->bufferValue());
  if (!startWrite(val)) {
    return false;
  }

  uint64_t byteOffset = view->byteOffset().valueOr(0);
  return out.write(byteOffset);
}

bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) {
  Rooted<ArrayBufferObject*> buffer(context(),
                                    obj->maybeUnwrapAs<ArrayBufferObject>());
  JSAutoRealm ar(context(), buffer);

  StructuredDataType type = !buffer->isResizable()
                                ? SCTAG_ARRAY_BUFFER_OBJECT
                                : SCTAG_RESIZABLE_ARRAY_BUFFER_OBJECT;

  if (!out.writePair(type, 0)) {
    return false;
  }

  uint64_t byteLength = buffer->byteLength();
  if (!out.write(byteLength)) {
    return false;
  }

  if (buffer->isResizable()) {
    uint64_t maxByteLength =
        buffer->as<ResizableArrayBufferObject>().maxByteLength();
    if (!out.write(maxByteLength)) {
      return false;
    }
  }

  return out.writeBytes(buffer->dataPointer(), byteLength);
}

bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj) {
  MOZ_ASSERT(obj->canUnwrapAs<SharedArrayBufferObject>());

  if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
    auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
                     ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
                     : JS_SCERR_NOT_CLONABLE;
    reportDataCloneError(error, "SharedArrayBuffer");
    return false;
  }

  output().sameProcessScopeRequired();

  // We must not transmit SAB pointers (including for WebAssembly.Memory)
  // cross-process.  The cloneDataPolicy should have guarded against this;
  // since it did not then throw, with a very explicit message.

  if (output().scope() > JS::StructuredCloneScope::SameProcess) {
    JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
                              JSMSG_SC_SHMEM_POLICY);
    return false;
  }

  Rooted<SharedArrayBufferObject*> sharedArrayBuffer(
      context(), obj->maybeUnwrapAs<SharedArrayBufferObject>());
  SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();

  if (!out.buf.refsHeld_.acquire(context(), rawbuf)) {
    return false;
  }

  // We must serialize the length so that the buffer object arrives in the
  // receiver with the same length, and not with the length read from the
  // rawbuf - that length can be different, and it can change at any time.

  StructuredDataType type = !sharedArrayBuffer->isGrowable()
                                ? SCTAG_SHARED_ARRAY_BUFFER_OBJECT
                                : SCTAG_GROWABLE_SHARED_ARRAY_BUFFER_OBJECT;

  intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
  uint64_t byteLength = sharedArrayBuffer->byteLengthOrMaxByteLength();
  if (!(out.writePair(type, /* unused data word */ 0) &&
        out.writeBytes(&byteLength, sizeof(byteLength)) &&
        out.writeBytes(&p, sizeof(p)))) {
    return false;
  }

  if (callbacks && callbacks->sabCloned &&
      !callbacks->sabCloned(context(), /*receiving=*/false, closure)) {
    return false;
  }

  return true;
}

bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj) {
  MOZ_ASSERT(obj->canUnwrapAs<WasmMemoryObject>());

  // Check the policy here so that we can report a sane error.
  if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
    auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
                     ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
                     : JS_SCERR_NOT_CLONABLE;
    reportDataCloneError(error, "WebAssembly.Memory");
    return false;
  }

  // If this changes, might need to change what we write.
  MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS == 3);

  Rooted<WasmMemoryObject*> memoryObj(context(),
                                      &obj->unwrapAs<WasmMemoryObject>());
  Rooted<SharedArrayBufferObject*> sab(
      context(), &memoryObj->buffer().as<SharedArrayBufferObject>());

  return out.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT, 0) &&
         out.writePair(SCTAG_BOOLEAN, memoryObj->isHuge()) &&
         writeSharedArrayBuffer(sab);
}

bool JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref) {
  // Handle cycles in the object graph.
  CloneMemory::AddPtr p = memory.lookupForAdd(obj);
  if ((*backref = p.found())) {
    return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
  }
  if (!memory.add(p, obj, memory.count())) {
    ReportOutOfMemory(context());
    return false;
  }

  if (memory.count() == UINT32_MAX) {
    JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
                              JSMSG_NEED_DIET, "object graph to serialize");
    return false;
  }

  return true;
}

static bool TryAppendNativeProperties(JSContext* cx, HandleObject obj,
                                      MutableHandleIdVector entries,
                                      size_t* properties, bool* optimized) {
  *optimized = false;

  if (!obj->is<NativeObject>()) {
    return true;
  }

  Handle<NativeObject*> nobj = obj.as<NativeObject>();
  if (nobj->isIndexed() || nobj->is<TypedArrayObject>() ||
      nobj->getClass()->getNewEnumerate() || nobj->getClass()->getEnumerate()) {
    return true;
  }

  *optimized = true;

  size_t count = 0;
  // We iterate from the last to the first property, so the property names
  // are already in reverse order.
  for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
    jsid id = iter->key();

    // Ignore symbols and non-enumerable properties.
    if (!iter->enumerable() || id.isSymbol()) {
      continue;
    }

    MOZ_ASSERT(id.isString());
    if (!entries.append(id)) {
      return false;
    }

    count++;
  }

  // Add dense element ids in reverse order.
  for (uint32_t i = nobj->getDenseInitializedLength(); i > 0; --i) {
    if (nobj->getDenseElement(i - 1).isMagic(JS_ELEMENTS_HOLE)) {
      continue;
    }

    if (!entries.append(PropertyKey::Int(i - 1))) {
      return false;
    }

    count++;
  }

  *properties = count;
  return true;
}

// Objects are written as a "preorder" traversal of the object graph: object
// "headers" (the class tag and any data needed for initial construction) are
// visited first, then the children are recursed through (where children are
// properties, Set or Map entries, etc.). So for example
//
//     obj1 = { key1: { key1.1: val1.1, key1.2: val1.2 }, key2: {} }
//
// would be stored as:
//
//     <Object tag for obj1>
//       <key1 data>
//       <Object tag for key1's value>
//         <key1.1 data>
//         <val1.1 data>
//         <key1.2 data>
//         <val1.2 data>
//       <end-of-children marker for key1's value>
//       <key2 data>
//       <Object tag for key2's value>
//       <end-of-children marker for key2's value>
//     <end-of-children marker for obj1>
//
// This nests nicely (ie, an entire recursive value starts with its tag and
// ends with its end-of-children marker) and so it can be presented indented.
// But see traverseMap below for how this looks different for Maps.
bool JSStructuredCloneWriter::traverseObject(HandleObject obj, ESClass cls) {
  size_t count;
  bool optimized = false;
  if (!js::SupportDifferentialTesting()) {
    if (!TryAppendNativeProperties(context(), obj, &objectEntries, &count,
                                   &optimized)) {
      return false;
    }
  }

  if (!optimized) {
    // Get enumerable property ids and put them in reverse order so that they
    // will come off the stack in forward order.
    RootedIdVector properties(context());
    if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties)) {
      return false;
    }

    for (size_t i = properties.length(); i > 0; --i) {
      jsid id = properties[i - 1];

      MOZ_ASSERT(id.isString() || id.isInt());
      if (!objectEntries.append(id)) {
        return false;
      }
    }

    count = properties.length();
  }

  // Push obj and count to the stack.
  if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) {
    return false;
  }

  checkStack();

#if DEBUG
  ESClass cls2;
  if (!GetBuiltinClass(context(), obj, &cls2)) {
    return false;
  }
  MOZ_ASSERT(cls2 == cls);
#endif

  // Write the header for obj.
  if (cls == ESClass::Array) {
    uint32_t length = 0;
    if (!JS::GetArrayLength(context(), obj, &length)) {
      return false;
    }

    return out.writePair(SCTAG_ARRAY_OBJECT,
                         NativeEndian::swapToLittleEndian(length));
  }

  return out.writePair(SCTAG_OBJECT_OBJECT, 0);
}

// Use the same basic setup as for traverseObject, but now keys can themselves
// be complex objects. Keys and values are visited first via startWrite(), then
// the key's children (if any) are handled, then the value's children.
//
//     m = new Map();
//     m.set(key1 = ..., value1 = ...)
//
// where key1 and value2 are both objects would be stored as
//
//     <Map tag>
//     <key1 class tag>
//     <value1 class tag>
//     ...key1 fields...
//     <end-of-children marker for key1>
//     ...value1 fields...
//     <end-of-children marker for value1>
//     <end-of-children marker for Map>
//
// Notice how the end-of-children marker for key1 is sandwiched between the
// value1 beginning and end.
bool JSStructuredCloneWriter::traverseMap(HandleObject obj) {
  Rooted<GCVector<Value>> newEntries(context(), GCVector<Value>(context()));
  {
    // If there is no wrapper, the compartment munging is a no-op.
    Rooted<MapObject*> unwrapped(context(), obj->maybeUnwrapAs<MapObject>());
    MOZ_ASSERT(unwrapped);
    JSAutoRealm ar(context(), unwrapped);
    if (!unwrapped->getKeysAndValuesInterleaved(&newEntries)) {
      return false;
    }
  }
  if (!context()->compartment()->wrap(context(), &newEntries)) {
    return false;
  }

  for (size_t i = newEntries.length(); i > 0; --i) {
    if (!otherEntries.append(newEntries[i - 1])) {
      return false;
    }
  }

  // Push obj and count to the stack.
  if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length())) {
    return false;
  }

  checkStack();

  // Write the header for obj.
  return out.writePair(SCTAG_MAP_OBJECT, 0);
}

// Similar to traverseMap, only there is a single value instead of a key and
// value, and thus no interleaving is possible: a value will be fully emitted
// before the next value is begun.
bool JSStructuredCloneWriter::traverseSet(HandleObject obj) {
  Rooted<GCVector<Value>> keys(context(), GCVector<Value>(context()));
  {
    // If there is no wrapper, the compartment munging is a no-op.
    Rooted<SetObject*> unwrapped(context(), obj->maybeUnwrapAs<SetObject>());
    MOZ_ASSERT(unwrapped);
    JSAutoRealm ar(context(), unwrapped);
    if (!unwrapped->keys(&keys)) {
      return false;
    }
  }
  if (!context()->compartment()->wrap(context(), &keys)) {
    return false;
  }

  for (size_t i = keys.length(); i > 0; --i) {
    if (!otherEntries.append(keys[i - 1])) {
      return false;
    }
  }

  // Push obj and count to the stack.
  if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length())) {
    return false;
  }

  checkStack();

  // Write the header for obj.
  return out.writePair(SCTAG_SET_OBJECT, 0);
}

bool JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj) {
  Rooted<SavedFrame*> savedFrame(context(), obj->maybeUnwrapAs<SavedFrame>());
  MOZ_ASSERT(savedFrame);

  RootedObject parent(context(), savedFrame->getParent());
  if (!context()->compartment()->wrap(context(), &parent)) {
    return false;
  }

  if (!objs.append(ObjectValue(*obj)) ||
      !otherEntries.append(parent ? ObjectValue(*parent) : NullValue()) ||
      !counts.append(1)) {
    return false;
  }

  checkStack();

  // Write the SavedFrame tag and the SavedFrame's principals.

  if (savedFrame->getPrincipals() ==
      &ReconstructedSavedFramePrincipals::IsSystem) {
    if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
                       SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM)) {
      return false;
    };
  } else if (savedFrame->getPrincipals() ==
             &ReconstructedSavedFramePrincipals::IsNotSystem) {
    if (!out.writePair(
            SCTAG_SAVED_FRAME_OBJECT,
            SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM)) {
      return false;
    }
  } else {
    if (auto principals = savedFrame->getPrincipals()) {
      if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
          !principals->write(context(), this)) {
        return false;
      }
    } else {
      if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS)) {
        return false;
      }
    }
  }

  // Write the SavedFrame's reserved slots, except for the parent, which is
  // queued on objs for further traversal.

  RootedValue val(context());

  val = BooleanValue(savedFrame->getMutedErrors());
  if (!writePrimitive(val)) {
    return false;
  }

  context()->markAtom(savedFrame->getSource());
  val = StringValue(savedFrame->getSource());
  if (!writePrimitive(val)) {
    return false;
  }

  val = NumberValue(savedFrame->getLine());
  if (!writePrimitive(val)) {
    return false;
  }

  val = NumberValue(*savedFrame->getColumn().addressOfValueForTranscode());
  if (!writePrimitive(val)) {
    return false;
  }

  auto name = savedFrame->getFunctionDisplayName();
  if (name) {
    context()->markAtom(name);
  }
  val = name ? StringValue(name) : NullValue();
  if (!writePrimitive(val)) {
    return false;
  }

  auto cause = savedFrame->getAsyncCause();
  if (cause) {
    context()->markAtom(cause);
  }
  val = cause ? StringValue(cause) : NullValue();
  if (!writePrimitive(val)) {
    return false;
  }

  return true;
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
// 2.7.3 StructuredSerializeInternal ( value, forStorage [ , memory ] )
//
// Step 17. Otherwise, if value has an [[ErrorData]] internal slot and
//          value is not a platform object, then:
//
// Note: This contains custom extensions for handling non-standard properties.
bool JSStructuredCloneWriter::traverseError(HandleObject obj) {
  JSContext* cx = context();

  // 1. Let name be ? Get(value, "name").
  RootedValue name(cx);
  if (!GetProperty(cx, obj, obj, cx->names().name, &name)) {
    return false;
  }

  // 2. If name is not one of "Error", "EvalError", "RangeError",
  // "ReferenceError", "SyntaxError", "TypeError", or "URIError",
  // (not yet specified: or "AggregateError")
  // then set name to "Error".
  JSExnType type = JSEXN_ERR;
  if (name.isString()) {
    JSLinearString* linear = name.toString()->ensureLinear(cx);
    if (!linear) {
      return false;
    }

    if (EqualStrings(linear, cx->names().Error)) {
      type = JSEXN_ERR;
    } else if (EqualStrings(linear, cx->names().EvalError)) {
      type = JSEXN_EVALERR;
    } else if (EqualStrings(linear, cx->names().RangeError)) {
      type = JSEXN_RANGEERR;
    } else if (EqualStrings(linear, cx->names().ReferenceError)) {
      type = JSEXN_REFERENCEERR;
    } else if (EqualStrings(linear, cx->names().SyntaxError)) {
      type = JSEXN_SYNTAXERR;
    } else if (EqualStrings(linear, cx->names().TypeError)) {
      type = JSEXN_TYPEERR;
    } else if (EqualStrings(linear, cx->names().URIError)) {
      type = JSEXN_URIERR;
    } else if (EqualStrings(linear, cx->names().AggregateError)) {
      type = JSEXN_AGGREGATEERR;
    }
  }

  // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
  RootedId messageId(cx, NameToId(cx->names().message));
  Rooted<Maybe<PropertyDescriptor>> messageDesc(cx);
  if (!GetOwnPropertyDescriptor(cx, obj, messageId, &messageDesc)) {
    return false;
  }

  // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false,
  //    and ? ToString(valueMessageDesc.[[Value]]) otherwise.
  RootedString message(cx);
  if (messageDesc.isSome() && messageDesc->isDataDescriptor()) {
    RootedValue messageVal(cx, messageDesc->value());
    message = ToString<CanGC>(cx, messageVal);
    if (!message) {
      return false;
    }
  }

  // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]:
  // message }.

  if (!objs.append(ObjectValue(*obj))) {
    return false;
  }

  Rooted<ErrorObject*> unwrapped(cx, obj->maybeUnwrapAs<ErrorObject>());
  MOZ_ASSERT(unwrapped);

  // Non-standard: Serialize |stack|.
  // The Error stack property is saved as SavedFrames.
  RootedValue stack(cx, NullValue());
  RootedObject stackObj(cx, unwrapped->stack());
  if (stackObj) {
    MOZ_ASSERT(stackObj->canUnwrapAs<SavedFrame>());
    stack.setObject(*stackObj);
    if (!cx->compartment()->wrap(cx, &stack)) {
      return false;
    }
  }
  if (!otherEntries.append(stack)) {
    return false;
  }

  // Serialize |errors|
  if (type == JSEXN_AGGREGATEERR) {
    RootedValue errors(cx);
    if (!GetProperty(cx, obj, obj, cx->names().errors, &errors)) {
      return false;
    }
    if (!otherEntries.append(errors)) {
      return false;
    }
  } else {
    if (!otherEntries.append(NullValue())) {
      return false;
    }
  }

  // Non-standard: Serialize |cause|. Because this property
  // might be missing we also write "hasCause" later.
  RootedId causeId(cx, NameToId(cx->names().cause));
  Rooted<Maybe<PropertyDescriptor>> causeDesc(cx);
  if (!GetOwnPropertyDescriptor(cx, obj, causeId, &causeDesc)) {
    return false;
  }

  Rooted<Maybe<Value>> cause(cx);
  if (causeDesc.isSome() && causeDesc->isDataDescriptor()) {
    cause = mozilla::Some(causeDesc->value());
  }
  if (!cx->compartment()->wrap(cx, &cause)) {
    return false;
  }
  if (!otherEntries.append(cause.get().valueOr(NullValue()))) {
    return false;
  }

  // |cause| + |errors| + |stack|, pushed in reverse order
  if (!counts.append(3)) {
    return false;
  }

  checkStack();

  if (!out.writePair(SCTAG_ERROR_OBJECT, type)) {
    return false;
  }

  RootedValue val(cx, message ? StringValue(message) : NullValue());
  if (!writePrimitive(val)) {
    return false;
  }

  // hasCause
  val = BooleanValue(cause.isSome());
  if (!writePrimitive(val)) {
    return false;
  }

  // Non-standard: Also serialize fileName, lineNumber and columnNumber.
  {
    JSAutoRealm ar(cx, unwrapped);
    val = StringValue(unwrapped->fileName(cx));
  }
  if (!cx->compartment()->wrap(cx, &val) || !writePrimitive(val)) {
    return false;
  }

  val = Int32Value(unwrapped->lineNumber());
  if (!writePrimitive(val)) {
    return false;
  }

  val = Int32Value(*unwrapped->columnNumber().addressOfValueForTranscode());
  return writePrimitive(val);
}

bool JSStructuredCloneWriter::writePrimitive(HandleValue v) {
  MOZ_ASSERT(v.isPrimitive());
  context()->check(v);

  if (v.isString()) {
    return writeString(SCTAG_STRING, v.toString());
  } else if (v.isInt32()) {
    if (js::SupportDifferentialTesting()) {
      return out.writeDouble(v.toInt32());
    }
    return out.writePair(SCTAG_INT32, v.toInt32());
  } else if (v.isDouble()) {
    return out.writeDouble(v.toDouble());
  } else if (v.isBoolean()) {
    return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
  } else if (v.isNull()) {
    return out.writePair(SCTAG_NULL, 0);
  } else if (v.isUndefined()) {
    return out.writePair(SCTAG_UNDEFINED, 0);
  } else if (v.isBigInt()) {
    return writeBigInt(SCTAG_BIGINT, v.toBigInt());
  }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=96 G=93

¤ Dauer der Verarbeitung: 0.53 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge