js/src/ctypes/CTypes.cpp
author Jeff Walden <jwalden@mit.edu>
Mon, 04 Mar 2019 15:19:16 -0800
changeset 520218 f4e9e5429fd82f9c3787ca2d42798a2bf3d353f9
parent 520200 a7bb6e5afaf2d5afa0787244ff1b7fad07c8c9c1
child 521370 b9d87882a36584e2f17331b652cbc8681aee17ce
permissions -rw-r--r--
Bug 1532005 - Implement a new js/public/ArrayBuffer.h header to centralize functionality for creating and interacting with ArrayBuffers. r=sfink Differential Revision: https://phabricator.services.mozilla.com/D21827

/* -*- 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 "ctypes/CTypes.h"

#include "mozilla/FloatingPoint.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/Vector.h"
#include "mozilla/WrappingOperations.h"

#if defined(XP_UNIX)
#  include <errno.h>
#endif
#if defined(XP_WIN)
#  include <float.h>
#endif
#if defined(SOLARIS)
#  include <ieeefp.h>
#endif
#include <limits>
#include <math.h>
#include <stdint.h>
#ifdef HAVE_SSIZE_T
#  include <sys/types.h>
#endif
#include <type_traits>

#include "jsexn.h"
#include "jsnum.h"

#include "builtin/TypedObject.h"
#include "ctypes/Library.h"
#include "gc/FreeOp.h"
#include "gc/Policy.h"
#include "jit/AtomicOperations.h"
#include "js/ArrayBuffer.h"  // JS::{IsArrayBufferObject,GetArrayBufferData,GetArrayBuffer{ByteLength,Data}}
#include "js/CharacterEncoding.h"
#include "js/PropertySpec.h"
#include "js/SharedArrayBuffer.h"  // JS::{GetSharedArrayBuffer{ByteLength,Data},IsSharedArrayBufferObject}
#include "js/StableStringChars.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "js/Vector.h"
#include "util/Unicode.h"
#include "util/Windows.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"

#include "vm/JSObject-inl.h"

using namespace std;

using mozilla::IsAsciiAlpha;
using mozilla::IsAsciiDigit;

using JS::AutoCheckCannotGC;
using JS::AutoStableStringChars;

namespace js {
namespace ctypes {

static bool HasUnpairedSurrogate(const char16_t* chars, size_t nchars,
                                 char16_t* unpaired) {
  for (const char16_t* end = chars + nchars; chars != end; chars++) {
    char16_t c = *chars;
    if (unicode::LeadSurrogateMin <= c && c <= unicode::TrailSurrogateMax) {
      chars++;
      if (c >= unicode::TrailSurrogateMin || chars == end) {
        *unpaired = c;
        return true;
      }
      char16_t c2 = *chars;
      if (c2 < unicode::TrailSurrogateMin || c2 > unicode::TrailSurrogateMax) {
        *unpaired = c;
        return true;
      }
    }
  }
  return false;
}

bool ReportErrorIfUnpairedSurrogatePresent(JSContext* cx, JSLinearString* str) {
  if (str->hasLatin1Chars()) {
    return true;
  }

  char16_t unpaired;
  {
    JS::AutoCheckCannotGC nogc;
    if (!HasUnpairedSurrogate(str->twoByteChars(nogc), str->length(),
                              &unpaired)) {
      return true;
    }
  }

  char buffer[10];
  SprintfLiteral(buffer, "0x%x", unpaired);
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_BAD_SURROGATE_CHAR, buffer);
  return false;
}

/*******************************************************************************
** JSAPI function prototypes
*******************************************************************************/

// We use an enclosing struct here out of paranoia about the ability of gcc 4.4
// (and maybe 4.5) to correctly compile this if it were a template function.
// See also the comments in dom/workers/Events.cpp (and other adjacent files) by
// the |struct Property| there.
template <JS::IsAcceptableThis Test, JS::NativeImpl Impl>
struct Property {
  static bool Fun(JSContext* cx, unsigned argc, JS::Value* vp) {
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    return JS::CallNonGenericMethod<Test, Impl>(cx, args);
  }
};

static bool ConstructAbstract(JSContext* cx, unsigned argc, Value* vp);

namespace CType {
static bool ConstructData(JSContext* cx, unsigned argc, Value* vp);
static bool ConstructBasic(JSContext* cx, HandleObject obj,
                           const CallArgs& args);

static void Trace(JSTracer* trc, JSObject* obj);
static void Finalize(JSFreeOp* fop, JSObject* obj);

bool IsCType(HandleValue v);
bool IsCTypeOrProto(HandleValue v);

bool PrototypeGetter(JSContext* cx, const JS::CallArgs& args);
bool NameGetter(JSContext* cx, const JS::CallArgs& args);
bool SizeGetter(JSContext* cx, const JS::CallArgs& args);
bool PtrGetter(JSContext* cx, const JS::CallArgs& args);

static bool CreateArray(JSContext* cx, unsigned argc, Value* vp);
static bool ToString(JSContext* cx, unsigned argc, Value* vp);
static bool ToSource(JSContext* cx, unsigned argc, Value* vp);
static bool HasInstance(JSContext* cx, HandleObject obj, MutableHandleValue v,
                        bool* bp);

/*
 * Get the global "ctypes" object.
 *
 * |obj| must be a CType object.
 *
 * This function never returns nullptr.
 */
static JSObject* GetGlobalCTypes(JSContext* cx, JSObject* obj);

}  // namespace CType

namespace ABI {
bool IsABI(JSObject* obj);
static bool ToSource(JSContext* cx, unsigned argc, Value* vp);
}  // namespace ABI

namespace PointerType {
static bool Create(JSContext* cx, unsigned argc, Value* vp);
static bool ConstructData(JSContext* cx, HandleObject obj,
                          const CallArgs& args);

bool IsPointerType(HandleValue v);
bool IsPointer(HandleValue v);

bool TargetTypeGetter(JSContext* cx, const JS::CallArgs& args);
bool ContentsGetter(JSContext* cx, const JS::CallArgs& args);
bool ContentsSetter(JSContext* cx, const JS::CallArgs& args);

static bool IsNull(JSContext* cx, unsigned argc, Value* vp);
static bool Increment(JSContext* cx, unsigned argc, Value* vp);
static bool Decrement(JSContext* cx, unsigned argc, Value* vp);
// The following is not an instance function, since we don't want to expose
// arbitrary pointer arithmetic at this moment.
static bool OffsetBy(JSContext* cx, const CallArgs& args, int offset,
                     const char* name);
}  // namespace PointerType

namespace ArrayType {
bool IsArrayType(HandleValue v);
bool IsArrayOrArrayType(HandleValue v);

static bool Create(JSContext* cx, unsigned argc, Value* vp);
static bool ConstructData(JSContext* cx, HandleObject obj,
                          const CallArgs& args);

bool ElementTypeGetter(JSContext* cx, const JS::CallArgs& args);
bool LengthGetter(JSContext* cx, const JS::CallArgs& args);

static bool Getter(JSContext* cx, HandleObject obj, HandleId idval,
                   MutableHandleValue vp, bool* handled);
static bool Setter(JSContext* cx, HandleObject obj, HandleId idval,
                   HandleValue v, ObjectOpResult& result, bool* handled);
static bool AddressOfElement(JSContext* cx, unsigned argc, Value* vp);
}  // namespace ArrayType

namespace StructType {
bool IsStruct(HandleValue v);

static bool Create(JSContext* cx, unsigned argc, Value* vp);
static bool ConstructData(JSContext* cx, HandleObject obj,
                          const CallArgs& args);

bool FieldsArrayGetter(JSContext* cx, const JS::CallArgs& args);

enum { SLOT_FIELDNAME };

static bool FieldGetter(JSContext* cx, unsigned argc, Value* vp);
static bool FieldSetter(JSContext* cx, unsigned argc, Value* vp);
static bool AddressOfField(JSContext* cx, unsigned argc, Value* vp);
static bool Define(JSContext* cx, unsigned argc, Value* vp);
}  // namespace StructType

namespace FunctionType {
static bool Create(JSContext* cx, unsigned argc, Value* vp);
static bool ConstructData(JSContext* cx, HandleObject typeObj,
                          HandleObject dataObj, HandleObject fnObj,
                          HandleObject thisObj, HandleValue errVal);

static bool Call(JSContext* cx, unsigned argc, Value* vp);

bool IsFunctionType(HandleValue v);

bool ArgTypesGetter(JSContext* cx, const JS::CallArgs& args);
bool ReturnTypeGetter(JSContext* cx, const JS::CallArgs& args);
bool ABIGetter(JSContext* cx, const JS::CallArgs& args);
bool IsVariadicGetter(JSContext* cx, const JS::CallArgs& args);
}  // namespace FunctionType

namespace CClosure {
static void Trace(JSTracer* trc, JSObject* obj);
static void Finalize(JSFreeOp* fop, JSObject* obj);

// libffi callback
static void ClosureStub(ffi_cif* cif, void* result, void** args,
                        void* userData);

struct ArgClosure : public ScriptEnvironmentPreparer::Closure {
  ArgClosure(ffi_cif* cifArg, void* resultArg, void** argsArg,
             ClosureInfo* cinfoArg)
      : cif(cifArg), result(resultArg), args(argsArg), cinfo(cinfoArg) {}

  bool operator()(JSContext* cx) override;

  ffi_cif* cif;
  void* result;
  void** args;
  ClosureInfo* cinfo;
};
}  // namespace CClosure

namespace CData {
static void Finalize(JSFreeOp* fop, JSObject* obj);

bool ValueGetter(JSContext* cx, const JS::CallArgs& args);
bool ValueSetter(JSContext* cx, const JS::CallArgs& args);

static bool Address(JSContext* cx, unsigned argc, Value* vp);
static bool ReadString(JSContext* cx, unsigned argc, Value* vp);
static bool ReadStringReplaceMalformed(JSContext* cx, unsigned argc, Value* vp);
static bool ToSource(JSContext* cx, unsigned argc, Value* vp);
static JSString* GetSourceString(JSContext* cx, HandleObject typeObj,
                                 void* data);

bool ErrnoGetter(JSContext* cx, const JS::CallArgs& args);

#if defined(XP_WIN)
bool LastErrorGetter(JSContext* cx, const JS::CallArgs& args);
#endif  // defined(XP_WIN)
}  // namespace CData

namespace CDataFinalizer {
/*
 * Attach a C function as a finalizer to a JS object.
 *
 * This function is available from JS as |ctypes.withFinalizer|.
 *
 * JavaScript signature:
 * function(CData, CData):   CDataFinalizer
 *          value  finalizer finalizable
 *
 * Where |finalizer| is a one-argument function taking a value
 * with the same type as |value|.
 */
static bool Construct(JSContext* cx, unsigned argc, Value* vp);

/*
 * Private data held by |CDataFinalizer|.
 *
 * See also |enum CDataFinalizerSlot| for the slots of
 * |CDataFinalizer|.
 *
 * Note: the private data may be nullptr, if |dispose|, |forget| or the
 * finalizer has already been called.
 */
struct Private {
  /*
   * The C data to pass to the code.
   * Finalization/|dispose|/|forget| release this memory.
   */
  void* cargs;

  /*
   * The total size of the buffer pointed by |cargs|
   */
  size_t cargs_size;

  /*
   * Low-level signature information.
   * Finalization/|dispose|/|forget| release this memory.
   */
  ffi_cif CIF;

  /*
   * The C function to invoke during finalization.
   * Do not deallocate this.
   */
  uintptr_t code;

  /*
   * A buffer for holding the return value.
   * Finalization/|dispose|/|forget| release this memory.
   */
  void* rvalue;
};

/*
 * Methods of instances of |CDataFinalizer|
 */
namespace Methods {
static bool Dispose(JSContext* cx, unsigned argc, Value* vp);
static bool Forget(JSContext* cx, unsigned argc, Value* vp);
static bool ReadString(JSContext* cx, unsigned argc, Value* vp);
static bool ToSource(JSContext* cx, unsigned argc, Value* vp);
static bool ToString(JSContext* cx, unsigned argc, Value* vp);
}  // namespace Methods

/*
 * Utility functions
 *
 * @return true if |obj| is a CDataFinalizer, false otherwise.
 */
static bool IsCDataFinalizer(JSObject* obj);

/*
 * Clean up the finalization information of a CDataFinalizer.
 *
 * Used by |Finalize|, |Dispose| and |Forget|.
 *
 * @param p The private information of the CDataFinalizer. If nullptr,
 * this function does nothing.
 * @param obj Either nullptr, if the object should not be cleaned up (i.e.
 * during finalization) or a CDataFinalizer JSObject. Always use nullptr
 * if you are calling from a finalizer.
 */
static void Cleanup(Private* p, JSObject* obj);

/*
 * Perform the actual call to the finalizer code.
 */
static void CallFinalizer(CDataFinalizer::Private* p, int* errnoStatus,
                          int32_t* lastErrorStatus);

/*
 * Return the CType of a CDataFinalizer object, or nullptr if the object
 * has been cleaned-up already.
 */
static JSObject* GetCType(JSContext* cx, JSObject* obj);

/*
 * Perform finalization of a |CDataFinalizer|
 */
static void Finalize(JSFreeOp* fop, JSObject* obj);

/*
 * Return the Value contained by this finalizer.
 *
 * Note that the Value is actually not recorded, but converted back from C.
 */
static bool GetValue(JSContext* cx, JSObject* obj, MutableHandleValue result);

}  // namespace CDataFinalizer

// Int64Base provides functions common to Int64 and UInt64.
namespace Int64Base {
JSObject* Construct(JSContext* cx, HandleObject proto, uint64_t data,
                    bool isUnsigned);

uint64_t GetInt(JSObject* obj);

bool ToString(JSContext* cx, JSObject* obj, const CallArgs& args,
              bool isUnsigned);

bool ToSource(JSContext* cx, JSObject* obj, const CallArgs& args,
              bool isUnsigned);

static void Finalize(JSFreeOp* fop, JSObject* obj);
}  // namespace Int64Base

namespace Int64 {
static bool Construct(JSContext* cx, unsigned argc, Value* vp);

static bool ToString(JSContext* cx, unsigned argc, Value* vp);
static bool ToSource(JSContext* cx, unsigned argc, Value* vp);

static bool Compare(JSContext* cx, unsigned argc, Value* vp);
static bool Lo(JSContext* cx, unsigned argc, Value* vp);
static bool Hi(JSContext* cx, unsigned argc, Value* vp);
static bool Join(JSContext* cx, unsigned argc, Value* vp);
}  // namespace Int64

namespace UInt64 {
static bool Construct(JSContext* cx, unsigned argc, Value* vp);

static bool ToString(JSContext* cx, unsigned argc, Value* vp);
static bool ToSource(JSContext* cx, unsigned argc, Value* vp);

static bool Compare(JSContext* cx, unsigned argc, Value* vp);
static bool Lo(JSContext* cx, unsigned argc, Value* vp);
static bool Hi(JSContext* cx, unsigned argc, Value* vp);
static bool Join(JSContext* cx, unsigned argc, Value* vp);
}  // namespace UInt64

/*******************************************************************************
** JSClass definitions and initialization functions
*******************************************************************************/

// Class representing the 'ctypes' object itself. This exists to contain the
// JSCTypesCallbacks set of function pointers.
static const JSClass sCTypesGlobalClass = {
    "ctypes", JSCLASS_HAS_RESERVED_SLOTS(CTYPESGLOBAL_SLOTS)};

static const JSClass sCABIClass = {"CABI",
                                   JSCLASS_HAS_RESERVED_SLOTS(CABI_SLOTS)};

// Class representing ctypes.{C,Pointer,Array,Struct,Function}Type.prototype.
// This exists to give said prototypes a class of "CType", and to provide
// reserved slots for stashing various other prototype objects.
static const JSClassOps sCTypeProtoClassOps = {
    nullptr, nullptr, nullptr,           nullptr, nullptr,
    nullptr, nullptr, ConstructAbstract, nullptr, ConstructAbstract};
static const JSClass sCTypeProtoClass = {
    "CType", JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS),
    &sCTypeProtoClassOps};

// Class representing ctypes.CData.prototype and the 'prototype' properties
// of CTypes. This exists to give said prototypes a class of "CData".
static const JSClass sCDataProtoClass = {"CData", 0};

static const JSClassOps sCTypeClassOps = {nullptr,
                                          nullptr,
                                          nullptr,
                                          nullptr,
                                          nullptr,
                                          nullptr,
                                          CType::Finalize,
                                          CType::ConstructData,
                                          CType::HasInstance,
                                          CType::ConstructData,
                                          CType::Trace};
static const JSClass sCTypeClass = {
    "CType",
    JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
    &sCTypeClassOps};

static const JSClassOps sCDataClassOps = {
    nullptr, nullptr,           nullptr,         nullptr,
    nullptr, nullptr,           CData::Finalize, FunctionType::Call,
    nullptr, FunctionType::Call};
static const JSClass sCDataClass = {
    "CData",
    JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
    &sCDataClassOps};

static const JSClassOps sCClosureClassOps = {nullptr,
                                             nullptr,
                                             nullptr,
                                             nullptr,
                                             nullptr,
                                             nullptr,
                                             CClosure::Finalize,
                                             nullptr,
                                             nullptr,
                                             nullptr,
                                             CClosure::Trace};
