diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/ctypes/CTypes.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/ctypes/CTypes.cpp')
-rw-r--r-- | js/src/ctypes/CTypes.cpp | 9052 |
1 files changed, 9052 insertions, 0 deletions
diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp new file mode 100644 index 000000000..0facd0009 --- /dev/null +++ b/js/src/ctypes/CTypes.cpp @@ -0,0 +1,9052 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/SizePrintfMacros.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Vector.h" + +#include <limits> +#include <math.h> +#include <stdint.h> + +#if defined(XP_WIN) +#include <float.h> +#endif + +#if defined(SOLARIS) +#include <ieeefp.h> +#endif + +#ifdef HAVE_SSIZE_T +#include <sys/types.h> +#endif + +#if defined(XP_UNIX) +#include <errno.h> +#elif defined(XP_WIN) +#include <windows.h> +#endif + +#include "jscntxt.h" +#include "jsexn.h" +#include "jsfun.h" +#include "jsnum.h" +#include "jsprf.h" + +#include "builtin/TypedObject.h" +#include "ctypes/Library.h" +#include "gc/Policy.h" +#include "gc/Zone.h" +#include "js/Vector.h" + +#include "jsatominlines.h" +#include "jsobjinlines.h" + +using namespace std; + +using JS::AutoCheckCannotGC; + +namespace js { +namespace ctypes { + +template <typename CharT> +size_t +GetDeflatedUTF8StringLength(JSContext* maybecx, const CharT* chars, + size_t nchars) +{ + size_t nbytes; + const CharT* end; + unsigned c, c2; + + nbytes = nchars; + for (end = chars + nchars; chars != end; chars++) { + c = *chars; + if (c < 0x80) + continue; + if (0xD800 <= c && c <= 0xDFFF) { + /* Surrogate pair. */ + chars++; + + /* nbytes sets 1 length since this is surrogate pair. */ + nbytes--; + if (c >= 0xDC00 || chars == end) + goto bad_surrogate; + c2 = *chars; + if (c2 < 0xDC00 || c2 > 0xDFFF) + goto bad_surrogate; + c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; + } + c >>= 11; + nbytes++; + while (c) { + c >>= 5; + nbytes++; + } + } + return nbytes; + + bad_surrogate: + if (maybecx) { + js::gc::AutoSuppressGC suppress(maybecx); + char buffer[10]; + SprintfLiteral(buffer, "0x%x", c); + JS_ReportErrorFlagsAndNumberASCII(maybecx, JSREPORT_ERROR, + GetErrorMessage, + nullptr, JSMSG_BAD_SURROGATE_CHAR, + buffer); + } + return (size_t) -1; +} + +template size_t +GetDeflatedUTF8StringLength(JSContext* maybecx, const Latin1Char* chars, + size_t nchars); + +template size_t +GetDeflatedUTF8StringLength(JSContext* maybecx, const char16_t* chars, + size_t nchars); + +static size_t +GetDeflatedUTF8StringLength(JSContext* maybecx, JSLinearString* str) +{ + size_t length = str->length(); + + JS::AutoCheckCannotGC nogc; + return str->hasLatin1Chars() + ? GetDeflatedUTF8StringLength(maybecx, str->latin1Chars(nogc), length) + : GetDeflatedUTF8StringLength(maybecx, str->twoByteChars(nogc), length); +} + +template <typename CharT> +bool +DeflateStringToUTF8Buffer(JSContext* maybecx, const CharT* src, size_t srclen, + char* dst, size_t* dstlenp) +{ + size_t i, utf8Len; + char16_t c, c2; + uint32_t v; + uint8_t utf8buf[6]; + + size_t dstlen = *dstlenp; + size_t origDstlen = dstlen; + + while (srclen) { + c = *src++; + srclen--; + if (c >= 0xDC00 && c <= 0xDFFF) + goto badSurrogate; + if (c < 0xD800 || c > 0xDBFF) { + v = c; + } else { + if (srclen < 1) + goto badSurrogate; + c2 = *src; + if ((c2 < 0xDC00) || (c2 > 0xDFFF)) + goto badSurrogate; + src++; + srclen--; + v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; + } + if (v < 0x0080) { + /* no encoding necessary - performance hack */ + if (dstlen == 0) + goto bufferTooSmall; + *dst++ = (char) v; + utf8Len = 1; + } else { + utf8Len = js::OneUcs4ToUtf8Char(utf8buf, v); + if (utf8Len > dstlen) + goto bufferTooSmall; + for (i = 0; i < utf8Len; i++) + *dst++ = (char) utf8buf[i]; + } + dstlen -= utf8Len; + } + *dstlenp = (origDstlen - dstlen); + return true; + +badSurrogate: + *dstlenp = (origDstlen - dstlen); + /* Delegate error reporting to the measurement function. */ + if (maybecx) + GetDeflatedUTF8StringLength(maybecx, src - 1, srclen + 1); + return false; + +bufferTooSmall: + *dstlenp = (origDstlen - dstlen); + if (maybecx) { + js::gc::AutoSuppressGC suppress(maybecx); + JS_ReportErrorNumberASCII(maybecx, GetErrorMessage, nullptr, + JSMSG_BUFFER_TOO_SMALL); + } + return false; +} + +template bool +DeflateStringToUTF8Buffer(JSContext* maybecx, const Latin1Char* src, size_t srclen, + char* dst, size_t* dstlenp); + +template bool +DeflateStringToUTF8Buffer(JSContext* maybecx, const char16_t* src, size_t srclen, + char* dst, size_t* dstlenp); + +static bool +DeflateStringToUTF8Buffer(JSContext* maybecx, JSLinearString* str, char* dst, + size_t* dstlenp) +{ + size_t length = str->length(); + + JS::AutoCheckCannotGC nogc; + return str->hasLatin1Chars() + ? DeflateStringToUTF8Buffer(maybecx, str->latin1Chars(nogc), length, dst, dstlenp) + : DeflateStringToUTF8Buffer(maybecx, str->twoByteChars(nogc), length, dst, dstlenp); +} + +/******************************************************************************* +** 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); +} // 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); + static bool Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp, + ObjectOpResult& result); + 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, 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, 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, ArrayType::Getter, ArrayType::Setter, + 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, 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, 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, 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 +}; + +static MOZ_ALWAYS_INLINE JSString* +NewUCString(JSContext* cx, const AutoString& 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 const char* +EncodeLatin1(JSContext* cx, AutoString& str, JSAutoByteString& bytes) +{ + return bytes.encodeLatin1(cx, NewUCString(cx, str)); +} + +static const char* +CTypesToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes) +{ + if (val.isObject() && + (CType::IsCType(&val.toObject()) || CData::IsCData(&val.toObject()))) { + RootedString str(cx, JS_ValueToSource(cx, val)); + return bytes.encodeLatin1(cx, str); + } + 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(source, #name); \ + break; + CTYPES_FOR_EACH_TYPE(BUILD_SOURCE) +#undef BUILD_SOURCE + case TYPE_void_t: + AppendString(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(source, "struct "); + AppendString(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(source, " "); + if (nameStr) { + MOZ_ASSERT(ptrCount == 0); + AppendString(source, nameStr); + } else if (ptrCount) { + AppendString(source, "("); + AppendChars(source, '*', ptrCount); + AppendString(source, ")"); + } + AppendString(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(source, ", "); + } + } + if (fninfo->mIsVariadic) { + AppendString(source, "..."); + } + } + AppendString(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(source, "<<error converting function to string>>"); + return; + } + AppendString(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(source, " at argument "); + AppendUInt(source, argIndex + 1); + AppendString(source, " of "); + BuildFunctionTypeSource(cx, funObj, source); + break; + } + case ConversionType::Finalizer: + MOZ_ASSERT(funObj); + + AppendString(source, " at argument 1 of "); + BuildFunctionTypeSource(cx, funObj, source); + break; + case ConversionType::Return: + MOZ_ASSERT(funObj); + + AppendString(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 bool +ConvError(JSContext* cx, const char* expectedStr, HandleValue actual, + ConversionType convType, + HandleObject funObj = nullptr, unsigned argIndex = 0, + HandleObject arrObj = nullptr, unsigned arrIndex = 0) +{ + JSAutoByteString 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); + + char indexStr[16]; + SprintfLiteral(indexStr, "%u", arrIndex); + + AutoString arrSource; + JSAutoByteString arrBytes; + BuildTypeSource(cx, arrObj, true, arrSource); + const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); + if (!arrStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_ARRAY, + valStr, indexStr, arrStr); + break; + } + case TYPE_struct: { + JSFlatString* name = GetFieldName(arrObj, arrIndex); + MOZ_ASSERT(name); + JSAutoByteString nameBytes; + const char* nameStr = nameBytes.encodeLatin1(cx, name); + if (!nameStr) + return false; + + AutoString structSource; + JSAutoByteString structBytes; + BuildTypeSource(cx, arrObj, true, structSource); + const char* structStr = EncodeLatin1(cx, structSource, structBytes); + if (!structStr) + return false; + + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_STRUCT, + valStr, nameStr, expectedStr, structStr, posStr); + break; + } + default: + MOZ_CRASH("invalid arrObj value"); + } + return false; + } + + switch (convType) { + case ConversionType::Argument: { + MOZ_ASSERT(funObj); + + char indexStr[16]; + SprintfLiteral(indexStr, "%u", argIndex + 1); + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_ARG, + valStr, indexStr, funStr); + break; + } + case ConversionType::Finalizer: { + MOZ_ASSERT(funObj); + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_FIN, valStr, funStr); + break; + } + case ConversionType::Return: { + MOZ_ASSERT(funObj); + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_RET, valStr, funStr); + break; + } + case ConversionType::Setter: + case ConversionType::Construct: + MOZ_ASSERT(!funObj); + + JS_ReportErrorNumberLatin1(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)); + + AutoString expectedSource; + JSAutoByteString expectedBytes; + BuildTypeSource(cx, expectedType, true, expectedSource); + const char* expectedStr = EncodeLatin1(cx, expectedSource, expectedBytes); + if (!expectedStr) + return false; + + return ConvError(cx, expectedStr, actual, convType, funObj, argIndex, + arrObj, arrIndex); +} + +static bool +ArgumentConvError(JSContext* cx, HandleValue actual, const char* funStr, + unsigned argIndex) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char indexStr[16]; + SprintfLiteral(indexStr, "%u", argIndex + 1); + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr, funStr); + return false; +} + +static bool +ArgumentLengthError(JSContext* cx, const char* fun, const char* count, + const char* s) +{ + JS_ReportErrorNumberLatin1(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)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char expectedLengthStr[16]; + SprintfLiteral(expectedLengthStr, "%u", expectedLength); + char actualLengthStr[16]; + SprintfLiteral(actualLengthStr, "%u", actualLength); + + AutoString arrSource; + JSAutoByteString arrBytes; + BuildTypeSource(cx, arrObj, true, arrSource); + const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); + if (!arrStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARRAY_MISMATCH, + valStr, arrStr, expectedLengthStr, actualLengthStr); + return false; +} + +static bool +ArrayLengthOverflow(JSContext* cx, unsigned expectedLength, HandleObject arrObj, + unsigned actualLength, HandleValue actual, + ConversionType convType) +{ + MOZ_ASSERT(arrObj && CType::IsCType(arrObj)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char expectedLengthStr[16]; + SprintfLiteral(expectedLengthStr, "%u", expectedLength); + char actualLengthStr[16]; + SprintfLiteral(actualLengthStr, "%u", actualLength); + + AutoString arrSource; + JSAutoByteString arrBytes; + BuildTypeSource(cx, arrObj, true, arrSource); + const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); + if (!arrStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARRAY_OVERFLOW, + valStr, arrStr, expectedLengthStr, actualLengthStr); + 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) +{ + JSAutoByteString nameBytes; + const char* nameStr = nameBytes.encodeLatin1(cx, name); + if (!nameStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_DUPLICATE_FIELD, nameStr); + 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) +{ + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_EMPTY_FIN, posStr); + 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)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + AutoString structSource; + JSAutoByteString structBytes; + BuildTypeSource(cx, structObj, true, structSource); + const char* structStr = EncodeLatin1(cx, structSource, structBytes); + if (!structStr) + return false; + + char expectedCountStr[16]; + SprintfLiteral(expectedCountStr, "%u", expectedCount); + char actualCountStr[16]; + SprintfLiteral(actualCountStr, "%u", actualCount); + + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_MISMATCH, + valStr, structStr, expectedCountStr, actualCountStr, + posStr); + return false; +} + +static bool +FieldDescriptorCountError(JSContext* cx, HandleValue typeVal, size_t length) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes); + if (!valStr) + return false; + + char lengthStr[16]; + SprintfLiteral(lengthStr, "%" PRIuSIZE, length); + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_DESC_COUNT, valStr, lengthStr); + return false; +} + +static bool +FieldDescriptorNameError(JSContext* cx, HandleId id) +{ + JSAutoByteString idBytes; + RootedValue idVal(cx, IdToValue(id)); + const char* propStr = CTypesToSourceForError(cx, idVal, idBytes); + if (!propStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_DESC_NAME, propStr); + return false; +} + +static bool +FieldDescriptorSizeError(JSContext* cx, HandleObject typeObj, HandleId id) +{ + RootedValue typeVal(cx, ObjectValue(*typeObj)); + JSAutoByteString typeBytes; + const char* typeStr = CTypesToSourceForError(cx, typeVal, typeBytes); + if (!typeStr) + return false; + + RootedString idStr(cx, IdToString(cx, id)); + JSAutoByteString idBytes; + const char* propStr = idBytes.encodeLatin1(cx, idStr); + if (!propStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_DESC_SIZE, typeStr, propStr); + return false; +} + +static bool +FieldDescriptorNameTypeError(JSContext* cx, HandleValue typeVal) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_DESC_NAMETYPE, valStr); + return false; +} + +static bool +FieldDescriptorTypeError(JSContext* cx, HandleValue poroVal, HandleId id) +{ + JSAutoByteString typeBytes; + const char* typeStr = CTypesToSourceForError(cx, poroVal, typeBytes); + if (!typeStr) + return false; + + RootedString idStr(cx, IdToString(cx, id)); + JSAutoByteString idBytes; + const char* propStr = idBytes.encodeLatin1(cx, idStr); + if (!propStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_DESC_TYPE, typeStr, propStr); + return false; +} + +static bool +FieldMissingError(JSContext* cx, JSObject* typeObj, JSFlatString* name_) +{ + JSAutoByteString typeBytes; + RootedString name(cx, name_); + RootedValue typeVal(cx, ObjectValue(*typeObj)); + const char* typeStr = CTypesToSourceForError(cx, typeVal, typeBytes); + if (!typeStr) + return false; + + JSAutoByteString nameBytes; + const char* nameStr = nameBytes.encodeLatin1(cx, name); + if (!nameStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_MISSING, typeStr, nameStr); + return false; +} + +static bool +FinalizerSizeError(JSContext* cx, HandleObject funObj, HandleValue actual) +{ + MOZ_ASSERT(CType::IsCType(funObj)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIN_SIZE_ERROR, funStr, valStr); + return false; +} + +static bool +FunctionArgumentLengthMismatch(JSContext* cx, + unsigned expectedCount, unsigned actualCount, + HandleObject funObj, HandleObject typeObj, + bool isVariadic) +{ + AutoString funSource; + JSAutoByteString funBytes; + Value slot = JS_GetReservedSlot(funObj, SLOT_REFERENT); + if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { + BuildFunctionTypeSource(cx, funObj, funSource); + } else { + BuildFunctionTypeSource(cx, typeObj, funSource); + } + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + char expectedCountStr[16]; + SprintfLiteral(expectedCountStr, "%u", expectedCount); + char actualCountStr[16]; + SprintfLiteral(actualCountStr, "%u", actualCount); + + const char* variadicStr = isVariadic ? " or more": ""; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARG_COUNT_MISMATCH, + funStr, expectedCountStr, variadicStr, + actualCountStr); + return false; +} + +static bool +FunctionArgumentTypeError(JSContext* cx, + uint32_t index, HandleValue typeVal, const char* reason) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes); + if (!valStr) + return false; + + char indexStr[16]; + SprintfLiteral(indexStr, "%u", index + 1); + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARG_TYPE_ERROR, + indexStr, reason, valStr); + return false; +} + +static bool +FunctionReturnTypeError(JSContext* cx, HandleValue type, const char* reason) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, type, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_RET_TYPE_ERROR, reason, valStr); + return false; +} + +static bool +IncompatibleCallee(JSContext* cx, const char* funName, HandleObject actualObj) +{ + JSAutoByteString valBytes; + RootedValue val(cx, ObjectValue(*actualObj)); + const char* valStr = CTypesToSourceForError(cx, val, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_INCOMPATIBLE_CALLEE, funName, valStr); + return false; +} + +static bool +IncompatibleThisProto(JSContext* cx, const char* funName, + const char* actualType) +{ + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_INCOMPATIBLE_THIS, + funName, actualType); + return false; +} + +static bool +IncompatibleThisProto(JSContext* cx, const char* funName, HandleValue actualVal) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actualVal, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(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) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actualVal, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_INCOMPATIBLE_THIS_VAL, + funName, actualType, valStr); + return false; +} + +static bool +InvalidIndexError(JSContext* cx, HandleValue val) +{ + JSAutoByteString idBytes; + const char* indexStr = CTypesToSourceForError(cx, val, idBytes); + if (!indexStr) + return false; + + JS_ReportErrorNumberLatin1(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) +{ + char indexStr[16]; + SprintfLiteral(indexStr, "%" PRIuSIZE, index); + + char lengthStr[16]; + SprintfLiteral(lengthStr,"%" PRIuSIZE, length); + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + CTYPESMSG_INVALID_RANGE, indexStr, lengthStr); + return false; +} + +static bool +NonPrimitiveError(JSContext* cx, HandleObject typeObj) +{ + MOZ_ASSERT(CType::IsCType(typeObj)); + + AutoString typeSource; + JSAutoByteString typeBytes; + BuildTypeSource(cx, typeObj, true, typeSource); + const char* typeStr = EncodeLatin1(cx, typeSource, typeBytes); + if (!typeStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_NON_PRIMITIVE, typeStr); + return false; +} + +static bool +NonStringBaseError(JSContext* cx, HandleValue thisVal) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, thisVal, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_NON_STRING_BASE, valStr); + return false; +} + +static bool +NullPointerError(JSContext* cx, const char* action, HandleObject obj) +{ + JSAutoByteString valBytes; + RootedValue val(cx, ObjectValue(*obj)); + const char* valStr = CTypesToSourceForError(cx, val, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(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) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + JSAutoByteString idBytes; + RootedValue idVal(cx, IdToValue(id)); + const char* propStr = CTypesToSourceForError(cx, idVal, idBytes); + if (!propStr) + return false; + + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_PROP_NONSTRING, propStr, valStr, posStr); + 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) +{ + JSAutoByteString bytes; + const char* src = CTypesToSourceForError(cx, actual, bytes); + if (!src) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_TYPE_ERROR, expected, src); + return false; +} + +static bool +TypeOverflow(JSContext* cx, const char* expected, HandleValue actual) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_TYPE_OVERFLOW, valStr, expected); + return false; +} + +static bool +UndefinedSizeCastError(JSContext* cx, HandleObject targetTypeObj) +{ + AutoString targetTypeSource; + JSAutoByteString targetTypeBytes; + BuildTypeSource(cx, targetTypeObj, true, targetTypeSource); + const char* targetTypeStr = EncodeLatin1(cx, targetTypeSource, + targetTypeBytes); + if (!targetTypeStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_UNDEFINED_SIZE_CAST, targetTypeStr); + return false; +} + +static bool +SizeMismatchCastError(JSContext* cx, + HandleObject sourceTypeObj, HandleObject targetTypeObj, + size_t sourceSize, size_t targetSize) +{ + AutoString sourceTypeSource; + JSAutoByteString sourceTypeBytes; + BuildTypeSource(cx, sourceTypeObj, true, sourceTypeSource); + const char* sourceTypeStr = EncodeLatin1(cx, sourceTypeSource, + sourceTypeBytes); + if (!sourceTypeStr) + return false; + + AutoString targetTypeSource; + JSAutoByteString targetTypeBytes; + BuildTypeSource(cx, targetTypeObj, true, targetTypeSource); + const char* targetTypeStr = EncodeLatin1(cx, targetTypeSource, + targetTypeBytes); + if (!targetTypeStr) + return false; + + char sourceSizeStr[16]; + char targetSizeStr[16]; + SprintfLiteral(sourceSizeStr, "%" PRIuSIZE, sourceSize); + SprintfLiteral(targetSizeStr, "%" PRIuSIZE, targetSize); + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_SIZE_MISMATCH_CAST, + targetTypeStr, sourceTypeStr, + targetSizeStr, sourceSizeStr); + return false; +} + +static bool +UndefinedSizePointerError(JSContext* cx, const char* action, HandleObject obj) +{ + JSAutoByteString valBytes; + RootedValue val(cx, ObjectValue(*obj)); + const char* valStr = CTypesToSourceForError(cx, val, valBytes); + if (!valStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_UNDEFINED_SIZE, action, valStr); + return false; +} + +static bool +VariadicArgumentTypeError(JSContext* cx, uint32_t index, HandleValue actual) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char indexStr[16]; + SprintfLiteral(indexStr, "%u", index + 1); + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + CTYPESMSG_VARG_TYPE_ERROR, indexStr, valStr); + return false; +} + +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, + JS_STUBGETTER, JS_STUBSETTER)){ + 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); + +// Templated helper to convert FromType to TargetType, for the default case +// where the trivial POD constructor will do. +template<class TargetType, class FromType> +struct ConvertImpl { + static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) { + return TargetType(d); + } +}; + +#ifdef _MSC_VER +// MSVC can't perform double to unsigned __int64 conversion when the +// double is greater than 2^63 - 1. Help it along a little. +template<> +struct ConvertImpl<uint64_t, double> { + static MOZ_ALWAYS_INLINE uint64_t Convert(double d) { + return d > 0x7fffffffffffffffui64 ? + uint64_t(d - 0x8000000000000000ui64) + 0x8000000000000000ui64 : + uint64_t(d); + } +}; +#endif + +// C++ doesn't guarantee that exact values are the only ones that will +// round-trip. In fact, on some platforms, including SPARC, there are pairs of +// values, a uint64_t and a double, such that neither value is exactly +// representable in the other type, but they cast to each other. +#if defined(SPARC) || defined(__powerpc__) +// Simulate x86 overflow behavior +template<> +struct ConvertImpl<uint64_t, double> { + static MOZ_ALWAYS_INLINE uint64_t Convert(double d) { + return d >= 0xffffffffffffffff ? + 0x8000000000000000 : uint64_t(d); + } +}; + +template<> +struct ConvertImpl<int64_t, double> { + static MOZ_ALWAYS_INLINE int64_t Convert(double d) { + return d >= 0x7fffffffffffffff ? + 0x8000000000000000 : int64_t(d); + } +}; +#endif + +template<class TargetType, class FromType> +static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) +{ + 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) +{ + // Require that TargetType is integral, to simplify conversion. + JS_STATIC_ASSERT(numeric_limits<TargetType>::is_exact); + + *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()) { + JSObject* obj = &val.toObject(); + if (CData::IsCData(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()) { + JSObject* obj = &val.toObject(); + if (CData::IsCData(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++; + if (c >= '0' && c <= '9') + c -= '0'; + else if (base == 16 && c >= 'a' && c <= 'f') + c = c - 'a' + 10; + else if (base == 16 && c >= 'A' && c <= 'F') + c = c - 'A' + 10; + else + return false; + + IntegerType ii = i; + i = ii * base + sign * c; + 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 -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast. + double d = val.toDouble(); + *result = mozilla::IsFinite(d) ? IntegerType(d) : 0; + 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, class AP> +void +IntegerToString(IntegerType i, int radix, mozilla::Vector<CharType, N, AP>& 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 (const CharType* s = begin; s != begin + max; ++s) + if (*s == 0) + return s - begin; + + 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::IsCData(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; \ + 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(); + JSLinearString* sourceLinear = sourceString->ensureLinear(cx); + if (!sourceLinear) + return false; + + switch (CType::GetTypeCode(baseType)) { + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Convert from UTF-16 to UTF-8. + size_t nbytes = GetDeflatedUTF8StringLength(cx, sourceLinear); + if (nbytes == (size_t) -1) + return false; + + char** charBuffer = static_cast<char**>(buffer); + *charBuffer = cx->pod_malloc<char>(nbytes + 1); + if (!*charBuffer) { + JS_ReportAllocationOverflow(cx); + return false; + } + + ASSERT_OK(DeflateStringToUTF8Buffer(cx, sourceLinear, *charBuffer, &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) { + JS_ReportAllocationOverflow(cx); + return false; + } + + *freePointer = true; + if (sourceLinear->hasLatin1Chars()) { + AutoCheckCannotGC nogc; + CopyAndInflateChars(*char16Buffer, sourceLinear->latin1Chars(nogc), sourceLength); + } else { + AutoCheckCannotGC nogc; + mozilla::PodCopy(*char16Buffer, sourceLinear->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(); + JSLinearString* sourceLinear = sourceString->ensureLinear(cx); + if (!sourceLinear) + return false; + + switch (CType::GetTypeCode(baseType)) { + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Convert from UTF-16 or Latin1 to UTF-8. + size_t nbytes = + GetDeflatedUTF8StringLength(cx, sourceLinear); + if (nbytes == (size_t) -1) + return false; + + if (targetLength < nbytes) { + MOZ_ASSERT(!funObj); + return ArrayLengthOverflow(cx, targetLength, targetType, nbytes, val, + convType); + } + + char* charBuffer = static_cast<char*>(buffer); + ASSERT_OK(DeflateStringToUTF8Buffer(cx, sourceLinear, charBuffer, + &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 (sourceLinear->hasLatin1Chars()) { + AutoCheckCannotGC nogc; + CopyAndInflateChars(dest, sourceLinear->latin1Chars(nogc), sourceLength); + } else { + AutoCheckCannotGC nogc; + mozilla::PodCopy(dest, sourceLinear->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) { + JS_ReportAllocationOverflow(cx); + 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) { + JS_ReportAllocationOverflow(cx); + 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(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(result, "("); + AppendString(result, ")"); + } + + // Array types go on the right. + AppendString(result, "["); + size_t length; + if (ArrayType::GetSafeLength(typeObj, &length)) + IntegerToString(length, 10, result); + + AppendString(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(result, "__stdcall"); + else if (abi == ABI_THISCALL) + PrependString(result, "__thiscall"); + else if (abi == ABI_WINAPI) + PrependString(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(result, "("); + AppendString(result, ")"); + } + + // Argument list goes on the right. + AppendString(result, "("); + for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { + RootedObject argType(cx, fninfo->mArgTypes[i]); + JSString* argName = CType::GetName(cx, argType); + AppendString(result, argName); + if (i != fninfo->mArgTypes.length() - 1 || + fninfo->mIsVariadic) + AppendString(result, ", "); + } + if (fninfo->mIsVariadic) + AppendString(result, "..."); + AppendString(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 (('a' <= result[0] && result[0] <= 'z') || + ('A' <= result[0] && result[0] <= 'Z') || + (result[0] == '_')) + PrependString(result, " "); + + // Stick the base type and derived type parts together. + JSString* baseName = CType::GetName(cx, typeObj); + PrependString(result, baseName); + return NewUCString(cx, result); +} + +// 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 BuildTypeSource, 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(result, "ctypes."); + JSString* nameStr = CType::GetName(cx, typeObj); + AppendString(result, nameStr); + break; + } + case TYPE_pointer: { + RootedObject baseType(cx, PointerType::GetBaseType(typeObj)); + + // Specialcase ctypes.voidptr_t. + if (CType::GetTypeCode(baseType) == TYPE_void_t) { + AppendString(result, "ctypes.voidptr_t"); + break; + } + + // Recursively build the source string, and append '.ptr'. + BuildTypeSource(cx, baseType, makeShort, result); + AppendString(result, ".ptr"); + break; + } + case TYPE_function: { + FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); + + AppendString(result, "ctypes.FunctionType("); + + switch (GetABICode(fninfo->mABI)) { + case ABI_DEFAULT: + AppendString(result, "ctypes.default_abi, "); + break; + case ABI_STDCALL: + AppendString(result, "ctypes.stdcall_abi, "); + break; + case ABI_THISCALL: + AppendString(result, "ctypes.thiscall_abi, "); + break; + case ABI_WINAPI: + AppendString(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(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(result, ", "); + } + if (fninfo->mIsVariadic) + AppendString(result, "\"...\""); + AppendString(result, "]"); + } + + AppendString(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(result, ".array("); + + size_t length; + if (ArrayType::GetSafeLength(typeObj, &length)) + IntegerToString(length, 10, result); + + AppendString(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(result, name); + break; + } + + // Write the full struct declaration. + AppendString(result, "ctypes.StructType(\""); + AppendString(result, name); + AppendString(result, "\""); + + // If it's an opaque struct, we're done. + if (!CType::IsSizeDefined(typeObj)) { + AppendString(result, ")"); + break; + } + + AppendString(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(result, "{ \""); + AppendString(result, entry->key()); + AppendString(result, "\": "); + BuildTypeSource(cx, entry->value().mType, true, result); + AppendString(result, " }"); + if (i != length - 1) + AppendString(result, ", "); + } + + AppendString(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 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(result, "true"); + else + AppendString(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(result, "ctypes.UInt64(\""); \ + else \ + AppendString(result, "ctypes.Int64(\""); \ + \ + IntegerToString(*static_cast<type*>(data), 10, result); \ + AppendString(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(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(result, "("); + } + + // Serialize the pointer value as a wrapped hexadecimal integer. + uintptr_t ptr = *static_cast<uintptr_t*>(data); + AppendString(result, "ctypes.UInt64(\"0x"); + IntegerToString(ptr, 16, result); + AppendString(result, "\")"); + + if (isImplicit) + AppendString(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(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(result, ", "); + } + AppendString(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(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(result, "\""); + AppendString(result, entry->key()); + AppendString(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(result, ", "); + } + + if (isImplicit) + AppendString(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 (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) + JS::TraceEdge(trc, &fninfo->mArgTypes[i], "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, JS_THIS_OBJECT(cx, vp)); + 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, JS_THIS_OBJECT(cx, vp)); + 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(type, "type "); + AppendString(type, GetName(cx, obj)); + result = NewUCString(cx, type); + } + 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 = JS_THIS_OBJECT(cx, vp); + 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); + result = NewUCString(cx, source); + } 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 = JS_THIS_OBJECT(cx, vp); + 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 = &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); + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) + return false; + if (!CData::IsCData(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) +{ + RootedObject obj(cx, JS_THIS_OBJECT(cx, args.base())); + if (!obj) + return false; + if (!CData::IsCData(obj)) { + if (offset == 1) { + return IncompatibleThisProto(cx, "PointerType.prototype.increment", + args.thisv()); + } + return IncompatibleThisProto(cx, "PointerType.prototype.decrement", + args.thisv()); + } + + RootedObject typeObj(cx, CData::GetCType(obj)); + if (CType::GetTypeCode(typeObj) != TYPE_pointer) { + if (offset == 1) { + return IncompatibleThisType(cx, "PointerType.prototype.increment", + "non-PointerType CData", args.thisv()); + } + return IncompatibleThisType(cx, "PointerType.prototype.decrement", + "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); +} + +bool +PointerType::Decrement(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return OffsetBy(cx, args, -1); +} + +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(); + JSLinearString* sourceLinear = sourceString->ensureLinear(cx); + if (!sourceLinear) + return false; + + switch (CType::GetTypeCode(baseType)) { + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Determine the UTF-8 length. + length = GetDeflatedUTF8StringLength(cx, sourceLinear); + if (length == (size_t) -1) + return false; + + ++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) { + JS_ReportOutOfMemory(cx); + 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) { + JS_ReportAllocationOverflow(cx); + return nullptr; + } + + for (size_t i = 0; i < length; ++i) + ffiType->elements[i] = ffiBaseType; + ffiType->elements[length] = nullptr; + + return Move(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 = &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) +{ + JSObject* obj = &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::IsCData(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) +{ + // 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. + // Chances are it's a regular property lookup, so return. + return true; + } + if (!ok) { + return InvalidIndexError(cx, idval); + } + if (index >= length) { + return InvalidIndexRangeError(cx, index, length); + } + + RootedObject baseType(cx, GetBaseType(typeObj)); + size_t elementSize = CType::GetSize(baseType); + char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index; + return ConvertToJS(cx, baseType, obj, data, false, false, vp); +} + +bool +ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp, + ObjectOpResult& result) +{ + // 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 setter", objVal); + } + + // Bail early if we're not an ArrayType. (This setter is present for all + // CData, regardless of CType.) + RootedObject typeObj(cx, CData::GetCType(obj)); + if (CType::GetTypeCode(typeObj) != TYPE_array) + return result.succeed(); + + // 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. + // Chances are it's a regular property lookup, so return. + return result.succeed(); + } + if (!ok) { + return InvalidIndexError(cx, idval); + } + if (index >= length) { + return InvalidIndexRangeError(cx, index, length); + } + + RootedObject baseType(cx, GetBaseType(typeObj)); + size_t elementSize = CType::GetSize(baseType); + char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index; + if (!ImplicitConvert(cx, vp, baseType, data, ConversionType::Setter, + nullptr, nullptr, 0, typeObj, index)) + return false; + return result.succeed(); +} + +bool +ArrayType::AddressOfElement(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "ArrayType.prototype.addressOfElement", + args.thisv()); + } + + RootedObject typeObj(cx, CData::GetCType(obj)); + if (CType::GetTypeCode(typeObj) != TYPE_array) { + return IncompatibleThisType(cx, "ArrayType.prototype.addressOfElement", + "non-ArrayType CData", args.thisv()); + } + + if (args.length() != 1) { + return ArgumentLengthError(cx, "ArrayType.prototype.addressOfElement", + "one", ""); + } + + RootedObject baseType(cx, GetBaseType(typeObj)); + RootedObject pointerType(cx, PointerType::CreateInternal(cx, baseType)); + if (!pointerType) + return false; + + // Create a PointerType CData object containing null. + RootedObject result(cx, CData::Create(cx, pointerType, nullptr, nullptr, true)); + if (!result) + return false; + + args.rval().setObject(*result); + + // Convert the index to a size_t and bounds-check it. + size_t index; + size_t length = GetLength(typeObj); + if (!jsvalToSize(cx, args[0], false, &index)) { + return InvalidIndexError(cx, args[0]); + } + if (index >= length) { + return InvalidIndexRangeError(cx, index, length); + } + + // Manually set the pointer inside the object, so we skip the conversion step. + void** data = static_cast<void**>(CData::GetData(result)); + size_t elementSize = CType::GetSize(baseType); + *data = static_cast<char*>(CData::GetData(obj)) + elementSize * index; + return true; +} + +/******************************************************************************* +** StructType implementation +*******************************************************************************/ + +// For a struct field descriptor 'val' of the form { name : type }, extract +// 'name' and 'type'. +static JSFlatString* +ExtractStructField(JSContext* cx, HandleValue val, MutableHandleObject typeObj) +{ + if (val.isPrimitive()) { + FieldDescriptorNameTypeError(cx, val); + return nullptr; + } + + RootedObject obj(cx, &val.toObject()); + Rooted<IdVector> props(cx, IdVector(cx)); + if (!JS_Enumerate(cx, obj, &props)) + return nullptr; + + // make sure we have one, and only one, property + if (props.length() != 1) { + FieldDescriptorCountError(cx, val, props.length()); + return nullptr; + } + + RootedId nameid(cx, props[0]); + if (!JSID_IS_STRING(nameid)) { + FieldDescriptorNameError(cx, nameid); + return nullptr; + } + + RootedValue propVal(cx); + if (!JS_GetPropertyById(cx, obj, nameid, &propVal)) + return nullptr; + + if (propVal.isPrimitive() || !CType::IsCType(&propVal.toObject())) { + FieldDescriptorTypeError(cx, propVal, nameid); + return nullptr; + } + + // Undefined size or zero size struct members are illegal. + // (Zero-size arrays are legal as struct members in C++, but libffi will + // choke on a zero-size struct, so we disallow them.) + typeObj.set(&propVal.toObject()); + size_t size; + if (!CType::GetSafeSize(typeObj, &size) || size == 0) { + FieldDescriptorSizeError(cx, typeObj, nameid); + return nullptr; + } + + return JSID_TO_FLAT_STRING(nameid); +} + +// For a struct field with 'name' and 'type', add an element of the form +// { name : type }. +static bool +AddFieldToArray(JSContext* cx, + MutableHandleValue element, + JSFlatString* name_, + JSObject* typeObj_) +{ + RootedObject typeObj(cx, typeObj_); + Rooted<JSFlatString*> name(cx, name_); + RootedObject fieldObj(cx, JS_NewPlainObject(cx)); + if (!fieldObj) + return false; + + element.setObject(*fieldObj); + + AutoStableStringChars nameChars(cx); + if (!nameChars.initTwoByte(cx, name)) + return false; + + if (!JS_DefineUCProperty(cx, fieldObj, + nameChars.twoByteChars(), name->length(), + typeObj, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return false; + + return JS_FreezeObject(cx, fieldObj); +} + +bool +StructType::Create(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Construct and return a new StructType object. + if (args.length() < 1 || args.length() > 2) { + return ArgumentLengthError(cx, "StructType", "one or two", "s"); + } + + Value name = args[0]; + if (!name.isString()) { + return ArgumentTypeMismatch(cx, "first ", "StructType", "a string"); + } + + // Get ctypes.StructType.prototype from the ctypes.StructType constructor. + RootedObject typeProto(cx, CType::GetProtoFromCtor(&args.callee(), SLOT_STRUCTPROTO)); + + // Create a simple StructType with no defined fields. The result will be + // non-instantiable as CData, will have no 'prototype' property, and will + // have undefined size and alignment and no ffi_type. + RootedObject result(cx, CType::Create(cx, typeProto, nullptr, TYPE_struct, + name.toString(), + JS::UndefinedHandleValue, + JS::UndefinedHandleValue, nullptr)); + if (!result) + return false; + + if (args.length() == 2) { + RootedObject arr(cx, args[1].isObject() ? &args[1].toObject() : nullptr); + bool isArray; + if (!arr) { + isArray = false; + } else { + if (!JS_IsArrayObject(cx, arr, &isArray)) + return false; + } + if (!isArray) + return ArgumentTypeMismatch(cx, "second ", "StructType", "an array"); + + // Define the struct fields. + if (!DefineInternal(cx, result, arr)) + return false; + } + + args.rval().setObject(*result); + return true; +} + +bool +StructType::DefineInternal(JSContext* cx, JSObject* typeObj_, JSObject* fieldsObj_) +{ + RootedObject typeObj(cx, typeObj_); + RootedObject fieldsObj(cx, fieldsObj_); + + uint32_t len; + ASSERT_OK(JS_GetArrayLength(cx, fieldsObj, &len)); + + // Get the common prototype for CData objects of this type from + // ctypes.CType.prototype. + RootedObject dataProto(cx, CType::GetProtoFromType(cx, typeObj, SLOT_STRUCTDATAPROTO)); + if (!dataProto) + return false; + + // Set up the 'prototype' and 'prototype.constructor' properties. + // The prototype will reflect the struct fields as properties on CData objects + // created from this type. + RootedObject prototype(cx, JS_NewObjectWithGivenProto(cx, &sCDataProtoClass, dataProto)); + if (!prototype) + return false; + + if (!JS_DefineProperty(cx, prototype, "constructor", typeObj, + JSPROP_READONLY | JSPROP_PERMANENT)) + return false; + + // Create a FieldInfoHash to stash on the type object. + Rooted<FieldInfoHash> fields(cx); + if (!fields.init(len)) { + JS_ReportOutOfMemory(cx); + return false; + } + + // Process the field types. + size_t structSize, structAlign; + if (len != 0) { + structSize = 0; + structAlign = 0; + + for (uint32_t i = 0; i < len; ++i) { + RootedValue item(cx); + if (!JS_GetElement(cx, fieldsObj, i, &item)) + return false; + + RootedObject fieldType(cx, nullptr); + Rooted<JSFlatString*> name(cx, ExtractStructField(cx, item, &fieldType)); + if (!name) + return false; + + // Make sure each field name is unique + FieldInfoHash::AddPtr entryPtr = fields.lookupForAdd(name); + if (entryPtr) { + return DuplicateFieldError(cx, name); + } + + // Add the field to the StructType's 'prototype' property. + AutoStableStringChars nameChars(cx); + if (!nameChars.initTwoByte(cx, name)) + return false; + + RootedFunction getter(cx, NewFunctionWithReserved(cx, StructType::FieldGetter, 0, 0, nullptr)); + if (!getter) + return false; + SetFunctionNativeReserved(getter, StructType::SLOT_FIELDNAME, + StringValue(JS_FORGET_STRING_FLATNESS(name))); + RootedObject getterObj(cx, JS_GetFunctionObject(getter)); + + RootedFunction setter(cx, NewFunctionWithReserved(cx, StructType::FieldSetter, 1, 0, nullptr)); + if (!setter) + return false; + SetFunctionNativeReserved(setter, StructType::SLOT_FIELDNAME, + StringValue(JS_FORGET_STRING_FLATNESS(name))); + RootedObject setterObj(cx, JS_GetFunctionObject(setter)); + + if (!JS_DefineUCProperty(cx, prototype, + nameChars.twoByteChars(), name->length(), UndefinedHandleValue, + JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_GETTER | JSPROP_SETTER, + JS_DATA_TO_FUNC_PTR(JSNative, getterObj.get()), + JS_DATA_TO_FUNC_PTR(JSNative, setterObj.get()))) + { + return false; + } + + size_t fieldSize = CType::GetSize(fieldType); + size_t fieldAlign = CType::GetAlignment(fieldType); + size_t fieldOffset = Align(structSize, fieldAlign); + // Check for overflow. Since we hold invariant that fieldSize % fieldAlign + // be zero, we can safely check fieldOffset + fieldSize without first + // checking fieldOffset for overflow. + if (fieldOffset + fieldSize < structSize) { + SizeOverflow(cx, "struct size", "size_t"); + return false; + } + + // Add field name to the hash + FieldInfo info; + info.mType = fieldType; + info.mIndex = i; + info.mOffset = fieldOffset; + if (!fields.add(entryPtr, name, info)) { + JS_ReportOutOfMemory(cx); + return false; + } + + structSize = fieldOffset + fieldSize; + + if (fieldAlign > structAlign) + structAlign = fieldAlign; + } + + // Pad the struct tail according to struct alignment. + size_t structTail = Align(structSize, structAlign); + if (structTail < structSize) { + SizeOverflow(cx, "struct size", "size_t"); + return false; + } + structSize = structTail; + + } else { + // Empty structs are illegal in C, but are legal and have a size of + // 1 byte in C++. We're going to allow them, and trick libffi into + // believing this by adding a char member. The resulting struct will have + // no getters or setters, and will be initialized to zero. + structSize = 1; + structAlign = 1; + } + + RootedValue sizeVal(cx); + if (!SizeTojsval(cx, structSize, &sizeVal)) { + SizeOverflow(cx, "struct size", "double"); + return false; + } + + // Move the field hash to the heap and store it in the typeObj. + FieldInfoHash *heapHash = cx->new_<FieldInfoHash>(mozilla::Move(fields.get())); + if (!heapHash) { + JS_ReportOutOfMemory(cx); + return false; + } + MOZ_ASSERT(heapHash->initialized()); + JS_SetReservedSlot(typeObj, SLOT_FIELDINFO, PrivateValue(heapHash)); + + JS_SetReservedSlot(typeObj, SLOT_SIZE, sizeVal); + JS_SetReservedSlot(typeObj, SLOT_ALIGN, Int32Value(structAlign)); + //if (!JS_FreezeObject(cx, prototype)0 // XXX fixme - see bug 541212! + // return false; + JS_SetReservedSlot(typeObj, SLOT_PROTO, ObjectValue(*prototype)); + return true; +} + +UniquePtrFFIType +StructType::BuildFFIType(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(CType::IsCType(obj)); + MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); + MOZ_ASSERT(CType::IsSizeDefined(obj)); + + const FieldInfoHash* fields = GetFieldInfo(obj); + size_t len = fields->count(); + + size_t structSize = CType::GetSize(obj); + size_t structAlign = CType::GetAlignment(obj); + + auto ffiType = cx->make_unique<ffi_type>(); + if (!ffiType) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + ffiType->type = FFI_TYPE_STRUCT; + + size_t count = len != 0 ? len + 1 : 2; + auto elements = cx->make_pod_array<ffi_type*>(count); + if (!elements) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + if (len != 0) { + elements[len] = nullptr; + + for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { + const FieldInfoHash::Entry& entry = r.front(); + ffi_type* fieldType = CType::GetFFIType(cx, entry.value().mType); + if (!fieldType) + return nullptr; + elements[entry.value().mIndex] = fieldType; + } + } else { + // Represent an empty struct as having a size of 1 byte, just like C++. + MOZ_ASSERT(structSize == 1); + MOZ_ASSERT(structAlign == 1); + elements[0] = &ffi_type_uint8; + elements[1] = nullptr; + } + + ffiType->elements = elements.release(); + +#ifdef DEBUG + // Perform a sanity check: the result of our struct size and alignment + // calculations should match libffi's. We force it to do this calculation + // by calling ffi_prep_cif. + ffi_cif cif; + ffiType->size = 0; + ffiType->alignment = 0; + ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 0, ffiType.get(), nullptr); + MOZ_ASSERT(status == FFI_OK); + MOZ_ASSERT(structSize == ffiType->size); + MOZ_ASSERT(structAlign == ffiType->alignment); +#else + // Fill in the ffi_type's size and align fields. This makes libffi treat the + // type as initialized; it will not recompute the values. (We assume + // everything agrees; if it doesn't, we really want to know about it, which + // is the purpose of the above debug-only check.) + ffiType->size = structSize; + ffiType->alignment = structAlign; +#endif + + return Move(ffiType); +} + +bool +StructType::Define(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + if (!CType::IsCType(obj)) { + return IncompatibleThisProto(cx, "StructType.prototype.define", + args.thisv()); + } + if (CType::GetTypeCode(obj) != TYPE_struct) { + return IncompatibleThisType(cx, "StructType.prototype.define", + "non-StructType", args.thisv()); + } + + if (CType::IsSizeDefined(obj)) { + JS_ReportErrorASCII(cx, "StructType has already been defined"); + return false; + } + + if (args.length() != 1) { + return ArgumentLengthError(cx, "StructType.prototype.define", "one", ""); + } + + HandleValue arg = args[0]; + if (arg.isPrimitive()) { + return ArgumentTypeMismatch(cx, "", "StructType.prototype.define", + "an array"); + } + + bool isArray; + if (!arg.isObject()) { + isArray = false; + } else { + if (!JS_IsArrayObject(cx, arg, &isArray)) + return false; + } + + if (!isArray) { + return ArgumentTypeMismatch(cx, "", "StructType.prototype.define", + "an array"); + } + + RootedObject arr(cx, &arg.toObject()); + return DefineInternal(cx, obj, arr); +} + +bool +StructType::ConstructData(JSContext* cx, + HandleObject obj, + const CallArgs& args) +{ + if (!CType::IsCType(obj) || CType::GetTypeCode(obj) != TYPE_struct) { + return IncompatibleCallee(cx, "StructType constructor", obj); + } + + if (!CType::IsSizeDefined(obj)) { + JS_ReportErrorASCII(cx, "cannot construct an opaque StructType"); + return false; + } + + JSObject* result = CData::Create(cx, obj, nullptr, nullptr, true); + if (!result) + return false; + + args.rval().setObject(*result); + + if (args.length() == 0) + return true; + + char* buffer = static_cast<char*>(CData::GetData(result)); + const FieldInfoHash* fields = GetFieldInfo(obj); + + if (args.length() == 1) { + // There are two possible interpretations of the argument: + // 1) It may be an object '{ ... }' with properties representing the + // struct fields intended to ExplicitConvert wholesale to our StructType. + // 2) If the struct contains one field, the arg may be intended to + // ImplicitConvert directly to that arg's CType. + // Thankfully, the conditions for these two possibilities to succeed + // are mutually exclusive, so we can pick the right one. + + // Try option 1) first. + if (ExplicitConvert(cx, args[0], obj, buffer, ConversionType::Construct)) + return true; + + if (fields->count() != 1) + return false; + + // If ExplicitConvert failed, and there is no pending exception, then assume + // hard failure (out of memory, or some other similarly serious condition). + if (!JS_IsExceptionPending(cx)) + return false; + + // Otherwise, assume soft failure, and clear the pending exception so that we + // can throw a different one as required. + JS_ClearPendingException(cx); + + // Fall through to try option 2). + } + + // We have a type constructor of the form 'ctypes.StructType(a, b, c, ...)'. + // ImplicitConvert each field. + if (args.length() == fields->count()) { + for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { + const FieldInfo& field = r.front().value(); + MOZ_ASSERT(field.mIndex < fields->count()); /* Quantified invariant */ + if (!ImplicitConvert(cx, args[field.mIndex], field.mType, + buffer + field.mOffset, ConversionType::Construct, + nullptr, nullptr, 0, obj, field.mIndex)) + return false; + } + + return true; + } + + size_t count = fields->count(); + if (count >= 2) { + char fieldLengthStr[32]; + SprintfLiteral(fieldLengthStr, "0, 1, or %" PRIuSIZE, count); + return ArgumentLengthError(cx, "StructType constructor", fieldLengthStr, + "s"); + } + return ArgumentLengthError(cx, "StructType constructor", "at most one", ""); +} + +const FieldInfoHash* +StructType::GetFieldInfo(JSObject* obj) +{ + MOZ_ASSERT(CType::IsCType(obj)); + MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); + + Value slot = JS_GetReservedSlot(obj, SLOT_FIELDINFO); + MOZ_ASSERT(!slot.isUndefined() && slot.toPrivate()); + + return static_cast<const FieldInfoHash*>(slot.toPrivate()); +} + +const FieldInfo* +StructType::LookupField(JSContext* cx, JSObject* obj, JSFlatString* name) +{ + MOZ_ASSERT(CType::IsCType(obj)); + MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); + + FieldInfoHash::Ptr ptr = GetFieldInfo(obj)->lookup(name); + if (ptr) + return &ptr->value(); + + FieldMissingError(cx, obj, name); + return nullptr; +} + +JSObject* +StructType::BuildFieldsArray(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(CType::IsCType(obj)); + MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); + MOZ_ASSERT(CType::IsSizeDefined(obj)); + + const FieldInfoHash* fields = GetFieldInfo(obj); + size_t len = fields->count(); + + // Prepare a new array for the 'fields' property of the StructType. + JS::AutoValueVector fieldsVec(cx); + if (!fieldsVec.resize(len)) + return nullptr; + + for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { + const FieldInfoHash::Entry& entry = r.front(); + // Add the field descriptor to the array. + if (!AddFieldToArray(cx, fieldsVec[entry.value().mIndex], + entry.key(), entry.value().mType)) + return nullptr; + } + + RootedObject fieldsProp(cx, JS_NewArrayObject(cx, fieldsVec)); + if (!fieldsProp) + return nullptr; + + // Seal the fields array. + if (!JS_FreezeObject(cx, fieldsProp)) + return nullptr; + + return fieldsProp; +} + +/* static */ bool +StructType::IsStruct(HandleValue v) +{ + if (!v.isObject()) + return false; + JSObject* obj = &v.toObject(); + return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_struct; +} + +bool +StructType::FieldsArrayGetter(JSContext* cx, const JS::CallArgs& args) +{ + RootedObject obj(cx, &args.thisv().toObject()); + + args.rval().set(JS_GetReservedSlot(obj, SLOT_FIELDS)); + + if (!CType::IsSizeDefined(obj)) { + MOZ_ASSERT(args.rval().isUndefined()); + return true; + } + + if (args.rval().isUndefined()) { + // Build the 'fields' array lazily. + JSObject* fields = BuildFieldsArray(cx, obj); + if (!fields) + return false; + JS_SetReservedSlot(obj, SLOT_FIELDS, ObjectValue(*fields)); + + args.rval().setObject(*fields); + } + + MOZ_ASSERT(args.rval().isObject()); + return true; +} + +bool +StructType::FieldGetter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject()) { + return IncompatibleThisProto(cx, "StructType property getter", args.thisv()); + } + + RootedObject obj(cx, &args.thisv().toObject()); + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "StructType property getter", args.thisv()); + } + + JSObject* typeObj = CData::GetCType(obj); + if (CType::GetTypeCode(typeObj) != TYPE_struct) { + return IncompatibleThisType(cx, "StructType property getter", + "non-StructType CData", args.thisv()); + } + + RootedValue nameVal(cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME)); + Rooted<JSFlatString*> name(cx, JS_FlattenString(cx, nameVal.toString())); + if (!name) + return false; + + const FieldInfo* field = LookupField(cx, typeObj, name); + if (!field) + return false; + + char* data = static_cast<char*>(CData::GetData(obj)) + field->mOffset; + RootedObject fieldType(cx, field->mType); + return ConvertToJS(cx, fieldType, obj, data, false, false, args.rval()); +} + +bool +StructType::FieldSetter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject()) { + return IncompatibleThisProto(cx, "StructType property setter", args.thisv()); + } + + RootedObject obj(cx, &args.thisv().toObject()); + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "StructType property setter", args.thisv()); + } + + RootedObject typeObj(cx, CData::GetCType(obj)); + if (CType::GetTypeCode(typeObj) != TYPE_struct) { + return IncompatibleThisType(cx, "StructType property setter", + "non-StructType CData", args.thisv()); + } + + RootedValue nameVal(cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME)); + Rooted<JSFlatString*> name(cx, JS_FlattenString(cx, nameVal.toString())); + if (!name) + return false; + + const FieldInfo* field = LookupField(cx, typeObj, name); + if (!field) + return false; + + args.rval().setUndefined(); + + char* data = static_cast<char*>(CData::GetData(obj)) + field->mOffset; + return ImplicitConvert(cx, args.get(0), field->mType, data, ConversionType::Setter, nullptr, + nullptr, 0, typeObj, field->mIndex); +} + +bool +StructType::AddressOfField(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "StructType.prototype.addressOfField", + args.thisv()); + } + + JSObject* typeObj = CData::GetCType(obj); + if (CType::GetTypeCode(typeObj) != TYPE_struct) { + return IncompatibleThisType(cx, "StructType.prototype.addressOfField", + "non-StructType CData", args.thisv()); + } + + if (args.length() != 1) { + return ArgumentLengthError(cx, "StructType.prototype.addressOfField", + "one", ""); + } + + if (!args[0].isString()) { + return ArgumentTypeMismatch(cx, "", "StructType.prototype.addressOfField", + "a string"); + } + + JSFlatString* str = JS_FlattenString(cx, args[0].toString()); + if (!str) + return false; + + const FieldInfo* field = LookupField(cx, typeObj, str); + if (!field) + return false; + + RootedObject baseType(cx, field->mType); + RootedObject pointerType(cx, PointerType::CreateInternal(cx, baseType)); + if (!pointerType) + return false; + + // Create a PointerType CData object containing null. + JSObject* result = CData::Create(cx, pointerType, nullptr, nullptr, true); + if (!result) + return false; + + args.rval().setObject(*result); + + // Manually set the pointer inside the object, so we skip the conversion step. + void** data = static_cast<void**>(CData::GetData(result)); + *data = static_cast<char*>(CData::GetData(obj)) + field->mOffset; + return true; +} + +/******************************************************************************* +** FunctionType implementation +*******************************************************************************/ + +// Helper class for handling allocation of function arguments. +struct AutoValue +{ + AutoValue() : mData(nullptr) { } + + ~AutoValue() + { + js_free(mData); + } + + bool SizeToType(JSContext* cx, JSObject* type) + { + // Allocate a minimum of sizeof(ffi_arg) to handle small integers. + size_t size = Align(CType::GetSize(type), sizeof(ffi_arg)); + mData = js_malloc(size); + if (mData) + memset(mData, 0, size); + return mData != nullptr; + } + + void* mData; +}; + +static bool +GetABI(JSContext* cx, HandleValue abiType, ffi_abi* result) +{ + if (abiType.isPrimitive()) + return false; + + ABICode abi = GetABICode(abiType.toObjectOrNull()); + + // determine the ABI from the subset of those available on the + // given platform. ABI_DEFAULT specifies the default + // C calling convention (cdecl) on each platform. + switch (abi) { + case ABI_DEFAULT: + *result = FFI_DEFAULT_ABI; + return true; + case ABI_THISCALL: +#if defined(_WIN64) + *result = FFI_WIN64; + return true; +#elif defined(_WIN32) + *result = FFI_THISCALL; + return true; +#else + break; +#endif + case ABI_STDCALL: + case ABI_WINAPI: +#if (defined(_WIN32) && !defined(_WIN64)) || defined(_OS2) + *result = FFI_STDCALL; + return true; +#elif (defined(_WIN64)) + // We'd like the same code to work across Win32 and Win64, so stdcall_api + // and winapi_abi become aliases to the lone Win64 ABI. + *result = FFI_WIN64; + return true; +#endif + case INVALID_ABI: + break; + } + return false; +} + +static JSObject* +PrepareType(JSContext* cx, uint32_t index, HandleValue type) +{ + if (type.isPrimitive() || !CType::IsCType(type.toObjectOrNull())) { + FunctionArgumentTypeError(cx, index, type, "is not a ctypes type"); + return nullptr; + } + + JSObject* result = type.toObjectOrNull(); + TypeCode typeCode = CType::GetTypeCode(result); + + if (typeCode == TYPE_array) { + // convert array argument types to pointers, just like C. + // ImplicitConvert will do the same, when passing an array as data. + RootedObject baseType(cx, ArrayType::GetBaseType(result)); + result = PointerType::CreateInternal(cx, baseType); + if (!result) + return nullptr; + + } else if (typeCode == TYPE_void_t || typeCode == TYPE_function) { + // disallow void or function argument types + FunctionArgumentTypeError(cx, index, type, "cannot be void or function"); + return nullptr; + } + + if (!CType::IsSizeDefined(result)) { + FunctionArgumentTypeError(cx, index, type, "must have defined size"); + return nullptr; + } + + // libffi cannot pass types of zero size by value. + MOZ_ASSERT(CType::GetSize(result) != 0); + + return result; +} + +static JSObject* +PrepareReturnType(JSContext* cx, HandleValue type) +{ + if (type.isPrimitive() || !CType::IsCType(type.toObjectOrNull())) { + FunctionReturnTypeError(cx, type, "is not a ctypes type"); + return nullptr; + } + + JSObject* result = type.toObjectOrNull(); + TypeCode typeCode = CType::GetTypeCode(result); + + // Arrays and functions can never be return types. + if (typeCode == TYPE_array || typeCode == TYPE_function) { + FunctionReturnTypeError(cx, type, "cannot be an array or function"); + return nullptr; + } + + if (typeCode != TYPE_void_t && !CType::IsSizeDefined(result)) { + FunctionReturnTypeError(cx, type, "must have defined size"); + return nullptr; + } + + // libffi cannot pass types of zero size by value. + MOZ_ASSERT(typeCode == TYPE_void_t || CType::GetSize(result) != 0); + + return result; +} + +static MOZ_ALWAYS_INLINE bool +IsEllipsis(JSContext* cx, HandleValue v, bool* isEllipsis) +{ + *isEllipsis = false; + if (!v.isString()) + return true; + JSString* str = v.toString(); + if (str->length() != 3) + return true; + JSLinearString* linear = str->ensureLinear(cx); + if (!linear) + return false; + char16_t dot = '.'; + *isEllipsis = (linear->latin1OrTwoByteChar(0) == dot && + linear->latin1OrTwoByteChar(1) == dot && + linear->latin1OrTwoByteChar(2) == dot); + return true; +} + +static bool +PrepareCIF(JSContext* cx, + FunctionInfo* fninfo) +{ + ffi_abi abi; + RootedValue abiType(cx, ObjectOrNullValue(fninfo->mABI)); + if (!GetABI(cx, abiType, &abi)) { + JS_ReportErrorASCII(cx, "Invalid ABI specification"); + return false; + } + + ffi_type* rtype = CType::GetFFIType(cx, fninfo->mReturnType); + if (!rtype) + return false; + + ffi_status status = + ffi_prep_cif(&fninfo->mCIF, + abi, + fninfo->mFFITypes.length(), + rtype, + fninfo->mFFITypes.begin()); + + switch (status) { + case FFI_OK: + return true; + case FFI_BAD_ABI: + JS_ReportErrorASCII(cx, "Invalid ABI specification"); + return false; + case FFI_BAD_TYPEDEF: + JS_ReportErrorASCII(cx, "Invalid type specification"); + return false; + default: + JS_ReportErrorASCII(cx, "Unknown libffi error"); + return false; + } +} + +void +FunctionType::BuildSymbolName(JSString* name, + JSObject* typeObj, + AutoCString& result) +{ + FunctionInfo* fninfo = GetFunctionInfo(typeObj); + + switch (GetABICode(fninfo->mABI)) { + case ABI_DEFAULT: + case ABI_THISCALL: + case ABI_WINAPI: + // For cdecl or WINAPI functions, no mangling is necessary. + AppendString(result, name); + break; + + case ABI_STDCALL: { +#if (defined(_WIN32) && !defined(_WIN64)) || defined(_OS2) + // On WIN32, stdcall functions look like: + // _foo@40 + // where 'foo' is the function name, and '40' is the aligned size of the + // arguments. + AppendString(result, "_"); + AppendString(result, name); + AppendString(result, "@"); + + // Compute the suffix by aligning each argument to sizeof(ffi_arg). + size_t size = 0; + for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { + JSObject* argType = fninfo->mArgTypes[i]; + size += Align(CType::GetSize(argType), sizeof(ffi_arg)); + } + + IntegerToString(size, 10, result); +#elif defined(_WIN64) + // On Win64, stdcall is an alias to the default ABI for compatibility, so no + // mangling is done. + AppendString(result, name); +#endif + break; + } + + case INVALID_ABI: + MOZ_CRASH("invalid abi"); + } +} + +static bool +CreateFunctionInfo(JSContext* cx, + HandleObject typeObj, + HandleValue abiType, + HandleObject returnType, + const HandleValueArray& args) +{ + FunctionInfo* fninfo(cx->new_<FunctionInfo>()); + if (!fninfo) { + JS_ReportOutOfMemory(cx); + return false; + } + + // Stash the FunctionInfo in a reserved slot. + JS_SetReservedSlot(typeObj, SLOT_FNINFO, PrivateValue(fninfo)); + + ffi_abi abi; + if (!GetABI(cx, abiType, &abi)) { + JS_ReportErrorASCII(cx, "Invalid ABI specification"); + return false; + } + fninfo->mABI = abiType.toObjectOrNull(); + + fninfo->mReturnType = returnType; + + // prepare the argument types + if (!fninfo->mArgTypes.reserve(args.length()) || + !fninfo->mFFITypes.reserve(args.length())) { + JS_ReportOutOfMemory(cx); + return false; + } + + fninfo->mIsVariadic = false; + + for (uint32_t i = 0; i < args.length(); ++i) { + bool isEllipsis; + if (!IsEllipsis(cx, args[i], &isEllipsis)) + return false; + if (isEllipsis) { + fninfo->mIsVariadic = true; + if (i < 1) { + JS_ReportErrorASCII(cx, "\"...\" may not be the first and only parameter " + "type of a variadic function declaration"); + return false; + } + if (i < args.length() - 1) { + JS_ReportErrorASCII(cx, "\"...\" must be the last parameter type of a " + "variadic function declaration"); + return false; + } + if (GetABICode(fninfo->mABI) != ABI_DEFAULT) { + JS_ReportErrorASCII(cx, "Variadic functions must use the __cdecl calling " + "convention"); + return false; + } + break; + } + + JSObject* argType = PrepareType(cx, i, args[i]); + if (!argType) + return false; + + ffi_type* ffiType = CType::GetFFIType(cx, argType); + if (!ffiType) + return false; + + fninfo->mArgTypes.infallibleAppend(argType); + fninfo->mFFITypes.infallibleAppend(ffiType); + } + + if (fninfo->mIsVariadic) { + // wait to PrepareCIF until function is called + return true; + } + + if (!PrepareCIF(cx, fninfo)) + return false; + + return true; +} + +bool +FunctionType::Create(JSContext* cx, unsigned argc, Value* vp) +{ + // Construct and return a new FunctionType object. + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 2 || args.length() > 3) { + return ArgumentLengthError(cx, "FunctionType", "two or three", "s"); + } + + AutoValueVector argTypes(cx); + RootedObject arrayObj(cx, nullptr); + + if (args.length() == 3) { + // Prepare an array of Values for the arguments. + bool isArray; + if (!args[2].isObject()) { + isArray = false; + } else { + if (!JS_IsArrayObject(cx, args[2], &isArray)) + return false; + } + + if (!isArray) + return ArgumentTypeMismatch(cx, "third ", "FunctionType", "an array"); + + arrayObj = &args[2].toObject(); + + uint32_t len; + ASSERT_OK(JS_GetArrayLength(cx, arrayObj, &len)); + + if (!argTypes.resize(len)) { + JS_ReportOutOfMemory(cx); + return false; + } + } + + // Pull out the argument types from the array, if any. + MOZ_ASSERT_IF(argTypes.length(), arrayObj); + for (uint32_t i = 0; i < argTypes.length(); ++i) { + if (!JS_GetElement(cx, arrayObj, i, argTypes[i])) + return false; + } + + JSObject* result = CreateInternal(cx, args[0], args[1], argTypes); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +JSObject* +FunctionType::CreateInternal(JSContext* cx, + HandleValue abi, + HandleValue rtype, + const HandleValueArray& args) +{ + // Prepare the result type + RootedObject returnType(cx, PrepareReturnType(cx, rtype)); + if (!returnType) + return nullptr; + + // Get ctypes.FunctionType.prototype and the common prototype for CData objects + // of this type, from ctypes.CType.prototype. + RootedObject typeProto(cx, CType::GetProtoFromType(cx, returnType, SLOT_FUNCTIONPROTO)); + if (!typeProto) + return nullptr; + RootedObject dataProto(cx, CType::GetProtoFromType(cx, returnType, SLOT_FUNCTIONDATAPROTO)); + if (!dataProto) + return nullptr; + + // Create a new CType object with the common properties and slots. + RootedObject typeObj(cx, CType::Create(cx, typeProto, dataProto, TYPE_function, + nullptr, JS::UndefinedHandleValue, + JS::UndefinedHandleValue, nullptr)); + if (!typeObj) + return nullptr; + + // Determine and check the types, and prepare the function CIF. + if (!CreateFunctionInfo(cx, typeObj, abi, returnType, args)) + return nullptr; + + return typeObj; +} + +// Construct a function pointer to a JS function (see CClosure::Create()). +// Regular function pointers are constructed directly in +// PointerType::ConstructData(). +bool +FunctionType::ConstructData(JSContext* cx, + HandleObject typeObj, + HandleObject dataObj, + HandleObject fnObj, + HandleObject thisObj, + HandleValue errVal) +{ + MOZ_ASSERT(CType::GetTypeCode(typeObj) == TYPE_function); + + PRFuncPtr* data = static_cast<PRFuncPtr*>(CData::GetData(dataObj)); + + FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); + if (fninfo->mIsVariadic) { + JS_ReportErrorASCII(cx, "Can't declare a variadic callback function"); + return false; + } + if (GetABICode(fninfo->mABI) == ABI_WINAPI) { + JS_ReportErrorASCII(cx, "Can't declare a ctypes.winapi_abi callback function, " + "use ctypes.stdcall_abi instead"); + return false; + } + + RootedObject closureObj(cx, CClosure::Create(cx, typeObj, fnObj, thisObj, errVal, data)); + if (!closureObj) + return false; + + // Set the closure object as the referent of the new CData object. + JS_SetReservedSlot(dataObj, SLOT_REFERENT, ObjectValue(*closureObj)); + + // Seal the CData object, to prevent modification of the function pointer. + // This permanently associates this object with the closure, and avoids + // having to do things like reset SLOT_REFERENT when someone tries to + // change the pointer value. + // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter + // could be called on a frozen object. + return JS_FreezeObject(cx, dataObj); +} + +typedef Vector<AutoValue, 16, SystemAllocPolicy> AutoValueAutoArray; + +static bool +ConvertArgument(JSContext* cx, + HandleObject funObj, + unsigned argIndex, + HandleValue arg, + JSObject* type, + AutoValue* value, + AutoValueAutoArray* strings) +{ + if (!value->SizeToType(cx, type)) { + JS_ReportAllocationOverflow(cx); + return false; + } + + bool freePointer = false; + if (!ImplicitConvert(cx, arg, type, value->mData, + ConversionType::Argument, &freePointer, + funObj, argIndex)) + return false; + + if (freePointer) { + // ImplicitConvert converted a string for us, which we have to free. + // Keep track of it. + if (!strings->growBy(1)) { + JS_ReportOutOfMemory(cx); + return false; + } + strings->back().mData = *static_cast<char**>(value->mData); + } + + return true; +} + +bool +FunctionType::Call(JSContext* cx, + unsigned argc, + Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + // get the callee object... + RootedObject obj(cx, &args.callee()); + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "FunctionType.prototype.call", + args.calleev()); + } + + RootedObject typeObj(cx, CData::GetCType(obj)); + if (CType::GetTypeCode(typeObj) != TYPE_pointer) { + return IncompatibleThisType(cx, "FunctionType.prototype.call", + "non-PointerType CData", args.calleev()); + } + + typeObj = PointerType::GetBaseType(typeObj); + if (CType::GetTypeCode(typeObj) != TYPE_function) { + return IncompatibleThisType(cx, "FunctionType.prototype.call", + "non-FunctionType pointer", args.calleev()); + } + + FunctionInfo* fninfo = GetFunctionInfo(typeObj); + uint32_t argcFixed = fninfo->mArgTypes.length(); + + if ((!fninfo->mIsVariadic && args.length() != argcFixed) || + (fninfo->mIsVariadic && args.length() < argcFixed)) { + return FunctionArgumentLengthMismatch(cx, argcFixed, args.length(), + obj, typeObj, fninfo->mIsVariadic); + } + + // Check if we have a Library object. If we do, make sure it's open. + Value slot = JS_GetReservedSlot(obj, SLOT_REFERENT); + if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { + PRLibrary* library = Library::GetLibrary(&slot.toObject()); + if (!library) { + JS_ReportErrorASCII(cx, "library is not open"); + return false; + } + } + + // prepare the values for each argument + AutoValueAutoArray values; + AutoValueAutoArray strings; + if (!values.resize(args.length())) { + JS_ReportOutOfMemory(cx); + return false; + } + + for (unsigned i = 0; i < argcFixed; ++i) + if (!ConvertArgument(cx, obj, i, args[i], fninfo->mArgTypes[i], + &values[i], &strings)) + return false; + + if (fninfo->mIsVariadic) { + if (!fninfo->mFFITypes.resize(args.length())) { + JS_ReportOutOfMemory(cx); + return false; + } + + RootedObject obj(cx); // Could reuse obj instead of declaring a second + RootedObject type(cx); // RootedObject, but readability would suffer. + + for (uint32_t i = argcFixed; i < args.length(); ++i) { + if (args[i].isPrimitive() || + !CData::IsCData(obj = &args[i].toObject())) { + // Since we know nothing about the CTypes of the ... arguments, + // they absolutely must be CData objects already. + return VariadicArgumentTypeError(cx, i, args[i]); + } + type = CData::GetCType(obj); + if (!type) { + // These functions report their own errors. + return false; + } + RootedValue typeVal(cx, ObjectValue(*type)); + type = PrepareType(cx, i, typeVal); + if (!type) { + return false; + } + // Relying on ImplicitConvert only for the limited purpose of + // converting one CType to another (e.g., T[] to T*). + if (!ConvertArgument(cx, obj, i, args[i], type, &values[i], &strings)) { + return false; + } + fninfo->mFFITypes[i] = CType::GetFFIType(cx, type); + if (!fninfo->mFFITypes[i]) { + return false; + } + } + if (!PrepareCIF(cx, fninfo)) + return false; + } + + // initialize a pointer to an appropriate location, for storing the result + AutoValue returnValue; + TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType); + if (typeCode != TYPE_void_t && + !returnValue.SizeToType(cx, fninfo->mReturnType)) { + JS_ReportAllocationOverflow(cx); + return false; + } + + // Let the runtime callback know that we are about to call into C. + js::AutoCTypesActivityCallback autoCallback(cx, js::CTYPES_CALL_BEGIN, js::CTYPES_CALL_END); + + uintptr_t fn = *reinterpret_cast<uintptr_t*>(CData::GetData(obj)); + +#if defined(XP_WIN) + int32_t lastErrorStatus; // The status as defined by |GetLastError| + int32_t savedLastError = GetLastError(); + SetLastError(0); +#endif //defined(XP_WIN) + int errnoStatus; // The status as defined by |errno| + int savedErrno = errno; + errno = 0; + + ffi_call(&fninfo->mCIF, FFI_FN(fn), returnValue.mData, + reinterpret_cast<void**>(values.begin())); + + // Save error value. + // We need to save it before leaving the scope of |suspend| as destructing + // |suspend| has the side-effect of clearing |GetLastError| + // (see bug 684017). + + errnoStatus = errno; +#if defined(XP_WIN) + lastErrorStatus = GetLastError(); + SetLastError(savedLastError); +#endif // defined(XP_WIN) + + errno = savedErrno; + + // We're no longer calling into C. + autoCallback.DoEndCallback(); + + // Store the error value for later consultation with |ctypes.getStatus| + JSObject* objCTypes = CType::GetGlobalCTypes(cx, typeObj); + if (!objCTypes) + return false; + + JS_SetReservedSlot(objCTypes, SLOT_ERRNO, Int32Value(errnoStatus)); +#if defined(XP_WIN) + JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, Int32Value(lastErrorStatus)); +#endif // defined(XP_WIN) + + // Small integer types get returned as a word-sized ffi_arg. Coerce it back + // into the correct size for ConvertToJS. + switch (typeCode) { +#define INTEGRAL_CASE(name, type, ffiType) \ + case TYPE_##name: \ + if (sizeof(type) < sizeof(ffi_arg)) { \ + ffi_arg data = *static_cast<ffi_arg*>(returnValue.mData); \ + *static_cast<type*>(returnValue.mData) = static_cast<type>(data); \ + } \ + break; + CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) +#undef INTEGRAL_CASE + default: + break; + } + + // prepare a JS object from the result + RootedObject returnType(cx, fninfo->mReturnType); + return ConvertToJS(cx, returnType, nullptr, returnValue.mData, false, true, args.rval()); +} + +FunctionInfo* +FunctionType::GetFunctionInfo(JSObject* obj) +{ + MOZ_ASSERT(CType::IsCType(obj)); + MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_function); + + Value slot = JS_GetReservedSlot(obj, SLOT_FNINFO); + MOZ_ASSERT(!slot.isUndefined() && slot.toPrivate()); + + return static_cast<FunctionInfo*>(slot.toPrivate()); +} + +bool +FunctionType::IsFunctionType(HandleValue v) +{ + if (!v.isObject()) + return false; + JSObject* obj = &v.toObject(); + return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_function; +} + +bool +FunctionType::ArgTypesGetter(JSContext* cx, const JS::CallArgs& args) +{ + JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + + args.rval().set(JS_GetReservedSlot(obj, SLOT_ARGS_T)); + if (!args.rval().isUndefined()) + return true; + + FunctionInfo* fninfo = GetFunctionInfo(obj); + size_t len = fninfo->mArgTypes.length(); + + // Prepare a new array. + JS::Rooted<JSObject*> argTypes(cx); + { + JS::AutoValueVector vec(cx); + if (!vec.resize(len)) + return false; + + for (size_t i = 0; i < len; ++i) + vec[i].setObject(*fninfo->mArgTypes[i]); + + argTypes = JS_NewArrayObject(cx, vec); + if (!argTypes) + return false; + } + + // Seal and cache it. + if (!JS_FreezeObject(cx, argTypes)) + return false; + JS_SetReservedSlot(obj, SLOT_ARGS_T, JS::ObjectValue(*argTypes)); + + args.rval().setObject(*argTypes); + return true; +} + +bool +FunctionType::ReturnTypeGetter(JSContext* cx, const JS::CallArgs& args) +{ + // Get the returnType object from the FunctionInfo. + args.rval().setObject(*GetFunctionInfo(&args.thisv().toObject())->mReturnType); + return true; +} + +bool +FunctionType::ABIGetter(JSContext* cx, const JS::CallArgs& args) +{ + // Get the abi object from the FunctionInfo. + args.rval().setObject(*GetFunctionInfo(&args.thisv().toObject())->mABI); + return true; +} + +bool +FunctionType::IsVariadicGetter(JSContext* cx, const JS::CallArgs& args) +{ + args.rval().setBoolean(GetFunctionInfo(&args.thisv().toObject())->mIsVariadic); + return true; +} + +/******************************************************************************* +** CClosure implementation +*******************************************************************************/ + +JSObject* +CClosure::Create(JSContext* cx, + HandleObject typeObj, + HandleObject fnObj, + HandleObject thisObj, + HandleValue errVal, + PRFuncPtr* fnptr) +{ + MOZ_ASSERT(fnObj); + + RootedObject result(cx, JS_NewObject(cx, &sCClosureClass)); + if (!result) + return nullptr; + + // Get the FunctionInfo from the FunctionType. + FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); + MOZ_ASSERT(!fninfo->mIsVariadic); + MOZ_ASSERT(GetABICode(fninfo->mABI) != ABI_WINAPI); + + // Get the prototype of the FunctionType object, of class CTypeProto, + // which stores our JSContext for use with the closure. + RootedObject proto(cx); + if (!JS_GetPrototype(cx, typeObj, &proto)) + return nullptr; + MOZ_ASSERT(proto); + MOZ_ASSERT(CType::IsCTypeProto(proto)); + + // Prepare the error sentinel value. It's important to do this now, because + // we might be unable to convert the value to the proper type. If so, we want + // the caller to know about it _now_, rather than some uncertain time in the + // future when the error sentinel is actually needed. + UniquePtr<uint8_t[], JS::FreePolicy> errResult; + if (!errVal.isUndefined()) { + + // Make sure the callback returns something. + if (CType::GetTypeCode(fninfo->mReturnType) == TYPE_void_t) { + JS_ReportErrorASCII(cx, "A void callback can't pass an error sentinel"); + return nullptr; + } + + // With the exception of void, the FunctionType constructor ensures that + // the return type has a defined size. + MOZ_ASSERT(CType::IsSizeDefined(fninfo->mReturnType)); + + // Allocate a buffer for the return value. + size_t rvSize = CType::GetSize(fninfo->mReturnType); + errResult = result->zone()->make_pod_array<uint8_t>(rvSize); + if (!errResult) + return nullptr; + + // Do the value conversion. This might fail, in which case we throw. + if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(), + ConversionType::Return, nullptr, typeObj)) + return nullptr; + } + + ClosureInfo* cinfo = cx->new_<ClosureInfo>(cx); + if (!cinfo) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + // Copy the important bits of context into cinfo. + cinfo->errResult = errResult.release(); + cinfo->closureObj = result; + cinfo->typeObj = typeObj; + cinfo->thisObj = thisObj; + cinfo->jsfnObj = fnObj; + + // Stash the ClosureInfo struct on our new object. + JS_SetReservedSlot(result, SLOT_CLOSUREINFO, PrivateValue(cinfo)); + + // Create an ffi_closure object and initialize it. + void* code; + cinfo->closure = + static_cast<ffi_closure*>(ffi_closure_alloc(sizeof(ffi_closure), &code)); + if (!cinfo->closure || !code) { + JS_ReportErrorASCII(cx, "couldn't create closure - libffi error"); + return nullptr; + } + + ffi_status status = ffi_prep_closure_loc(cinfo->closure, &fninfo->mCIF, + CClosure::ClosureStub, cinfo, code); + if (status != FFI_OK) { + JS_ReportErrorASCII(cx, "couldn't create closure - libffi error"); + return nullptr; + } + + // Casting between void* and a function pointer is forbidden in C and C++. + // Do it via an integral type. + *fnptr = reinterpret_cast<PRFuncPtr>(reinterpret_cast<uintptr_t>(code)); + return result; +} + +void +CClosure::Trace(JSTracer* trc, JSObject* obj) +{ + // Make sure our ClosureInfo slot is legit. If it's not, bail. + Value slot = JS_GetReservedSlot(obj, SLOT_CLOSUREINFO); + if (slot.isUndefined()) + return; + + ClosureInfo* cinfo = static_cast<ClosureInfo*>(slot.toPrivate()); + + // Identify our objects to the tracer. (There's no need to identify + // 'closureObj', since that's us.) + JS::TraceEdge(trc, &cinfo->typeObj, "typeObj"); + JS::TraceEdge(trc, &cinfo->jsfnObj, "jsfnObj"); + if (cinfo->thisObj) + JS::TraceEdge(trc, &cinfo->thisObj, "thisObj"); +} + +void +CClosure::Finalize(JSFreeOp* fop, JSObject* obj) +{ + // Make sure our ClosureInfo slot is legit. If it's not, bail. + Value slot = JS_GetReservedSlot(obj, SLOT_CLOSUREINFO); + if (slot.isUndefined()) + return; + + ClosureInfo* cinfo = static_cast<ClosureInfo*>(slot.toPrivate()); + FreeOp::get(fop)->delete_(cinfo); +} + +void +CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData) +{ + MOZ_ASSERT(cif); + MOZ_ASSERT(result); + MOZ_ASSERT(args); + MOZ_ASSERT(userData); + + // Retrieve the essentials from our closure object. + ArgClosure argClosure(cif, result, args, static_cast<ClosureInfo*>(userData)); + JSContext* cx = argClosure.cinfo->cx; + RootedObject fun(cx, argClosure.cinfo->jsfnObj); + + js::PrepareScriptEnvironmentAndInvoke(cx, fun, argClosure); +} + +bool CClosure::ArgClosure::operator()(JSContext* cx) +{ + // Let the runtime callback know that we are about to call into JS again. The end callback will + // fire automatically when we exit this function. + js::AutoCTypesActivityCallback autoCallback(cx, js::CTYPES_CALLBACK_BEGIN, + js::CTYPES_CALLBACK_END); + + RootedObject typeObj(cx, cinfo->typeObj); + RootedObject thisObj(cx, cinfo->thisObj); + RootedValue jsfnVal(cx, ObjectValue(*cinfo->jsfnObj)); + AssertSameCompartment(cx, cinfo->jsfnObj); + + + JS_AbortIfWrongThread(cx); + + // Assert that our CIFs agree. + FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); + MOZ_ASSERT(cif == &fninfo->mCIF); + + TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType); + + // Initialize the result to zero, in case something fails. Small integer types + // are promoted to a word-sized ffi_arg, so we must be careful to zero the + // whole word. + size_t rvSize = 0; + if (cif->rtype != &ffi_type_void) { + rvSize = cif->rtype->size; + switch (typeCode) { +#define INTEGRAL_CASE(name, type, ffiType) case TYPE_##name: + CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) +#undef INTEGRAL_CASE + rvSize = Align(rvSize, sizeof(ffi_arg)); + break; + default: + break; + } + memset(result, 0, rvSize); + } + + // Set up an array for converted arguments. + JS::AutoValueVector argv(cx); + if (!argv.resize(cif->nargs)) { + JS_ReportOutOfMemory(cx); + return false; + } + + for (uint32_t i = 0; i < cif->nargs; ++i) { + // Convert each argument, and have any CData objects created depend on + // the existing buffers. + RootedObject argType(cx, fninfo->mArgTypes[i]); + if (!ConvertToJS(cx, argType, nullptr, args[i], false, false, argv[i])) + return false; + } + + // Call the JS function. 'thisObj' may be nullptr, in which case the JS + // engine will find an appropriate object to use. + RootedValue rval(cx); + bool success = JS_CallFunctionValue(cx, thisObj, jsfnVal, argv, &rval); + + // Convert the result. Note that we pass 'ConversionType::Return', such that + // ImplicitConvert will *not* autoconvert a JS string into a pointer-to-char + // type, which would require an allocation that we can't track. The JS + // function must perform this conversion itself and return a PointerType + // CData; thusly, the burden of freeing the data is left to the user. + if (success && cif->rtype != &ffi_type_void) + success = ImplicitConvert(cx, rval, fninfo->mReturnType, result, + ConversionType::Return, nullptr, typeObj); + + if (!success) { + // Something failed. The callee may have thrown, or it may not have + // returned a value that ImplicitConvert() was happy with. Depending on how + // prudent the consumer has been, we may or may not have a recovery plan. + // + // Note that PrepareScriptEnvironmentAndInvoke should take care of reporting + // the exception. + + if (cinfo->errResult) { + // Good case: we have a sentinel that we can return. Copy it in place of + // the actual return value, and then proceed. + + // The buffer we're returning might be larger than the size of the return + // type, due to libffi alignment issues (see above). But it should never + // be smaller. + size_t copySize = CType::GetSize(fninfo->mReturnType); + MOZ_ASSERT(copySize <= rvSize); + memcpy(result, cinfo->errResult, copySize); + + // We still want to return false here, so that + // PrepareScriptEnvironmentAndInvoke will report the exception. + } else { + // Bad case: not much we can do here. The rv is already zeroed out, so we + // just return and hope for the best. + } + return false; + } + + // Small integer types must be returned as a word-sized ffi_arg. Coerce it + // back into the size libffi expects. + switch (typeCode) { +#define INTEGRAL_CASE(name, type, ffiType) \ + case TYPE_##name: \ + if (sizeof(type) < sizeof(ffi_arg)) { \ + ffi_arg data = *static_cast<type*>(result); \ + *static_cast<ffi_arg*>(result) = data; \ + } \ + break; + CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) + CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) +#undef INTEGRAL_CASE + default: + break; + } + + return true; +} + +/******************************************************************************* +** CData implementation +*******************************************************************************/ + +// Create a new CData object of type 'typeObj' containing binary data supplied +// in 'source', optionally with a referent object 'refObj'. +// +// * 'typeObj' must be a CType of defined (but possibly zero) size. +// +// * If an object 'refObj' is supplied, the new CData object stores the +// referent object in a reserved slot for GC safety, such that 'refObj' will +// be held alive by the resulting CData object. 'refObj' may or may not be +// a CData object; merely an object we want to keep alive. +// * If 'refObj' is a CData object, 'ownResult' must be false. +// * Otherwise, 'refObj' is a Library or CClosure object, and 'ownResult' +// may be true or false. +// * Otherwise 'refObj' is nullptr. In this case, 'ownResult' may be true or +// false. +// +// * If 'ownResult' is true, the CData object will allocate an appropriately +// sized buffer, and free it upon finalization. If 'source' data is +// supplied, the data will be copied from 'source' into the buffer; +// otherwise, the entirety of the new buffer will be initialized to zero. +// * If 'ownResult' is false, the new CData's buffer refers to a slice of +// another buffer kept alive by 'refObj'. 'source' data must be provided, +// and the new CData's buffer will refer to 'source'. +JSObject* +CData::Create(JSContext* cx, + HandleObject typeObj, + HandleObject refObj, + void* source, + bool ownResult) +{ + MOZ_ASSERT(typeObj); + MOZ_ASSERT(CType::IsCType(typeObj)); + MOZ_ASSERT(CType::IsSizeDefined(typeObj)); + MOZ_ASSERT(ownResult || source); + MOZ_ASSERT_IF(refObj && CData::IsCData(refObj), !ownResult); + + // Get the 'prototype' property from the type. + Value slot = JS_GetReservedSlot(typeObj, SLOT_PROTO); + MOZ_ASSERT(slot.isObject()); + + RootedObject proto(cx, &slot.toObject()); + + RootedObject dataObj(cx, JS_NewObjectWithGivenProto(cx, &sCDataClass, proto)); + if (!dataObj) + return nullptr; + + // set the CData's associated type + JS_SetReservedSlot(dataObj, SLOT_CTYPE, ObjectValue(*typeObj)); + + // Stash the referent object, if any, for GC safety. + if (refObj) + JS_SetReservedSlot(dataObj, SLOT_REFERENT, ObjectValue(*refObj)); + + // Set our ownership flag. + JS_SetReservedSlot(dataObj, SLOT_OWNS, BooleanValue(ownResult)); + + // attach the buffer. since it might not be 2-byte aligned, we need to + // allocate an aligned space for it and store it there. :( + char** buffer = cx->new_<char*>(); + if (!buffer) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + char* data; + if (!ownResult) { + data = static_cast<char*>(source); + } else { + // Initialize our own buffer. + size_t size = CType::GetSize(typeObj); + data = dataObj->zone()->pod_malloc<char>(size); + if (!data) { + // Report a catchable allocation error. + JS_ReportAllocationOverflow(cx); + js_free(buffer); + return nullptr; + } + + if (!source) + memset(data, 0, size); + else + memcpy(data, source, size); + } + + *buffer = data; + JS_SetReservedSlot(dataObj, SLOT_DATA, PrivateValue(buffer)); + + return dataObj; +} + +void +CData::Finalize(JSFreeOp* fop, JSObject* obj) +{ + // Delete our buffer, and the data it contains if we own it. + Value slot = JS_GetReservedSlot(obj, SLOT_OWNS); + if (slot.isUndefined()) + return; + + bool owns = slot.toBoolean(); + + slot = JS_GetReservedSlot(obj, SLOT_DATA); + if (slot.isUndefined()) + return; + char** buffer = static_cast<char**>(slot.toPrivate()); + + if (owns) + FreeOp::get(fop)->free_(*buffer); + FreeOp::get(fop)->delete_(buffer); +} + +JSObject* +CData::GetCType(JSObject* dataObj) +{ + MOZ_ASSERT(CData::IsCData(dataObj)); + + Value slot = JS_GetReservedSlot(dataObj, SLOT_CTYPE); + JSObject* typeObj = slot.toObjectOrNull(); + MOZ_ASSERT(CType::IsCType(typeObj)); + return typeObj; +} + +void* +CData::GetData(JSObject* dataObj) +{ + MOZ_ASSERT(CData::IsCData(dataObj)); + + Value slot = JS_GetReservedSlot(dataObj, SLOT_DATA); + + void** buffer = static_cast<void**>(slot.toPrivate()); + MOZ_ASSERT(buffer); + MOZ_ASSERT(*buffer); + return *buffer; +} + +bool +CData::IsCData(JSObject* obj) +{ + return JS_GetClass(obj) == &sCDataClass; +} + +bool +CData::IsCData(HandleValue v) +{ + return v.isObject() && CData::IsCData(&v.toObject()); +} + +bool +CData::IsCDataProto(JSObject* obj) +{ + return JS_GetClass(obj) == &sCDataProtoClass; +} + +bool +CData::ValueGetter(JSContext* cx, const JS::CallArgs& args) +{ + RootedObject obj(cx, &args.thisv().toObject()); + + // Convert the value to a primitive; do not create a new CData object. + RootedObject ctype(cx, GetCType(obj)); + return ConvertToJS(cx, ctype, nullptr, GetData(obj), true, false, args.rval()); +} + +bool +CData::ValueSetter(JSContext* cx, const JS::CallArgs& args) +{ + RootedObject obj(cx, &args.thisv().toObject()); + args.rval().setUndefined(); + return ImplicitConvert(cx, args.get(0), GetCType(obj), GetData(obj), + ConversionType::Setter, nullptr); +} + +bool +CData::Address(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 0) { + return ArgumentLengthError(cx, "CData.prototype.address", "no", "s"); + } + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + if (!IsCData(obj)) { + return IncompatibleThisProto(cx, "CData.prototype.address", args.thisv()); + } + + RootedObject typeObj(cx, CData::GetCType(obj)); + RootedObject pointerType(cx, PointerType::CreateInternal(cx, typeObj)); + if (!pointerType) + return false; + + // Create a PointerType CData object containing null. + JSObject* result = CData::Create(cx, pointerType, nullptr, nullptr, true); + if (!result) + return false; + + args.rval().setObject(*result); + + // Manually set the pointer inside the object, so we skip the conversion step. + void** data = static_cast<void**>(GetData(result)); + *data = GetData(obj); + return true; +} + +bool +CData::Cast(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) { + return ArgumentLengthError(cx, "ctypes.cast", "two", "s"); + } + + if (args[0].isPrimitive() || !CData::IsCData(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "first ", "ctypes.cast", "a CData"); + } + RootedObject sourceData(cx, &args[0].toObject()); + RootedObject sourceType(cx, CData::GetCType(sourceData)); + + if (args[1].isPrimitive() || !CType::IsCType(&args[1].toObject())) { + return ArgumentTypeMismatch(cx, "second ", "ctypes.cast", "a CType"); + } + + RootedObject targetType(cx, &args[1].toObject()); + size_t targetSize; + if (!CType::GetSafeSize(targetType, &targetSize)) { + return UndefinedSizeCastError(cx, targetType); + } + if (targetSize > CType::GetSize(sourceType)) { + return SizeMismatchCastError(cx, sourceType, targetType, + CType::GetSize(sourceType), targetSize); + } + + // Construct a new CData object with a type of 'targetType' and a referent + // of 'sourceData'. + void* data = CData::GetData(sourceData); + JSObject* result = CData::Create(cx, targetType, sourceData, data, false); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +bool +CData::GetRuntime(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ArgumentLengthError(cx, "ctypes.getRuntime", "one", ""); + } + + if (args[0].isPrimitive() || !CType::IsCType(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "ctypes.getRuntime", "a CType"); + } + + RootedObject targetType(cx, &args[0].toObject()); + size_t targetSize; + if (!CType::GetSafeSize(targetType, &targetSize) || + targetSize != sizeof(void*)) { + JS_ReportErrorASCII(cx, "target CType has non-pointer size"); + return false; + } + + void* data = static_cast<void*>(cx->runtime()); + JSObject* result = CData::Create(cx, targetType, nullptr, &data, true); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +typedef JS::TwoByteCharsZ (*InflateUTF8Method)(JSContext*, const JS::UTF8Chars, size_t*); + +static bool +ReadStringCommon(JSContext* cx, InflateUTF8Method inflateUTF8, unsigned argc, + Value* vp, const char* funName) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 0) { + return ArgumentLengthError(cx, funName, "no", "s"); + } + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) { + return IncompatibleThisProto(cx, funName, args.thisv()); + } + if (!CData::IsCData(obj)) { + if (!CDataFinalizer::IsCDataFinalizer(obj)) { + return IncompatibleThisProto(cx, funName, args.thisv()); + } + + CDataFinalizer::Private* p = (CDataFinalizer::Private*) + JS_GetPrivate(obj); + if (!p) { + return EmptyFinalizerCallError(cx, funName); + } + + RootedValue dataVal(cx); + if (!CDataFinalizer::GetValue(cx, obj, &dataVal)) { + return IncompatibleThisProto(cx, funName, args.thisv()); + } + + if (dataVal.isPrimitive()) { + return IncompatibleThisProto(cx, funName, args.thisv()); + } + + obj = dataVal.toObjectOrNull(); + if (!obj || !CData::IsCData(obj)) { + return IncompatibleThisProto(cx, funName, args.thisv()); + } + } + + // Make sure we are a pointer to, or an array of, an 8-bit or 16-bit + // character or integer type. + JSObject* baseType; + JSObject* typeObj = CData::GetCType(obj); + TypeCode typeCode = CType::GetTypeCode(typeObj); + void* data; + size_t maxLength = -1; + switch (typeCode) { + case TYPE_pointer: + baseType = PointerType::GetBaseType(typeObj); + data = *static_cast<void**>(CData::GetData(obj)); + if (data == nullptr) { + return NullPointerError(cx, "read contents of", obj); + } + break; + case TYPE_array: + baseType = ArrayType::GetBaseType(typeObj); + data = CData::GetData(obj); + maxLength = ArrayType::GetLength(typeObj); + break; + default: + return TypeError(cx, "PointerType or ArrayType", args.thisv()); + } + + // Convert the string buffer, taking care to determine the correct string + // length in the case of arrays (which may contain embedded nulls). + JSString* result; + switch (CType::GetTypeCode(baseType)) { + case TYPE_int8_t: + case TYPE_uint8_t: + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + char* bytes = static_cast<char*>(data); + size_t length = strnlen(bytes, maxLength); + + // Determine the length. + char16_t* dst = inflateUTF8(cx, JS::UTF8Chars(bytes, length), &length).get(); + if (!dst) + return false; + + result = JS_NewUCString(cx, dst, length); + if (!result) { + js_free(dst); + return false; + } + + break; + } + case TYPE_int16_t: + case TYPE_uint16_t: + case TYPE_short: + case TYPE_unsigned_short: + case TYPE_char16_t: { + char16_t* chars = static_cast<char16_t*>(data); + size_t length = strnlen(chars, maxLength); + result = JS_NewUCStringCopyN(cx, chars, length); + break; + } + default: + return NonStringBaseError(cx, args.thisv()); + } + + if (!result) + return false; + + args.rval().setString(result); + return true; +} + +bool +CData::ReadString(JSContext* cx, unsigned argc, Value* vp) +{ + return ReadStringCommon(cx, JS::UTF8CharsToNewTwoByteCharsZ, argc, vp, + "CData.prototype.readString"); +} + +bool +CDataFinalizer::Methods::ReadString(JSContext* cx, unsigned argc, Value* vp) +{ + return ReadStringCommon(cx, JS::UTF8CharsToNewTwoByteCharsZ, argc, vp, + "CDataFinalizer.prototype.readString"); +} + +bool +CData::ReadStringReplaceMalformed(JSContext* cx, unsigned argc, Value* vp) +{ + return ReadStringCommon(cx, JS::LossyUTF8CharsToNewTwoByteCharsZ, argc, vp, + "CData.prototype.readStringReplaceMalformed"); +} + +JSString* +CData::GetSourceString(JSContext* cx, HandleObject typeObj, void* data) +{ + // Walk the types, building up the toSource() string. + // First, we build up the type expression: + // 't.ptr' for pointers; + // 't.array([n])' for arrays; + // 'n' for structs, where n = t.name, the struct's name. (We assume this is + // bound to a variable in the current scope.) + AutoString source; + BuildTypeSource(cx, typeObj, true, source); + AppendString(source, "("); + if (!BuildDataSource(cx, typeObj, data, false, source)) + return nullptr; + + AppendString(source, ")"); + + return NewUCString(cx, source); +} + +bool +CData::ToSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 0) { + return ArgumentLengthError(cx, "CData.prototype.toSource", "no", "s"); + } + + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) + return false; + if (!CData::IsCData(obj) && !CData::IsCDataProto(obj)) { + return IncompatibleThisProto(cx, "CData.prototype.toSource", + InformalValueTypeName(args.thisv())); + } + + JSString* result; + if (CData::IsCData(obj)) { + RootedObject typeObj(cx, CData::GetCType(obj)); + void* data = CData::GetData(obj); + + result = CData::GetSourceString(cx, typeObj, data); + } else { + result = JS_NewStringCopyZ(cx, "[CData proto object]"); + } + + if (!result) + return false; + + args.rval().setString(result); + return true; +} + +bool +CData::ErrnoGetter(JSContext* cx, const JS::CallArgs& args) +{ + args.rval().set(JS_GetReservedSlot(&args.thisv().toObject(), SLOT_ERRNO)); + return true; +} + +#if defined(XP_WIN) +bool +CData::LastErrorGetter(JSContext* cx, const JS::CallArgs& args) +{ + args.rval().set(JS_GetReservedSlot(&args.thisv().toObject(), SLOT_LASTERROR)); + return true; +} +#endif // defined(XP_WIN) + +bool +CDataFinalizer::Methods::ToSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject objThis(cx, JS_THIS_OBJECT(cx, vp)); + if (!objThis) + return false; + if (!CDataFinalizer::IsCDataFinalizer(objThis)) { + return IncompatibleThisProto(cx, "CDataFinalizer.prototype.toSource", + InformalValueTypeName(args.thisv())); + } + + CDataFinalizer::Private* p = (CDataFinalizer::Private*) + JS_GetPrivate(objThis); + + JSString* strMessage; + if (!p) { + strMessage = JS_NewStringCopyZ(cx, "ctypes.CDataFinalizer()"); + } else { + RootedObject objType(cx, CDataFinalizer::GetCType(cx, objThis)); + if (!objType) { + JS_ReportErrorASCII(cx, "CDataFinalizer has no type"); + return false; + } + + AutoString source; + AppendString(source, "ctypes.CDataFinalizer("); + JSString* srcValue = CData::GetSourceString(cx, objType, p->cargs); + if (!srcValue) { + return false; + } + AppendString(source, srcValue); + AppendString(source, ", "); + Value valCodePtrType = JS_GetReservedSlot(objThis, + SLOT_DATAFINALIZER_CODETYPE); + if (valCodePtrType.isPrimitive()) { + return false; + } + + RootedObject typeObj(cx, valCodePtrType.toObjectOrNull()); + JSString* srcDispose = CData::GetSourceString(cx, typeObj, &(p->code)); + if (!srcDispose) { + return false; + } + + AppendString(source, srcDispose); + AppendString(source, ")"); + strMessage = NewUCString(cx, source); + } + + if (!strMessage) { + // This is a memory issue, no error message + return false; + } + + args.rval().setString(strMessage); + return true; +} + +bool +CDataFinalizer::Methods::ToString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JSObject* objThis = JS_THIS_OBJECT(cx, vp); + if (!objThis) + return false; + if (!CDataFinalizer::IsCDataFinalizer(objThis)) { + return IncompatibleThisProto(cx, "CDataFinalizer.prototype.toString", + InformalValueTypeName(args.thisv())); + } + + JSString* strMessage; + RootedValue value(cx); + if (!JS_GetPrivate(objThis)) { + // Pre-check whether CDataFinalizer::GetValue can fail + // to avoid reporting an error when not appropriate. + strMessage = JS_NewStringCopyZ(cx, "[CDataFinalizer - empty]"); + if (!strMessage) { + return false; + } + } else if (!CDataFinalizer::GetValue(cx, objThis, &value)) { + MOZ_CRASH("Could not convert an empty CDataFinalizer"); + } else { + strMessage = ToString(cx, value); + if (!strMessage) { + return false; + } + } + args.rval().setString(strMessage); + return true; +} + +bool +CDataFinalizer::IsCDataFinalizer(JSObject* obj) +{ + return JS_GetClass(obj) == &sCDataFinalizerClass; +} + + +JSObject* +CDataFinalizer::GetCType(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(IsCDataFinalizer(obj)); + + Value valData = JS_GetReservedSlot(obj, + SLOT_DATAFINALIZER_VALTYPE); + if (valData.isUndefined()) { + return nullptr; + } + + return valData.toObjectOrNull(); +} + +bool +CDataFinalizer::GetValue(JSContext* cx, JSObject* obj, + MutableHandleValue aResult) +{ + MOZ_ASSERT(IsCDataFinalizer(obj)); + + CDataFinalizer::Private* p = (CDataFinalizer::Private*) + JS_GetPrivate(obj); + + if (!p) { + // We have called |dispose| or |forget| already. + JS_ReportErrorASCII(cx, "Attempting to get the value of an empty CDataFinalizer"); + return false; + } + + RootedObject ctype(cx, GetCType(cx, obj)); + return ConvertToJS(cx, ctype, /*parent*/nullptr, p->cargs, false, true, aResult); +} + +/* + * Attach a C function as a finalizer to a JS object. + * + * Pseudo-JS signature: + * function(CData<T>, CData<T -> U>): CDataFinalizer<T> + * value, finalizer + * + * This function attaches strong references to the following values: + * - the CType of |value| + * + * Note: This function takes advantage of the fact that non-variadic + * CData functions are initialized during creation. + */ +bool +CDataFinalizer::Construct(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject objSelf(cx, &args.callee()); + RootedObject objProto(cx); + if (!GetObjectProperty(cx, objSelf, "prototype", &objProto)) { + JS_ReportErrorASCII(cx, "CDataFinalizer.prototype does not exist"); + return false; + } + + // Get arguments + if (args.length() == 0) { // Special case: the empty (already finalized) object + JSObject* objResult = JS_NewObjectWithGivenProto(cx, &sCDataFinalizerClass, objProto); + args.rval().setObject(*objResult); + return true; + } + + if (args.length() != 2) { + return ArgumentLengthError(cx, "CDataFinalizer constructor", "two", "s"); + } + + JS::HandleValue valCodePtr = args[1]; + if (!valCodePtr.isObject()) { + return TypeError(cx, "_a CData object_ of a function pointer type", + valCodePtr); + } + JSObject* objCodePtr = &valCodePtr.toObject(); + + //Note: Using a custom argument formatter here would be awkward (requires + //a destructor just to uninstall the formatter). + + // 2. Extract argument type of |objCodePtr| + if (!CData::IsCData(objCodePtr)) { + return TypeError(cx, "a _CData_ object of a function pointer type", + valCodePtr); + } + RootedObject objCodePtrType(cx, CData::GetCType(objCodePtr)); + RootedValue valCodePtrType(cx, ObjectValue(*objCodePtrType)); + MOZ_ASSERT(objCodePtrType); + + TypeCode typCodePtr = CType::GetTypeCode(objCodePtrType); + if (typCodePtr != TYPE_pointer) { + return TypeError(cx, "a CData object of a function _pointer_ type", + valCodePtr); + } + + JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType); + MOZ_ASSERT(objCodeType); + + TypeCode typCode = CType::GetTypeCode(objCodeType); + if (typCode != TYPE_function) { + return TypeError(cx, "a CData object of a _function_ pointer type", + valCodePtr); + } + uintptr_t code = *reinterpret_cast<uintptr_t*>(CData::GetData(objCodePtr)); + if (!code) { + return TypeError(cx, "a CData object of a _non-NULL_ function pointer type", + valCodePtr); + } + + FunctionInfo* funInfoFinalizer = + FunctionType::GetFunctionInfo(objCodeType); + MOZ_ASSERT(funInfoFinalizer); + + if ((funInfoFinalizer->mArgTypes.length() != 1) + || (funInfoFinalizer->mIsVariadic)) { + RootedValue valCodeType(cx, ObjectValue(*objCodeType)); + return TypeError(cx, "a function accepting exactly one argument", + valCodeType); + } + RootedObject objArgType(cx, funInfoFinalizer->mArgTypes[0]); + RootedObject returnType(cx, funInfoFinalizer->mReturnType); + + // Invariant: At this stage, we know that funInfoFinalizer->mIsVariadic + // is |false|. Therefore, funInfoFinalizer->mCIF has already been initialized. + + bool freePointer = false; + + // 3. Perform dynamic cast of |args[0]| into |objType|, store it in |cargs| + + size_t sizeArg; + RootedValue valData(cx, args[0]); + if (!CType::GetSafeSize(objArgType, &sizeArg)) { + RootedValue valCodeType(cx, ObjectValue(*objCodeType)); + return TypeError(cx, "a function with one known size argument", + valCodeType); + } + + ScopedJSFreePtr<void> cargs(malloc(sizeArg)); + + if (!ImplicitConvert(cx, valData, objArgType, cargs.get(), + ConversionType::Finalizer, &freePointer, + objCodePtrType, 0)) { + return false; + } + if (freePointer) { + // Note: We could handle that case, if necessary. + JS_ReportErrorASCII(cx, "Internal Error during CDataFinalizer. Object cannot be represented"); + return false; + } + + // 4. Prepare buffer for holding return value + + ScopedJSFreePtr<void> rvalue; + if (CType::GetTypeCode(returnType) != TYPE_void_t) { + rvalue = malloc(Align(CType::GetSize(returnType), + sizeof(ffi_arg))); + } //Otherwise, simply do not allocate + + // 5. Create |objResult| + + JSObject* objResult = JS_NewObjectWithGivenProto(cx, &sCDataFinalizerClass, objProto); + if (!objResult) { + return false; + } + + // If our argument is a CData, it holds a type. + // This is the type that we should capture, not that + // of the function, which may be less precise. + JSObject* objBestArgType = objArgType; + if (valData.isObject()) { + JSObject* objData = &valData.toObject(); + if (CData::IsCData(objData)) { + objBestArgType = CData::GetCType(objData); + size_t sizeBestArg; + if (!CType::GetSafeSize(objBestArgType, &sizeBestArg)) { + MOZ_CRASH("object with unknown size"); + } + if (sizeBestArg != sizeArg) { + return FinalizerSizeError(cx, objCodePtrType, valData); + } + } + } + + // Used by GetCType + JS_SetReservedSlot(objResult, + SLOT_DATAFINALIZER_VALTYPE, + ObjectOrNullValue(objBestArgType)); + + // Used by ToSource + JS_SetReservedSlot(objResult, + SLOT_DATAFINALIZER_CODETYPE, + ObjectValue(*objCodePtrType)); + + RootedValue abiType(cx, ObjectOrNullValue(funInfoFinalizer->mABI)); + ffi_abi abi; + if (!GetABI(cx, abiType, &abi)) { + JS_ReportErrorASCII(cx, "Internal Error: " + "Invalid ABI specification in CDataFinalizer"); + return false; + } + + ffi_type* rtype = CType::GetFFIType(cx, funInfoFinalizer->mReturnType); + if (!rtype) { + JS_ReportErrorASCII(cx, "Internal Error: " + "Could not access ffi type of CDataFinalizer"); + return false; + } + + // 7. Store C information as private + ScopedJSFreePtr<CDataFinalizer::Private> + p((CDataFinalizer::Private*)malloc(sizeof(CDataFinalizer::Private))); + + memmove(&p->CIF, &funInfoFinalizer->mCIF, sizeof(ffi_cif)); + + p->cargs = cargs.forget(); + p->rvalue = rvalue.forget(); + p->cargs_size = sizeArg; + p->code = code; + + + JS_SetPrivate(objResult, p.forget()); + args.rval().setObject(*objResult); + return true; +} + + +/* + * Actually call the finalizer. Does not perform any cleanup on the object. + * + * Preconditions: |this| must be a |CDataFinalizer|, |p| must be non-null. + * The function fails if |this| has gone through |Forget|/|Dispose| + * or |Finalize|. + * + * This function does not alter the value of |errno|/|GetLastError|. + * + * If argument |errnoStatus| is non-nullptr, it receives the value of |errno| + * immediately after the call. Under Windows, if argument |lastErrorStatus| + * is non-nullptr, it receives the value of |GetLastError| immediately after + * the call. On other platforms, |lastErrorStatus| is ignored. + */ +void +CDataFinalizer::CallFinalizer(CDataFinalizer::Private* p, + int* errnoStatus, + int32_t* lastErrorStatus) +{ + int savedErrno = errno; + errno = 0; +#if defined(XP_WIN) + int32_t savedLastError = GetLastError(); + SetLastError(0); +#endif // defined(XP_WIN) + + void* args[1] = {p->cargs}; + ffi_call(&p->CIF, FFI_FN(p->code), p->rvalue, args); + + if (errnoStatus) { + *errnoStatus = errno; + } + errno = savedErrno; +#if defined(XP_WIN) + if (lastErrorStatus) { + *lastErrorStatus = GetLastError(); + } + SetLastError(savedLastError); +#endif // defined(XP_WIN) +} + +/* + * Forget the value. + * + * Preconditions: |this| must be a |CDataFinalizer|. + * The function fails if |this| has gone through |Forget|/|Dispose| + * or |Finalize|. + * + * Does not call the finalizer. Cleans up the Private memory and releases all + * strong references. + */ +bool +CDataFinalizer::Methods::Forget(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 0) { + return ArgumentLengthError(cx, "CDataFinalizer.prototype.forget", "no", + "s"); + } + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + if (!CDataFinalizer::IsCDataFinalizer(obj)) { + return IncompatibleThisProto(cx, "CDataFinalizer.prototype.forget", + args.thisv()); + } + + CDataFinalizer::Private* p = (CDataFinalizer::Private*) + JS_GetPrivate(obj); + + if (!p) { + return EmptyFinalizerCallError(cx, "CDataFinalizer.prototype.forget"); + } + + RootedValue valJSData(cx); + RootedObject ctype(cx, GetCType(cx, obj)); + if (!ConvertToJS(cx, ctype, nullptr, p->cargs, false, true, &valJSData)) { + JS_ReportErrorASCII(cx, "CDataFinalizer value cannot be represented"); + return false; + } + + CDataFinalizer::Cleanup(p, obj); + + args.rval().set(valJSData); + return true; +} + +/* + * Clean up the value. + * + * Preconditions: |this| must be a |CDataFinalizer|. + * The function fails if |this| has gone through |Forget|/|Dispose| + * or |Finalize|. + * + * Calls the finalizer, cleans up the Private memory and releases all + * strong references. + */ +bool +CDataFinalizer::Methods::Dispose(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 0) { + return ArgumentLengthError(cx, "CDataFinalizer.prototype.dispose", "no", + "s"); + } + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + if (!CDataFinalizer::IsCDataFinalizer(obj)) { + return IncompatibleThisProto(cx, "CDataFinalizer.prototype.dispose", + args.thisv()); + } + + CDataFinalizer::Private* p = (CDataFinalizer::Private*) + JS_GetPrivate(obj); + + if (!p) { + return EmptyFinalizerCallError(cx, "CDataFinalizer.prototype.dispose"); + } + + Value valType = JS_GetReservedSlot(obj, SLOT_DATAFINALIZER_VALTYPE); + MOZ_ASSERT(valType.isObject()); + + RootedObject objCTypes(cx, CType::GetGlobalCTypes(cx, &valType.toObject())); + if (!objCTypes) + return false; + + Value valCodePtrType = JS_GetReservedSlot(obj, SLOT_DATAFINALIZER_CODETYPE); + MOZ_ASSERT(valCodePtrType.isObject()); + JSObject* objCodePtrType = &valCodePtrType.toObject(); + + JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType); + MOZ_ASSERT(objCodeType); + MOZ_ASSERT(CType::GetTypeCode(objCodeType) == TYPE_function); + + RootedObject resultType(cx, FunctionType::GetFunctionInfo(objCodeType)->mReturnType); + RootedValue result(cx); + + int errnoStatus; +#if defined(XP_WIN) + int32_t lastErrorStatus; + CDataFinalizer::CallFinalizer(p, &errnoStatus, &lastErrorStatus); +#else + CDataFinalizer::CallFinalizer(p, &errnoStatus, nullptr); +#endif // defined(XP_WIN) + + JS_SetReservedSlot(objCTypes, SLOT_ERRNO, Int32Value(errnoStatus)); +#if defined(XP_WIN) + JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, Int32Value(lastErrorStatus)); +#endif // defined(XP_WIN) + + if (ConvertToJS(cx, resultType, nullptr, p->rvalue, false, true, &result)) { + CDataFinalizer::Cleanup(p, obj); + args.rval().set(result); + return true; + } + CDataFinalizer::Cleanup(p, obj); + return false; +} + +/* + * Perform finalization. + * + * Preconditions: |this| must be the result of |CDataFinalizer|. + * It may have gone through |Forget|/|Dispose|. + * + * If |this| has not gone through |Forget|/|Dispose|, calls the + * finalizer, cleans up the Private memory and releases all + * strong references. + */ +void +CDataFinalizer::Finalize(JSFreeOp* fop, JSObject* obj) +{ + CDataFinalizer::Private* p = (CDataFinalizer::Private*) + JS_GetPrivate(obj); + + if (!p) { + return; + } + + CDataFinalizer::CallFinalizer(p, nullptr, nullptr); + CDataFinalizer::Cleanup(p, nullptr); +} + +/* + * Perform cleanup of a CDataFinalizer + * + * Release strong references, cleanup |Private|. + * + * Argument |p| contains the private information of the CDataFinalizer. If + * nullptr, this function does nothing. + * Argument |obj| should contain |nullptr| during finalization (or in any + * context in which the object itself should not be cleaned up), or a + * CDataFinalizer object otherwise. + */ +void +CDataFinalizer::Cleanup(CDataFinalizer::Private* p, JSObject* obj) +{ + if (!p) { + return; // We have already cleaned up + } + + free(p->cargs); + free(p->rvalue); + free(p); + + if (!obj) { + return; // No slots to clean up + } + + MOZ_ASSERT(CDataFinalizer::IsCDataFinalizer(obj)); + + JS_SetPrivate(obj, nullptr); + for (int i = 0; i < CDATAFINALIZER_SLOTS; ++i) { + JS_SetReservedSlot(obj, i, JS::NullValue()); + } +} + + +/******************************************************************************* +** Int64 and UInt64 implementation +*******************************************************************************/ + +JSObject* +Int64Base::Construct(JSContext* cx, + HandleObject proto, + uint64_t data, + bool isUnsigned) +{ + const JSClass* clasp = isUnsigned ? &sUInt64Class : &sInt64Class; + RootedObject result(cx, JS_NewObjectWithGivenProto(cx, clasp, proto)); + if (!result) + return nullptr; + + // attach the Int64's data + uint64_t* buffer = cx->new_<uint64_t>(data); + if (!buffer) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + JS_SetReservedSlot(result, SLOT_INT64, PrivateValue(buffer)); + + if (!JS_FreezeObject(cx, result)) + return nullptr; + + return result; +} + +void +Int64Base::Finalize(JSFreeOp* fop, JSObject* obj) +{ + Value slot = JS_GetReservedSlot(obj, SLOT_INT64); + if (slot.isUndefined()) + return; + + FreeOp::get(fop)->delete_(static_cast<uint64_t*>(slot.toPrivate())); +} + +uint64_t +Int64Base::GetInt(JSObject* obj) { + MOZ_ASSERT(Int64::IsInt64(obj) || UInt64::IsUInt64(obj)); + + Value slot = JS_GetReservedSlot(obj, SLOT_INT64); + return *static_cast<uint64_t*>(slot.toPrivate()); +} + +bool +Int64Base::ToString(JSContext* cx, + JSObject* obj, + const CallArgs& args, + bool isUnsigned) +{ + if (args.length() > 1) { + if (isUnsigned) { + return ArgumentLengthError(cx, "UInt64.prototype.toString", + "at most one", ""); + } + return ArgumentLengthError(cx, "Int64.prototype.toString", + "at most one", ""); + } + + int radix = 10; + if (args.length() == 1) { + Value arg = args[0]; + if (arg.isInt32()) + radix = arg.toInt32(); + if (!arg.isInt32() || radix < 2 || radix > 36) { + if (isUnsigned) { + return ArgumentRangeMismatch(cx, "UInt64.prototype.toString", "an integer at least 2 and no greater than 36"); + } + return ArgumentRangeMismatch(cx, "Int64.prototype.toString", "an integer at least 2 and no greater than 36"); + } + } + + AutoString intString; + if (isUnsigned) { + IntegerToString(GetInt(obj), radix, intString); + } else { + IntegerToString(static_cast<int64_t>(GetInt(obj)), radix, intString); + } + + JSString* result = NewUCString(cx, intString); + if (!result) + return false; + + args.rval().setString(result); + return true; +} + +bool +Int64Base::ToSource(JSContext* cx, + JSObject* obj, + const CallArgs& args, + bool isUnsigned) +{ + if (args.length() != 0) { + if (isUnsigned) { + return ArgumentLengthError(cx, "UInt64.prototype.toSource", "no", "s"); + } + return ArgumentLengthError(cx, "Int64.prototype.toSource", "no", "s"); + } + + // Return a decimal string suitable for constructing the number. + AutoString source; + if (isUnsigned) { + AppendString(source, "ctypes.UInt64(\""); + IntegerToString(GetInt(obj), 10, source); + } else { + AppendString(source, "ctypes.Int64(\""); + IntegerToString(static_cast<int64_t>(GetInt(obj)), 10, source); + } + AppendString(source, "\")"); + + JSString* result = NewUCString(cx, source); + if (!result) + return false; + + args.rval().setString(result); + return true; +} + +bool +Int64::Construct(JSContext* cx, + unsigned argc, + Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Construct and return a new Int64 object. + if (args.length() != 1) { + return ArgumentLengthError(cx, "Int64 constructor", "one", ""); + } + + int64_t i = 0; + bool overflow = false; + if (!jsvalToBigInteger(cx, args[0], true, &i, &overflow)) { + if (overflow) { + return TypeOverflow(cx, "int64", args[0]); + } + return ArgumentConvError(cx, args[0], "Int64", 0); + } + + // Get ctypes.Int64.prototype from the 'prototype' property of the ctor. + RootedValue slot(cx); + RootedObject callee(cx, &args.callee()); + ASSERT_OK(JS_GetProperty(cx, callee, "prototype", &slot)); + RootedObject proto(cx, slot.toObjectOrNull()); + MOZ_ASSERT(JS_GetClass(proto) == &sInt64ProtoClass); + + JSObject* result = Int64Base::Construct(cx, proto, i, false); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +bool +Int64::IsInt64(JSObject* obj) +{ + return JS_GetClass(obj) == &sInt64Class; +} + +bool +Int64::ToString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) + return false; + if (!Int64::IsInt64(obj)) { + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "Int64.prototype.toString", + InformalValueTypeName(args.thisv())); + } + return IncompatibleThisType(cx, "Int64.prototype.toString", + "non-Int64 CData"); + } + + return Int64Base::ToString(cx, obj, args, false); +} + +bool +Int64::ToSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) + return false; + if (!Int64::IsInt64(obj)) { + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "Int64.prototype.toSource", + InformalValueTypeName(args.thisv())); + } + return IncompatibleThisType(cx, "Int64.prototype.toSource", + "non-Int64 CData"); + } + + return Int64Base::ToSource(cx, obj, args, false); +} + +bool +Int64::Compare(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) { + return ArgumentLengthError(cx, "Int64.compare", "two", "s"); + } + if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "first ", "Int64.compare", "a Int64"); + } + if (args[1].isPrimitive() ||!Int64::IsInt64(&args[1].toObject())) { + return ArgumentTypeMismatch(cx, "second ", "Int64.compare", "a Int64"); + } + + JSObject* obj1 = &args[0].toObject(); + JSObject* obj2 = &args[1].toObject(); + + int64_t i1 = Int64Base::GetInt(obj1); + int64_t i2 = Int64Base::GetInt(obj2); + + if (i1 == i2) + args.rval().setInt32(0); + else if (i1 < i2) + args.rval().setInt32(-1); + else + args.rval().setInt32(1); + + return true; +} + +#define LO_MASK ((uint64_t(1) << 32) - 1) +#define INT64_LO(i) ((i) & LO_MASK) +#define INT64_HI(i) ((i) >> 32) + +bool +Int64::Lo(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ArgumentLengthError(cx, "Int64.lo", "one", ""); + } + if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "Int64.lo", "a Int64"); + } + + JSObject* obj = &args[0].toObject(); + int64_t u = Int64Base::GetInt(obj); + double d = uint32_t(INT64_LO(u)); + + args.rval().setNumber(d); + return true; +} + +bool +Int64::Hi(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ArgumentLengthError(cx, "Int64.hi", "one", ""); + } + if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "Int64.hi", "a Int64"); + } + + JSObject* obj = &args[0].toObject(); + int64_t u = Int64Base::GetInt(obj); + double d = int32_t(INT64_HI(u)); + + args.rval().setDouble(d); + return true; +} + +bool +Int64::Join(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) { + return ArgumentLengthError(cx, "Int64.join", "two", "s"); + } + + int32_t hi; + uint32_t lo; + if (!jsvalToInteger(cx, args[0], &hi)) + return ArgumentConvError(cx, args[0], "Int64.join", 0); + if (!jsvalToInteger(cx, args[1], &lo)) + return ArgumentConvError(cx, args[1], "Int64.join", 1); + + int64_t i = (int64_t(hi) << 32) + int64_t(lo); + + // Get Int64.prototype from the function's reserved slot. + JSObject* callee = &args.callee(); + + Value slot = js::GetFunctionNativeReserved(callee, SLOT_FN_INT64PROTO); + RootedObject proto(cx, &slot.toObject()); + MOZ_ASSERT(JS_GetClass(proto) == &sInt64ProtoClass); + + JSObject* result = Int64Base::Construct(cx, proto, i, false); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +bool +UInt64::Construct(JSContext* cx, + unsigned argc, + Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Construct and return a new UInt64 object. + if (args.length() != 1) { + return ArgumentLengthError(cx, "UInt64 constructor", "one", ""); + } + + uint64_t u = 0; + bool overflow = false; + if (!jsvalToBigInteger(cx, args[0], true, &u, &overflow)) { + if (overflow) { + return TypeOverflow(cx, "uint64", args[0]); + } + return ArgumentConvError(cx, args[0], "UInt64", 0); + } + + // Get ctypes.UInt64.prototype from the 'prototype' property of the ctor. + RootedValue slot(cx); + RootedObject callee(cx, &args.callee()); + ASSERT_OK(JS_GetProperty(cx, callee, "prototype", &slot)); + RootedObject proto(cx, &slot.toObject()); + MOZ_ASSERT(JS_GetClass(proto) == &sUInt64ProtoClass); + + JSObject* result = Int64Base::Construct(cx, proto, u, true); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +bool +UInt64::IsUInt64(JSObject* obj) +{ + return JS_GetClass(obj) == &sUInt64Class; +} + +bool +UInt64::ToString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) + return false; + if (!UInt64::IsUInt64(obj)) { + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "UInt64.prototype.toString", + InformalValueTypeName(args.thisv())); + } + return IncompatibleThisType(cx, "UInt64.prototype.toString", + "non-UInt64 CData"); + } + + return Int64Base::ToString(cx, obj, args, true); +} + +bool +UInt64::ToSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) + return false; + if (!UInt64::IsUInt64(obj)) { + if (!CData::IsCData(obj)) { + return IncompatibleThisProto(cx, "UInt64.prototype.toSource", + InformalValueTypeName(args.thisv())); + } + return IncompatibleThisType(cx, "UInt64.prototype.toSource", + "non-UInt64 CData"); + } + + return Int64Base::ToSource(cx, obj, args, true); +} + +bool +UInt64::Compare(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) { + return ArgumentLengthError(cx, "UInt64.compare", "two", "s"); + } + if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "first ", "UInt64.compare", "a UInt64"); + } + if (args[1].isPrimitive() || !UInt64::IsUInt64(&args[1].toObject())) { + return ArgumentTypeMismatch(cx, "second ", "UInt64.compare", "a UInt64"); + } + + JSObject* obj1 = &args[0].toObject(); + JSObject* obj2 = &args[1].toObject(); + + uint64_t u1 = Int64Base::GetInt(obj1); + uint64_t u2 = Int64Base::GetInt(obj2); + + if (u1 == u2) + args.rval().setInt32(0); + else if (u1 < u2) + args.rval().setInt32(-1); + else + args.rval().setInt32(1); + + return true; +} + +bool +UInt64::Lo(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ArgumentLengthError(cx, "UInt64.lo", "one", ""); + } + if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "UInt64.lo", "a UInt64"); + } + + JSObject* obj = &args[0].toObject(); + uint64_t u = Int64Base::GetInt(obj); + double d = uint32_t(INT64_LO(u)); + + args.rval().setDouble(d); + return true; +} + +bool +UInt64::Hi(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ArgumentLengthError(cx, "UInt64.hi", "one", ""); + } + if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "UInt64.hi", "a UInt64"); + } + + JSObject* obj = &args[0].toObject(); + uint64_t u = Int64Base::GetInt(obj); + double d = uint32_t(INT64_HI(u)); + + args.rval().setDouble(d); + return true; +} + +bool +UInt64::Join(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) { + return ArgumentLengthError(cx, "UInt64.join", "two", "s"); + } + + uint32_t hi; + uint32_t lo; + if (!jsvalToInteger(cx, args[0], &hi)) + return ArgumentConvError(cx, args[0], "UInt64.join", 0); + if (!jsvalToInteger(cx, args[1], &lo)) + return ArgumentConvError(cx, args[1], "UInt64.join", 1); + + uint64_t u = (uint64_t(hi) << 32) + uint64_t(lo); + + // Get UInt64.prototype from the function's reserved slot. + JSObject* callee = &args.callee(); + + Value slot = js::GetFunctionNativeReserved(callee, SLOT_FN_INT64PROTO); + RootedObject proto(cx, &slot.toObject()); + MOZ_ASSERT(JS_GetClass(proto) == &sUInt64ProtoClass); + + JSObject* result = Int64Base::Construct(cx, proto, u, true); + if (!result) + return false; + + args.rval().setObject(*result); + return true; +} + +} // namespace ctypes +} // namespace js |