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

#include "vm/ArrayBufferObject-inl.h"
#include "vm/ArrayBufferObject.h"

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/TaggedAnonymousMemory.h"

#include <algorithm>  // std::max, std::min
#include <memory>     // std::uninitialized_copy_n
#include <string.h>
#if !defined(XP_WIN) && !defined(__wasi__)
#  include <sys/mman.h>
#endif
#include <tuple>  // std::tuple
#include <type_traits>
#ifdef MOZ_VALGRIND
#  include <valgrind/memcheck.h>
#endif

#include "jsnum.h"
#include "jstypes.h"

#include "gc/Barrier.h"
#include "gc/Memory.h"
#include "js/ArrayBuffer.h"
#include "js/Conversions.h"
#include "js/experimental/TypedData.h"  // JS_IsArrayBufferViewObject
#include "js/friend/ErrorMessages.h"    // js::GetErrorMessage, JSMSG_*
#include "js/MemoryMetrics.h"
#include "js/Prefs.h"
#include "js/PropertySpec.h"
#include "js/SharedArrayBuffer.h"
#include "js/Wrapper.h"
#include "util/WindowsWrapper.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/SelfHosting.h"
#include "vm/SharedArrayObject.h"
#include "vm/Warnings.h"  // js::WarnNumberASCII
#include "wasm/WasmConstants.h"
#include "wasm/WasmLog.h"
#include "wasm/WasmMemory.h"
#include "wasm/WasmModuleTypes.h"
#include "wasm/WasmProcess.h"

#include "gc/GCContext-inl.h"
#include "gc/Marking-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/Realm-inl.h"  // js::AutoRealm

using js::wasm::AddressType;
using js::wasm::Pages;
using mozilla::Atomic;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::Some;

using namespace js;

// Wasm allows large amounts of memory to be reserved at a time. On 64-bit
// platforms (with "huge memories") we reserve around 4GB of virtual address
// space for every wasm memory; on 32-bit platforms we usually do not, but users
// often initialize memories in the hundreds of megabytes.
//
// If too many wasm memories remain live, we run up against system resource
// exhaustion (address space or number of memory map descriptors) - see bug
// 1068684, bug 1073934, bug 1517412, bug 1502733 for details. The limiting case
// seems to be Android on ARM64, where the per-process address space is limited
// to 4TB (39 bits) by the organization of the page tables. An earlier problem
// was Windows Vista Home 64-bit, where the per-process address space is limited
// to 8TB (40 bits). And 32-bit platforms only have 4GB of address space anyway.
//
// Thus we track the amount of memory reserved for wasm, and set a limit per
// process. We trigger GC work when we approach the limit and we throw an OOM
// error if the per-process limit is exceeded. The limit (WasmReservedBytesMax)
// is specific to architecture, OS, and OS configuration.
//
// Since the WasmReservedBytesMax limit is not generally accounted for by
// any existing GC-trigger heuristics, we need an extra heuristic for triggering
// GCs when the caller is allocating memories rapidly without other garbage
// (e.g. bug 1773225). Thus, once the reserved memory crosses the threshold
// WasmReservedBytesStartTriggering, we start triggering GCs every
// WasmReservedBytesPerTrigger bytes. Once we reach
// WasmReservedBytesStartSyncFullGC bytes reserved, we perform expensive
// non-incremental full GCs as a last-ditch effort to avoid unnecessary failure.
// Once we reach WasmReservedBytesMax, we perform further full GCs before giving
// up.
//
// (History: The original implementation only tracked the number of "huge
// memories" allocated by WASM, but this was found to be insufficient because
// 32-bit platforms have similar resource exhaustion issues. We now track
// reserved bytes directly.)
//
// (We also used to reserve significantly more than 4GB for huge memories, but
// this was reduced in bug 1442544.)

// ASAN and TSAN use a ton of vmem for bookkeeping leaving a lot less for the
// program so use a lower limit.
#if defined(MOZ_TSAN) || defined(MOZ_ASAN)
static const uint64_t WasmMemAsanOverhead = 2;
#else
static const uint64_t WasmMemAsanOverhead = 1;
#endif

// WasmReservedStartTriggering + WasmReservedPerTrigger must be well below
// WasmReservedStartSyncFullGC in order to provide enough time for incremental
// GC to do its job.

#if defined(JS_CODEGEN_ARM64) && defined(ANDROID)

static const uint64_t WasmReservedBytesMax =
    75 * wasm::HugeMappedSize / WasmMemAsanOverhead;
static const uint64_t WasmReservedBytesStartTriggering =
    15 * wasm::HugeMappedSize;
static const uint64_t WasmReservedBytesStartSyncFullGC =
    WasmReservedBytesMax - 15 * wasm::HugeMappedSize;
static const uint64_t WasmReservedBytesPerTrigger = 15 * wasm::HugeMappedSize;

#elif defined(WASM_SUPPORTS_HUGE_MEMORY)

static const uint64_t WasmReservedBytesMax =
    1000 * wasm::HugeMappedSize / WasmMemAsanOverhead;
static const uint64_t WasmReservedBytesStartTriggering =
    100 * wasm::HugeMappedSize;
static const uint64_t WasmReservedBytesStartSyncFullGC =
    WasmReservedBytesMax - 100 * wasm::HugeMappedSize;
static const uint64_t WasmReservedBytesPerTrigger = 100 * wasm::HugeMappedSize;

#else  // 32-bit (and weird 64-bit platforms without huge memory)

static const uint64_t GiB = 1024 * 1024 * 1024;

static const uint64_t WasmReservedBytesMax =
    (4 * GiB) / 2 / WasmMemAsanOverhead;
static const uint64_t WasmReservedBytesStartTriggering = (4 * GiB) / 8;
static const uint64_t WasmReservedBytesStartSyncFullGC =
    WasmReservedBytesMax - (4 * GiB) / 8;
static const uint64_t WasmReservedBytesPerTrigger = (4 * GiB) / 8;

#endif

// The total number of bytes reserved for wasm memories.
static Atomic<uint64_t, mozilla::ReleaseAcquire> wasmReservedBytes(0);
// The number of bytes of wasm memory reserved since the last GC trigger.
static Atomic<uint64_t, mozilla::ReleaseAcquire> wasmReservedBytesSinceLast(0);

uint64_t js::WasmReservedBytes() { return wasmReservedBytes; }

[[nodiscard]] static bool CheckArrayBufferTooLarge(JSContext* cx,
                                                   uint64_t nbytes) {
  // Refuse to allocate too large buffers.
  if (MOZ_UNLIKELY(nbytes > ArrayBufferObject::ByteLengthLimit)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BAD_ARRAY_LENGTH);
    return false;
  }

  return true;
}

void* js::MapBufferMemory(wasm::AddressType t, size_t mappedSize,
                          size_t initialCommittedSize) {
  MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
  MOZ_ASSERT(initialCommittedSize % gc::SystemPageSize() == 0);
  MOZ_ASSERT(initialCommittedSize <= mappedSize);

  auto failed = mozilla::MakeScopeExit(
      [&] { wasmReservedBytes -= uint64_t(mappedSize); });
  wasmReservedBytes += uint64_t(mappedSize);

  // Test >= to guard against the case where multiple extant runtimes
  // race to allocate.
  if (wasmReservedBytes >= WasmReservedBytesMax) {
    if (OnLargeAllocationFailure) {
      OnLargeAllocationFailure();
    }
    if (wasmReservedBytes >= WasmReservedBytesMax) {
      return nullptr;
    }
  }

#ifdef XP_WIN
  void* data = VirtualAlloc(nullptr, mappedSize, MEM_RESERVE, PAGE_NOACCESS);
  if (!data) {
    return nullptr;
  }

  if (!VirtualAlloc(data, initialCommittedSize, MEM_COMMIT, PAGE_READWRITE)) {
    VirtualFree(data, 0, MEM_RELEASE);
    return nullptr;
  }

  gc::RecordMemoryAlloc(initialCommittedSize);
#elif defined(__wasi__)
  void* data = nullptr;
  if (int err = posix_memalign(&data, gc::SystemPageSize(), mappedSize)) {
    MOZ_ASSERT(err == ENOMEM);
    (void)err;
    return nullptr;
  }
  MOZ_ASSERT(data);
  memset(data, 0, mappedSize);
#else   // !XP_WIN && !__wasi__
  void* data =
      MozTaggedAnonymousMmap(nullptr, mappedSize, PROT_NONE,
                             MAP_PRIVATE | MAP_ANON, -1, 0, "wasm-reserved");
  if (data == MAP_FAILED) {
    return nullptr;
  }

  // Note we will waste a page on zero-sized memories here
  if (mprotect(data, initialCommittedSize, PROT_READ | PROT_WRITE)) {
    munmap(data, mappedSize);
    return nullptr;
  }

  gc::RecordMemoryAlloc(initialCommittedSize);
#endif  // !XP_WIN && !__wasi__

#if defined(MOZ_VALGRIND) && \
    defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
  VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(
      (unsigned char*)data + initialCommittedSize,
      mappedSize - initialCommittedSize);
#endif

  failed.release();
  return data;
}

bool js::CommitBufferMemory(void* dataEnd, size_t delta) {
  MOZ_ASSERT(delta);
  MOZ_ASSERT(delta % gc::SystemPageSize() == 0);

#ifdef XP_WIN
  if (!VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE)) {
    return false;
  }
#elif defined(__wasi__)
  // posix_memalign'd memory is already committed
  return true;
#else
  if (mprotect(dataEnd, delta, PROT_READ | PROT_WRITE)) {
    return false;
  }
#endif  // XP_WIN

  gc::RecordMemoryAlloc(delta);

#if defined(MOZ_VALGRIND) && \
    defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
  VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
#endif

  return true;
}

void js::UnmapBufferMemory(wasm::AddressType t, void* base, size_t mappedSize,
                           size_t committedSize) {
  MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
  MOZ_ASSERT(committedSize % gc::SystemPageSize() == 0);

#ifdef XP_WIN
  VirtualFree(base, 0, MEM_RELEASE);
  gc::RecordMemoryFree(committedSize);
#elif defined(__wasi__)
  free(base);
  (void)committedSize;
#else
  munmap(base, mappedSize);
  gc::RecordMemoryFree(committedSize);
#endif  // XP_WIN

#if defined(MOZ_VALGRIND) && \
    defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
  VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)base,
                                                mappedSize);
#endif

  // Untrack reserved memory *after* releasing memory -- otherwise, a race
  // condition could enable the creation of unlimited buffers.
  wasmReservedBytes -= uint64_t(mappedSize);
}

/*
 * ArrayBufferObject
 *
 * This class holds the underlying raw buffer that the TypedArrayObject classes
 * access.  It can be created explicitly and passed to a TypedArrayObject, or
 * can be created implicitly by constructing a TypedArrayObject with a size.
 */

/*
 * ArrayBufferObject (base)
 */

static const JSClassOps ArrayBufferObjectClassOps = {
    nullptr,                      // addProperty
    nullptr,                      // delProperty
    nullptr,                      // enumerate
    nullptr,                      // newEnumerate
    nullptr,                      // resolve
    nullptr,                      // mayResolve
    ArrayBufferObject::finalize,  // finalize
    nullptr,                      // call
    nullptr,                      // construct
    nullptr,                      // trace
};

static const JSFunctionSpec arraybuffer_functions[] = {
    JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0),
    JS_FS_END,
};

static const JSPropertySpec arraybuffer_properties[] = {
    JS_SELF_HOSTED_SYM_GET(species, "$ArrayBufferSpecies", 0),
    JS_PS_END,
};

static const JSFunctionSpec arraybuffer_proto_functions[] = {
    JS_FN("slice", ArrayBufferObject::slice, 2, 0),
    JS_FN("resize", ArrayBufferObject::resize, 1, 0),
    JS_FN("transfer", ArrayBufferObject::transfer, 0, 0),
    JS_FN("transferToFixedLength", ArrayBufferObject::transferToFixedLength, 0,
          0),
    JS_FS_END,
};

static const JSPropertySpec arraybuffer_proto_properties[] = {
    JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
    JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0),
    JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0),
    JS_PSG("detached", ArrayBufferObject::detachedGetter, 0),
    JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
    JS_PS_END,
};

static JSObject* CreateArrayBufferPrototype(JSContext* cx, JSProtoKey key) {
  return GlobalObject::createBlankPrototype(cx, cx->global(),
                                            &ArrayBufferObject::protoClass_);
}

static const ClassSpec ArrayBufferObjectClassSpec = {
    GenericCreateConstructor<ArrayBufferObject::class_constructor, 1,
                             gc::AllocKind::FUNCTION>,
    CreateArrayBufferPrototype,
    arraybuffer_functions,
    arraybuffer_properties,
    arraybuffer_proto_functions,
    arraybuffer_proto_properties,
    GenericFinishInit<WhichHasFuseProperty::ProtoAndCtor>,
};

static const ClassExtension FixedLengthArrayBufferObjectClassExtension = {
    ArrayBufferObject::objectMoved<
        FixedLengthArrayBufferObject>,  // objectMovedOp
};

static const ClassExtension ResizableArrayBufferObjectClassExtension = {
    ArrayBufferObject::objectMoved<
        ResizableArrayBufferObject>,  // objectMovedOp
};

const JSClass ArrayBufferObject::protoClass_ = {
    "ArrayBuffer.prototype",
    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer),
    JS_NULL_CLASS_OPS,
    &ArrayBufferObjectClassSpec,
};

const JSClass FixedLengthArrayBufferObject::class_ = {
    "ArrayBuffer",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
        JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
        JSCLASS_BACKGROUND_FINALIZE,
    &ArrayBufferObjectClassOps,
    &ArrayBufferObjectClassSpec,
    &FixedLengthArrayBufferObjectClassExtension,
};

const JSClass ResizableArrayBufferObject::class_ = {
    "ArrayBuffer",
    JSCLASS_DELAY_METADATA_BUILDER |
        JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
        JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
        JSCLASS_BACKGROUND_FINALIZE,
    &ArrayBufferObjectClassOps,
    &ArrayBufferObjectClassSpec,
    &ResizableArrayBufferObjectClassExtension,
};

static bool IsArrayBuffer(HandleValue v) {
  return v.isObject() && v.toObject().is<ArrayBufferObject>();
}

static bool IsResizableArrayBuffer(HandleValue v) {
  return v.isObject() && v.toObject().is<ResizableArrayBufferObject>();
}

MOZ_ALWAYS_INLINE bool ArrayBufferObject::byteLengthGetterImpl(
    JSContext* cx, const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));
  auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
  args.rval().setNumber(buffer->byteLength());
  return true;
}

bool ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc,
                                         Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
}

enum class PreserveResizability : bool { No, Yes };

/**
 * ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability )
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffercopyanddetach
 */