static const JSClass sCClosureClass = {
    "CClosure",
    JSCLASS_HAS_RESERVED_SLOTS(CCLOSURE_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
    &sCClosureClassOps};

/*
 * Class representing the prototype of CDataFinalizer.
 */
static const JSClass sCDataFinalizerProtoClass = {"CDataFinalizer", 0};

/*
 * Class representing instances of CDataFinalizer.
 *
 * Instances of CDataFinalizer have both private data (with type
 * |CDataFinalizer::Private|) and slots (see |CDataFinalizerSlots|).
 */
static const JSClassOps sCDataFinalizerClassOps = {nullptr,
                                                   nullptr,
                                                   nullptr,
                                                   nullptr,
                                                   nullptr,
                                                   nullptr,
                                                   CDataFinalizer::Finalize};
static const JSClass sCDataFinalizerClass = {
    "CDataFinalizer",
    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(CDATAFINALIZER_SLOTS) |
        JSCLASS_FOREGROUND_FINALIZE,
    &sCDataFinalizerClassOps};

#define CTYPESFN_FLAGS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)

#define CTYPESCTOR_FLAGS (CTYPESFN_FLAGS | JSFUN_CONSTRUCTOR)

#define CTYPESACC_FLAGS (JSPROP_ENUMERATE | JSPROP_PERMANENT)

#define CABIFN_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT)

#define CDATAFN_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT)

#define CDATAFINALIZERFN_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT)

static const JSPropertySpec sCTypeProps[] = {
    JS_PSG("name", (Property<CType::IsCType, CType::NameGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PSG("size", (Property<CType::IsCType, CType::SizeGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PSG("ptr", (Property<CType::IsCType, CType::PtrGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PSG("prototype",
           (Property<CType::IsCTypeOrProto, CType::PrototypeGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PS_END};

static const JSFunctionSpec sCTypeFunctions[] = {
    JS_FN("array", CType::CreateArray, 0, CTYPESFN_FLAGS),
    JS_FN("toString", CType::ToString, 0, CTYPESFN_FLAGS),
    JS_FN("toSource", CType::ToSource, 0, CTYPESFN_FLAGS), JS_FS_END};

static const JSFunctionSpec sCABIFunctions[] = {
    JS_FN("toSource", ABI::ToSource, 0, CABIFN_FLAGS),
    JS_FN("toString", ABI::ToSource, 0, CABIFN_FLAGS), JS_FS_END};

static const JSPropertySpec sCDataProps[] = {
    JS_PSGS("value", (Property<CData::IsCData, CData::ValueGetter>::Fun),
            (Property<CData::IsCData, CData::ValueSetter>::Fun),
            JSPROP_PERMANENT),
    JS_PS_END};

static const JSFunctionSpec sCDataFunctions[] = {
    JS_FN("address", CData::Address, 0, CDATAFN_FLAGS),
    JS_FN("readString", CData::ReadString, 0, CDATAFN_FLAGS),
    JS_FN("readStringReplaceMalformed", CData::ReadStringReplaceMalformed, 0,
          CDATAFN_FLAGS),
    JS_FN("toSource", CData::ToSource, 0, CDATAFN_FLAGS),
    JS_FN("toString", CData::ToSource, 0, CDATAFN_FLAGS),
    JS_FS_END};

static const JSFunctionSpec sCDataFinalizerFunctions[] = {
    JS_FN("dispose", CDataFinalizer::Methods::Dispose, 0,
          CDATAFINALIZERFN_FLAGS),
    JS_FN("forget", CDataFinalizer::Methods::Forget, 0, CDATAFINALIZERFN_FLAGS),
    JS_FN("readString", CDataFinalizer::Methods::ReadString, 0,
          CDATAFINALIZERFN_FLAGS),
    JS_FN("toString", CDataFinalizer::Methods::ToString, 0,
          CDATAFINALIZERFN_FLAGS),
    JS_FN("toSource", CDataFinalizer::Methods::ToSource, 0,
          CDATAFINALIZERFN_FLAGS),
    JS_FS_END};

static const JSFunctionSpec sPointerFunction =
    JS_FN("PointerType", PointerType::Create, 1, CTYPESCTOR_FLAGS);

static const JSPropertySpec sPointerProps[] = {
    JS_PSG("targetType",
           (Property<PointerType::IsPointerType,
                     PointerType::TargetTypeGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PS_END};

static const JSFunctionSpec sPointerInstanceFunctions[] = {
    JS_FN("isNull", PointerType::IsNull, 0, CTYPESFN_FLAGS),
    JS_FN("increment", PointerType::Increment, 0, CTYPESFN_FLAGS),
    JS_FN("decrement", PointerType::Decrement, 0, CTYPESFN_FLAGS), JS_FS_END};

static const JSPropertySpec sPointerInstanceProps[] = {
    JS_PSGS(
        "contents",
        (Property<PointerType::IsPointer, PointerType::ContentsGetter>::Fun),
        (Property<PointerType::IsPointer, PointerType::ContentsSetter>::Fun),
        JSPROP_PERMANENT),
    JS_PS_END};

static const JSFunctionSpec sArrayFunction =
    JS_FN("ArrayType", ArrayType::Create, 1, CTYPESCTOR_FLAGS);

static const JSPropertySpec sArrayProps[] = {
    JS_PSG(
        "elementType",
        (Property<ArrayType::IsArrayType, ArrayType::ElementTypeGetter>::Fun),
        CTYPESACC_FLAGS),
    JS_PSG(
        "length",
        (Property<ArrayType::IsArrayOrArrayType, ArrayType::LengthGetter>::Fun),
        CTYPESACC_FLAGS),
    JS_PS_END};

static const JSFunctionSpec sArrayInstanceFunctions[] = {
    JS_FN("addressOfElement", ArrayType::AddressOfElement, 1, CDATAFN_FLAGS),
    JS_FS_END};

static const JSPropertySpec sArrayInstanceProps[] = {
    JS_PSG(
        "length",
        (Property<ArrayType::IsArrayOrArrayType, ArrayType::LengthGetter>::Fun),
        JSPROP_PERMANENT),
    JS_PS_END};

static const JSFunctionSpec sStructFunction =
    JS_FN("StructType", StructType::Create, 2, CTYPESCTOR_FLAGS);

static const JSPropertySpec sStructProps[] = {
    JS_PSG("fields",
           (Property<StructType::IsStruct, StructType::FieldsArrayGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PS_END};

static const JSFunctionSpec sStructFunctions[] = {
    JS_FN("define", StructType::Define, 1, CDATAFN_FLAGS), JS_FS_END};

static const JSFunctionSpec sStructInstanceFunctions[] = {
    JS_FN("addressOfField", StructType::AddressOfField, 1, CDATAFN_FLAGS),
    JS_FS_END};

static const JSFunctionSpec sFunctionFunction =
    JS_FN("FunctionType", FunctionType::Create, 2, CTYPESCTOR_FLAGS);

static const JSPropertySpec sFunctionProps[] = {
    JS_PSG("argTypes",
           (Property<FunctionType::IsFunctionType,
                     FunctionType::ArgTypesGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PSG("returnType",
           (Property<FunctionType::IsFunctionType,
                     FunctionType::ReturnTypeGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PSG(
        "abi",
        (Property<FunctionType::IsFunctionType, FunctionType::ABIGetter>::Fun),
        CTYPESACC_FLAGS),
    JS_PSG("isVariadic",
           (Property<FunctionType::IsFunctionType,
                     FunctionType::IsVariadicGetter>::Fun),
           CTYPESACC_FLAGS),
    JS_PS_END};

static const JSFunctionSpec sFunctionInstanceFunctions[] = {
    JS_FN("call", js::fun_call, 1, CDATAFN_FLAGS),
    JS_FN("apply", js::fun_apply, 2, CDATAFN_FLAGS), JS_FS_END};

static const JSClass sInt64ProtoClass = {"Int64", 0};

static const JSClass sUInt64ProtoClass = {"UInt64", 0};

static const JSClassOps sInt64ClassOps = {
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, Int64Base::Finalize};

static const JSClass sInt64Class = {
    "Int64",
    JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
    &sInt64ClassOps};

static const JSClass sUInt64Class = {
    "UInt64",
    JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
    &sInt64ClassOps};

static const JSFunctionSpec sInt64StaticFunctions[] = {
    JS_FN("compare", Int64::Compare, 2, CTYPESFN_FLAGS),
    JS_FN("lo", Int64::Lo, 1, CTYPESFN_FLAGS),
    JS_FN("hi", Int64::Hi, 1, CTYPESFN_FLAGS),
    // "join" is defined specially; see InitInt64Class.
    JS_FS_END};

static const JSFunctionSpec sUInt64StaticFunctions[] = {
    JS_FN("compare", UInt64::Compare, 2, CTYPESFN_FLAGS),
    JS_FN("lo", UInt64::Lo, 1, CTYPESFN_FLAGS),
    JS_FN("hi", UInt64::Hi, 1, CTYPESFN_FLAGS),
    // "join" is defined specially; see InitInt64Class.
    JS_FS_END};

static const JSFunctionSpec sInt64Functions[] = {
    JS_FN("toString", Int64::ToString, 0, CTYPESFN_FLAGS),
    JS_FN("toSource", Int64::ToSource, 0, CTYPESFN_FLAGS), JS_FS_END};

static const JSFunctionSpec sUInt64Functions[] = {
    JS_FN("toString", UInt64::ToString, 0, CTYPESFN_FLAGS),
    JS_FN("toSource", UInt64::ToSource, 0, CTYPESFN_FLAGS), JS_FS_END};

static const JSPropertySpec sModuleProps[] = {
    JS_PSG("errno", (Property<IsCTypesGlobal, CData::ErrnoGetter>::Fun),
           JSPROP_PERMANENT),
#if defined(XP_WIN)
    JS_PSG("winLastError",
           (Property<IsCTypesGlobal, CData::LastErrorGetter>::Fun),
           JSPROP_PERMANENT),
#endif  // defined(XP_WIN)
    JS_PS_END};

static const JSFunctionSpec sModuleFunctions[] = {
    JS_FN("CDataFinalizer", CDataFinalizer::Construct, 2, CTYPESFN_FLAGS),
    JS_FN("open", Library::Open, 1, CTYPESFN_FLAGS),
    JS_FN("cast", CData::Cast, 2, CTYPESFN_FLAGS),
    JS_FN("getRuntime", CData::GetRuntime, 1, CTYPESFN_FLAGS),
    JS_FN("libraryName", Library::Name, 1, CTYPESFN_FLAGS),
    JS_FS_END};

// Wrapper for arrays, to intercept indexed gets/sets.
class CDataArrayProxyHandler : public ForwardingProxyHandler {
 public:
  static const CDataArrayProxyHandler singleton;
  static const char family;

  constexpr CDataArrayProxyHandler() : ForwardingProxyHandler(&family) {}

  bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
           MutableHandleValue vp) const override;
  bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
           HandleValue receiver, ObjectOpResult& result) const override;
};

const CDataArrayProxyHandler CDataArrayProxyHandler::singleton;
const char CDataArrayProxyHandler::family = 0;

bool CDataArrayProxyHandler::get(JSContext* cx, HandleObject proxy,
                                 HandleValue receiver, HandleId id,
                                 MutableHandleValue vp) const {
  RootedObject target(cx, proxy->as<ProxyObject>().target());
  bool handled = false;
  if (!ArrayType::Getter(cx, target, id, vp, &handled)) {
    return false;
  }
  if (handled) {
    return true;
  }
  return ForwardingProxyHandler::get(cx, proxy, receiver, id, vp);
}

bool CDataArrayProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
                                 HandleValue v, HandleValue receiver,
                                 ObjectOpResult& result) const {
  RootedObject target(cx, proxy->as<ProxyObject>().target());
  bool handled = false;
  if (!ArrayType::Setter(cx, target, id, v, result, &handled)) {
    return false;
  }
  if (handled) {
    return true;
  }
  return ForwardingProxyHandler::set(cx, proxy, id, v, receiver, result);
}

static JSObject* MaybeUnwrapArrayWrapper(JSObject* obj) {
  if (IsProxy(obj) &&
      obj->as<ProxyObject>().handler() == &CDataArrayProxyHandler::singleton) {
    return obj->as<ProxyObject>().target();
  }
  return obj;
}

static MOZ_ALWAYS_INLINE JSString* NewUCString(JSContext* cx,
                                               const AutoStringChars&& from) {
  return JS_NewUCStringCopyN(cx, from.begin(), from.length());
}

/*
 * Return a size rounded up to a multiple of a power of two.
 *
 * Note: |align| must be a power of 2.
 */
static MOZ_ALWAYS_INLINE size_t Align(size_t val, size_t align) {
  // Ensure that align is a power of two.
  MOZ_ASSERT(align != 0 && (align & (align - 1)) == 0);
  return ((val - 1) | (align - 1)) + 1;
}

static ABICode GetABICode(JSObject* obj) {
  // make sure we have an object representing a CABI class,
  // and extract the enumerated class type from the reserved slot.
  if (JS_GetClass(obj) != &sCABIClass) {
    return INVALID_ABI;
  }

  Value result = JS_GetReservedSlot(obj, SLOT_ABICODE);
  return ABICode(result.toInt32());
}

static const JSErrorFormatString ErrorFormatString[CTYPESERR_LIMIT] = {
#define MSG_DEF(name, count, exception, format) \
  {#name, format, count, exception},
#include "ctypes/ctypes.msg"
#undef MSG_DEF
};

static const JSErrorFormatString* GetErrorMessage(void* userRef,
                                                  const unsigned errorNumber) {
  if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT) {
    return &ErrorFormatString[errorNumber];
  }
  return nullptr;
}

static JS::UniqueChars EncodeUTF8(JSContext* cx, AutoString& str) {
  RootedString string(cx, NewUCString(cx, str.finish()));
  if (!string) {
    return nullptr;
  }
  return JS_EncodeStringToUTF8(cx, string);
}

static const char* CTypesToSourceForError(JSContext* cx, HandleValue val,
                                          JS::UniqueChars& bytes) {
  if (val.isObject()) {
    RootedObject obj(cx, &val.toObject());
    if (CType::IsCType(obj) || CData::IsCDataMaybeUnwrap(&obj)) {
      RootedValue v(cx, ObjectValue(*obj));
      RootedString str(cx, JS_ValueToSource(cx, v));
      bytes = JS_EncodeStringToUTF8(cx, str);
      return bytes.get();
    }
  }
  return ValueToSourceForError(cx, val, bytes);
}

static void BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj,
                                          HandleString nameStr,
                                          unsigned ptrCount,
                                          AutoString& source);

static void BuildCStyleTypeSource(JSContext* cx, JSObject* typeObj_,
                                  AutoString& source) {
  RootedObject typeObj(cx, typeObj_);

  MOZ_ASSERT(CType::IsCType(typeObj));

  switch (CType::GetTypeCode(typeObj)) {
#define BUILD_SOURCE(name, fromType, ffiType) \
  case TYPE_##name:                           \
    AppendString(cx, source, #name);          \
    break;
    CTYPES_FOR_EACH_TYPE(BUILD_SOURCE)
#undef BUILD_SOURCE
    case TYPE_void_t:
      AppendString(cx, source, "void");
      break;
    case TYPE_pointer: {
      unsigned ptrCount = 0;
      TypeCode type;
      RootedObject baseTypeObj(cx, typeObj);
      do {
        baseTypeObj = PointerType::GetBaseType(baseTypeObj);
        ptrCount++;
        type = CType::GetTypeCode(baseTypeObj);
      } while (type == TYPE_pointer || type == TYPE_array);
      if (type == TYPE_function) {
        BuildCStyleFunctionTypeSource(cx, baseTypeObj, nullptr, ptrCount,
                                      source);
        break;
      }
      BuildCStyleTypeSource(cx, baseTypeObj, source);
      AppendChars(source, '*', ptrCount);
      break;
    }
    case TYPE_struct: {
      RootedString name(cx, CType::GetName(cx, typeObj));
      AppendString(cx, source, "struct ");
      AppendString(cx, source, name);
      break;
    }
    case TYPE_function:
      BuildCStyleFunctionTypeSource(cx, typeObj, nullptr, 0, source);
      break;
    case TYPE_array:
      MOZ_CRASH("TYPE_array shouldn't appear in function type");
  }
}

static void BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj,
                                          HandleString nameStr,
                                          unsigned ptrCount,
                                          AutoString& source) {
  MOZ_ASSERT(CType::IsCType(typeObj));

  FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj);
  BuildCStyleTypeSource(cx, fninfo->mReturnType, source);
  AppendString(cx, source, " ");
  if (nameStr) {
    MOZ_ASSERT(ptrCount == 0);
    AppendString(cx, source, nameStr);
  } else if (ptrCount) {
    AppendString(cx, source, "(");
    AppendChars(source, '*', ptrCount);
    AppendString(cx, source, ")");
  }
  AppendString(cx, source, "(");
  if (fninfo->mArgTypes.length() > 0) {
    for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) {
      BuildCStyleTypeSource(cx, fninfo->mArgTypes[i], source);
      if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) {
        AppendString(cx, source, ", ");
      }
    }
    if (fninfo->mIsVariadic) {
      AppendString(cx, source, "...");
    }
  }
  AppendString(cx, source, ")");
}

static void BuildFunctionTypeSource(JSContext* cx, HandleObject funObj,
                                    AutoString& source) {
  MOZ_ASSERT(CData::IsCData(funObj) || CType::IsCType(funObj));

  if (CData::IsCData(funObj)) {
    Value slot = JS_GetReservedSlot(funObj, SLOT_REFERENT);
    if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) {
      slot = JS_GetReservedSlot(funObj, SLOT_FUNNAME);
      MOZ_ASSERT(!slot.isUndefined());
      RootedObject typeObj(cx, CData::GetCType(funObj));
      RootedObject baseTypeObj(cx, PointerType::GetBaseType(typeObj));
      RootedString nameStr(cx, slot.toString());
      BuildCStyleFunctionTypeSource(cx, baseTypeObj, nameStr, 0, source);
      return;
    }
  }

  RootedValue funVal(cx, ObjectValue(*funObj));
  RootedString funcStr(cx, JS_ValueToSource(cx, funVal));
  if (!funcStr) {
    JS_ClearPendingException(cx);
    AppendString(cx, source, "<<error converting function to string>>");
    return;
  }
  AppendString(cx, source, funcStr);
}

enum class ConversionType {
  Argument = 0,
  Construct,
  Finalizer,
  Return,
  Setter
};

static void BuildConversionPosition(JSContext* cx, ConversionType convType,
                                    HandleObject funObj, unsigned argIndex,
                                    AutoString& source) {
  switch (convType) {
    case ConversionType::Argument: {
      MOZ_ASSERT(funObj);

      AppendString(cx, source, " at argument ");
      AppendUInt(source, argIndex + 1);
      AppendString(cx, source, " of ");
      BuildFunctionTypeSource(cx, funObj, source);
      break;
    }
    case ConversionType::Finalizer:
      MOZ_ASSERT(funObj);

      AppendString(cx, source, " at argument 1 of ");
      BuildFunctionTypeSource(cx, funObj, source);
      break;
    case ConversionType::Return:
      MOZ_ASSERT(funObj);

      AppendString(cx, source, " at the return value of ");
      BuildFunctionTypeSource(cx, funObj, source);
      break;
    default:
      MOZ_ASSERT(!funObj);
      break;
  }
}

static JSFlatString* GetFieldName(HandleObject structObj, unsigned fieldIndex) {
  const FieldInfoHash* fields = StructType::GetFieldInfo(structObj);
  for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
    if (r.front().value().mIndex == fieldIndex) {
      return (&r.front())->key();
    }
  }
  return nullptr;
}

static void BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort,
                            AutoString& result);

static JS::UniqueChars TypeSourceForError(JSContext* cx, JSObject* typeObj) {
  AutoString source;
  BuildTypeSource(cx, typeObj, true, source);
  if (!source) {
    return nullptr;
  }
  return EncodeUTF8(cx, source);
}

static JS::UniqueChars FunctionTypeSourceForError(JSContext* cx,
                                                  HandleObject funObj) {
  AutoString funSource;
  BuildFunctionTypeSource(cx, funObj, funSource);
  if (!funSource) {
    return nullptr;
  }
  return EncodeUTF8(cx, funSource);
}

static JS::UniqueChars ConversionPositionForError(JSContext* cx,
                                                  ConversionType convType,
                                                  HandleObject funObj,
                                                  unsigned argIndex) {
  AutoString posSource;
  BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
  if (!posSource) {
    return nullptr;
  }
  return EncodeUTF8(cx, posSource);
}

class IndexCString final {
  char indexStr[21];  // space for UINT64_MAX plus terminating null
  static_assert(sizeof(size_t) <= 8, "index array too small");

 public:
  explicit IndexCString(size_t index) {
    SprintfLiteral(indexStr, "%zu", index);
  }

  const char* get() const { return indexStr; }
};

static bool ConvError(JSContext* cx, const char* expectedStr,
                      HandleValue actual, ConversionType convType,
                      HandleObject funObj = nullptr, unsigned argIndex = 0,
                      HandleObject arrObj = nullptr, unsigned arrIndex = 0) {
  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  if (arrObj) {
    MOZ_ASSERT(CType::IsCType(arrObj));

    switch (CType::GetTypeCode(arrObj)) {
      case TYPE_array: {
        MOZ_ASSERT(!funObj);

        IndexCString indexStr(arrIndex);

        JS::UniqueChars arrStr = TypeSourceForError(cx, arrObj);
        if (!arrStr) {
          return false;
        }

        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                 CTYPESMSG_CONV_ERROR_ARRAY, valStr,
                                 indexStr.get(), arrStr.get());
        break;
      }
      case TYPE_struct: {
        RootedString name(cx, GetFieldName(arrObj, arrIndex));
        MOZ_ASSERT(name);
        JS::UniqueChars nameStr = JS_EncodeStringToUTF8(cx, name);
        if (!nameStr) {
          return false;
        }

        JS::UniqueChars structStr = TypeSourceForError(cx, arrObj);
        if (!structStr) {
          return false;
        }

        JS::UniqueChars posStr;
        if (funObj) {
          posStr = ConversionPositionForError(cx, convType, funObj, argIndex);
          if (!posStr) {
            return false;
          }
        }

        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                 CTYPESMSG_CONV_ERROR_STRUCT, valStr,
                                 nameStr.get(), expectedStr, structStr.get(),
                                 (posStr ? posStr.get() : ""));
        break;
      }
      default:
        MOZ_CRASH("invalid arrObj value");
    }
    return false;
  }

  switch (convType) {
    case ConversionType::Argument: {
      MOZ_ASSERT(funObj);

      IndexCString indexStr(argIndex + 1);

      JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj);
      if (!funStr) {
        return false;
      }

      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr.get(),
                               funStr.get());
      break;
    }
    case ConversionType::Finalizer: {
      MOZ_ASSERT(funObj);

      JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj);
      if (!funStr) {
        return false;
      }

      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               CTYPESMSG_CONV_ERROR_FIN, valStr, funStr.get());
      break;
    }
    case ConversionType::Return: {
      MOZ_ASSERT(funObj);

      JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj);
      if (!funStr) {
        return false;
      }

      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               CTYPESMSG_CONV_ERROR_RET, valStr, funStr.get());
      break;
    }
    case ConversionType::Setter:
    case ConversionType::Construct:
      MOZ_ASSERT(!funObj);

      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               CTYPESMSG_CONV_ERROR_SET, valStr, expectedStr);
      break;
  }

  return false;
}

static bool ConvError(JSContext* cx, HandleObject expectedType,
                      HandleValue actual, ConversionType convType,
                      HandleObject funObj = nullptr, unsigned argIndex = 0,
                      HandleObject arrObj = nullptr, unsigned arrIndex = 0) {
  MOZ_ASSERT(CType::IsCType(expectedType));

  JS::UniqueChars expectedStr = TypeSourceForError(cx, expectedType);
  if (!expectedStr) {
    return false;
  }

  return ConvError(cx, expectedStr.get(), actual, convType, funObj, argIndex,
                   arrObj, arrIndex);
}

static bool ArgumentConvError(JSContext* cx, HandleValue actual,
                              const char* funStr, unsigned argIndex) {
  MOZ_ASSERT(JS::StringIsASCII(funStr));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  IndexCString indexStr(argIndex + 1);

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr.get(),
                           funStr);
  return false;
}

static bool ArgumentLengthError(JSContext* cx, const char* fun,
                                const char* count, const char* s) {
  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_WRONG_ARG_LENGTH, fun, count, s);
  return false;
}

static bool ArrayLengthMismatch(JSContext* cx, unsigned expectedLength,
                                HandleObject arrObj, unsigned actualLength,
                                HandleValue actual, ConversionType convType) {
  MOZ_ASSERT(arrObj && CType::IsCType(arrObj));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  IndexCString expectedLengthStr(expectedLength);
  IndexCString actualLengthStr(actualLength);

  JS::UniqueChars arrStr = TypeSourceForError(cx, arrObj);
  if (!arrStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_ARRAY_MISMATCH, valStr, arrStr.get(),
                           expectedLengthStr.get(), actualLengthStr.get());
  return false;
}

static bool ArrayLengthOverflow(JSContext* cx, unsigned expectedLength,
                                HandleObject arrObj, unsigned actualLength,
                                HandleValue actual, ConversionType convType) {
  MOZ_ASSERT(arrObj && CType::IsCType(arrObj));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  IndexCString expectedLengthStr(expectedLength);
  IndexCString actualLengthStr(actualLength);

  JS::UniqueChars arrStr = TypeSourceForError(cx, arrObj);
  if (!arrStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_ARRAY_OVERFLOW, valStr, arrStr.get(),
                           expectedLengthStr.get(), actualLengthStr.get());
  return false;
}

static bool ArgumentRangeMismatch(JSContext* cx, const char* func,
                                  const char* range) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_ARG_RANGE_MISMATCH, func, range);
  return false;
}

static bool ArgumentTypeMismatch(JSContext* cx, const char* arg,
                                 const char* func, const char* type) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_ARG_TYPE_MISMATCH, arg, func, type);
  return false;
}

static bool CannotConstructError(JSContext* cx, const char* type) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_CANNOT_CONSTRUCT, type);
  return false;
}

static bool DuplicateFieldError(JSContext* cx, Handle<JSFlatString*> name) {
  JS::UniqueChars nameStr = JS_EncodeStringToUTF8(cx, name);
  if (!nameStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_DUPLICATE_FIELD, nameStr.get());
  return false;
}

static bool EmptyFinalizerCallError(JSContext* cx, const char* funName) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_EMPTY_FIN_CALL, funName);
  return false;
}

static bool EmptyFinalizerError(JSContext* cx, ConversionType convType,
                                HandleObject funObj = nullptr,
                                unsigned argIndex = 0) {
  JS::UniqueChars posStr;
  if (funObj) {
    posStr = ConversionPositionForError(cx, convType, funObj, argIndex);
    if (!posStr) {
      return false;
    }
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_EMPTY_FIN,
                           (posStr ? posStr.get() : ""));
  return false;
}

static bool FieldCountMismatch(JSContext* cx, unsigned expectedCount,
                               HandleObject structObj, unsigned actualCount,
                               HandleValue actual, ConversionType convType,
                               HandleObject funObj = nullptr,
                               unsigned argIndex = 0) {
  MOZ_ASSERT(structObj && CType::IsCType(structObj));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  JS::UniqueChars structStr = TypeSourceForError(cx, structObj);
  if (!structStr) {
    return false;
  }

  IndexCString expectedCountStr(expectedCount);
  IndexCString actualCountStr(actualCount);

  JS::UniqueChars posStr;
  if (funObj) {
    posStr = ConversionPositionForError(cx, convType, funObj, argIndex);
    if (!posStr) {
      return false;
    }
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_MISMATCH, valStr, structStr.get(),
                           expectedCountStr.get(), actualCountStr.get(),
                           (posStr ? posStr.get() : ""));
  return false;
}

static bool FieldDescriptorCountError(JSContext* cx, HandleValue typeVal,
                                      size_t length) {
  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes);
  if (!valStr) {
    return false;
  }

  IndexCString lengthStr(length);

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_DESC_COUNT, valStr, lengthStr.get());
  return false;
}

