| /* -*- 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/NumericLimits.h" |
| #include "mozilla/Vector.h" |
| |
| #include <math.h> |
| #include <stdint.h> |
| |
| #if defined(XP_WIN) |
| #include <float.h> |
| #endif |
| |
| #if defined(SOLARIS) |
| #include <ieeefp.h> |
| #endif |
| |
| #ifdef HAVE_SSIZE_T |
| #include <sys/types.h> |
| #endif |
| |
| #if defined(XP_UNIX) |
| #include <errno.h> |
| #elif defined(XP_WIN) |
| #include <windows.h> |
| #endif |
| |
| #include "jscntxt.h" |
| #include "jsexn.h" |
| #include "jsfun.h" |
| #include "jsnum.h" |
| #include "jsprf.h" |
| |
| #include "builtin/TypedObject.h" |
| #include "ctypes/Library.h" |
| #include "gc/Zone.h" |
| #include "js/Vector.h" |
| |
| #include "jsatominlines.h" |
| #include "jsobjinlines.h" |
| |
| using namespace std; |
| using mozilla::NumericLimits; |
| |
| using JS::AutoCheckCannotGC; |
| |
| namespace js { |
| namespace ctypes { |
| |
| template <typename CharT> |
| size_t |
| GetDeflatedUTF8StringLength(JSContext* maybecx, const CharT* chars, |
| size_t nchars) |
| { |
| size_t nbytes; |
| const CharT* end; |
| unsigned c, c2; |
| char buffer[10]; |
| |
| 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); |
| JS_snprintf(buffer, 10, "0x%x", c); |
| JS_ReportErrorFlagsAndNumber(maybecx, JSREPORT_ERROR, GetErrorMessage, |
| nullptr, JSMSG_BAD_SURROGATE_CHAR, buffer); |
| } |
| return (size_t) -1; |
| } |
| |
| template size_t |
| GetDeflatedUTF8StringLength(JSContext* maybecx, const Latin1Char* chars, |
| size_t nchars); |
| |
| template size_t |
| GetDeflatedUTF8StringLength(JSContext* maybecx, const char16_t* chars, |
| size_t nchars); |
| |
| static size_t |
| GetDeflatedUTF8StringLength(JSContext* maybecx, JSLinearString* str) |
| { |
| size_t length = str->length(); |
| |
| JS::AutoCheckCannotGC nogc; |
| return str->hasLatin1Chars() |
| ? GetDeflatedUTF8StringLength(maybecx, str->latin1Chars(nogc), length) |
| : GetDeflatedUTF8StringLength(maybecx, str->twoByteChars(nogc), length); |
| } |
| |
| template <typename CharT> |
| bool |
| DeflateStringToUTF8Buffer(JSContext* maybecx, const CharT* src, size_t srclen, |
| char* dst, size_t* dstlenp) |
| { |
| size_t i, utf8Len; |
| char16_t c, c2; |
| uint32_t v; |
| uint8_t utf8buf[6]; |
| |
| size_t dstlen = *dstlenp; |
| size_t origDstlen = dstlen; |
| |
| while (srclen) { |
| c = *src++; |
| srclen--; |
| if (c >= 0xDC00 && c <= 0xDFFF) |
| goto badSurrogate; |
| if (c < 0xD800 || c > 0xDBFF) { |
| v = c; |
| } else { |
| if (srclen < 1) |
| goto badSurrogate; |
| c2 = *src; |
| if ((c2 < 0xDC00) || (c2 > 0xDFFF)) |
| goto badSurrogate; |
| src++; |
| srclen--; |
| v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; |
| } |
| if (v < 0x0080) { |
| /* no encoding necessary - performance hack */ |
| if (dstlen == 0) |
| goto bufferTooSmall; |
| *dst++ = (char) v; |
| utf8Len = 1; |
| } else { |
| utf8Len = js::OneUcs4ToUtf8Char(utf8buf, v); |
| if (utf8Len > dstlen) |
| goto bufferTooSmall; |
| for (i = 0; i < utf8Len; i++) |
| *dst++ = (char) utf8buf[i]; |
| } |
| dstlen -= utf8Len; |
| } |
| *dstlenp = (origDstlen - dstlen); |
| return true; |
| |
| badSurrogate: |
| *dstlenp = (origDstlen - dstlen); |
| /* Delegate error reporting to the measurement function. */ |
| if (maybecx) |
| GetDeflatedUTF8StringLength(maybecx, src - 1, srclen + 1); |
| return false; |
| |
| bufferTooSmall: |
| *dstlenp = (origDstlen - dstlen); |
| if (maybecx) { |
| js::gc::AutoSuppressGC suppress(maybecx); |
| JS_ReportErrorNumber(maybecx, GetErrorMessage, nullptr, |
| JSMSG_BUFFER_TOO_SMALL); |
| } |
| return false; |
| } |
| |
| template bool |
| DeflateStringToUTF8Buffer(JSContext* maybecx, const Latin1Char* src, size_t srclen, |
| char* dst, size_t* dstlenp); |
| |
| template bool |
| DeflateStringToUTF8Buffer(JSContext* maybecx, const char16_t* src, size_t srclen, |
| char* dst, size_t* dstlenp); |
| |
| static bool |
| DeflateStringToUTF8Buffer(JSContext* maybecx, JSLinearString* str, char* dst, |
| size_t* dstlenp) |
| { |
| size_t length = str->length(); |
| |
| JS::AutoCheckCannotGC nogc; |
| return str->hasLatin1Chars() |
| ? DeflateStringToUTF8Buffer(maybecx, str->latin1Chars(nogc), length, dst, dstlenp) |
| : DeflateStringToUTF8Buffer(maybecx, str->twoByteChars(nogc), length, dst, dstlenp); |
| } |
| |
| /******************************************************************************* |
| ** JSAPI function prototypes |
| *******************************************************************************/ |
| |
| // We use an enclosing struct here out of paranoia about the ability of gcc 4.4 |
| // (and maybe 4.5) to correctly compile this if it were a template function. |
| // See also the comments in dom/workers/Events.cpp (and other adjacent files) by |
| // the |struct Property| there. |
| template<JS::IsAcceptableThis Test, JS::NativeImpl Impl> |
| struct Property |
| { |
| static bool |
| Fun(JSContext* cx, unsigned argc, JS::Value* vp) |
| { |
| JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
| return JS::CallNonGenericMethod<Test, Impl>(cx, args); |
| } |
| }; |
| |
| static bool ConstructAbstract(JSContext* cx, unsigned argc, Value* vp); |
| |
| namespace CType { |
| static bool ConstructData(JSContext* cx, unsigned argc, Value* vp); |
| static bool ConstructBasic(JSContext* cx, HandleObject obj, const CallArgs& args); |
| |
| static void Trace(JSTracer* trc, JSObject* obj); |
| static void Finalize(JSFreeOp* fop, JSObject* obj); |
| |
| bool IsCType(HandleValue v); |
| bool IsCTypeOrProto(HandleValue v); |
| |
| bool PrototypeGetter(JSContext* cx, const JS::CallArgs& args); |
| bool NameGetter(JSContext* cx, const JS::CallArgs& args); |
| bool SizeGetter(JSContext* cx, const JS::CallArgs& args); |
| bool PtrGetter(JSContext* cx, const JS::CallArgs& args); |
| |
| static bool CreateArray(JSContext* cx, unsigned argc, Value* vp); |
| static bool ToString(JSContext* cx, unsigned argc, Value* vp); |
| static bool ToSource(JSContext* cx, unsigned argc, Value* vp); |
| static bool HasInstance(JSContext* cx, HandleObject obj, MutableHandleValue v, bool* bp); |
| |
| |
| /* |
| * Get the global "ctypes" object. |
| * |
| * |obj| must be a CType object. |
| * |
| * This function never returns nullptr. |
| */ |
| static JSObject* GetGlobalCTypes(JSContext* cx, JSObject* obj); |
| |
| } // namespace CType |
| |
| namespace ABI { |
| bool IsABI(JSObject* obj); |
| static bool ToSource(JSContext* cx, unsigned argc, Value* vp); |
| } // namespace ABI |
| |
| namespace PointerType { |
| static bool Create(JSContext* cx, unsigned argc, Value* vp); |
| static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args); |
| |
| bool IsPointerType(HandleValue v); |
| bool IsPointer(HandleValue v); |
| |
| bool TargetTypeGetter(JSContext* cx, const JS::CallArgs& args); |
| bool ContentsGetter(JSContext* cx, const JS::CallArgs& args); |
| bool ContentsSetter(JSContext* cx, const JS::CallArgs& args); |
| |
| static bool IsNull(JSContext* cx, unsigned argc, Value* vp); |
| static bool Increment(JSContext* cx, unsigned argc, Value* vp); |
| static bool Decrement(JSContext* cx, unsigned argc, Value* vp); |
| // The following is not an instance function, since we don't want to expose arbitrary |
| // pointer arithmetic at this moment. |
| static bool OffsetBy(JSContext* cx, const CallArgs& args, int offset); |
| } // namespace PointerType |
| |
| namespace ArrayType { |
| bool IsArrayType(HandleValue v); |
| bool IsArrayOrArrayType(HandleValue v); |
| |
| static bool Create(JSContext* cx, unsigned argc, Value* vp); |
| static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args); |
| |
| bool ElementTypeGetter(JSContext* cx, const JS::CallArgs& args); |
| bool LengthGetter(JSContext* cx, const JS::CallArgs& args); |
| |
| static bool Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp); |
| static bool Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp, |
| ObjectOpResult& result); |
| static bool AddressOfElement(JSContext* cx, unsigned argc, Value* vp); |
| } // namespace ArrayType |
| |
| namespace StructType { |
| bool IsStruct(HandleValue v); |
| |
| static bool Create(JSContext* cx, unsigned argc, Value* vp); |
| static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args); |
| |
| bool FieldsArrayGetter(JSContext* cx, const JS::CallArgs& args); |
| |
| enum { |
| SLOT_FIELDNAME |
| }; |
| |
| static bool FieldGetter(JSContext* cx, unsigned argc, Value* vp); |
| static bool FieldSetter(JSContext* cx, unsigned argc, Value* vp); |
| static bool AddressOfField(JSContext* cx, unsigned argc, Value* vp); |
| static bool Define(JSContext* cx, unsigned argc, Value* vp); |
| } // namespace StructType |
| |
| namespace FunctionType { |
| static bool Create(JSContext* cx, unsigned argc, Value* vp); |
| static bool ConstructData(JSContext* cx, HandleObject typeObj, |
| HandleObject dataObj, HandleObject fnObj, HandleObject thisObj, Value 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 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); |
| |
| static JSObject* GetCData(JSContext* cx, JSObject* obj); |
| } // 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 JSClass sCTypeProtoClass = { |
| "CType", |
| JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS), |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, nullptr, |
| ConstructAbstract, nullptr, ConstructAbstract |
| }; |
| |
| // 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 JSClass sCTypeClass = { |
| "CType", |
| JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS), |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, CType::Finalize, |
| CType::ConstructData, CType::HasInstance, CType::ConstructData, |
| CType::Trace |
| }; |
| |
| static const JSClass sCDataClass = { |
| "CData", |
| JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS), |
| nullptr, nullptr, ArrayType::Getter, ArrayType::Setter, |
| nullptr, nullptr, nullptr, CData::Finalize, |
| FunctionType::Call, nullptr, FunctionType::Call |
| }; |
| |
| static const JSClass sCClosureClass = { |
| "CClosure", |
| JSCLASS_HAS_RESERVED_SLOTS(CCLOSURE_SLOTS), |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, CClosure::Finalize, |
| nullptr, nullptr, nullptr, CClosure::Trace |
| }; |
| |
| /* |
| * 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 JSClass sCDataFinalizerClass = { |
| "CDataFinalizer", |
| JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(CDATAFINALIZER_SLOTS), |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, CDataFinalizer::Finalize |
| }; |
| |
| |
| #define CTYPESFN_FLAGS \ |
| (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) |
| |
| #define CTYPESCTOR_FLAGS \ |
| (CTYPESFN_FLAGS | JSFUN_CONSTRUCTOR) |
| |
| #define CTYPESACC_FLAGS \ |
| (JSPROP_ENUMERATE | JSPROP_PERMANENT) |
| |
| #define CABIFN_FLAGS \ |
| (JSPROP_READONLY | JSPROP_PERMANENT) |
| |
| #define CDATAFN_FLAGS \ |
| (JSPROP_READONLY | JSPROP_PERMANENT) |
| |
| #define CDATAFINALIZERFN_FLAGS \ |
| (JSPROP_READONLY | JSPROP_PERMANENT) |
| |
| static const JSPropertySpec sCTypeProps[] = { |
| JS_PSG("name", |
| (Property<CType::IsCType, CType::NameGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("size", |
| (Property<CType::IsCType, CType::SizeGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("ptr", |
| (Property<CType::IsCType, CType::PtrGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("prototype", |
| (Property<CType::IsCTypeOrProto, CType::PrototypeGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sCTypeFunctions[] = { |
| JS_FN("array", CType::CreateArray, 0, CTYPESFN_FLAGS), |
| JS_FN("toString", CType::ToString, 0, CTYPESFN_FLAGS), |
| JS_FN("toSource", CType::ToSource, 0, CTYPESFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sCABIFunctions[] = { |
| JS_FN("toSource", ABI::ToSource, 0, CABIFN_FLAGS), |
| JS_FN("toString", ABI::ToSource, 0, CABIFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSPropertySpec sCDataProps[] = { |
| JS_PSGS("value", |
| (Property<CData::IsCData, CData::ValueGetter>::Fun), |
| (Property<CData::IsCData, CData::ValueSetter>::Fun), |
| JSPROP_PERMANENT), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sCDataFunctions[] = { |
| JS_FN("address", CData::Address, 0, CDATAFN_FLAGS), |
| JS_FN("readString", CData::ReadString, 0, CDATAFN_FLAGS), |
| JS_FN("readStringReplaceMalformed", CData::ReadStringReplaceMalformed, 0, CDATAFN_FLAGS), |
| JS_FN("toSource", CData::ToSource, 0, CDATAFN_FLAGS), |
| JS_FN("toString", CData::ToSource, 0, CDATAFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sCDataFinalizerFunctions[] = { |
| JS_FN("dispose", CDataFinalizer::Methods::Dispose, 0, CDATAFINALIZERFN_FLAGS), |
| JS_FN("forget", CDataFinalizer::Methods::Forget, 0, CDATAFINALIZERFN_FLAGS), |
| JS_FN("readString",CData::ReadString, 0, CDATAFINALIZERFN_FLAGS), |
| JS_FN("toString", CDataFinalizer::Methods::ToString, 0, CDATAFINALIZERFN_FLAGS), |
| JS_FN("toSource", CDataFinalizer::Methods::ToSource, 0, CDATAFINALIZERFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sPointerFunction = |
| JS_FN("PointerType", PointerType::Create, 1, CTYPESCTOR_FLAGS); |
| |
| static const JSPropertySpec sPointerProps[] = { |
| JS_PSG("targetType", |
| (Property<PointerType::IsPointerType, PointerType::TargetTypeGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sPointerInstanceFunctions[] = { |
| JS_FN("isNull", PointerType::IsNull, 0, CTYPESFN_FLAGS), |
| JS_FN("increment", PointerType::Increment, 0, CTYPESFN_FLAGS), |
| JS_FN("decrement", PointerType::Decrement, 0, CTYPESFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSPropertySpec sPointerInstanceProps[] = { |
| JS_PSGS("contents", |
| (Property<PointerType::IsPointer, PointerType::ContentsGetter>::Fun), |
| (Property<PointerType::IsPointer, PointerType::ContentsSetter>::Fun), |
| JSPROP_PERMANENT), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sArrayFunction = |
| JS_FN("ArrayType", ArrayType::Create, 1, CTYPESCTOR_FLAGS); |
| |
| static const JSPropertySpec sArrayProps[] = { |
| JS_PSG("elementType", |
| (Property<ArrayType::IsArrayType, ArrayType::ElementTypeGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("length", |
| (Property<ArrayType::IsArrayOrArrayType, ArrayType::LengthGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sArrayInstanceFunctions[] = { |
| JS_FN("addressOfElement", ArrayType::AddressOfElement, 1, CDATAFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSPropertySpec sArrayInstanceProps[] = { |
| JS_PSG("length", |
| (Property<ArrayType::IsArrayOrArrayType, ArrayType::LengthGetter>::Fun), |
| JSPROP_PERMANENT), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sStructFunction = |
| JS_FN("StructType", StructType::Create, 2, CTYPESCTOR_FLAGS); |
| |
| static const JSPropertySpec sStructProps[] = { |
| JS_PSG("fields", |
| (Property<StructType::IsStruct, StructType::FieldsArrayGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sStructFunctions[] = { |
| JS_FN("define", StructType::Define, 1, CDATAFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sStructInstanceFunctions[] = { |
| JS_FN("addressOfField", StructType::AddressOfField, 1, CDATAFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sFunctionFunction = |
| JS_FN("FunctionType", FunctionType::Create, 2, CTYPESCTOR_FLAGS); |
| |
| static const JSPropertySpec sFunctionProps[] = { |
| JS_PSG("argTypes", |
| (Property<FunctionType::IsFunctionType, FunctionType::ArgTypesGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("returnType", |
| (Property<FunctionType::IsFunctionType, FunctionType::ReturnTypeGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("abi", |
| (Property<FunctionType::IsFunctionType, FunctionType::ABIGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PSG("isVariadic", |
| (Property<FunctionType::IsFunctionType, FunctionType::IsVariadicGetter>::Fun), |
| CTYPESACC_FLAGS), |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sFunctionInstanceFunctions[] = { |
| JS_FN("call", js::fun_call, 1, CDATAFN_FLAGS), |
| JS_FN("apply", js::fun_apply, 2, CDATAFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSClass sInt64ProtoClass = { |
| "Int64", |
| 0 |
| }; |
| |
| static const JSClass sUInt64ProtoClass = { |
| "UInt64", |
| 0 |
| }; |
| |
| static const JSClass sInt64Class = { |
| "Int64", |
| JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS), |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, Int64Base::Finalize |
| }; |
| |
| static const JSClass sUInt64Class = { |
| "UInt64", |
| JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS), |
| nullptr, nullptr, nullptr, nullptr, |
| nullptr, nullptr, nullptr, Int64Base::Finalize |
| }; |
| |
| static const JSFunctionSpec sInt64StaticFunctions[] = { |
| JS_FN("compare", Int64::Compare, 2, CTYPESFN_FLAGS), |
| JS_FN("lo", Int64::Lo, 1, CTYPESFN_FLAGS), |
| JS_FN("hi", Int64::Hi, 1, CTYPESFN_FLAGS), |
| // "join" is defined specially; see InitInt64Class. |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sUInt64StaticFunctions[] = { |
| JS_FN("compare", UInt64::Compare, 2, CTYPESFN_FLAGS), |
| JS_FN("lo", UInt64::Lo, 1, CTYPESFN_FLAGS), |
| JS_FN("hi", UInt64::Hi, 1, CTYPESFN_FLAGS), |
| // "join" is defined specially; see InitInt64Class. |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sInt64Functions[] = { |
| JS_FN("toString", Int64::ToString, 0, CTYPESFN_FLAGS), |
| JS_FN("toSource", Int64::ToSource, 0, CTYPESFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSFunctionSpec sUInt64Functions[] = { |
| JS_FN("toString", UInt64::ToString, 0, CTYPESFN_FLAGS), |
| JS_FN("toSource", UInt64::ToSource, 0, CTYPESFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static const JSPropertySpec sModuleProps[] = { |
| JS_PSG("errno", |
| (Property<IsCTypesGlobal, CData::ErrnoGetter>::Fun), |
| JSPROP_PERMANENT), |
| #if defined(XP_WIN) |
| JS_PSG("winLastError", |
| (Property<IsCTypesGlobal, CData::LastErrorGetter>::Fun), |
| JSPROP_PERMANENT), |
| #endif // defined(XP_WIN) |
| JS_PS_END |
| }; |
| |
| static const JSFunctionSpec sModuleFunctions[] = { |
| JS_FN("CDataFinalizer", CDataFinalizer::Construct, 2, CTYPESFN_FLAGS), |
| JS_FN("open", Library::Open, 1, CTYPESFN_FLAGS), |
| JS_FN("cast", CData::Cast, 2, CTYPESFN_FLAGS), |
| JS_FN("getRuntime", CData::GetRuntime, 1, CTYPESFN_FLAGS), |
| JS_FN("libraryName", Library::Name, 1, CTYPESFN_FLAGS), |
| JS_FS_END |
| }; |
| |
| static MOZ_ALWAYS_INLINE JSString* |
| NewUCString(JSContext* cx, const AutoString& from) |
| { |
| return JS_NewUCStringCopyN(cx, from.begin(), from.length()); |
| } |
| |
| /* |
| * Return a size rounded up to a multiple of a power of two. |
| * |
| * Note: |align| must be a power of 2. |
| */ |
| static MOZ_ALWAYS_INLINE size_t |
| Align(size_t val, size_t align) |
| { |
| // Ensure that align is a power of two. |
| MOZ_ASSERT(align != 0 && (align & (align - 1)) == 0); |
| return ((val - 1) | (align - 1)) + 1; |
| } |
| |
| static ABICode |
| GetABICode(JSObject* obj) |
| { |
| // make sure we have an object representing a CABI class, |
| // and extract the enumerated class type from the reserved slot. |
| if (JS_GetClass(obj) != &sCABIClass) |
| return INVALID_ABI; |
| |
| Value result = JS_GetReservedSlot(obj, SLOT_ABICODE); |
| return ABICode(result.toInt32()); |
| } |
| |
| static const JSErrorFormatString ErrorFormatString[CTYPESERR_LIMIT] = { |
| #define MSG_DEF(name, count, exception, format) \ |
| { format, count, exception } , |
| #include "ctypes/ctypes.msg" |
| #undef MSG_DEF |
| }; |
| |
| static const JSErrorFormatString* |
| GetErrorMessage(void* userRef, const unsigned errorNumber) |
| { |
| if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT) |
| return &ErrorFormatString[errorNumber]; |
| return nullptr; |
| } |
| |
| static const char* |
| EncodeLatin1(JSContext* cx, AutoString& str, JSAutoByteString& bytes) |
| { |
| return bytes.encodeLatin1(cx, NewUCString(cx, str)); |
| } |
| |
| static const char* |
| CTypesToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes) |
| { |
| if (val.isObject() && |
| (CType::IsCType(&val.toObject()) || CData::IsCData(&val.toObject()))) { |
| RootedString str(cx, JS_ValueToSource(cx, val)); |
| return bytes.encodeLatin1(cx, str); |
| } |
| return ValueToSourceForError(cx, val, bytes); |
| } |
| |
| static void |
| BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj, |
| HandleString nameStr, unsigned ptrCount, |
| AutoString& source); |
| |
| static void |
| BuildCStyleTypeSource(JSContext* cx, JSObject* typeObj_, AutoString& source) |
| { |
| RootedObject typeObj(cx, typeObj_); |
| |
| MOZ_ASSERT(CType::IsCType(typeObj)); |
| |
| switch (CType::GetTypeCode(typeObj)) { |
| #define BUILD_SOURCE(name, fromType, ffiType) \ |
| case TYPE_##name: \ |
| AppendString(source, #name); \ |
| break; |
| CTYPES_FOR_EACH_TYPE(BUILD_SOURCE) |
| #undef BUILD_SOURCE |
| case TYPE_void_t: |
| AppendString(source, "void"); |
| break; |
| case TYPE_pointer: { |
| unsigned ptrCount = 0; |
| TypeCode type; |
| RootedObject baseTypeObj(cx, typeObj); |
| do { |
| baseTypeObj = PointerType::GetBaseType(baseTypeObj); |
| ptrCount++; |
| type = CType::GetTypeCode(baseTypeObj); |
| } while (type == TYPE_pointer || type == TYPE_array); |
| if (type == TYPE_function) { |
| BuildCStyleFunctionTypeSource(cx, baseTypeObj, nullptr, ptrCount, |
| source); |
| break; |
| } |
| BuildCStyleTypeSource(cx, baseTypeObj, source); |
| AppendChars(source, '*', ptrCount); |
| break; |
| } |
| case TYPE_struct: { |
| RootedString name(cx, CType::GetName(cx, typeObj)); |
| AppendString(source, "struct "); |
| AppendString(source, name); |
| break; |
| } |
| case TYPE_function: |
| BuildCStyleFunctionTypeSource(cx, typeObj, nullptr, 0, source); |
| break; |
| case TYPE_array: |
| MOZ_CRASH("TYPE_array shouldn't appear in function type"); |
| } |
| } |
| |
| static void |
| BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj, |
| HandleString nameStr, unsigned ptrCount, |
| AutoString& source) |
| { |
| MOZ_ASSERT(CType::IsCType(typeObj)); |
| |
| FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); |
| BuildCStyleTypeSource(cx, fninfo->mReturnType, source); |
| AppendString(source, " "); |
| if (nameStr) { |
| MOZ_ASSERT(ptrCount == 0); |
| AppendString(source, nameStr); |
| } else if (ptrCount) { |
| AppendString(source, "("); |
| AppendChars(source, '*', ptrCount); |
| AppendString(source, ")"); |
| } |
| AppendString(source, "("); |
| if (fninfo->mArgTypes.length() > 0) { |
| for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { |
| BuildCStyleTypeSource(cx, fninfo->mArgTypes[i], source); |
| if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) { |
| AppendString(source, ", "); |
| } |
| } |
| if (fninfo->mIsVariadic) { |
| AppendString(source, "..."); |
| } |
| } |
| AppendString(source, ")"); |
| } |
| |
| static void |
| BuildFunctionTypeSource(JSContext* cx, HandleObject funObj, AutoString& source) |
| { |
| MOZ_ASSERT(CData::IsCData(funObj) || CType::IsCType(funObj)); |
| |
| if (CData::IsCData(funObj)) { |
| Value slot = JS_GetReservedSlot(funObj, SLOT_REFERENT); |
| if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { |
| slot = JS_GetReservedSlot(funObj, SLOT_FUNNAME); |
| MOZ_ASSERT(!slot.isUndefined()); |
| RootedObject typeObj(cx, CData::GetCType(funObj)); |
| RootedObject baseTypeObj(cx, PointerType::GetBaseType(typeObj)); |
| RootedString nameStr(cx, slot.toString()); |
| BuildCStyleFunctionTypeSource(cx, baseTypeObj, nameStr, 0, source); |
| return; |
| } |
| } |
| |
| RootedValue funVal(cx, ObjectValue(*funObj)); |
| RootedString funcStr(cx, JS_ValueToSource(cx, funVal)); |
| if (!funcStr) { |
| JS_ClearPendingException(cx); |
| AppendString(source, "<<error converting function to string>>"); |
| return; |
| } |
| AppendString(source, funcStr); |
| } |
| |
| enum class ConversionType { |
| Argument = 0, |
| Construct, |
| Finalizer, |
| Return, |
| Setter |
| }; |
| |
| static void |
| BuildConversionPosition(JSContext* cx, ConversionType convType, |
| HandleObject funObj, unsigned argIndex, |
| AutoString& source) |
| { |
| switch (convType) { |
| case ConversionType::Argument: { |
| MOZ_ASSERT(funObj); |
| |
| AppendString(source, " at argument "); |
| AppendUInt(source, argIndex + 1); |
| AppendString(source, " of "); |
| BuildFunctionTypeSource(cx, funObj, source); |
| break; |
| } |
| case ConversionType::Finalizer: |
| MOZ_ASSERT(funObj); |
| |
| AppendString(source, " at argument 1 of "); |
| BuildFunctionTypeSource(cx, funObj, source); |
| break; |
| case ConversionType::Return: |
| MOZ_ASSERT(funObj); |
| |
| AppendString(source, " at the return value of "); |
| BuildFunctionTypeSource(cx, funObj, source); |
| break; |
| default: |
| MOZ_ASSERT(!funObj); |
| break; |
| } |
| } |
| |
| static JSFlatString* |
| GetFieldName(HandleObject structObj, unsigned fieldIndex) |
| { |
| const FieldInfoHash* fields = StructType::GetFieldInfo(structObj); |
| for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { |
| if (r.front().value().mIndex == fieldIndex) { |
| return (&r.front())->key(); |
| } |
| } |
| return nullptr; |
| } |
| |
| static void |
| BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort, |
| AutoString& result); |
| |
| static bool |
| ConvError(JSContext* cx, const char* expectedStr, HandleValue actual, |
| ConversionType convType, |
| HandleObject funObj = nullptr, unsigned argIndex = 0, |
| HandleObject arrObj = nullptr, unsigned arrIndex = 0) |
| { |
| JSAutoByteString valBytes; |
| const char* valStr = CTypesToSourceForError(cx, actual, valBytes); |
| if (!valStr) |
| return false; |
| |
| if (arrObj) { |
| MOZ_ASSERT(CType::IsCType(arrObj)); |
| |
| switch (CType::GetTypeCode(arrObj)) { |
| case TYPE_array: { |
| MOZ_ASSERT(!funObj); |
| |
| char indexStr[16]; |
| JS_snprintf(indexStr, 16, "%u", arrIndex); |
| |
| AutoString arrSource; |
| JSAutoByteString arrBytes; |
| BuildTypeSource(cx, arrObj, true, arrSource); |
| const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); |
| if (!arrStr) |
| return false; |
| |
| JS_ReportErrorNumber(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_ReportErrorNumber(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]; |
| JS_snprintf(indexStr, 16, "%u", argIndex + 1); |
| |
| AutoString funSource; |
| JSAutoByteString funBytes; |
| BuildFunctionTypeSource(cx, funObj, funSource); |
| const char* funStr = EncodeLatin1(cx, funSource, funBytes); |
| if (!funStr) |
| return false; |
| |
| JS_ReportErrorNumber(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_ReportErrorNumber(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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_CONV_ERROR_RET, valStr, funStr); |
| break; |
| } |
| case ConversionType::Setter: |
| case ConversionType::Construct: |
| MOZ_ASSERT(!funObj); |
| |
| JS_ReportErrorNumber(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]; |
| JS_snprintf(indexStr, 16, "%u", argIndex + 1); |
| |
| JS_ReportErrorNumber(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_ReportErrorNumber(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]; |
| JS_snprintf(expectedLengthStr, 16, "%u", expectedLength); |
| char actualLengthStr[16]; |
| JS_snprintf(actualLengthStr, 16, "%u", actualLength); |
| |
| AutoString arrSource; |
| JSAutoByteString arrBytes; |
| BuildTypeSource(cx, arrObj, true, arrSource); |
| const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); |
| if (!arrStr) |
| return false; |
| |
| JS_ReportErrorNumber(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]; |
| JS_snprintf(expectedLengthStr, 16, "%u", expectedLength); |
| char actualLengthStr[16]; |
| JS_snprintf(actualLengthStr, 16, "%u", actualLength); |
| |
| AutoString arrSource; |
| JSAutoByteString arrBytes; |
| BuildTypeSource(cx, arrObj, true, arrSource); |
| const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); |
| if (!arrStr) |
| return false; |
| |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_ARRAY_OVERFLOW, |
| valStr, arrStr, expectedLengthStr, actualLengthStr); |
| return false; |
| } |
| |
| static bool |
| ArgumentRangeMismatch(JSContext* cx, const char* arg, const char* func, |
| const char* range) |
| { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_ARG_RANGE_MISMATCH, arg, func, range); |
| return false; |
| } |
| |
| static bool |
| ArgumentTypeMismatch(JSContext* cx, const char* arg, const char* func, |
| const char* type) |
| { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_ARG_TYPE_MISMATCH, arg, func, type); |
| 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_ReportErrorNumber(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]; |
| JS_snprintf(expectedCountStr, 16, "%u", expectedCount); |
| char actualCountStr[16]; |
| JS_snprintf(actualCountStr, 16, "%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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_FIELD_MISMATCH, |
| valStr, structStr, expectedCountStr, actualCountStr, |
| posStr); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_FIN_SIZE_ERROR, funStr, valStr); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_NON_PRIMITIVE, typeStr); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_PROP_NONSTRING, propStr, valStr, posStr); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| CTYPESMSG_TYPE_ERROR, expected, src); |
| 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, "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, \ |
| CType::DefineBuiltin(cx, ctypesObj, #name, CTypeProto, CDataProto, #name, \ |
| TYPE_##name, Int32Value(sizeof(type)), \ |
| Int32Value(ffiType.alignment), &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::UndefinedValue(), JS::UndefinedValue(), |
| &ffi_type_void)); |
| if (!typeObj) |
| return false; |
| |
| typeObj = PointerType::CreateInternal(cx, typeObj); |
| if (!typeObj) |
| return false; |
| if (!JS_DefineProperty(cx, ctypesObj, "voidptr_t", typeObj, |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| IsCTypesGlobal(JSObject* obj) |
| { |
| return JS_GetClass(obj) == &sCTypesGlobalClass; |
| } |
| |
| bool |
| IsCTypesGlobal(HandleValue v) |
| { |
| return v.isObject() && IsCTypesGlobal(&v.toObject()); |
| } |
| |
| // Get the JSCTypesCallbacks struct from the 'ctypes' object 'obj'. |
| const JSCTypesCallbacks* |
| GetCallbacks(JSObject* obj) |
| { |
| MOZ_ASSERT(IsCTypesGlobal(obj)); |
| |
| Value result = JS_GetReservedSlot(obj, SLOT_CALLBACKS); |
| if (result.isUndefined()) |
| return nullptr; |
| |
| return static_cast<const JSCTypesCallbacks*>(result.toPrivate()); |
| } |
| |
| // Utility function to access a property of an object as an object |
| // returns false and sets the error if the property does not exist |
| // or is not an object |
| static bool GetObjectProperty(JSContext* cx, HandleObject obj, |
| const char* property, MutableHandleObject result) |
| { |
| RootedValue val(cx); |
| if (!JS_GetProperty(cx, obj, property, &val)) { |
| return false; |
| } |
| |
| if (val.isPrimitive()) { |
| JS_ReportError(cx, "missing or non-object field"); |
| return false; |
| } |
| |
| result.set(val.toObjectOrNull()); |
| return true; |
| } |
| |
| } /* namespace ctypes */ |
| } /* namespace js */ |
| |
| using namespace js; |
| using namespace js::ctypes; |
| |
| JS_PUBLIC_API(bool) |
| JS_InitCTypesClass(JSContext* cx, HandleObject global) |
| { |
| // attach ctypes property to global object |
| RootedObject ctypes(cx, JS_NewObject(cx, &sCTypesGlobalClass)); |
| if (!ctypes) |
| return false; |
| |
| if (!JS_DefineProperty(cx, global, "ctypes", ctypes, |
| JSPROP_READONLY | JSPROP_PERMANENT, |
| JS_STUBGETTER, JS_STUBSETTER)){ |
| return false; |
| } |
| |
| if (!InitTypeClasses(cx, ctypes)) |
| return false; |
| |
| // attach API functions and properties |
| if (!JS_DefineFunctions(cx, ctypes, sModuleFunctions) || |
| !JS_DefineProperties(cx, ctypes, sModuleProps)) |
| return false; |
| |
| // Set up ctypes.CDataFinalizer.prototype. |
| RootedObject ctor(cx); |
| if (!GetObjectProperty(cx, ctypes, "CDataFinalizer", &ctor)) |
| return false; |
| |
| RootedObject prototype(cx, JS_NewObject(cx, &sCDataFinalizerProtoClass)); |
| if (!prototype) |
| return false; |
| |
| if (!JS_DefineFunctions(cx, prototype, sCDataFinalizerFunctions)) |
| return false; |
| |
| if (!JS_DefineProperty(cx, ctor, "prototype", prototype, |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) |
| return false; |
| |
| if (!JS_DefineProperty(cx, prototype, "constructor", ctor, |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) |
| return false; |
| |
| |
| // Seal the ctypes object, to prevent modification. |
| return JS_FreezeObject(cx, ctypes); |
| } |
| |
| JS_PUBLIC_API(void) |
| JS_SetCTypesCallbacks(JSObject* ctypesObj, const JSCTypesCallbacks* callbacks) |
| { |
| MOZ_ASSERT(callbacks); |
| MOZ_ASSERT(IsCTypesGlobal(ctypesObj)); |
| |
| // Set the callbacks on a reserved slot. |
| JS_SetReservedSlot(ctypesObj, SLOT_CALLBACKS, |
| PrivateValue(const_cast<JSCTypesCallbacks*>(callbacks))); |
| } |
| |
| namespace js { |
| |
| JS_FRIEND_API(size_t) |
| SizeOfDataIfCDataObject(mozilla::MallocSizeOf mallocSizeOf, JSObject* obj) |
| { |
| if (!CData::IsCData(obj)) |
| return 0; |
| |
| size_t n = 0; |
| Value slot = JS_GetReservedSlot(obj, ctypes::SLOT_OWNS); |
| if (!slot.isUndefined()) { |
| bool owns = slot.toBoolean(); |
| slot = JS_GetReservedSlot(obj, ctypes::SLOT_DATA); |
| if (!slot.isUndefined()) { |
| char** buffer = static_cast<char**>(slot.toPrivate()); |
| n += mallocSizeOf(buffer); |
| if (owns) |
| n += mallocSizeOf(*buffer); |
| } |
| } |
| return n; |
| } |
| |
| namespace ctypes { |
| |
| /******************************************************************************* |
| ** Type conversion functions |
| *******************************************************************************/ |
| |
| // Enforce some sanity checks on type widths and properties. |
| // Where the architecture is 64-bit, make sure it's LP64 or LLP64. (ctypes.int |
| // autoconverts to a primitive JS number; to support ILP64 architectures, it |
| // would need to autoconvert to an Int64 object instead. Therefore we enforce |
| // this invariant here.) |
| JS_STATIC_ASSERT(sizeof(bool) == 1 || sizeof(bool) == 4); |
| JS_STATIC_ASSERT(sizeof(char) == 1); |
| JS_STATIC_ASSERT(sizeof(short) == 2); |
| JS_STATIC_ASSERT(sizeof(int) == 4); |
| JS_STATIC_ASSERT(sizeof(unsigned) == 4); |
| JS_STATIC_ASSERT(sizeof(long) == 4 || sizeof(long) == 8); |
| JS_STATIC_ASSERT(sizeof(long long) == 8); |
| JS_STATIC_ASSERT(sizeof(size_t) == sizeof(uintptr_t)); |
| JS_STATIC_ASSERT(sizeof(float) == 4); |
| JS_STATIC_ASSERT(sizeof(PRFuncPtr) == sizeof(void*)); |
| JS_STATIC_ASSERT(NumericLimits<double>::is_signed); |
| |
| // Templated helper to convert FromType to TargetType, for the default case |
| // where the trivial POD constructor will do. |
| template<class TargetType, class FromType> |
| struct ConvertImpl { |
| static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) { |
| return TargetType(d); |
| } |
| }; |
| |
| #ifdef _MSC_VER |
| // MSVC can't perform double to unsigned __int64 conversion when the |
| // double is greater than 2^63 - 1. Help it along a little. |
| template<> |
| struct ConvertImpl<uint64_t, double> { |
| static MOZ_ALWAYS_INLINE uint64_t Convert(double d) { |
| return d > 0x7fffffffffffffffui64 ? |
| uint64_t(d - 0x8000000000000000ui64) + 0x8000000000000000ui64 : |
| uint64_t(d); |
| } |
| }; |
| #endif |
| |
| // C++ doesn't guarantee that exact values are the only ones that will |
| // round-trip. In fact, on some platforms, including SPARC, there are pairs of |
| // values, a uint64_t and a double, such that neither value is exactly |
| // representable in the other type, but they cast to each other. |
| #if defined(SPARC) || defined(__powerpc__) |
| // Simulate x86 overflow behavior |
| template<> |
| struct ConvertImpl<uint64_t, double> { |
| static MOZ_ALWAYS_INLINE uint64_t Convert(double d) { |
| return d >= 0xffffffffffffffff ? |
| 0x8000000000000000 : uint64_t(d); |
| } |
| }; |
| |
| template<> |
| struct ConvertImpl<int64_t, double> { |
| static MOZ_ALWAYS_INLINE int64_t Convert(double d) { |
| return d >= 0x7fffffffffffffff ? |
| 0x8000000000000000 : int64_t(d); |
| } |
| }; |
| #endif |
| |
| template<class TargetType, class FromType> |
| static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) |
| { |
| return ConvertImpl<TargetType, FromType>::Convert(d); |
| } |
| |
| template<class TargetType, class FromType> |
| static MOZ_ALWAYS_INLINE bool IsAlwaysExact() |
| { |
| // Return 'true' if TargetType can always exactly represent FromType. |
| // This means that: |
| // 1) TargetType must be the same or more bits wide as FromType. For integers |
| // represented in 'n' bits, unsigned variants will have 'n' digits while |
| // signed will have 'n - 1'. For floating point types, 'digits' is the |
| // mantissa width. |
| // 2) If FromType is signed, TargetType must also be signed. (Floating point |
| // types are always signed.) |
| // 3) If TargetType is an exact integral type, FromType must be also. |
| if (NumericLimits<TargetType>::digits < NumericLimits<FromType>::digits) |
| return false; |
| |
| if (NumericLimits<FromType>::is_signed && |
| !NumericLimits<TargetType>::is_signed) |
| return false; |
| |
| if (!NumericLimits<FromType>::is_exact && |
| NumericLimits<TargetType>::is_exact) |
| return false; |
| |
| return true; |
| } |
| |
| // Templated helper to determine if FromType 'i' converts losslessly to |
| // TargetType 'j'. Default case where both types are the same signedness. |
| template<class TargetType, class FromType, bool TargetSigned, bool FromSigned> |
| struct IsExactImpl { |
| static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { |
| JS_STATIC_ASSERT(NumericLimits<TargetType>::is_exact); |
| return FromType(j) == i; |
| } |
| }; |
| |
| // Specialization where TargetType is unsigned, FromType is signed. |
| template<class TargetType, class FromType> |
| struct IsExactImpl<TargetType, FromType, false, true> { |
| static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { |
| JS_STATIC_ASSERT(NumericLimits<TargetType>::is_exact); |
| return i >= 0 && FromType(j) == i; |
| } |
| }; |
| |
| // Specialization where TargetType is signed, FromType is unsigned. |
| template<class TargetType, class FromType> |
| struct IsExactImpl<TargetType, FromType, true, false> { |
| static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { |
| JS_STATIC_ASSERT(NumericLimits<TargetType>::is_exact); |
| return TargetType(i) >= 0 && FromType(j) == i; |
| } |
| }; |
| |
| // Convert FromType 'i' to TargetType 'result', returning true iff 'result' |
| // is an exact representation of 'i'. |
| template<class TargetType, class FromType> |
| static MOZ_ALWAYS_INLINE bool ConvertExact(FromType i, TargetType* result) |
| { |
| // Require that TargetType is integral, to simplify conversion. |
| JS_STATIC_ASSERT(NumericLimits<TargetType>::is_exact); |
| |
| *result = Convert<TargetType>(i); |
| |
| // See if we can avoid a dynamic check. |
| if (IsAlwaysExact<TargetType, FromType>()) |
| return true; |
| |
| // Return 'true' if 'i' is exactly representable in 'TargetType'. |
| return IsExactImpl<TargetType, |
| FromType, |
| NumericLimits<TargetType>::is_signed, |
| NumericLimits<FromType>::is_signed>::Test(i, *result); |
| } |
| |
| // Templated helper to determine if Type 'i' is negative. Default case |
| // where IntegerType is unsigned. |
| template<class Type, bool IsSigned> |
| struct IsNegativeImpl { |
| static MOZ_ALWAYS_INLINE bool Test(Type i) { |
| return false; |
| } |
| }; |
| |
| // Specialization where Type is signed. |
| template<class Type> |
| struct IsNegativeImpl<Type, true> { |
| static MOZ_ALWAYS_INLINE bool Test(Type i) { |
| return i < 0; |
| } |
| }; |
| |
| // Determine whether Type 'i' is negative. |
| template<class Type> |
| static MOZ_ALWAYS_INLINE bool IsNegative(Type i) |
| { |
| return IsNegativeImpl<Type, NumericLimits<Type>::is_signed>::Test(i); |
| } |
| |
| // Implicitly convert val to bool, allowing bool, int, and double |
| // arguments numerically equal to 0 or 1. |
| static bool |
| jsvalToBool(JSContext* cx, Value val, bool* result) |
| { |
| if (val.isBoolean()) { |
| *result = val.toBoolean(); |
| return true; |
| } |
| if (val.isInt32()) { |
| int32_t i = val.toInt32(); |
| *result = i != 0; |
| return i == 0 || i == 1; |
| } |
| if (val.isDouble()) { |
| double d = val.toDouble(); |
| *result = d != 0; |
| // Allow -0. |
| return d == 1 || d == 0; |
| } |
| // Don't silently convert null to bool. It's probably a mistake. |
| return false; |
| } |
| |
| // Implicitly convert val to IntegerType, allowing bool, int, double, |
| // Int64, UInt64, and CData integer types 't' where all values of 't' are |
| // representable by IntegerType. |
| template<class IntegerType> |
| static bool |
| jsvalToInteger(JSContext* cx, Value val, IntegerType* result) |
| { |
| JS_STATIC_ASSERT(NumericLimits<IntegerType>::is_exact); |
| |
| if (val.isInt32()) { |
| // Make sure the integer fits in the alotted precision, and has the right |
| // sign. |
| int32_t i = val.toInt32(); |
| return ConvertExact(i, result); |
| } |
| if (val.isDouble()) { |
| // Don't silently lose bits here -- check that val really is an |
| // integer value, and has the right sign. |
| double d = val.toDouble(); |
| return ConvertExact(d, result); |
| } |
| if (val.isObject()) { |
| JSObject* obj = &val.toObject(); |
| if (CData::IsCData(obj)) { |
| JSObject* typeObj = CData::GetCType(obj); |
| void* data = CData::GetData(obj); |
| |
| // Check whether the source type is always representable, with exact |
| // precision, by the target type. If it is, convert the value. |
| switch (CType::GetTypeCode(typeObj)) { |
| #define INTEGER_CASE(name, fromType, ffiType) \ |
| case TYPE_##name: \ |
| if (!IsAlwaysExact<IntegerType, fromType>()) \ |
| return false; \ |
| *result = IntegerType(*static_cast<fromType*>(data)); \ |
| return true; |
| CTYPES_FOR_EACH_INT_TYPE(INTEGER_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGER_CASE) |
| #undef INTEGER_CASE |
| case TYPE_void_t: |
| case TYPE_bool: |
| case TYPE_float: |
| case TYPE_double: |
| case TYPE_float32_t: |
| case TYPE_float64_t: |
| case TYPE_char: |
| case TYPE_signed_char: |
| case TYPE_unsigned_char: |
| case TYPE_char16_t: |
| case TYPE_pointer: |
| case TYPE_function: |
| case TYPE_array: |
| case TYPE_struct: |
| // Not a compatible number type. |
| return false; |
| } |
| } |
| |
| if (Int64::IsInt64(obj)) { |
| // Make sure the integer fits in IntegerType. |
| int64_t i = Int64Base::GetInt(obj); |
| return ConvertExact(i, result); |
| } |
| |
| if (UInt64::IsUInt64(obj)) { |
| // Make sure the integer fits in IntegerType. |
| uint64_t i = Int64Base::GetInt(obj); |
| return ConvertExact(i, result); |
| } |
| |
| if (CDataFinalizer::IsCDataFinalizer(obj)) { |
| RootedValue innerData(cx); |
| if (!CDataFinalizer::GetValue(cx, obj, &innerData)) { |
| return false; // Nothing to convert |
| } |
| return jsvalToInteger(cx, innerData, result); |
| } |
| |
| return false; |
| } |
| if (val.isBoolean()) { |
| // Implicitly promote boolean values to 0 or 1, like C. |
| *result = val.toBoolean(); |
| MOZ_ASSERT(*result == 0 || *result == 1); |
| return true; |
| } |
| // Don't silently convert null to an integer. It's probably a mistake. |
| return false; |
| } |
| |
| // Implicitly convert val to FloatType, allowing int, double, |
| // Int64, UInt64, and CData numeric types 't' where all values of 't' are |
| // representable by FloatType. |
| template<class FloatType> |
| static bool |
| jsvalToFloat(JSContext* cx, Value val, FloatType* result) |
| { |
| JS_STATIC_ASSERT(!NumericLimits<FloatType>::is_exact); |
| |
| // The following casts may silently throw away some bits, but there's |
| // no good way around it. Sternly requiring that the 64-bit double |
| // argument be exactly representable as a 32-bit float is |
| // unrealistic: it would allow 1/2 to pass but not 1/3. |
| if (val.isInt32()) { |
| *result = FloatType(val.toInt32()); |
| return true; |
| } |
| if (val.isDouble()) { |
| *result = FloatType(val.toDouble()); |
| return true; |
| } |
| if (val.isObject()) { |
| JSObject* obj = &val.toObject(); |
| if (CData::IsCData(obj)) { |
| JSObject* typeObj = CData::GetCType(obj); |
| void* data = CData::GetData(obj); |
| |
| // Check whether the source type is always representable, with exact |
| // precision, by the target type. If it is, convert the value. |
| switch (CType::GetTypeCode(typeObj)) { |
| #define NUMERIC_CASE(name, fromType, ffiType) \ |
| case TYPE_##name: \ |
| if (!IsAlwaysExact<FloatType, fromType>()) \ |
| return false; \ |
| *result = FloatType(*static_cast<fromType*>(data)); \ |
| return true; |
| CTYPES_FOR_EACH_FLOAT_TYPE(NUMERIC_CASE) |
| CTYPES_FOR_EACH_INT_TYPE(NUMERIC_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(NUMERIC_CASE) |
| #undef NUMERIC_CASE |
| case TYPE_void_t: |
| case TYPE_bool: |
| case TYPE_char: |
| case TYPE_signed_char: |
| case TYPE_unsigned_char: |
| case TYPE_char16_t: |
| case TYPE_pointer: |
| case TYPE_function: |
| case TYPE_array: |
| case TYPE_struct: |
| // Not a compatible number type. |
| return false; |
| } |
| } |
| } |
| // Don't silently convert true to 1.0 or false to 0.0, even though C/C++ |
| // does it. It's likely to be a mistake. |
| return false; |
| } |
| |
| template <class IntegerType, class CharT> |
| static bool |
| StringToInteger(JSContext* cx, CharT* cp, size_t length, IntegerType* result) |
| { |
| JS_STATIC_ASSERT(NumericLimits<IntegerType>::is_exact); |
| |
| const CharT* end = cp + length; |
| if (cp == end) |
| return false; |
| |
| IntegerType sign = 1; |
| if (cp[0] == '-') { |
| if (!NumericLimits<IntegerType>::is_signed) |
| return false; |
| |
| sign = -1; |
| ++cp; |
| } |
| |
| // Assume base-10, unless the string begins with '0x' or '0X'. |
| IntegerType base = 10; |
| if (end - cp > 2 && cp[0] == '0' && (cp[1] == 'x' || cp[1] == 'X')) { |
| cp += 2; |
| base = 16; |
| } |
| |
| // Scan the string left to right and build the number, |
| // checking for valid characters 0 - 9, a - f, A - F and overflow. |
| IntegerType i = 0; |
| while (cp != end) { |
| char16_t c = *cp++; |
| if (c >= '0' && c <= '9') |
| c -= '0'; |
| else if (base == 16 && c >= 'a' && c <= 'f') |
| c = c - 'a' + 10; |
| else if (base == 16 && c >= 'A' && c <= 'F') |
| c = c - 'A' + 10; |
| else |
| return false; |
| |
| IntegerType ii = i; |
| i = ii * base + sign * c; |
| if (i / base != ii) // overflow |
| return false; |
| } |
| |
| *result = i; |
| return true; |
| } |
| |
| template<class IntegerType> |
| static bool |
| StringToInteger(JSContext* cx, JSString* string, IntegerType* result) |
| { |
| JSLinearString* linear = string->ensureLinear(cx); |
| if (!linear) |
| return false; |
| |
| AutoCheckCannotGC nogc; |
| size_t length = linear->length(); |
| return string->hasLatin1Chars() |
| ? StringToInteger<IntegerType>(cx, linear->latin1Chars(nogc), length, result) |
| : StringToInteger<IntegerType>(cx, linear->twoByteChars(nogc), length, result); |
| } |
| |
| // Implicitly convert val to IntegerType, allowing int, double, |
| // Int64, UInt64, and optionally a decimal or hexadecimal string argument. |
| // (This is common code shared by jsvalToSize and the Int64/UInt64 constructors.) |
| template<class IntegerType> |
| static bool |
| jsvalToBigInteger(JSContext* cx, |
| Value val, |
| bool allowString, |
| IntegerType* result) |
| { |
| JS_STATIC_ASSERT(NumericLimits<IntegerType>::is_exact); |
| |
| if (val.isInt32()) { |
| // Make sure the integer fits in the alotted precision, and has the right |
| // sign. |
| int32_t i = val.toInt32(); |
| return ConvertExact(i, result); |
| } |
| if (val.isDouble()) { |
| // Don't silently lose bits here -- check that val really is an |
| // integer value, and has the right sign. |
| double d = val.toDouble(); |
| return ConvertExact(d, result); |
| } |
| if (allowString && val.isString()) { |
| // Allow conversion from base-10 or base-16 strings, provided the result |
| // fits in IntegerType. (This allows an Int64 or UInt64 object to be passed |
| // to the JS array element operator, which will automatically call |
| // toString() on the object for us.) |
| return StringToInteger(cx, val.toString(), result); |
| } |
| 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); |
| } |
| |
| } |
| 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, Value val, bool allowString, size_t* result) |
| { |
| if (!jsvalToBigInteger(cx, val, allowString, result)) |
| return false; |
| |
| // Also check that the result fits in a double. |
| return Convert<size_t>(double(*result)) == *result; |
| } |
| |
| // Implicitly convert val to IntegerType, allowing int, double, |
| // Int64, UInt64, and optionally a decimal or hexadecimal string argument. |
| // (This is common code shared by jsvalToSize and the Int64/UInt64 constructors.) |
| template<class IntegerType> |
| static bool |
| jsidToBigInteger(JSContext* cx, |
| jsid val, |
| bool allowString, |
| IntegerType* result) |
| { |
| JS_STATIC_ASSERT(NumericLimits<IntegerType>::is_exact); |
| |
| if (JSID_IS_INT(val)) { |
| // Make sure the integer fits in the alotted precision, and has the right |
| // sign. |
| int32_t i = JSID_TO_INT(val); |
| return ConvertExact(i, result); |
| } |
| if (allowString && JSID_IS_STRING(val)) { |
| // Allow conversion from base-10 or base-16 strings, provided the result |
| // fits in IntegerType. (This allows an Int64 or UInt64 object to be passed |
| // to the JS array element operator, which will automatically call |
| // toString() on the object for us.) |
| return StringToInteger(cx, JSID_TO_STRING(val), result); |
| } |
| return false; |
| } |
| |
| // Implicitly convert val to a size value, where the size value is represented |
| // by size_t but must also fit in a double. |
| static bool |
| jsidToSize(JSContext* cx, jsid val, bool allowString, size_t* result) |
| { |
| if (!jsidToBigInteger(cx, val, allowString, result)) |
| return false; |
| |
| // Also check that the result fits in a double. |
| return Convert<size_t>(double(*result)) == *result; |
| } |
| |
| // Implicitly convert a size value to a Value, ensuring that the size_t value |
| // fits in a double. |
| static bool |
| SizeTojsval(JSContext* cx, size_t size, MutableHandleValue result) |
| { |
| if (Convert<size_t>(double(size)) != size) { |
| JS_ReportError(cx, "size overflow"); |
| return false; |
| } |
| |
| result.setNumber(double(size)); |
| return true; |
| } |
| |
| // Forcefully convert val to IntegerType when explicitly requested. |
| template<class IntegerType> |
| static bool |
| jsvalToIntegerExplicit(Value val, IntegerType* result) |
| { |
| JS_STATIC_ASSERT(NumericLimits<IntegerType>::is_exact); |
| |
| if (val.isDouble()) { |
| // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast. |
| double d = val.toDouble(); |
| *result = mozilla::IsFinite(d) ? IntegerType(d) : 0; |
| return true; |
| } |
| if (val.isObject()) { |
| // Convert Int64 and UInt64 values by C-style cast. |
| JSObject* obj = &val.toObject(); |
| if (Int64::IsInt64(obj)) { |
| int64_t i = Int64Base::GetInt(obj); |
| *result = IntegerType(i); |
| return true; |
| } |
| if (UInt64::IsUInt64(obj)) { |
| uint64_t i = Int64Base::GetInt(obj); |
| *result = IntegerType(i); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Forcefully convert val to a pointer value when explicitly requested. |
| static bool |
| jsvalToPtrExplicit(JSContext* cx, Value val, uintptr_t* result) |
| { |
| if (val.isInt32()) { |
| // int32_t always fits in intptr_t. If the integer is negative, cast through |
| // an intptr_t intermediate to sign-extend. |
| int32_t i = val.toInt32(); |
| *result = i < 0 ? uintptr_t(intptr_t(i)) : uintptr_t(i); |
| return true; |
| } |
| if (val.isDouble()) { |
| double d = val.toDouble(); |
| if (d < 0) { |
| // Cast through an intptr_t intermediate to sign-extend. |
| intptr_t i = Convert<intptr_t>(d); |
| if (double(i) != d) |
| return false; |
| |
| *result = uintptr_t(i); |
| return true; |
| } |
| |
| // Don't silently lose bits here -- check that val really is an |
| // integer value, and has the right sign. |
| *result = Convert<uintptr_t>(d); |
| return double(*result) == d; |
| } |
| if (val.isObject()) { |
| JSObject* obj = &val.toObject(); |
| if (Int64::IsInt64(obj)) { |
| int64_t i = Int64Base::GetInt(obj); |
| intptr_t p = intptr_t(i); |
| |
| // Make sure the integer fits in the alotted precision. |
| if (int64_t(p) != i) |
| return false; |
| *result = uintptr_t(p); |
| return true; |
| } |
| |
| if (UInt64::IsUInt64(obj)) { |
| uint64_t i = Int64Base::GetInt(obj); |
| |
| // Make sure the integer fits in the alotted precision. |
| *result = uintptr_t(i); |
| return uint64_t(*result) == i; |
| } |
| } |
| return false; |
| } |
| |
| template<class IntegerType, class CharType, size_t N, class AP> |
| void |
| IntegerToString(IntegerType i, int radix, mozilla::Vector<CharType, N, AP>& result) |
| { |
| JS_STATIC_ASSERT(NumericLimits<IntegerType>::is_exact); |
| |
| // The buffer must be big enough for all the bits of IntegerType to fit, |
| // in base-2, including '-'. |
| CharType buffer[sizeof(IntegerType) * 8 + 1]; |
| CharType* end = buffer + sizeof(buffer) / sizeof(CharType); |
| CharType* cp = end; |
| |
| // Build the string in reverse. We use multiplication and subtraction |
| // instead of modulus because that's much faster. |
| const bool isNegative = IsNegative(i); |
| size_t sign = isNegative ? -1 : 1; |
| do { |
| IntegerType ii = i / IntegerType(radix); |
| size_t index = sign * size_t(i - ii * IntegerType(radix)); |
| *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[index]; |
| i = ii; |
| } while (i != 0); |
| |
| if (isNegative) |
| *--cp = '-'; |
| |
| MOZ_ASSERT(cp >= buffer); |
| result.append(cp, end); |
| } |
| |
| template<class CharType> |
| static size_t |
| strnlen(const CharType* begin, size_t max) |
| { |
| for (const CharType* s = begin; s != begin + max; ++s) |
| if (*s == 0) |
| return s - begin; |
| |
| return max; |
| } |
| |
| // Convert C binary value 'data' of CType 'typeObj' to a JS primitive, where |
| // possible; otherwise, construct and return a CData object. The following |
| // semantics apply when constructing a CData object for return: |
| // * If 'wantPrimitive' is true, the caller indicates that 'result' must be |
| // a JS primitive, and ConvertToJS will fail if 'result' would be a CData |
| // object. Otherwise: |
| // * If a CData object 'parentObj' is supplied, the new CData object is |
| // dependent on the given parent and its buffer refers to a slice of the |
| // parent's buffer. |
| // * If 'parentObj' is null, the new CData object may or may not own its |
| // resulting buffer depending on the 'ownResult' argument. |
| static bool |
| ConvertToJS(JSContext* cx, |
| HandleObject typeObj, |
| HandleObject parentObj, |
| void* data, |
| bool wantPrimitive, |
| bool ownResult, |
| MutableHandleValue result) |
| { |
| MOZ_ASSERT(!parentObj || CData::IsCData(parentObj)); |
| MOZ_ASSERT(!parentObj || !ownResult); |
| MOZ_ASSERT(!wantPrimitive || !ownResult); |
| |
| TypeCode typeCode = CType::GetTypeCode(typeObj); |
| |
| switch (typeCode) { |
| case TYPE_void_t: |
| result.setUndefined(); |
| break; |
| case TYPE_bool: |
| result.setBoolean(*static_cast<bool*>(data)); |
| break; |
| #define INT_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| type value = *static_cast<type*>(data); \ |
| if (sizeof(type) < 4) \ |
| result.setInt32(int32_t(value)); \ |
| else \ |
| result.setDouble(double(value)); \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_INT_TYPE(INT_CASE) |
| #undef INT_CASE |
| #define WRAPPED_INT_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| /* Return an Int64 or UInt64 object - do not convert to a JS number. */ \ |
| uint64_t value; \ |
| RootedObject proto(cx); \ |
| if (!NumericLimits<type>::is_signed) { \ |
| value = *static_cast<type*>(data); \ |
| /* Get ctypes.UInt64.prototype from ctypes.CType.prototype. */ \ |
| proto = CType::GetProtoFromType(cx, typeObj, SLOT_UINT64PROTO); \ |
| if (!proto) \ |
| return false; \ |
| } else { \ |
| value = int64_t(*static_cast<type*>(data)); \ |
| /* Get ctypes.Int64.prototype from ctypes.CType.prototype. */ \ |
| proto = CType::GetProtoFromType(cx, typeObj, SLOT_INT64PROTO); \ |
| if (!proto) \ |
| return false; \ |
| } \ |
| \ |
| JSObject* obj = Int64Base::Construct(cx, proto, value, \ |
| !NumericLimits<type>::is_signed); \ |
| if (!obj) \ |
| return false; \ |
| result.setObject(*obj); \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE) |
| #undef WRAPPED_INT_CASE |
| #define FLOAT_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| type value = *static_cast<type*>(data); \ |
| result.setDouble(double(value)); \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) |
| #undef FLOAT_CASE |
| #define CHAR_CASE(name, type, ffiType) \ |
| case TYPE_##name: \ |
| /* Convert to an integer. We have no idea what character encoding to */ \ |
| /* use, if any. */ \ |
| result.setInt32(*static_cast<type*>(data)); \ |
| break; |
| CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE) |
| #undef CHAR_CASE |
| case TYPE_char16_t: { |
| // Convert the char16_t to a 1-character string. |
| JSString* str = JS_NewUCStringCopyN(cx, static_cast<char16_t*>(data), 1); |
| if (!str) |
| return false; |
| |
| result.setString(str); |
| break; |
| } |
| case TYPE_pointer: |
| case TYPE_array: |
| case TYPE_struct: { |
| // We're about to create a new CData object to return. If the caller doesn't |
| // want this, return early. |
| if (wantPrimitive) { |
| return NonPrimitiveError(cx, typeObj); |
| } |
| |
| JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult); |
| if (!obj) |
| return false; |
| |
| result.setObject(*obj); |
| break; |
| } |
| case TYPE_function: |
| MOZ_CRASH("cannot return a FunctionType"); |
| } |
| |
| return true; |
| } |
| |
| // Determine if the contents of a typed array can be converted without |
| // ambiguity to a C type. Elements of a Int8Array are converted to |
| // ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc. |
| bool CanConvertTypedArrayItemTo(JSObject* baseType, JSObject* valObj, JSContext* cx) { |
| TypeCode baseTypeCode = CType::GetTypeCode(baseType); |
| if (baseTypeCode == TYPE_void_t || baseTypeCode == TYPE_char) { |
| return true; |
| } |
| TypeCode elementTypeCode; |
| switch (JS_GetArrayBufferViewType(valObj)) { |
| case Scalar::Int8: |
| elementTypeCode = TYPE_int8_t; |
| break; |
| case Scalar::Uint8: |
| case Scalar::Uint8Clamped: |
| elementTypeCode = TYPE_uint8_t; |
| break; |
| case Scalar::Int16: |
| elementTypeCode = TYPE_int16_t; |
| break; |
| case Scalar::Uint16: |
| elementTypeCode = TYPE_uint16_t; |
| break; |
| case Scalar::Int32: |
| elementTypeCode = TYPE_int32_t; |
| break; |
| case Scalar::Uint32: |
| elementTypeCode = TYPE_uint32_t; |
| break; |
| case Scalar::Float32: |
| elementTypeCode = TYPE_float32_t; |
| break; |
| case Scalar::Float64: |
| elementTypeCode = TYPE_float64_t; |
| break; |
| default: |
| return false; |
| } |
| |
| return elementTypeCode == baseTypeCode; |
| } |
| |
| // Implicitly convert Value 'val' to a C binary representation of CType |
| // 'targetType', storing the result in 'buffer'. Adequate space must be |
| // provided in 'buffer' by the caller. This function generally does minimal |
| // coercion between types. There are two cases in which this function is used: |
| // 1) The target buffer is internal to a CData object; we simply write data |
| // into it. |
| // 2) We are converting an argument for an ffi call, in which case 'convType' |
| // will be 'ConversionType::Argument'. This allows us to handle a special |
| // case: if necessary, we can autoconvert a JS string primitive to a |
| // pointer-to-character type. In this case, ownership of the allocated string |
| // is handed off to the caller; 'freePointer' will be set to indicate this. |
| static bool |
| ImplicitConvert(JSContext* cx, |
| HandleValue val, |
| JSObject* targetType_, |
| void* buffer, |
| ConversionType convType, |
| bool* freePointer, |
| HandleObject funObj = nullptr, unsigned argIndex = 0, |
| HandleObject arrObj = nullptr, unsigned arrIndex = 0) |
| { |
| RootedObject targetType(cx, targetType_); |
| MOZ_ASSERT(CType::IsSizeDefined(targetType)); |
| |
| // First, check if val is either a CData object or a CDataFinalizer |
| // of type targetType. |
| JSObject* sourceData = nullptr; |
| JSObject* sourceType = nullptr; |
| RootedObject valObj(cx, nullptr); |
| if (val.isObject()) { |
| valObj = &val.toObject(); |
| if (CData::IsCData(valObj)) { |
| sourceData = valObj; |
| sourceType = CData::GetCType(sourceData); |
| |
| // If the types are equal, copy the buffer contained within the CData. |
| // (Note that the buffers may overlap partially or completely.) |
| if (CType::TypesEqual(sourceType, targetType)) { |
| size_t size = CType::GetSize(sourceType); |
| memmove(buffer, CData::GetData(sourceData), size); |
| return true; |
| } |
| } else if (CDataFinalizer::IsCDataFinalizer(valObj)) { |
| sourceData = valObj; |
| sourceType = CDataFinalizer::GetCType(cx, sourceData); |
| |
| CDataFinalizer::Private* p = (CDataFinalizer::Private*) |
| JS_GetPrivate(sourceData); |
| |
| if (!p) { |
| // We have called |dispose| or |forget| already. |
| return EmptyFinalizerError(cx, convType, funObj, argIndex); |
| } |
| |
| // If the types are equal, copy the buffer contained within the CData. |
| if (CType::TypesEqual(sourceType, targetType)) { |
| memmove(buffer, p->cargs, p->cargs_size); |
| return true; |
| } |
| } |
| } |
| |
| TypeCode targetCode = CType::GetTypeCode(targetType); |
| |
| switch (targetCode) { |
| case TYPE_bool: { |
| // Do not implicitly lose bits, but allow the values 0, 1, and -0. |
| // Programs can convert explicitly, if needed, using `Boolean(v)` or `!!v`. |
| bool result; |
| if (!jsvalToBool(cx, val, &result)) |
| return ConvError(cx, "boolean", val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| *static_cast<bool*>(buffer) = result; |
| break; |
| } |
| #define CHAR16_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| /* Convert from a 1-character string, regardless of encoding, */ \ |
| /* or from an integer, provided the result fits in 'type'. */ \ |
| type result; \ |
| if (val.isString()) { \ |
| JSString* str = val.toString(); \ |
| if (str->length() != 1) \ |
| return ConvError(cx, #name, val, convType, funObj, argIndex, \ |
| arrObj, arrIndex); \ |
| JSLinearString* linear = str->ensureLinear(cx); \ |
| if (!linear) \ |
| return false; \ |
| result = linear->latin1OrTwoByteChar(0); \ |
| } else if (!jsvalToInteger(cx, val, &result)) { \ |
| return ConvError(cx, #name, val, convType, funObj, argIndex, \ |
| arrObj, arrIndex); \ |
| } \ |
| *static_cast<type*>(buffer) = result; \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_CHAR16_TYPE(CHAR16_CASE) |
| #undef CHAR16_CASE |
| #define INTEGRAL_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| /* Do not implicitly lose bits. */ \ |
| type result; \ |
| if (!jsvalToInteger(cx, val, &result)) \ |
| return ConvError(cx, #name, val, convType, funObj, argIndex, \ |
| arrObj, arrIndex); \ |
| *static_cast<type*>(buffer) = result; \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) |
| // It's hard to believe ctypes.char16_t("f") should work yet ctypes.char("f") |
| // should not. Ditto for ctypes.{un,}signed_char. But this is how ctypes |
| // has always worked, so preserve these semantics, and don't switch to an |
| // algorithm similar to that in DEFINE_CHAR16_TYPE above, just yet. |
| CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) |
| #undef INTEGRAL_CASE |
| #define FLOAT_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| type result; \ |
| if (!jsvalToFloat(cx, val, &result)) \ |
| return ConvError(cx, #name, val, convType, funObj, argIndex, \ |
| arrObj, arrIndex); \ |
| *static_cast<type*>(buffer) = result; \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) |
| #undef FLOAT_CASE |
| case TYPE_pointer: { |
| if (val.isNull()) { |
| // Convert to a null pointer. |
| *static_cast<void**>(buffer) = nullptr; |
| break; |
| } |
| |
| JS::Rooted<JSObject*> baseType(cx, PointerType::GetBaseType(targetType)); |
| if (sourceData) { |
| // First, determine if the targetType is ctypes.void_t.ptr. |
| TypeCode sourceCode = CType::GetTypeCode(sourceType); |
| void* sourceBuffer = CData::GetData(sourceData); |
| bool voidptrTarget = CType::GetTypeCode(baseType) == TYPE_void_t; |
| |
| if (sourceCode == TYPE_pointer && voidptrTarget) { |
| // Autoconvert if targetType is ctypes.voidptr_t. |
| *static_cast<void**>(buffer) = *static_cast<void**>(sourceBuffer); |
| break; |
| } |
| if (sourceCode == TYPE_array) { |
| // Autoconvert an array to a ctypes.void_t.ptr or to |
| // sourceType.elementType.ptr, just like C. |
| JSObject* elementType = ArrayType::GetBaseType(sourceType); |
| if (voidptrTarget || CType::TypesEqual(baseType, elementType)) { |
| *static_cast<void**>(buffer) = sourceBuffer; |
| break; |
| } |
| } |
| |
| } else if (convType == ConversionType::Argument && val.isString()) { |
| // Convert the string for the ffi call. This requires allocating space |
| // which the caller assumes ownership of. |
| // TODO: Extend this so we can safely convert strings at other times also. |
| JSString* sourceString = val.toString(); |
| size_t sourceLength = sourceString->length(); |
| JSLinearString* sourceLinear = sourceString->ensureLinear(cx); |
| if (!sourceLinear) |
| return false; |
| |
| switch (CType::GetTypeCode(baseType)) { |
| case TYPE_char: |
| case TYPE_signed_char: |
| case TYPE_unsigned_char: { |
| // Convert from UTF-16 to UTF-8. |
| size_t nbytes = GetDeflatedUTF8StringLength(cx, sourceLinear); |
| if (nbytes == (size_t) -1) |
| return false; |
| |
| char** charBuffer = static_cast<char**>(buffer); |
| *charBuffer = cx->pod_malloc<char>(nbytes + 1); |
| if (!*charBuffer) { |
| JS_ReportAllocationOverflow(cx); |
| return false; |
| } |
| |
| ASSERT_OK(DeflateStringToUTF8Buffer(cx, sourceLinear, *charBuffer, &nbytes)); |
| (*charBuffer)[nbytes] = 0; |
| *freePointer = true; |
| break; |
| } |
| case TYPE_char16_t: { |
| // Copy the char16_t string data. (We could provide direct access to the |
| // JSString's buffer, but this approach is safer if the caller happens |
| // to modify the string.) |
| char16_t** char16Buffer = static_cast<char16_t**>(buffer); |
| *char16Buffer = cx->pod_malloc<char16_t>(sourceLength + 1); |
| if (!*char16Buffer) { |
| JS_ReportAllocationOverflow(cx); |
| return false; |
| } |
| |
| *freePointer = true; |
| if (sourceLinear->hasLatin1Chars()) { |
| AutoCheckCannotGC nogc; |
| CopyAndInflateChars(*char16Buffer, sourceLinear->latin1Chars(nogc), sourceLength); |
| } else { |
| AutoCheckCannotGC nogc; |
| mozilla::PodCopy(*char16Buffer, sourceLinear->twoByteChars(nogc), sourceLength); |
| } |
| (*char16Buffer)[sourceLength] = 0; |
| break; |
| } |
| default: |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| break; |
| } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) { |
| // Convert ArrayBuffer to pointer without any copy. This is only valid |
| // when converting an argument to a function call, as it is possible for |
| // the pointer to be invalidated by anything that runs JS code. (It is |
| // invalid to invoke JS code from a ctypes function call.) |
| if (convType != ConversionType::Argument) { |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| void* ptr; |
| { |
| JS::AutoCheckCannotGC nogc; |
| bool isShared; |
| ptr = JS_GetArrayBufferData(valObj, &isShared, nogc); |
| MOZ_ASSERT(!isShared); // Because ArrayBuffer |
| } |
| if (!ptr) { |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| *static_cast<void**>(buffer) = ptr; |
| break; |
| } else if (val.isObject() && JS_IsSharedArrayBufferObject(valObj)) { |
| // CTypes has not yet opted in to allowing shared memory pointers |
| // to escape. Exporting a pointer to the shared buffer without |
| // indicating sharedness would expose client code to races. |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } else if (val.isObject() && JS_IsArrayBufferViewObject(valObj)) { |
| // Same as ArrayBuffer, above, though note that this will take the |
| // offset of the view into account. |
| if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) { |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| if (convType != ConversionType::Argument) { |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| void* ptr; |
| { |
| JS::AutoCheckCannotGC nogc; |
| bool isShared; |
| ptr = JS_GetArrayBufferViewData(valObj, &isShared, nogc); |
| if (isShared) { |
| // Opt out of shared memory, for now. Exporting a |
| // pointer to the shared buffer without indicating |
| // sharedness would expose client code to races. |
| ptr = nullptr; |
| } |
| } |
| if (!ptr) { |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| *static_cast<void**>(buffer) = ptr; |
| break; |
| } |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| case TYPE_array: { |
| MOZ_ASSERT(!funObj); |
| |
| RootedObject baseType(cx, ArrayType::GetBaseType(targetType)); |
| size_t targetLength = ArrayType::GetLength(targetType); |
| |
| if (val.isString()) { |
| JSString* sourceString = val.toString(); |
| size_t sourceLength = sourceString->length(); |
| JSLinearString* sourceLinear = sourceString->ensureLinear(cx); |
| if (!sourceLinear) |
| return false; |
| |
| switch (CType::GetTypeCode(baseType)) { |
| case TYPE_char: |
| case TYPE_signed_char: |
| case TYPE_unsigned_char: { |
| // Convert from UTF-16 or Latin1 to UTF-8. |
| size_t nbytes = |
| GetDeflatedUTF8StringLength(cx, sourceLinear); |
| if (nbytes == (size_t) -1) |
| return false; |
| |
| if (targetLength < nbytes) { |
| MOZ_ASSERT(!funObj); |
| return ArrayLengthOverflow(cx, targetLength, targetType, nbytes, val, |
| convType); |
| } |
| |
| char* charBuffer = static_cast<char*>(buffer); |
| ASSERT_OK(DeflateStringToUTF8Buffer(cx, sourceLinear, charBuffer, |
| &nbytes)); |
| |
| if (targetLength > nbytes) |
| charBuffer[nbytes] = 0; |
| |
| break; |
| } |
| case TYPE_char16_t: { |
| // Copy the string data, char16_t for char16_t, including the terminator |
| // if there's space. |
| if (targetLength < sourceLength) { |
| MOZ_ASSERT(!funObj); |
| return ArrayLengthOverflow(cx, targetLength, targetType, |
| sourceLength, val, convType); |
| } |
| |
| char16_t* dest = static_cast<char16_t*>(buffer); |
| if (sourceLinear->hasLatin1Chars()) { |
| AutoCheckCannotGC nogc; |
| CopyAndInflateChars(dest, sourceLinear->latin1Chars(nogc), sourceLength); |
| } else { |
| AutoCheckCannotGC nogc; |
| mozilla::PodCopy(dest, sourceLinear->twoByteChars(nogc), sourceLength); |
| } |
| |
| if (targetLength > sourceLength) |
| dest[sourceLength] = 0; |
| |
| break; |
| } |
| default: |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| } else { |
| ESClassValue cls; |
| if (!GetClassOfValue(cx, val, &cls)) |
| return false; |
| |
| if (cls == ESClass_Array) { |
| // Convert each element of the array by calling ImplicitConvert. |
| uint32_t sourceLength; |
| if (!JS_GetArrayLength(cx, valObj, &sourceLength) || |
| targetLength != size_t(sourceLength)) { |
| MOZ_ASSERT(!funObj); |
| return ArrayLengthMismatch(cx, targetLength, targetType, |
| size_t(sourceLength), val, convType); |
| } |
| |
| // Convert into an intermediate, in case of failure. |
| size_t elementSize = CType::GetSize(baseType); |
| size_t arraySize = elementSize * targetLength; |
| auto intermediate = cx->make_pod_array<char>(arraySize); |
| if (!intermediate) { |
| JS_ReportAllocationOverflow(cx); |
| return false; |
| } |
| |
| RootedValue item(cx); |
| for (uint32_t i = 0; i < sourceLength; ++i) { |
| if (!JS_GetElement(cx, valObj, i, &item)) |
| return false; |
| |
| char* data = intermediate.get() + elementSize * i; |
| if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr, |
| funObj, argIndex, targetType, i)) |
| return false; |
| } |
| |
| memcpy(buffer, intermediate.get(), arraySize); |
| } else if (cls == ESClass_ArrayBuffer || cls == ESClass_SharedArrayBuffer) { |
| // Check that array is consistent with type, then |
| // copy the array. |
| const bool bufferShared = cls == ESClass_SharedArrayBuffer; |
| uint32_t sourceLength = bufferShared ? JS_GetSharedArrayBufferByteLength(valObj) |
| : JS_GetArrayBufferByteLength(valObj); |
| size_t elementSize = CType::GetSize(baseType); |
| size_t arraySize = elementSize * targetLength; |
| if (arraySize != size_t(sourceLength)) { |
| MOZ_ASSERT(!funObj); |
| return ArrayLengthMismatch(cx, arraySize, targetType, |
| size_t(sourceLength), val, convType); |
| } |
| SharedMem<void*> target = SharedMem<void*>::unshared(buffer); |
| JS::AutoCheckCannotGC nogc; |
| bool isShared; |
| SharedMem<void*> src = |
| (bufferShared ? |
| SharedMem<void*>::shared(JS_GetSharedArrayBufferData(valObj, &isShared, nogc)) : |
| SharedMem<void*>::unshared(JS_GetArrayBufferData(valObj, &isShared, nogc))); |
| MOZ_ASSERT(isShared == bufferShared); |
| jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength); |
| break; |
| } else if (JS_IsTypedArrayObject(valObj)) { |
| // Check that array is consistent with type, then |
| // copy the array. It is OK to copy from shared to unshared |
| // or vice versa. |
| if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) { |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| |
| uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj); |
| size_t elementSize = CType::GetSize(baseType); |
| size_t arraySize = elementSize * targetLength; |
| if (arraySize != size_t(sourceLength)) { |
| MOZ_ASSERT(!funObj); |
| return ArrayLengthMismatch(cx, arraySize, targetType, |
| size_t(sourceLength), val, convType); |
| } |
| SharedMem<void*> target = SharedMem<void*>::unshared(buffer); |
| JS::AutoCheckCannotGC nogc; |
| bool isShared; |
| SharedMem<void*> src = |
| SharedMem<void*>::shared(JS_GetArrayBufferViewData(valObj, &isShared, nogc)); |
| jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength); |
| break; |
| } else { |
| // Don't implicitly convert to string. Users can implicitly convert |
| // with `String(x)` or `""+x`. |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| } |
| break; |
| } |
| case TYPE_struct: { |
| if (val.isObject() && !sourceData) { |
| // Enumerate the properties of the object; if they match the struct |
| // specification, convert the fields. |
| Rooted<IdVector> props(cx, IdVector(cx)); |
| if (!JS_Enumerate(cx, valObj, &props)) |
| return false; |
| |
| // Convert into an intermediate, in case of failure. |
| size_t structSize = CType::GetSize(targetType); |
| auto intermediate = cx->make_pod_array<char>(structSize); |
| if (!intermediate) { |
| JS_ReportAllocationOverflow(cx); |
| return false; |
| } |
| |
| const FieldInfoHash* fields = StructType::GetFieldInfo(targetType); |
| if (props.length() != fields->count()) { |
| return FieldCountMismatch(cx, fields->count(), targetType, |
| props.length(), val, convType, |
| funObj, argIndex); |
| } |
| |
| RootedId id(cx); |
| for (size_t i = 0; i < props.length(); ++i) { |
| id = props[i]; |
| |
| if (!JSID_IS_STRING(id)) { |
| return PropNameNonStringError(cx, id, val, convType, |
| funObj, argIndex); |
| } |
| |
| JSFlatString* name = JSID_TO_FLAT_STRING(id); |
| const FieldInfo* field = StructType::LookupField(cx, targetType, name); |
| if (!field) |
| return false; |
| |
| RootedValue prop(cx); |
| if (!JS_GetPropertyById(cx, valObj, id, &prop)) |
| return false; |
| |
| // Convert the field via ImplicitConvert(). |
| char* fieldData = intermediate.get() + field->mOffset; |
| if (!ImplicitConvert(cx, prop, field->mType, fieldData, convType, |
| nullptr, funObj, argIndex, targetType, i)) |
| return false; |
| } |
| |
| memcpy(buffer, intermediate.get(), structSize); |
| break; |
| } |
| |
| return ConvError(cx, targetType, val, convType, funObj, argIndex, |
| arrObj, arrIndex); |
| } |
| case TYPE_void_t: |
| case TYPE_function: |
| MOZ_CRASH("invalid type"); |
| } |
| |
| return true; |
| } |
| |
| // Convert Value 'val' to a C binary representation of CType 'targetType', |
| // storing the result in 'buffer'. This function is more forceful than |
| // ImplicitConvert. |
| static bool |
| ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, |
| void* buffer, ConversionType convType) |
| { |
| // If ImplicitConvert succeeds, use that result. |
| if (ImplicitConvert(cx, val, targetType, buffer, convType, nullptr)) |
| return true; |
| |
| // If ImplicitConvert failed, and there is no pending exception, then assume |
| // hard failure (out of memory, or some other similarly serious condition). |
| // We store any pending exception in case we need to re-throw it. |
| RootedValue ex(cx); |
| if (!JS_GetPendingException(cx, &ex)) |
| return false; |
| |
| // Otherwise, assume soft failure. Clear the pending exception so that we |
| // can throw a different one as required. |
| JS_ClearPendingException(cx); |
| |
| TypeCode type = CType::GetTypeCode(targetType); |
| |
| switch (type) { |
| case TYPE_bool: { |
| *static_cast<bool*>(buffer) = ToBoolean(val); |
| break; |
| } |
| #define INTEGRAL_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| /* Convert numeric values with a C-style cast, and */ \ |
| /* allow conversion from a base-10 or base-16 string. */ \ |
| type result; \ |
| if (!jsvalToIntegerExplicit(val, &result) && \ |
| (!val.isString() || \ |
| !StringToInteger(cx, val.toString(), &result))) \ |
| return ConvError(cx, #name, val, convType); \ |
| *static_cast<type*>(buffer) = result; \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) |
| #undef INTEGRAL_CASE |
| case TYPE_pointer: { |
| // Convert a number, Int64 object, or UInt64 object to a pointer. |
| uintptr_t result; |
| if (!jsvalToPtrExplicit(cx, val, &result)) |
| return ConvError(cx, targetType, val, convType); |
| *static_cast<uintptr_t*>(buffer) = result; |
| break; |
| } |
| case TYPE_float32_t: |
| case TYPE_float64_t: |
| case TYPE_float: |
| case TYPE_double: |
| case TYPE_array: |
| case TYPE_struct: |
| // ImplicitConvert is sufficient. Re-throw the exception it generated. |
| JS_SetPendingException(cx, ex); |
| return false; |
| case TYPE_void_t: |
| case TYPE_function: |
| MOZ_CRASH("invalid type"); |
| } |
| return true; |
| } |
| |
| // Given a CType 'typeObj', generate a string describing the C type declaration |
| // corresponding to 'typeObj'. For instance, the CType constructed from |
| // 'ctypes.int32_t.ptr.array(4).ptr.ptr' will result in the type string |
| // 'int32_t*(**)[4]'. |
| static JSString* |
| BuildTypeName(JSContext* cx, JSObject* typeObj_) |
| { |
| AutoString result; |
| RootedObject typeObj(cx, typeObj_); |
| |
| // Walk the hierarchy of types, outermost to innermost, building up the type |
| // string. This consists of the base type, which goes on the left. |
| // Derived type modifiers (* and []) build from the inside outward, with |
| // pointers on the left and arrays on the right. An excellent description |
| // of the rules for building C type declarations can be found at: |
| // http://unixwiz.net/techtips/reading-cdecl.html |
| TypeCode prevGrouping = CType::GetTypeCode(typeObj), currentGrouping; |
| while (1) { |
| 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_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_WINAPI: |
| AppendString(result, "ctypes.winapi_abi, "); |
| break; |
| case INVALID_ABI: |
| MOZ_CRASH("invalid abi"); |
| } |
| |
| // Recursively build the source string describing the function return and |
| // argument types. |
| BuildTypeSource(cx, fninfo->mReturnType, true, result); |
| |
| if (fninfo->mArgTypes.length() > 0) { |
| AppendString(result, ", ["); |
| for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { |
| BuildTypeSource(cx, fninfo->mArgTypes[i], true, result); |
| if (i != fninfo->mArgTypes.length() - 1 || |
| fninfo->mIsVariadic) |
| AppendString(result, ", "); |
| } |
| if (fninfo->mIsVariadic) |
| AppendString(result, "\"...\""); |
| AppendString(result, "]"); |
| } |
| |
| AppendString(result, ")"); |
| break; |
| } |
| case TYPE_array: { |
| // Recursively build the source string, and append '.array(n)', |
| // where n is the array length, or the empty string if the array length |
| // is undefined. |
| JSObject* baseType = ArrayType::GetBaseType(typeObj); |
| BuildTypeSource(cx, baseType, makeShort, result); |
| AppendString(result, ".array("); |
| |
| size_t length; |
| if (ArrayType::GetSafeLength(typeObj, &length)) |
| IntegerToString(length, 10, result); |
| |
| AppendString(result, ")"); |
| break; |
| } |
| case TYPE_struct: { |
| JSString* name = CType::GetName(cx, typeObj); |
| |
| if (makeShort) { |
| // Shorten the type declaration by assuming that StructType 't' is bound |
| // to an in-scope variable of name 't.name'. |
| AppendString(result, name); |
| break; |
| } |
| |
| // Write the full struct declaration. |
| AppendString(result, "ctypes.StructType(\""); |
| AppendString(result, name); |
| AppendString(result, "\""); |
| |
| // If it's an opaque struct, we're done. |
| if (!CType::IsSizeDefined(typeObj)) { |
| AppendString(result, ")"); |
| break; |
| } |
| |
| AppendString(result, ", ["); |
| |
| const FieldInfoHash* fields = StructType::GetFieldInfo(typeObj); |
| size_t length = fields->count(); |
| Vector<const FieldInfoHash::Entry*, 64, SystemAllocPolicy> fieldsArray; |
| if (!fieldsArray.resize(length)) |
| break; |
| |
| for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) |
| fieldsArray[r.front().value().mIndex] = &r.front(); |
| |
| for (size_t i = 0; i < length; ++i) { |
| const FieldInfoHash::Entry* entry = fieldsArray[i]; |
| AppendString(result, "{ \""); |
| AppendString(result, entry->key()); |
| AppendString(result, "\": "); |
| BuildTypeSource(cx, entry->value().mType, true, result); |
| AppendString(result, " }"); |
| if (i != length - 1) |
| AppendString(result, ", "); |
| } |
| |
| AppendString(result, "])"); |
| break; |
| } |
| } |
| } |
| |
| // Given a CData object of CType 'typeObj' with binary value 'data', generate a |
| // string 'result' such that 'eval(result)' would construct a CData object with |
| // the same CType and containing the same binary value. This assumes that any |
| // StructType 't' is bound to an in-scope variable of name 't.name'. (This means |
| // the type comparison function CType::TypesEqual will return true when |
| // comparing the types, since struct equality is determined by strict JSObject |
| // pointer equality.) Further, if 'isImplicit' is true, ensure that the |
| // resulting string can ImplicitConvert successfully if passed to another data |
| // constructor. (This is important when called recursively, since fields of |
| // structs and arrays are converted with ImplicitConvert.) |
| static bool |
| BuildDataSource(JSContext* cx, |
| HandleObject typeObj, |
| void* data, |
| bool isImplicit, |
| AutoString& result) |
| { |
| TypeCode type = CType::GetTypeCode(typeObj); |
| switch (type) { |
| case TYPE_bool: |
| if (*static_cast<bool*>(data)) |
| AppendString(result, "true"); |
| else |
| AppendString(result, "false"); |
| break; |
| #define INTEGRAL_CASE(name, type, ffiType) \ |
| case TYPE_##name: \ |
| /* Serialize as a primitive decimal integer. */ \ |
| IntegerToString(*static_cast<type*>(data), 10, result); \ |
| break; |
| CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) |
| #undef INTEGRAL_CASE |
| #define WRAPPED_INT_CASE(name, type, ffiType) \ |
| case TYPE_##name: \ |
| /* Serialize as a wrapped decimal integer. */ \ |
| if (!NumericLimits<type>::is_signed) \ |
| AppendString(result, "ctypes.UInt64(\""); \ |
| else \ |
| AppendString(result, "ctypes.Int64(\""); \ |
| \ |
| IntegerToString(*static_cast<type*>(data), 10, result); \ |
| AppendString(result, "\")"); \ |
| break; |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE) |
| #undef WRAPPED_INT_CASE |
| #define FLOAT_CASE(name, type, ffiType) \ |
| case TYPE_##name: { \ |
| /* Serialize as a primitive double. */ \ |
| double fp = *static_cast<type*>(data); \ |
| ToCStringBuf cbuf; \ |
| char* str = NumberToCString(cx, &cbuf, fp); \ |
| if (!str) { \ |
| JS_ReportOutOfMemory(cx); \ |
| return false; \ |
| } \ |
| \ |
| result.append(str, strlen(str)); \ |
| break; \ |
| } |
| CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) |
| #undef FLOAT_CASE |
| #define CHAR_CASE(name, type, ffiType) \ |
| case TYPE_##name: \ |
| /* Serialize as an integer. */ \ |
| IntegerToString(*static_cast<type*>(data), 10, result); \ |
| break; |
| CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE) |
| #undef CHAR_CASE |
| case TYPE_char16_t: { |
| // Serialize as a 1-character JS string. |
| JSString* str = JS_NewUCStringCopyN(cx, static_cast<char16_t*>(data), 1); |
| if (!str) |
| return false; |
| |
| // Escape characters, and quote as necessary. |
| RootedValue valStr(cx, StringValue(str)); |
| JSString* src = JS_ValueToSource(cx, valStr); |
| if (!src) |
| return false; |
| |
| AppendString(result, src); |
| break; |
| } |
| case TYPE_pointer: |
| case TYPE_function: { |
| if (isImplicit) { |
| // The result must be able to ImplicitConvert successfully. |
| // Wrap in a type constructor, then serialize for ExplicitConvert. |
| BuildTypeSource(cx, typeObj, true, result); |
| AppendString(result, "("); |
| } |
| |
| // Serialize the pointer value as a wrapped hexadecimal integer. |
| uintptr_t ptr = *static_cast<uintptr_t*>(data); |
| AppendString(result, "ctypes.UInt64(\"0x"); |
| IntegerToString(ptr, 16, result); |
| AppendString(result, "\")"); |
| |
| if (isImplicit) |
| AppendString(result, ")"); |
| |
| break; |
| } |
| case TYPE_array: { |
| // Serialize each element of the array recursively. Each element must |
| // be able to ImplicitConvert successfully. |
| RootedObject baseType(cx, ArrayType::GetBaseType(typeObj)); |
| AppendString(result, "["); |
| |
| size_t length = ArrayType::GetLength(typeObj); |
| size_t elementSize = CType::GetSize(baseType); |
| for (size_t i = 0; i < length; ++i) { |
| char* element = static_cast<char*>(data) + elementSize * i; |
| if (!BuildDataSource(cx, baseType, element, true, result)) |
| return false; |
| |
| if (i + 1 < length) |
| AppendString(result, ", "); |
| } |
| AppendString(result, "]"); |
| break; |
| } |
| case TYPE_struct: { |
| if (isImplicit) { |
| // The result must be able to ImplicitConvert successfully. |
| // Serialize the data as an object with properties, rather than |
| // a sequence of arguments to the StructType constructor. |
| AppendString(result, "{"); |
| } |
| |
| // Serialize each field of the struct recursively. Each field must |
| // be able to ImplicitConvert successfully. |
| const FieldInfoHash* fields = StructType::GetFieldInfo(typeObj); |
| size_t length = fields->count(); |
| Vector<const FieldInfoHash::Entry*, 64, SystemAllocPolicy> fieldsArray; |
| if (!fieldsArray.resize(length)) |
| return false; |
| |
| for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) |
| fieldsArray[r.front().value().mIndex] = &r.front(); |
| |
| for (size_t i = 0; i < length; ++i) { |
| const FieldInfoHash::Entry* entry = fieldsArray[i]; |
| |
| if (isImplicit) { |
| AppendString(result, "\""); |
| AppendString(result, entry->key()); |
| AppendString(result, "\": "); |
| } |
| |
| char* fieldData = static_cast<char*>(data) + entry->value().mOffset; |
| RootedObject entryType(cx, entry->value().mType); |
| if (!BuildDataSource(cx, entryType, fieldData, true, result)) |
| return false; |
| |
| if (i + 1 != length) |
| AppendString(result, ", "); |
| } |
| |
| if (isImplicit) |
| AppendString(result, "}"); |
| |
| break; |
| } |
| case TYPE_void_t: |
| MOZ_CRASH("invalid type"); |
| } |
| |
| return true; |
| } |
| |
| /******************************************************************************* |
| ** JSAPI callback function implementations |
| *******************************************************************************/ |
| |
| bool |
| ConstructAbstract(JSContext* cx, |
| unsigned argc, |
| Value* vp) |
| { |
| // Calling an abstract base class constructor is disallowed. |
| JS_ReportError(cx, "cannot construct from abstract type"); |
| return false; |
| } |
| |
| /******************************************************************************* |
| ** 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)) { |
| JS_ReportError(cx, "not a CType"); |
| return false; |
| } |
| |
| // 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: |
| JS_ReportError(cx, "cannot construct from void_t"); |
| return false; |
| case TYPE_function: |
| JS_ReportError(cx, "cannot construct from FunctionType; use FunctionType.ptr instead"); |
| 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_, |
| Value size_, |
| Value align_, |
| ffi_type* ffiType) |
| { |
| RootedString name(cx, name_); |
| RootedValue size(cx, size_); |
| RootedValue align(cx, align_); |
| |
| // 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, |
| Value size_, |
| Value align_, |
| ffi_type* ffiType) |
| { |
| RootedObject typeProto(cx, typeProto_); |
| RootedObject dataProto(cx, dataProto_); |
| RootedValue size(cx, size_); |
| RootedValue align(cx, align_); |
| |
| RootedString nameStr(cx, JS_NewStringCopyZ(cx, name)); |
| if (!nameStr) |
| return nullptr; |
| |
| // Create a new CType object with the common properties and slots. |
| RootedObject typeObj(cx, Create(cx, typeProto, dataProto, type, nameStr, size, align, ffiType)); |
| if (!typeObj) |
| return nullptr; |
| |
| // Define the CType as a 'propName' property on 'ctypesObj'. |
| if (!JS_DefineProperty(cx, ctypesObj, propName, typeObj, |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) |
| return nullptr; |
| |
| return typeObj; |
| } |
| |
| void |
| CType::Finalize(JSFreeOp* fop, JSObject* obj) |
| { |
| // Make sure our TypeCode slot is legit. If it's not, bail. |
| Value slot = JS_GetReservedSlot(obj, SLOT_TYPECODE); |
| if (slot.isUndefined()) |
| return; |
| |
| // The contents of our slots depends on what kind of type we are. |
| switch (TypeCode(slot.toInt32())) { |
| case TYPE_function: { |
| // Free the FunctionInfo. |
| slot = JS_GetReservedSlot(obj, SLOT_FNINFO); |
| if (!slot.isUndefined()) |
| FreeOp::get(fop)->delete_(static_cast<FunctionInfo*>(slot.toPrivate())); |
| break; |
| } |
| |
| case TYPE_struct: { |
| // Free the FieldInfoHash table. |
| slot = JS_GetReservedSlot(obj, SLOT_FIELDINFO); |
| if (!slot.isUndefined()) { |
| void* info = slot.toPrivate(); |
| FreeOp::get(fop)->delete_(static_cast<FieldInfoHash*>(info)); |
| } |
| } |
| |
| // Fall through. |
| case TYPE_array: { |
| // Free the ffi_type info. |
| slot = JS_GetReservedSlot(obj, SLOT_FFITYPE); |
| if (!slot.isUndefined()) { |
| ffi_type* ffiType = static_cast<ffi_type*>(slot.toPrivate()); |
| FreeOp::get(fop)->free_(ffiType->elements); |
| FreeOp::get(fop)->delete_(ffiType); |
| } |
| |
| break; |
| } |
| default: |
| // Nothing to do here. |
| break; |
| } |
| } |
| |
| void |
| CType::Trace(JSTracer* trc, JSObject* obj) |
| { |
| // Make sure our TypeCode slot is legit. If it's not, bail. |
| Value slot = obj->as<NativeObject>().getSlot(SLOT_TYPECODE); |
| if (slot.isUndefined()) |
| return; |
| |
| // The contents of our slots depends on what kind of type we are. |
| switch (TypeCode(slot.toInt32())) { |
| case TYPE_struct: { |
| slot = obj->as<NativeObject>().getReservedSlot(SLOT_FIELDINFO); |
| if (slot.isUndefined()) |
| return; |
| |
| FieldInfoHash* fields = static_cast<FieldInfoHash*>(slot.toPrivate()); |
| fields->trace(trc); |
| break; |
| } |
| case TYPE_function: { |
| // Check if we have a FunctionInfo. |
| slot = obj->as<NativeObject>().getReservedSlot(SLOT_FNINFO); |
| if (slot.isUndefined()) |
| return; |
| |
| FunctionInfo* fninfo = static_cast<FunctionInfo*>(slot.toPrivate()); |
| MOZ_ASSERT(fninfo); |
| |
| // Identify our objects to the tracer. |
| JS_CallObjectTracer(trc, &fninfo->mABI, "abi"); |
| JS_CallObjectTracer(trc, &fninfo->mReturnType, "returnType"); |
| for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) |
| JS_CallObjectTracer(trc, &fninfo->mArgTypes[i], "argType"); |
| |
| break; |
| } |
| default: |
| // Nothing to do here. |
| break; |
| } |
| } |
| |
| bool |
| CType::IsCType(JSObject* obj) |
| { |
| return JS_GetClass(obj) == &sCTypeClass; |
| } |
| |
| bool |
| CType::IsCTypeProto(JSObject* obj) |
| { |
| return JS_GetClass(obj) == &sCTypeProtoClass; |
| } |
| |
| TypeCode |
| CType::GetTypeCode(JSObject* typeObj) |
| { |
| MOZ_ASSERT(IsCType(typeObj)); |
| |
| Value result = JS_GetReservedSlot(typeObj, SLOT_TYPECODE); |
| return TypeCode(result.toInt32()); |
| } |
| |
| bool |
| CType::TypesEqual(JSObject* t1, JSObject* t2) |
| { |
| MOZ_ASSERT(IsCType(t1) && IsCType(t2)); |
| |
| // Fast path: check for object equality. |
| if (t1 == t2) |
| return true; |
| |
| // First, perform shallow comparison. |
| TypeCode c1 = GetTypeCode(t1); |
| TypeCode c2 = GetTypeCode(t2); |
| if (c1 != c2) |
| return false; |
| |
| // Determine whether the types require shallow or deep comparison. |
| switch (c1) { |
| case TYPE_pointer: { |
| // Compare base types. |
| JSObject* b1 = PointerType::GetBaseType(t1); |
| JSObject* b2 = PointerType::GetBaseType(t2); |
| return TypesEqual(b1, b2); |
| } |
| case TYPE_function: { |
| FunctionInfo* f1 = FunctionType::GetFunctionInfo(t1); |
| FunctionInfo* f2 = FunctionType::GetFunctionInfo(t2); |
| |
| // Compare abi, return type, and argument types. |
| if (f1->mABI != f2->mABI) |
| return false; |
| |
| if (!TypesEqual(f1->mReturnType, f2->mReturnType)) |
| return false; |
| |
| if (f1->mArgTypes.length() != f2->mArgTypes.length()) |
| return false; |
| |
| if (f1->mIsVariadic != f2->mIsVariadic) |
| return false; |
| |
| for (size_t i = 0; i < f1->mArgTypes.length(); ++i) { |
| if (!TypesEqual(f1->mArgTypes[i], f2->mArgTypes[i])) |
| return false; |
| } |
| |
| return true; |
| } |
| case TYPE_array: { |
| // Compare length, then base types. |
| // An undefined length array matches other undefined length arrays. |
| size_t s1 = 0, s2 = 0; |
| bool d1 = ArrayType::GetSafeLength(t1, &s1); |
| bool d2 = ArrayType::GetSafeLength(t2, &s2); |
| if (d1 != d2 || (d1 && s1 != s2)) |
| return false; |
| |
| JSObject* b1 = ArrayType::GetBaseType(t1); |
| JSObject* b2 = ArrayType::GetBaseType(t2); |
| return TypesEqual(b1, b2); |
| } |
| case TYPE_struct: |
| // Require exact type object equality. |
| return false; |
| default: |
| // Shallow comparison is sufficient. |
| return true; |
| } |
| } |
| |
| bool |
| CType::GetSafeSize(JSObject* obj, size_t* result) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value size = JS_GetReservedSlot(obj, SLOT_SIZE); |
| |
| // The "size" property can be an int, a double, or JS::UndefinedValue() |
| // (for arrays of undefined length), and must always fit in a size_t. |
| if (size.isInt32()) { |
| *result = size.toInt32(); |
| return true; |
| } |
| if (size.isDouble()) { |
| *result = Convert<size_t>(size.toDouble()); |
| return true; |
| } |
| |
| MOZ_ASSERT(size.isUndefined()); |
| return false; |
| } |
| |
| size_t |
| CType::GetSize(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value size = JS_GetReservedSlot(obj, SLOT_SIZE); |
| |
| MOZ_ASSERT(!size.isUndefined()); |
| |
| // The "size" property can be an int, a double, or JS::UndefinedValue() |
| // (for arrays of undefined length), and must always fit in a size_t. |
| // For callers who know it can never be JS::UndefinedValue(), return a size_t |
| // directly. |
| if (size.isInt32()) |
| return size.toInt32(); |
| return Convert<size_t>(size.toDouble()); |
| } |
| |
| bool |
| CType::IsSizeDefined(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value size = JS_GetReservedSlot(obj, SLOT_SIZE); |
| |
| // The "size" property can be an int, a double, or JS::UndefinedValue() |
| // (for arrays of undefined length), and must always fit in a size_t. |
| MOZ_ASSERT(size.isInt32() || size.isDouble() || size.isUndefined()); |
| return !size.isUndefined(); |
| } |
| |
| size_t |
| CType::GetAlignment(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value slot = JS_GetReservedSlot(obj, SLOT_ALIGN); |
| return static_cast<size_t>(slot.toInt32()); |
| } |
| |
| ffi_type* |
| CType::GetFFIType(JSContext* cx, JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value slot = JS_GetReservedSlot(obj, SLOT_FFITYPE); |
| |
| if (!slot.isUndefined()) { |
| return static_cast<ffi_type*>(slot.toPrivate()); |
| } |
| |
| UniquePtrFFIType result; |
| switch (CType::GetTypeCode(obj)) { |
| case TYPE_array: |
| result = ArrayType::BuildFFIType(cx, obj); |
| break; |
| |
| case TYPE_struct: |
| result = StructType::BuildFFIType(cx, obj); |
| break; |
| |
| default: |
| MOZ_CRASH("simple types must have an ffi_type"); |
| } |
| |
| if (!result) |
| return nullptr; |
| JS_SetReservedSlot(obj, SLOT_FFITYPE, PrivateValue(result.get())); |
| return result.release(); |
| } |
| |
| JSString* |
| CType::GetName(JSContext* cx, HandleObject obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value string = JS_GetReservedSlot(obj, SLOT_NAME); |
| if (!string.isUndefined()) |
| return string.toString(); |
| |
| // Build the type name lazily. |
| JSString* name = BuildTypeName(cx, obj); |
| if (!name) |
| return nullptr; |
| JS_SetReservedSlot(obj, SLOT_NAME, StringValue(name)); |
| return name; |
| } |
| |
| JSObject* |
| CType::GetProtoFromCtor(JSObject* obj, CTypeProtoSlot slot) |
| { |
| // Get ctypes.{Pointer,Array,Struct}Type.prototype from a reserved slot |
| // on the type constructor. |
| Value protoslot = js::GetFunctionNativeReserved(obj, SLOT_FN_CTORPROTO); |
| JSObject* proto = &protoslot.toObject(); |
| MOZ_ASSERT(proto); |
| MOZ_ASSERT(CType::IsCTypeProto(proto)); |
| |
| // Get the desired prototype. |
| Value result = JS_GetReservedSlot(proto, slot); |
| return &result.toObject(); |
| } |
| |
| JSObject* |
| CType::GetProtoFromType(JSContext* cx, JSObject* objArg, CTypeProtoSlot slot) |
| { |
| MOZ_ASSERT(IsCType(objArg)); |
| RootedObject obj(cx, objArg); |
| |
| // Get the prototype of the type object. |
| RootedObject proto(cx); |
| if (!JS_GetPrototype(cx, obj, &proto)) |
| return nullptr; |
| MOZ_ASSERT(proto); |
| MOZ_ASSERT(CType::IsCTypeProto(proto)); |
| |
| // Get the requested ctypes.{Pointer,Array,Struct,Function}Type.prototype. |
| Value result = JS_GetReservedSlot(proto, slot); |
| MOZ_ASSERT(result.isObject()); |
| return &result.toObject(); |
| } |
| |
| bool |
| CType::IsCTypeOrProto(HandleValue v) |
| { |
| if (!v.isObject()) |
| return false; |
| JSObject* obj = &v.toObject(); |
| return CType::IsCType(obj) || CType::IsCTypeProto(obj); |
| } |
| |
| bool |
| CType::PrototypeGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| unsigned slot = CType::IsCTypeProto(obj) ? (unsigned) SLOT_OURDATAPROTO |
| : (unsigned) SLOT_PROTO; |
| args.rval().set(JS_GetReservedSlot(obj, slot)); |
| MOZ_ASSERT(args.rval().isObject() || args.rval().isUndefined()); |
| return true; |
| } |
| |
| bool |
| CType::IsCType(HandleValue v) |
| { |
| return v.isObject() && CType::IsCType(&v.toObject()); |
| } |
| |
| bool |
| CType::NameGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| JSString* name = CType::GetName(cx, obj); |
| if (!name) |
| return false; |
| |
| args.rval().setString(name); |
| return true; |
| } |
| |
| bool |
| CType::SizeGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| args.rval().set(JS_GetReservedSlot(obj, SLOT_SIZE)); |
| MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined()); |
| return true; |
| } |
| |
| bool |
| CType::PtrGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| JSObject* pointerType = PointerType::CreateInternal(cx, obj); |
| if (!pointerType) |
| return false; |
| |
| args.rval().setObject(*pointerType); |
| return true; |
| } |
| |
| bool |
| CType::CreateArray(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject baseType(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!baseType) |
| return false; |
| if (!CType::IsCType(baseType)) { |
| JS_ReportError(cx, "not a CType"); |
| return false; |
| } |
| |
| // 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)) { |
| JS_ReportError(cx, "not a CType"); |
| return false; |
| } |
| |
| // 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)) |
| { |
| JS_ReportError(cx, "not a CType"); |
| return false; |
| } |
| |
| // Create the appropriate string depending on whether we're sCTypeClass or |
| // sCTypeProtoClass. |
| JSString* result; |
| if (CType::IsCType(obj)) { |
| AutoString source; |
| BuildTypeSource(cx, obj, false, source); |
| result = NewUCString(cx, source); |
| } else { |
| result = JS_NewStringCopyZ(cx, "[CType proto object]"); |
| } |
| if (!result) |
| return false; |
| |
| args.rval().setString(result); |
| return true; |
| } |
| |
| bool |
| CType::HasInstance(JSContext* cx, HandleObject obj, MutableHandleValue v, bool* bp) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| |
| Value slot = JS_GetReservedSlot(obj, SLOT_PROTO); |
| JS::Rooted<JSObject*> prototype(cx, &slot.toObject()); |
| MOZ_ASSERT(prototype); |
| MOZ_ASSERT(CData::IsCDataProto(prototype)); |
| |
| *bp = false; |
| if (v.isPrimitive()) |
| return true; |
| |
| RootedObject proto(cx, &v.toObject()); |
| for (;;) { |
| if (!JS_GetPrototype(cx, proto, &proto)) |
| return false; |
| if (!proto) |
| break; |
| if (proto == prototype) { |
| *bp = true; |
| break; |
| } |
| } |
| return true; |
| } |
| |
| static JSObject* |
| CType::GetGlobalCTypes(JSContext* cx, JSObject* objArg) |
| { |
| MOZ_ASSERT(CType::IsCType(objArg)); |
| |
| RootedObject obj(cx, objArg); |
| RootedObject objTypeProto(cx); |
| if (!JS_GetPrototype(cx, obj, &objTypeProto)) |
| return nullptr; |
| MOZ_ASSERT(objTypeProto); |
| MOZ_ASSERT(CType::IsCTypeProto(objTypeProto)); |
| |
| Value valCTypes = JS_GetReservedSlot(objTypeProto, SLOT_CTYPES); |
| MOZ_ASSERT(valCTypes.isObject()); |
| return &valCTypes.toObject(); |
| } |
| |
| /******************************************************************************* |
| ** ABI implementation |
| *******************************************************************************/ |
| |
| bool |
| ABI::IsABI(JSObject* obj) |
| { |
| return JS_GetClass(obj) == &sCABIClass; |
| } |
| |
| bool |
| ABI::ToSource(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 0) { |
| return ArgumentLengthError(cx, "ABI.prototype.toSource", "no", "s"); |
| } |
| |
| JSObject* obj = JS_THIS_OBJECT(cx, vp); |
| if (!obj) |
| return false; |
| if (!ABI::IsABI(obj)) { |
| JS_ReportError(cx, "not an ABI"); |
| return false; |
| } |
| |
| 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_WINAPI: |
| result = JS_NewStringCopyZ(cx, "ctypes.winapi_abi"); |
| break; |
| default: |
| JS_ReportError(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. |
| JSObject* typeObj = CType::Create(cx, typeProto, dataProto, TYPE_pointer, |
| nullptr, Int32Value(sizeof(void*)), |
| Int32Value(ffi_type_pointer.alignment), |
| &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) { |
| JS_ReportError(cx, "not a PointerType"); |
| return false; |
| } |
| |
| 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. |
| Value errVal = JS::UndefinedValue(); |
| 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)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| // Get pointer type and base type. |
| JSObject* typeObj = CData::GetCType(obj); |
| if (CType::GetTypeCode(typeObj) != TYPE_pointer) { |
| JS_ReportError(cx, "not a PointerType"); |
| return false; |
| } |
| |
| void* data = *static_cast<void**>(CData::GetData(obj)); |
| args.rval().setBoolean(data == nullptr); |
| return true; |
| } |
| |
| bool |
| PointerType::OffsetBy(JSContext* cx, const CallArgs& args, int offset) |
| { |
| JSObject* obj = JS_THIS_OBJECT(cx, args.base()); |
| if (!obj) |
| return false; |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject typeObj(cx, CData::GetCType(obj)); |
| if (CType::GetTypeCode(typeObj) != TYPE_pointer) { |
| JS_ReportError(cx, "not a PointerType"); |
| return false; |
| } |
| |
| RootedObject baseType(cx, PointerType::GetBaseType(typeObj)); |
| if (!CType::IsSizeDefined(baseType)) { |
| JS_ReportError(cx, "cannot modify pointer of undefined size"); |
| return false; |
| } |
| |
| size_t elementSize = CType::GetSize(baseType); |
| char* data = static_cast<char*>(*static_cast<void**>(CData::GetData(obj))); |
| void* address = data + offset * elementSize; |
| |
| // Create a PointerType CData object containing the new address. |
| JSObject* result = CData::Create(cx, typeObj, nullptr, &address, true); |
| if (!result) |
| return false; |
| |
| args.rval().setObject(*result); |
| return true; |
| } |
| |
| bool |
| PointerType::Increment(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return OffsetBy(cx, args, 1); |
| } |
| |
| bool |
| PointerType::Decrement(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return OffsetBy(cx, args, -1); |
| } |
| |
| bool |
| PointerType::ContentsGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| RootedObject baseType(cx, GetBaseType(CData::GetCType(obj))); |
| if (!CType::IsSizeDefined(baseType)) { |
| JS_ReportError(cx, "cannot get contents of undefined size"); |
| return false; |
| } |
| |
| void* data = *static_cast<void**>(CData::GetData(obj)); |
| if (data == nullptr) { |
| JS_ReportError(cx, "cannot read contents of null pointer"); |
| return false; |
| } |
| |
| 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)) { |
| JS_ReportError(cx, "cannot set contents of undefined size"); |
| return false; |
| } |
| |
| void* data = *static_cast<void**>(CData::GetData(obj)); |
| if (data == nullptr) { |
| JS_ReportError(cx, "cannot write contents to null pointer"); |
| return false; |
| } |
| |
| 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_ReportError(cx, "base size must be defined"); |
| return nullptr; |
| } |
| |
| RootedValue sizeVal(cx, JS::UndefinedValue()); |
| RootedValue lengthVal(cx, JS::UndefinedValue()); |
| 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) { |
| JS_ReportError(cx, "size overflow"); |
| return nullptr; |
| } |
| if (!SizeTojsval(cx, size, &sizeVal) || |
| !SizeTojsval(cx, length, &lengthVal)) |
| return nullptr; |
| } |
| |
| size_t align = 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, Int32Value(align), 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) { |
| JS_ReportError(cx, "not an ArrayType"); |
| return false; |
| } |
| |
| // Decide whether we have an object to initialize from. We'll override this |
| // if we get a length argument instead. |
| bool convertObject = args.length() == 1; |
| |
| // Check if we're an array of undefined length. If we are, allow construction |
| // with a length argument, or with an actual JS array. |
| if (CType::IsSizeDefined(obj)) { |
| if (args.length() > 1) { |
| return ArgumentLengthError(cx, "size defined ArrayType constructor", |
| "at most one", ""); |
| } |
| |
| } else { |
| if (args.length() != 1) { |
| return ArgumentLengthError(cx, "size undefined ArrayType constructor", |
| "one", ""); |
| } |
| |
| RootedObject baseType(cx, GetBaseType(obj)); |
| |
| size_t length; |
| if (jsvalToSize(cx, args[0], false, &length)) { |
| // Have a length, rather than an object to initialize from. |
| convertObject = false; |
| |
| } else if (args[0].isObject()) { |
| // We were given an object with a .length property. |
| // This could be a JS array, or a CData array. |
| RootedObject arg(cx, &args[0].toObject()); |
| RootedValue lengthVal(cx); |
| if (!JS_GetProperty(cx, arg, "length", &lengthVal) || |
| !jsvalToSize(cx, lengthVal, false, &length)) { |
| return ArgumentTypeMismatch(cx, "", |
| "size undefined ArrayType constructor", |
| "an array object or integer"); |
| } |
| |
| } else if (args[0].isString()) { |
| // We were given a string. Size the array to the appropriate length, |
| // including space for the terminator. |
| JSString* sourceString = args[0].toString(); |
| size_t sourceLength = sourceString->length(); |
| JSLinearString* sourceLinear = sourceString->ensureLinear(cx); |
| if (!sourceLinear) |
| return false; |
| |
| switch (CType::GetTypeCode(baseType)) { |
| case TYPE_char: |
| case TYPE_signed_char: |
| case TYPE_unsigned_char: { |
| // Determine the UTF-8 length. |
| length = GetDeflatedUTF8StringLength(cx, sourceLinear); |
| if (length == (size_t) -1) |
| return false; |
| |
| ++length; |
| break; |
| } |
| case TYPE_char16_t: |
| length = sourceLength + 1; |
| break; |
| default: |
| return ConvError(cx, obj, args[0], ConversionType::Construct); |
| } |
| |
| } else { |
| return ArgumentTypeMismatch(cx, "", |
| "size undefined ArrayType constructor", |
| "an array object or integer"); |
| } |
| |
| // Construct a new ArrayType of defined length, for the new CData object. |
| obj = CreateInternal(cx, baseType, length, true); |
| if (!obj) |
| return false; |
| } |
| |
| JSObject* result = CData::Create(cx, obj, nullptr, nullptr, true); |
| if (!result) |
| return false; |
| |
| args.rval().setObject(*result); |
| |
| if (convertObject) { |
| if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result), |
| ConversionType::Construct)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| JSObject* |
| ArrayType::GetBaseType(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); |
| |
| Value type = JS_GetReservedSlot(obj, SLOT_ELEMENT_T); |
| MOZ_ASSERT(!type.isNull()); |
| return &type.toObject(); |
| } |
| |
| bool |
| ArrayType::GetSafeLength(JSObject* obj, size_t* result) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); |
| |
| Value length = JS_GetReservedSlot(obj, SLOT_LENGTH); |
| |
| // The "length" property can be an int, a double, or JS::UndefinedValue() |
| // (for arrays of undefined length), and must always fit in a size_t. |
| if (length.isInt32()) { |
| *result = length.toInt32(); |
| return true; |
| } |
| if (length.isDouble()) { |
| *result = Convert<size_t>(length.toDouble()); |
| return true; |
| } |
| |
| MOZ_ASSERT(length.isUndefined()); |
| return false; |
| } |
| |
| size_t |
| ArrayType::GetLength(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); |
| |
| Value length = JS_GetReservedSlot(obj, SLOT_LENGTH); |
| |
| MOZ_ASSERT(!length.isUndefined()); |
| |
| // The "length" property can be an int, a double, or JS::UndefinedValue() |
| // (for arrays of undefined length), and must always fit in a size_t. |
| // For callers who know it can never be JS::UndefinedValue(), return a size_t |
| // directly. |
| if (length.isInt32()) |
| return length.toInt32(); |
| return Convert<size_t>(length.toDouble()); |
| } |
| |
| UniquePtrFFIType |
| ArrayType::BuildFFIType(JSContext* cx, JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); |
| MOZ_ASSERT(CType::IsSizeDefined(obj)); |
| |
| JSObject* baseType = ArrayType::GetBaseType(obj); |
| ffi_type* ffiBaseType = CType::GetFFIType(cx, baseType); |
| if (!ffiBaseType) |
| return nullptr; |
| |
| size_t length = ArrayType::GetLength(obj); |
| |
| // Create an ffi_type to represent the array. This is necessary for the case |
| // where the array is part of a struct. Since libffi has no intrinsic |
| // support for array types, we approximate it by creating a struct type |
| // with elements of type 'baseType' and with appropriate size and alignment |
| // values. It would be nice to not do all the work of setting up 'elements', |
| // but some libffi platforms currently require that it be meaningful. I'm |
| // looking at you, x86_64. |
| auto ffiType = cx->make_unique<ffi_type>(); |
| if (!ffiType) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| ffiType->type = FFI_TYPE_STRUCT; |
| ffiType->size = CType::GetSize(obj); |
| ffiType->alignment = CType::GetAlignment(obj); |
| ffiType->elements = cx->pod_malloc<ffi_type*>(length + 1); |
| if (!ffiType->elements) { |
| JS_ReportAllocationOverflow(cx); |
| return nullptr; |
| } |
| |
| for (size_t i = 0; i < length; ++i) |
| ffiType->elements[i] = ffiBaseType; |
| ffiType->elements[length] = nullptr; |
| |
| return Move(ffiType); |
| } |
| |
| bool |
| ArrayType::IsArrayType(HandleValue v) |
| { |
| if (!v.isObject()) |
| return false; |
| JSObject* obj = &v.toObject(); |
| return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array; |
| } |
| |
| bool |
| ArrayType::IsArrayOrArrayType(HandleValue v) |
| { |
| if (!v.isObject()) |
| return false; |
| JSObject* obj = &v.toObject(); |
| |
| // Allow both CTypes and CDatas of the ArrayType persuasion by extracting the |
| // CType if we're dealing with a CData. |
| if (CData::IsCData(obj)) { |
| obj = CData::GetCType(obj); |
| } |
| return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array; |
| } |
| |
| bool |
| ArrayType::ElementTypeGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| args.rval().set(JS_GetReservedSlot(obj, SLOT_ELEMENT_T)); |
| MOZ_ASSERT(args.rval().isObject()); |
| return true; |
| } |
| |
| bool |
| ArrayType::LengthGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| JSObject* obj = &args.thisv().toObject(); |
| |
| // This getter exists for both CTypes and CDatas of the ArrayType persuasion. |
| // If we're dealing with a CData, get the CType from it. |
| if (CData::IsCData(obj)) |
| obj = CData::GetCType(obj); |
| |
| args.rval().set(JS_GetReservedSlot(obj, SLOT_LENGTH)); |
| MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined()); |
| return true; |
| } |
| |
| bool |
| ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp) |
| { |
| // This should never happen, but we'll check to be safe. |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| // 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; |
| if (!ok && JSID_IS_STRING(idval) && |
| !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) { |
| // 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 || index >= length) { |
| JS_ReportError(cx, "invalid index"); |
| return false; |
| } |
| |
| RootedObject baseType(cx, GetBaseType(typeObj)); |
| size_t elementSize = CType::GetSize(baseType); |
| char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index; |
| return ConvertToJS(cx, baseType, obj, data, false, false, vp); |
| } |
| |
| bool |
| ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp, |
| ObjectOpResult& result) |
| { |
| // This should never happen, but we'll check to be safe. |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| // 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; |
| if (!ok && JSID_IS_STRING(idval) && |
| !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) { |
| // 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 || index >= length) { |
| JS_ReportError(cx, "invalid index"); |
| return false; |
| } |
| |
| RootedObject baseType(cx, GetBaseType(typeObj)); |
| size_t elementSize = CType::GetSize(baseType); |
| char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index; |
| if (!ImplicitConvert(cx, vp, baseType, data, ConversionType::Setter, |
| nullptr, nullptr, 0, typeObj, index)) |
| return false; |
| return result.succeed(); |
| } |
| |
| bool |
| ArrayType::AddressOfElement(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!obj) |
| return false; |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject typeObj(cx, CData::GetCType(obj)); |
| if (CType::GetTypeCode(typeObj) != TYPE_array) { |
| JS_ReportError(cx, "not an ArrayType"); |
| return false; |
| } |
| |
| 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) || |
| index >= length) { |
| JS_ReportError(cx, "invalid index"); |
| return false; |
| } |
| |
| // Manually set the pointer inside the object, so we skip the conversion step. |
| void** data = static_cast<void**>(CData::GetData(result)); |
| size_t elementSize = CType::GetSize(baseType); |
| *data = static_cast<char*>(CData::GetData(obj)) + elementSize * index; |
| return true; |
| } |
| |
| /******************************************************************************* |
| ** StructType implementation |
| *******************************************************************************/ |
| |
| // For a struct field descriptor 'val' of the form { name : type }, extract |
| // 'name' and 'type'. |
| static JSFlatString* |
| ExtractStructField(JSContext* cx, Value val, MutableHandleObject typeObj) |
| { |
| if (val.isPrimitive()) { |
| JS_ReportError(cx, "struct field descriptors require a valid name and type"); |
| return nullptr; |
| } |
| |
| RootedObject obj(cx, &val.toObject()); |
| Rooted<IdVector> props(cx, IdVector(cx)); |
| if (!JS_Enumerate(cx, obj, &props)) |
| return nullptr; |
| |
| // make sure we have one, and only one, property |
| if (props.length() != 1) { |
| JS_ReportError(cx, "struct field descriptors must contain one property"); |
| return nullptr; |
| } |
| |
| RootedId nameid(cx, props[0]); |
| if (!JSID_IS_STRING(nameid)) { |
| JS_ReportError(cx, "struct field descriptors require a valid name and type"); |
| return nullptr; |
| } |
| |
| RootedValue propVal(cx); |
| if (!JS_GetPropertyById(cx, obj, nameid, &propVal)) |
| return nullptr; |
| |
| if (propVal.isPrimitive() || !CType::IsCType(&propVal.toObject())) { |
| JS_ReportError(cx, "struct field descriptors require a valid name and type"); |
| 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) { |
| JS_ReportError(cx, "struct field types must have defined and nonzero size"); |
| return nullptr; |
| } |
| |
| return JSID_TO_FLAT_STRING(nameid); |
| } |
| |
| // For a struct field with 'name' and 'type', add an element of the form |
| // { name : type }. |
| static bool |
| AddFieldToArray(JSContext* cx, |
| MutableHandleValue element, |
| JSFlatString* name_, |
| JSObject* typeObj_) |
| { |
| RootedObject typeObj(cx, typeObj_); |
| Rooted<JSFlatString*> name(cx, name_); |
| RootedObject fieldObj(cx, JS_NewPlainObject(cx)); |
| if (!fieldObj) |
| return false; |
| |
| element.setObject(*fieldObj); |
| |
| AutoStableStringChars nameChars(cx); |
| if (!nameChars.initTwoByte(cx, name)) |
| return false; |
| |
| if (!JS_DefineUCProperty(cx, fieldObj, |
| nameChars.twoByteChars(), name->length(), |
| typeObj, |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) |
| return false; |
| |
| return JS_FreezeObject(cx, fieldObj); |
| } |
| |
| bool |
| StructType::Create(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| // Construct and return a new StructType object. |
| if (args.length() < 1 || args.length() > 2) { |
| return ArgumentLengthError(cx, "StructType", "one or two", "s"); |
| } |
| |
| Value name = args[0]; |
| if (!name.isString()) { |
| return ArgumentTypeMismatch(cx, "first ", "StructType", "a string"); |
| } |
| |
| // Get ctypes.StructType.prototype from the ctypes.StructType constructor. |
| RootedObject typeProto(cx, CType::GetProtoFromCtor(&args.callee(), SLOT_STRUCTPROTO)); |
| |
| // Create a simple StructType with no defined fields. The result will be |
| // non-instantiable as CData, will have no 'prototype' property, and will |
| // have undefined size and alignment and no ffi_type. |
| RootedObject result(cx, CType::Create(cx, typeProto, nullptr, TYPE_struct, |
| name.toString(), JS::UndefinedValue(), |
| JS::UndefinedValue(), 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; |
| } |
| |
| static void |
| PostBarrierCallback(JSTracer* trc, JSString* key, void* data) |
| { |
| typedef HashMap<JSFlatString*, |
| UnbarrieredFieldInfo, |
| FieldHashPolicy, |
| SystemAllocPolicy> UnbarrieredFieldInfoHash; |
| |
| UnbarrieredFieldInfoHash* table = reinterpret_cast<UnbarrieredFieldInfoHash*>(data); |
| JSString* prior = key; |
| JS_CallUnbarrieredStringTracer(trc, &key, "CType fieldName"); |
| table->rekeyIfMoved(JS_ASSERT_STRING_IS_FLAT(prior), JS_ASSERT_STRING_IS_FLAT(key)); |
| } |
| |
| bool |
| StructType::DefineInternal(JSContext* cx, JSObject* typeObj_, JSObject* fieldsObj_) |
| { |
| RootedObject typeObj(cx, typeObj_); |
| RootedObject fieldsObj(cx, fieldsObj_); |
| |
| uint32_t len; |
| ASSERT_OK(JS_GetArrayLength(cx, fieldsObj, &len)); |
| |
| // Get the common prototype for CData objects of this type from |
| // ctypes.CType.prototype. |
| RootedObject dataProto(cx, CType::GetProtoFromType(cx, typeObj, SLOT_STRUCTDATAPROTO)); |
| if (!dataProto) |
| return false; |
| |
| // Set up the 'prototype' and 'prototype.constructor' properties. |
| // The prototype will reflect the struct fields as properties on CData objects |
| // created from this type. |
| RootedObject prototype(cx, JS_NewObjectWithGivenProto(cx, &sCDataProtoClass, dataProto)); |
| if (!prototype) |
| return false; |
| |
| if (!JS_DefineProperty(cx, prototype, "constructor", typeObj, |
| JSPROP_READONLY | JSPROP_PERMANENT)) |
| return false; |
| |
| // Create a FieldInfoHash to stash on the type object. |
| Rooted<FieldInfoHash> fields(cx); |
| if (!fields.init(len)) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| // Process the field types. |
| size_t structSize, structAlign; |
| if (len != 0) { |
| structSize = 0; |
| structAlign = 0; |
| |
| for (uint32_t i = 0; i < len; ++i) { |
| RootedValue item(cx); |
| if (!JS_GetElement(cx, fieldsObj, i, &item)) |
| return false; |
| |
| RootedObject fieldType(cx, nullptr); |
| Rooted<JSFlatString*> name(cx, ExtractStructField(cx, item, &fieldType)); |
| if (!name) |
| return false; |
| |
| // Make sure each field name is unique |
| FieldInfoHash::AddPtr entryPtr = fields.lookupForAdd(name); |
| if (entryPtr) { |
| JS_ReportError(cx, "struct fields must have unique names"); |
| return false; |
| } |
| |
| // 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) { |
| JS_ReportError(cx, "size overflow"); |
| return false; |
| } |
| |
| // Add field name to the hash |
| FieldInfo info; |
| info.mType = fieldType; |
| info.mIndex = i; |
| info.mOffset = fieldOffset; |
| ASSERT_OK(fields.add(entryPtr, name, info)); |
| JS_StoreStringPostBarrierCallback(cx, PostBarrierCallback, name, fields.address()); |
| |
| 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) { |
| JS_ReportError(cx, "size overflow"); |
| 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)) |
| return false; |
| |
| // Move the field hash to the heap and store it in the typeObj. |
| FieldInfoHash *heapHash = cx->new_<FieldInfoHash>(mozilla::Move(fields.get())); |
| if (!heapHash) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| MOZ_ASSERT(heapHash->initialized()); |
| JS_SetReservedSlot(typeObj, SLOT_FIELDINFO, PrivateValue(heapHash)); |
| |
| JS_SetReservedSlot(typeObj, SLOT_SIZE, sizeVal); |
| JS_SetReservedSlot(typeObj, SLOT_ALIGN, Int32Value(structAlign)); |
| //if (!JS_FreezeObject(cx, prototype)0 // XXX fixme - see bug 541212! |
| // return false; |
| JS_SetReservedSlot(typeObj, SLOT_PROTO, ObjectValue(*prototype)); |
| return true; |
| } |
| |
| UniquePtrFFIType |
| StructType::BuildFFIType(JSContext* cx, JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); |
| MOZ_ASSERT(CType::IsSizeDefined(obj)); |
| |
| const FieldInfoHash* fields = GetFieldInfo(obj); |
| size_t len = fields->count(); |
| |
| size_t structSize = CType::GetSize(obj); |
| size_t structAlign = CType::GetAlignment(obj); |
| |
| auto ffiType = cx->make_unique<ffi_type>(); |
| if (!ffiType) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| ffiType->type = FFI_TYPE_STRUCT; |
| |
| size_t count = len != 0 ? len + 1 : 2; |
| auto elements = cx->make_pod_array<ffi_type*>(count); |
| if (!elements) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| if (len != 0) { |
| elements[len] = nullptr; |
| |
| for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { |
| const FieldInfoHash::Entry& entry = r.front(); |
| ffi_type* fieldType = CType::GetFFIType(cx, entry.value().mType); |
| if (!fieldType) |
| return nullptr; |
| elements[entry.value().mIndex] = fieldType; |
| } |
| } else { |
| // Represent an empty struct as having a size of 1 byte, just like C++. |
| MOZ_ASSERT(structSize == 1); |
| MOZ_ASSERT(structAlign == 1); |
| elements[0] = &ffi_type_uint8; |
| elements[1] = nullptr; |
| } |
| |
| ffiType->elements = elements.release(); |
| |
| #ifdef DEBUG |
| // Perform a sanity check: the result of our struct size and alignment |
| // calculations should match libffi's. We force it to do this calculation |
| // by calling ffi_prep_cif. |
| ffi_cif cif; |
| ffiType->size = 0; |
| ffiType->alignment = 0; |
| ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 0, ffiType.get(), nullptr); |
| MOZ_ASSERT(status == FFI_OK); |
| MOZ_ASSERT(structSize == ffiType->size); |
| MOZ_ASSERT(structAlign == ffiType->alignment); |
| #else |
| // Fill in the ffi_type's size and align fields. This makes libffi treat the |
| // type as initialized; it will not recompute the values. (We assume |
| // everything agrees; if it doesn't, we really want to know about it, which |
| // is the purpose of the above debug-only check.) |
| ffiType->size = structSize; |
| ffiType->alignment = structAlign; |
| #endif |
| |
| return Move(ffiType); |
| } |
| |
| bool |
| StructType::Define(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!obj) |
| return false; |
| if (!CType::IsCType(obj) || |
| CType::GetTypeCode(obj) != TYPE_struct) { |
| JS_ReportError(cx, "not a StructType"); |
| return false; |
| } |
| |
| if (CType::IsSizeDefined(obj)) { |
| JS_ReportError(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) { |
| JS_ReportError(cx, "not a StructType"); |
| return false; |
| } |
| |
| if (!CType::IsSizeDefined(obj)) { |
| JS_ReportError(cx, "cannot construct an opaque StructType"); |
| return false; |
| } |
| |
| JSObject* result = CData::Create(cx, obj, nullptr, nullptr, true); |
| if (!result) |
| return false; |
| |
| args.rval().setObject(*result); |
| |
| if (args.length() == 0) |
| return true; |
| |
| char* buffer = static_cast<char*>(CData::GetData(result)); |
| const FieldInfoHash* fields = GetFieldInfo(obj); |
| |
| if (args.length() == 1) { |
| // There are two possible interpretations of the argument: |
| // 1) It may be an object '{ ... }' with properties representing the |
| // struct fields intended to ExplicitConvert wholesale to our StructType. |
| // 2) If the struct contains one field, the arg may be intended to |
| // ImplicitConvert directly to that arg's CType. |
| // Thankfully, the conditions for these two possibilities to succeed |
| // are mutually exclusive, so we can pick the right one. |
| |
| // Try option 1) first. |
| if (ExplicitConvert(cx, args[0], obj, buffer, ConversionType::Construct)) |
| return true; |
| |
| if (fields->count() != 1) |
| return false; |
| |
| // If ExplicitConvert failed, and there is no pending exception, then assume |
| // hard failure (out of memory, or some other similarly serious condition). |
| if (!JS_IsExceptionPending(cx)) |
| return false; |
| |
| // Otherwise, assume soft failure, and clear the pending exception so that we |
| // can throw a different one as required. |
| JS_ClearPendingException(cx); |
| |
| // Fall through to try option 2). |
| } |
| |
| // We have a type constructor of the form 'ctypes.StructType(a, b, c, ...)'. |
| // ImplicitConvert each field. |
| if (args.length() == fields->count()) { |
| for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { |
| const FieldInfo& field = r.front().value(); |
| MOZ_ASSERT(field.mIndex < fields->count()); /* Quantified invariant */ |
| if (!ImplicitConvert(cx, args[field.mIndex], field.mType, |
| buffer + field.mOffset, ConversionType::Construct, |
| nullptr, nullptr, 0, obj, field.mIndex)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| size_t count = fields->count(); |
| if (count >= 2) { |
| char fieldLengthStr[32]; |
| JS_snprintf(fieldLengthStr, 32, "0, 1, or %u", count); |
| return ArgumentLengthError(cx, "StructType constructor", fieldLengthStr, |
| "s"); |
| } |
| return ArgumentLengthError(cx, "StructType constructor", "at most one", ""); |
| } |
| |
| const FieldInfoHash* |
| StructType::GetFieldInfo(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); |
| |
| Value slot = JS_GetReservedSlot(obj, SLOT_FIELDINFO); |
| MOZ_ASSERT(!slot.isUndefined() && slot.toPrivate()); |
| |
| return static_cast<const FieldInfoHash*>(slot.toPrivate()); |
| } |
| |
| const FieldInfo* |
| StructType::LookupField(JSContext* cx, JSObject* obj, JSFlatString* name) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); |
| |
| FieldInfoHash::Ptr ptr = GetFieldInfo(obj)->lookup(name); |
| if (ptr) |
| return &ptr->value(); |
| |
| JSAutoByteString bytes(cx, name); |
| if (!bytes) |
| return nullptr; |
| |
| JS_ReportError(cx, "%s does not name a field", bytes.ptr()); |
| 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()) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject obj(cx, &args.thisv().toObject()); |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| JSObject* typeObj = CData::GetCType(obj); |
| if (CType::GetTypeCode(typeObj) != TYPE_struct) { |
| JS_ReportError(cx, "not a StructType"); |
| return false; |
| } |
| |
| RootedValue nameVal(cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME)); |
| Rooted<JSFlatString*> name(cx, JS_FlattenString(cx, nameVal.toString())); |
| if (!name) |
| return false; |
| |
| const FieldInfo* field = LookupField(cx, typeObj, name); |
| if (!field) |
| return false; |
| |
| char* data = static_cast<char*>(CData::GetData(obj)) + field->mOffset; |
| RootedObject fieldType(cx, field->mType); |
| return ConvertToJS(cx, fieldType, obj, data, false, false, args.rval()); |
| } |
| |
| bool |
| StructType::FieldSetter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (!args.thisv().isObject()) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject obj(cx, &args.thisv().toObject()); |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject typeObj(cx, CData::GetCType(obj)); |
| if (CType::GetTypeCode(typeObj) != TYPE_struct) { |
| JS_ReportError(cx, "not a StructType"); |
| return false; |
| } |
| |
| RootedValue nameVal(cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME)); |
| Rooted<JSFlatString*> name(cx, JS_FlattenString(cx, nameVal.toString())); |
| if (!name) |
| return false; |
| |
| const FieldInfo* field = LookupField(cx, typeObj, name); |
| if (!field) |
| return false; |
| |
| args.rval().setUndefined(); |
| |
| char* data = static_cast<char*>(CData::GetData(obj)) + field->mOffset; |
| return ImplicitConvert(cx, args.get(0), field->mType, data, ConversionType::Setter, nullptr, |
| nullptr, 0, typeObj, field->mIndex); |
| } |
| |
| bool |
| StructType::AddressOfField(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!obj) |
| return false; |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| JSObject* typeObj = CData::GetCType(obj); |
| if (CType::GetTypeCode(typeObj) != TYPE_struct) { |
| JS_ReportError(cx, "not a StructType"); |
| return false; |
| } |
| |
| if (args.length() != 1) { |
| return ArgumentLengthError(cx, "StructType.prototype.addressOfField", |
| "one", ""); |
| } |
| |
| if (!args[0].isString()) { |
| return ArgumentTypeMismatch(cx, "", "StructType.prototype.addressOfField", |
| "a string"); |
| } |
| |
| JSFlatString* str = JS_FlattenString(cx, args[0].toString()); |
| if (!str) |
| return false; |
| |
| const FieldInfo* field = LookupField(cx, typeObj, str); |
| if (!field) |
| return false; |
| |
| RootedObject baseType(cx, field->mType); |
| RootedObject pointerType(cx, PointerType::CreateInternal(cx, baseType)); |
| if (!pointerType) |
| return false; |
| |
| // Create a PointerType CData object containing null. |
| JSObject* result = CData::Create(cx, pointerType, nullptr, nullptr, true); |
| if (!result) |
| return false; |
| |
| args.rval().setObject(*result); |
| |
| // Manually set the pointer inside the object, so we skip the conversion step. |
| void** data = static_cast<void**>(CData::GetData(result)); |
| *data = static_cast<char*>(CData::GetData(obj)) + field->mOffset; |
| return true; |
| } |
| |
| /******************************************************************************* |
| ** FunctionType implementation |
| *******************************************************************************/ |
| |
| // Helper class for handling allocation of function arguments. |
| struct AutoValue |
| { |
| AutoValue() : mData(nullptr) { } |
| |
| ~AutoValue() |
| { |
| js_free(mData); |
| } |
| |
| bool SizeToType(JSContext* cx, JSObject* type) |
| { |
| // Allocate a minimum of sizeof(ffi_arg) to handle small integers. |
| size_t size = Align(CType::GetSize(type), sizeof(ffi_arg)); |
| mData = js_malloc(size); |
| if (mData) |
| memset(mData, 0, size); |
| return mData != nullptr; |
| } |
| |
| void* mData; |
| }; |
| |
| static bool |
| GetABI(JSContext* cx, Value 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_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, Value type) |
| { |
| if (type.isPrimitive() || !CType::IsCType(type.toObjectOrNull())) { |
| JS_ReportError(cx, "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 |
| JS_ReportError(cx, "Cannot have void or function argument type"); |
| return nullptr; |
| } |
| |
| if (!CType::IsSizeDefined(result)) { |
| JS_ReportError(cx, "Argument 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, Value type) |
| { |
| if (type.isPrimitive() || !CType::IsCType(type.toObjectOrNull())) { |
| JS_ReportError(cx, "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) { |
| JS_ReportError(cx, "Return type cannot be an array or function"); |
| return nullptr; |
| } |
| |
| if (typeCode != TYPE_void_t && !CType::IsSizeDefined(result)) { |
| JS_ReportError(cx, "Return 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, Value 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; |
| if (!GetABI(cx, ObjectOrNullValue(fninfo->mABI), &abi)) { |
| JS_ReportError(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_ReportError(cx, "Invalid ABI specification"); |
| return false; |
| case FFI_BAD_TYPEDEF: |
| JS_ReportError(cx, "Invalid type specification"); |
| return false; |
| default: |
| JS_ReportError(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_WINAPI: |
| // For cdecl or WINAPI functions, no mangling is necessary. |
| AppendString(result, name); |
| break; |
| |
| case ABI_STDCALL: { |
| #if (defined(_WIN32) && !defined(_WIN64)) || defined(_OS2) |
| // On WIN32, stdcall functions look like: |
| // _foo@40 |
| // where 'foo' is the function name, and '40' is the aligned size of the |
| // arguments. |
| AppendString(result, "_"); |
| AppendString(result, name); |
| AppendString(result, "@"); |
| |
| // Compute the suffix by aligning each argument to sizeof(ffi_arg). |
| size_t size = 0; |
| for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { |
| JSObject* argType = fninfo->mArgTypes[i]; |
| size += Align(CType::GetSize(argType), sizeof(ffi_arg)); |
| } |
| |
| IntegerToString(size, 10, result); |
| #elif defined(_WIN64) |
| // On Win64, stdcall is an alias to the default ABI for compatibility, so no |
| // mangling is done. |
| AppendString(result, name); |
| #endif |
| break; |
| } |
| |
| case INVALID_ABI: |
| MOZ_CRASH("invalid abi"); |
| } |
| } |
| |
| static bool |
| CreateFunctionInfo(JSContext* cx, |
| HandleObject typeObj, |
| HandleValue abiType, |
| HandleObject returnType, |
| const HandleValueArray& args) |
| { |
| FunctionInfo* fninfo(cx->new_<FunctionInfo>()); |
| if (!fninfo) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| // Stash the FunctionInfo in a reserved slot. |
| JS_SetReservedSlot(typeObj, SLOT_FNINFO, PrivateValue(fninfo)); |
| |
| ffi_abi abi; |
| if (!GetABI(cx, abiType, &abi)) { |
| JS_ReportError(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_ReportError(cx, "\"...\" may not be the first and only parameter " |
| "type of a variadic function declaration"); |
| return false; |
| } |
| if (i < args.length() - 1) { |
| JS_ReportError(cx, "\"...\" must be the last parameter type of a " |
| "variadic function declaration"); |
| return false; |
| } |
| if (GetABICode(fninfo->mABI) != ABI_DEFAULT) { |
| JS_ReportError(cx, "Variadic functions must use the __cdecl calling " |
| "convention"); |
| return false; |
| } |
| break; |
| } |
| |
| JSObject* argType = PrepareType(cx, 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::UndefinedValue(), |
| JS::UndefinedValue(), 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, |
| Value errVal) |
| { |
| MOZ_ASSERT(CType::GetTypeCode(typeObj) == TYPE_function); |
| |
| PRFuncPtr* data = static_cast<PRFuncPtr*>(CData::GetData(dataObj)); |
| |
| FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); |
| if (fninfo->mIsVariadic) { |
| JS_ReportError(cx, "Can't declare a variadic callback function"); |
| return false; |
| } |
| if (GetABICode(fninfo->mABI) == ABI_WINAPI) { |
| JS_ReportError(cx, "Can't declare a ctypes.winapi_abi callback function, " |
| "use ctypes.stdcall_abi instead"); |
| return false; |
| } |
| |
| RootedObject closureObj(cx, CClosure::Create(cx, typeObj, fnObj, thisObj, errVal, data)); |
| if (!closureObj) |
| return false; |
| |
| // Set the closure object as the referent of the new CData object. |
| JS_SetReservedSlot(dataObj, SLOT_REFERENT, ObjectValue(*closureObj)); |
| |
| // Seal the CData object, to prevent modification of the function pointer. |
| // This permanently associates this object with the closure, and avoids |
| // having to do things like reset SLOT_REFERENT when someone tries to |
| // change the pointer value. |
| // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter |
| // could be called on a frozen object. |
| return JS_FreezeObject(cx, dataObj); |
| } |
| |
| typedef Vector<AutoValue, 16, SystemAllocPolicy> AutoValueAutoArray; |
| |
| static bool |
| ConvertArgument(JSContext* cx, |
| HandleObject funObj, |
| unsigned argIndex, |
| HandleValue arg, |
| JSObject* type, |
| AutoValue* value, |
| AutoValueAutoArray* strings) |
| { |
| if (!value->SizeToType(cx, type)) { |
| JS_ReportAllocationOverflow(cx); |
| return false; |
| } |
| |
| bool freePointer = false; |
| if (!ImplicitConvert(cx, arg, type, value->mData, |
| ConversionType::Argument, &freePointer, |
| funObj, argIndex)) |
| return false; |
| |
| if (freePointer) { |
| // ImplicitConvert converted a string for us, which we have to free. |
| // Keep track of it. |
| if (!strings->growBy(1)) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| strings->back().mData = *static_cast<char**>(value->mData); |
| } |
| |
| return true; |
| } |
| |
| bool |
| FunctionType::Call(JSContext* cx, |
| unsigned argc, |
| Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| // get the callee object... |
| RootedObject obj(cx, &args.callee()); |
| if (!CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject typeObj(cx, CData::GetCType(obj)); |
| if (CType::GetTypeCode(typeObj) != TYPE_pointer) { |
| JS_ReportError(cx, "not a FunctionType.ptr"); |
| return false; |
| } |
| |
| typeObj = PointerType::GetBaseType(typeObj); |
| if (CType::GetTypeCode(typeObj) != TYPE_function) { |
| JS_ReportError(cx, "not a FunctionType.ptr"); |
| return false; |
| } |
| |
| FunctionInfo* fninfo = GetFunctionInfo(typeObj); |
| uint32_t argcFixed = fninfo->mArgTypes.length(); |
| |
| if ((!fninfo->mIsVariadic && args.length() != argcFixed) || |
| (fninfo->mIsVariadic && args.length() < argcFixed)) { |
| JS_ReportError(cx, "Number of arguments does not match declaration"); |
| return false; |
| } |
| |
| // 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_ReportError(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. |
| JS_ReportError(cx, "argument %d of type %s is not a CData object", |
| i, InformalValueTypeName(args[i])); |
| return false; |
| } |
| if (!(type = CData::GetCType(obj)) || |
| !(type = PrepareType(cx, ObjectValue(*type))) || |
| // Relying on ImplicitConvert only for the limited purpose of |
| // converting one CType to another (e.g., T[] to T*). |
| !ConvertArgument(cx, obj, i, args[i], type, &values[i], &strings) || |
| !(fninfo->mFFITypes[i] = CType::GetFFIType(cx, type))) { |
| // These functions report their own errors. |
| return false; |
| } |
| } |
| if (!PrepareCIF(cx, fninfo)) |
| return false; |
| } |
| |
| // initialize a pointer to an appropriate location, for storing the result |
| AutoValue returnValue; |
| TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType); |
| if (typeCode != TYPE_void_t && |
| !returnValue.SizeToType(cx, fninfo->mReturnType)) { |
| JS_ReportAllocationOverflow(cx); |
| return false; |
| } |
| |
| // Let the runtime callback know that we are about to call into C. |
| js::AutoCTypesActivityCallback autoCallback(cx, js::CTYPES_CALL_BEGIN, js::CTYPES_CALL_END); |
| |
| uintptr_t fn = *reinterpret_cast<uintptr_t*>(CData::GetData(obj)); |
| |
| #if defined(XP_WIN) |
| int32_t lastErrorStatus; // The status as defined by |GetLastError| |
| int32_t savedLastError = GetLastError(); |
| SetLastError(0); |
| #endif //defined(XP_WIN) |
| int errnoStatus; // The status as defined by |errno| |
| int savedErrno = errno; |
| errno = 0; |
| |
| ffi_call(&fninfo->mCIF, FFI_FN(fn), returnValue.mData, |
| reinterpret_cast<void**>(values.begin())); |
| |
| // Save error value. |
| // We need to save it before leaving the scope of |suspend| as destructing |
| // |suspend| has the side-effect of clearing |GetLastError| |
| // (see bug 684017). |
| |
| errnoStatus = errno; |
| #if defined(XP_WIN) |
| lastErrorStatus = GetLastError(); |
| SetLastError(savedLastError); |
| #endif // defined(XP_WIN) |
| |
| errno = savedErrno; |
| |
| // We're no longer calling into C. |
| autoCallback.DoEndCallback(); |
| |
| // Store the error value for later consultation with |ctypes.getStatus| |
| JSObject* objCTypes = CType::GetGlobalCTypes(cx, typeObj); |
| if (!objCTypes) |
| return false; |
| |
| JS_SetReservedSlot(objCTypes, SLOT_ERRNO, Int32Value(errnoStatus)); |
| #if defined(XP_WIN) |
| JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, Int32Value(lastErrorStatus)); |
| #endif // defined(XP_WIN) |
| |
| // Small integer types get returned as a word-sized ffi_arg. Coerce it back |
| // into the correct size for ConvertToJS. |
| switch (typeCode) { |
| #define INTEGRAL_CASE(name, type, ffiType) \ |
| case TYPE_##name: \ |
| if (sizeof(type) < sizeof(ffi_arg)) { \ |
| ffi_arg data = *static_cast<ffi_arg*>(returnValue.mData); \ |
| *static_cast<type*>(returnValue.mData) = static_cast<type>(data); \ |
| } \ |
| break; |
| CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) |
| #undef INTEGRAL_CASE |
| default: |
| break; |
| } |
| |
| // prepare a JS object from the result |
| RootedObject returnType(cx, fninfo->mReturnType); |
| return ConvertToJS(cx, returnType, nullptr, returnValue.mData, false, true, args.rval()); |
| } |
| |
| FunctionInfo* |
| FunctionType::GetFunctionInfo(JSObject* obj) |
| { |
| MOZ_ASSERT(CType::IsCType(obj)); |
| MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_function); |
| |
| Value slot = JS_GetReservedSlot(obj, SLOT_FNINFO); |
| MOZ_ASSERT(!slot.isUndefined() && slot.toPrivate()); |
| |
| return static_cast<FunctionInfo*>(slot.toPrivate()); |
| } |
| |
| bool |
| FunctionType::IsFunctionType(HandleValue v) |
| { |
| if (!v.isObject()) |
| return false; |
| JSObject* obj = &v.toObject(); |
| return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_function; |
| } |
| |
| bool |
| FunctionType::ArgTypesGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); |
| |
| args.rval().set(JS_GetReservedSlot(obj, SLOT_ARGS_T)); |
| if (!args.rval().isUndefined()) |
| return true; |
| |
| FunctionInfo* fninfo = GetFunctionInfo(obj); |
| size_t len = fninfo->mArgTypes.length(); |
| |
| // Prepare a new array. |
| JS::Rooted<JSObject*> argTypes(cx); |
| { |
| JS::AutoValueVector vec(cx); |
| if (!vec.resize(len)) |
| return false; |
| |
| for (size_t i = 0; i < len; ++i) |
| vec[i].setObject(*fninfo->mArgTypes[i]); |
| |
| argTypes = JS_NewArrayObject(cx, vec); |
| if (!argTypes) |
| return false; |
| } |
| |
| // Seal and cache it. |
| if (!JS_FreezeObject(cx, argTypes)) |
| return false; |
| JS_SetReservedSlot(obj, SLOT_ARGS_T, JS::ObjectValue(*argTypes)); |
| |
| args.rval().setObject(*argTypes); |
| return true; |
| } |
| |
| bool |
| FunctionType::ReturnTypeGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| // Get the returnType object from the FunctionInfo. |
| args.rval().setObject(*GetFunctionInfo(&args.thisv().toObject())->mReturnType); |
| return true; |
| } |
| |
| bool |
| FunctionType::ABIGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| // Get the abi object from the FunctionInfo. |
| args.rval().setObject(*GetFunctionInfo(&args.thisv().toObject())->mABI); |
| return true; |
| } |
| |
| bool |
| FunctionType::IsVariadicGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| args.rval().setBoolean(GetFunctionInfo(&args.thisv().toObject())->mIsVariadic); |
| return true; |
| } |
| |
| /******************************************************************************* |
| ** CClosure implementation |
| *******************************************************************************/ |
| |
| JSObject* |
| CClosure::Create(JSContext* cx, |
| HandleObject typeObj, |
| HandleObject fnObj, |
| HandleObject thisObj, |
| Value errVal_, |
| PRFuncPtr* fnptr) |
| { |
| RootedValue errVal(cx, errVal_); |
| 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. |
| mozilla::UniquePtr<uint8_t[], JS::FreePolicy> errResult; |
| if (!errVal.isUndefined()) { |
| |
| // Make sure the callback returns something. |
| if (CType::GetTypeCode(fninfo->mReturnType) == TYPE_void_t) { |
| JS_ReportError(cx, "A void callback can't pass an error sentinel"); |
| return nullptr; |
| } |
| |
| // With the exception of void, the FunctionType constructor ensures that |
| // the return type has a defined size. |
| MOZ_ASSERT(CType::IsSizeDefined(fninfo->mReturnType)); |
| |
| // Allocate a buffer for the return value. |
| size_t rvSize = CType::GetSize(fninfo->mReturnType); |
| errResult = result->zone()->make_pod_array<uint8_t>(rvSize); |
| if (!errResult) |
| return nullptr; |
| |
| // Do the value conversion. This might fail, in which case we throw. |
| if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(), |
| ConversionType::Return, nullptr, typeObj)) |
| return nullptr; |
| } |
| |
| ClosureInfo* cinfo = cx->new_<ClosureInfo>(JS_GetRuntime(cx)); |
| if (!cinfo) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| // Copy the important bits of context into cinfo. |
| cinfo->errResult = errResult.release(); |
| cinfo->closureObj = result; |
| cinfo->typeObj = typeObj; |
| cinfo->thisObj = thisObj; |
| cinfo->jsfnObj = fnObj; |
| |
| // Stash the ClosureInfo struct on our new object. |
| JS_SetReservedSlot(result, SLOT_CLOSUREINFO, PrivateValue(cinfo)); |
| |
| // Create an ffi_closure object and initialize it. |
| void* code; |
| cinfo->closure = |
| static_cast<ffi_closure*>(ffi_closure_alloc(sizeof(ffi_closure), &code)); |
| if (!cinfo->closure || !code) { |
| JS_ReportError(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_ReportError(cx, "couldn't create closure - libffi error"); |
| return nullptr; |
| } |
| |
| // Casting between void* and a function pointer is forbidden in C and C++. |
| // Do it via an integral type. |
| *fnptr = reinterpret_cast<PRFuncPtr>(reinterpret_cast<uintptr_t>(code)); |
| return result; |
| } |
| |
| void |
| CClosure::Trace(JSTracer* trc, JSObject* obj) |
| { |
| // Make sure our ClosureInfo slot is legit. If it's not, bail. |
| Value slot = JS_GetReservedSlot(obj, SLOT_CLOSUREINFO); |
| if (slot.isUndefined()) |
| return; |
| |
| ClosureInfo* cinfo = static_cast<ClosureInfo*>(slot.toPrivate()); |
| |
| // Identify our objects to the tracer. (There's no need to identify |
| // 'closureObj', since that's us.) |
| JS_CallObjectTracer(trc, &cinfo->typeObj, "typeObj"); |
| JS_CallObjectTracer(trc, &cinfo->jsfnObj, "jsfnObj"); |
| if (cinfo->thisObj) |
| JS_CallObjectTracer(trc, &cinfo->thisObj, "thisObj"); |
| } |
| |
| void |
| CClosure::Finalize(JSFreeOp* fop, JSObject* obj) |
| { |
| // Make sure our ClosureInfo slot is legit. If it's not, bail. |
| Value slot = JS_GetReservedSlot(obj, SLOT_CLOSUREINFO); |
| if (slot.isUndefined()) |
| return; |
| |
| ClosureInfo* cinfo = static_cast<ClosureInfo*>(slot.toPrivate()); |
| FreeOp::get(fop)->delete_(cinfo); |
| } |
| |
| void |
| CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData) |
| { |
| MOZ_ASSERT(cif); |
| MOZ_ASSERT(result); |
| MOZ_ASSERT(args); |
| MOZ_ASSERT(userData); |
| |
| // Retrieve the essentials from our closure object. |
| ArgClosure argClosure(cif, result, args, static_cast<ClosureInfo*>(userData)); |
| JSRuntime* rt = argClosure.cinfo->rt; |
| RootedObject fun(rt, argClosure.cinfo->jsfnObj); |
| |
| // Arbitrarily choose a cx in which to run this code. This is bad, as |
| // JSContexts are stateful and have options. The hope is to eliminate |
| // JSContexts (see bug 650361). |
| js::PrepareScriptEnvironmentAndInvoke(rt->contextList.getFirst(), 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(JS_GetRuntime(cx)); |
| |
| // Assert that our CIFs agree. |
| FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); |
| MOZ_ASSERT(cif == &fninfo->mCIF); |
| |
| TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType); |
| |
| // Initialize the result to zero, in case something fails. Small integer types |
| // are promoted to a word-sized ffi_arg, so we must be careful to zero the |
| // whole word. |
| size_t rvSize = 0; |
| if (cif->rtype != &ffi_type_void) { |
| rvSize = cif->rtype->size; |
| switch (typeCode) { |
| #define INTEGRAL_CASE(name, type, ffiType) case TYPE_##name: |
| CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) |
| #undef INTEGRAL_CASE |
| rvSize = Align(rvSize, sizeof(ffi_arg)); |
| break; |
| default: |
| break; |
| } |
| memset(result, 0, rvSize); |
| } |
| |
| // Set up an array for converted arguments. |
| JS::AutoValueVector argv(cx); |
| if (!argv.resize(cif->nargs)) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| for (uint32_t i = 0; i < cif->nargs; ++i) { |
| // Convert each argument, and have any CData objects created depend on |
| // the existing buffers. |
| RootedObject argType(cx, fninfo->mArgTypes[i]); |
| if (!ConvertToJS(cx, argType, nullptr, args[i], false, false, argv[i])) |
| return false; |
| } |
| |
| // Call the JS function. 'thisObj' may be nullptr, in which case the JS |
| // engine will find an appropriate object to use. |
| RootedValue rval(cx); |
| bool success = JS_CallFunctionValue(cx, thisObj, jsfnVal, argv, &rval); |
| |
| // Convert the result. Note that we pass 'ConversionType::Return', such that |
| // ImplicitConvert will *not* autoconvert a JS string into a pointer-to-char |
| // type, which would require an allocation that we can't track. The JS |
| // function must perform this conversion itself and return a PointerType |
| // CData; thusly, the burden of freeing the data is left to the user. |
| if (success && cif->rtype != &ffi_type_void) |
| success = ImplicitConvert(cx, rval, fninfo->mReturnType, result, |
| ConversionType::Return, nullptr, typeObj); |
| |
| if (!success) { |
| // Something failed. The callee may have thrown, or it may not have |
| // returned a value that ImplicitConvert() was happy with. Depending on how |
| // prudent the consumer has been, we may or may not have a recovery plan. |
| // |
| // Note that PrepareScriptEnvironmentAndInvoke should take care of reporting |
| // the exception. |
| |
| if (cinfo->errResult) { |
| // Good case: we have a sentinel that we can return. Copy it in place of |
| // the actual return value, and then proceed. |
| |
| // The buffer we're returning might be larger than the size of the return |
| // type, due to libffi alignment issues (see above). But it should never |
| // be smaller. |
| size_t copySize = CType::GetSize(fninfo->mReturnType); |
| MOZ_ASSERT(copySize <= rvSize); |
| memcpy(result, cinfo->errResult, copySize); |
| |
| // We still want to return false here, so that |
| // PrepareScriptEnvironmentAndInvoke will report the exception. |
| } else { |
| // Bad case: not much we can do here. The rv is already zeroed out, so we |
| // just return and hope for the best. |
| } |
| return false; |
| } |
| |
| // Small integer types must be returned as a word-sized ffi_arg. Coerce it |
| // back into the size libffi expects. |
| switch (typeCode) { |
| #define INTEGRAL_CASE(name, type, ffiType) \ |
| case TYPE_##name: \ |
| if (sizeof(type) < sizeof(ffi_arg)) { \ |
| ffi_arg data = *static_cast<type*>(result); \ |
| *static_cast<ffi_arg*>(result) = data; \ |
| } \ |
| break; |
| CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) |
| CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) |
| #undef INTEGRAL_CASE |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| /******************************************************************************* |
| ** CData implementation |
| *******************************************************************************/ |
| |
| // Create a new CData object of type 'typeObj' containing binary data supplied |
| // in 'source', optionally with a referent object 'refObj'. |
| // |
| // * 'typeObj' must be a CType of defined (but possibly zero) size. |
| // |
| // * If an object 'refObj' is supplied, the new CData object stores the |
| // referent object in a reserved slot for GC safety, such that 'refObj' will |
| // be held alive by the resulting CData object. 'refObj' may or may not be |
| // a CData object; merely an object we want to keep alive. |
| // * If 'refObj' is a CData object, 'ownResult' must be false. |
| // * Otherwise, 'refObj' is a Library or CClosure object, and 'ownResult' |
| // may be true or false. |
| // * Otherwise 'refObj' is nullptr. In this case, 'ownResult' may be true or |
| // false. |
| // |
| // * If 'ownResult' is true, the CData object will allocate an appropriately |
| // sized buffer, and free it upon finalization. If 'source' data is |
| // supplied, the data will be copied from 'source' into the buffer; |
| // otherwise, the entirety of the new buffer will be initialized to zero. |
| // * If 'ownResult' is false, the new CData's buffer refers to a slice of |
| // another buffer kept alive by 'refObj'. 'source' data must be provided, |
| // and the new CData's buffer will refer to 'source'. |
| JSObject* |
| CData::Create(JSContext* cx, |
| HandleObject typeObj, |
| HandleObject refObj, |
| void* source, |
| bool ownResult) |
| { |
| MOZ_ASSERT(typeObj); |
| MOZ_ASSERT(CType::IsCType(typeObj)); |
| MOZ_ASSERT(CType::IsSizeDefined(typeObj)); |
| MOZ_ASSERT(ownResult || source); |
| MOZ_ASSERT_IF(refObj && CData::IsCData(refObj), !ownResult); |
| |
| // Get the 'prototype' property from the type. |
| Value slot = JS_GetReservedSlot(typeObj, SLOT_PROTO); |
| MOZ_ASSERT(slot.isObject()); |
| |
| RootedObject proto(cx, &slot.toObject()); |
| |
| RootedObject dataObj(cx, JS_NewObjectWithGivenProto(cx, &sCDataClass, proto)); |
| if (!dataObj) |
| return nullptr; |
| |
| // set the CData's associated type |
| JS_SetReservedSlot(dataObj, SLOT_CTYPE, ObjectValue(*typeObj)); |
| |
| // Stash the referent object, if any, for GC safety. |
| if (refObj) |
| JS_SetReservedSlot(dataObj, SLOT_REFERENT, ObjectValue(*refObj)); |
| |
| // Set our ownership flag. |
| JS_SetReservedSlot(dataObj, SLOT_OWNS, BooleanValue(ownResult)); |
| |
| // attach the buffer. since it might not be 2-byte aligned, we need to |
| // allocate an aligned space for it and store it there. :( |
| char** buffer = cx->new_<char*>(); |
| if (!buffer) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| char* data; |
| if (!ownResult) { |
| data = static_cast<char*>(source); |
| } else { |
| // Initialize our own buffer. |
| size_t size = CType::GetSize(typeObj); |
| data = dataObj->zone()->pod_malloc<char>(size); |
| if (!data) { |
| // Report a catchable allocation error. |
| JS_ReportAllocationOverflow(cx); |
| js_free(buffer); |
| return nullptr; |
| } |
| |
| if (!source) |
| memset(data, 0, size); |
| else |
| memcpy(data, source, size); |
| } |
| |
| *buffer = data; |
| JS_SetReservedSlot(dataObj, SLOT_DATA, PrivateValue(buffer)); |
| |
| return dataObj; |
| } |
| |
| void |
| CData::Finalize(JSFreeOp* fop, JSObject* obj) |
| { |
| // Delete our buffer, and the data it contains if we own it. |
| Value slot = JS_GetReservedSlot(obj, SLOT_OWNS); |
| if (slot.isUndefined()) |
| return; |
| |
| bool owns = slot.toBoolean(); |
| |
| slot = JS_GetReservedSlot(obj, SLOT_DATA); |
| if (slot.isUndefined()) |
| return; |
| char** buffer = static_cast<char**>(slot.toPrivate()); |
| |
| if (owns) |
| FreeOp::get(fop)->free_(*buffer); |
| FreeOp::get(fop)->delete_(buffer); |
| } |
| |
| JSObject* |
| CData::GetCType(JSObject* dataObj) |
| { |
| MOZ_ASSERT(CData::IsCData(dataObj)); |
| |
| Value slot = JS_GetReservedSlot(dataObj, SLOT_CTYPE); |
| JSObject* typeObj = slot.toObjectOrNull(); |
| MOZ_ASSERT(CType::IsCType(typeObj)); |
| return typeObj; |
| } |
| |
| void* |
| CData::GetData(JSObject* dataObj) |
| { |
| MOZ_ASSERT(CData::IsCData(dataObj)); |
| |
| Value slot = JS_GetReservedSlot(dataObj, SLOT_DATA); |
| |
| void** buffer = static_cast<void**>(slot.toPrivate()); |
| MOZ_ASSERT(buffer); |
| MOZ_ASSERT(*buffer); |
| return *buffer; |
| } |
| |
| bool |
| CData::IsCData(JSObject* obj) |
| { |
| return JS_GetClass(obj) == &sCDataClass; |
| } |
| |
| bool |
| CData::IsCData(HandleValue v) |
| { |
| return v.isObject() && CData::IsCData(&v.toObject()); |
| } |
| |
| bool |
| CData::IsCDataProto(JSObject* obj) |
| { |
| return JS_GetClass(obj) == &sCDataProtoClass; |
| } |
| |
| bool |
| CData::ValueGetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| |
| // Convert the value to a primitive; do not create a new CData object. |
| RootedObject ctype(cx, GetCType(obj)); |
| return ConvertToJS(cx, ctype, nullptr, GetData(obj), true, false, args.rval()); |
| } |
| |
| bool |
| CData::ValueSetter(JSContext* cx, const JS::CallArgs& args) |
| { |
| RootedObject obj(cx, &args.thisv().toObject()); |
| args.rval().setUndefined(); |
| return ImplicitConvert(cx, args.get(0), GetCType(obj), GetData(obj), |
| ConversionType::Setter, nullptr); |
| } |
| |
| bool |
| CData::Address(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 0) { |
| return ArgumentLengthError(cx, "CData.prototype.address", "no", "s"); |
| } |
| |
| RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!obj) |
| return false; |
| if (!IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| RootedObject typeObj(cx, CData::GetCType(obj)); |
| RootedObject pointerType(cx, PointerType::CreateInternal(cx, typeObj)); |
| if (!pointerType) |
| return false; |
| |
| // Create a PointerType CData object containing null. |
| JSObject* result = CData::Create(cx, pointerType, nullptr, nullptr, true); |
| if (!result) |
| return false; |
| |
| args.rval().setObject(*result); |
| |
| // Manually set the pointer inside the object, so we skip the conversion step. |
| void** data = static_cast<void**>(GetData(result)); |
| *data = GetData(obj); |
| return true; |
| } |
| |
| bool |
| CData::Cast(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 2) { |
| return ArgumentLengthError(cx, "ctypes.cast", "two", "s"); |
| } |
| |
| if (args[0].isPrimitive() || !CData::IsCData(&args[0].toObject())) { |
| return ArgumentTypeMismatch(cx, "first ", "ctypes.cast", "a CData"); |
| } |
| RootedObject sourceData(cx, &args[0].toObject()); |
| JSObject* sourceType = 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) || |
| targetSize > CType::GetSize(sourceType)) { |
| JS_ReportError(cx, |
| "target CType has undefined or larger size than source CType"); |
| return false; |
| } |
| |
| // 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_ReportError(cx, "target CType has non-pointer size"); |
| return false; |
| } |
| |
| void* data = static_cast<void*>(cx->runtime()); |
| JSObject* result = CData::Create(cx, targetType, nullptr, &data, true); |
| if (!result) |
| return false; |
| |
| args.rval().setObject(*result); |
| return true; |
| } |
| |
| typedef JS::TwoByteCharsZ (*InflateUTF8Method)(JSContext*, const JS::UTF8Chars, size_t*); |
| |
| static bool |
| ReadStringCommon(JSContext* cx, InflateUTF8Method inflateUTF8, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 0) { |
| if (inflateUTF8 == JS::UTF8CharsToNewTwoByteCharsZ) { |
| return ArgumentLengthError(cx, "CData.prototype.readString", "no", "s"); |
| } |
| return ArgumentLengthError(cx, "CData.prototype.readStringReplaceMalformed", |
| "no", "s"); |
| } |
| |
| JSObject* obj = CDataFinalizer::GetCData(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!obj || !CData::IsCData(obj)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| // Make sure we are a pointer to, or an array of, an 8-bit or 16-bit |
| // character or integer type. |
| JSObject* baseType; |
| JSObject* typeObj = CData::GetCType(obj); |
| TypeCode typeCode = CType::GetTypeCode(typeObj); |
| void* data; |
| size_t maxLength = -1; |
| switch (typeCode) { |
| case TYPE_pointer: |
| baseType = PointerType::GetBaseType(typeObj); |
| data = *static_cast<void**>(CData::GetData(obj)); |
| if (data == nullptr) { |
| JS_ReportError(cx, "cannot read contents of null pointer"); |
| return false; |
| } |
| break; |
| case TYPE_array: |
| baseType = ArrayType::GetBaseType(typeObj); |
| data = CData::GetData(obj); |
| maxLength = ArrayType::GetLength(typeObj); |
| break; |
| default: |
| JS_ReportError(cx, "not a PointerType or ArrayType"); |
| return false; |
| } |
| |
| // Convert the string buffer, taking care to determine the correct string |
| // length in the case of arrays (which may contain embedded nulls). |
| JSString* result; |
| switch (CType::GetTypeCode(baseType)) { |
| case TYPE_int8_t: |
| case TYPE_uint8_t: |
| case TYPE_char: |
| case TYPE_signed_char: |
| case TYPE_unsigned_char: { |
| char* bytes = static_cast<char*>(data); |
| size_t length = strnlen(bytes, maxLength); |
| |
| // Determine the length. |
| char16_t* dst = inflateUTF8(cx, JS::UTF8Chars(bytes, length), &length).get(); |
| if (!dst) |
| return false; |
| |
| result = JS_NewUCString(cx, dst, length); |
| break; |
| } |
| case TYPE_int16_t: |
| case TYPE_uint16_t: |
| case TYPE_short: |
| case TYPE_unsigned_short: |
| case TYPE_char16_t: { |
| char16_t* chars = static_cast<char16_t*>(data); |
| size_t length = strnlen(chars, maxLength); |
| result = JS_NewUCStringCopyN(cx, chars, length); |
| break; |
| } |
| default: |
| JS_ReportError(cx, |
| "base type is not an 8-bit or 16-bit integer or character type"); |
| return false; |
| } |
| |
| 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); |
| } |
| |
| bool |
| CData::ReadStringReplaceMalformed(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return ReadStringCommon(cx, JS::LossyUTF8CharsToNewTwoByteCharsZ, argc, vp); |
| } |
| |
| 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)) { |
| JS_ReportError(cx, "not a CData"); |
| return false; |
| } |
| |
| 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)) { |
| JS_ReportError(cx, "not a CDataFinalizer"); |
| return false; |
| } |
| |
| 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_ReportError(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)) { |
| JS_ReportError(cx, "not a CDataFinalizer"); |
| return false; |
| } |
| |
| 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(); |
| } |
| |
| JSObject* |
| CDataFinalizer::GetCData(JSContext* cx, JSObject* obj) |
| { |
| if (!obj) { |
| JS_ReportError(cx, "No C data"); |
| return nullptr; |
| } |
| if (CData::IsCData(obj)) { |
| return obj; |
| } |
| if (!CDataFinalizer::IsCDataFinalizer(obj)) { |
| JS_ReportError(cx, "Not C data"); |
| return nullptr; |
| } |
| RootedValue val(cx); |
| if (!CDataFinalizer::GetValue(cx, obj, &val) || val.isPrimitive()) { |
| JS_ReportError(cx, "Empty CDataFinalizer"); |
| return nullptr; |
| } |
| return val.toObjectOrNull(); |
| } |
| |
| bool |
| CDataFinalizer::GetValue(JSContext* cx, JSObject* obj, MutableHandleValue aResult) |
| { |
| MOZ_ASSERT(IsCDataFinalizer(obj)); |
| |
| CDataFinalizer::Private* p = (CDataFinalizer::Private*) |
| JS_GetPrivate(obj); |
| |
| if (!p) { |
| JS_ReportError(cx, "Attempting to get the value of an empty CDataFinalizer"); |
| return false; // We have called |dispose| or |forget| already. |
| } |
| |
| RootedObject ctype(cx, GetCType(cx, obj)); |
| return ConvertToJS(cx, ctype, /*parent*/nullptr, p->cargs, false, true, aResult); |
| } |
| |
| /* |
| * Attach a C function as a finalizer to a JS object. |
| * |
| * Pseudo-JS signature: |
| * function(CData<T>, CData<T -> U>): CDataFinalizer<T> |
| * value, finalizer |
| * |
| * This function attaches strong references to the following values: |
| * - the CType of |value| |
| * |
| * Note: This function takes advantage of the fact that non-variadic |
| * CData functions are initialized during creation. |
| */ |
| bool |
| CDataFinalizer::Construct(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject objSelf(cx, &args.callee()); |
| RootedObject objProto(cx); |
| if (!GetObjectProperty(cx, objSelf, "prototype", &objProto)) { |
| JS_ReportError(cx, "CDataFinalizer.prototype does not exist"); |
| return false; |
| } |
| |
| // Get arguments |
| if (args.length() == 0) { // Special case: the empty (already finalized) object |
| JSObject* objResult = JS_NewObjectWithGivenProto(cx, &sCDataFinalizerClass, objProto); |
| args.rval().setObject(*objResult); |
| return true; |
| } |
| |
| if (args.length() != 2) { |
| return ArgumentLengthError(cx, "CDataFinalizer constructor", "two", "s"); |
| } |
| |
| JS::HandleValue valCodePtr = args[1]; |
| if (!valCodePtr.isObject()) { |
| return TypeError(cx, "_a CData object_ of a function pointer type", |
| valCodePtr); |
| } |
| JSObject* objCodePtr = &valCodePtr.toObject(); |
| |
| //Note: Using a custom argument formatter here would be awkward (requires |
| //a destructor just to uninstall the formatter). |
| |
| // 2. Extract argument type of |objCodePtr| |
| if (!CData::IsCData(objCodePtr)) { |
| return TypeError(cx, "a _CData_ object of a function pointer type", |
| valCodePtr); |
| } |
| RootedObject objCodePtrType(cx, CData::GetCType(objCodePtr)); |
| RootedValue valCodePtrType(cx, ObjectValue(*objCodePtrType)); |
| MOZ_ASSERT(objCodePtrType); |
| |
| TypeCode typCodePtr = CType::GetTypeCode(objCodePtrType); |
| if (typCodePtr != TYPE_pointer) { |
| return TypeError(cx, "a CData object of a function _pointer_ type", |
| valCodePtr); |
| } |
| |
| JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType); |
| MOZ_ASSERT(objCodeType); |
| |
| TypeCode typCode = CType::GetTypeCode(objCodeType); |
| if (typCode != TYPE_function) { |
| return TypeError(cx, "a CData object of a _function_ pointer type", |
| valCodePtr); |
| } |
| uintptr_t code = *reinterpret_cast<uintptr_t*>(CData::GetData(objCodePtr)); |
| if (!code) { |
| return TypeError(cx, "a CData object of a _non-NULL_ function pointer type", |
| valCodePtr); |
| } |
| |
| FunctionInfo* funInfoFinalizer = |
| FunctionType::GetFunctionInfo(objCodeType); |
| MOZ_ASSERT(funInfoFinalizer); |
| |
| if ((funInfoFinalizer->mArgTypes.length() != 1) |
| || (funInfoFinalizer->mIsVariadic)) { |
| RootedValue valCodeType(cx, ObjectValue(*objCodeType)); |
| return TypeError(cx, "a function accepting exactly one argument", |
| valCodeType); |
| } |
| RootedObject objArgType(cx, funInfoFinalizer->mArgTypes[0]); |
| RootedObject returnType(cx, funInfoFinalizer->mReturnType); |
| |
| // Invariant: At this stage, we know that funInfoFinalizer->mIsVariadic |
| // is |false|. Therefore, funInfoFinalizer->mCIF has already been initialized. |
| |
| bool freePointer = false; |
| |
| // 3. Perform dynamic cast of |args[0]| into |objType|, store it in |cargs| |
| |
| size_t sizeArg; |
| RootedValue valData(cx, args[0]); |
| if (!CType::GetSafeSize(objArgType, &sizeArg)) { |
| RootedValue valCodeType(cx, ObjectValue(*objCodeType)); |
| return TypeError(cx, "a function with one known size argument", |
| valCodeType); |
| } |
| |
| ScopedJSFreePtr<void> cargs(malloc(sizeArg)); |
| |
| if (!ImplicitConvert(cx, valData, objArgType, cargs.get(), |
| ConversionType::Finalizer, &freePointer, |
| objCodePtrType, 0)) { |
| return false; |
| } |
| if (freePointer) { |
| // Note: We could handle that case, if necessary. |
| JS_ReportError(cx, "Internal Error during CDataFinalizer. Object cannot be represented"); |
| return false; |
| } |
| |
| // 4. Prepare buffer for holding return value |
| |
| ScopedJSFreePtr<void> rvalue; |
| if (CType::GetTypeCode(returnType) != TYPE_void_t) { |
| rvalue = malloc(Align(CType::GetSize(returnType), |
| sizeof(ffi_arg))); |
| } //Otherwise, simply do not allocate |
| |
| // 5. Create |objResult| |
| |
| JSObject* objResult = JS_NewObjectWithGivenProto(cx, &sCDataFinalizerClass, objProto); |
| if (!objResult) { |
| return false; |
| } |
| |
| // If our argument is a CData, it holds a type. |
| // This is the type that we should capture, not that |
| // of the function, which may be less precise. |
| JSObject* objBestArgType = objArgType; |
| if (valData.isObject()) { |
| JSObject* objData = &valData.toObject(); |
| if (CData::IsCData(objData)) { |
| objBestArgType = CData::GetCType(objData); |
| size_t sizeBestArg; |
| if (!CType::GetSafeSize(objBestArgType, &sizeBestArg)) { |
| MOZ_CRASH("object with unknown size"); |
| } |
| if (sizeBestArg != sizeArg) { |
| return FinalizerSizeError(cx, objCodePtrType, valData); |
| } |
| } |
| } |
| |
| // Used by GetCType |
| JS_SetReservedSlot(objResult, |
| SLOT_DATAFINALIZER_VALTYPE, |
| ObjectOrNullValue(objBestArgType)); |
| |
| // Used by ToSource |
| JS_SetReservedSlot(objResult, |
| SLOT_DATAFINALIZER_CODETYPE, |
| ObjectValue(*objCodePtrType)); |
| |
| ffi_abi abi; |
| if (!GetABI(cx, ObjectOrNullValue(funInfoFinalizer->mABI), &abi)) { |
| JS_ReportError(cx, "Internal Error: " |
| "Invalid ABI specification in CDataFinalizer"); |
| return false; |
| } |
| |
| ffi_type* rtype = CType::GetFFIType(cx, funInfoFinalizer->mReturnType); |
| if (!rtype) { |
| JS_ReportError(cx, "Internal Error: " |
| "Could not access ffi type of CDataFinalizer"); |
| return false; |
| } |
| |
| // 7. Store C information as private |
| ScopedJSFreePtr<CDataFinalizer::Private> |
| p((CDataFinalizer::Private*)malloc(sizeof(CDataFinalizer::Private))); |
| |
| memmove(&p->CIF, &funInfoFinalizer->mCIF, sizeof(ffi_cif)); |
| |
| p->cargs = cargs.forget(); |
| p->rvalue = rvalue.forget(); |
| p->cargs_size = sizeArg; |
| p->code = code; |
| |
| |
| JS_SetPrivate(objResult, p.forget()); |
| args.rval().setObject(*objResult); |
| return true; |
| } |
| |
| |
| /* |
| * Actually call the finalizer. Does not perform any cleanup on the object. |
| * |
| * Preconditions: |this| must be a |CDataFinalizer|, |p| must be non-null. |
| * The function fails if |this| has gone through |Forget|/|Dispose| |
| * or |Finalize|. |
| * |
| * This function does not alter the value of |errno|/|GetLastError|. |
| * |
| * If argument |errnoStatus| is non-nullptr, it receives the value of |errno| |
| * immediately after the call. Under Windows, if argument |lastErrorStatus| |
| * is non-nullptr, it receives the value of |GetLastError| immediately after |
| * the call. On other platforms, |lastErrorStatus| is ignored. |
| */ |
| void |
| CDataFinalizer::CallFinalizer(CDataFinalizer::Private* p, |
| int* errnoStatus, |
| int32_t* lastErrorStatus) |
| { |
| int savedErrno = errno; |
| errno = 0; |
| #if defined(XP_WIN) |
| int32_t savedLastError = GetLastError(); |
| SetLastError(0); |
| #endif // defined(XP_WIN) |
| |
| void* args[1] = {p->cargs}; |
| ffi_call(&p->CIF, FFI_FN(p->code), p->rvalue, args); |
| |
| if (errnoStatus) { |
| *errnoStatus = errno; |
| } |
| errno = savedErrno; |
| #if defined(XP_WIN) |
| if (lastErrorStatus) { |
| *lastErrorStatus = GetLastError(); |
| } |
| SetLastError(savedLastError); |
| #endif // defined(XP_WIN) |
| } |
| |
| /* |
| * Forget the value. |
| * |
| * Preconditions: |this| must be a |CDataFinalizer|. |
| * The function fails if |this| has gone through |Forget|/|Dispose| |
| * or |Finalize|. |
| * |
| * Does not call the finalizer. Cleans up the Private memory and releases all |
| * strong references. |
| */ |
| bool |
| CDataFinalizer::Methods::Forget(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 0) { |
| return ArgumentLengthError(cx, "CDataFinalizer.prototype.forget", "no", |
| "s"); |
| } |
| |
| JS::Rooted<JSObject*> obj(cx, args.thisv().toObjectOrNull()); |
| if (!obj) |
| return false; |
| if (!CDataFinalizer::IsCDataFinalizer(obj)) { |
| JS_ReportError(cx, "not a CDataFinalizer"); |
| return false; |
| } |
| |
| CDataFinalizer::Private* p = (CDataFinalizer::Private*) |
| JS_GetPrivate(obj); |
| |
| if (!p) { |
| JS_ReportError(cx, "forget called on an empty CDataFinalizer"); |
| return false; |
| } |
| |
| RootedValue valJSData(cx); |
| RootedObject ctype(cx, GetCType(cx, obj)); |
| if (!ConvertToJS(cx, ctype, nullptr, p->cargs, false, true, &valJSData)) { |
| JS_ReportError(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)) { |
| JS_ReportError(cx, "not a CDataFinalizer"); |
| return false; |
| } |
| |
| CDataFinalizer::Private* p = (CDataFinalizer::Private*) |
| JS_GetPrivate(obj); |
| |
| if (!p) { |
| JS_ReportError(cx, "dispose called on an empty CDataFinalizer."); |
| return false; |
| } |
| |
| Value valType = JS_GetReservedSlot(obj, SLOT_DATAFINALIZER_VALTYPE); |
| MOZ_ASSERT(valType.isObject()); |
| |
| JSObject* objCTypes = 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, JS::UndefinedValue()); |
| |
| int errnoStatus; |
| #if defined(XP_WIN) |
| int32_t lastErrorStatus; |
| CDataFinalizer::CallFinalizer(p, &errnoStatus, &lastErrorStatus); |
| #else |
| CDataFinalizer::CallFinalizer(p, &errnoStatus, nullptr); |
| #endif // defined(XP_WIN) |
| |
| JS_SetReservedSlot(objCTypes, SLOT_ERRNO, Int32Value(errnoStatus)); |
| #if defined(XP_WIN) |
| JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, Int32Value(lastErrorStatus)); |
| #endif // defined(XP_WIN) |
| |
| if (ConvertToJS(cx, resultType, nullptr, p->rvalue, false, true, &result)) { |
| CDataFinalizer::Cleanup(p, obj); |
| args.rval().set(result); |
| return true; |
| } |
| CDataFinalizer::Cleanup(p, obj); |
| return false; |
| } |
| |
| /* |
| * Perform finalization. |
| * |
| * Preconditions: |this| must be the result of |CDataFinalizer|. |
| * It may have gone through |Forget|/|Dispose|. |
| * |
| * If |this| has not gone through |Forget|/|Dispose|, calls the |
| * finalizer, cleans up the Private memory and releases all |
| * strong references. |
| */ |
| void |
| CDataFinalizer::Finalize(JSFreeOp* fop, JSObject* obj) |
| { |
| CDataFinalizer::Private* p = (CDataFinalizer::Private*) |
| JS_GetPrivate(obj); |
| |
| if (!p) { |
| return; |
| } |
| |
| CDataFinalizer::CallFinalizer(p, nullptr, nullptr); |
| CDataFinalizer::Cleanup(p, nullptr); |
| } |
| |
| /* |
| * Perform cleanup of a CDataFinalizer |
| * |
| * Release strong references, cleanup |Private|. |
| * |
| * Argument |p| contains the private information of the CDataFinalizer. If |
| * nullptr, this function does nothing. |
| * Argument |obj| should contain |nullptr| during finalization (or in any |
| * context in which the object itself should not be cleaned up), or a |
| * CDataFinalizer object otherwise. |
| */ |
| void |
| CDataFinalizer::Cleanup(CDataFinalizer::Private* p, JSObject* obj) |
| { |
| if (!p) { |
| return; // We have already cleaned up |
| } |
| |
| free(p->cargs); |
| free(p->rvalue); |
| free(p); |
| |
| if (!obj) { |
| return; // No slots to clean up |
| } |
| |
| MOZ_ASSERT(CDataFinalizer::IsCDataFinalizer(obj)); |
| |
| JS_SetPrivate(obj, nullptr); |
| for (int i = 0; i < CDATAFINALIZER_SLOTS; ++i) { |
| JS_SetReservedSlot(obj, i, JS::NullValue()); |
| } |
| } |
| |
| |
| /******************************************************************************* |
| ** Int64 and UInt64 implementation |
| *******************************************************************************/ |
| |
| JSObject* |
| Int64Base::Construct(JSContext* cx, |
| HandleObject proto, |
| uint64_t data, |
| bool isUnsigned) |
| { |
| const JSClass* clasp = isUnsigned ? &sUInt64Class : &sInt64Class; |
| RootedObject result(cx, JS_NewObjectWithGivenProto(cx, clasp, proto)); |
| if (!result) |
| return nullptr; |
| |
| // attach the Int64's data |
| uint64_t* buffer = cx->new_<uint64_t>(data); |
| if (!buffer) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| JS_SetReservedSlot(result, SLOT_INT64, PrivateValue(buffer)); |
| |
| if (!JS_FreezeObject(cx, result)) |
| return nullptr; |
| |
| return result; |
| } |
| |
| void |
| Int64Base::Finalize(JSFreeOp* fop, JSObject* obj) |
| { |
| Value slot = JS_GetReservedSlot(obj, SLOT_INT64); |
| if (slot.isUndefined()) |
| return; |
| |
| FreeOp::get(fop)->delete_(static_cast<uint64_t*>(slot.toPrivate())); |
| } |
| |
| uint64_t |
| Int64Base::GetInt(JSObject* obj) { |
| MOZ_ASSERT(Int64::IsInt64(obj) || UInt64::IsUInt64(obj)); |
| |
| Value slot = JS_GetReservedSlot(obj, SLOT_INT64); |
| return *static_cast<uint64_t*>(slot.toPrivate()); |
| } |
| |
| bool |
| Int64Base::ToString(JSContext* cx, |
| JSObject* obj, |
| const CallArgs& args, |
| bool isUnsigned) |
| { |
| if (args.length() > 1) { |
| if (isUnsigned) { |
| return ArgumentLengthError(cx, "UInt64.prototype.toString", |
| "at most one", ""); |
| } |
| return ArgumentLengthError(cx, "Int64.prototype.toString", |
| "at most one", ""); |
| } |
| |
| int radix = 10; |
| if (args.length() == 1) { |
| Value arg = args[0]; |
| if (arg.isInt32()) |
| radix = arg.toInt32(); |
| if (!arg.isInt32() || radix < 2 || radix > 36) { |
| if (isUnsigned) { |
| return ArgumentRangeMismatch(cx, "", "UInt64.prototype.toString", "an integer at least 2 and no greater than 36"); |
| } |
| return ArgumentRangeMismatch(cx, "", "Int64.prototype.toString", "an integer at least 2 and no greater than 36"); |
| } |
| } |
| |
| AutoString intString; |
| if (isUnsigned) { |
| IntegerToString(GetInt(obj), radix, intString); |
| } else { |
| IntegerToString(static_cast<int64_t>(GetInt(obj)), radix, intString); |
| } |
| |
| JSString* result = NewUCString(cx, intString); |
| if (!result) |
| return false; |
| |
| args.rval().setString(result); |
| return true; |
| } |
| |
| bool |
| Int64Base::ToSource(JSContext* cx, |
| JSObject* obj, |
| const CallArgs& args, |
| bool isUnsigned) |
| { |
| if (args.length() != 0) { |
| if (isUnsigned) { |
| return ArgumentLengthError(cx, "UInt64.prototype.toSource", "no", "s"); |
| } |
| return ArgumentLengthError(cx, "Int64.prototype.toSource", "no", "s"); |
| } |
| |
| // Return a decimal string suitable for constructing the number. |
| AutoString source; |
| if (isUnsigned) { |
| AppendString(source, "ctypes.UInt64(\""); |
| IntegerToString(GetInt(obj), 10, source); |
| } else { |
| AppendString(source, "ctypes.Int64(\""); |
| IntegerToString(static_cast<int64_t>(GetInt(obj)), 10, source); |
| } |
| AppendString(source, "\")"); |
| |
| JSString* result = NewUCString(cx, source); |
| if (!result) |
| return false; |
| |
| args.rval().setString(result); |
| return true; |
| } |
| |
| bool |
| Int64::Construct(JSContext* cx, |
| unsigned argc, |
| Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| // Construct and return a new Int64 object. |
| if (args.length() != 1) { |
| return ArgumentLengthError(cx, "Int64 constructor", "one", ""); |
| } |
| |
| int64_t i = 0; |
| if (!jsvalToBigInteger(cx, args[0], true, &i)) { |
| 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)) { |
| JS_ReportError(cx, "not an Int64"); |
| return false; |
| } |
| |
| 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)) { |
| JS_ReportError(cx, "not an Int64"); |
| return false; |
| } |
| |
| 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; |
| if (!jsvalToBigInteger(cx, args[0], true, &u)) { |
| 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)) { |
| JS_ReportError(cx, "not a UInt64"); |
| return false; |
| } |
| |
| 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)) { |
| JS_ReportError(cx, "not a UInt64"); |
| return false; |
| } |
| |
| 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 |