static ArrayBufferObject* ArrayBufferCopyAndDetach(
    JSContext* cx, Handle<ArrayBufferObject*> arrayBuffer,
    Handle<Value> newLength, PreserveResizability preserveResizability) {
  // Steps 1-2. (Not applicable in our implementation.)

  // Steps 3-4.
  uint64_t newByteLength;
  if (newLength.isUndefined()) {
    // Step 3.a.
    newByteLength = arrayBuffer->byteLength();
  } else {
    // Step 4.a.
    if (!ToIndex(cx, newLength, &newByteLength)) {
      return nullptr;
    }
  }

  // Step 5.
  if (arrayBuffer->isDetached()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return nullptr;
  }
  if (arrayBuffer->isLengthPinned()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_PINNED);
    return nullptr;
  }

  // Steps 6-7.
  mozilla::Maybe<size_t> maxByteLength;
  if (preserveResizability == PreserveResizability::Yes &&
      arrayBuffer->isResizable()) {
    auto* resizableBuffer = &arrayBuffer->as<ResizableArrayBufferObject>();
    maxByteLength = mozilla::Some(resizableBuffer->maxByteLength());
  }

  // Step 8.
  if (arrayBuffer->hasDefinedDetachKey()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_NO_TRANSFER);
    return nullptr;
  }

  // Steps 9-16.
  //
  // 25.1.2.1 AllocateArrayBuffer, step 2.
  // 6.2.9.1 CreateByteDataBlock, step 2.
  if (!CheckArrayBufferTooLarge(cx, newByteLength)) {
    return nullptr;
  }

  if (maxByteLength) {
    if (size_t(newByteLength) > *maxByteLength) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
      return nullptr;
    }

    Rooted<ResizableArrayBufferObject*> resizableBuffer(
        cx, &arrayBuffer->as<ResizableArrayBufferObject>());
    return ResizableArrayBufferObject::copyAndDetach(cx, size_t(newByteLength),
                                                     resizableBuffer);
  }

  return ArrayBufferObject::copyAndDetach(cx, size_t(newByteLength),
                                          arrayBuffer);
}

/**
 * get ArrayBuffer.prototype.maxByteLength
 *
 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
 */
bool ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx,
                                                const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));

  auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();

  // Steps 4-6.
  size_t maxByteLength = buffer->maxByteLength();
  MOZ_ASSERT_IF(buffer->isDetached(), maxByteLength == 0);

  // Step 7.
  args.rval().setNumber(maxByteLength);
  return true;
}

/**
 * get ArrayBuffer.prototype.maxByteLength
 *
 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
 */
bool ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc,
                                            Value* vp) {
  // Steps 1-3.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args);
}

/**
 * get ArrayBuffer.prototype.resizable
 *
 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
 */
bool ArrayBufferObject::resizableGetterImpl(JSContext* cx,
                                            const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));

  // Step 4.
  auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
  args.rval().setBoolean(buffer->isResizable());
  return true;
}

/**
 * get ArrayBuffer.prototype.resizable
 *
 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
 */
bool ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc,
                                        Value* vp) {
  // Steps 1-3.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args);
}

/**
 * get ArrayBuffer.prototype.detached
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
 */
bool ArrayBufferObject::detachedGetterImpl(JSContext* cx,
                                           const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));

  // Step 4.
  auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
  args.rval().setBoolean(buffer->isDetached());
  return true;
}

/**
 * get ArrayBuffer.prototype.detached
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
 */
bool ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc,
                                       Value* vp) {
  // Steps 1-3.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args);
}

/**
 * ArrayBuffer.prototype.transfer ( [ newLength ] )
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
 */
bool ArrayBufferObject::transferImpl(JSContext* cx, const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));

  // Steps 1-2.
  Rooted<ArrayBufferObject*> buffer(
      cx, &args.thisv().toObject().as<ArrayBufferObject>());
  auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0),
                                             PreserveResizability::Yes);
  if (!newBuffer) {
    return false;
  }

  args.rval().setObject(*newBuffer);
  return true;
}

/**
 * ArrayBuffer.prototype.transfer ( [ newLength ] )
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
 */
bool ArrayBufferObject::transfer(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, transferImpl>(cx, args);
}

/**
 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
 */
bool ArrayBufferObject::transferToFixedLengthImpl(JSContext* cx,
                                                  const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));

  // Steps 1-2.
  Rooted<ArrayBufferObject*> buffer(
      cx, &args.thisv().toObject().as<ArrayBufferObject>());
  auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0),
                                             PreserveResizability::No);
  if (!newBuffer) {
    return false;
  }

  args.rval().setObject(*newBuffer);
  return true;
}

/**
 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )
 *
 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
 */
bool ArrayBufferObject::transferToFixedLength(JSContext* cx, unsigned argc,
                                              Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, transferToFixedLengthImpl>(cx,
                                                                        args);
}

/**
 * ArrayBuffer.prototype.resize ( newLength )
 *
 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
 */
bool ArrayBufferObject::resizeImpl(JSContext* cx, const CallArgs& args) {
  MOZ_ASSERT(IsResizableArrayBuffer(args.thisv()));

  Rooted<ResizableArrayBufferObject*> obj(
      cx, &args.thisv().toObject().as<ResizableArrayBufferObject>());

  // Step 4.
  uint64_t newByteLength;
  if (!ToIndex(cx, args.get(0), &newByteLength)) {
    return false;
  }

  // Step 5.
  if (obj->isDetached()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return false;
  }
  if (obj->isLengthPinned()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_PINNED);
    return false;
  }

  // Step 6.
  if (newByteLength > obj->maxByteLength()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
    return false;
  }

  if (obj->isWasm()) {
    // Special case for resizing of Wasm buffers.
    if (newByteLength % wasm::PageSize != 0) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_WASM_ARRAYBUFFER_PAGE_MULTIPLE);
      return false;
    }
    if (newByteLength < obj->byteLength()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_WASM_ARRAYBUFFER_CANNOT_SHRINK);
      return false;
    }

    Pages newPages = Pages::fromByteLengthExact(newByteLength);
    MOZ_RELEASE_ASSERT(WasmArrayBufferSourceMaxPages(obj).isSome());
    Rooted<ArrayBufferObject*> res(
        cx,
        obj->wasmGrowToPagesInPlace(obj->wasmAddressType(), newPages, obj, cx));
    MOZ_ASSERT_IF(res, res == obj);
    return !!res;
  }

  // Steps 7-15.
  obj->resize(size_t(newByteLength));

  // Step 16.
  args.rval().setUndefined();
  return true;
}

/**
 * ArrayBuffer.prototype.resize ( newLength )
 *
 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize
 */
bool ArrayBufferObject::resize(JSContext* cx, unsigned argc, Value* vp) {
  // Steps 1-3.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsResizableArrayBuffer, resizeImpl>(cx, args);
}

static bool IsArrayBufferSpecies(JSContext* cx, JSFunction* species) {
  return IsSelfHostedFunctionWithName(species,
                                      cx->names().dollar_ArrayBufferSpecies_);
}

static bool HasBuiltinArrayBufferSpecies(ArrayBufferObject* obj,
                                         JSContext* cx) {
  // Ensure `ArrayBuffer.prototype.constructor` and `ArrayBuffer[@@species]`
  // haven't been mutated.
  if (!cx->realm()->realmFuses.optimizeArrayBufferSpeciesFuse.intact()) {
    return false;
  }

  // Ensure |obj|'s prototype is the actual ArrayBuffer.prototype.
  auto* proto = cx->global()->maybeGetPrototype(JSProto_ArrayBuffer);
  if (!proto || obj->staticPrototype() != proto) {
    return false;
  }

  // Fail if |obj| has an own `constructor` property.
  if (obj->containsPure(NameToId(cx->names().constructor))) {
    return false;
  }

  return true;
}

/**
 * ArrayBuffer.prototype.slice ( start, end )
 *
 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
 */
bool ArrayBufferObject::sliceImpl(JSContext* cx, const CallArgs& args) {
  MOZ_ASSERT(IsArrayBuffer(args.thisv()));

  Rooted<ArrayBufferObject*> obj(
      cx, &args.thisv().toObject().as<ArrayBufferObject>());

  // Step 4.
  if (obj->isDetached()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return false;
  }

  // Step 5.
  size_t len = obj->byteLength();

  // Steps 6-9.
  size_t first = 0;
  if (args.hasDefined(0)) {
    if (!ToIntegerIndex(cx, args[0], len, &first)) {
      return false;
    }
  }

  // Steps 10-13.
  size_t final_ = len;
  if (args.hasDefined(1)) {
    if (!ToIntegerIndex(cx, args[1], len, &final_)) {
      return false;
    }
  }

  // Step 14.
  size_t newLen = final_ >= first ? final_ - first : 0;
  MOZ_ASSERT(newLen <= ArrayBufferObject::ByteLengthLimit);

  // Steps 15-21.
  Rooted<JSObject*> resultObj(cx);
  ArrayBufferObject* unwrappedResult = nullptr;
  if (HasBuiltinArrayBufferSpecies(obj, cx)) {
    // Steps 15-16.
    unwrappedResult = createZeroed(cx, newLen);
    if (!unwrappedResult) {
      return false;
    }
    resultObj.set(unwrappedResult);

    // Steps 17-18. (Not applicable)

    // Step 19.
    MOZ_ASSERT(!unwrappedResult->isDetached());

    // Step 20.
    MOZ_ASSERT(unwrappedResult != obj);

    // Step 21.
    MOZ_ASSERT(unwrappedResult->byteLength() == newLen);
  } else {
    // Step 15.
    Rooted<JSObject*> ctor(cx, SpeciesConstructor(cx, obj, JSProto_ArrayBuffer,
                                                  IsArrayBufferSpecies));
    if (!ctor) {
      return false;
    }

    // Step 16.
    {
      FixedConstructArgs<1> cargs(cx);
      cargs[0].setNumber(newLen);

      Rooted<Value> ctorVal(cx, ObjectValue(*ctor));
      if (!Construct(cx, ctorVal, cargs, ctorVal, &resultObj)) {
        return false;
      }
    }

    // Steps 17-18.
    unwrappedResult = resultObj->maybeUnwrapIf<ArrayBufferObject>();
    if (!unwrappedResult) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_NON_ARRAY_BUFFER_RETURNED);
      return false;
    }

    // Step 19.
    if (unwrappedResult->isDetached()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_TYPED_ARRAY_DETACHED);
      return false;
    }

    // Step 20.
    if (unwrappedResult == obj) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_SAME_ARRAY_BUFFER_RETURNED);
      return false;
    }

    // Step 21.
    size_t resultByteLength = unwrappedResult->byteLength();
    if (resultByteLength < newLen) {
      ToCStringBuf resultLenCbuf;
      const char* resultLenStr =
          NumberToCString(&resultLenCbuf, double(resultByteLength));

      ToCStringBuf newLenCbuf;
      const char* newLenStr = NumberToCString(&newLenCbuf, double(newLen));

      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_SHORT_ARRAY_BUFFER_RETURNED, newLenStr,
                                resultLenStr);
      return false;
    }
  }

  // Steps 22-23.
  if (obj->isDetached()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return false;
  }

  // Step 26.
  //
  // Reacquire the length in case the buffer has been resized.
  size_t currentLen = obj->byteLength();

  // Steps 24-25 and 27.
  if (first < currentLen) {
    // Step 27.a.
    size_t count = std::min(newLen, currentLen - first);

    // Steps 24-25 and 27.b.
    ArrayBufferObject::copyData(unwrappedResult, 0, obj, first, count);
  }

  // Step 28.
  args.rval().setObject(*resultObj);
  return true;
}

/**
 * ArrayBuffer.prototype.slice ( start, end )
 *
 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
 */
bool ArrayBufferObject::slice(JSContext* cx, unsigned argc, Value* vp) {
  // Steps 1-3.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsArrayBuffer, sliceImpl>(cx, args);
}

/*
 * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
 */
bool ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  args.rval().setBoolean(args.get(0).isObject() &&
                         JS_IsArrayBufferViewObject(&args.get(0).toObject()));
  return true;
}

// ES2024 draft rev 3a773fc9fae58be023228b13dbbd402ac18eeb6b
// 25.1.4.1 ArrayBuffer ( length [ , options ] )
bool ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc,
                                          Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // Step 1.
  if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer")) {
    return false;
  }

  // Step 2.
  uint64_t byteLength;
  if (!ToIndex(cx, args.get(0), &byteLength)) {
    return false;
  }

  // Step 3.
  mozilla::Maybe<uint64_t> maxByteLength;
  // Inline call to GetArrayBufferMaxByteLengthOption.
  if (args.get(1).isObject()) {
    Rooted<JSObject*> options(cx, &args[1].toObject());

    Rooted<Value> val(cx);
    if (!GetProperty(cx, options, options, cx->names().maxByteLength, &val)) {
      return false;
    }
    if (!val.isUndefined()) {
      uint64_t maxByteLengthInt;
      if (!ToIndex(cx, val, &maxByteLengthInt)) {
        return false;
      }

      // 25.1.3.1 AllocateArrayBuffer, step 3.a.
      if (byteLength > maxByteLengthInt) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
        return false;
      }
      maxByteLength = mozilla::Some(maxByteLengthInt);
    }
  }

  // Step 4 (Inlined 25.1.3.1 AllocateArrayBuffer).
  // 25.1.3.1, step 4 (Inlined 10.1.13 OrdinaryCreateFromConstructor, step 2).
  RootedObject proto(cx);
  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ArrayBuffer,
                                          &proto)) {
    return false;
  }

  // 25.1.3.1, step 5 (Inlined 6.2.9.1 CreateByteDataBlock, step 2).
  if (!CheckArrayBufferTooLarge(cx, byteLength)) {
    return false;
  }

  if (maxByteLength) {
    // 25.1.3.1, step 8.a.
    if (!CheckArrayBufferTooLarge(cx, *maxByteLength)) {
      return false;
    }

    // 25.1.3.1, remaining steps.
    auto* bufobj = ResizableArrayBufferObject::createZeroed(
        cx, byteLength, *maxByteLength, proto);
    if (!bufobj) {
      return false;
    }
    args.rval().setObject(*bufobj);
    return true;
  }

  // 25.1.3.1, remaining steps.
  JSObject* bufobj = createZeroed(cx, byteLength, proto);
  if (!bufobj) {
    return false;
  }
  args.rval().setObject(*bufobj);
  return true;
}

using ArrayBufferContents = UniquePtr<uint8_t[], JS::FreePolicy>;

static ArrayBufferContents AllocateUninitializedArrayBufferContents(
    JSContext* cx, size_t nbytes) {
  // First attempt a normal allocation.
  uint8_t* p =
      cx->maybe_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
  if (MOZ_UNLIKELY(!p)) {
    // Otherwise attempt a large allocation, calling the
    // large-allocation-failure callback if necessary.
    p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
        js::AllocFunction::Malloc, js::ArrayBufferContentsArena, nbytes));
    if (!p) {
      MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode,
                            "OOM in AllocateUninitializedArrayBufferContents");
      ReportOutOfMemory(cx);
    }
  }

  return ArrayBufferContents(p);
}