static bool FieldDescriptorNameError(JSContext* cx, HandleId id) {
  JS::UniqueChars idBytes;
  RootedValue idVal(cx, IdToValue(id));
  const char* propStr = CTypesToSourceForError(cx, idVal, idBytes);
  if (!propStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_DESC_NAME, propStr);
  return false;
}

static bool FieldDescriptorSizeError(JSContext* cx, HandleObject typeObj,
                                     HandleId id) {
  RootedValue typeVal(cx, ObjectValue(*typeObj));
  JS::UniqueChars typeBytes;
  const char* typeStr = CTypesToSourceForError(cx, typeVal, typeBytes);
  if (!typeStr) {
    return false;
  }

  RootedString idStr(cx, IdToString(cx, id));
  JS::UniqueChars propStr = JS_EncodeStringToUTF8(cx, idStr);
  if (!propStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_DESC_SIZE, typeStr, propStr.get());
  return false;
}

static bool FieldDescriptorNameTypeError(JSContext* cx, HandleValue typeVal) {
  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_DESC_NAMETYPE, valStr);
  return false;
}

static bool FieldDescriptorTypeError(JSContext* cx, HandleValue poroVal,
                                     HandleId id) {
  JS::UniqueChars typeBytes;
  const char* typeStr = CTypesToSourceForError(cx, poroVal, typeBytes);
  if (!typeStr) {
    return false;
  }

  RootedString idStr(cx, IdToString(cx, id));
  JS::UniqueChars propStr = JS_EncodeStringToUTF8(cx, idStr);
  if (!propStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_DESC_TYPE, typeStr, propStr.get());
  return false;
}

static bool FieldMissingError(JSContext* cx, JSObject* typeObj,
                              JSFlatString* name_) {
  JS::UniqueChars typeBytes;
  RootedString name(cx, name_);
  RootedValue typeVal(cx, ObjectValue(*typeObj));
  const char* typeStr = CTypesToSourceForError(cx, typeVal, typeBytes);
  if (!typeStr) {
    return false;
  }

  JS::UniqueChars nameStr = JS_EncodeStringToUTF8(cx, name);
  if (!nameStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIELD_MISSING, typeStr, nameStr.get());
  return false;
}

static bool FinalizerSizeError(JSContext* cx, HandleObject funObj,
                               HandleValue actual) {
  MOZ_ASSERT(CType::IsCType(funObj));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj);
  if (!funStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_FIN_SIZE_ERROR, funStr.get(), valStr);
  return false;
}

static bool FunctionArgumentLengthMismatch(
    JSContext* cx, unsigned expectedCount, unsigned actualCount,
    HandleObject funObj, HandleObject typeObj, bool isVariadic) {
  JS::UniqueChars funStr;
  Value slot = JS_GetReservedSlot(funObj, SLOT_REFERENT);
  if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) {
    funStr = FunctionTypeSourceForError(cx, funObj);
  } else {
    funStr = FunctionTypeSourceForError(cx, typeObj);
  }
  if (!funStr) {
    return false;
  }

  IndexCString expectedCountStr(expectedCount);
  IndexCString actualCountStr(actualCount);

  const char* variadicStr = isVariadic ? " or more" : "";

  JS_ReportErrorNumberUTF8(
      cx, GetErrorMessage, nullptr, CTYPESMSG_ARG_COUNT_MISMATCH, funStr.get(),
      expectedCountStr.get(), variadicStr, actualCountStr.get());
  return false;
}

static bool FunctionArgumentTypeError(JSContext* cx, uint32_t index,
                                      HandleValue typeVal, const char* reason) {
  MOZ_ASSERT(JS::StringIsASCII(reason));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes);
  if (!valStr) {
    return false;
  }

  IndexCString indexStr(index + 1);

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_ARG_TYPE_ERROR, indexStr.get(), reason,
                           valStr);
  return false;
}

static bool FunctionReturnTypeError(JSContext* cx, HandleValue type,
                                    const char* reason) {
  MOZ_ASSERT(JS::StringIsASCII(reason));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, type, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_RET_TYPE_ERROR, reason, valStr);
  return false;
}

static bool IncompatibleCallee(JSContext* cx, const char* funName,
                               HandleObject actualObj) {
  MOZ_ASSERT(JS::StringIsASCII(funName));

  JS::UniqueChars valBytes;
  RootedValue val(cx, ObjectValue(*actualObj));
  const char* valStr = CTypesToSourceForError(cx, val, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_INCOMPATIBLE_CALLEE, funName, valStr);
  return false;
}

static bool IncompatibleThisProto(JSContext* cx, const char* funName,
                                  const char* actualType) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_INCOMPATIBLE_THIS, funName, actualType);
  return false;
}

static bool IncompatibleThisProto(JSContext* cx, const char* funName,
                                  HandleValue actualVal) {
  MOZ_ASSERT(JS::StringIsASCII(funName));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actualVal, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_INCOMPATIBLE_THIS_VAL, funName,
                           "incompatible object", valStr);
  return false;
}

static bool IncompatibleThisType(JSContext* cx, const char* funName,
                                 const char* actualType) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_INCOMPATIBLE_THIS_TYPE, funName,
                            actualType);
  return false;
}

static bool IncompatibleThisType(JSContext* cx, const char* funName,
                                 const char* actualType,
                                 HandleValue actualVal) {
  MOZ_ASSERT(JS::StringIsASCII(funName));
  MOZ_ASSERT(JS::StringIsASCII(actualType));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actualVal, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_INCOMPATIBLE_THIS_VAL, funName, actualType,
                           valStr);
  return false;
}

static bool InvalidIndexError(JSContext* cx, HandleValue val) {
  JS::UniqueChars idBytes;
  const char* indexStr = CTypesToSourceForError(cx, val, idBytes);
  if (!indexStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_INVALID_INDEX, indexStr);
  return false;
}

static bool InvalidIndexError(JSContext* cx, HandleId id) {
  RootedValue idVal(cx, IdToValue(id));
  return InvalidIndexError(cx, idVal);
}

static bool InvalidIndexRangeError(JSContext* cx, size_t index, size_t length) {
  IndexCString indexStr(index);
  IndexCString lengthStr(length);

  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_INVALID_RANGE, indexStr.get(),
                            lengthStr.get());
  return false;
}

static bool NonPrimitiveError(JSContext* cx, HandleObject typeObj) {
  MOZ_ASSERT(CType::IsCType(typeObj));

  JS::UniqueChars typeStr = TypeSourceForError(cx, typeObj);
  if (!typeStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_NON_PRIMITIVE, typeStr.get());
  return false;
}

static bool NonStringBaseError(JSContext* cx, HandleValue thisVal) {
  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, thisVal, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_NON_STRING_BASE, valStr);
  return false;
}

static bool NullPointerError(JSContext* cx, const char* action,
                             HandleObject obj) {
  MOZ_ASSERT(JS::StringIsASCII(action));

  JS::UniqueChars valBytes;
  RootedValue val(cx, ObjectValue(*obj));
  const char* valStr = CTypesToSourceForError(cx, val, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_NULL_POINTER,
                           action, valStr);
  return false;
}

static bool PropNameNonStringError(JSContext* cx, HandleId id,
                                   HandleValue actual, ConversionType convType,
                                   HandleObject funObj = nullptr,
                                   unsigned argIndex = 0) {
  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  JS::UniqueChars idBytes;
  RootedValue idVal(cx, IdToValue(id));
  const char* propStr = CTypesToSourceForError(cx, idVal, idBytes);
  if (!propStr) {
    return false;
  }

  JS::UniqueChars posStr;
  if (funObj) {
    posStr = ConversionPositionForError(cx, convType, funObj, argIndex);
    if (!posStr) {
      return false;
    }
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_PROP_NONSTRING, propStr, valStr,
                           (posStr ? posStr.get() : ""));
  return false;
}

static bool SizeOverflow(JSContext* cx, const char* name, const char* limit) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            CTYPESMSG_SIZE_OVERFLOW, name, limit);
  return false;
}

static bool TypeError(JSContext* cx, const char* expected, HandleValue actual) {
  MOZ_ASSERT(JS::StringIsASCII(expected));

  JS::UniqueChars bytes;
  const char* src = CTypesToSourceForError(cx, actual, bytes);
  if (!src) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_TYPE_ERROR,
                           expected, src);
  return false;
}

static bool TypeOverflow(JSContext* cx, const char* expected,
                         HandleValue actual) {
  MOZ_ASSERT(JS::StringIsASCII(expected));

  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_TYPE_OVERFLOW, valStr, expected);
  return false;
}

static bool UndefinedSizeCastError(JSContext* cx, HandleObject targetTypeObj) {
  JS::UniqueChars targetTypeStr = TypeSourceForError(cx, targetTypeObj);
  if (!targetTypeStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_UNDEFINED_SIZE_CAST, targetTypeStr.get());
  return false;
}

static bool SizeMismatchCastError(JSContext* cx, HandleObject sourceTypeObj,
                                  HandleObject targetTypeObj, size_t sourceSize,
                                  size_t targetSize) {
  JS::UniqueChars sourceTypeStr = TypeSourceForError(cx, sourceTypeObj);
  if (!sourceTypeStr) {
    return false;
  }

  JS::UniqueChars targetTypeStr = TypeSourceForError(cx, targetTypeObj);
  if (!targetTypeStr) {
    return false;
  }

  IndexCString sourceSizeStr(sourceSize);
  IndexCString targetSizeStr(targetSize);

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_SIZE_MISMATCH_CAST, targetTypeStr.get(),
                           sourceTypeStr.get(), targetSizeStr.get(),
                           sourceSizeStr.get());
  return false;
}

static bool UndefinedSizePointerError(JSContext* cx, const char* action,
                                      HandleObject obj) {
  MOZ_ASSERT(JS::StringIsASCII(action));

  JS::UniqueChars valBytes;
  RootedValue val(cx, ObjectValue(*obj));
  const char* valStr = CTypesToSourceForError(cx, val, valBytes);
  if (!valStr) {
    return false;
  }

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_UNDEFINED_SIZE, action, valStr);
  return false;
}

static bool VariadicArgumentTypeError(JSContext* cx, uint32_t index,
                                      HandleValue actual) {
  JS::UniqueChars valBytes;
  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
  if (!valStr) {
    return false;
  }

  IndexCString indexStr(index + 1);

  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                           CTYPESMSG_VARG_TYPE_ERROR, indexStr.get(), valStr);
  return false;
}

MOZ_MUST_USE JSObject* GetThisObject(JSContext* cx, const CallArgs& args,
                                     const char* msg) {
  if (!args.thisv().isObject()) {
    IncompatibleThisProto(cx, msg, args.thisv());
    return nullptr;
  }

  return &args.thisv().toObject();
}

