/* -*- 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 #include #include #if defined(XP_WIN) #include #endif #if defined(XP_SOLARIS) #include #endif #ifdef HAVE_SSIZE_T #include #endif #if defined(XP_UNIX) #include #elif defined(XP_WIN) #include #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 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 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 struct Property { static bool Fun(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); return JS::CallNonGenericMethod(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::Fun), CTYPESACC_FLAGS), JS_PSG("size", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("ptr", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("prototype", (Property::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::Fun), (Property::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::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::Fun), (Property::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::Fun), CTYPESACC_FLAGS), JS_PSG("length", (Property::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::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::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::Fun), CTYPESACC_FLAGS), JS_PSG("returnType", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("abi", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("isVariadic", (Property::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::Fun), JSPROP_PERMANENT), #if defined(XP_WIN) JS_PSG("winLastError", (Property::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, "<>"); 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 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(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(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(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::is_signed); // Templated helper to convert FromType to TargetType, for the default case // where the trivial POD constructor will do. template 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 { 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 { static MOZ_ALWAYS_INLINE uint64_t Convert(double d) { return d >= 0xffffffffffffffff ? 0x8000000000000000 : uint64_t(d); } }; template<> struct ConvertImpl { static MOZ_ALWAYS_INLINE int64_t Convert(double d) { return d >= 0x7fffffffffffffff ? 0x8000000000000000 : int64_t(d); } }; #endif template static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) { return ConvertImpl::Convert(d); } template 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::digits < numeric_limits::digits) return false; if (numeric_limits::is_signed && !numeric_limits::is_signed) return false; if (!numeric_limits::is_exact && numeric_limits::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 struct IsExactImpl { static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { JS_STATIC_ASSERT(numeric_limits::is_exact); return FromType(j) == i; } }; // Specialization where TargetType is unsigned, FromType is signed. template struct IsExactImpl { static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { JS_STATIC_ASSERT(numeric_limits::is_exact); return i >= 0 && FromType(j) == i; } }; // Specialization where TargetType is signed, FromType is unsigned. template struct IsExactImpl { static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { JS_STATIC_ASSERT(numeric_limits::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 static MOZ_ALWAYS_INLINE bool ConvertExact(FromType i, TargetType* result) { // Require that TargetType is integral, to simplify conversion. JS_STATIC_ASSERT(numeric_limits::is_exact); *result = Convert(i); // See if we can avoid a dynamic check. if (IsAlwaysExact()) return true; // Return 'true' if 'i' is exactly representable in 'TargetType'. return IsExactImpl::is_signed, numeric_limits::is_signed>::Test(i, *result); } // Templated helper to determine if Type 'i' is negative. Default case // where IntegerType is unsigned. template struct IsNegativeImpl { static MOZ_ALWAYS_INLINE bool Test(Type i) { return false; } }; // Specialization where Type is signed. template struct IsNegativeImpl { static MOZ_ALWAYS_INLINE bool Test(Type i) { return i < 0; } }; // Determine whether Type 'i' is negative. template static MOZ_ALWAYS_INLINE bool IsNegative(Type i) { return IsNegativeImpl::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 static bool jsvalToInteger(JSContext* cx, HandleValue val, IntegerType* result) { JS_STATIC_ASSERT(numeric_limits::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()) \ return false; \ *result = IntegerType(*static_cast(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 static bool jsvalToFloat(JSContext* cx, HandleValue val, FloatType* result) { JS_STATIC_ASSERT(!numeric_limits::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()) \ return false; \ *result = FloatType(*static_cast(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 static bool StringToInteger(JSContext* cx, CharT* cp, size_t length, IntegerType* result, bool* overflow) { JS_STATIC_ASSERT(numeric_limits::is_exact); const CharT* end = cp + length; if (cp == end) return false; IntegerType sign = 1; if (cp[0] == '-') { if (!numeric_limits::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 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(cx, linear->latin1Chars(nogc), length, result, overflow) : StringToInteger(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 static bool jsvalToBigInteger(JSContext* cx, HandleValue val, bool allowString, IntegerType* result, bool* overflow) { JS_STATIC_ASSERT(numeric_limits::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(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 static bool jsidToBigInteger(JSContext* cx, jsid val, bool allowString, IntegerType* result) { JS_STATIC_ASSERT(numeric_limits::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(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(double(size)) != size) { return false; } result.setNumber(double(size)); return true; } // Forcefully convert val to IntegerType when explicitly requested. template static bool jsvalToIntegerExplicit(HandleValue val, IntegerType* result) { JS_STATIC_ASSERT(numeric_limits::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(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(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 void IntegerToString(IntegerType i, int radix, mozilla::Vector& result) { JS_STATIC_ASSERT(numeric_limits::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 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(data)); break; #define INT_CASE(name, type, ffiType) \ case TYPE_##name: { \ type value = *static_cast(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::is_signed) { \ value = *static_cast(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(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::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(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(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(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(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(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(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(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(buffer) = nullptr; break; } JS::Rooted 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(buffer) = *static_cast(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(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(buffer); *charBuffer = cx->pod_malloc(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(buffer); *char16Buffer = cx->pod_malloc(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(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(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(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(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(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 target = SharedMem::unshared(buffer); JS::AutoCheckCannotGC nogc; bool isShared; SharedMem src = (bufferShared ? SharedMem::shared(JS_GetSharedArrayBufferData(valObj, &isShared, nogc)) : SharedMem::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 target = SharedMem::unshared(buffer); JS::AutoCheckCannotGC nogc; bool isShared; SharedMem src = SharedMem::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 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(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(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(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(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 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(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(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::is_signed) \ AppendString(result, "ctypes.UInt64(\""); \ else \ AppendString(result, "ctypes.Int64(\""); \ \ IntegerToString(*static_cast(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(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(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(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(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(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 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(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(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(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(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().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().getReservedSlot(SLOT_FIELDINFO); if (slot.isUndefined()) return; FieldInfoHash* fields = static_cast(slot.toPrivate()); fields->trace(trc); break; } case TYPE_function: { // Check if we have a FunctionInfo. slot = obj->as().getReservedSlot(SLOT_FNINFO); if (slot.isUndefined()) return; FunctionInfo* fninfo = static_cast(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.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.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(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(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 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(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(*static_cast(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(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(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(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(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(); 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(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(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(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(CData::GetData(result)); size_t elementSize = CType::GetSize(baseType); *data = static_cast(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 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 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 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 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_(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(); 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(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(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(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 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(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 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(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(CData::GetData(result)); *data = static_cast(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_()); 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(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 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(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(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(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(returnValue.mData); \ *static_cast(returnValue.mData) = static_cast(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(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 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 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 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(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_(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_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(reinterpret_cast(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(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(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(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(result); \ *static_cast(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_(); if (!buffer) { JS_ReportOutOfMemory(cx); return nullptr; } char* data; if (!ownResult) { data = static_cast(source); } else { // Initialize our own buffer. size_t size = CType::GetSize(typeObj); data = dataObj->zone()->pod_malloc(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(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(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(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(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(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(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(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, CData U>): CDataFinalizer * 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(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 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 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 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_(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(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(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(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(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