static ArrayBufferContents AllocateArrayBufferContents(JSContext* cx,
                                                       size_t nbytes) {
  // First attempt a normal allocation.
  uint8_t* p =
      cx->maybe_pod_arena_calloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
  if (MOZ_UNLIKELY(!p)) {
    // Otherwise attempt a large allocation, calling the
    // large-allocation-failure callback if necessary.
    p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
        js::AllocFunction::Calloc, js::ArrayBufferContentsArena, nbytes));
    if (!p) {
      ReportOutOfMemory(cx);
    }
  }

  return ArrayBufferContents(p);
}

static ArrayBufferContents ReallocateArrayBufferContents(JSContext* cx,
                                                         uint8_t* old,
                                                         size_t oldSize,
                                                         size_t newSize) {
  // First attempt a normal reallocation.
  uint8_t* p = cx->maybe_pod_arena_realloc<uint8_t>(
      js::ArrayBufferContentsArena, old, oldSize, newSize);
  if (MOZ_UNLIKELY(!p)) {
    // Otherwise attempt a large allocation, calling the
    // large-allocation-failure callback if necessary.
    p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
        js::AllocFunction::Realloc, js::ArrayBufferContentsArena, newSize,
        old));
    if (!p) {
      ReportOutOfMemory(cx);
    }
  }

  return ArrayBufferContents(p);
}

static ArrayBufferContents NewCopiedBufferContents(
    JSContext* cx, Handle<ArrayBufferObject*> buffer) {
  ArrayBufferContents dataCopy =
      AllocateUninitializedArrayBufferContents(cx, buffer->byteLength());
  if (dataCopy) {
    if (auto count = buffer->byteLength()) {
      memcpy(dataCopy.get(), buffer->dataPointer(), count);
    }
  }
  return dataCopy;
}

/* static */
void ArrayBufferObject::detach(JSContext* cx,
                               Handle<ArrayBufferObject*> buffer) {
  cx->check(buffer);
  MOZ_ASSERT(!buffer->isPreparedForAsmJS());
  MOZ_ASSERT(!buffer->isLengthPinned());

  // Update all views of the buffer to account for the buffer having been
  // detached, and clear the buffer's data and list of views.

  auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
  if (InnerViewTable::ViewVector* views =
          innerViews.maybeViewsUnbarriered(buffer)) {
    AutoTouchingGrayThings tgt;
    for (size_t i = 0; i < views->length(); i++) {
      JSObject* view = (*views)[i];
      view->as<ArrayBufferViewObject>().notifyBufferDetached();
    }
    innerViews.removeViews(buffer);
  }
  if (JSObject* view = buffer->firstView()) {
    view->as<ArrayBufferViewObject>().notifyBufferDetached();
    buffer->setFirstView(nullptr);
  }

  if (buffer->dataPointer()) {
    buffer->releaseData(cx->gcContext());
    buffer->setDataPointer(BufferContents::createNoData());
  }

  buffer->setByteLength(0);
  buffer->setIsDetached();
  if (buffer->isResizable()) {
    buffer->as<ResizableArrayBufferObject>().setMaxByteLength(0);
  }
}

void ResizableArrayBufferObject::resize(size_t newByteLength) {
  MOZ_ASSERT(!isPreparedForAsmJS());
  MOZ_ASSERT(!isWasm());
  MOZ_ASSERT(!isDetached());
  MOZ_ASSERT(!isLengthPinned());
  MOZ_ASSERT(isResizable());
  MOZ_ASSERT(newByteLength <= maxByteLength());

  // Clear the bytes between `data[newByteLength..oldByteLength]` when
  // shrinking the buffer. We don't need to clear any bytes when growing the
  // buffer, because the new space was either initialized to zero when creating
  // the buffer, or a prior shrink zeroed it out here.
  size_t oldByteLength = byteLength();
  if (newByteLength < oldByteLength) {
    size_t nbytes = oldByteLength - newByteLength;
    memset(dataPointer() + newByteLength, 0, nbytes);
  }

  setByteLength(newByteLength);

  // Update all views of the buffer to account for the buffer having been
  // resized.

  auto& innerViews = ObjectRealm::get(this).innerViews.get();
  if (InnerViewTable::ViewVector* views =
          innerViews.maybeViewsUnbarriered(this)) {
    AutoTouchingGrayThings tgt;
    for (auto& view : *views) {
      view->notifyBufferResized();
    }
  }
  if (auto* view = firstView()) {
    view->as<ArrayBufferViewObject>().notifyBufferResized();
  }
}

/*
 * [SMDOC] WASM Linear Memory structure
 *
 * The wasm linear memory is, on its face, a simple buffer of memory. However,
 * we perform several optimizations with the memory allocation to avoid bounds
 * checks and to avoid moving grows. Many different types/objects are involved
 * in our memory allocations:
 *
 *  - wasm::Instance - stores information about each memory in a
 *    MemoryInstanceData, which itself holds a reference to a WasmMemoryObject.
 *    Memory 0 gets special treatment to avoid loading the MemoryInstanceData at
 *    runtime.
 *
 *  - WasmMemoryObject - stores a reference to a (Shared)ArrayBufferObject for
 *    the backing storage.
 *
 *  - ArrayBufferObject - owns the actual buffer of memory for asm.js memories
 *    and non-shared wasm memories. For wasm memories (but NOT asm.js memories),
 *    additional wasm metadata is stored in a WasmArrayRawBuffer next to the
 *    data itself.
 *
 *  - SharedArrayBufferObject - owns the actual buffer of memory for shared wasm
 *    memories, in the form of a WasmSharedArrayRawBuffer.
 *
 *  - WasmArrayRawBuffer - metadata for a non-shared wasm memory allocation. See
 *    below for details.
 *
 *  - WasmSharedArrayRawBuffer - a nearly equivalent class to WasmArrayRawBuffer
 *    used for shared wasm memories only. The same terminology from
 *    WasmArrayRawBuffer applies, but the memory allocation strategy is
 *    different.
 *
 *
 * ## Wasm memory terminology
 *
 * A wasm/asm.js linear memory is an mmap'd array buffer. In the general case,
 * accesses to memory must be bounds checked, but bounds checks can be
 * simplified, omitted, or deferred to signal handling based on the properties
 * of the memory (such as a known maximum size). Some common terminology applies
 * to all asm.js and wasm memories, and is generally handled by
 * WasmMemoryObject. The following terms are all expressed in bytes for clarity,
 * but in practice they may be stored as a page count instead:
 *
 *  - byteLength - the actual current length of the buffer. Accesses in
 *    the range [0, byteLength) must succeed; accesses >= byteLength must fail.
 *    May only increase, if the memory grows.
 *
 *  - boundsCheckLimit - the size against which we perform bounds checks. This
 *    is sometimes equal to byteLength, but may be greater. Regardless, it is
 *    always safe to use in bounds checks for reasons described in "Linear
 *    memory addresses and bounds checking" in wasm/WasmMemory.cpp.
 *
 *  - sourceMaxSize - the optional declared limit on how far byteLength can
 *    grow. This is the unmodified maximum size from the source module or JS-API
 *    invocation. This may not be representable in pointer size, nor feasible
 *    for a module to actually grow to due to implementation limits. It is used
 *    for correct linking checks and js-types reflection.
 *
 *  - clampedMaxSize - the maximum size on how far the byteLength can grow. This
 *    value respects implementation limits. Every memory has a clampedMaxSize,
 *    even if no maximum was specified in source. This value may be greater
 *    than or less than mappedSize.
 *
 *  - mappedSize - the actual mmap'd size. Access in the range [0, mappedSize)
 *    will either succeed, or be handled by the wasm signal handlers. The amount
 *    that we map can vary according to multiple factors - see "Allocation
 *    strategies" below. (This property does not apply to asm.js.)
 *
 * The below diagram shows the layout of the wasm heap. The wasm-visible portion
 * of the heap starts at 0. There is one extra page prior to the start of the
 * wasm heap which contains the WasmArrayRawBuffer struct at its end (i.e. right
 * before the start of the WASM memory). The mappedSize does NOT include this
 * page.
 *
 *
 *          Wasm(Shared)ArrayRawBuffer
 *          │ dataPointer() (from (Shared)ArrayBufferObject)
 *          │ │
 *   ┌──────│─│──────────────────────────────────────────────────────┐
 *   └──────│─│──────────────│─────────────────────────────────│─────│
 *            0          byteLength             boundsCheckLimit     mappedSize
 *
 *   └───────────────────────┘
 *           COMMITTED
 *                           └─────────────────────────────────┴─────┘
 *                                             SLOP
 *   └───────────────────────────────────────────────────────────────┘
 *                                MAPPED
 *
 *
 * Invariants on byteLength and mappedSize:
 *  - byteLength only increases.
 *  - 0 <= byteLength <= mappedSize.
 *  - If sourceMaxSize is not specified, mappedSize may grow.
 *    Otherwise, mappedSize is constant.
 *
 * Invariants on sourceMaxSize and clampedMaxSize:
 *  - initialLength <= clampedMaxSize <= sourceMaxSize (if present).
 *  - clampedMaxSize <= wasm::MaxMemoryPages().
 *
 * Invariants on boundsCheckLimit:
 *  - For asm.js code: boundsCheckLimit == byteLength.
 *    Signal handlers will not be invoked.
 *  - For wasm code without the huge memory trick:
 *    byteLength <= boundsCheckLimit < mappedSize
 *  - For wasm code with the huge memory trick:
 *    boundsCheckLimit is irrelevant as bounds checks are unnecessary.
 *  - If sourceMaxSize is present, boundsCheckLimit is constant.
 *    Otherwise, it may increase with mappedSize.
 *  - On ARM, boundsCheckLimit must be a valid ARM immediate.
 *
 * The region between byteLength and mappedSize is the SLOP - an area where we
 * use signal handlers to catch invalid accesses, including those that slip by
 * bounds checks. Logically it has two parts:
 *
 *  - From byteLength to boundsCheckLimit - as described above, we sometimes set
 *    boundsCheckLimit higher than byteLength, and this part of the SLOP catches
 *    accesses to memory that therefore pass a bounds check but should
 *    nonetheless trap.
 *
 *  - From boundsCheckLimit to mappedSize - this part of the SLOP is a guard
 *    region that allows us to ignore most offsets when performing bounds
 *    checks.
 *
 * For more information about both of these cases, see "Linear memory addresses
 * and bounds checking" in wasm/WasmMemory.cpp.
 *
 *
 * ## Allocation strategies
 *
 * Our memory allocation strategy varies according to several factors.
 *
 *  - Huge memories - if allocating a 32-bit memory on a platform with
 *    sufficient address space, we simply map 4GiB of memory plus ample guard
 *    space. This eliminates the need for bounds checks entirely except in cases
 *    of very large offsets. This applies whether or not the memory has a
 *    declared maximum. See "Linear memory addresses and bounds checking" in
 *    wasm/WasmMemory.cpp.
 *
 *  - Memories with no maximum - rather than over-allocate, we conservatively
 *    map only the requested initial pages plus the required guard region.
 *
 *  - Memories with a maximum - we do our best to reserve the entire maximum
 *    space up front so that grows will not fail later. If we fail to map the
 *    entire maximum, we iteratively map less and less memory until allocation
 *    succeeds.
 *
 *
 * ## Grow strategies
 *
 * We have two strategies for growing a wasm memory.
 *
 *  - Growing in place - if mappedSize is large enough to contain the new size,
 *    we can simply commit the previously-mapped pages. This is always the case
 *    for huge memories, and for memories with a declared max (and by extension
 *    shared memories).
 *
 *  - Moving grow - if we have not previously mapped enough space, we must
 *    allocate an entirely new buffer and copy the contents of the old buffer.
 *    This causes the memory to move and therefore requires us to update
 *    information on the Instance. It also increases memory pressure because
 *    both the old and new memories must be resident at the same time, and
 *    afterwards the entirety of the old buffer's contents will be resident in
 *    memory (even if some pages had never been touched before the move).
 *
 *    A moving grow can fall back to a grow in place if the mappedSize allows
 *    for it. However, at the time of writing, our memory allocation/grow
 *    strategies do not allow this to happen.
 *
 */

[[nodiscard]] bool WasmArrayRawBuffer::growToPagesInPlace(Pages newPages) {
  size_t newSize = newPages.byteLength();
  size_t oldSize = byteLength();

  MOZ_ASSERT(newSize >= oldSize);
  MOZ_ASSERT(newPages <= clampedMaxPages());
  MOZ_ASSERT(newSize <= mappedSize());

  size_t delta = newSize - oldSize;
  MOZ_ASSERT(delta % wasm::PageSize == 0);

  uint8_t* dataEnd = dataPointer() + oldSize;
  MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);

  if (delta && !CommitBufferMemory(dataEnd, delta)) {
    return false;
  }

  length_ = newSize;

  return true;
}

void WasmArrayRawBuffer::discard(size_t byteOffset, size_t byteLen) {
  uint8_t* memBase = dataPointer();

  // The caller is responsible for ensuring these conditions are met; see this
  // function's comment in ArrayBufferObject.h.
  MOZ_ASSERT(byteOffset % wasm::PageSize == 0);
  MOZ_ASSERT(byteLen % wasm::PageSize == 0);
  MOZ_ASSERT(wasm::MemoryBoundsCheck(uint64_t(byteOffset), uint64_t(byteLen),
                                     byteLength()));

  // Discarding zero bytes "succeeds" with no effect.
  if (byteLen == 0) {
    return;
  }

  void* addr = memBase + uintptr_t(byteOffset);

  // On POSIX-ish platforms, we discard memory by overwriting previously-mapped
  // pages with freshly-mapped pages (which are all zeroed). The operating
  // system recognizes this and decreases the process RSS, and eventually
  // collects the abandoned physical pages.
  //
  // On Windows, committing over previously-committed pages has no effect, and
  // the memory must be explicitly decommitted first. This is not the same as an
  // munmap; the address space is still reserved.

#ifdef XP_WIN
  if (!VirtualFree(addr, byteLen, MEM_DECOMMIT)) {
    MOZ_CRASH("wasm discard: failed to decommit memory");
  }
  if (!VirtualAlloc(addr, byteLen, MEM_COMMIT, PAGE_READWRITE)) {
    MOZ_CRASH("wasm discard: decommitted memory but failed to recommit");
  };
#elif defined(__wasi__)
  memset(addr, 0, byteLen);
#else  // !XP_WIN
  void* data = MozTaggedAnonymousMmap(addr, byteLen, PROT_READ | PROT_WRITE,
                                      MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0,
                                      "wasm-reserved");
  if (data == MAP_FAILED) {
    MOZ_CRASH("failed to discard wasm memory; memory mappings may be broken");
  }
#endif
}