static JSObject* InitCTypeClass(JSContext* cx, HandleObject ctypesObj) {
  JSFunction* fun = JS_DefineFunction(cx, ctypesObj, "CType", ConstructAbstract,
                                      0, CTYPESCTOR_FLAGS);
  if (!fun) {
    return nullptr;
  }

  RootedObject ctor(cx, JS_GetFunctionObject(fun));
  RootedObject fnproto(cx);
  if (!JS_GetPrototype(cx, ctor, &fnproto)) {
    return nullptr;
  }
  MOZ_ASSERT(ctor);
  MOZ_ASSERT(fnproto);

  // Set up ctypes.CType.prototype.
  RootedObject prototype(
      cx, JS_NewObjectWithGivenProto(cx, &sCTypeProtoClass, fnproto));
  if (!prototype) {
    return nullptr;
  }

  if (!JS_DefineProperty(cx, ctor, "prototype", prototype,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return nullptr;

  if (!JS_DefineProperty(cx, prototype, "constructor", ctor,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return nullptr;

  // Define properties and functions common to all CTypes.
  if (!JS_DefineProperties(cx, prototype, sCTypeProps) ||
      !JS_DefineFunctions(cx, prototype, sCTypeFunctions))
    return nullptr;

  if (!JS_FreezeObject(cx, ctor) || !JS_FreezeObject(cx, prototype)) {
    return nullptr;
  }

  return prototype;
}

static JSObject* InitABIClass(JSContext* cx) {
  RootedObject obj(cx, JS_NewPlainObject(cx));

  if (!obj) {
    return nullptr;
  }

  if (!JS_DefineFunctions(cx, obj, sCABIFunctions)) {
    return nullptr;
  }

  return obj;
}

static JSObject* InitCDataClass(JSContext* cx, HandleObject parent,
                                HandleObject CTypeProto) {
  JSFunction* fun = JS_DefineFunction(cx, parent, "CData", ConstructAbstract, 0,
                                      CTYPESCTOR_FLAGS);
  if (!fun) {
    return nullptr;
  }

  RootedObject ctor(cx, JS_GetFunctionObject(fun));
  MOZ_ASSERT(ctor);

  // Set up ctypes.CData.__proto__ === ctypes.CType.prototype.
  // (Note that 'ctypes.CData instanceof Function' is still true, thanks to the
  // prototype chain.)
  if (!JS_SetPrototype(cx, ctor, CTypeProto)) {
    return nullptr;
  }

  // Set up ctypes.CData.prototype.
  RootedObject prototype(cx, JS_NewObject(cx, &sCDataProtoClass));
  if (!prototype) {
    return nullptr;
  }

  if (!JS_DefineProperty(cx, ctor, "prototype", prototype,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return nullptr;

  if (!JS_DefineProperty(cx, prototype, "constructor", ctor,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return nullptr;

  // Define properties and functions common to all CDatas.
  if (!JS_DefineProperties(cx, prototype, sCDataProps) ||
      !JS_DefineFunctions(cx, prototype, sCDataFunctions))
    return nullptr;

  if (  // !JS_FreezeObject(cx, prototype) || // XXX fixme - see bug 541212!
      !JS_FreezeObject(cx, ctor))
    return nullptr;

  return prototype;
}

static bool DefineABIConstant(JSContext* cx, HandleObject ctypesObj,
                              const char* name, ABICode code,
                              HandleObject prototype) {
  RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, &sCABIClass, prototype));
  if (!obj) {
    return false;
  }
  JS_SetReservedSlot(obj, SLOT_ABICODE, Int32Value(code));

  if (!JS_FreezeObject(cx, obj)) {
    return false;
  }

  return JS_DefineProperty(
      cx, ctypesObj, name, obj,
      JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
}

// Set up a single type constructor for
// ctypes.{Pointer,Array,Struct,Function}Type.
static bool InitTypeConstructor(
    JSContext* cx, HandleObject parent, HandleObject CTypeProto,
    HandleObject CDataProto, const JSFunctionSpec spec,
    const JSFunctionSpec* fns, const JSPropertySpec* props,
    const JSFunctionSpec* instanceFns, const JSPropertySpec* instanceProps,
    MutableHandleObject typeProto, MutableHandleObject dataProto) {
  JSFunction* fun = js::DefineFunctionWithReserved(
      cx, parent, spec.name, spec.call.op, spec.nargs, spec.flags);
  if (!fun) {
    return false;
  }

  RootedObject obj(cx, JS_GetFunctionObject(fun));
  if (!obj) {
    return false;
  }

  // Set up the .prototype and .prototype.constructor properties.
  typeProto.set(JS_NewObjectWithGivenProto(cx, &sCTypeProtoClass, CTypeProto));
  if (!typeProto) {
    return false;
  }

  // Define property before proceeding, for GC safety.
  if (!JS_DefineProperty(cx, obj, "prototype", typeProto,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  if (fns && !JS_DefineFunctions(cx, typeProto, fns)) {
    return false;
  }

  if (!JS_DefineProperties(cx, typeProto, props)) {
    return false;
  }

  if (!JS_DefineProperty(cx, typeProto, "constructor", obj,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  // Stash ctypes.{Pointer,Array,Struct}Type.prototype on a reserved slot of
  // the type constructor, for faster lookup.
  js::SetFunctionNativeReserved(obj, SLOT_FN_CTORPROTO,
                                ObjectValue(*typeProto));

  // Create an object to serve as the common ancestor for all CData objects
  // created from the given type constructor. This has ctypes.CData.prototype
  // as its prototype, such that it inherits the properties and functions
  // common to all CDatas.
  dataProto.set(JS_NewObjectWithGivenProto(cx, &sCDataProtoClass, CDataProto));
  if (!dataProto) {
    return false;
  }

  // Define functions and properties on the 'dataProto' object that are common
  // to all CData objects created from this type constructor. (These will
  // become functions and properties on CData objects created from this type.)
  if (instanceFns && !JS_DefineFunctions(cx, dataProto, instanceFns)) {
    return false;
  }

  if (instanceProps && !JS_DefineProperties(cx, dataProto, instanceProps)) {
    return false;
  }

  // Link the type prototype to the data prototype.
  JS_SetReservedSlot(typeProto, SLOT_OURDATAPROTO, ObjectValue(*dataProto));

  if (!JS_FreezeObject(cx, obj) ||
      // !JS_FreezeObject(cx, dataProto) || // XXX fixme - see bug 541212!
      !JS_FreezeObject(cx, typeProto))
    return false;

  return true;
}

static JSObject* InitInt64Class(JSContext* cx, HandleObject parent,
                                const JSClass* clasp, JSNative construct,
                                const JSFunctionSpec* fs,
                                const JSFunctionSpec* static_fs) {
  // Init type class and constructor
  RootedObject prototype(cx, JS_InitClass(cx, parent, nullptr, clasp, construct,
                                          0, nullptr, fs, nullptr, static_fs));
  if (!prototype) {
    return nullptr;
  }

  RootedObject ctor(cx, JS_GetConstructor(cx, prototype));
  if (!ctor) {
    return nullptr;
  }

  // Define the 'join' function as an extended native and stash
  // ctypes.{Int64,UInt64}.prototype in a reserved slot of the new function.
  MOZ_ASSERT(clasp == &sInt64ProtoClass || clasp == &sUInt64ProtoClass);
  JSNative native = (clasp == &sInt64ProtoClass) ? Int64::Join : UInt64::Join;
  JSFunction* fun = js::DefineFunctionWithReserved(cx, ctor, "join", native, 2,
                                                   CTYPESFN_FLAGS);
  if (!fun) {
    return nullptr;
  }

  js::SetFunctionNativeReserved(fun, SLOT_FN_INT64PROTO,
                                ObjectValue(*prototype));

  if (!JS_FreezeObject(cx, ctor)) {
    return nullptr;
  }
  if (!JS_FreezeObject(cx, prototype)) {
    return nullptr;
  }

  return prototype;
}

static void AttachProtos(JSObject* proto, const AutoObjectVector& protos) {
  // For a given 'proto' of [[Class]] "CTypeProto", attach each of the 'protos'
  // to the appropriate CTypeProtoSlot. (SLOT_CTYPES is the last slot
  // of [[Class]] "CTypeProto" that we fill in this automated manner.)
  for (uint32_t i = 0; i <= SLOT_CTYPES; ++i) {
    JS_SetReservedSlot(proto, i, ObjectOrNullValue(protos[i]));
  }
}

static bool InitTypeClasses(JSContext* cx, HandleObject ctypesObj) {
  // Initialize the ctypes.CType class. This acts as an abstract base class for
  // the various types, and provides the common API functions. It has:
  //   * [[Class]] "Function"
  //   * __proto__ === Function.prototype
  //   * A constructor that throws a TypeError. (You can't construct an
  //     abstract type!)
  //   * 'prototype' property:
  //     * [[Class]] "CTypeProto"
  //     * __proto__ === Function.prototype
  //     * A constructor that throws a TypeError. (You can't construct an
  //       abstract type instance!)
  //     * 'constructor' property === ctypes.CType
  //     * Provides properties and functions common to all CTypes.
  RootedObject CTypeProto(cx, InitCTypeClass(cx, ctypesObj));
  if (!CTypeProto) {
    return false;
  }

  // Initialize the ctypes.CData class. This acts as an abstract base class for
  // instances of the various types, and provides the common API functions.
  // It has:
  //   * [[Class]] "Function"
  //   * __proto__ === Function.prototype
  //   * A constructor that throws a TypeError. (You can't construct an
  //     abstract type instance!)
  //   * 'prototype' property:
  //     * [[Class]] "CDataProto"
  //     * 'constructor' property === ctypes.CData
  //     * Provides properties and functions common to all CDatas.
  RootedObject CDataProto(cx, InitCDataClass(cx, ctypesObj, CTypeProto));
  if (!CDataProto) {
    return false;
  }

  // Link CTypeProto to CDataProto.
  JS_SetReservedSlot(CTypeProto, SLOT_OURDATAPROTO, ObjectValue(*CDataProto));

  // Create and attach the special class constructors: ctypes.PointerType,
  // ctypes.ArrayType, ctypes.StructType, and ctypes.FunctionType.
  // Each of these constructors 'c' has, respectively:
  //   * [[Class]] "Function"
  //   * __proto__ === Function.prototype
  //   * A constructor that creates a user-defined type.
  //   * 'prototype' property:
  //     * [[Class]] "CTypeProto"
  //     * __proto__ === ctypes.CType.prototype
  //     * 'constructor' property === 'c'
  // We also construct an object 'p' to serve, given a type object 't'
  // constructed from one of these type constructors, as
  // 't.prototype.__proto__'. This object has:
  //   * [[Class]] "CDataProto"
  //   * __proto__ === ctypes.CData.prototype
  //   * Properties and functions common to all CDatas.
  // Therefore an instance 't' of ctypes.{Pointer,Array,Struct,Function}Type
  // will have, resp.:
  //   * [[Class]] "CType"
  //   * __proto__ === ctypes.{Pointer,Array,Struct,Function}Type.prototype
  //   * A constructor which creates and returns a CData object, containing
  //     binary data of the given type.
  //   * 'prototype' property:
  //     * [[Class]] "CDataProto"
  //     * __proto__ === 'p', the prototype object from above
  //     * 'constructor' property === 't'
  AutoObjectVector protos(cx);
  if (!protos.resize(CTYPEPROTO_SLOTS)) {
    return false;
  }
  if (!InitTypeConstructor(
          cx, ctypesObj, CTypeProto, CDataProto, sPointerFunction, nullptr,
          sPointerProps, sPointerInstanceFunctions, sPointerInstanceProps,
          protos[SLOT_POINTERPROTO], protos[SLOT_POINTERDATAPROTO]))
    return false;

  if (!InitTypeConstructor(
          cx, ctypesObj, CTypeProto, CDataProto, sArrayFunction, nullptr,
          sArrayProps, sArrayInstanceFunctions, sArrayInstanceProps,
          protos[SLOT_ARRAYPROTO], protos[SLOT_ARRAYDATAPROTO]))
    return false;

  if (!InitTypeConstructor(
          cx, ctypesObj, CTypeProto, CDataProto, sStructFunction,
          sStructFunctions, sStructProps, sStructInstanceFunctions, nullptr,
          protos[SLOT_STRUCTPROTO], protos[SLOT_STRUCTDATAPROTO]))
    return false;

  if (!InitTypeConstructor(cx, ctypesObj, CTypeProto,
                           protos[SLOT_POINTERDATAPROTO], sFunctionFunction,
                           nullptr, sFunctionProps, sFunctionInstanceFunctions,
                           nullptr, protos[SLOT_FUNCTIONPROTO],
                           protos[SLOT_FUNCTIONDATAPROTO]))
    return false;

  protos[SLOT_CDATAPROTO].set(CDataProto);

  // Create and attach the ctypes.{Int64,UInt64} constructors.
  // Each of these has, respectively:
  //   * [[Class]] "Function"
  //   * __proto__ === Function.prototype
  //   * A constructor that creates a ctypes.{Int64,UInt64} object,
  //     respectively.
  //   * 'prototype' property:
  //     * [[Class]] {"Int64Proto","UInt64Proto"}
  //     * 'constructor' property === ctypes.{Int64,UInt64}
  protos[SLOT_INT64PROTO].set(InitInt64Class(cx, ctypesObj, &sInt64ProtoClass,
                                             Int64::Construct, sInt64Functions,
                                             sInt64StaticFunctions));
  if (!protos[SLOT_INT64PROTO]) {
    return false;
  }
  protos[SLOT_UINT64PROTO].set(
      InitInt64Class(cx, ctypesObj, &sUInt64ProtoClass, UInt64::Construct,
                     sUInt64Functions, sUInt64StaticFunctions));
  if (!protos[SLOT_UINT64PROTO]) {
    return false;
  }

  // Finally, store a pointer to the global ctypes object.
  // Note that there is no other reliable manner of locating this object.
  protos[SLOT_CTYPES].set(ctypesObj);

  // Attach the prototypes just created to each of ctypes.CType.prototype,
  // and the special type constructors, so we can access them when constructing
  // instances of those types.
  AttachProtos(CTypeProto, protos);
  AttachProtos(protos[SLOT_POINTERPROTO], protos);
  AttachProtos(protos[SLOT_ARRAYPROTO], protos);
  AttachProtos(protos[SLOT_STRUCTPROTO], protos);
  AttachProtos(protos[SLOT_FUNCTIONPROTO], protos);

  RootedObject ABIProto(cx, InitABIClass(cx));
  if (!ABIProto) {
    return false;
  }

  // Attach objects representing ABI constants.
  if (!DefineABIConstant(cx, ctypesObj, "default_abi", ABI_DEFAULT, ABIProto) ||
      !DefineABIConstant(cx, ctypesObj, "stdcall_abi", ABI_STDCALL, ABIProto) ||
      !DefineABIConstant(cx, ctypesObj, "thiscall_abi", ABI_THISCALL,
                         ABIProto) ||
      !DefineABIConstant(cx, ctypesObj, "winapi_abi", ABI_WINAPI, ABIProto))
    return false;

    // Create objects representing the builtin types, and attach them to the
    // ctypes object. Each type object 't' has:
    //   * [[Class]] "CType"
    //   * __proto__ === ctypes.CType.prototype
    //   * A constructor which creates and returns a CData object, containing
    //     binary data of the given type.
    //   * 'prototype' property:
    //     * [[Class]] "CDataProto"
    //     * __proto__ === ctypes.CData.prototype
    //     * 'constructor' property === 't'
#define DEFINE_TYPE(name, type, ffiType)                                       \
  RootedObject typeObj_##name(cx);                                             \
  {                                                                            \
    RootedValue typeVal(cx, Int32Value(sizeof(type)));                         \
    RootedValue alignVal(cx, Int32Value(ffiType.alignment));                   \
    typeObj_##name =                                                           \
        CType::DefineBuiltin(cx, ctypesObj, #name, CTypeProto, CDataProto,     \
                             #name, TYPE_##name, typeVal, alignVal, &ffiType); \
    if (!typeObj_##name) return false;                                         \
  }
  CTYPES_FOR_EACH_TYPE(DEFINE_TYPE)
#undef DEFINE_TYPE

  // Alias 'ctypes.unsigned' as 'ctypes.unsigned_int', since they represent
  // the same type in C.
  if (!JS_DefineProperty(cx, ctypesObj, "unsigned", typeObj_unsigned_int,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  // Alias 'ctypes.jschar' as 'ctypes.char16_t' to prevent breaking addons
  // that are still using jschar (bug 1064935).
  if (!JS_DefineProperty(cx, ctypesObj, "jschar", typeObj_char16_t,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  // Create objects representing the special types void_t and voidptr_t.
  RootedObject typeObj(
      cx, CType::DefineBuiltin(cx, ctypesObj, "void_t", CTypeProto, CDataProto,
                               "void", TYPE_void_t, JS::UndefinedHandleValue,
                               JS::UndefinedHandleValue, &ffi_type_void));
  if (!typeObj) {
    return false;
  }

  typeObj = PointerType::CreateInternal(cx, typeObj);
  if (!typeObj) {
    return false;
  }
  if (!JS_DefineProperty(cx, ctypesObj, "voidptr_t", typeObj,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  return true;
}

bool IsCTypesGlobal(JSObject* obj) {
  return JS_GetClass(obj) == &sCTypesGlobalClass;
}

bool IsCTypesGlobal(HandleValue v) {
  return v.isObject() && IsCTypesGlobal(&v.toObject());
}

// Get the JSCTypesCallbacks struct from the 'ctypes' object 'obj'.
const JSCTypesCallbacks* GetCallbacks(JSObject* obj) {
  MOZ_ASSERT(IsCTypesGlobal(obj));

  Value result = JS_GetReservedSlot(obj, SLOT_CALLBACKS);
  if (result.isUndefined()) {
    return nullptr;
  }

  return static_cast<const JSCTypesCallbacks*>(result.toPrivate());
}

// Utility function to access a property of an object as an object
// returns false and sets the error if the property does not exist
// or is not an object
static bool GetObjectProperty(JSContext* cx, HandleObject obj,
                              const char* property,
                              MutableHandleObject result) {
  RootedValue val(cx);
  if (!JS_GetProperty(cx, obj, property, &val)) {
    return false;
  }

  if (val.isPrimitive()) {
    JS_ReportErrorASCII(cx, "missing or non-object field");
    return false;
  }

  result.set(val.toObjectOrNull());
  return true;
}

} /* namespace ctypes */
} /* namespace js */

using namespace js;
using namespace js::ctypes;

JS_PUBLIC_API bool JS_InitCTypesClass(JSContext* cx, HandleObject global) {
  // attach ctypes property to global object
  RootedObject ctypes(cx, JS_NewObject(cx, &sCTypesGlobalClass));
  if (!ctypes) {
    return false;
  }

  if (!JS_DefineProperty(cx, global, "ctypes", ctypes,
                         JSPROP_READONLY | JSPROP_PERMANENT)) {
    return false;
  }

  if (!InitTypeClasses(cx, ctypes)) {
    return false;
  }

  // attach API functions and properties
  if (!JS_DefineFunctions(cx, ctypes, sModuleFunctions) ||
      !JS_DefineProperties(cx, ctypes, sModuleProps))
    return false;

  // Set up ctypes.CDataFinalizer.prototype.
  RootedObject ctor(cx);
  if (!GetObjectProperty(cx, ctypes, "CDataFinalizer", &ctor)) {
    return false;
  }

  RootedObject prototype(cx, JS_NewObject(cx, &sCDataFinalizerProtoClass));
  if (!prototype) {
    return false;
  }

  if (!JS_DefineFunctions(cx, prototype, sCDataFinalizerFunctions)) {
    return false;
  }

  if (!JS_DefineProperty(cx, ctor, "prototype", prototype,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  if (!JS_DefineProperty(cx, prototype, "constructor", ctor,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return false;

  // Seal the ctypes object, to prevent modification.
  return JS_FreezeObject(cx, ctypes);
}

JS_PUBLIC_API void JS_SetCTypesCallbacks(JSObject* ctypesObj,
                                         const JSCTypesCallbacks* callbacks) {
  MOZ_ASSERT(callbacks);
  MOZ_ASSERT(IsCTypesGlobal(ctypesObj));

  // Set the callbacks on a reserved slot.
  JS_SetReservedSlot(ctypesObj, SLOT_CALLBACKS,
                     PrivateValue(const_cast<JSCTypesCallbacks*>(callbacks)));
}

namespace js {

JS_FRIEND_API size_t SizeOfDataIfCDataObject(mozilla::MallocSizeOf mallocSizeOf,
                                             JSObject* obj) {
  if (!CData::IsCData(obj)) {
    return 0;
  }

  size_t n = 0;
  Value slot = JS_GetReservedSlot(obj, ctypes::SLOT_OWNS);
  if (!slot.isUndefined()) {
    bool owns = slot.toBoolean();
    slot = JS_GetReservedSlot(obj, ctypes::SLOT_DATA);
    if (!slot.isUndefined()) {
      char** buffer = static_cast<char**>(slot.toPrivate());
      n += mallocSizeOf(buffer);
      if (owns) {
        n += mallocSizeOf(*buffer);
      }
    }
  }
  return n;
}

namespace ctypes {

/*******************************************************************************
** Type conversion functions
*******************************************************************************/

// Enforce some sanity checks on type widths and properties.
// Where the architecture is 64-bit, make sure it's LP64 or LLP64. (ctypes.int
// autoconverts to a primitive JS number; to support ILP64 architectures, it
// would need to autoconvert to an Int64 object instead. Therefore we enforce
// this invariant here.)
JS_STATIC_ASSERT(sizeof(bool) == 1 || sizeof(bool) == 4);
JS_STATIC_ASSERT(sizeof(char) == 1);
JS_STATIC_ASSERT(sizeof(short) == 2);
JS_STATIC_ASSERT(sizeof(int) == 4);
JS_STATIC_ASSERT(sizeof(unsigned) == 4);
JS_STATIC_ASSERT(sizeof(long) == 4 || sizeof(long) == 8);
JS_STATIC_ASSERT(sizeof(long long) == 8);
JS_STATIC_ASSERT(sizeof(size_t) == sizeof(uintptr_t));
JS_STATIC_ASSERT(sizeof(float) == 4);
JS_STATIC_ASSERT(sizeof(PRFuncPtr) == sizeof(void*));
JS_STATIC_ASSERT(numeric_limits<double>::is_signed);

template <typename TargetType, typename FromType,
          bool FromIsIntegral = std::is_integral<FromType>::value>
struct ConvertImpl;

template <typename TargetType, typename FromType>
struct ConvertImpl<TargetType, FromType, false> {
  static MOZ_ALWAYS_INLINE TargetType Convert(FromType input) {
    return JS::ToSignedOrUnsignedInteger<TargetType>(input);
  }
};

template <typename TargetType>
struct ConvertUnsignedTargetTo {
  static TargetType convert(
      typename std::make_unsigned<TargetType>::type input) {
    return std::is_signed<TargetType>::value ? mozilla::WrapToSigned(input)
                                             : input;
  }
};

template <>
struct ConvertUnsignedTargetTo<char16_t> {
  static char16_t convert(char16_t input) {
    // mozilla::WrapToSigned can't be used on char16_t.
    return input;
  }
};

template <typename TargetType, typename FromType>
struct ConvertImpl<TargetType, FromType, true> {
  static MOZ_ALWAYS_INLINE TargetType Convert(FromType input) {
    using UnsignedTargetType = typename std::make_unsigned<TargetType>::type;
    auto resultUnsigned = static_cast<UnsignedTargetType>(input);

    return ConvertUnsignedTargetTo<TargetType>::convert(resultUnsigned);
  }
};

template <class TargetType, class FromType>
static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) {
  static_assert(std::is_integral<FromType>::value !=
                    std::is_floating_point<FromType>::value,
                "should only be converting from floating/integral type");

  return ConvertImpl<TargetType, FromType>::Convert(d);
}

template <class TargetType, class FromType>
static MOZ_ALWAYS_INLINE bool IsAlwaysExact() {
  // Return 'true' if TargetType can always exactly represent FromType.
  // This means that:
  // 1) TargetType must be the same or more bits wide as FromType. For integers
  //    represented in 'n' bits, unsigned variants will have 'n' digits while
  //    signed will have 'n - 1'. For floating point types, 'digits' is the
  //    mantissa width.
  // 2) If FromType is signed, TargetType must also be signed. (Floating point
  //    types are always signed.)
  // 3) If TargetType is an exact integral type, FromType must be also.
  if (numeric_limits<TargetType>::digits < numeric_limits<FromType>::digits) {
    return false;
  }

  if (numeric_limits<FromType>::is_signed &&
      !numeric_limits<TargetType>::is_signed)
    return false;

  if (!numeric_limits<FromType>::is_exact &&
      numeric_limits<TargetType>::is_exact)
    return false;

  return true;
}

// Templated helper to determine if FromType 'i' converts losslessly to
// TargetType 'j'. Default case where both types are the same signedness.
template <class TargetType, class FromType, bool TargetSigned, bool FromSigned>
struct IsExactImpl {
  static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) {
    JS_STATIC_ASSERT(numeric_limits<TargetType>::is_exact);
    return FromType(j) == i;
  }
};

// Specialization where TargetType is unsigned, FromType is signed.
template <class TargetType, class FromType>
struct IsExactImpl<TargetType, FromType, false, true> {
  static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) {
    JS_STATIC_ASSERT(numeric_limits<TargetType>::is_exact);
    return i >= 0 && FromType(j) == i;
  }
};

// Specialization where TargetType is signed, FromType is unsigned.
template <class TargetType, class FromType>
struct IsExactImpl<TargetType, FromType, true, false> {
  static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) {
    JS_STATIC_ASSERT(numeric_limits<TargetType>::is_exact);
    return TargetType(i) >= 0 && FromType(j) == i;
  }
};

// Convert FromType 'i' to TargetType 'result', returning true iff 'result'
// is an exact representation of 'i'.
template <class TargetType, class FromType>
static MOZ_ALWAYS_INLINE bool ConvertExact(FromType i, TargetType* result) {
  static_assert(std::numeric_limits<TargetType>::is_exact,
                "TargetType must be exact to simplify conversion");

  *result = Convert<TargetType>(i);

  // See if we can avoid a dynamic check.
  if (IsAlwaysExact<TargetType, FromType>()) {
    return true;
  }

  // Return 'true' if 'i' is exactly representable in 'TargetType'.
  return IsExactImpl<TargetType, FromType,
                     numeric_limits<TargetType>::is_signed,
                     numeric_limits<FromType>::is_signed>::Test(i, *result);
}

// Templated helper to determine if Type 'i' is negative. Default case
// where IntegerType is unsigned.
template <class Type, bool IsSigned>
struct IsNegativeImpl {
  static MOZ_ALWAYS_INLINE bool Test(Type i) { return false; }
};

// Specialization where Type is signed.
template <class Type>
struct IsNegativeImpl<Type, true> {
  static MOZ_ALWAYS_INLINE bool Test(Type i) { return i < 0; }
};

// Determine whether Type 'i' is negative.
template <class Type>
static MOZ_ALWAYS_INLINE bool IsNegative(Type i) {
  return IsNegativeImpl<Type, numeric_limits<Type>::is_signed>::Test(i);
}

// Implicitly convert val to bool, allowing bool, int, and double
// arguments numerically equal to 0 or 1.
static bool jsvalToBool(JSContext* cx, HandleValue val, bool* result) {
  if (val.isBoolean()) {
    *result = val.toBoolean();
    return true;
  }
  if (val.isInt32()) {
    int32_t i = val.toInt32();
    *result = i != 0;
    return i == 0 || i == 1;
  }
  if (val.isDouble()) {
    double d = val.toDouble();
    *result = d != 0;
    // Allow -0.
    return d == 1 || d == 0;
  }
  // Don't silently convert null to bool. It's probably a mistake.
  return false;
}

// Implicitly convert val to IntegerType, allowing bool, int, double,
// Int64, UInt64, and CData integer types 't' where all values of 't' are
// representable by IntegerType.
template <class IntegerType>
static bool jsvalToInteger(JSContext* cx, HandleValue val,
                           IntegerType* result) {
  JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);

  if (val.isInt32()) {
    // Make sure the integer fits in the alotted precision, and has the right
    // sign.
    int32_t i = val.toInt32();
    return ConvertExact(i, result);
  }
  if (val.isDouble()) {
    // Don't silently lose bits here -- check that val really is an
    // integer value, and has the right sign.
    double d = val.toDouble();
    return ConvertExact(d, result);
  }
  if (val.isObject()) {
    RootedObject obj(cx, &val.toObject());
    if (CData::IsCDataMaybeUnwrap(&obj)) {
      JSObject* typeObj = CData::GetCType(obj);
      void* data = CData::GetData(obj);

      // Check whether the source type is always representable, with exact
      // precision, by the target type. If it is, convert the value.
      switch (CType::GetTypeCode(typeObj)) {
#define INTEGER_CASE(name, fromType, ffiType)                  \
  case TYPE_##name:                                            \
    if (!IsAlwaysExact<IntegerType, fromType>()) return false; \
    *result = IntegerType(*static_cast<fromType*>(data));      \
    return true;
        CTYPES_FOR_EACH_INT_TYPE(INTEGER_CASE)
        CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGER_CASE)
#undef INTEGER_CASE
        case TYPE_void_t:
        case TYPE_bool:
        case TYPE_float:
        case TYPE_double:
        case TYPE_float32_t:
        case TYPE_float64_t:
        case TYPE_char:
        case TYPE_signed_char:
        case TYPE_unsigned_char:
        case TYPE_char16_t:
        case TYPE_pointer:
        case TYPE_function:
        case TYPE_array:
        case TYPE_struct:
          // Not a compatible number type.
          return false;
      }
    }

    if (Int64::IsInt64(obj)) {
      // Make sure the integer fits in IntegerType.
      int64_t i = Int64Base::GetInt(obj);
      return ConvertExact(i, result);
    }

    if (UInt64::IsUInt64(obj)) {
      // Make sure the integer fits in IntegerType.
      uint64_t i = Int64Base::GetInt(obj);
      return ConvertExact(i, result);
    }

    if (CDataFinalizer::IsCDataFinalizer(obj)) {
      RootedValue innerData(cx);
      if (!CDataFinalizer::GetValue(cx, obj, &innerData)) {
        return false;  // Nothing to convert
      }
      return jsvalToInteger(cx, innerData, result);
    }

    return false;
  }
  if (val.isBoolean()) {
    // Implicitly promote boolean values to 0 or 1, like C.
    *result = val.toBoolean();
    MOZ_ASSERT(*result == 0 || *result == 1);
    return true;
  }
  // Don't silently convert null to an integer. It's probably a mistake.
  return false;
}

// Implicitly convert val to FloatType, allowing int, double,
// Int64, UInt64, and CData numeric types 't' where all values of 't' are
// representable by FloatType.
template <class FloatType>
static bool jsvalToFloat(JSContext* cx, HandleValue val, FloatType* result) {
  JS_STATIC_ASSERT(!numeric_limits<FloatType>::is_exact);

  // The following casts may silently throw away some bits, but there's
  // no good way around it. Sternly requiring that the 64-bit double
  // argument be exactly representable as a 32-bit float is
  // unrealistic: it would allow 1/2 to pass but not 1/3.
  if (val.isInt32()) {
    *result = FloatType(val.toInt32());
    return true;
  }
  if (val.isDouble()) {
    *result = FloatType(val.toDouble());
    return true;
  }
  if (val.isObject()) {
    RootedObject obj(cx, &val.toObject());
    if (CData::IsCDataMaybeUnwrap(&obj)) {
      JSObject* typeObj = CData::GetCType(obj);
      void* data = CData::GetData(obj);

      // Check whether the source type is always representable, with exact
      // precision, by the target type. If it is, convert the value.
      switch (CType::GetTypeCode(typeObj)) {
#define NUMERIC_CASE(name, fromType, ffiType)                \
  case TYPE_##name:                                          \
    if (!IsAlwaysExact<FloatType, fromType>()) return false; \
    *result = FloatType(*static_cast<fromType*>(data));      \
    return true;
        CTYPES_FOR_EACH_FLOAT_TYPE(NUMERIC_CASE)
        CTYPES_FOR_EACH_INT_TYPE(NUMERIC_CASE)
        CTYPES_FOR_EACH_WRAPPED_INT_TYPE(NUMERIC_CASE)
#undef NUMERIC_CASE
        case TYPE_void_t:
        case TYPE_bool:
        case TYPE_char:
        case TYPE_signed_char:
        case TYPE_unsigned_char:
        case TYPE_char16_t:
        case TYPE_pointer:
        case TYPE_function:
        case TYPE_array:
        case TYPE_struct:
          // Not a compatible number type.
          return false;
      }
    }
  }
  // Don't silently convert true to 1.0 or false to 0.0, even though C/C++
  // does it. It's likely to be a mistake.
  return false;
}

template <class IntegerType, class CharT>
static bool StringToInteger(JSContext* cx, CharT* cp, size_t length,
                            IntegerType* result, bool* overflow) {
  JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);

  const CharT* end = cp + length;
  if (cp == end) {
    return false;
  }

  IntegerType sign = 1;
  if (cp[0] == '-') {
    if (!numeric_limits<IntegerType>::is_signed) {
      return false;
    }

    sign = -1;
    ++cp;
  }

  // Assume base-10, unless the string begins with '0x' or '0X'.
  IntegerType base = 10;
  if (end - cp > 2 && cp[0] == '0' && (cp[1] == 'x' || cp[1] == 'X')) {
    cp += 2;
    base = 16;
  }

  // Scan the string left to right and build the number,
  // checking for valid characters 0 - 9, a - f, A - F and overflow.
  IntegerType i = 0;
  while (cp != end) {
    char16_t c = *cp++;
    uint8_t digit;
    if (IsAsciiDigit(c)) {
      digit = c - '0';
    } else if (base == 16 && c >= 'a' && c <= 'f') {
      digit = c - 'a' + 10;
    } else if (base == 16 && c >= 'A' && c <= 'F') {
      digit = c - 'A' + 10;
    } else {
      return false;
    }

    IntegerType ii = i;
    i = ii * base + sign * digit;
    if (i / base != ii) {
      *overflow = true;
      return false;
    }
  }

  *result = i;
  return true;
}

template <class IntegerType>
static bool StringToInteger(JSContext* cx, JSString* string,
                            IntegerType* result, bool* overflow) {
  JSLinearString* linear = string->ensureLinear(cx);
  if (!linear) {
    return false;
  }

  AutoCheckCannotGC nogc;
  size_t length = linear->length();
  return string->hasLatin1Chars()
             ? StringToInteger<IntegerType>(cx, linear->latin1Chars(nogc),
                                            length, result, overflow)
             : StringToInteger<IntegerType>(cx, linear->twoByteChars(nogc),
                                            length, result, overflow);
}

// Implicitly convert val to IntegerType, allowing int, double,
// Int64, UInt64, and optionally a decimal or hexadecimal string argument.
// (This is common code shared by jsvalToSize and the Int64/UInt64
// constructors.)
template <class IntegerType>
static bool jsvalToBigInteger(JSContext* cx, HandleValue val, bool allowString,
                              IntegerType* result, bool* overflow) {
  JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);

  if (val.isInt32()) {
    // Make sure the integer fits in the alotted precision, and has the right
    // sign.
    int32_t i = val.toInt32();
    return ConvertExact(i, result);
  }
  if (val.isDouble()) {
    // Don't silently lose bits here -- check that val really is an
    // integer value, and has the right sign.
    double d = val.toDouble();
    return ConvertExact(d, result);
  }
  if (allowString && val.isString()) {
    // Allow conversion from base-10 or base-16 strings, provided the result
    // fits in IntegerType. (This allows an Int64 or UInt64 object to be passed
    // to the JS array element operator, which will automatically call
    // toString() on the object for us.)
    return StringToInteger(cx, val.toString(), result, overflow);
  }
  if (val.isObject()) {
    // Allow conversion from an Int64 or UInt64 object directly.
    JSObject* obj = &val.toObject();

    if (UInt64::IsUInt64(obj)) {
      // Make sure the integer fits in IntegerType.
      uint64_t i = Int64Base::GetInt(obj);
      return ConvertExact(i, result);
    }

    if (Int64::IsInt64(obj)) {
      // Make sure the integer fits in IntegerType.
      int64_t i = Int64Base::GetInt(obj);
      return ConvertExact(i, result);
    }

    if (CDataFinalizer::IsCDataFinalizer(obj)) {
      RootedValue innerData(cx);
      if (!CDataFinalizer::GetValue(cx, obj, &innerData)) {
        return false;  // Nothing to convert
      }
      return jsvalToBigInteger(cx, innerData, allowString, result, overflow);
    }
  }
  return false;
}

// Implicitly convert val to a size value, where the size value is represented
// by size_t but must also fit in a double.
static bool jsvalToSize(JSContext* cx, HandleValue val, bool allowString,
                        size_t* result) {
  bool dummy;
  if (!jsvalToBigInteger(cx, val, allowString, result, &dummy)) {
    return false;
  }

  // Also check that the result fits in a double.
  return Convert<size_t>(double(*result)) == *result;
}

// Implicitly convert val to IntegerType, allowing int, double,
// Int64, UInt64, and optionally a decimal or hexadecimal string argument.
// (This is common code shared by jsvalToSize and the Int64/UInt64
// constructors.)
template <class IntegerType>
static bool jsidToBigInteger(JSContext* cx, jsid val, bool allowString,
                             IntegerType* result) {
  JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);

  if (JSID_IS_INT(val)) {
    // Make sure the integer fits in the alotted precision, and has the right
    // sign.
    int32_t i = JSID_TO_INT(val);
    return ConvertExact(i, result);
  }
  if (allowString && JSID_IS_STRING(val)) {
    // Allow conversion from base-10 or base-16 strings, provided the result
    // fits in IntegerType. (This allows an Int64 or UInt64 object to be passed
    // to the JS array element operator, which will automatically call
    // toString() on the object for us.)
    bool dummy;
    return StringToInteger(cx, JSID_TO_STRING(val), result, &dummy);
  }
  return false;
}

// Implicitly convert val to a size value, where the size value is represented
// by size_t but must also fit in a double.
static bool jsidToSize(JSContext* cx, jsid val, bool allowString,
                       size_t* result) {
  if (!jsidToBigInteger(cx, val, allowString, result)) {
    return false;
  }

  // Also check that the result fits in a double.
  return Convert<size_t>(double(*result)) == *result;
}

// Implicitly convert a size value to a Value, ensuring that the size_t value
// fits in a double.
static bool SizeTojsval(JSContext* cx, size_t size, MutableHandleValue result) {
  if (Convert<size_t>(double(size)) != size) {
    return false;
  }

  result.setNumber(double(size));
  return true;
}

// Forcefully convert val to IntegerType when explicitly requested.
template <class IntegerType>
static bool jsvalToIntegerExplicit(HandleValue val, IntegerType* result) {
  JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);

  if (val.isDouble()) {
    // Convert using ToInt32-style semantics: non-finite numbers become 0, and
    // everything else rounds toward zero then maps into |IntegerType| with
    // wraparound semantics.
    double d = val.toDouble();
    *result = JS::ToSignedOrUnsignedInteger<IntegerType>(d);
    return true;
  }
  if (val.isObject()) {
    // Convert Int64 and UInt64 values by C-style cast.
    JSObject* obj = &val.toObject();
    if (Int64::IsInt64(obj)) {
      int64_t i = Int64Base::GetInt(obj);
      *result = IntegerType(i);
      return true;
    }
    if (UInt64::IsUInt64(obj)) {
      uint64_t i = Int64Base::GetInt(obj);
      *result = IntegerType(i);
      return true;
    }
  }
  return false;
}

// Forcefully convert val to a pointer value when explicitly requested.
static bool jsvalToPtrExplicit(JSContext* cx, HandleValue val,
                               uintptr_t* result) {
  if (val.isInt32()) {
    // int32_t always fits in intptr_t. If the integer is negative, cast through
    // an intptr_t intermediate to sign-extend.
    int32_t i = val.toInt32();
    *result = i < 0 ? uintptr_t(intptr_t(i)) : uintptr_t(i);
    return true;
  }
  if (val.isDouble()) {
    double d = val.toDouble();
    if (d < 0) {
      // Cast through an intptr_t intermediate to sign-extend.
      intptr_t i = Convert<intptr_t>(d);
      if (double(i) != d) {
        return false;
      }

      *result = uintptr_t(i);
      return true;
    }

    // Don't silently lose bits here -- check that val really is an
    // integer value, and has the right sign.
    *result = Convert<uintptr_t>(d);
    return double(*result) == d;
  }
  if (val.isObject()) {
    JSObject* obj = &val.toObject();
    if (Int64::IsInt64(obj)) {
      int64_t i = Int64Base::GetInt(obj);
      intptr_t p = intptr_t(i);

      // Make sure the integer fits in the alotted precision.
      if (int64_t(p) != i) {
        return false;
      }
      *result = uintptr_t(p);
      return true;
    }

    if (UInt64::IsUInt64(obj)) {
      uint64_t i = Int64Base::GetInt(obj);

      // Make sure the integer fits in the alotted precision.
      *result = uintptr_t(i);
      return uint64_t(*result) == i;
    }
  }
  return false;
}

template <class IntegerType, class CharType, size_t N>
void IntegerToString(IntegerType i, int radix,
                     StringBuilder<CharType, N>& result) {
  JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);

  // The buffer must be big enough for all the bits of IntegerType to fit,
  // in base-2, including '-'.
  CharType buffer[sizeof(IntegerType) * 8 + 1];
  CharType* end = buffer + sizeof(buffer) / sizeof(CharType);
  CharType* cp = end;

  // Build the string in reverse. We use multiplication and subtraction
  // instead of modulus because that's much faster.
  const bool isNegative = IsNegative(i);
  size_t sign = isNegative ? -1 : 1;
  do {
    IntegerType ii = i / IntegerType(radix);
    size_t index = sign * size_t(i - ii * IntegerType(radix));
    *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[index];
    i = ii;
  } while (i != 0);

  if (isNegative) {
    *--cp = '-';
  }

  MOZ_ASSERT(cp >= buffer);
  if (!result.append(cp, end)) {
    return;
  }
}

template <class CharType>
static size_t strnlen(const CharType* begin, size_t max) {
  for (size_t i = 0; i < max; i++) {
    if (begin[i] == '\0') {
      return i;
    }
  }

  return max;
}

// Convert C binary value 'data' of CType 'typeObj' to a JS primitive, where
// possible; otherwise, construct and return a CData object. The following
// semantics apply when constructing a CData object for return:
// * If 'wantPrimitive' is true, the caller indicates that 'result' must be
//   a JS primitive, and ConvertToJS will fail if 'result' would be a CData
//   object. Otherwise:
// * If a CData object 'parentObj' is supplied, the new CData object is
//   dependent on the given parent and its buffer refers to a slice of the
//   parent's buffer.
// * If 'parentObj' is null, the new CData object may or may not own its
//   resulting buffer depending on the 'ownResult' argument.
static bool ConvertToJS(JSContext* cx, HandleObject typeObj,
                        HandleObject parentObj, void* data, bool wantPrimitive,
                        bool ownResult, MutableHandleValue result) {
  MOZ_ASSERT(!parentObj || CData::IsCData(parentObj));
  MOZ_ASSERT(!parentObj || !ownResult);
  MOZ_ASSERT(!wantPrimitive || !ownResult);

  TypeCode typeCode = CType::GetTypeCode(typeObj);

  switch (typeCode) {
    case TYPE_void_t:
      result.setUndefined();
      break;
    case TYPE_bool:
      result.setBoolean(*static_cast<bool*>(data));
      break;
#define INT_CASE(name, type, ffiType)       \
  case TYPE_##name: {                       \
    type value = *static_cast<type*>(data); \
    if (sizeof(type) < 4)                   \
      result.setInt32(int32_t(value));      \
    else                                    \
      result.setDouble(double(value));      \
    break;                                  \
  }
      CTYPES_FOR_EACH_INT_TYPE(INT_CASE)
#undef INT_CASE
#define WRAPPED_INT_CASE(name, type, ffiType)                               \
  case TYPE_##name: {                                                       \
    /* Return an Int64 or UInt64 object - do not convert to a JS number. */ \
    uint64_t value;                                                         \
    RootedObject proto(cx);                                                 \
    if (!numeric_limits<type>::is_signed) {                                 \
      value = *static_cast<type*>(data);                                    \
      /* Get ctypes.UInt64.prototype from ctypes.CType.prototype. */        \
      proto = CType::GetProtoFromType(cx, typeObj, SLOT_UINT64PROTO);       \
      if (!proto) return false;                                             \
    } else {                                                                \
      value = int64_t(*static_cast<type*>(data));                           \
      /* Get ctypes.Int64.prototype from ctypes.CType.prototype. */         \
      proto = CType::GetProtoFromType(cx, typeObj, SLOT_INT64PROTO);        \
      if (!proto) return false;                                             \
    }                                                                       \
                                                                            \
    JSObject* obj = Int64Base::Construct(cx, proto, value,                  \
                                         !numeric_limits<type>::is_signed); \
    if (!obj) return false;                                                 \
    result.setObject(*obj);                                                 \
    break;                                                                  \
  }
      CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE)
#undef WRAPPED_INT_CASE
#define FLOAT_CASE(name, type, ffiType)     \
  case TYPE_##name: {                       \
    type value = *static_cast<type*>(data); \
    result.setDouble(double(value));        \
    break;                                  \
  }
      CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE)
#undef FLOAT_CASE
#define CHAR_CASE(name, type, ffiType)                                      \
  case TYPE_##name:                                                         \
    /* Convert to an integer. We have no idea what character encoding to */ \
    /* use, if any. */                                                      \
    result.setInt32(*static_cast<type*>(data));                             \
    break;
      CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE)
#undef CHAR_CASE
    case TYPE_char16_t: {
      // Convert the char16_t to a 1-character string.
      JSString* str = JS_NewUCStringCopyN(cx, static_cast<char16_t*>(data), 1);
      if (!str) {
        return false;
      }

      result.setString(str);
      break;
    }
    case TYPE_pointer:
    case TYPE_array:
    case TYPE_struct: {
      // We're about to create a new CData object to return. If the caller
      // doesn't want this, return early.
      if (wantPrimitive) {
        return NonPrimitiveError(cx, typeObj);
      }

      JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult);
      if (!obj) {
        return false;
      }

      result.setObject(*obj);
      break;
    }
    case TYPE_function:
      MOZ_CRASH("cannot return a FunctionType");
  }

  return true;
}

// Determine if the contents of a typed array can be converted without
// ambiguity to a C type. Elements of a Int8Array are converted to
// ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc.
bool CanConvertTypedArrayItemTo(JSObject* baseType, JSObject* valObj,
                                JSContext* cx) {
  TypeCode baseTypeCode = CType::GetTypeCode(baseType);
  if (baseTypeCode == TYPE_void_t || baseTypeCode == TYPE_char) {
    return true;
  }
  TypeCode elementTypeCode;
  switch (JS_GetArrayBufferViewType(valObj)) {
    case Scalar::Int8:
      elementTypeCode = TYPE_int8_t;
      break;
    case Scalar::Uint8:
    case Scalar::Uint8Clamped:
      elementTypeCode = TYPE_uint8_t;
      break;
    case Scalar::Int16:
      elementTypeCode = TYPE_int16_t;
      break;
    case Scalar::Uint16:
      elementTypeCode = TYPE_uint16_t;
      break;
    case Scalar::Int32:
      elementTypeCode = TYPE_int32_t;
      break;
    case Scalar::Uint32:
      elementTypeCode = TYPE_uint32_t;
      break;
    case Scalar::Float32:
      elementTypeCode = TYPE_float32_t;
      break;
    case Scalar::Float64:
      elementTypeCode = TYPE_float64_t;
      break;
    default:
      return false;
  }

  return elementTypeCode == baseTypeCode;
}

// Implicitly convert Value 'val' to a C binary representation of CType
// 'targetType', storing the result in 'buffer'. Adequate space must be
// provided in 'buffer' by the caller. This function generally does minimal
// coercion between types. There are two cases in which this function is used:
// 1) The target buffer is internal to a CData object; we simply write data
//    into it.
// 2) We are converting an argument for an ffi call, in which case 'convType'
//    will be 'ConversionType::Argument'. This allows us to handle a special
//    case: if necessary, we can autoconvert a JS string primitive to a
//    pointer-to-character type. In this case, ownership of the allocated string
//    is handed off to the caller; 'freePointer' will be set to indicate this.
static bool ImplicitConvert(JSContext* cx, HandleValue val,
                            JSObject* targetType_, void* buffer,
                            ConversionType convType, bool* freePointer,
                            HandleObject funObj = nullptr,
                            unsigned argIndex = 0,
                            HandleObject arrObj = nullptr,
                            unsigned arrIndex = 0) {
  RootedObject targetType(cx, targetType_);
  MOZ_ASSERT(CType::IsSizeDefined(targetType));

  // First, check if val is either a CData object or a CDataFinalizer
  // of type targetType.
  JSObject* sourceData = nullptr;
  JSObject* sourceType = nullptr;
  RootedObject valObj(cx, nullptr);
  if (val.isObject()) {
    valObj = &val.toObject();
    if (CData::IsCDataMaybeUnwrap(&valObj)) {
      sourceData = valObj;
      sourceType = CData::GetCType(sourceData);

      // If the types are equal, copy the buffer contained within the CData.
      // (Note that the buffers may overlap partially or completely.)
      if (CType::TypesEqual(sourceType, targetType)) {
        size_t size = CType::GetSize(sourceType);
        memmove(buffer, CData::GetData(sourceData), size);
        return true;
      }
    } else if (CDataFinalizer::IsCDataFinalizer(valObj)) {
      sourceData = valObj;
      sourceType = CDataFinalizer::GetCType(cx, sourceData);

      CDataFinalizer::Private* p =
          (CDataFinalizer::Private*)JS_GetPrivate(sourceData);

      if (!p) {
        // We have called |dispose| or |forget| already.
        return EmptyFinalizerError(cx, convType, funObj, argIndex);
      }

      // If the types are equal, copy the buffer contained within the CData.
      if (CType::TypesEqual(sourceType, targetType)) {
        memmove(buffer, p->cargs, p->cargs_size);
        return true;
      }
    }
  }

  TypeCode targetCode = CType::GetTypeCode(targetType);

  switch (targetCode) {
    case TYPE_bool: {
      // Do not implicitly lose bits, but allow the values 0, 1, and -0.
      // Programs can convert explicitly, if needed, using `Boolean(v)` or
      // `!!v`.
      bool result;
      if (!jsvalToBool(cx, val, &result)) {
        return ConvError(cx, "boolean", val, convType, funObj, argIndex, arrObj,
                         arrIndex);
      }
      *static_cast<bool*>(buffer) = result;
      break;
    }
#define CHAR16_CASE(name, type, ffiType)                                     \
  case TYPE_##name: {                                                        \
    /* Convert from a 1-character string, regardless of encoding, */         \
    /* or from an integer, provided the result fits in 'type'. */            \
    type result = 0;                                                         \
    if (val.isString()) {                                                    \
      JSString* str = val.toString();                                        \
      if (str->length() != 1)                                                \
        return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \
                         arrIndex);                                          \
      JSLinearString* linear = str->ensureLinear(cx);                        \
      if (!linear) return false;                                             \
      result = linear->latin1OrTwoByteChar(0);                               \
    } else if (!jsvalToInteger(cx, val, &result)) {                          \
      return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj,   \
                       arrIndex);                                            \
    }                                                                        \
    *static_cast<type*>(buffer) = result;                                    \
    break;                                                                   \
  }
      CTYPES_FOR_EACH_CHAR16_TYPE(CHAR16_CASE)
#undef CHAR16_CASE
#define INTEGRAL_CASE(name, type, ffiType)                                 \
  case TYPE_##name: {                                                      \
    /* Do not implicitly lose bits. */                                     \
    type result;                                                           \
    if (!jsvalToInteger(cx, val, &result))                                 \
      return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \
                       arrIndex);                                          \
    *static_cast<type*>(buffer) = result;                                  \
    break;                                                                 \
  }
      CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
      CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE)
      // It's hard to believe ctypes.char16_t("f") should work yet
      // ctypes.char("f") should not.  Ditto for ctypes.{un,}signed_char.  But
      // this is how ctypes has always worked, so preserve these semantics, and
      // don't switch to an algorithm similar to that in DEFINE_CHAR16_TYPE
      // above, just yet.
      CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE)
#undef INTEGRAL_CASE
#define FLOAT_CASE(name, type, ffiType)                                    \
  case TYPE_##name: {                                                      \
    type result;                                                           \
    if (!jsvalToFloat(cx, val, &result))                                   \
      return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \
                       arrIndex);                                          \
    *static_cast<type*>(buffer) = result;                                  \
    break;                                                                 \
  }
      CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE)
#undef FLOAT_CASE
    case TYPE_pointer: {
      if (val.isNull()) {
        // Convert to a null pointer.
        *static_cast<void**>(buffer) = nullptr;
        break;
      }

      JS::Rooted<JSObject*> baseType(cx, PointerType::GetBaseType(targetType));
      if (sourceData) {
        // First, determine if the targetType is ctypes.void_t.ptr.
        TypeCode sourceCode = CType::GetTypeCode(sourceType);
        void* sourceBuffer = CData::GetData(sourceData);
        bool voidptrTarget = CType::GetTypeCode(baseType) == TYPE_void_t;

        if (sourceCode == TYPE_pointer && voidptrTarget) {
          // Autoconvert if targetType is ctypes.voidptr_t.
          *static_cast<void**>(buffer) = *static_cast<void**>(sourceBuffer);
          break;
        }
        if (sourceCode == TYPE_array) {
          // Autoconvert an array to a ctypes.void_t.ptr or to
          // sourceType.elementType.ptr, just like C.
          JSObject* elementType = ArrayType::GetBaseType(sourceType);
          if (voidptrTarget || CType::TypesEqual(baseType, elementType)) {
            *static_cast<void**>(buffer) = sourceBuffer;
            break;
          }
        }

      } else if (convType == ConversionType::Argument && val.isString()) {
        // Convert the string for the ffi call. This requires allocating space
        // which the caller assumes ownership of.
        // TODO: Extend this so we can safely convert strings at other times
        // also.
        JSString* sourceString = val.toString();
        size_t sourceLength = sourceString->length();
        Rooted<JSFlatString*> sourceFlat(cx, sourceString->ensureFlat(cx));
        if (!sourceFlat) {
          return false;
        }

        switch (CType::GetTypeCode(baseType)) {
          case TYPE_char:
          case TYPE_signed_char:
          case TYPE_unsigned_char: {
            // Reject if unpaired surrogate characters are present.
            if (!ReportErrorIfUnpairedSurrogatePresent(cx, sourceFlat)) {
              return false;
            }

            // Convert from UTF-16 to UTF-8.
            size_t nbytes = JS::GetDeflatedUTF8StringLength(sourceFlat);

            char** charBuffer = static_cast<char**>(buffer);
            *charBuffer = cx->pod_malloc<char>(nbytes + 1);
            if (!*charBuffer) {
              return false;
            }

            JS::DeflateStringToUTF8Buffer(
                sourceFlat, mozilla::RangedPtr<char>(*charBuffer, nbytes),
                &nbytes);
            (*charBuffer)[nbytes] = 0;
            *freePointer = true;
            break;
          }
          case TYPE_char16_t: {
            // Copy the char16_t string data. (We could provide direct access to
            // the JSString's buffer, but this approach is safer if the caller
            // happens to modify the string.)
            char16_t** char16Buffer = static_cast<char16_t**>(buffer);
            *char16Buffer = cx->pod_malloc<char16_t>(sourceLength + 1);
            if (!*char16Buffer) {
              return false;
            }

            *freePointer = true;
            if (sourceFlat->hasLatin1Chars()) {
              AutoCheckCannotGC nogc;
              CopyAndInflateChars(*char16Buffer, sourceFlat->latin1Chars(nogc),
                                  sourceLength);
            } else {
              AutoCheckCannotGC nogc;
              mozilla::PodCopy(*char16Buffer, sourceFlat->twoByteChars(nogc),
                               sourceLength);
            }
            (*char16Buffer)[sourceLength] = 0;
            break;
          }
          default:
            return ConvError(cx, targetType, val, convType, funObj, argIndex,
                             arrObj, arrIndex);
        }
        break;
      } else if (val.isObject() && JS::IsArrayBufferObject(valObj)) {
        // Convert ArrayBuffer to pointer without any copy. This is only valid
        // when converting an argument to a function call, as it is possible for
        // the pointer to be invalidated by anything that runs JS code. (It is
        // invalid to invoke JS code from a ctypes function call.)
        if (convType != ConversionType::Argument) {
          return ConvError(cx, targetType, val, convType, funObj, argIndex,
                           arrObj, arrIndex);
        }
        void* ptr;
        {
          JS::AutoCheckCannotGC nogc;
          bool isShared;
          ptr = JS::GetArrayBufferData(valObj, &isShared, nogc);
          MOZ_ASSERT(!isShared);  // Because ArrayBuffer
        }
        if (!ptr) {
          return ConvError(cx, targetType, val, convType, funObj, argIndex,
                           arrObj, arrIndex);
        }
        *static_cast<void**>(buffer) = ptr;
        break;
      } else if (val.isObject() && JS::IsSharedArrayBufferObject(valObj)) {
        // CTypes has not yet opted in to allowing shared memory pointers
        // to escape.  Exporting a pointer to the shared buffer without
        // indicating sharedness would expose client code to races.
        return ConvError(cx, targetType, val, convType, funObj, argIndex,
                         arrObj, arrIndex);
      } else if (val.isObject() && JS_IsArrayBufferViewObject(valObj)) {
        // Same as ArrayBuffer, above, though note that this will take the
        // offset of the view into account.
        if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
          return ConvError(cx, targetType, val, convType, funObj, argIndex,
                           arrObj, arrIndex);
        }
        if (convType != ConversionType::Argument) {
          return ConvError(cx, targetType, val, convType, funObj, argIndex,
                           arrObj, arrIndex);
        }
        void* ptr;
        {
          JS::AutoCheckCannotGC nogc;
          bool isShared;
          ptr = JS_GetArrayBufferViewData(valObj, &isShared, nogc);
          if (isShared) {
            // Opt out of shared memory, for now.  Exporting a
            // pointer to the shared buffer without indicating
            // sharedness would expose client code to races.
            ptr = nullptr;
          }
        }
        if (!ptr) {
          return ConvError(cx, targetType, val, convType, funObj, argIndex,
                           arrObj, arrIndex);
        }
        *static_cast<void**>(buffer) = ptr;
        break;
      }
      return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj,
                       arrIndex);
    }
    case TYPE_array: {
      MOZ_ASSERT(!funObj);

      RootedObject baseType(cx, ArrayType::GetBaseType(targetType));
      size_t targetLength = ArrayType::GetLength(targetType);

      if (val.isString()) {
        JSString* sourceString = val.toString();
        size_t sourceLength = sourceString->length();
        Rooted<JSFlatString*> sourceFlat(cx, sourceString->ensureFlat(cx));
        if (!sourceFlat) {
          return false;
        }

        switch (CType::GetTypeCode(baseType)) {
          case TYPE_char:
          case TYPE_signed_char:
          case TYPE_unsigned_char: {
            // Reject if unpaired surrogate characters are present.
            if (!ReportErrorIfUnpairedSurrogatePresent(cx, sourceFlat)) {
              return false;
            }

            // Convert from UTF-16 or Latin1 to UTF-8.
            size_t nbytes = JS::GetDeflatedUTF8StringLength(sourceFlat);

            if (targetLength < nbytes) {
              MOZ_ASSERT(!funObj);
              return ArrayLengthOverflow(cx, targetLength, targetType, nbytes,
                                         val, convType);
            }

            char* charBuffer = static_cast<char*>(buffer);
            JS::DeflateStringToUTF8Buffer(
                sourceFlat, mozilla::RangedPtr<char>(charBuffer, nbytes),
                &nbytes);

            if (targetLength > nbytes) {
              charBuffer[nbytes] = 0;
            }

            break;
          }
          case TYPE_char16_t: {
            // Copy the string data, char16_t for char16_t, including the
            // terminator if there's space.
            if (targetLength < sourceLength) {
              MOZ_ASSERT(!funObj);
              return ArrayLengthOverflow(cx, targetLength, targetType,
                                         sourceLength, val, convType);
            }

            char16_t* dest = static_cast<char16_t*>(buffer);
            if (sourceFlat->hasLatin1Chars()) {
              AutoCheckCannotGC nogc;
              CopyAndInflateChars(dest, sourceFlat->latin1Chars(nogc),
                                  sourceLength);
            } else {
              AutoCheckCannotGC nogc;
              mozilla::PodCopy(dest, sourceFlat->twoByteChars(nogc),
                               sourceLength);
            }

            if (targetLength > sourceLength) {
              dest[sourceLength] = 0;
            }

            break;
          }
          default:
            return ConvError(cx, targetType, val, convType, funObj, argIndex,
                             arrObj, arrIndex);
        }
      } else {
        ESClass cls;
        if (!GetClassOfValue(cx, val, &cls)) {
          return false;
        }

        if (cls == ESClass::Array) {
          // Convert each element of the array by calling ImplicitConvert.
          uint32_t sourceLength;
          if (!JS_GetArrayLength(cx, valObj, &sourceLength) ||
              targetLength != size_t(sourceLength)) {
            MOZ_ASSERT(!funObj);
            return ArrayLengthMismatch(cx, targetLength, targetType,
                                       size_t(sourceLength), val, convType);
          }

          // Convert into an intermediate, in case of failure.
          size_t elementSize = CType::GetSize(baseType);
          size_t arraySize = elementSize * targetLength;
          auto intermediate = cx->make_pod_array<char>(arraySize);
          if (!intermediate) {
            return false;
          }

          RootedValue item(cx);
          for (uint32_t i = 0; i < sourceLength; ++i) {
            if (!JS_GetElement(cx, valObj, i, &item)) {
              return false;
            }

            char* data = intermediate.get() + elementSize * i;
            if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr,
                                 funObj, argIndex, targetType, i))
              return false;
          }

          memcpy(buffer, intermediate.get(), arraySize);
        } else if (cls == ESClass::ArrayBuffer ||
                   cls == ESClass::SharedArrayBuffer) {
          // Check that array is consistent with type, then
          // copy the array.
          const bool bufferShared = cls == ESClass::SharedArrayBuffer;
          uint32_t sourceLength =
              bufferShared ? JS::GetSharedArrayBufferByteLength(valObj)
                           : JS::GetArrayBufferByteLength(valObj);
          size_t elementSize = CType::GetSize(baseType);
          size_t arraySize = elementSize * targetLength;
          if (arraySize != size_t(sourceLength)) {
            MOZ_ASSERT(!funObj);
            return ArrayLengthMismatch(cx, arraySize, targetType,
                                       size_t(sourceLength), val, convType);
          }
          SharedMem<void*> target = SharedMem<void*>::unshared(buffer);
          JS::AutoCheckCannotGC nogc;
          bool isShared;
          SharedMem<void*> src =
              (bufferShared
                   ? SharedMem<void*>::shared(
                         JS::GetSharedArrayBufferData(valObj, &isShared, nogc))
                   : SharedMem<void*>::unshared(
                         JS::GetArrayBufferData(valObj, &isShared, nogc)));
          MOZ_ASSERT(isShared == bufferShared);
          jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength);
          break;
        } else if (JS_IsTypedArrayObject(valObj)) {
          // Check that array is consistent with type, then
          // copy the array.  It is OK to copy from shared to unshared
          // or vice versa.
          if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
            return ConvError(cx, targetType, val, convType, funObj, argIndex,
                             arrObj, arrIndex);
          }

          uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj);
          size_t elementSize = CType::GetSize(baseType);
          size_t arraySize = elementSize * targetLength;
          if (arraySize != size_t(sourceLength)) {
            MOZ_ASSERT(!funObj);
            return ArrayLengthMismatch(cx, arraySize, targetType,
                                       size_t(sourceLength), val, convType);
          }
          SharedMem<void*> target = SharedMem<void*>::unshared(buffer);
          JS::AutoCheckCannotGC nogc;
          bool isShared;
          SharedMem<void*> src = SharedMem<void*>::shared(
              JS_GetArrayBufferViewData(valObj, &isShared, nogc));
          jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength);
          break;
        } else {
          // Don't implicitly convert to string. Users can implicitly convert
          // with `String(x)` or `""+x`.
          return ConvError(cx, targetType, val, convType, funObj, argIndex,
                           arrObj, arrIndex);
        }
      }
      break;
    }
    case TYPE_struct: {
      if (val.isObject() && !sourceData) {
        // Enumerate the properties of the object; if they match the struct
        // specification, convert the fields.
        Rooted<IdVector> props(cx, IdVector(cx));
        if (!JS_Enumerate(cx, valObj, &props)) {
          return false;
        }

        // Convert into an intermediate, in case of failure.
        size_t structSize = CType::GetSize(targetType);
        auto intermediate = cx->make_pod_array<char>(structSize);
        if (!intermediate) {
          return false;
        }

        const FieldInfoHash* fields = StructType::GetFieldInfo(targetType);
        if (props.length() != fields->count()) {
          return FieldCountMismatch(cx, fields->count(), targetType,
                                    props.length(), val, convType, funObj,
                                    argIndex);
        }

        RootedId id(cx);
        for (size_t i = 0; i < props.length(); ++i) {
          id = props[i];

          if (!JSID_IS_STRING(id)) {
            return PropNameNonStringError(cx, id, val, convType, funObj,
                                          argIndex);
          }

          JSFlatString* name = JSID_TO_FLAT_STRING(id);
          const FieldInfo* field =
              StructType::LookupField(cx, targetType, name);
          if (!field) {
            return false;
          }

          RootedValue prop(cx);
          if (!JS_GetPropertyById(cx, valObj, id, &prop)) {
            return false;
          }

          // Convert the field via ImplicitConvert().
          char* fieldData = intermediate.get() + field->mOffset;
          if (!ImplicitConvert(cx, prop, field->mType, fieldData, convType,
                               nullptr, funObj, argIndex, targetType, i))
            return false;
        }

        memcpy(buffer, intermediate.get(), structSize);
        break;
      }

      return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj,
                       arrIndex);
    }
    case TYPE_void_t:
    case TYPE_function:
      MOZ_CRASH("invalid type");
  }

  return true;
}