/* static */
WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm(
    AddressType addressType, Pages initialPages, Pages clampedMaxPages,
    const Maybe<Pages>& sourceMaxPages, const Maybe<size_t>& mapped) {
  // Prior code has asserted that initial pages is within our implementation
  // limits (wasm::MaxMemoryPages) and we can assume it is a valid size_t.
  MOZ_ASSERT(initialPages.hasByteLength());
  size_t numBytes = initialPages.byteLength();

  // If there is a specified maximum, attempt to map the whole range for
  // clampedMaxPages. Or else map only what's required for initialPages.
  Pages initialMappedPages =
      sourceMaxPages.isSome() ? clampedMaxPages : initialPages;

  // Use an override mapped size, or else compute the mapped size from
  // initialMappedPages.
  size_t mappedSize =
      mapped.isSome() ? *mapped : wasm::ComputeMappedSize(initialMappedPages);

  MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
  MOZ_RELEASE_ASSERT(numBytes <= SIZE_MAX - gc::SystemPageSize());
  MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages);
  MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
  MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);

  uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize();
  uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize();

  void* data = MapBufferMemory(addressType, (size_t)mappedSizeWithHeader,
                               (size_t)numBytesWithHeader);
  if (!data) {
    return nullptr;
  }

  uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize();
  uint8_t* header = base - sizeof(WasmArrayRawBuffer);

  auto rawBuf = new (header) WasmArrayRawBuffer(
      addressType, base, clampedMaxPages, sourceMaxPages, mappedSize, numBytes);
  return rawBuf;
}

/* static */
void WasmArrayRawBuffer::Release(void* mem) {
  WasmArrayRawBuffer* header =
      (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer));

  MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());
  size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
  size_t committedSize = header->byteLength() + gc::SystemPageSize();

  static_assert(std::is_trivially_destructible_v<WasmArrayRawBuffer>,
                "no need to call the destructor");

  UnmapBufferMemory(header->addressType(), header->basePointer(),
                    mappedSizeWithHeader, committedSize);
}

WasmArrayRawBuffer* ArrayBufferObject::BufferContents::wasmBuffer() const {
  MOZ_RELEASE_ASSERT(kind_ == WASM);
  return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
}

template <typename ObjT, typename RawbufT>
static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer(
    JSContext* cx, const wasm::MemoryDesc& memory) {
  bool useHugeMemory = wasm::IsHugeMemoryEnabled(memory.addressType());
  Pages initialPages = memory.initialPages();
  Maybe<Pages> sourceMaxPages = memory.maximumPages();
  Pages clampedMaxPages = wasm::ClampedMaxPages(
      memory.addressType(), initialPages, sourceMaxPages, useHugeMemory);

  Maybe<size_t> mappedSize;
#ifdef WASM_SUPPORTS_HUGE_MEMORY
  // Override the mapped size if we are using huge memory. If we are not, then
  // it will be calculated by the raw buffer we are using.
  if (useHugeMemory) {
    mappedSize = Some(wasm::HugeMappedSize);
  }
#endif

  RawbufT* buffer =
      RawbufT::AllocateWasm(memory.limits.addressType, initialPages,
                            clampedMaxPages, sourceMaxPages, mappedSize);
  if (!buffer) {
    if (useHugeMemory) {
      WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED);
      if (cx->isExceptionPending()) {
        cx->clearPendingException();
      }

      ReportOutOfMemory(cx);
      return nullptr;
    }

    // If we fail, and have a sourceMaxPages, try to reserve the biggest
    // chunk in the range [initialPages, clampedMaxPages) using log backoff.
    if (!sourceMaxPages) {
      wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed",
                initialPages.value());
      ReportOutOfMemory(cx);
      return nullptr;
    }

    uint64_t cur = clampedMaxPages.value() / 2;
    for (; Pages(cur) > initialPages; cur /= 2) {
      buffer = RawbufT::AllocateWasm(memory.limits.addressType, initialPages,
                                     Pages(cur), sourceMaxPages, mappedSize);
      if (buffer) {
        break;
      }
    }

    if (!buffer) {
      wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed",
                initialPages.value());
      ReportOutOfMemory(cx);
      return nullptr;
    }
  }

  // ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case
  // of failure.
  Rooted<ArrayBufferObjectMaybeShared*> object(
      cx, ObjT::createFromNewRawBuffer(cx, buffer, initialPages.byteLength()));
  if (!object) {
    return nullptr;
  }

  // See MaximumLiveMappedBuffers comment above.
  if (wasmReservedBytes > WasmReservedBytesStartSyncFullGC) {
    JS::PrepareForFullGC(cx);
    JS::NonIncrementalGC(cx, JS::GCOptions::Normal,
                         JS::GCReason::TOO_MUCH_WASM_MEMORY);
    wasmReservedBytesSinceLast = 0;
  } else if (wasmReservedBytes > WasmReservedBytesStartTriggering) {
    wasmReservedBytesSinceLast += uint64_t(buffer->mappedSize());
    if (wasmReservedBytesSinceLast > WasmReservedBytesPerTrigger) {
      (void)cx->runtime()->gc.triggerGC(JS::GCReason::TOO_MUCH_WASM_MEMORY);
      wasmReservedBytesSinceLast = 0;
    }
  } else {
    wasmReservedBytesSinceLast = 0;
  }

  // Log the result with details on the memory allocation
  if (sourceMaxPages) {
    if (useHugeMemory) {
      wasm::Log(cx,
                "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64
                " pages}) succeeded",
                initialPages.value(), sourceMaxPages->value());
    } else {
      wasm::Log(cx,
                "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64
                " pages}) succeeded "
                "with internal maximum of %" PRIu64 " pages",
                initialPages.value(), sourceMaxPages->value(),
                object->wasmClampedMaxPages().value());
    }
  } else {
    wasm::Log(cx, "new Memory({initial:%" PRIu64 " pages}) succeeded",
              initialPages.value());
  }

  return object;
}

ArrayBufferObjectMaybeShared* js::CreateWasmBuffer(
    JSContext* cx, const wasm::MemoryDesc& memory) {
  MOZ_RELEASE_ASSERT(memory.initialPages() <=
                     wasm::MaxMemoryPages(memory.addressType()));
  MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);

  if (memory.isShared()) {
    if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_WASM_NO_SHMEM_LINK);
      return nullptr;
    }
    return CreateSpecificWasmBuffer<SharedArrayBufferObject,
                                    WasmSharedArrayRawBuffer>(cx, memory);
  }
  return CreateSpecificWasmBuffer<ArrayBufferObject, WasmArrayRawBuffer>(
      cx, memory);
}

bool ArrayBufferObject::prepareForAsmJS() {
  MOZ_ASSERT(byteLength() % wasm::PageSize == 0,
             "prior size checking should have guaranteed page-size multiple");
  MOZ_ASSERT(byteLength() > 0,
             "prior size checking should have excluded empty buffers");
  MOZ_ASSERT(!isResizable(),
             "prior checks should have excluded resizable buffers");

  switch (bufferKind()) {
    case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
    case MALLOCED_UNKNOWN_ARENA:
    case MAPPED:
    case EXTERNAL:
      // It's okay if this uselessly sets the flag a second time.
      setIsPreparedForAsmJS();
      return true;

    case INLINE_DATA:
      static_assert(
          wasm::PageSize > FixedLengthArrayBufferObject::MaxInlineBytes,
          "inline data must be too small to be a page size multiple");
      MOZ_ASSERT_UNREACHABLE(
          "inline-data buffers should be implicitly excluded by size checks");
      return false;

    case NO_DATA:
      MOZ_ASSERT_UNREACHABLE(
          "size checking should have excluded detached or empty buffers");
      return false;

    // asm.js code and associated buffers are potentially long-lived.  Yet a
    // buffer of user-owned data *must* be detached by the user before the
    // user-owned data is disposed.  No caller wants to use a user-owned
    // ArrayBuffer with asm.js, so just don't support this and avoid a mess of
    // complexity.
    case USER_OWNED:
    // wasm buffers can be detached at any time.
    case WASM:
      MOZ_ASSERT(!isPreparedForAsmJS());
      return false;
  }

  MOZ_ASSERT_UNREACHABLE("non-exhaustive kind-handling switch?");
  return false;
}

ArrayBufferObject::BufferContents ArrayBufferObject::createMappedContents(
    int fd, size_t offset, size_t length) {
  void* data =
      gc::AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
  return BufferContents::createMapped(data);
}

uint8_t* FixedLengthArrayBufferObject::inlineDataPointer() const {
  return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
}

uint8_t* ResizableArrayBufferObject::inlineDataPointer() const {
  return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
}

uint8_t* ArrayBufferObject::dataPointer() const {
  return static_cast<uint8_t*>(getFixedSlot(DATA_SLOT).toPrivate());
}

SharedMem<uint8_t*> ArrayBufferObject::dataPointerShared() const {
  return SharedMem<uint8_t*>::unshared(getFixedSlot(DATA_SLOT).toPrivate());
}

ArrayBufferObject::FreeInfo* ArrayBufferObject::freeInfo() const {
  MOZ_ASSERT(isExternal());
  MOZ_ASSERT(!isResizable());
  auto* data = as<FixedLengthArrayBufferObject>().inlineDataPointer();
  return reinterpret_cast<FreeInfo*>(data);
}

void ArrayBufferObject::releaseData(JS::GCContext* gcx) {
  switch (bufferKind()) {
    case INLINE_DATA:
      // Inline data doesn't require releasing.
      break;
    case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
    case MALLOCED_UNKNOWN_ARENA:
      gcx->free_(this, dataPointer(), associatedBytes(),
                 MemoryUse::ArrayBufferContents);
      break;
    case NO_DATA:
      // There's nothing to release if there's no data.
      MOZ_ASSERT(dataPointer() == nullptr);
      break;
    case USER_OWNED:
      // User-owned data is released by, well, the user.
      break;
    case MAPPED:
      gc::DeallocateMappedContent(dataPointer(), byteLength());
      gcx->removeCellMemory(this, associatedBytes(),
                            MemoryUse::ArrayBufferContents);
      break;
    case WASM:
      WasmArrayRawBuffer::Release(dataPointer());
      gcx->removeCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents);
      break;
    case EXTERNAL:
      MOZ_ASSERT(freeInfo()->freeFunc);
      {
        // The analyzer can't know for sure whether the embedder-supplied
        // free function will GC. We give the analyzer a hint here.
        // (Doing a GC in the free function is considered a programmer
        // error.)
        JS::AutoSuppressGCAnalysis nogc;
        freeInfo()->freeFunc(dataPointer(), freeInfo()->freeUserData);
      }
      break;
  }
}

void ArrayBufferObject::setDataPointer(BufferContents contents) {
  setFixedSlot(DATA_SLOT, PrivateValue(contents.data()));
  setFlags((flags() & ~KIND_MASK) | contents.kind());

  if (isExternal()) {
    auto info = freeInfo();
    info->freeFunc = contents.freeFunc();
    info->freeUserData = contents.freeUserData();
  }
}

size_t ArrayBufferObject::byteLength() const {
  return size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate());
}

inline size_t ArrayBufferObject::associatedBytes() const {
  if (isMalloced()) {
    return maxByteLength();
  }
  if (isMapped()) {
    return RoundUp(byteLength(), js::gc::SystemPageSize());
  }
  MOZ_CRASH("Unexpected buffer kind");
}

void ArrayBufferObject::setByteLength(size_t length) {
  MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit);
  setFixedSlot(BYTE_LENGTH_SLOT, PrivateValue(length));
}

size_t ArrayBufferObject::wasmMappedSize() const {
  if (isWasm()) {
    return contents().wasmBuffer()->mappedSize();
  }
  return byteLength();
}

AddressType ArrayBufferObject::wasmAddressType() const {
  if (isWasm()) {
    return contents().wasmBuffer()->addressType();
  }
  MOZ_ASSERT(isPreparedForAsmJS());
  return wasm::AddressType::I32;
}

Pages ArrayBufferObject::wasmPages() const {
  if (isWasm()) {
    return contents().wasmBuffer()->pages();
  }
  MOZ_ASSERT(isPreparedForAsmJS());
  return Pages::fromByteLengthExact(byteLength());
}

Pages ArrayBufferObject::wasmClampedMaxPages() const {
  if (isWasm()) {
    return contents().wasmBuffer()->clampedMaxPages();
  }
  MOZ_ASSERT(isPreparedForAsmJS());
  return Pages::fromByteLengthExact(byteLength());
}

Maybe<Pages> ArrayBufferObject::wasmSourceMaxPages() const {
  if (isWasm()) {
    return contents().wasmBuffer()->sourceMaxPages();
  }
  MOZ_ASSERT(isPreparedForAsmJS());
  return Some<Pages>(Pages::fromByteLengthExact(byteLength()));
}

size_t js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf) {
  if (buf->is<ArrayBufferObject>()) {
    return buf->as<ArrayBufferObject>().wasmMappedSize();
  }
  return buf->as<SharedArrayBufferObject>().wasmMappedSize();
}

AddressType js::WasmArrayBufferAddressType(
    const ArrayBufferObjectMaybeShared* buf) {
  if (buf->is<ArrayBufferObject>()) {
    return buf->as<ArrayBufferObject>().wasmAddressType();
  }
  return buf->as<SharedArrayBufferObject>().wasmAddressType();
}
Pages js::WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf) {
  if (buf->is<ArrayBufferObject>()) {
    return buf->as<ArrayBufferObject>().wasmPages();
  }
  return buf->as<SharedArrayBufferObject>().volatileWasmPages();
}
Pages js::WasmArrayBufferClampedMaxPages(
    const ArrayBufferObjectMaybeShared* buf) {
  if (buf->is<ArrayBufferObject>()) {
    return buf->as<ArrayBufferObject>().wasmClampedMaxPages();
  }
  return buf->as<SharedArrayBufferObject>().wasmClampedMaxPages();
}
Maybe<Pages> js::WasmArrayBufferSourceMaxPages(
    const ArrayBufferObjectMaybeShared* buf) {
  if (buf->is<ArrayBufferObject>()) {
    return buf->as<ArrayBufferObject>().wasmSourceMaxPages();
  }
  return Some(buf->as<SharedArrayBufferObject>().wasmSourceMaxPages());
}

static void CheckStealPreconditions(Handle<ArrayBufferObject*> buffer,
                                    JSContext* cx) {
  cx->check(buffer);

  MOZ_ASSERT(!buffer->isDetached(), "can't steal from a detached buffer");
  MOZ_ASSERT(!buffer->isLengthPinned(),
             "can't steal from a buffer with a pinned length");
  MOZ_ASSERT(!buffer->isPreparedForAsmJS(),
             "asm.js-prepared buffers don't have detachable/stealable data");
}

/* static */
ArrayBufferObject* ArrayBufferObject::wasmGrowToPagesInPlace(
    wasm::AddressType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf,
    JSContext* cx) {
  if (oldBuf->isLengthPinned()) {
    return nullptr;
  }

  MOZ_ASSERT(oldBuf->isWasm());

  // Check that the new pages is within our allowable range. This will
  // simultaneously check against the maximum specified in source and our
  // implementation limits.
  if (newPages > oldBuf->wasmClampedMaxPages()) {
    return nullptr;
  }
  MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) &&
             newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit);

  if (oldBuf->is<ResizableArrayBufferObject>()) {
    RemoveCellMemory(oldBuf, oldBuf->byteLength(),
                     MemoryUse::ArrayBufferContents);

    if (!oldBuf->contents().wasmBuffer()->growToPagesInPlace(newPages)) {
      // If fails, the buffer still exists on oldBuf, keep tracking
      // cell memory there.
      AddCellMemory(oldBuf, oldBuf->byteLength(),
                    MemoryUse::ArrayBufferContents);
      return nullptr;
    }
    oldBuf->setByteLength(newPages.byteLength());
    AddCellMemory(oldBuf, newPages.byteLength(),
                  MemoryUse::ArrayBufferContents);
    return oldBuf;
  }

  CheckStealPreconditions(oldBuf, cx);

  // We have checked against the clamped maximum and so we know we can convert
  // to byte lengths now.
  size_t newSize = newPages.byteLength();

  // On failure, do not throw and ensure that the original buffer is
  // unmodified and valid. After WasmArrayRawBuffer::growToPagesInPlace(), the
  // wasm-visible length of the buffer has been increased so it must be the
  // last fallible operation.

  auto* newBuf = ArrayBufferObject::createEmpty(cx);
  if (!newBuf) {
    cx->clearPendingException();
    return nullptr;
  }

  MOZ_ASSERT(newBuf->isNoData());

  if (!oldBuf->contents().wasmBuffer()->growToPagesInPlace(newPages)) {
    return nullptr;
  }

  // Extract the grown contents from |oldBuf|.
  BufferContents oldContents = oldBuf->contents();

  // Overwrite |oldBuf|'s data pointer *without* releasing old data.
  oldBuf->setDataPointer(BufferContents::createNoData());

  // Detach |oldBuf| now that doing so won't release |oldContents|.
  RemoveCellMemory(oldBuf, oldBuf->byteLength(),
                   MemoryUse::ArrayBufferContents);
  ArrayBufferObject::detach(cx, oldBuf);

  // Set |newBuf|'s contents to |oldBuf|'s original contents.
  newBuf->initialize(newSize, oldContents);
  AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents);

  return newBuf;
}

/* static */
ArrayBufferObject* ArrayBufferObject::wasmMovingGrowToPages(
    AddressType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf,
    JSContext* cx) {
  MOZ_ASSERT(oldBuf->is<FixedLengthArrayBufferObject>());

  // On failure, do not throw and ensure that the original buffer is
  // unmodified and valid.
  if (oldBuf->isLengthPinned()) {
    return nullptr;
  }

  // Check that the new pages is within our allowable range. This will
  // simultaneously check against the maximum specified in source and our
  // implementation limits.
  if (newPages > oldBuf->wasmClampedMaxPages()) {
    return nullptr;
  }
  MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) &&
             newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit);

  // We have checked against the clamped maximum and so we know we can convert
  // to byte lengths now.
  size_t newSize = newPages.byteLength();

  // TODO: If the memory reservation allows, or if we can extend the mapping in
  // place, fall back to wasmGrowToPagesInPlace to avoid a new allocation +
  // copy.

  Rooted<ArrayBufferObject*> newBuf(cx, ArrayBufferObject::createEmpty(cx));
  if (!newBuf) {
    cx->clearPendingException();
    return nullptr;
  }

  Pages clampedMaxPages =
      wasm::ClampedMaxPages(t, newPages, Nothing(), /* hugeMemory */ false);
  WasmArrayRawBuffer* newRawBuf =
      WasmArrayRawBuffer::AllocateWasm(oldBuf->wasmAddressType(), newPages,
                                       clampedMaxPages, Nothing(), Nothing());
  if (!newRawBuf) {
    return nullptr;
  }

  AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents);

  BufferContents contents =
      BufferContents::createWasm(newRawBuf->dataPointer());
  newBuf->initialize(newSize, contents);

  memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
  ArrayBufferObject::detach(cx, oldBuf);

  return newBuf;
}

/* static */
void ArrayBufferObject::wasmDiscard(Handle<ArrayBufferObject*> buf,
                                    uint64_t byteOffset, uint64_t byteLen) {
  MOZ_ASSERT(buf->isWasm());
  buf->contents().wasmBuffer()->discard(byteOffset, byteLen);
}

uint32_t ArrayBufferObject::flags() const {
  return uint32_t(getFixedSlot(FLAGS_SLOT).toInt32());
}

void ArrayBufferObject::setFlags(uint32_t flags) {
  setFixedSlot(FLAGS_SLOT, Int32Value(flags));
}

static constexpr js::gc::AllocKind GetArrayBufferGCObjectKind(size_t numSlots) {
  if (numSlots <= 4) {
    return js::gc::AllocKind::ARRAYBUFFER4;
  }
  if (numSlots <= 8) {
    return js::gc::AllocKind::ARRAYBUFFER8;
  }
  if (numSlots <= 12) {
    return js::gc::AllocKind::ARRAYBUFFER12;
  }
  return js::gc::AllocKind::ARRAYBUFFER16;
}

template <class ArrayBufferType>
static ArrayBufferType* NewArrayBufferObject(JSContext* cx, HandleObject proto_,
                                             gc::AllocKind allocKind) {
  MOZ_ASSERT(allocKind == gc::AllocKind::ARRAYBUFFER4 ||
             allocKind == gc::AllocKind::ARRAYBUFFER8 ||
             allocKind == gc::AllocKind::ARRAYBUFFER12 ||
             allocKind == gc::AllocKind::ARRAYBUFFER16);

  static_assert(std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject> ||
                std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>);

  RootedObject proto(cx, proto_);
  if (!proto) {
    proto = GlobalObject::getOrCreatePrototype(cx, JSProto_ArrayBuffer);
    if (!proto) {
      MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "creating ArrayBuffer proto");
      return nullptr;
    }
  }

  const JSClass* clasp = &ArrayBufferType::class_;

  // Array buffers can store data inline so we only use fixed slots to cover the
  // reserved slots, ignoring the AllocKind.
  MOZ_ASSERT(ClassCanHaveFixedData(clasp));
  constexpr size_t nfixed = ArrayBufferType::RESERVED_SLOTS;
  static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS);

  Rooted<SharedShape*> shape(
      cx,
      SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
                                   nfixed, ObjectFlags()));
  if (!shape) {
    MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "get ArrayBuffer initial shape");
    return nullptr;
  }

  // Array buffers can't be nursery allocated but can be background-finalized.
  MOZ_ASSERT(IsBackgroundFinalized(allocKind));
  MOZ_ASSERT(!CanNurseryAllocateFinalizedClass(clasp));
  constexpr gc::Heap heap = gc::Heap::Tenured;

  auto* buffer =
      NativeObject::create<ArrayBufferType>(cx, allocKind, heap, shape);
  if (!buffer) {
    MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "create NativeObject failed");
  }
  return buffer;
}

// Creates a new ArrayBufferObject with %ArrayBuffer.prototype% as proto and no
// space for inline data.
static ArrayBufferObject* NewArrayBufferObject(JSContext* cx) {
  constexpr auto allocKind =
      GetArrayBufferGCObjectKind(FixedLengthArrayBufferObject::RESERVED_SLOTS);
  return NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr,
                                                            allocKind);
}
static ResizableArrayBufferObject* NewResizableArrayBufferObject(
    JSContext* cx) {
  constexpr auto allocKind =
      GetArrayBufferGCObjectKind(ResizableArrayBufferObject::RESERVED_SLOTS);
  return NewArrayBufferObject<ResizableArrayBufferObject>(cx, nullptr,
                                                          allocKind);
}

ArrayBufferObject* ArrayBufferObject::createForContents(
    JSContext* cx, size_t nbytes, BufferContents contents) {
  MOZ_ASSERT(contents);
  MOZ_ASSERT(contents.kind() != INLINE_DATA);
  MOZ_ASSERT(contents.kind() != NO_DATA);
  MOZ_ASSERT(contents.kind() != WASM);

  // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
  if (!CheckArrayBufferTooLarge(cx, nbytes)) {
    return nullptr;
  }

  // Some |contents| kinds need to store extra data in the ArrayBuffer beyond a
  // data pointer.  If needed for the particular kind, add extra fixed slots to
  // the ArrayBuffer for use as raw storage to store such information.
  constexpr size_t reservedSlots = FixedLengthArrayBufferObject::RESERVED_SLOTS;

  size_t nAllocated = 0;
  size_t nslots = reservedSlots;
  if (contents.kind() == USER_OWNED) {
    // No accounting to do in this case.
  } else if (contents.kind() == EXTERNAL) {
    // Store the FreeInfo in the inline data slots so that we
    // don't use up slots for it in non-refcounted array buffers.
    constexpr size_t freeInfoSlots = HowMany(sizeof(FreeInfo), sizeof(Value));
    static_assert(
        reservedSlots + freeInfoSlots <= NativeObject::MAX_FIXED_SLOTS,
        "FreeInfo must fit in inline slots");
    nslots += freeInfoSlots;
  } else {
    // The ABO is taking ownership, so account the bytes against the zone.
    nAllocated = nbytes;
    if (contents.kind() == MAPPED) {
      nAllocated = RoundUp(nbytes, js::gc::SystemPageSize());
    } else {
      MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
                     contents.kind() == MALLOCED_UNKNOWN_ARENA,
                 "should have handled all possible callers' kinds");
    }
  }

  gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);

  AutoSetNewObjectMetadata metadata(cx);
  Rooted<ArrayBufferObject*> buffer(
      cx, NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr,
                                                             allocKind));
  if (!buffer) {
    return nullptr;
  }

  MOZ_ASSERT(!gc::IsInsideNursery(buffer),
             "ArrayBufferObject has a finalizer that must be called to not "
             "leak in some cases, so it can't be nursery-allocated");

  buffer->initialize(nbytes, contents);

  if (contents.kind() == MAPPED ||
      contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
      contents.kind() == MALLOCED_UNKNOWN_ARENA) {
    AddCellMemory(buffer, nAllocated, MemoryUse::ArrayBufferContents);
  }

  return buffer;
}

template <class ArrayBufferType, ArrayBufferObject::FillContents FillType>
/* static */ std::tuple<ArrayBufferType*, uint8_t*>
ArrayBufferObject::createUninitializedBufferAndData(
    JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata&,
    JS::Handle<JSObject*> proto) {
  MOZ_ASSERT(nbytes <= ArrayBufferObject::ByteLengthLimit,
             "caller must validate the byte count it passes");

  static_assert(std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject> ||
                std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>);

  // Try fitting the data inline with the object by repurposing fixed-slot
  // storage.  Add extra fixed slots if necessary to accomplish this, but don't
  // exceed the maximum number of fixed slots!
  size_t nslots = ArrayBufferType::RESERVED_SLOTS;
  ArrayBufferContents data;
  if (nbytes <= ArrayBufferType::MaxInlineBytes) {
    int newSlots = HowMany(nbytes, sizeof(Value));
    MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));

    nslots += newSlots;
  } else {
    data = FillType == FillContents::Uninitialized
               ? AllocateUninitializedArrayBufferContents(cx, nbytes)
               : AllocateArrayBufferContents(cx, nbytes);
    if (!data) {
      if (cx->brittleMode) {
        if (nbytes < INT32_MAX) {
          MOZ_DIAGNOSTIC_CRASH("ArrayBuffer allocation OOM < 2GB - 1");
        } else {
          MOZ_DIAGNOSTIC_ASSERT(
              false,
              "ArrayBuffer allocation OOM between 2GB and ByteLengthLimit");
        }
      }
      return {nullptr, nullptr};
    }
  }

  gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);

  auto* buffer = NewArrayBufferObject<ArrayBufferType>(cx, proto, allocKind);
  if (!buffer) {
    return {nullptr, nullptr};
  }

  MOZ_ASSERT(!gc::IsInsideNursery(buffer),
             "ArrayBufferObject has a finalizer that must be called to not "
             "leak in some cases, so it can't be nursery-allocated");

  if (data) {
    return {buffer, data.release()};
  }

  if constexpr (FillType == FillContents::Zero) {
    memset(buffer->inlineDataPointer(), 0, nbytes);
  }
  return {buffer, nullptr};
}

template <ArrayBufferObject::FillContents FillType>
/* static */ std::tuple<ArrayBufferObject*, uint8_t*>
ArrayBufferObject::createBufferAndData(
    JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata,
    JS::Handle<JSObject*> proto /* = nullptr */) {
  MOZ_ASSERT(nbytes <= ArrayBufferObject::ByteLengthLimit,
             "caller must validate the byte count it passes");

  auto [buffer, data] =
      createUninitializedBufferAndData<FixedLengthArrayBufferObject, FillType>(
          cx, nbytes, metadata, proto);
  if (!buffer) {
    return {nullptr, nullptr};
  }

  if (data) {
    buffer->initialize(
        nbytes, BufferContents::createMallocedArrayBufferContentsArena(data));
    AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
  } else {
    data = buffer->inlineDataPointer();
    buffer->initialize(nbytes, BufferContents::createInlineData(data));
  }
  return {buffer, data};
}

template <ArrayBufferObject::FillContents FillType>
/* static */ std::tuple<ResizableArrayBufferObject*, uint8_t*>
ResizableArrayBufferObject::createBufferAndData(
    JSContext* cx, size_t byteLength, size_t maxByteLength,
    AutoSetNewObjectMetadata& metadata, Handle<JSObject*> proto) {
  MOZ_ASSERT(byteLength <= maxByteLength);
  MOZ_ASSERT(maxByteLength <= ArrayBufferObject::ByteLengthLimit,
             "caller must validate the byte count it passes");

  // NOTE: The spec proposal for resizable ArrayBuffers suggests to use a
  // virtual memory based approach to avoid eagerly allocating the maximum byte
  // length. We don't yet support this and instead are allocating the maximum
  // byte length direct from the start.
  size_t nbytes = maxByteLength;

  auto [buffer, data] =
      createUninitializedBufferAndData<ResizableArrayBufferObject, FillType>(
          cx, nbytes, metadata, proto);
  if (!buffer) {
    return {nullptr, nullptr};
  }

  if (data) {
    buffer->initialize(
        byteLength, maxByteLength,
        BufferContents::createMallocedArrayBufferContentsArena(data));
    AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
  } else {
    data = buffer->inlineDataPointer();
    buffer->initialize(byteLength, maxByteLength,
                       BufferContents::createInlineData(data));
  }
  return {buffer, data};
}