// Convert Value 'val' to a C binary representation of CType 'targetType',
// storing the result in 'buffer'. This function is more forceful than
// ImplicitConvert.
static bool ExplicitConvert(JSContext* cx, HandleValue val,
                            HandleObject targetType, void* buffer,
                            ConversionType convType) {
  // If ImplicitConvert succeeds, use that result.
  if (ImplicitConvert(cx, val, targetType, buffer, convType, nullptr)) {
    return true;
  }

  // If ImplicitConvert failed, and there is no pending exception, then assume
  // hard failure (out of memory, or some other similarly serious condition).
  // We store any pending exception in case we need to re-throw it.
  RootedValue ex(cx);
  if (!JS_GetPendingException(cx, &ex)) {
    return false;
  }

  // Otherwise, assume soft failure. Clear the pending exception so that we
  // can throw a different one as required.
  JS_ClearPendingException(cx);

  TypeCode type = CType::GetTypeCode(targetType);

  switch (type) {
    case TYPE_bool: {
      *static_cast<bool*>(buffer) = ToBoolean(val);
      break;
    }
#define INTEGRAL_CASE(name, type, ffiType)                            \
  case TYPE_##name: {                                                 \
    /* Convert numeric values with a C-style cast, and */             \
    /* allow conversion from a base-10 or base-16 string. */          \
    type result;                                                      \
    bool overflow = false;                                            \
    if (!jsvalToIntegerExplicit(val, &result) &&                      \
        (!val.isString() ||                                           \
         !StringToInteger(cx, val.toString(), &result, &overflow))) { \
      if (overflow) {                                                 \
        return TypeOverflow(cx, #name, val);                          \
      }                                                               \
      return ConvError(cx, #name, val, convType);                     \
    }                                                                 \
    *static_cast<type*>(buffer) = result;                             \
    break;                                                            \
  }
      CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
      CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE)
      CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE)
      CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE)
#undef INTEGRAL_CASE
    case TYPE_pointer: {
      // Convert a number, Int64 object, or UInt64 object to a pointer.
      uintptr_t result;
      if (!jsvalToPtrExplicit(cx, val, &result)) {
        return ConvError(cx, targetType, val, convType);
      }
      *static_cast<uintptr_t*>(buffer) = result;
      break;
    }
    case TYPE_float32_t:
    case TYPE_float64_t:
    case TYPE_float:
    case TYPE_double:
    case TYPE_array:
    case TYPE_struct:
      // ImplicitConvert is sufficient. Re-throw the exception it generated.
      JS_SetPendingException(cx, ex);
      return false;
    case TYPE_void_t:
    case TYPE_function:
      MOZ_CRASH("invalid type");
  }
  return true;
}

// Given a CType 'typeObj', generate a string describing the C type declaration
// corresponding to 'typeObj'. For instance, the CType constructed from
// 'ctypes.int32_t.ptr.array(4).ptr.ptr' will result in the type string
// 'int32_t*(**)[4]'.
static JSString* BuildTypeName(JSContext* cx, JSObject* typeObj_) {
  AutoString result;
  RootedObject typeObj(cx, typeObj_);

  // Walk the hierarchy of types, outermost to innermost, building up the type
  // string. This consists of the base type, which goes on the left.
  // Derived type modifiers (* and []) build from the inside outward, with
  // pointers on the left and arrays on the right. An excellent description
  // of the rules for building C type declarations can be found at:
  // http://unixwiz.net/techtips/reading-cdecl.html
  TypeCode prevGrouping = CType::GetTypeCode(typeObj), currentGrouping;
  while (true) {
    currentGrouping = CType::GetTypeCode(typeObj);
    switch (currentGrouping) {
      case TYPE_pointer: {
        // Pointer types go on the left.
        PrependString(cx, result, "*");

        typeObj = PointerType::GetBaseType(typeObj);
        prevGrouping = currentGrouping;
        continue;
      }
      case TYPE_array: {
        if (prevGrouping == TYPE_pointer) {
          // Outer type is pointer, inner type is array. Grouping is required.
          PrependString(cx, result, "(");
          AppendString(cx, result, ")");
        }

        // Array types go on the right.
        AppendString(cx, result, "[");
        size_t length;
        if (ArrayType::GetSafeLength(typeObj, &length)) {
          IntegerToString(length, 10, result);
        }

        AppendString(cx, result, "]");

        typeObj = ArrayType::GetBaseType(typeObj);
        prevGrouping = currentGrouping;
        continue;
      }
      case TYPE_function: {
        FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj);

        // Add in the calling convention, if it's not cdecl.
        // There's no trailing or leading space needed here, as none of the
        // modifiers can produce a string beginning with an identifier ---
        // except for TYPE_function itself, which is fine because functions
        // can't return functions.
        ABICode abi = GetABICode(fninfo->mABI);
        if (abi == ABI_STDCALL) {
          PrependString(cx, result, "__stdcall");
        } else if (abi == ABI_THISCALL) {
          PrependString(cx, result, "__thiscall");
        } else if (abi == ABI_WINAPI) {
          PrependString(cx, result, "WINAPI");
        }

        // Function application binds more tightly than dereferencing, so
        // wrap pointer types in parens. Functions can't return functions
        // (only pointers to them), and arrays can't hold functions
        // (similarly), so we don't need to address those cases.
        if (prevGrouping == TYPE_pointer) {
          PrependString(cx, result, "(");
          AppendString(cx, result, ")");
        }

        // Argument list goes on the right.
        AppendString(cx, result, "(");
        for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) {
          RootedObject argType(cx, fninfo->mArgTypes[i]);
          JSString* argName = CType::GetName(cx, argType);
          AppendString(cx, result, argName);
          if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic)
            AppendString(cx, result, ", ");
        }
        if (fninfo->mIsVariadic) {
          AppendString(cx, result, "...");
        }
        AppendString(cx, result, ")");

        // Set 'typeObj' to the return type, and let the loop process it.
        // 'prevGrouping' doesn't matter here, because functions cannot return
        // arrays -- thus the parenthetical rules don't get tickled.
        typeObj = fninfo->mReturnType;
        continue;
      }
      default:
        // Either a basic or struct type. Use the type's name as the base type.
        break;
    }
    break;
  }

  // If prepending the base type name directly would splice two
  // identifiers, insert a space.
  if (IsAsciiAlpha(result[0]) || result[0] == '_') {
    PrependString(cx, result, " ");
  }

  // Stick the base type and derived type parts together.
  JSString* baseName = CType::GetName(cx, typeObj);
  PrependString(cx, result, baseName);
  if (!result) {
    return nullptr;
  }
  return NewUCString(cx, result.finish());
}

// Given a CType 'typeObj', generate a string 'result' such that 'eval(result)'
// would construct the same CType. If 'makeShort' is true, assume that any
// StructType 't' is bound to an in-scope variable of name 't.name', and use
// that variable in place of generating a string to construct the type 't'.
// (This means the type comparison function CType::TypesEqual will return true
// when comparing the input and output of AppendTypeSource, since struct
// equality is determined by strict JSObject pointer equality.)
static void BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort,
                            AutoString& result) {
  RootedObject typeObj(cx, typeObj_);

  // Walk the types, building up the toSource() string.
  switch (CType::GetTypeCode(typeObj)) {
    case TYPE_void_t:
#define CASE_FOR_TYPE(name, type, ffiType) case TYPE_##name:
      CTYPES_FOR_EACH_TYPE(CASE_FOR_TYPE)
#undef CASE_FOR_TYPE
      {
        AppendString(cx, result, "ctypes.");
        JSString* nameStr = CType::GetName(cx, typeObj);
        AppendString(cx, result, nameStr);
        break;
      }
    case TYPE_pointer: {
      RootedObject baseType(cx, PointerType::GetBaseType(typeObj));

      // Specialcase ctypes.voidptr_t.
      if (CType::GetTypeCode(baseType) == TYPE_void_t) {
        AppendString(cx, result, "ctypes.voidptr_t");
        break;
      }

      // Recursively build the source string, and append '.ptr'.
      BuildTypeSource(cx, baseType, makeShort, result);
      AppendString(cx, result, ".ptr");
      break;
    }
    case TYPE_function: {
      FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj);

      AppendString(cx, result, "ctypes.FunctionType(");

      switch (GetABICode(fninfo->mABI)) {
        case ABI_DEFAULT:
          AppendString(cx, result, "ctypes.default_abi, ");
          break;
        case ABI_STDCALL:
          AppendString(cx, result, "ctypes.stdcall_abi, ");
          break;
        case ABI_THISCALL:
          AppendString(cx, result, "ctypes.thiscall_abi, ");
          break;
        case ABI_WINAPI:
          AppendString(cx, result, "ctypes.winapi_abi, ");
          break;
        case INVALID_ABI:
          MOZ_CRASH("invalid abi");
      }

      // Recursively build the source string describing the function return and
      // argument types.
      BuildTypeSource(cx, fninfo->mReturnType, true, result);

      if (fninfo->mArgTypes.length() > 0) {
        AppendString(cx, result, ", [");
        for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) {
          BuildTypeSource(cx, fninfo->mArgTypes[i], true, result);
          if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic)
            AppendString(cx, result, ", ");
        }
        if (fninfo->mIsVariadic) {
          AppendString(cx, result, "\"...\"");
        }
        AppendString(cx, result, "]");
      }

      AppendString(cx, result, ")");
      break;
    }
    case TYPE_array: {
      // Recursively build the source string, and append '.array(n)',
      // where n is the array length, or the empty string if the array length
      // is undefined.
      JSObject* baseType = ArrayType::GetBaseType(typeObj);
      BuildTypeSource(cx, baseType, makeShort, result);
      AppendString(cx, result, ".array(");

      size_t length;
      if (ArrayType::GetSafeLength(typeObj, &length)) {
        IntegerToString(length, 10, result);
      }

      AppendString(cx, result, ")");
      break;
    }
    case TYPE_struct: {
      JSString* name = CType::GetName(cx, typeObj);

      if (makeShort) {
        // Shorten the type declaration by assuming that StructType 't' is bound
        // to an in-scope variable of name 't.name'.
        AppendString(cx, result, name);
        break;
      }

      // Write the full struct declaration.
      AppendString(cx, result, "ctypes.StructType(\"");
      AppendString(cx, result, name);
      AppendString(cx, result, "\"");

      // If it's an opaque struct, we're done.
      if (!CType::IsSizeDefined(typeObj)) {
        AppendString(cx, result, ")");
        break;
      }

      AppendString(cx, result, ", [");

      const FieldInfoHash* fields = StructType::GetFieldInfo(typeObj);
      size_t length = fields->count();
      Vector<const FieldInfoHash::Entry*, 64, SystemAllocPolicy> fieldsArray;
      if (!fieldsArray.resize(length)) {
        break;
      }

      for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
        fieldsArray[r.front().value().mIndex] = &r.front();
      }

      for (size_t i = 0; i < length; ++i) {
        const FieldInfoHash::Entry* entry = fieldsArray[i];
        AppendString(cx, result, "{ \"");
        AppendString(cx, result, entry->key());
        AppendString(cx, result, "\": ");
        BuildTypeSource(cx, entry->value().mType, true, result);
        AppendString(cx, result, " }");
        if (i != length - 1) {
          AppendString(cx, result, ", ");
        }
      }

      AppendString(cx, result, "])");
      break;
    }
  }
}

// Given a CData object of CType 'typeObj' with binary value 'data', generate a
// string 'result' such that 'eval(result)' would construct a CData object with
// the same CType and containing the same binary value. This assumes that any
// StructType 't' is bound to an in-scope variable of name 't.name'. (This means
// the type comparison function CType::TypesEqual will return true when
// comparing the types, since struct equality is determined by strict JSObject
// pointer equality.) Further, if 'isImplicit' is true, ensure that the
// resulting string can ImplicitConvert successfully if passed to another data
// constructor. (This is important when called recursively, since fields of
// structs and arrays are converted with ImplicitConvert.)
static MOZ_MUST_USE bool BuildDataSource(JSContext* cx, HandleObject typeObj,
                                         void* data, bool isImplicit,
                                         AutoString& result) {
  TypeCode type = CType::GetTypeCode(typeObj);
  switch (type) {
    case TYPE_bool:
      if (*static_cast<bool*>(data)) {
        AppendString(cx, result, "true");
      } else {
        AppendString(cx, result, "false");
      }
      break;
#define INTEGRAL_CASE(name, type, ffiType)                  \
  case TYPE_##name:                                         \
    /* Serialize as a primitive decimal integer. */         \
    IntegerToString(*static_cast<type*>(data), 10, result); \
    break;
      CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
#undef INTEGRAL_CASE
#define WRAPPED_INT_CASE(name, type, ffiType)               \
  case TYPE_##name:                                         \
    /* Serialize as a wrapped decimal integer. */           \
    if (!numeric_limits<type>::is_signed)                   \
      AppendString(cx, result, "ctypes.UInt64(\"");         \
    else                                                    \
      AppendString(cx, result, "ctypes.Int64(\"");          \
                                                            \
    IntegerToString(*static_cast<type*>(data), 10, result); \
    AppendString(cx, result, "\")");                        \
    break;
      CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE)
#undef WRAPPED_INT_CASE
#define FLOAT_CASE(name, type, ffiType)             \
  case TYPE_##name: {                               \
    /* Serialize as a primitive double. */          \
    double fp = *static_cast<type*>(data);          \
    ToCStringBuf cbuf;                              \
    char* str = NumberToCString(cx, &cbuf, fp);     \
    if (!str || !result.append(str, strlen(str))) { \
      JS_ReportOutOfMemory(cx);                     \
      return false;                                 \
    }                                               \
    break;                                          \
  }
      CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE)
#undef FLOAT_CASE
#define CHAR_CASE(name, type, ffiType)                      \
  case TYPE_##name:                                         \
    /* Serialize as an integer. */                          \
    IntegerToString(*static_cast<type*>(data), 10, result); \
    break;
      CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE)