/* static */ ArrayBufferObject* ArrayBufferObject::copy(
    JSContext* cx, size_t newByteLength,
    JS::Handle<ArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit,
             "caller must validate the byte count it passes");

  size_t sourceByteLength = source->byteLength();

  if (newByteLength > sourceByteLength) {
    // Copy into a larger buffer.
    AutoSetNewObjectMetadata metadata(cx);
    auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
        cx, newByteLength, metadata, nullptr);
    if (!buffer) {
      return nullptr;
    }

    std::copy_n(source->dataPointer(), sourceByteLength, toFill);

    return buffer;
  }

  // Copy into a smaller or same size buffer.
  AutoSetNewObjectMetadata metadata(cx);
  auto [buffer, toFill] = createBufferAndData<FillContents::Uninitialized>(
      cx, newByteLength, metadata, nullptr);
  if (!buffer) {
    return nullptr;
  }

  std::uninitialized_copy_n(source->dataPointer(), newByteLength, toFill);

  return buffer;
}

/* static */ ResizableArrayBufferObject* ResizableArrayBufferObject::copy(
    JSContext* cx, size_t newByteLength,
    JS::Handle<ResizableArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(newByteLength <= source->maxByteLength());

  size_t sourceByteLength = source->byteLength();
  size_t newMaxByteLength = source->maxByteLength();

  AutoSetNewObjectMetadata metadata(cx);
  auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
      cx, newByteLength, newMaxByteLength, metadata, nullptr);
  if (!buffer) {
    return nullptr;
  }

  size_t nbytes = std::min(newByteLength, sourceByteLength);
  std::copy_n(source->dataPointer(), nbytes, toFill);

  return buffer;
}

/* static */ ArrayBufferObject* ArrayBufferObject::copyAndDetach(
    JSContext* cx, size_t newByteLength,
    JS::Handle<ArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(!source->isLengthPinned());
  MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit,
             "caller must validate the byte count it passes");

  if (newByteLength > FixedLengthArrayBufferObject::MaxInlineBytes &&
      source->isMalloced()) {
    if (newByteLength == source->associatedBytes()) {
      return copyAndDetachSteal(cx, source);
    }
    if (source->bufferKind() ==
        ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA) {
      return copyAndDetachRealloc(cx, newByteLength, source);
    }
  }

  auto* newBuffer = ArrayBufferObject::copy(cx, newByteLength, source);
  if (!newBuffer) {
    return nullptr;
  }
  ArrayBufferObject::detach(cx, source);

  return newBuffer;
}

/* static */ ArrayBufferObject* ArrayBufferObject::copyAndDetachSteal(
    JSContext* cx, JS::Handle<ArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(!source->isLengthPinned());
  MOZ_ASSERT(source->isMalloced());

  size_t newByteLength = source->associatedBytes();
  MOZ_ASSERT(newByteLength > FixedLengthArrayBufferObject::MaxInlineBytes,
             "prefer copying small buffers");
  MOZ_ASSERT(source->byteLength() <= newByteLength,
             "source length is less-or-equal to |newByteLength|");

  auto* newBuffer = ArrayBufferObject::createEmpty(cx);
  if (!newBuffer) {
    return nullptr;
  }

  // Extract the contents from |source|.
  BufferContents contents = source->contents();
  MOZ_ASSERT(contents);
  MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
             contents.kind() == MALLOCED_UNKNOWN_ARENA);

  // Overwrite |source|'s data pointer *without* releasing the data.
  source->setDataPointer(BufferContents::createNoData());

  // Detach |source| now that doing so won't release |contents|.
  RemoveCellMemory(source, newByteLength, MemoryUse::ArrayBufferContents);
  ArrayBufferObject::detach(cx, source);

  // Set |newBuffer|'s contents to |source|'s original contents.
  newBuffer->initialize(newByteLength, contents);
  AddCellMemory(newBuffer, newByteLength, MemoryUse::ArrayBufferContents);

  return newBuffer;
}

/* static */ ArrayBufferObject* ArrayBufferObject::copyAndDetachRealloc(
    JSContext* cx, size_t newByteLength,
    JS::Handle<ArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(!source->isLengthPinned());
  MOZ_ASSERT(source->bufferKind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);
  MOZ_ASSERT(newByteLength > FixedLengthArrayBufferObject::MaxInlineBytes,
             "prefer copying small buffers");
  MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit,
             "caller must validate the byte count it passes");

  size_t oldByteLength = source->associatedBytes();
  MOZ_ASSERT(oldByteLength != newByteLength,
             "steal instead of realloc same size buffers");
  MOZ_ASSERT(source->byteLength() <= oldByteLength,
             "source length is less-or-equal to |oldByteLength|");

  Rooted<ArrayBufferObject*> newBuffer(cx, ArrayBufferObject::createEmpty(cx));
  if (!newBuffer) {
    return nullptr;
  }

  // Extract the contents from |source|.
  BufferContents contents = source->contents();
  MOZ_ASSERT(contents);
  MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);

  // Reallocate the data pointer.
  auto newData = ReallocateArrayBufferContents(cx, contents.data(),
                                               oldByteLength, newByteLength);
  if (!newData) {
    // If reallocation failed, the old pointer is still valid, so just return.
    return nullptr;
  }
  auto newContents =
      BufferContents::createMallocedArrayBufferContentsArena(newData.release());

  // Overwrite |source|'s data pointer *without* releasing the data.
  source->setDataPointer(BufferContents::createNoData());

  // Detach |source| now that doing so won't release |contents|.
  RemoveCellMemory(source, oldByteLength, MemoryUse::ArrayBufferContents);
  ArrayBufferObject::detach(cx, source);

  // Set |newBuffer|'s contents to |newContents|.
  newBuffer->initialize(newByteLength, newContents);
  AddCellMemory(newBuffer, newByteLength, MemoryUse::ArrayBufferContents);

  // Zero initialize the newly allocated memory, if necessary.
  if (newByteLength > oldByteLength) {
    size_t count = newByteLength - oldByteLength;
    std::uninitialized_fill_n(newContents.data() + oldByteLength, count, 0);
  }

  return newBuffer;
}

/* static */ ResizableArrayBufferObject*
ResizableArrayBufferObject::copyAndDetach(
    JSContext* cx, size_t newByteLength,
    JS::Handle<ResizableArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(!source->isLengthPinned());
  MOZ_ASSERT(newByteLength <= source->maxByteLength());

  if (source->maxByteLength() > ResizableArrayBufferObject::MaxInlineBytes &&
      source->isMalloced()) {
    return copyAndDetachSteal(cx, newByteLength, source);
  }

  auto* newBuffer = ResizableArrayBufferObject::copy(cx, newByteLength, source);
  if (!newBuffer) {
    return nullptr;
  }
  ArrayBufferObject::detach(cx, source);

  return newBuffer;
}

/* static */ ResizableArrayBufferObject*
ResizableArrayBufferObject::copyAndDetachSteal(
    JSContext* cx, size_t newByteLength,
    JS::Handle<ResizableArrayBufferObject*> source) {
  MOZ_ASSERT(!source->isDetached());
  MOZ_ASSERT(!source->isLengthPinned());
  MOZ_ASSERT(newByteLength <= source->maxByteLength());
  MOZ_ASSERT(source->isMalloced());

  size_t sourceByteLength = source->byteLength();
  size_t maxByteLength = source->maxByteLength();
  MOZ_ASSERT(maxByteLength > ResizableArrayBufferObject::MaxInlineBytes,
             "prefer copying small buffers");

  auto* newBuffer = ResizableArrayBufferObject::createEmpty(cx);
  if (!newBuffer) {
    return nullptr;
  }

  // Extract the contents from |source|.
  BufferContents contents = source->contents();
  MOZ_ASSERT(contents);
  MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
             contents.kind() == MALLOCED_UNKNOWN_ARENA);

  // Overwrite |source|'s data pointer *without* releasing the data.
  source->setDataPointer(BufferContents::createNoData());

  // Detach |source| now that doing so won't release |contents|.
  RemoveCellMemory(source, maxByteLength, MemoryUse::ArrayBufferContents);
  ArrayBufferObject::detach(cx, source);

  // Set |newBuffer|'s contents to |source|'s original contents.
  newBuffer->initialize(newByteLength, maxByteLength, contents);
  AddCellMemory(newBuffer, maxByteLength, MemoryUse::ArrayBufferContents);

  // Clear the bytes between `data[newByteLength..sourceByteLength]`.
  if (newByteLength < sourceByteLength) {
    size_t nbytes = sourceByteLength - newByteLength;
    memset(newBuffer->dataPointer() + newByteLength, 0, nbytes);
  }

  return newBuffer;
}

ArrayBufferObject* ArrayBufferObject::createZeroed(
    JSContext* cx, size_t nbytes, HandleObject proto /* = nullptr */) {
  // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
  if (!CheckArrayBufferTooLarge(cx, nbytes)) {
    MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "buffer too large");
    return nullptr;
  }

  AutoSetNewObjectMetadata metadata(cx);
  auto [buffer, toFill] =
      createBufferAndData<FillContents::Zero>(cx, nbytes, metadata, proto);
  (void)toFill;
  return buffer;
}

ResizableArrayBufferObject* ResizableArrayBufferObject::createZeroed(
    JSContext* cx, size_t byteLength, size_t maxByteLength,
    HandleObject proto /* = nullptr */) {
  // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
  if (!CheckArrayBufferTooLarge(cx, byteLength) ||
      !CheckArrayBufferTooLarge(cx, maxByteLength)) {
    return nullptr;
  }
  if (byteLength > maxByteLength) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM);
    return nullptr;
  }

  AutoSetNewObjectMetadata metadata(cx);
  auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
      cx, byteLength, maxByteLength, metadata, proto);
  (void)toFill;
  return buffer;
}

ArrayBufferObject* ArrayBufferObject::createEmpty(JSContext* cx) {
  AutoSetNewObjectMetadata metadata(cx);
  ArrayBufferObject* obj = NewArrayBufferObject(cx);
  if (!obj) {
    return nullptr;
  }

  obj->initialize(0, BufferContents::createNoData());
  return obj;
}

ResizableArrayBufferObject* ResizableArrayBufferObject::createEmpty(
    JSContext* cx) {
  AutoSetNewObjectMetadata metadata(cx);
  auto* obj = NewResizableArrayBufferObject(cx);
  if (!obj) {
    return nullptr;
  }

  obj->initialize(0, 0, BufferContents::createNoData());
  return obj;
}

ArrayBufferObject* ArrayBufferObject::createFromNewRawBuffer(
    JSContext* cx, WasmArrayRawBuffer* rawBuffer, size_t initialSize) {
  AutoSetNewObjectMetadata metadata(cx);
  ArrayBufferObject* buffer = NewArrayBufferObject(cx);
  if (!buffer) {
    WasmArrayRawBuffer::Release(rawBuffer->dataPointer());
    return nullptr;
  }

  MOZ_ASSERT(initialSize == rawBuffer->byteLength());

  auto contents = BufferContents::createWasm(rawBuffer->dataPointer());
  buffer->initialize(initialSize, contents);

  AddCellMemory(buffer, initialSize, MemoryUse::ArrayBufferContents);

  return buffer;
}

template <typename ArrayBufferType>
ArrayBufferType* ArrayBufferObject::createFromWasmObject(
    JSContext* cx, Handle<ArrayBufferObject*> donor) {
  AutoSetNewObjectMetadata metadata(cx);
  // Similar to NewResizableArrayBufferObject/NewArrayBufferObject
  constexpr auto allocKind =
      GetArrayBufferGCObjectKind(ArrayBufferType::RESERVED_SLOTS);
  ArrayBufferType* buffer =
      NewArrayBufferObject<ArrayBufferType>(cx, nullptr, allocKind);
  if (!buffer) {
    return nullptr;
  }

  RemoveCellMemory(donor, donor->byteLength(), MemoryUse::ArrayBufferContents);

  MOZ_RELEASE_ASSERT(donor->isWasm());
  // The only flag allowed to be set on the `donor` is the resizable flag.
  MOZ_RELEASE_ASSERT((donor->flags() & ~KIND_MASK & ~RESIZABLE) == 0);
  BufferContents contents(donor->dataPointer(), WASM);
  size_t byteLength = donor->byteLength();
  [[maybe_unused]] size_t maxByteLength = donor->wasmClampedMaxByteLength();

  // Overwrite |oldBuf|'s data pointer *without* releasing old data.
  donor->setDataPointer(BufferContents::createNoData());
  ArrayBufferObject::detach(cx, donor);

  if constexpr (std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>) {
    buffer->initialize(byteLength, maxByteLength, contents);
  } else {
    static_assert(
        std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject>);
    buffer->initialize(byteLength, contents);
  }

  AddCellMemory(buffer, buffer->byteLength(), MemoryUse::ArrayBufferContents);
  return buffer;
}

template FixedLengthArrayBufferObject*
ArrayBufferObject::createFromWasmObject<FixedLengthArrayBufferObject>(
    JSContext* cx, Handle<ArrayBufferObject*> donor);

template ResizableArrayBufferObject*
ArrayBufferObject::createFromWasmObject<ResizableArrayBufferObject>(
    JSContext* cx, Handle<ArrayBufferObject*> donor);

/* static */ uint8_t* ArrayBufferObject::stealMallocedContents(
    JSContext* cx, Handle<ArrayBufferObject*> buffer) {
  CheckStealPreconditions(buffer, cx);

  switch (buffer->bufferKind()) {
    case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
    case MALLOCED_UNKNOWN_ARENA: {
      uint8_t* stolenData = buffer->dataPointer();
      MOZ_ASSERT(stolenData);

      // Resizable buffers are initially allocated with their maximum
      // byte-length. When stealing the buffer contents shrink the allocated
      // memory to the actually used byte-length.
      if (buffer->isResizable()) {
        auto* resizableBuffer = &buffer->as<ResizableArrayBufferObject>();
        size_t byteLength = resizableBuffer->byteLength();
        size_t maxByteLength = resizableBuffer->maxByteLength();
        MOZ_ASSERT(byteLength <= maxByteLength);

        if (byteLength < maxByteLength) {
          auto newData = ReallocateArrayBufferContents(
              cx, stolenData, maxByteLength, byteLength);
          if (!newData) {
            // If reallocation failed, the old pointer is still valid. The
            // ArrayBuffer isn't detached and still owns the malloc'ed memory.
            return nullptr;
          }

          // The following code must be infallible, because the data pointer of
          // |buffer| is possibly no longer valid after the above realloc.

          stolenData = newData.release();
        }
      }

      RemoveCellMemory(buffer, buffer->associatedBytes(),
                       MemoryUse::ArrayBufferContents);

      // Overwrite the old data pointer *without* releasing the contents
      // being stolen.
      buffer->setDataPointer(BufferContents::createNoData());

      // Detach |buffer| now that doing so won't free |stolenData|.
      ArrayBufferObject::detach(cx, buffer);
      return stolenData;
    }

    case INLINE_DATA:
    case NO_DATA:
    case USER_OWNED:
    case MAPPED:
    case EXTERNAL: {
      // We can't use these data types directly.  Make a copy to return.
      ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer);
      if (!copiedData) {
        return nullptr;
      }

      // Detach |buffer|.  This immediately releases the currently owned
      // contents, freeing or unmapping data in the MAPPED and EXTERNAL cases.
      ArrayBufferObject::detach(cx, buffer);
      return copiedData.release();
    }

    case WASM:
      MOZ_ASSERT_UNREACHABLE(
          "wasm buffers aren't stealable except by a "
          "memory.grow operation that shouldn't call this "
          "function");
      return nullptr;
  }

  MOZ_ASSERT_UNREACHABLE("garbage kind computed");
  return nullptr;
}

/* static */ ArrayBufferObject::BufferContents
ArrayBufferObject::extractStructuredCloneContents(
    JSContext* cx, Handle<ArrayBufferObject*> buffer) {
  if (buffer->isLengthPinned()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_PINNED);
    return BufferContents::createFailed();
  }

  CheckStealPreconditions(buffer, cx);

  // We don't yet support extracting the contents of resizable buffers.
  MOZ_ASSERT(!buffer->isResizable(),
             "extracting the contents of resizable buffers not supported");

  BufferContents contents = buffer->contents();

  switch (contents.kind()) {
    case INLINE_DATA:
    case NO_DATA:
    case USER_OWNED: {
      ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer);
      if (!copiedData) {
        return BufferContents::createFailed();
      }

      ArrayBufferObject::detach(cx, buffer);
      return BufferContents::createMallocedArrayBufferContentsArena(
          copiedData.release());
    }

    case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
    case MALLOCED_UNKNOWN_ARENA:
    case MAPPED: {
      MOZ_ASSERT(contents);

      RemoveCellMemory(buffer, buffer->associatedBytes(),
                       MemoryUse::ArrayBufferContents);

      // Overwrite the old data pointer *without* releasing old data.
      buffer->setDataPointer(BufferContents::createNoData());

      // Detach |buffer| now that doing so won't release |oldContents|.
      ArrayBufferObject::detach(cx, buffer);
      return contents;
    }

    case WASM:
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_WASM_NO_TRANSFER);
      return BufferContents::createFailed();

    case EXTERNAL:
      MOZ_ASSERT_UNREACHABLE(
          "external ArrayBuffer shouldn't have passed the "
          "structured-clone preflighting");
      break;
  }

  MOZ_ASSERT_UNREACHABLE("garbage kind computed");
  return BufferContents::createFailed();
}

/* static */
bool ArrayBufferObject::ensureNonInline(JSContext* cx,
                                        Handle<ArrayBufferObject*> buffer) {
  if (buffer->isDetached() || buffer->isPreparedForAsmJS()) {
    return true;
  }

  if (buffer->isLengthPinned()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_PINNED);
    MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "ArrayBuffer length pinned");
    return false;
  }

  BufferContents inlineContents = buffer->contents();
  if (inlineContents.kind() != INLINE_DATA) {
    return true;
  }

  size_t nbytes = buffer->maxByteLength();
  ArrayBufferContents copy = NewCopiedBufferContents(cx, buffer);
  if (!copy) {
    return false;
  }
  BufferContents outOfLineContents =
      BufferContents::createMallocedArrayBufferContentsArena(copy.release());
  buffer->setDataPointer(outOfLineContents);
  AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);

  if (!buffer->firstView()) {
    return true;  // No views! Easy!
  }

  buffer->firstView()->as<ArrayBufferViewObject>().notifyBufferMoved(
      inlineContents.data(), outOfLineContents.data());

  auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
  if (InnerViewTable::ViewVector* views =
          innerViews.maybeViewsUnbarriered(buffer)) {
    for (JSObject* view : *views) {
      view->as<ArrayBufferViewObject>().notifyBufferMoved(
          inlineContents.data(), outOfLineContents.data());
    }
  }

  return true;
}

/* static */
void ArrayBufferObject::addSizeOfExcludingThis(
    JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info,
    JS::RuntimeSizes* runtimeSizes) {
  auto& buffer = obj->as<ArrayBufferObject>();
  switch (buffer.bufferKind()) {
    case INLINE_DATA:
      // Inline data's size should be reported by this object's size-class
      // reporting.
      break;
    case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
    case MALLOCED_UNKNOWN_ARENA:
      if (buffer.isPreparedForAsmJS()) {
        info->objectsMallocHeapElementsAsmJS +=
            mallocSizeOf(buffer.dataPointer());
      } else {
        info->objectsMallocHeapElementsNormal +=
            mallocSizeOf(buffer.dataPointer());
      }
      break;
    case NO_DATA:
      // No data is no memory.
      MOZ_ASSERT(buffer.dataPointer() == nullptr);
      break;
    case USER_OWNED:
      // User-owned data should be accounted for by the user.
      break;
    case EXTERNAL:
      // External data will be accounted for by the owner of the buffer,
      // not this view.
      break;
    case MAPPED:
      info->objectsNonHeapElementsNormal += buffer.byteLength();
      break;
    case WASM:
      if (!buffer.isDetached()) {
        info->objectsNonHeapElementsWasm += buffer.byteLength();
        if (runtimeSizes) {
          MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength());
          runtimeSizes->wasmGuardPages +=
              buffer.wasmMappedSize() - buffer.byteLength();
        }
      }
      break;
  }
}

/* static */
void ArrayBufferObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  obj->as<ArrayBufferObject>().releaseData(gcx);
}

/* static */
void ArrayBufferObject::copyData(ArrayBufferObject* toBuffer, size_t toIndex,
                                 ArrayBufferObject* fromBuffer,
                                 size_t fromIndex, size_t count) {
  MOZ_ASSERT(!toBuffer->isDetached());
  MOZ_ASSERT(toBuffer->byteLength() >= count);
  MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
  MOZ_ASSERT(!fromBuffer->isDetached());
  MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
  MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);

  memcpy(toBuffer->dataPointer() + toIndex,
         fromBuffer->dataPointer() + fromIndex, count);
}

template <class ArrayBufferType>
/* static */
size_t ArrayBufferObject::objectMoved(JSObject* obj, JSObject* old) {
  auto& dst = obj->as<ArrayBufferType>();
  const auto& src = old->as<ArrayBufferType>();

#ifdef DEBUG
  // Check the data pointer is not inside the nursery, but take account of the
  // fact that inline data pointers for zero length buffers can point to the end
  // of a chunk which can abut the start of the nursery.
  if (src.byteLength() != 0 || (uintptr_t(src.dataPointer()) & gc::ChunkMask)) {
    Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery();
    MOZ_ASSERT(!nursery.isInside(src.dataPointer()));
  }
#endif

  // Fix up possible inline data pointer.
  if (src.hasInlineData()) {
    dst.setFixedSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
  }

  return 0;
}

JSObject* ArrayBufferObject::firstView() {
  return getFixedSlot(FIRST_VIEW_SLOT).isObject()
             ? &getFixedSlot(FIRST_VIEW_SLOT).toObject()
             : nullptr;
}

void ArrayBufferObject::setFirstView(ArrayBufferViewObject* view) {
  setFixedSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
}

bool ArrayBufferObject::addView(JSContext* cx, ArrayBufferViewObject* view) {
  if (!firstView()) {
    setFirstView(view);
    return true;
  }

  return ObjectRealm::get(this).innerViews.get().addView(cx, this, view);
}

#if defined(DEBUG) || defined(JS_JITSPEW)

template <typename KnownF, typename UnknownF>
void BufferKindToString(ArrayBufferObject::BufferKind kind, KnownF known,
                        UnknownF unknown) {
  switch (kind) {
    case ArrayBufferObject::BufferKind::INLINE_DATA:
      known("INLINE_DATA");
      break;
    case ArrayBufferObject::BufferKind::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA:
      known("MALLOCED_ARRAYBUFFER_CONTENTS_ARENA");
      break;
    case ArrayBufferObject::BufferKind::NO_DATA:
      known("NO_DATA");
      break;
    case ArrayBufferObject::BufferKind::USER_OWNED:
      known("USER_OWNED");
      break;
    case ArrayBufferObject::BufferKind::WASM:
      known("WASM");
      break;
    case ArrayBufferObject::BufferKind::MAPPED:
      known("MAPPED");
      break;
    case ArrayBufferObject::BufferKind::EXTERNAL:
      known("EXTERNAL");
      break;
    case ArrayBufferObject::BufferKind::MALLOCED_UNKNOWN_ARENA:
      known("MALLOCED_UNKNOWN_ARENA");
      break;
    default:
      unknown(uint8_t(kind));
      break;
  }
}

template <typename KnownF, typename UnknownF>
void ForEachArrayBufferFlag(uint32_t flags, KnownF known, UnknownF unknown) {
  for (uint32_t i = ArrayBufferObject::ArrayBufferFlags::BUFFER_KIND_MASK + 1;
       i; i = i << 1) {
    if (!(flags & i)) {
      continue;
    }
    switch (ArrayBufferObject::ArrayBufferFlags(flags & i)) {
      case ArrayBufferObject::ArrayBufferFlags::DETACHED:
        known("DETACHED");
        break;
      case ArrayBufferObject::ArrayBufferFlags::FOR_ASMJS:
        known("FOR_ASMJS");
        break;
      default:
        unknown(i);
        break;
    }
  }
}

void ArrayBufferObject::dumpOwnFields(js::JSONPrinter& json) const {
  json.formatProperty("byteLength", "%zu",
                      size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate()));

  BufferKindToString(
      bufferKind(),
      [&](const char* name) { json.property("bufferKind", name); },
      [&](uint8_t value) {
        json.formatProperty("bufferKind", "Unknown(%02x)", value);
      });

  json.beginInlineListProperty("flags");
  ForEachArrayBufferFlag(
      flags(), [&](const char* name) { json.value("%s", name); },
      [&](uint32_t value) { json.value("Unknown(%08x)", value); });
  json.endInlineList();

  void* data = dataPointer();
  if (data) {
    json.formatProperty("data", "0x%p", data);
  } else {
    json.nullProperty("data");
  }
}

void ArrayBufferObject::dumpOwnStringContent(js::GenericPrinter& out) const {
  out.printf("byteLength=%zu, ",
             size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate()));

  BufferKindToString(
      bufferKind(),
      [&](const char* name) { out.printf("bufferKind=%s, ", name); },
      [&](uint8_t value) { out.printf("bufferKind=Unknown(%02x), ", value); });

  out.printf("flags=[");
  bool first = true;
  ForEachArrayBufferFlag(
      flags(),
      [&](const char* name) {
        if (!first) {
          out.put(",");
        }
        first = false;
        out.put(name);
      },
      [&](uint32_t value) {
        if (!first) {
          out.put(",");
        }
        first = false;
        out.printf("Unknown(%08x)", value);
      });
  out.put("], ");

  void* data = dataPointer();
  if (data) {
    out.printf("data=0x%p", data);
  } else {
    out.put("data=null");
  }
}
#endif

/*
 * InnerViewTable
 */

inline bool InnerViewTable::Views::empty() { return views.empty(); }

inline bool InnerViewTable::Views::hasNurseryViews() {
  return firstNurseryView < views.length();
}

bool InnerViewTable::Views::addView(ArrayBufferViewObject* view) {
  // Add the view to the list, ensuring that all nursery views are at end.

  if (!views.append(view)) {
    return false;
  }

  if (!gc::IsInsideNursery(view)) {
    // Move tenured views before |firstNurseryView|.
    if (firstNurseryView != views.length() - 1) {
      std::swap(views[firstNurseryView], views.back());
    }
    firstNurseryView++;
  }

  check();

  return true;
}

bool InnerViewTable::Views::sweepAfterMinorGC(JSTracer* trc) {
  return traceWeak(trc, firstNurseryView);
}

bool InnerViewTable::Views::traceWeak(JSTracer* trc, size_t startIndex) {
  // Use |trc| to trace the view vector from |startIndex| to the end, removing
  // dead views and updating |firstNurseryView|.

  size_t index = startIndex;
  bool sawNurseryView = false;
  views.mutableEraseIf(
      [&](auto& view) {
        if (!JS::GCPolicy<ViewVector::ElementType>::traceWeak(trc, &view)) {
          return true;
        }

        if (!sawNurseryView && gc::IsInsideNursery(view)) {
          sawNurseryView = true;
          firstNurseryView = index;
        }

        index++;
        return false;
      },
      startIndex);

  if (!sawNurseryView) {
    firstNurseryView = views.length();
  }

  check();

  return !views.empty();
}

inline void InnerViewTable::Views::check() {
#ifdef DEBUG
  MOZ_ASSERT(firstNurseryView <= views.length());
  if (views.length() < 100) {
    for (size_t i = 0; i < views.length(); i++) {
      MOZ_ASSERT(gc::IsInsideNursery(views[i]) == (i >= firstNurseryView));
    }
  }
#endif
}

bool InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer,
                             ArrayBufferViewObject* view) {
  // ArrayBufferObject entries are only added when there are multiple views.
  MOZ_ASSERT(buffer->firstView());
  MOZ_ASSERT(!gc::IsInsideNursery(buffer));

  // Ensure the buffer is present in the map, getting the list of views.
  auto ptr = map.lookupForAdd(buffer);
  if (!ptr && !map.add(ptr, buffer, Views(cx->zone()))) {
    ReportOutOfMemory(cx);
    return false;
  }
  Views& views = ptr->value();

  bool isNurseryView = gc::IsInsideNursery(view);
  bool hadNurseryViews = views.hasNurseryViews();
  if (!views.addView(view)) {
    ReportOutOfMemory(cx);
    return false;
  }

  // If we added the first nursery view, add the buffer to the list of buffers
  // which have nursery views.
  if (isNurseryView && !hadNurseryViews && nurseryKeysValid) {
#ifdef DEBUG
    if (nurseryKeys.length() < 100) {
      for (const auto& key : nurseryKeys) {
        MOZ_ASSERT(key != buffer);
      }
    }
#endif
    if (!nurseryKeys.append(buffer)) {
      nurseryKeysValid = false;
    }
  }

  return true;
}

InnerViewTable::ViewVector* InnerViewTable::maybeViewsUnbarriered(
    ArrayBufferObject* buffer) {
  auto ptr = map.lookup(buffer);
  if (ptr) {
    return &ptr->value().views;
  }
  return nullptr;
}

void InnerViewTable::removeViews(ArrayBufferObject* buffer) {
  auto ptr = map.lookup(buffer);
  MOZ_ASSERT(ptr);

  map.remove(ptr);
}

bool InnerViewTable::traceWeak(JSTracer* trc) {
  nurseryKeys.traceWeak(trc);
  map.traceWeak(trc);
  return true;
}