#undef CHAR_CASE
    case TYPE_char16_t: {
      // Serialize as a 1-character JS string.
      JSString* str = JS_NewUCStringCopyN(cx, static_cast<char16_t*>(data), 1);
      if (!str) {
        return false;
      }

      // Escape characters, and quote as necessary.
      RootedValue valStr(cx, StringValue(str));
      JSString* src = JS_ValueToSource(cx, valStr);
      if (!src) {
        return false;
      }

      AppendString(cx, result, src);
      break;
    }
    case TYPE_pointer:
    case TYPE_function: {
      if (isImplicit) {
        // The result must be able to ImplicitConvert successfully.
        // Wrap in a type constructor, then serialize for ExplicitConvert.
        BuildTypeSource(cx, typeObj, true, result);
        AppendString(cx, result, "(");
      }

      // Serialize the pointer value as a wrapped hexadecimal integer.
      uintptr_t ptr = *static_cast<uintptr_t*>(data);
      AppendString(cx, result, "ctypes.UInt64(\"0x");
      IntegerToString(ptr, 16, result);
      AppendString(cx, result, "\")");

      if (isImplicit) {
        AppendString(cx, result, ")");
      }

      break;
    }
    case TYPE_array: {
      // Serialize each element of the array recursively. Each element must
      // be able to ImplicitConvert successfully.
      RootedObject baseType(cx, ArrayType::GetBaseType(typeObj));
      AppendString(cx, result, "[");

      size_t length = ArrayType::GetLength(typeObj);
      size_t elementSize = CType::GetSize(baseType);
      for (size_t i = 0; i < length; ++i) {
        char* element = static_cast<char*>(data) + elementSize * i;
        if (!BuildDataSource(cx, baseType, element, true, result)) {
          return false;
        }

        if (i + 1 < length) {
          AppendString(cx, result, ", ");
        }
      }
      AppendString(cx, result, "]");
      break;
    }
    case TYPE_struct: {
      if (isImplicit) {
        // The result must be able to ImplicitConvert successfully.
        // Serialize the data as an object with properties, rather than
        // a sequence of arguments to the StructType constructor.
        AppendString(cx, result, "{");
      }

      // Serialize each field of the struct recursively. Each field must
      // be able to ImplicitConvert successfully.
      const FieldInfoHash* fields = StructType::GetFieldInfo(typeObj);
      size_t length = fields->count();
      Vector<const FieldInfoHash::Entry*, 64, SystemAllocPolicy> fieldsArray;
      if (!fieldsArray.resize(length)) {
        return false;
      }

      for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
        fieldsArray[r.front().value().mIndex] = &r.front();
      }

      for (size_t i = 0; i < length; ++i) {
        const FieldInfoHash::Entry* entry = fieldsArray[i];

        if (isImplicit) {
          AppendString(cx, result, "\"");
          AppendString(cx, result, entry->key());
          AppendString(cx, result, "\": ");
        }

        char* fieldData = static_cast<char*>(data) + entry->value().mOffset;
        RootedObject entryType(cx, entry->value().mType);
        if (!BuildDataSource(cx, entryType, fieldData, true, result)) {
          return false;
        }

        if (i + 1 != length) {
          AppendString(cx, result, ", ");
        }
      }

      if (isImplicit) {
        AppendString(cx, result, "}");
      }

      break;
    }
    case TYPE_void_t:
      MOZ_CRASH("invalid type");
  }

  return true;
}

/*******************************************************************************
** JSAPI callback function implementations
*******************************************************************************/

bool ConstructAbstract(JSContext* cx, unsigned argc, Value* vp) {
  // Calling an abstract base class constructor is disallowed.
  return CannotConstructError(cx, "abstract type");
}

/*******************************************************************************
** CType implementation
*******************************************************************************/

bool CType::ConstructData(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  // get the callee object...
  RootedObject obj(cx, &args.callee());
  if (!CType::IsCType(obj)) {
    return IncompatibleCallee(cx, "CType constructor", obj);
  }

  // How we construct the CData object depends on what type we represent.
  // An instance 'd' of a CData object of type 't' has:
  //   * [[Class]] "CData"
  //   * __proto__ === t.prototype
  switch (GetTypeCode(obj)) {
    case TYPE_void_t:
      return CannotConstructError(cx, "void_t");
    case TYPE_function:
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                CTYPESMSG_FUNCTION_CONSTRUCT);
      return false;
    case TYPE_pointer:
      return PointerType::ConstructData(cx, obj, args);
    case TYPE_array:
      return ArrayType::ConstructData(cx, obj, args);
    case TYPE_struct:
      return StructType::ConstructData(cx, obj, args);
    default:
      return ConstructBasic(cx, obj, args);
  }
}

bool CType::ConstructBasic(JSContext* cx, HandleObject obj,
                           const CallArgs& args) {
  if (args.length() > 1) {
    return ArgumentLengthError(cx, "CType constructor", "at most one", "");
  }

  // construct a CData object
  RootedObject result(cx, CData::Create(cx, obj, nullptr, nullptr, true));
  if (!result) {
    return false;
  }

  if (args.length() == 1) {
    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result),
                         ConversionType::Construct))
      return false;
  }

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

JSObject* CType::Create(JSContext* cx, HandleObject typeProto,
                        HandleObject dataProto, TypeCode type, JSString* name_,
                        HandleValue size, HandleValue align,
                        ffi_type* ffiType) {
  RootedString name(cx, name_);

  // Create a CType object with the properties and slots common to all CTypes.
  // Each type object 't' has:
  //   * [[Class]] "CType"
  //   * __proto__ === 'typeProto'; one of ctypes.{CType,PointerType,ArrayType,
  //     StructType}.prototype
  //   * A constructor which creates and returns a CData object, containing
  //     binary data of the given type.
  //   * 'prototype' property:
  //     * [[Class]] "CDataProto"
  //     * __proto__ === 'dataProto'; an object containing properties and
  //       functions common to all CData objects of types derived from
  //       'typeProto'. (For instance, this could be ctypes.CData.prototype
  //       for simple types, or something representing structs for StructTypes.)
  //     * 'constructor' property === 't'
  //     * Additional properties specified by 'ps', as appropriate for the
  //       specific type instance 't'.
  RootedObject typeObj(cx,
                       JS_NewObjectWithGivenProto(cx, &sCTypeClass, typeProto));
  if (!typeObj) {
    return nullptr;
  }

  // Set up the reserved slots.
  JS_SetReservedSlot(typeObj, SLOT_TYPECODE, Int32Value(type));
  if (ffiType) {
    JS_SetReservedSlot(typeObj, SLOT_FFITYPE, PrivateValue(ffiType));
  }
  if (name) {
    JS_SetReservedSlot(typeObj, SLOT_NAME, StringValue(name));
  }
  JS_SetReservedSlot(typeObj, SLOT_SIZE, size);
  JS_SetReservedSlot(typeObj, SLOT_ALIGN, align);

  if (dataProto) {
    // Set up the 'prototype' and 'prototype.constructor' properties.
    RootedObject prototype(
        cx, JS_NewObjectWithGivenProto(cx, &sCDataProtoClass, dataProto));
    if (!prototype) {
      return nullptr;
    }

    if (!JS_DefineProperty(cx, prototype, "constructor", typeObj,
                           JSPROP_READONLY | JSPROP_PERMANENT))
      return nullptr;

    // Set the 'prototype' object.
    // if (!JS_FreezeObject(cx, prototype)) // XXX fixme - see bug 541212!
    //  return nullptr;
    JS_SetReservedSlot(typeObj, SLOT_PROTO, ObjectValue(*prototype));
  }

  if (!JS_FreezeObject(cx, typeObj)) {
    return nullptr;
  }

  // Assert a sanity check on size and alignment: size % alignment should always
  // be zero.
  MOZ_ASSERT_IF(IsSizeDefined(typeObj),
                GetSize(typeObj) % GetAlignment(typeObj) == 0);

  return typeObj;
}

JSObject* CType::DefineBuiltin(JSContext* cx, HandleObject ctypesObj,
                               const char* propName, JSObject* typeProto_,
                               JSObject* dataProto_, const char* name,
                               TypeCode type, HandleValue size,
                               HandleValue align, ffi_type* ffiType) {
  RootedObject typeProto(cx, typeProto_);
  RootedObject dataProto(cx, dataProto_);

  RootedString nameStr(cx, JS_NewStringCopyZ(cx, name));
  if (!nameStr) {
    return nullptr;
  }

  // Create a new CType object with the common properties and slots.
  RootedObject typeObj(cx, Create(cx, typeProto, dataProto, type, nameStr, size,
                                  align, ffiType));
  if (!typeObj) {
    return nullptr;
  }

  // Define the CType as a 'propName' property on 'ctypesObj'.
  if (!JS_DefineProperty(cx, ctypesObj, propName, typeObj,
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
    return nullptr;

  return typeObj;
}

void CType::Finalize(JSFreeOp* fop, JSObject* obj) {
  // Make sure our TypeCode slot is legit. If it's not, bail.
  Value slot = JS_GetReservedSlot(obj, SLOT_TYPECODE);
  if (slot.isUndefined()) {
    return;
  }

  // The contents of our slots depends on what kind of type we are.
  switch (TypeCode(slot.toInt32())) {
    case TYPE_function: {
      // Free the FunctionInfo.
      slot = JS_GetReservedSlot(obj, SLOT_FNINFO);
      if (!slot.isUndefined()) {
        FreeOp::get(fop)->delete_(static_cast<FunctionInfo*>(slot.toPrivate()));
      }
      break;
    }

    case TYPE_struct: {
      // Free the FieldInfoHash table.
      slot = JS_GetReservedSlot(obj, SLOT_FIELDINFO);
      if (!slot.isUndefined()) {
        void* info = slot.toPrivate();
        FreeOp::get(fop)->delete_(static_cast<FieldInfoHash*>(info));
      }
    }

      MOZ_FALLTHROUGH;

    case TYPE_array: {
      // Free the ffi_type info.
      slot = JS_GetReservedSlot(obj, SLOT_FFITYPE);
      if (!slot.isUndefined()) {
        ffi_type* ffiType = static_cast<ffi_type*>(slot.toPrivate());
        FreeOp::get(fop)->free_(ffiType->elements);
        FreeOp::get(fop)->delete_(ffiType);
      }

      break;
    }
    default:
      // Nothing to do here.
      break;
  }
}

void CType::Trace(JSTracer* trc, JSObject* obj) {
  // Make sure our TypeCode slot is legit. If it's not, bail.
  Value slot = obj->as<NativeObject>().getSlot(SLOT_TYPECODE);
  if (slot.isUndefined()) {
    return;
  }

  // The contents of our slots depends on what kind of type we are.
  switch (TypeCode(slot.toInt32())) {
    case TYPE_struct: {
      slot = obj->as<NativeObject>().getReservedSlot(SLOT_FIELDINFO);
      if (slot.isUndefined()) {
        return;
      }

      FieldInfoHash* fields = static_cast<FieldInfoHash*>(slot.toPrivate());
      fields->trace(trc);
      break;
    }
    case TYPE_function: {
      // Check if we have a FunctionInfo.
      slot = obj->as<NativeObject>().getReservedSlot(SLOT_FNINFO);
      if (slot.isUndefined()) {
        return;
      }

      FunctionInfo* fninfo = static_cast<FunctionInfo*>(slot.toPrivate());
      MOZ_ASSERT(fninfo);

      // Identify our objects to the tracer.
      JS::TraceEdge(trc, &fninfo->mABI, "abi");
      JS::TraceEdge(trc, &fninfo->mReturnType, "returnType");
      for (auto& argType : fninfo->mArgTypes) {
        JS::TraceEdge(trc, &argType, "argType");
      }

      break;
    }
    default:
      // Nothing to do here.
      break;
  }
}

bool CType::IsCType(JSObject* obj) { return JS_GetClass(obj) == &sCTypeClass; }

bool CType::IsCTypeProto(JSObject* obj) {
  return JS_GetClass(obj) == &sCTypeProtoClass;
}

TypeCode CType::GetTypeCode(JSObject* typeObj) {
  MOZ_ASSERT(IsCType(typeObj));

  Value result = JS_GetReservedSlot(typeObj, SLOT_TYPECODE);
  return TypeCode(result.toInt32());
}

bool CType::TypesEqual(JSObject* t1, JSObject* t2) {
  MOZ_ASSERT(IsCType(t1) && IsCType(t2));

  // Fast path: check for object equality.
  if (t1 == t2) {
    return true;
  }

  // First, perform shallow comparison.
  TypeCode c1 = GetTypeCode(t1);
  TypeCode c2 = GetTypeCode(t2);
  if (c1 != c2) {
    return false;
  }

  // Determine whether the types require shallow or deep comparison.
  switch (c1) {
    case TYPE_pointer: {
      // Compare base types.
      JSObject* b1 = PointerType::GetBaseType(t1);
      JSObject* b2 = PointerType::GetBaseType(t2);
      return TypesEqual(b1, b2);
    }
    case TYPE_function: {
      FunctionInfo* f1 = FunctionType::GetFunctionInfo(t1);
      FunctionInfo* f2 = FunctionType::GetFunctionInfo(t2);

      // Compare abi, return type, and argument types.
      if (f1->mABI != f2->mABI) {
        return false;
      }

      if (!TypesEqual(f1->mReturnType, f2->mReturnType)) {
        return false;
      }

      if (f1->mArgTypes.length() != f2->mArgTypes.length()) {
        return false;
      }

      if (f1->mIsVariadic != f2->mIsVariadic) {
        return false;
      }

      for (size_t i = 0; i < f1->mArgTypes.length(); ++i) {
        if (!TypesEqual(f1->mArgTypes[i], f2->mArgTypes[i])) {
          return false;
        }
      }

      return true;
    }
    case TYPE_array: {
      // Compare length, then base types.
      // An undefined length array matches other undefined length arrays.
      size_t s1 = 0, s2 = 0;
      bool d1 = ArrayType::GetSafeLength(t1, &s1);
      bool d2 = ArrayType::GetSafeLength(t2, &s2);
      if (d1 != d2 || (d1 && s1 != s2)) {
        return false;
      }

      JSObject* b1 = ArrayType::GetBaseType(t1);
      JSObject* b2 = ArrayType::GetBaseType(t2);
      return TypesEqual(b1, b2);
    }
    case TYPE_struct:
      // Require exact type object equality.
      return false;
    default:
      // Shallow comparison is sufficient.
      return true;
  }
}

bool CType::GetSafeSize(JSObject* obj, size_t* result) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value size = JS_GetReservedSlot(obj, SLOT_SIZE);

  // The "size" property can be an int, a double, or JS::UndefinedValue()
  // (for arrays of undefined length), and must always fit in a size_t.
  if (size.isInt32()) {
    *result = size.toInt32();
    return true;
  }
  if (size.isDouble()) {
    *result = Convert<size_t>(size.toDouble());
    return true;
  }

  MOZ_ASSERT(size.isUndefined());
  return false;
}

size_t CType::GetSize(JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value size = JS_GetReservedSlot(obj, SLOT_SIZE);

  MOZ_ASSERT(!size.isUndefined());

  // The "size" property can be an int, a double, or JS::UndefinedValue()
  // (for arrays of undefined length), and must always fit in a size_t.
  // For callers who know it can never be JS::UndefinedValue(), return a size_t
  // directly.
  if (size.isInt32()) {
    return size.toInt32();
  }
  return Convert<size_t>(size.toDouble());
}

bool CType::IsSizeDefined(JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value size = JS_GetReservedSlot(obj, SLOT_SIZE);

  // The "size" property can be an int, a double, or JS::UndefinedValue()
  // (for arrays of undefined length), and must always fit in a size_t.
  MOZ_ASSERT(size.isInt32() || size.isDouble() || size.isUndefined());
  return !size.isUndefined();
}

size_t CType::GetAlignment(JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value slot = JS_GetReservedSlot(obj, SLOT_ALIGN);
  return static_cast<size_t>(slot.toInt32());
}

ffi_type* CType::GetFFIType(JSContext* cx, JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value slot = JS_GetReservedSlot(obj, SLOT_FFITYPE);

  if (!slot.isUndefined()) {
    return static_cast<ffi_type*>(slot.toPrivate());
  }

  UniquePtrFFIType result;
  switch (CType::GetTypeCode(obj)) {
    case TYPE_array:
      result = ArrayType::BuildFFIType(cx, obj);
      break;

    case TYPE_struct:
      result = StructType::BuildFFIType(cx, obj);
      break;

    default:
      MOZ_CRASH("simple types must have an ffi_type");
  }

  if (!result) {
    return nullptr;
  }
  JS_SetReservedSlot(obj, SLOT_FFITYPE, PrivateValue(result.get()));
  return result.release();
}

JSString* CType::GetName(JSContext* cx, HandleObject obj) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value string = JS_GetReservedSlot(obj, SLOT_NAME);
  if (!string.isUndefined()) {
    return string.toString();
  }

  // Build the type name lazily.
  JSString* name = BuildTypeName(cx, obj);
  if (!name) {
    return nullptr;
  }
  JS_SetReservedSlot(obj, SLOT_NAME, StringValue(name));
  return name;
}

JSObject* CType::GetProtoFromCtor(JSObject* obj, CTypeProtoSlot slot) {
  // Get ctypes.{Pointer,Array,Struct}Type.prototype from a reserved slot
  // on the type constructor.
  Value protoslot = js::GetFunctionNativeReserved(obj, SLOT_FN_CTORPROTO);
  JSObject* proto = &protoslot.toObject();
  MOZ_ASSERT(proto);
  MOZ_ASSERT(CType::IsCTypeProto(proto));

  // Get the desired prototype.
  Value result = JS_GetReservedSlot(proto, slot);
  return &result.toObject();
}

JSObject* CType::GetProtoFromType(JSContext* cx, JSObject* objArg,
                                  CTypeProtoSlot slot) {
  MOZ_ASSERT(IsCType(objArg));
  RootedObject obj(cx, objArg);

  // Get the prototype of the type object.
  RootedObject proto(cx);
  if (!JS_GetPrototype(cx, obj, &proto)) {
    return nullptr;
  }
  MOZ_ASSERT(proto);
  MOZ_ASSERT(CType::IsCTypeProto(proto));

  // Get the requested ctypes.{Pointer,Array,Struct,Function}Type.prototype.
  Value result = JS_GetReservedSlot(proto, slot);
  MOZ_ASSERT(result.isObject());
  return &result.toObject();
}

bool CType::IsCTypeOrProto(HandleValue v) {
  if (!v.isObject()) {
    return false;
  }
  JSObject* obj = &v.toObject();
  return CType::IsCType(obj) || CType::IsCTypeProto(obj);
}

bool CType::PrototypeGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  unsigned slot = CType::IsCTypeProto(obj) ? (unsigned)SLOT_OURDATAPROTO
                                           : (unsigned)SLOT_PROTO;
  args.rval().set(JS_GetReservedSlot(obj, slot));
  MOZ_ASSERT(args.rval().isObject() || args.rval().isUndefined());
  return true;
}

bool CType::IsCType(HandleValue v) {
  return v.isObject() && CType::IsCType(&v.toObject());
}

bool CType::NameGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  JSString* name = CType::GetName(cx, obj);
  if (!name) {
    return false;
  }

  args.rval().setString(name);
  return true;
}

bool CType::SizeGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  args.rval().set(JS_GetReservedSlot(obj, SLOT_SIZE));
  MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined());
  return true;
}

bool CType::PtrGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  JSObject* pointerType = PointerType::CreateInternal(cx, obj);
  if (!pointerType) {
    return false;
  }

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

bool CType::CreateArray(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedObject baseType(cx, GetThisObject(cx, args, "CType.prototype.array"));
  if (!baseType) {
    return false;
  }
  if (!CType::IsCType(baseType)) {
    return IncompatibleThisProto(cx, "CType.prototype.array", args.thisv());
  }

  // Construct and return a new ArrayType object.
  if (args.length() > 1) {
    return ArgumentLengthError(cx, "CType.prototype.array", "at most one", "");
  }

  // Convert the length argument to a size_t.
  size_t length = 0;
  if (args.length() == 1 && !jsvalToSize(cx, args[0], false, &length)) {
    return ArgumentTypeMismatch(cx, "", "CType.prototype.array",
                                "a nonnegative integer");
  }

  JSObject* result =
      ArrayType::CreateInternal(cx, baseType, length, args.length() == 1);
  if (!result) {
    return false;
  }

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

bool CType::ToString(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedObject obj(cx, GetThisObject(cx, args, "CType.prototype.toString"));
  if (!obj) {
    return false;
  }
  if (!CType::IsCType(obj) && !CType::IsCTypeProto(obj)) {
    return IncompatibleThisProto(cx, "CType.prototype.toString",
                                 InformalValueTypeName(args.thisv()));
  }

  // Create the appropriate string depending on whether we're sCTypeClass or
  // sCTypeProtoClass.
  JSString* result;
  if (CType::IsCType(obj)) {
    AutoString type;
    AppendString(cx, type, "type ");
    AppendString(cx, type, GetName(cx, obj));
    if (!type) {
      return false;
    }
    result = NewUCString(cx, type.finish());
  } else {
    result = JS_NewStringCopyZ(cx, "[CType proto object]");
  }
  if (!result) {
    return false;
  }

  args.rval().setString(result);
  return true;
}

bool CType::ToSource(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  JSObject* obj = GetThisObject(cx, args, "CType.prototype.toSource");
  if (!obj) {
    return false;
  }
  if (!CType::IsCType(obj) && !CType::IsCTypeProto(obj)) {
    return IncompatibleThisProto(cx, "CType.prototype.toSource",
                                 InformalValueTypeName(args.thisv()));
  }

  // Create the appropriate string depending on whether we're sCTypeClass or
  // sCTypeProtoClass.
  JSString* result;
  if (CType::IsCType(obj)) {
    AutoString source;
    BuildTypeSource(cx, obj, false, source);
    if (!source) {
      return false;
    }
    result = NewUCString(cx, source.finish());
  } else {
    result = JS_NewStringCopyZ(cx, "[CType proto object]");
  }
  if (!result) {
    return false;
  }

  args.rval().setString(result);
  return true;
}

bool CType::HasInstance(JSContext* cx, HandleObject obj, MutableHandleValue v,
                        bool* bp) {
  MOZ_ASSERT(CType::IsCType(obj));

  Value slot = JS_GetReservedSlot(obj, SLOT_PROTO);
  JS::Rooted<JSObject*> prototype(cx, &slot.toObject());
  MOZ_ASSERT(prototype);
  MOZ_ASSERT(CData::IsCDataProto(prototype));

  *bp = false;
  if (v.isPrimitive()) {
    return true;
  }

  RootedObject proto(cx, &v.toObject());
  for (;;) {
    if (!JS_GetPrototype(cx, proto, &proto)) {
      return false;
    }
    if (!proto) {
      break;
    }
    if (proto == prototype) {
      *bp = true;
      break;
    }
  }
  return true;
}

static JSObject* CType::GetGlobalCTypes(JSContext* cx, JSObject* objArg) {
  MOZ_ASSERT(CType::IsCType(objArg));

  RootedObject obj(cx, objArg);
  RootedObject objTypeProto(cx);
  if (!JS_GetPrototype(cx, obj, &objTypeProto)) {
    return nullptr;
  }
  MOZ_ASSERT(objTypeProto);
  MOZ_ASSERT(CType::IsCTypeProto(objTypeProto));

  Value valCTypes = JS_GetReservedSlot(objTypeProto, SLOT_CTYPES);
  MOZ_ASSERT(valCTypes.isObject());
  return &valCTypes.toObject();
}

/*******************************************************************************
** ABI implementation
*******************************************************************************/

bool ABI::IsABI(JSObject* obj) { return JS_GetClass(obj) == &sCABIClass; }

bool ABI::ToSource(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (args.length() != 0) {
    return ArgumentLengthError(cx, "ABI.prototype.toSource", "no", "s");
  }

  JSObject* obj = GetThisObject(cx, args, "ABI.prototype.toSource");
  if (!obj) {
    return false;
  }
  if (!ABI::IsABI(obj)) {
    return IncompatibleThisProto(cx, "ABI.prototype.toSource",
                                 InformalValueTypeName(args.thisv()));
  }

  JSString* result;
  switch (GetABICode(obj)) {
    case ABI_DEFAULT:
      result = JS_NewStringCopyZ(cx, "ctypes.default_abi");
      break;
    case ABI_STDCALL:
      result = JS_NewStringCopyZ(cx, "ctypes.stdcall_abi");
      break;
    case ABI_THISCALL:
      result = JS_NewStringCopyZ(cx, "ctypes.thiscall_abi");
      break;
    case ABI_WINAPI:
      result = JS_NewStringCopyZ(cx, "ctypes.winapi_abi");
      break;
    default:
      JS_ReportErrorASCII(cx, "not a valid ABICode");
      return false;
  }
  if (!result) {
    return false;
  }

  args.rval().setString(result);
  return true;
}

/*******************************************************************************
** PointerType implementation
*******************************************************************************/

bool PointerType::Create(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  // Construct and return a new PointerType object.
  if (args.length() != 1) {
    return ArgumentLengthError(cx, "PointerType", "one", "");
  }

  Value arg = args[0];
  RootedObject obj(cx);
  if (arg.isPrimitive() || !CType::IsCType(obj = &arg.toObject())) {
    return ArgumentTypeMismatch(cx, "", "PointerType", "a CType");
  }

  JSObject* result = CreateInternal(cx, obj);
  if (!result) {
    return false;
  }

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

JSObject* PointerType::CreateInternal(JSContext* cx, HandleObject baseType) {
  // check if we have a cached PointerType on our base CType.
  Value slot = JS_GetReservedSlot(baseType, SLOT_PTR);
  if (!slot.isUndefined()) {
    return &slot.toObject();
  }

  // Get ctypes.PointerType.prototype and the common prototype for CData objects
  // of this type, or ctypes.FunctionType.prototype for function pointers.
  CTypeProtoSlot slotId = CType::GetTypeCode(baseType) == TYPE_function
                              ? SLOT_FUNCTIONDATAPROTO
                              : SLOT_POINTERDATAPROTO;
  RootedObject dataProto(cx, CType::GetProtoFromType(cx, baseType, slotId));
  if (!dataProto) {
    return nullptr;
  }
  RootedObject typeProto(
      cx, CType::GetProtoFromType(cx, baseType, SLOT_POINTERPROTO));
  if (!typeProto) {
    return nullptr;
  }

  // Create a new CType object with the common properties and slots.
  RootedValue sizeVal(cx, Int32Value(sizeof(void*)));
  RootedValue alignVal(cx, Int32Value(ffi_type_pointer.alignment));
  JSObject* typeObj =
      CType::Create(cx, typeProto, dataProto, TYPE_pointer, nullptr, sizeVal,
                    alignVal, &ffi_type_pointer);
  if (!typeObj) {
    return nullptr;
  }

  // Set the target type. (This will be 'null' for an opaque pointer type.)
  JS_SetReservedSlot(typeObj, SLOT_TARGET_T, ObjectValue(*baseType));

  // Finally, cache our newly-created PointerType on our pointed-to CType.
  JS_SetReservedSlot(baseType, SLOT_PTR, ObjectValue(*typeObj));

  return typeObj;
}

bool PointerType::ConstructData(JSContext* cx, HandleObject obj,
                                const CallArgs& args) {
  if (!CType::IsCType(obj) || CType::GetTypeCode(obj) != TYPE_pointer) {
    return IncompatibleCallee(cx, "PointerType constructor", obj);
  }

  if (args.length() > 3) {
    return ArgumentLengthError(cx, "PointerType constructor", "0, 1, 2, or 3",
                               "s");
  }

  RootedObject result(cx, CData::Create(cx, obj, nullptr, nullptr, true));
  if (!result) {
    return false;
  }

  // Set return value early, must not observe *vp after
  args.rval().setObject(*result);

  // There are 3 things that we might be creating here:
  // 1 - A null pointer (no arguments)
  // 2 - An initialized pointer (1 argument)
  // 3 - A closure (1-3 arguments)
  //
  // The API doesn't give us a perfect way to distinguish 2 and 3, but the
  // heuristics we use should be fine.

  //
  // Case 1 - Null pointer
  //
  if (args.length() == 0) {
    return true;
  }

  // Analyze the arguments a bit to decide what to do next.
  RootedObject baseObj(cx, PointerType::GetBaseType(obj));
  bool looksLikeClosure = CType::GetTypeCode(baseObj) == TYPE_function &&
                          args[0].isObject() &&
                          JS::IsCallable(&args[0].toObject());

  //
  // Case 2 - Initialized pointer
  //
  if (!looksLikeClosure) {
    if (args.length() != 1) {
      return ArgumentLengthError(cx, "FunctionType constructor", "one", "");
    }
    return ExplicitConvert(cx, args[0], obj, CData::GetData(result),
                           ConversionType::Construct);
  }

  //
  // Case 3 - Closure
  //

  // The second argument is an optional 'this' parameter with which to invoke
  // the given js function. Callers may leave this blank, or pass null if they
  // wish to pass the third argument.
  RootedObject thisObj(cx, nullptr);
  if (args.length() >= 2) {
    if (args[1].isNull()) {
      thisObj = nullptr;
    } else if (args[1].isObject()) {
      thisObj = &args[1].toObject();
    } else if (!JS_ValueToObject(cx, args[1], &thisObj)) {
      return false;
    }
  }

  // The third argument is an optional error sentinel that js-ctypes will return
  // if an exception is raised while executing the closure. The type must match
  // the return type of the callback.
  RootedValue errVal(cx);
  if (args.length() == 3) {
    errVal = args[2];
  }

  RootedObject fnObj(cx, &args[0].toObject());
  return FunctionType::ConstructData(cx, baseObj, result, fnObj, thisObj,
                                     errVal);
}

JSObject* PointerType::GetBaseType(JSObject* obj) {
  MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_pointer);

  Value type = JS_GetReservedSlot(obj, SLOT_TARGET_T);
  MOZ_ASSERT(!type.isNull());
  return &type.toObject();
}

bool PointerType::IsPointerType(HandleValue v) {
  if (!v.isObject()) {
    return false;
  }
  JSObject* obj = &v.toObject();
  return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_pointer;
}

bool PointerType::IsPointer(HandleValue v) {
  if (!v.isObject()) {
    return false;
  }
  JSObject* obj = MaybeUnwrapArrayWrapper(&v.toObject());
  return CData::IsCData(obj) &&
         CType::GetTypeCode(CData::GetCType(obj)) == TYPE_pointer;
}

bool PointerType::TargetTypeGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  args.rval().set(JS_GetReservedSlot(obj, SLOT_TARGET_T));
  MOZ_ASSERT(args.rval().isObject());
  return true;
}

bool PointerType::IsNull(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedObject obj(cx, GetThisObject(cx, args, "PointerType.prototype.isNull"));
  if (!obj) {
    return false;
  }
  if (!CData::IsCDataMaybeUnwrap(&obj)) {
    return IncompatibleThisProto(cx, "PointerType.prototype.isNull",
                                 args.thisv());
  }

  // Get pointer type and base type.
  JSObject* typeObj = CData::GetCType(obj);
  if (CType::GetTypeCode(typeObj) != TYPE_pointer) {
    return IncompatibleThisType(cx, "PointerType.prototype.isNull",
                                "non-PointerType CData", args.thisv());
  }

  void* data = *static_cast<void**>(CData::GetData(obj));
  args.rval().setBoolean(data == nullptr);
  return true;
}

bool PointerType::OffsetBy(JSContext* cx, const CallArgs& args, int offset,
                           const char* name) {
  RootedObject obj(cx, GetThisObject(cx, args, name));
  if (!obj) {
    return false;
  }
  if (!CData::IsCDataMaybeUnwrap(&obj)) {
    return IncompatibleThisProto(cx, name, args.thisv());
  }

  RootedObject typeObj(cx, CData::GetCType(obj));
  if (CType::GetTypeCode(typeObj) != TYPE_pointer) {
    return IncompatibleThisType(cx, name, "non-PointerType CData",
                                args.thisv());
  }

  RootedObject baseType(cx, PointerType::GetBaseType(typeObj));
  if (!CType::IsSizeDefined(baseType)) {
    return UndefinedSizePointerError(cx, "modify", obj);
  }

  size_t elementSize = CType::GetSize(baseType);
  char* data = static_cast<char*>(*static_cast<void**>(CData::GetData(obj)));
  void* address = data + offset * elementSize;

  // Create a PointerType CData object containing the new address.
  JSObject* result = CData::Create(cx, typeObj, nullptr, &address, true);
  if (!result) {
    return false;
  }

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

bool PointerType::Increment(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return OffsetBy(cx, args, 1, "PointerType.prototype.increment");
}

bool PointerType::Decrement(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return OffsetBy(cx, args, -1, "PointerType.prototype.decrement");
}

bool PointerType::ContentsGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  RootedObject baseType(cx, GetBaseType(CData::GetCType(obj)));
  if (!CType::IsSizeDefined(baseType)) {
    return UndefinedSizePointerError(cx, "get contents of", obj);
  }

  void* data = *static_cast<void**>(CData::GetData(obj));
  if (data == nullptr) {
    return NullPointerError(cx, "read contents of", obj);
  }

  RootedValue result(cx);
  if (!ConvertToJS(cx, baseType, nullptr, data, false, false, &result)) {
    return false;
  }

  args.rval().set(result);
  return true;
}

bool PointerType::ContentsSetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  RootedObject baseType(cx, GetBaseType(CData::GetCType(obj)));
  if (!CType::IsSizeDefined(baseType)) {
    return UndefinedSizePointerError(cx, "set contents of", obj);
  }

  void* data = *static_cast<void**>(CData::GetData(obj));
  if (data == nullptr) {
    return NullPointerError(cx, "write contents to", obj);
  }

  args.rval().setUndefined();
  return ImplicitConvert(cx, args.get(0), baseType, data,
                         ConversionType::Setter, nullptr);
}

/*******************************************************************************
** ArrayType implementation
*******************************************************************************/

bool ArrayType::Create(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  // Construct and return a new ArrayType object.
  if (args.length() < 1 || args.length() > 2) {
    return ArgumentLengthError(cx, "ArrayType", "one or two", "s");
  }

  if (args[0].isPrimitive() || !CType::IsCType(&args[0].toObject())) {
    return ArgumentTypeMismatch(cx, "first ", "ArrayType", "a CType");
  }

  // Convert the length argument to a size_t.
  size_t length = 0;
  if (args.length() == 2 && !jsvalToSize(cx, args[1], false, &length)) {
    return ArgumentTypeMismatch(cx, "second ", "ArrayType",
                                "a nonnegative integer");
  }

  RootedObject baseType(cx, &args[0].toObject());
  JSObject* result = CreateInternal(cx, baseType, length, args.length() == 2);
  if (!result) {
    return false;
  }

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

JSObject* ArrayType::CreateInternal(JSContext* cx, HandleObject baseType,
                                    size_t length, bool lengthDefined) {
  // Get ctypes.ArrayType.prototype and the common prototype for CData objects
  // of this type, from ctypes.CType.prototype.
  RootedObject typeProto(
      cx, CType::GetProtoFromType(cx, baseType, SLOT_ARRAYPROTO));
  if (!typeProto) {
    return nullptr;
  }
  RootedObject dataProto(
      cx, CType::GetProtoFromType(cx, baseType, SLOT_ARRAYDATAPROTO));
  if (!dataProto) {
    return nullptr;
  }

  // Determine the size of the array from the base type, if possible.
  // The size of the base type must be defined.
  // If our length is undefined, both our size and length will be undefined.
  size_t baseSize;
  if (!CType::GetSafeSize(baseType, &baseSize)) {
    JS_ReportErrorASCII(cx, "base size must be defined");
    return nullptr;
  }

  RootedValue sizeVal(cx);
  RootedValue lengthVal(cx);
  if (lengthDefined) {
    // Check for overflow, and convert to an int or double as required.
    size_t size = length * baseSize;
    if (length > 0 && size / length != baseSize) {
      SizeOverflow(cx, "array size", "size_t");
      return nullptr;
    }
    if (!SizeTojsval(cx, size, &sizeVal)) {
      SizeOverflow(cx, "array size", "JavaScript number");
      return nullptr;
    }
    if (!SizeTojsval(cx, length, &lengthVal)) {
      SizeOverflow(cx, "array length", "JavaScript number");
      return nullptr;
    }
  }

  RootedValue alignVal(cx, Int32Value(CType::GetAlignment(baseType)));

  // Create a new CType object with the common properties and slots.
  JSObject* typeObj = CType::Create(cx, typeProto, dataProto, TYPE_array,
                                    nullptr, sizeVal, alignVal, nullptr);
  if (!typeObj) {
    return nullptr;
  }

  // Set the element type.
  JS_SetReservedSlot(typeObj, SLOT_ELEMENT_T, ObjectValue(*baseType));

  // Set the length.
  JS_SetReservedSlot(typeObj, SLOT_LENGTH, lengthVal);

  return typeObj;
}

bool ArrayType::ConstructData(JSContext* cx, HandleObject obj_,
                              const CallArgs& args) {
  RootedObject obj(cx, obj_);  // Make a mutable version

  if (!CType::IsCType(obj) || CType::GetTypeCode(obj) != TYPE_array) {
    return IncompatibleCallee(cx, "ArrayType constructor", obj);
  }

  // Decide whether we have an object to initialize from. We'll override this
  // if we get a length argument instead.
  bool convertObject = args.length() == 1;

  // Check if we're an array of undefined length. If we are, allow construction
  // with a length argument, or with an actual JS array.
  if (CType::IsSizeDefined(obj)) {
    if (args.length() > 1) {
      return ArgumentLengthError(cx, "size defined ArrayType constructor",
                                 "at most one", "");
    }

  } else {
    if (args.length() != 1) {
      return ArgumentLengthError(cx, "size undefined ArrayType constructor",
                                 "one", "");
    }

    RootedObject baseType(cx, GetBaseType(obj));

    size_t length;
    if (jsvalToSize(cx, args[0], false, &length)) {
      // Have a length, rather than an object to initialize from.
      convertObject = false;

    } else if (args[0].isObject()) {
      // We were given an object with a .length property.
      // This could be a JS array, or a CData array.
      RootedObject arg(cx, &args[0].toObject());
      RootedValue lengthVal(cx);
      if (!JS_GetProperty(cx, arg, "length", &lengthVal) ||
          !jsvalToSize(cx, lengthVal, false, &length)) {
        return ArgumentTypeMismatch(cx, "",
                                    "size undefined ArrayType constructor",
                                    "an array object or integer");
      }

    } else if (args[0].isString()) {
      // We were given a string. Size the array to the appropriate length,
      // including space for the terminator.
      JSString* sourceString = args[0].toString();
      size_t sourceLength = sourceString->length();
      Rooted<JSFlatString*> sourceFlat(cx, sourceString->ensureFlat(cx));
      if (!sourceFlat) {
        return false;
      }

      switch (CType::GetTypeCode(baseType)) {
        case TYPE_char:
        case TYPE_signed_char:
        case TYPE_unsigned_char: {
          // Reject if unpaired surrogate characters are present.
          if (!ReportErrorIfUnpairedSurrogatePresent(cx, sourceFlat)) {
            return false;
          }

          // Determine the UTF-8 length.
          length = JS::GetDeflatedUTF8StringLength(sourceFlat);

          ++length;
          break;
        }
        case TYPE_char16_t:
          length = sourceLength + 1;
          break;
        default:
          return ConvError(cx, obj, args[0], ConversionType::Construct);
      }

    } else {
      return ArgumentTypeMismatch(cx, "",
                                  "size undefined ArrayType constructor",
                                  "an array object or integer");
    }

    // Construct a new ArrayType of defined length, for the new CData object.
    obj = CreateInternal(cx, baseType, length, true);
    if (!obj) {
      return false;
    }
  }

  JSObject* result = CData::Create(cx, obj, nullptr, nullptr, true);
  if (!result) {
    return false;
  }

  args.rval().setObject(*result);

  if (convertObject) {
    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result),
                         ConversionType::Construct))
      return false;
  }

  return true;
}

JSObject* ArrayType::GetBaseType(JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));
  MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array);

  Value type = JS_GetReservedSlot(obj, SLOT_ELEMENT_T);
  MOZ_ASSERT(!type.isNull());
  return &type.toObject();
}

bool ArrayType::GetSafeLength(JSObject* obj, size_t* result) {
  MOZ_ASSERT(CType::IsCType(obj));
  MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array);

  Value length = JS_GetReservedSlot(obj, SLOT_LENGTH);

  // The "length" property can be an int, a double, or JS::UndefinedValue()
  // (for arrays of undefined length), and must always fit in a size_t.
  if (length.isInt32()) {
    *result = length.toInt32();
    return true;
  }
  if (length.isDouble()) {
    *result = Convert<size_t>(length.toDouble());
    return true;
  }

  MOZ_ASSERT(length.isUndefined());
  return false;
}

size_t ArrayType::GetLength(JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));
  MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array);

  Value length = JS_GetReservedSlot(obj, SLOT_LENGTH);

  MOZ_ASSERT(!length.isUndefined());

  // The "length" property can be an int, a double, or JS::UndefinedValue()
  // (for arrays of undefined length), and must always fit in a size_t.
  // For callers who know it can never be JS::UndefinedValue(), return a size_t
  // directly.
  if (length.isInt32()) {
    return length.toInt32();
  }
  return Convert<size_t>(length.toDouble());
}

UniquePtrFFIType ArrayType::BuildFFIType(JSContext* cx, JSObject* obj) {
  MOZ_ASSERT(CType::IsCType(obj));
  MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array);
  MOZ_ASSERT(CType::IsSizeDefined(obj));

  JSObject* baseType = ArrayType::GetBaseType(obj);
  ffi_type* ffiBaseType = CType::GetFFIType(cx, baseType);
  if (!ffiBaseType) {
    return nullptr;
  }

  size_t length = ArrayType::GetLength(obj);

  // Create an ffi_type to represent the array. This is necessary for the case
  // where the array is part of a struct. Since libffi has no intrinsic
  // support for array types, we approximate it by creating a struct type
  // with elements of type 'baseType' and with appropriate size and alignment
  // values. It would be nice to not do all the work of setting up 'elements',
  // but some libffi platforms currently require that it be meaningful. I'm
  // looking at you, x86_64.
  auto ffiType = cx->make_unique<ffi_type>();
  if (!ffiType) {
    return nullptr;
  }

  ffiType->type = FFI_TYPE_STRUCT;
  ffiType->size = CType::GetSize(obj);
  ffiType->alignment = CType::GetAlignment(obj);
  ffiType->elements = cx->pod_malloc<ffi_type*>(length + 1);
  if (!ffiType->elements) {
    return nullptr;
  }

  for (size_t i = 0; i < length; ++i) {
    ffiType->elements[i] = ffiBaseType;
  }
  ffiType->elements[length] = nullptr;

  return ffiType;
}

bool ArrayType::IsArrayType(HandleValue v) {
  if (!v.isObject()) {
    return false;
  }
  JSObject* obj = &v.toObject();
  return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array;
}

bool ArrayType::IsArrayOrArrayType(HandleValue v) {
  if (!v.isObject()) {
    return false;
  }
  JSObject* obj = MaybeUnwrapArrayWrapper(&v.toObject());

  // Allow both CTypes and CDatas of the ArrayType persuasion by extracting the
  // CType if we're dealing with a CData.
  if (CData::IsCData(obj)) {
    obj = CData::GetCType(obj);
  }
  return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array;
}

bool ArrayType::ElementTypeGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());
  args.rval().set(JS_GetReservedSlot(obj, SLOT_ELEMENT_T));
  MOZ_ASSERT(args.rval().isObject());
  return true;
}

bool ArrayType::LengthGetter(JSContext* cx, const JS::CallArgs& args) {
  RootedObject obj(cx, &args.thisv().toObject());

  // This getter exists for both CTypes and CDatas of the ArrayType persuasion.
  // If we're dealing with a CData, get the CType from it.
  if (CData::IsCDataMaybeUnwrap(&obj)) {
    obj = CData::GetCType(obj);
  }

  args.rval().set(JS_GetReservedSlot(obj, SLOT_LENGTH));
  MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined());
  return true;
}

bool ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval,
                       MutableHandleValue vp, bool* handled) {
  *handled = false;

  // This should never happen, but we'll check to be safe.
  if (!CData::IsCData(obj)) {
    RootedValue objVal(cx, ObjectValue(*obj));
    return IncompatibleThisProto(cx, "ArrayType property getter", objVal);
  }

  // Bail early if we're not an ArrayType. (This setter is present for all
  // CData, regardless of CType.)
  JSObject* typeObj = CData::GetCType(obj);
  if (CType::GetTypeCode(typeObj) != TYPE_array) {
    return true;
  }

  // Convert the index to a size_t and bounds-check it.
  size_t index;
  size_t length = GetLength(typeObj);
  bool ok = jsidToSize(cx, idval, true, &index);
  int32_t dummy;
  if (!ok && JSID_IS_SYMBOL(idval)) {
    return true;
  }
  bool dummy2;
  if (!ok && JSID_IS_STRING(idval) &&
      !StringToInteger(cx, JSID_TO_STRING(idval), &dummy, &dummy2)) {
    // String either isn't a number, or doesn't fit in size_t.