void InnerViewTable::sweepAfterMinorGC(JSTracer* trc) {
  MOZ_ASSERT(needsSweepAfterMinorGC());

  NurseryKeysVector keys;
  bool valid = true;
  std::swap(nurseryKeys, keys);
  std::swap(nurseryKeysValid, valid);

  // Use nursery keys vector if possible.
  if (valid) {
    for (ArrayBufferObject* buffer : keys) {
      MOZ_ASSERT(!gc::IsInsideNursery(buffer));
      auto ptr = map.lookup(buffer);
      if (ptr && !sweepViewsAfterMinorGC(trc, buffer, ptr->value())) {
        map.remove(ptr);
      }
    }
    return;
  }

  // Otherwise look at every map entry.
  for (ArrayBufferViewMap::Enum e(map); !e.empty(); e.popFront()) {
    MOZ_ASSERT(!gc::IsInsideNursery(e.front().key()));
    if (!sweepViewsAfterMinorGC(trc, e.front().key(), e.front().value())) {
      e.removeFront();
    }
  }
}

bool InnerViewTable::sweepViewsAfterMinorGC(JSTracer* trc,
                                            ArrayBufferObject* buffer,
                                            Views& views) {
  if (!views.sweepAfterMinorGC(trc)) {
    return false;  // No more views.
  }

  if (views.hasNurseryViews() && !nurseryKeys.append(buffer)) {
    nurseryKeysValid = false;
  }

  return true;
}

size_t InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
  size_t vectorSize = 0;
  for (auto r = map.all(); !r.empty(); r.popFront()) {
    vectorSize += r.front().value().views.sizeOfExcludingThis(mallocSizeOf);
  }

  return vectorSize + map.shallowSizeOfExcludingThis(mallocSizeOf) +
         nurseryKeys.sizeOfExcludingThis(mallocSizeOf);
}

template <>
bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const {
  return is<ArrayBufferObject>() || is<SharedArrayBufferObject>();
}

JS_PUBLIC_API size_t JS::GetArrayBufferByteLength(JSObject* obj) {
  ArrayBufferObject* aobj = obj->maybeUnwrapAs<ArrayBufferObject>();
  return aobj ? aobj->byteLength() : 0;
}

JS_PUBLIC_API uint8_t* JS::GetArrayBufferData(JSObject* obj,
                                              bool* isSharedMemory,
                                              const JS::AutoRequireNoGC&) {
  ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
  if (!aobj) {
    return nullptr;
  }
  *isSharedMemory = false;
  return aobj->dataPointer();
}

static ArrayBufferObject* UnwrapOrReportArrayBuffer(
    JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer) {
  JSObject* obj = CheckedUnwrapStatic(maybeArrayBuffer);
  if (!obj) {
    ReportAccessDenied(cx);
    return nullptr;
  }

  if (!obj->is<ArrayBufferObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_REQUIRED);
    return nullptr;
  }

  return &obj->as<ArrayBufferObject>();
}

JS_PUBLIC_API bool JS::DetachArrayBuffer(JSContext* cx, HandleObject obj) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);
  cx->check(obj);

  Rooted<ArrayBufferObject*> unwrappedBuffer(
      cx, UnwrapOrReportArrayBuffer(cx, obj));
  if (!unwrappedBuffer) {
    return false;
  }

  if (unwrappedBuffer->hasDefinedDetachKey()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_NO_TRANSFER);
    return false;
  }
  if (unwrappedBuffer->isLengthPinned()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_PINNED);
    return false;
  }

  AutoRealm ar(cx, unwrappedBuffer);
  ArrayBufferObject::detach(cx, unwrappedBuffer);
  return true;
}

JS_PUBLIC_API bool JS::HasDefinedArrayBufferDetachKey(JSContext* cx,
                                                      HandleObject obj,
                                                      bool* isDefined) {
  Rooted<ArrayBufferObject*> unwrappedBuffer(
      cx, UnwrapOrReportArrayBuffer(cx, obj));
  if (!unwrappedBuffer) {
    return false;
  }

  *isDefined = unwrappedBuffer->hasDefinedDetachKey();
  return true;
}

JS_PUBLIC_API bool JS::IsDetachedArrayBufferObject(JSObject* obj) {
  ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
  if (!aobj) {
    return false;
  }

  return aobj->isDetached();
}

JS_PUBLIC_API JSObject* JS::NewArrayBuffer(JSContext* cx, size_t nbytes) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);

  return ArrayBufferObject::createZeroed(cx, nbytes);
}

JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents(
    JSContext* cx, size_t nbytes,
    mozilla::UniquePtr<void, JS::FreePolicy> contents) {
  auto* result = NewArrayBufferWithContents(
      cx, nbytes, contents.get(),
      JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
  if (result) {
    // If and only if an ArrayBuffer is successfully created, ownership of
    // |contents| is transferred to the new ArrayBuffer.
    (void)contents.release();
  }
  return result;
}

JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents(
    JSContext* cx, size_t nbytes, void* data, NewArrayBufferOutOfMemory) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);
  MOZ_ASSERT_IF(!data, nbytes == 0);

  if (!data) {
    // Don't pass nulled contents to |createForContents|.
    return ArrayBufferObject::createZeroed(cx, 0);
  }

  using BufferContents = ArrayBufferObject::BufferContents;

  BufferContents contents = BufferContents::createMallocedUnknownArena(data);
  return ArrayBufferObject::createForContents(cx, nbytes, contents);
}

JS_PUBLIC_API JSObject* JS::CopyArrayBuffer(JSContext* cx,
                                            Handle<JSObject*> arrayBuffer) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);

  MOZ_ASSERT(arrayBuffer != nullptr);

  Rooted<ArrayBufferObject*> unwrappedSource(
      cx, UnwrapOrReportArrayBuffer(cx, arrayBuffer));
  if (!unwrappedSource) {
    return nullptr;
  }

  if (unwrappedSource->isDetached()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return nullptr;
  }

  return ArrayBufferObject::copy(cx, unwrappedSource->byteLength(),
                                 unwrappedSource);
}

JS_PUBLIC_API JSObject* JS::NewExternalArrayBuffer(
    JSContext* cx, size_t nbytes,
    mozilla::UniquePtr<void, JS::BufferContentsDeleter> contents) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);

  MOZ_ASSERT(contents);

  using BufferContents = ArrayBufferObject::BufferContents;

  BufferContents bufferContents = BufferContents::createExternal(
      contents.get(), contents.get_deleter().freeFunc(),
      contents.get_deleter().userData());
  auto* result =
      ArrayBufferObject::createForContents(cx, nbytes, bufferContents);
  if (result) {
    // If and only if an ArrayBuffer is successfully created, ownership of
    // |contents| is transferred to the new ArrayBuffer.
    (void)contents.release();
  }
  return result;
}

JS_PUBLIC_API JSObject* JS::NewArrayBufferWithUserOwnedContents(JSContext* cx,
                                                                size_t nbytes,
                                                                void* data) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);

  MOZ_ASSERT(data);

  using BufferContents = ArrayBufferObject::BufferContents;

  BufferContents contents = BufferContents::createUserOwned(data);
  return ArrayBufferObject::createForContents(cx, nbytes, contents);
}

JS_PUBLIC_API bool JS::IsArrayBufferObject(JSObject* obj) {
  return obj->canUnwrapAs<ArrayBufferObject>();
}

JS_PUBLIC_API bool JS::ArrayBufferHasData(JSObject* obj) {
  return !obj->unwrapAs<ArrayBufferObject>().isDetached();
}

JS_PUBLIC_API JSObject* JS::UnwrapArrayBuffer(JSObject* obj) {
  return obj->maybeUnwrapIf<ArrayBufferObject>();
}

JS_PUBLIC_API JSObject* JS::UnwrapSharedArrayBuffer(JSObject* obj) {
  return obj->maybeUnwrapIf<SharedArrayBufferObject>();
}

JS_PUBLIC_API void* JS::StealArrayBufferContents(JSContext* cx,
                                                 HandleObject obj) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);
  cx->check(obj);

  Rooted<ArrayBufferObject*> unwrappedBuffer(
      cx, UnwrapOrReportArrayBuffer(cx, obj));
  if (!unwrappedBuffer) {
    return nullptr;
  }

  if (unwrappedBuffer->isDetached()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return nullptr;
  }
  if (unwrappedBuffer->isLengthPinned()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_LENGTH_PINNED);
    return nullptr;
  }

  if (unwrappedBuffer->hasDefinedDetachKey()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_NO_TRANSFER);
    return nullptr;
  }

  AutoRealm ar(cx, unwrappedBuffer);
  return ArrayBufferObject::stealMallocedContents(cx, unwrappedBuffer);
}

JS_PUBLIC_API JSObject* JS::NewMappedArrayBufferWithContents(JSContext* cx,
                                                             size_t nbytes,
                                                             void* data) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);

  MOZ_ASSERT(data);

  using BufferContents = ArrayBufferObject::BufferContents;

  BufferContents contents = BufferContents::createMapped(data);
  return ArrayBufferObject::createForContents(cx, nbytes, contents);
}

JS_PUBLIC_API void* JS::CreateMappedArrayBufferContents(int fd, size_t offset,
                                                        size_t length) {
  return ArrayBufferObject::createMappedContents(fd, offset, length).data();
}

JS_PUBLIC_API void JS::ReleaseMappedArrayBufferContents(void* contents,
                                                        size_t length) {
  gc::DeallocateMappedContent(contents, length);
}

JS_PUBLIC_API bool JS::IsMappedArrayBufferObject(JSObject* obj) {
  ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
  if (!aobj) {
    return false;
  }

  return aobj->isMapped();
}

JS_PUBLIC_API JSObject* JS::GetObjectAsArrayBuffer(JSObject* obj,
                                                   size_t* length,
                                                   uint8_t** data) {
  ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>();
  if (!aobj) {
    return nullptr;
  }

  *length = aobj->byteLength();
  *data = aobj->dataPointer();

  return aobj;
}

JS_PUBLIC_API void JS::GetArrayBufferLengthAndData(JSObject* obj,
                                                   size_t* length,
                                                   bool* isSharedMemory,
                                                   uint8_t** data) {
  auto& aobj = obj->as<ArrayBufferObject>();
  *length = aobj.byteLength();
  *data = aobj.dataPointer();
  *isSharedMemory = false;
}

const JSClass* const JS::ArrayBuffer::FixedLengthUnsharedClass =
    &FixedLengthArrayBufferObject::class_;
const JSClass* const JS::ArrayBuffer::ResizableUnsharedClass =
    &ResizableArrayBufferObject::class_;
const JSClass* const JS::ArrayBuffer::FixedLengthSharedClass =
    &FixedLengthSharedArrayBufferObject::class_;
const JSClass* const JS::ArrayBuffer::GrowableSharedClass =
    &GrowableSharedArrayBufferObject::class_;

/* static */ JS::ArrayBuffer JS::ArrayBuffer::create(JSContext* cx,
                                                     size_t nbytes) {
  AssertHeapIsIdle();
  CHECK_THREAD(cx);
  return JS::ArrayBuffer(ArrayBufferObject::createZeroed(cx, nbytes));
}

mozilla::Span<uint8_t> JS::ArrayBuffer::getData(
    bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) {
  auto* buffer = obj->maybeUnwrapAs<ArrayBufferObjectMaybeShared>();
  if (!buffer) {
    return nullptr;
  }
  size_t length = buffer->byteLength();
  if (buffer->is<SharedArrayBufferObject>()) {
    *isSharedMemory = true;
    return {buffer->dataPointerEither().unwrap(), length};
  }
  *isSharedMemory = false;
  return {buffer->as<ArrayBufferObject>().dataPointer(), length};
};

JS::ArrayBuffer JS::ArrayBuffer::unwrap(JSObject* maybeWrapped) {
  if (!maybeWrapped) {
    return JS::ArrayBuffer(nullptr);
  }
  auto* ab = maybeWrapped->maybeUnwrapIf<ArrayBufferObjectMaybeShared>();
  return fromObject(ab);
}

bool JS::ArrayBufferCopyData(JSContext* cx, Handle<JSObject*> toBlock,
                             size_t toIndex, Handle<JSObject*> fromBlock,
                             size_t fromIndex, size_t count) {
  Rooted<ArrayBufferObjectMaybeShared*> unwrappedToBlock(
      cx, toBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>());
  if (!unwrappedToBlock) {
    ReportAccessDenied(cx);
    return false;
  }

  Rooted<ArrayBufferObjectMaybeShared*> unwrappedFromBlock(
      cx, fromBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>());
  if (!unwrappedFromBlock) {
    ReportAccessDenied(cx);
    return false;
  }

  // Verify that lengths still make sense and throw otherwise.
  if (toIndex + count < toIndex ||      // size_t overflow
      fromIndex + count < fromIndex ||  // size_t overflow
      toIndex + count > unwrappedToBlock->byteLength() ||
      fromIndex + count > unwrappedFromBlock->byteLength()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ARRAYBUFFER_COPY_RANGE);
    return false;
  }

  // If both are array buffers, can use ArrayBufferCopyData
  if (unwrappedToBlock->is<ArrayBufferObject>() &&
      unwrappedFromBlock->is<ArrayBufferObject>()) {
    Rooted<ArrayBufferObject*> toArray(
        cx, &unwrappedToBlock->as<ArrayBufferObject>());
    Rooted<ArrayBufferObject*> fromArray(
        cx, &unwrappedFromBlock->as<ArrayBufferObject>());
    ArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex, count);
    return true;
  }

  Rooted<ArrayBufferObjectMaybeShared*> toArray(
      cx, &unwrappedToBlock->as<ArrayBufferObjectMaybeShared>());
  Rooted<ArrayBufferObjectMaybeShared*> fromArray(
      cx, &unwrappedFromBlock->as<ArrayBufferObjectMaybeShared>());
  SharedArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex,
                                    count);

  return true;
}

// https://tc39.es/ecma262/#sec-clonearraybuffer
// We only support the case where cloneConstructor is %ArrayBuffer%. Note,
// this means that cloning a SharedArrayBuffer will produce an ArrayBuffer
JSObject* JS::ArrayBufferClone(JSContext* cx, Handle<JSObject*> srcBuffer,
                               size_t srcByteOffset, size_t srcLength) {
  MOZ_ASSERT(srcBuffer->is<ArrayBufferObjectMaybeShared>());

  // 2. (reordered) If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
  // exception.
  if (IsDetachedArrayBufferObject(srcBuffer)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TYPED_ARRAY_DETACHED);
    return nullptr;
  }

  // 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
  JS::RootedObject targetBuffer(cx, JS::NewArrayBuffer(cx, srcLength));
  if (!targetBuffer) {
    return nullptr;
  }

  // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
  // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].
  // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset,
  // srcLength).
  if (!ArrayBufferCopyData(cx, targetBuffer, 0, srcBuffer, srcByteOffset,
                           srcLength)) {
    return nullptr;
  }

  // 6. Return targetBuffer.
  return targetBuffer;
}
