/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ctypes/CTypes.h" #include "js/experimental/CTypes.h" // JS::CTypesActivity{Callback,Type}, JS::InitCTypesClass, JS::SetCTypesActivityCallback, JS::SetCTypesCallbacks #include "mozilla/CheckedInt.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Sprintf.h" #include "mozilla/TextUtils.h" #include "mozilla/Vector.h" #include "mozilla/WrappingOperations.h" #if defined(XP_UNIX) # include #endif #if defined(XP_WIN) # include #endif #if defined(SOLARIS) # include #endif #include #include #include #ifdef HAVE_SSIZE_T # include #endif #include #include "jsapi.h" #include "jsexn.h" #include "jsnum.h" #include "ctypes/Library.h" #include "gc/GCContext.h" #include "jit/AtomicOperations.h" #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject #include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,GetArrayBufferData,GetArrayBuffer{ByteLength,Data}} #include "js/CallAndConstruct.h" // JS::IsCallable, JS_CallFunctionValue #include "js/CharacterEncoding.h" #include "js/experimental/TypedData.h" // JS_GetArrayBufferView{Type,Data}, JS_GetTypedArrayByteLength, JS_IsArrayBufferViewObject, JS_IsTypedArrayObject #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/GlobalObject.h" // JS::CurrentGlobalOrNull #include "js/Object.h" // JS::GetMaybePtrFromReservedSlot, JS::GetReservedSlot, JS::SetReservedSlot #include "js/PropertyAndElement.h" // JS_DefineFunction, JS_DefineFunctions, JS_DefineProperties, JS_DefineProperty, JS_DefinePropertyById, JS_DefineUCProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById #include "js/PropertySpec.h" #include "js/SharedArrayBuffer.h" // JS::{GetSharedArrayBuffer{ByteLength,Data},IsSharedArrayBufferObject} #include "js/StableStringChars.h" #include "js/UniquePtr.h" #include "js/Utility.h" #include "js/Vector.h" #include "util/Text.h" #include "util/Unicode.h" #include "util/WindowsWrapper.h" #include "vm/JSContext.h" #include "vm/JSFunction.h" #include "vm/JSObject.h" #include "gc/GCContext-inl.h" #include "vm/JSObject-inl.h" using std::numeric_limits; using mozilla::CheckedInt; using mozilla::IsAsciiAlpha; using mozilla::IsAsciiDigit; using JS::AutoCheckCannotGC; using JS::AutoCTypesActivityCallback; using JS::AutoStableStringChars; using JS::CTypesActivityType; namespace js::ctypes { static bool HasUnpairedSurrogate(const char16_t* chars, size_t nchars, char16_t* unpaired) { for (const char16_t* end = chars + nchars; chars != end; chars++) { char16_t c = *chars; if (unicode::IsSurrogate(c)) { chars++; if (unicode::IsTrailSurrogate(c) || chars == end) { *unpaired = c; return true; } char16_t c2 = *chars; if (!unicode::IsTrailSurrogate(c2)) { *unpaired = c; return true; } } } return false; } bool ReportErrorIfUnpairedSurrogatePresent(JSContext* cx, JSLinearString* str) { if (str->hasLatin1Chars()) { return true; } char16_t unpaired; { JS::AutoCheckCannotGC nogc; if (!HasUnpairedSurrogate(str->twoByteChars(nogc), str->length(), &unpaired)) { return true; } } char buffer[10]; SprintfLiteral(buffer, "0x%x", unpaired); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_SURROGATE_CHAR, buffer); return false; } /******************************************************************************* ** JSAPI function prototypes *******************************************************************************/ // We use an enclosing struct here out of paranoia about the ability of gcc 4.4 // (and maybe 4.5) to correctly compile this if it were a template function. // See also the comments in dom/workers/Events.cpp (and other adjacent files) by // the |struct Property| there. template struct Property { static bool Fun(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); return JS::CallNonGenericMethod(cx, args); } }; static bool ConstructAbstract(JSContext* cx, unsigned argc, Value* vp); namespace CType { static bool ConstructData(JSContext* cx, unsigned argc, Value* vp); static bool ConstructBasic(JSContext* cx, HandleObject obj, const CallArgs& args); static void Trace(JSTracer* trc, JSObject* obj); static void Finalize(JS::GCContext* gcx, 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); /* * Get the global "ctypes" object. * * |obj| must be a CType object. * * This function never returns nullptr. */ static JSObject* GetGlobalCTypes(JSContext* cx, JSObject* obj); } // namespace CType namespace ABI { bool IsABI(JSObject* obj); static bool ToSource(JSContext* cx, unsigned argc, Value* vp); } // namespace ABI namespace PointerType { static bool Create(JSContext* cx, unsigned argc, Value* vp); static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args); bool IsPointerType(HandleValue v); bool IsPointer(HandleValue v); bool TargetTypeGetter(JSContext* cx, const JS::CallArgs& args); bool ContentsGetter(JSContext* cx, const JS::CallArgs& args); bool ContentsSetter(JSContext* cx, const JS::CallArgs& args); static bool IsNull(JSContext* cx, unsigned argc, Value* vp); static bool Increment(JSContext* cx, unsigned argc, Value* vp); static bool Decrement(JSContext* cx, unsigned argc, Value* vp); // The following is not an instance function, since we don't want to expose // arbitrary pointer arithmetic at this moment. static bool OffsetBy(JSContext* cx, const CallArgs& args, int offset, const char* name); } // namespace PointerType namespace ArrayType { bool IsArrayType(HandleValue v); bool IsArrayOrArrayType(HandleValue v); static bool Create(JSContext* cx, unsigned argc, Value* vp); static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args); bool ElementTypeGetter(JSContext* cx, const JS::CallArgs& args); bool LengthGetter(JSContext* cx, const JS::CallArgs& args); static bool Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp, bool* handled); static bool Setter(JSContext* cx, HandleObject obj, HandleId idval, HandleValue v, ObjectOpResult& result, bool* handled); static bool AddressOfElement(JSContext* cx, unsigned argc, Value* vp); } // namespace ArrayType namespace StructType { bool IsStruct(HandleValue v); static bool Create(JSContext* cx, unsigned argc, Value* vp); static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args); bool FieldsArrayGetter(JSContext* cx, const JS::CallArgs& args); enum { SLOT_FIELDNAME }; static bool FieldGetter(JSContext* cx, unsigned argc, Value* vp); static bool FieldSetter(JSContext* cx, unsigned argc, Value* vp); static bool AddressOfField(JSContext* cx, unsigned argc, Value* vp); static bool Define(JSContext* cx, unsigned argc, Value* vp); } // namespace StructType namespace FunctionType { static bool Create(JSContext* cx, unsigned argc, Value* vp); static bool ConstructData(JSContext* cx, HandleObject typeObj, HandleObject dataObj, HandleObject fnObj, HandleObject thisObj, HandleValue errVal); static bool Call(JSContext* cx, unsigned argc, Value* vp); bool IsFunctionType(HandleValue v); bool ArgTypesGetter(JSContext* cx, const JS::CallArgs& args); bool ReturnTypeGetter(JSContext* cx, const JS::CallArgs& args); bool ABIGetter(JSContext* cx, const JS::CallArgs& args); bool IsVariadicGetter(JSContext* cx, const JS::CallArgs& args); } // namespace FunctionType namespace CClosure { static void Trace(JSTracer* trc, JSObject* obj); static void Finalize(JS::GCContext* gcx, 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(JS::GCContext* gcx, 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 ReadTypedArray(JSContext* cx, unsigned argc, Value* vp); static bool ToSource(JSContext* cx, unsigned argc, Value* vp); static JSString* GetSourceString(JSContext* cx, HandleObject typeObj, void* data); bool ErrnoGetter(JSContext* cx, const JS::CallArgs& args); #if defined(XP_WIN) bool LastErrorGetter(JSContext* cx, const JS::CallArgs& args); #endif // defined(XP_WIN) } // namespace CData namespace CDataFinalizer { /* * Attach a C function as a finalizer to a JS object. * * This function is available from JS as |ctypes.withFinalizer|. * * JavaScript signature: * function(CData, CData): CDataFinalizer * value finalizer finalizable * * Where |finalizer| is a one-argument function taking a value * with the same type as |value|. */ static bool Construct(JSContext* cx, unsigned argc, Value* vp); /* * Private data held by |CDataFinalizer|. * * See also |enum CDataFinalizerSlot| for the slots of * |CDataFinalizer|. * * Note: the private data may be nullptr, if |dispose|, |forget| or the * finalizer has already been called. */ struct Private { /* * The C data to pass to the code. * Finalization/|dispose|/|forget| release this memory. */ void* cargs; /* * The total size of the buffer pointed by |cargs| */ size_t cargs_size; /* * Low-level signature information. * Finalization/|dispose|/|forget| release this memory. */ ffi_cif CIF; /* * The C function to invoke during finalization. * Do not deallocate this. */ uintptr_t code; /* * A buffer for holding the return value. * Finalization/|dispose|/|forget| release this memory. */ void* rvalue; }; /* * Methods of instances of |CDataFinalizer| */ namespace Methods { static bool Dispose(JSContext* cx, unsigned argc, Value* vp); static bool Forget(JSContext* cx, unsigned argc, Value* vp); static bool ReadString(JSContext* cx, unsigned argc, Value* vp); static bool ReadTypedArray(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(JS::GCContext* gcx, JSObject* obj); /* * Return the Value contained by this finalizer. * * Note that the Value is actually not recorded, but converted back from C. */ static bool GetValue(JSContext* cx, JSObject* obj, MutableHandleValue result); } // namespace CDataFinalizer // Int64Base provides functions common to Int64 and UInt64. namespace Int64Base { JSObject* Construct(JSContext* cx, HandleObject proto, uint64_t data, bool isUnsigned); uint64_t GetInt(JSObject* obj); bool ToString(JSContext* cx, JSObject* obj, const CallArgs& args, bool isUnsigned); bool ToSource(JSContext* cx, JSObject* obj, const CallArgs& args, bool isUnsigned); static void Finalize(JS::GCContext* gcx, 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 // JS::CTypesCallbacks set of function pointers. static const JSClass sCTypesGlobalClass = { "ctypes", JSCLASS_HAS_RESERVED_SLOTS(CTYPESGLOBAL_SLOTS)}; static const JSClass sCABIClass = {"CABI", JSCLASS_HAS_RESERVED_SLOTS(CABI_SLOTS)}; // Class representing ctypes.{C,Pointer,Array,Struct,Function}Type.prototype. // This exists to give said prototypes a class of "CType", and to provide // reserved slots for stashing various other prototype objects. static const JSClassOps sCTypeProtoClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve nullptr, // finalize ConstructAbstract, // call ConstructAbstract, // construct nullptr, // trace }; static const JSClass sCTypeProtoClass = { "CType", JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS), &sCTypeProtoClassOps}; // Class representing ctypes.CData.prototype and the 'prototype' properties // of CTypes. This exists to give said prototypes a class of "CData". static const JSClass sCDataProtoClass = {"CData", 0}; static const JSClassOps sCTypeClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve CType::Finalize, // finalize CType::ConstructData, // call CType::ConstructData, // construct CType::Trace, // trace }; static const JSClass sCTypeClass = { "CType", JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, &sCTypeClassOps}; static const JSClassOps sCDataClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve CData::Finalize, // finalize FunctionType::Call, // call FunctionType::Call, // construct nullptr, // trace }; static const JSClass sCDataClass = { "CData", JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, &sCDataClassOps}; static const JSClassOps sCClosureClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve CClosure::Finalize, // finalize nullptr, // call nullptr, // construct CClosure::Trace, // trace }; static const JSClass sCClosureClass = { "CClosure", JSCLASS_HAS_RESERVED_SLOTS(CCLOSURE_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, &sCClosureClassOps}; /* * Class representing the prototype of CDataFinalizer. */ static const JSClass sCDataFinalizerProtoClass = {"CDataFinalizer", 0}; /* * Class representing instances of CDataFinalizer. * * Instances of CDataFinalizer have both private data (with type * |CDataFinalizer::Private|) and slots (see |CDataFinalizerSlots|). */ static const JSClassOps sCDataFinalizerClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve CDataFinalizer::Finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; static const JSClass sCDataFinalizerClass = { "CDataFinalizer", JSCLASS_HAS_RESERVED_SLOTS(CDATAFINALIZER_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, &sCDataFinalizerClassOps}; #define CTYPESFN_FLAGS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) #define CTYPESCTOR_FLAGS (CTYPESFN_FLAGS | JSFUN_CONSTRUCTOR) #define CTYPESACC_FLAGS (JSPROP_ENUMERATE | JSPROP_PERMANENT) #define CABIFN_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT) #define CDATAFN_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT) #define CDATAFINALIZERFN_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT) static const JSPropertySpec sCTypeProps[] = { JS_PSG("name", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("size", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("ptr", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("prototype", (Property::Fun), CTYPESACC_FLAGS), JS_PS_END}; static const JSFunctionSpec sCTypeFunctions[] = { JS_FN("array", CType::CreateArray, 0, CTYPESFN_FLAGS), JS_FN("toString", CType::ToString, 0, CTYPESFN_FLAGS), JS_FN("toSource", CType::ToSource, 0, CTYPESFN_FLAGS), JS_FS_END}; static const JSFunctionSpec sCABIFunctions[] = { JS_FN("toSource", ABI::ToSource, 0, CABIFN_FLAGS), JS_FN("toString", ABI::ToSource, 0, CABIFN_FLAGS), JS_FS_END}; static const JSPropertySpec sCDataProps[] = { JS_PSGS("value", (Property::Fun), (Property::Fun), JSPROP_PERMANENT), JS_PS_END}; static const JSFunctionSpec sCDataFunctions[] = { JS_FN("address", CData::Address, 0, CDATAFN_FLAGS), JS_FN("readString", CData::ReadString, 0, CDATAFN_FLAGS), JS_FN("readStringReplaceMalformed", CData::ReadStringReplaceMalformed, 0, CDATAFN_FLAGS), JS_FN("readTypedArray", CData::ReadTypedArray, 0, CDATAFN_FLAGS), JS_FN("toSource", CData::ToSource, 0, CDATAFN_FLAGS), JS_FN("toString", CData::ToSource, 0, CDATAFN_FLAGS), JS_FS_END}; static const JSFunctionSpec sCDataFinalizerFunctions[] = { JS_FN("dispose", CDataFinalizer::Methods::Dispose, 0, CDATAFINALIZERFN_FLAGS), JS_FN("forget", CDataFinalizer::Methods::Forget, 0, CDATAFINALIZERFN_FLAGS), JS_FN("readString", CDataFinalizer::Methods::ReadString, 0, CDATAFINALIZERFN_FLAGS), JS_FN("readTypedArray", CDataFinalizer::Methods::ReadTypedArray, 0, CDATAFINALIZERFN_FLAGS), JS_FN("toString", CDataFinalizer::Methods::ToString, 0, CDATAFINALIZERFN_FLAGS), JS_FN("toSource", CDataFinalizer::Methods::ToSource, 0, CDATAFINALIZERFN_FLAGS), JS_FS_END}; static const JSFunctionSpec sPointerFunction = JS_FN("PointerType", PointerType::Create, 1, CTYPESCTOR_FLAGS); static const JSPropertySpec sPointerProps[] = { JS_PSG("targetType", (Property::Fun), CTYPESACC_FLAGS), JS_PS_END}; static const JSFunctionSpec sPointerInstanceFunctions[] = { JS_FN("isNull", PointerType::IsNull, 0, CTYPESFN_FLAGS), JS_FN("increment", PointerType::Increment, 0, CTYPESFN_FLAGS), JS_FN("decrement", PointerType::Decrement, 0, CTYPESFN_FLAGS), JS_FS_END}; static const JSPropertySpec sPointerInstanceProps[] = { JS_PSGS( "contents", (Property::Fun), (Property::Fun), JSPROP_PERMANENT), JS_PS_END}; static const JSFunctionSpec sArrayFunction = JS_FN("ArrayType", ArrayType::Create, 1, CTYPESCTOR_FLAGS); static const JSPropertySpec sArrayProps[] = { JS_PSG( "elementType", (Property::Fun), CTYPESACC_FLAGS), JS_PSG( "length", (Property::Fun), CTYPESACC_FLAGS), JS_PS_END}; static const JSFunctionSpec sArrayInstanceFunctions[] = { JS_FN("addressOfElement", ArrayType::AddressOfElement, 1, CDATAFN_FLAGS), JS_FS_END}; static const JSPropertySpec sArrayInstanceProps[] = { JS_PSG( "length", (Property::Fun), JSPROP_PERMANENT), JS_PS_END}; static const JSFunctionSpec sStructFunction = JS_FN("StructType", StructType::Create, 2, CTYPESCTOR_FLAGS); static const JSPropertySpec sStructProps[] = { JS_PSG("fields", (Property::Fun), CTYPESACC_FLAGS), JS_PS_END}; static const JSFunctionSpec sStructFunctions[] = { JS_FN("define", StructType::Define, 1, CDATAFN_FLAGS), JS_FS_END}; static const JSFunctionSpec sStructInstanceFunctions[] = { JS_FN("addressOfField", StructType::AddressOfField, 1, CDATAFN_FLAGS), JS_FS_END}; static const JSFunctionSpec sFunctionFunction = JS_FN("FunctionType", FunctionType::Create, 2, CTYPESCTOR_FLAGS); static const JSPropertySpec sFunctionProps[] = { JS_PSG("argTypes", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("returnType", (Property::Fun), CTYPESACC_FLAGS), JS_PSG( "abi", (Property::Fun), CTYPESACC_FLAGS), JS_PSG("isVariadic", (Property::Fun), CTYPESACC_FLAGS), JS_PS_END}; static const JSFunctionSpec sFunctionInstanceFunctions[] = { JS_FN("call", js::fun_call, 1, CDATAFN_FLAGS), JS_FN("apply", js::fun_apply, 2, CDATAFN_FLAGS), JS_FS_END}; static const JSClass sInt64ProtoClass = {"Int64", 0}; static const JSClass sUInt64ProtoClass = {"UInt64", 0}; static const JSClassOps sInt64ClassOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve Int64Base::Finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; static const JSClass sInt64Class = { "Int64", JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, &sInt64ClassOps}; static const JSClass sUInt64Class = { "UInt64", JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, &sInt64ClassOps}; static const JSFunctionSpec sInt64StaticFunctions[] = { JS_FN("compare", Int64::Compare, 2, CTYPESFN_FLAGS), JS_FN("lo", Int64::Lo, 1, CTYPESFN_FLAGS), JS_FN("hi", Int64::Hi, 1, CTYPESFN_FLAGS), // "join" is defined specially; see InitInt64Class. JS_FS_END}; static const JSFunctionSpec sUInt64StaticFunctions[] = { JS_FN("compare", UInt64::Compare, 2, CTYPESFN_FLAGS), JS_FN("lo", UInt64::Lo, 1, CTYPESFN_FLAGS), JS_FN("hi", UInt64::Hi, 1, CTYPESFN_FLAGS), // "join" is defined specially; see InitInt64Class. JS_FS_END}; static const JSFunctionSpec sInt64Functions[] = { JS_FN("toString", Int64::ToString, 0, CTYPESFN_FLAGS), JS_FN("toSource", Int64::ToSource, 0, CTYPESFN_FLAGS), JS_FS_END}; static const JSFunctionSpec sUInt64Functions[] = { JS_FN("toString", UInt64::ToString, 0, CTYPESFN_FLAGS), JS_FN("toSource", UInt64::ToSource, 0, CTYPESFN_FLAGS), JS_FS_END}; static const JSPropertySpec sModuleProps[] = { JS_PSG("errno", (Property::Fun), JSPROP_PERMANENT), #if defined(XP_WIN) JS_PSG("winLastError", (Property::Fun), JSPROP_PERMANENT), #endif // defined(XP_WIN) JS_PS_END}; static const JSFunctionSpec sModuleFunctions[] = { JS_FN("CDataFinalizer", CDataFinalizer::Construct, 2, CTYPESFN_FLAGS), JS_FN("open", Library::Open, 1, CTYPESFN_FLAGS), JS_FN("cast", CData::Cast, 2, CTYPESFN_FLAGS), JS_FN("getRuntime", CData::GetRuntime, 1, CTYPESFN_FLAGS), JS_FN("libraryName", Library::Name, 1, CTYPESFN_FLAGS), JS_FS_END}; // Wrapper for arrays, to intercept indexed gets/sets. class CDataArrayProxyHandler : public ForwardingProxyHandler { public: static const CDataArrayProxyHandler singleton; static const char family; constexpr CDataArrayProxyHandler() : ForwardingProxyHandler(&family) {} bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const override; bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const override; }; const CDataArrayProxyHandler CDataArrayProxyHandler::singleton; const char CDataArrayProxyHandler::family = 0; bool CDataArrayProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const { RootedObject target(cx, proxy->as().target()); bool handled = false; if (!ArrayType::Getter(cx, target, id, vp, &handled)) { return false; } if (handled) { return true; } return ForwardingProxyHandler::get(cx, proxy, receiver, id, vp); } bool CDataArrayProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const { RootedObject target(cx, proxy->as().target()); bool handled = false; if (!ArrayType::Setter(cx, target, id, v, result, &handled)) { return false; } if (handled) { return true; } return ForwardingProxyHandler::set(cx, proxy, id, v, receiver, result); } static JSObject* MaybeUnwrapArrayWrapper(JSObject* obj) { if (obj->is() && obj->as().handler() == &CDataArrayProxyHandler::singleton) { return obj->as().target(); } return obj; } static MOZ_ALWAYS_INLINE JSString* NewUCString(JSContext* cx, const AutoStringChars&& from) { return JS_NewUCStringCopyN(cx, from.begin(), from.length()); } /* * Return a size rounded up to a multiple of a power of two. * * Note: |align| must be a power of 2. */ static MOZ_ALWAYS_INLINE size_t Align(size_t val, size_t align) { // Ensure that align is a power of two. MOZ_ASSERT(align != 0 && (align & (align - 1)) == 0); return ((val - 1) | (align - 1)) + 1; } static ABICode GetABICode(JSObject* obj) { // make sure we have an object representing a CABI class, // and extract the enumerated class type from the reserved slot. if (!obj->hasClass(&sCABIClass)) { return INVALID_ABI; } Value result = JS::GetReservedSlot(obj, SLOT_ABICODE); return ABICode(result.toInt32()); } static const JSErrorFormatString ErrorFormatString[CTYPESERR_LIMIT] = { #define MSG_DEF(name, count, exception, format) \ {#name, format, count, exception}, #include "ctypes/ctypes.msg" #undef MSG_DEF }; static const JSErrorFormatString* GetErrorMessage(void* userRef, const unsigned errorNumber) { if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT) { return &ErrorFormatString[errorNumber]; } return nullptr; } static JS::UniqueChars EncodeUTF8(JSContext* cx, AutoString& str) { RootedString string(cx, NewUCString(cx, str.finish())); if (!string) { return nullptr; } return JS_EncodeStringToUTF8(cx, string); } static const char* CTypesToSourceForError(JSContext* cx, HandleValue val, JS::UniqueChars& bytes) { if (val.isObject()) { RootedObject obj(cx, &val.toObject()); if (CType::IsCType(obj) || CData::IsCDataMaybeUnwrap(&obj)) { RootedValue v(cx, ObjectValue(*obj)); RootedString str(cx, JS_ValueToSource(cx, v)); bytes = JS_EncodeStringToUTF8(cx, str); return bytes.get(); } } return ValueToSourceForError(cx, val, bytes); } static void BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj, HandleString nameStr, unsigned ptrCount, AutoString& source); static void BuildCStyleTypeSource(JSContext* cx, JSObject* typeObj_, AutoString& source) { RootedObject typeObj(cx, typeObj_); MOZ_ASSERT(CType::IsCType(typeObj)); switch (CType::GetTypeCode(typeObj)) { #define BUILD_SOURCE(name, fromType, ffiType) \ case TYPE_##name: \ AppendString(cx, source, #name); \ break; CTYPES_FOR_EACH_TYPE(BUILD_SOURCE) #undef BUILD_SOURCE case TYPE_void_t: AppendString(cx, source, "void"); break; case TYPE_pointer: { unsigned ptrCount = 0; TypeCode type; RootedObject baseTypeObj(cx, typeObj); do { baseTypeObj = PointerType::GetBaseType(baseTypeObj); ptrCount++; type = CType::GetTypeCode(baseTypeObj); } while (type == TYPE_pointer || type == TYPE_array); if (type == TYPE_function) { BuildCStyleFunctionTypeSource(cx, baseTypeObj, nullptr, ptrCount, source); break; } BuildCStyleTypeSource(cx, baseTypeObj, source); AppendChars(source, '*', ptrCount); break; } case TYPE_struct: { RootedString name(cx, CType::GetName(cx, typeObj)); AppendString(cx, source, "struct "); AppendString(cx, source, name); break; } case TYPE_function: BuildCStyleFunctionTypeSource(cx, typeObj, nullptr, 0, source); break; case TYPE_array: MOZ_CRASH("TYPE_array shouldn't appear in function type"); } } static void BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj, HandleString nameStr, unsigned ptrCount, AutoString& source) { MOZ_ASSERT(CType::IsCType(typeObj)); FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); BuildCStyleTypeSource(cx, fninfo->mReturnType, source); AppendString(cx, source, " "); if (nameStr) { MOZ_ASSERT(ptrCount == 0); AppendString(cx, source, nameStr); } else if (ptrCount) { AppendString(cx, source, "("); AppendChars(source, '*', ptrCount); AppendString(cx, source, ")"); } AppendString(cx, source, "("); if (fninfo->mArgTypes.length() > 0) { for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { BuildCStyleTypeSource(cx, fninfo->mArgTypes[i], source); if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) { AppendString(cx, source, ", "); } } if (fninfo->mIsVariadic) { AppendString(cx, source, "..."); } } AppendString(cx, source, ")"); } static void BuildFunctionTypeSource(JSContext* cx, HandleObject funObj, AutoString& source) { MOZ_ASSERT(CData::IsCData(funObj) || CType::IsCType(funObj)); if (CData::IsCData(funObj)) { Value slot = JS::GetReservedSlot(funObj, SLOT_REFERENT); if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { slot = JS::GetReservedSlot(funObj, SLOT_FUNNAME); MOZ_ASSERT(!slot.isUndefined()); RootedObject typeObj(cx, CData::GetCType(funObj)); RootedObject baseTypeObj(cx, PointerType::GetBaseType(typeObj)); RootedString nameStr(cx, slot.toString()); BuildCStyleFunctionTypeSource(cx, baseTypeObj, nameStr, 0, source); return; } } RootedValue funVal(cx, ObjectValue(*funObj)); RootedString funcStr(cx, JS_ValueToSource(cx, funVal)); if (!funcStr) { JS_ClearPendingException(cx); AppendString(cx, source, "<>"); return; } AppendString(cx, source, funcStr); } enum class ConversionType { Argument = 0, Construct, Finalizer, Return, Setter }; static void BuildConversionPosition(JSContext* cx, ConversionType convType, HandleObject funObj, unsigned argIndex, AutoString& source) { switch (convType) { case ConversionType::Argument: { MOZ_ASSERT(funObj); AppendString(cx, source, " at argument "); AppendUInt(source, argIndex + 1); AppendString(cx, source, " of "); BuildFunctionTypeSource(cx, funObj, source); break; } case ConversionType::Finalizer: MOZ_ASSERT(funObj); AppendString(cx, source, " at argument 1 of "); BuildFunctionTypeSource(cx, funObj, source); break; case ConversionType::Return: MOZ_ASSERT(funObj); AppendString(cx, source, " at the return value of "); BuildFunctionTypeSource(cx, funObj, source); break; default: MOZ_ASSERT(!funObj); break; } } static JSLinearString* GetFieldName(HandleObject structObj, unsigned fieldIndex) { const FieldInfoHash* fields = StructType::GetFieldInfo(structObj); for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { if (r.front().value().mIndex == fieldIndex) { return (&r.front())->key(); } } return nullptr; } static void BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort, AutoString& result); static JS::UniqueChars TypeSourceForError(JSContext* cx, JSObject* typeObj) { AutoString source; BuildTypeSource(cx, typeObj, true, source); if (!source) { return nullptr; } return EncodeUTF8(cx, source); } static JS::UniqueChars FunctionTypeSourceForError(JSContext* cx, HandleObject funObj) { AutoString funSource; BuildFunctionTypeSource(cx, funObj, funSource); if (!funSource) { return nullptr; } return EncodeUTF8(cx, funSource); } static JS::UniqueChars ConversionPositionForError(JSContext* cx, ConversionType convType, HandleObject funObj, unsigned argIndex) { AutoString posSource; BuildConversionPosition(cx, convType, funObj, argIndex, posSource); if (!posSource) { return nullptr; } return EncodeUTF8(cx, posSource); } class IndexCString final { char indexStr[21]; // space for UINT64_MAX plus terminating null static_assert(sizeof(size_t) <= 8, "index array too small"); public: explicit IndexCString(size_t index) { SprintfLiteral(indexStr, "%zu", index); } const char* get() const { return indexStr; } }; static bool ConvError(JSContext* cx, const char* expectedStr, HandleValue actual, ConversionType convType, HandleObject funObj = nullptr, unsigned argIndex = 0, HandleObject arrObj = nullptr, unsigned arrIndex = 0) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } if (arrObj) { MOZ_ASSERT(CType::IsCType(arrObj)); switch (CType::GetTypeCode(arrObj)) { case TYPE_array: { MOZ_ASSERT(!funObj); IndexCString indexStr(arrIndex); JS::UniqueChars arrStr = TypeSourceForError(cx, arrObj); if (!arrStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_ARRAY, valStr, indexStr.get(), arrStr.get()); break; } case TYPE_struct: { RootedString name(cx, GetFieldName(arrObj, arrIndex)); MOZ_ASSERT(name); JS::UniqueChars nameStr = JS_EncodeStringToUTF8(cx, name); if (!nameStr) { return false; } JS::UniqueChars structStr = TypeSourceForError(cx, arrObj); if (!structStr) { return false; } JS::UniqueChars posStr; if (funObj) { posStr = ConversionPositionForError(cx, convType, funObj, argIndex); if (!posStr) { return false; } } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_STRUCT, valStr, nameStr.get(), expectedStr, structStr.get(), (posStr ? posStr.get() : "")); break; } default: MOZ_CRASH("invalid arrObj value"); } return false; } switch (convType) { case ConversionType::Argument: { MOZ_ASSERT(funObj); IndexCString indexStr(argIndex + 1); JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj); if (!funStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr.get(), funStr.get()); break; } case ConversionType::Finalizer: { MOZ_ASSERT(funObj); JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj); if (!funStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_FIN, valStr, funStr.get()); break; } case ConversionType::Return: { MOZ_ASSERT(funObj); JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj); if (!funStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_RET, valStr, funStr.get()); break; } case ConversionType::Setter: case ConversionType::Construct: MOZ_ASSERT(!funObj); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_SET, valStr, expectedStr); break; } return false; } static bool ConvError(JSContext* cx, HandleObject expectedType, HandleValue actual, ConversionType convType, HandleObject funObj = nullptr, unsigned argIndex = 0, HandleObject arrObj = nullptr, unsigned arrIndex = 0) { MOZ_ASSERT(CType::IsCType(expectedType)); JS::UniqueChars expectedStr = TypeSourceForError(cx, expectedType); if (!expectedStr) { return false; } return ConvError(cx, expectedStr.get(), actual, convType, funObj, argIndex, arrObj, arrIndex); } static bool ArgumentConvError(JSContext* cx, HandleValue actual, const char* funStr, unsigned argIndex) { MOZ_ASSERT(JS::StringIsASCII(funStr)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } IndexCString indexStr(argIndex + 1); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr.get(), funStr); return false; } static bool ArgumentLengthError(JSContext* cx, const char* fun, const char* count, const char* s) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_WRONG_ARG_LENGTH, fun, count, s); return false; } static bool ArrayLengthMismatch(JSContext* cx, size_t expectedLength, HandleObject arrObj, size_t actualLength, HandleValue actual, ConversionType convType) { MOZ_ASSERT(arrObj && CType::IsCType(arrObj)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } IndexCString expectedLengthStr(expectedLength); IndexCString actualLengthStr(actualLength); JS::UniqueChars arrStr = TypeSourceForError(cx, arrObj); if (!arrStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_ARRAY_MISMATCH, valStr, arrStr.get(), expectedLengthStr.get(), actualLengthStr.get()); return false; } static bool ArrayLengthOverflow(JSContext* cx, unsigned expectedLength, HandleObject arrObj, unsigned actualLength, HandleValue actual, ConversionType convType) { MOZ_ASSERT(arrObj && CType::IsCType(arrObj)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } IndexCString expectedLengthStr(expectedLength); IndexCString actualLengthStr(actualLength); JS::UniqueChars arrStr = TypeSourceForError(cx, arrObj); if (!arrStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_ARRAY_OVERFLOW, valStr, arrStr.get(), expectedLengthStr.get(), actualLengthStr.get()); return false; } static bool ArgumentRangeMismatch(JSContext* cx, const char* func, const char* range) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_ARG_RANGE_MISMATCH, func, range); return false; } static bool ArgumentTypeMismatch(JSContext* cx, const char* arg, const char* func, const char* type) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_ARG_TYPE_MISMATCH, arg, func, type); return false; } static bool CannotConstructError(JSContext* cx, const char* type) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_CANNOT_CONSTRUCT, type); return false; } static bool DuplicateFieldError(JSContext* cx, Handle name) { JS::UniqueChars nameStr = JS_EncodeStringToUTF8(cx, name); if (!nameStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_DUPLICATE_FIELD, nameStr.get()); return false; } static bool EmptyFinalizerCallError(JSContext* cx, const char* funName) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_EMPTY_FIN_CALL, funName); return false; } static bool EmptyFinalizerError(JSContext* cx, ConversionType convType, HandleObject funObj = nullptr, unsigned argIndex = 0) { JS::UniqueChars posStr; if (funObj) { posStr = ConversionPositionForError(cx, convType, funObj, argIndex); if (!posStr) { return false; } } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_EMPTY_FIN, (posStr ? posStr.get() : "")); return false; } static bool FieldCountMismatch(JSContext* cx, unsigned expectedCount, HandleObject structObj, unsigned actualCount, HandleValue actual, ConversionType convType, HandleObject funObj = nullptr, unsigned argIndex = 0) { MOZ_ASSERT(structObj && CType::IsCType(structObj)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } JS::UniqueChars structStr = TypeSourceForError(cx, structObj); if (!structStr) { return false; } IndexCString expectedCountStr(expectedCount); IndexCString actualCountStr(actualCount); JS::UniqueChars posStr; if (funObj) { posStr = ConversionPositionForError(cx, convType, funObj, argIndex); if (!posStr) { return false; } } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_MISMATCH, valStr, structStr.get(), expectedCountStr.get(), actualCountStr.get(), (posStr ? posStr.get() : "")); return false; } static bool FieldDescriptorCountError(JSContext* cx, HandleValue typeVal, size_t length) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes); if (!valStr) { return false; } IndexCString lengthStr(length); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_DESC_COUNT, valStr, lengthStr.get()); return false; } static bool FieldDescriptorNameError(JSContext* cx, HandleId id) { JS::UniqueChars idBytes; RootedValue idVal(cx, IdToValue(id)); const char* propStr = CTypesToSourceForError(cx, idVal, idBytes); if (!propStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_DESC_NAME, propStr); return false; } static bool FieldDescriptorSizeError(JSContext* cx, HandleObject typeObj, HandleId id) { RootedValue typeVal(cx, ObjectValue(*typeObj)); JS::UniqueChars typeBytes; const char* typeStr = CTypesToSourceForError(cx, typeVal, typeBytes); if (!typeStr) { return false; } RootedString idStr(cx, IdToString(cx, id)); JS::UniqueChars propStr = JS_EncodeStringToUTF8(cx, idStr); if (!propStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_DESC_SIZE, typeStr, propStr.get()); return false; } static bool FieldDescriptorNameTypeError(JSContext* cx, HandleValue typeVal) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_DESC_NAMETYPE, valStr); return false; } static bool FieldDescriptorTypeError(JSContext* cx, HandleValue poroVal, HandleId id) { JS::UniqueChars typeBytes; const char* typeStr = CTypesToSourceForError(cx, poroVal, typeBytes); if (!typeStr) { return false; } RootedString idStr(cx, IdToString(cx, id)); JS::UniqueChars propStr = JS_EncodeStringToUTF8(cx, idStr); if (!propStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_DESC_TYPE, typeStr, propStr.get()); return false; } static bool FieldMissingError(JSContext* cx, JSObject* typeObj, JSLinearString* name_) { JS::UniqueChars typeBytes; RootedString name(cx, name_); RootedValue typeVal(cx, ObjectValue(*typeObj)); const char* typeStr = CTypesToSourceForError(cx, typeVal, typeBytes); if (!typeStr) { return false; } JS::UniqueChars nameStr = JS_EncodeStringToUTF8(cx, name); if (!nameStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIELD_MISSING, typeStr, nameStr.get()); return false; } static bool FinalizerSizeError(JSContext* cx, HandleObject funObj, HandleValue actual) { MOZ_ASSERT(CType::IsCType(funObj)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } JS::UniqueChars funStr = FunctionTypeSourceForError(cx, funObj); if (!funStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_FIN_SIZE_ERROR, funStr.get(), valStr); return false; } static bool FunctionArgumentLengthMismatch( JSContext* cx, unsigned expectedCount, unsigned actualCount, HandleObject funObj, HandleObject typeObj, bool isVariadic) { JS::UniqueChars funStr; Value slot = JS::GetReservedSlot(funObj, SLOT_REFERENT); if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { funStr = FunctionTypeSourceForError(cx, funObj); } else { funStr = FunctionTypeSourceForError(cx, typeObj); } if (!funStr) { return false; } IndexCString expectedCountStr(expectedCount); IndexCString actualCountStr(actualCount); const char* variadicStr = isVariadic ? " or more" : ""; JS_ReportErrorNumberUTF8( cx, GetErrorMessage, nullptr, CTYPESMSG_ARG_COUNT_MISMATCH, funStr.get(), expectedCountStr.get(), variadicStr, actualCountStr.get()); return false; } static bool FunctionArgumentTypeError(JSContext* cx, uint32_t index, HandleValue typeVal, const char* reason) { MOZ_ASSERT(JS::StringIsASCII(reason)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, typeVal, valBytes); if (!valStr) { return false; } IndexCString indexStr(index + 1); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_ARG_TYPE_ERROR, indexStr.get(), reason, valStr); return false; } static bool FunctionReturnTypeError(JSContext* cx, HandleValue type, const char* reason) { MOZ_ASSERT(JS::StringIsASCII(reason)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, type, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_RET_TYPE_ERROR, reason, valStr); return false; } static bool IncompatibleCallee(JSContext* cx, const char* funName, HandleObject actualObj) { MOZ_ASSERT(JS::StringIsASCII(funName)); JS::UniqueChars valBytes; RootedValue val(cx, ObjectValue(*actualObj)); const char* valStr = CTypesToSourceForError(cx, val, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_INCOMPATIBLE_CALLEE, funName, valStr); return false; } static bool IncompatibleThisProto(JSContext* cx, const char* funName, const char* actualType) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_INCOMPATIBLE_THIS, funName, actualType); return false; } static bool IncompatibleThisProto(JSContext* cx, const char* funName, HandleValue actualVal) { MOZ_ASSERT(JS::StringIsASCII(funName)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actualVal, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_INCOMPATIBLE_THIS_VAL, funName, "incompatible object", valStr); return false; } static bool IncompatibleThisType(JSContext* cx, const char* funName, const char* actualType) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_INCOMPATIBLE_THIS_TYPE, funName, actualType); return false; } static bool IncompatibleThisType(JSContext* cx, const char* funName, const char* actualType, HandleValue actualVal) { MOZ_ASSERT(JS::StringIsASCII(funName)); MOZ_ASSERT(JS::StringIsASCII(actualType)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actualVal, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_INCOMPATIBLE_THIS_VAL, funName, actualType, valStr); return false; } static bool InvalidIndexError(JSContext* cx, HandleValue val) { JS::UniqueChars idBytes; const char* indexStr = CTypesToSourceForError(cx, val, idBytes); if (!indexStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_INVALID_INDEX, indexStr); return false; } static bool InvalidIndexError(JSContext* cx, HandleId id) { RootedValue idVal(cx, IdToValue(id)); return InvalidIndexError(cx, idVal); } static bool InvalidIndexRangeError(JSContext* cx, size_t index, size_t length) { IndexCString indexStr(index); IndexCString lengthStr(length); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_INVALID_RANGE, indexStr.get(), lengthStr.get()); return false; } static bool NonPrimitiveError(JSContext* cx, HandleObject typeObj) { MOZ_ASSERT(CType::IsCType(typeObj)); JS::UniqueChars typeStr = TypeSourceForError(cx, typeObj); if (!typeStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_NON_PRIMITIVE, typeStr.get()); return false; } static bool NonStringBaseError(JSContext* cx, HandleValue thisVal) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, thisVal, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_NON_STRING_BASE, valStr); return false; } static bool NonTypedArrayBaseError(JSContext* cx, HandleValue thisVal) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, thisVal, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_NON_TYPEDARRAY_BASE, valStr); return false; } static bool NullPointerError(JSContext* cx, const char* action, HandleObject obj) { MOZ_ASSERT(JS::StringIsASCII(action)); JS::UniqueChars valBytes; RootedValue val(cx, ObjectValue(*obj)); const char* valStr = CTypesToSourceForError(cx, val, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_NULL_POINTER, action, valStr); return false; } static bool PropNameNonStringError(JSContext* cx, HandleId id, HandleValue actual, ConversionType convType, HandleObject funObj = nullptr, unsigned argIndex = 0) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } JS::UniqueChars idBytes; RootedValue idVal(cx, IdToValue(id)); const char* propStr = CTypesToSourceForError(cx, idVal, idBytes); if (!propStr) { return false; } JS::UniqueChars posStr; if (funObj) { posStr = ConversionPositionForError(cx, convType, funObj, argIndex); if (!posStr) { return false; } } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_PROP_NONSTRING, propStr, valStr, (posStr ? posStr.get() : "")); return false; } static bool SizeOverflow(JSContext* cx, const char* name, const char* limit) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_SIZE_OVERFLOW, name, limit); return false; } static bool TypeError(JSContext* cx, const char* expected, HandleValue actual) { MOZ_ASSERT(JS::StringIsASCII(expected)); JS::UniqueChars bytes; const char* src = CTypesToSourceForError(cx, actual, bytes); if (!src) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_TYPE_ERROR, expected, src); return false; } static bool TypeOverflow(JSContext* cx, const char* expected, HandleValue actual) { MOZ_ASSERT(JS::StringIsASCII(expected)); JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_TYPE_OVERFLOW, valStr, expected); return false; } static bool UndefinedSizeCastError(JSContext* cx, HandleObject targetTypeObj) { JS::UniqueChars targetTypeStr = TypeSourceForError(cx, targetTypeObj); if (!targetTypeStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_UNDEFINED_SIZE_CAST, targetTypeStr.get()); return false; } static bool SizeMismatchCastError(JSContext* cx, HandleObject sourceTypeObj, HandleObject targetTypeObj, size_t sourceSize, size_t targetSize) { JS::UniqueChars sourceTypeStr = TypeSourceForError(cx, sourceTypeObj); if (!sourceTypeStr) { return false; } JS::UniqueChars targetTypeStr = TypeSourceForError(cx, targetTypeObj); if (!targetTypeStr) { return false; } IndexCString sourceSizeStr(sourceSize); IndexCString targetSizeStr(targetSize); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_SIZE_MISMATCH_CAST, targetTypeStr.get(), sourceTypeStr.get(), targetSizeStr.get(), sourceSizeStr.get()); return false; } static bool UndefinedSizePointerError(JSContext* cx, const char* action, HandleObject obj) { MOZ_ASSERT(JS::StringIsASCII(action)); JS::UniqueChars valBytes; RootedValue val(cx, ObjectValue(*obj)); const char* valStr = CTypesToSourceForError(cx, val, valBytes); if (!valStr) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_UNDEFINED_SIZE, action, valStr); return false; } static bool VariadicArgumentTypeError(JSContext* cx, uint32_t index, HandleValue actual) { JS::UniqueChars valBytes; const char* valStr = CTypesToSourceForError(cx, actual, valBytes); if (!valStr) { return false; } IndexCString indexStr(index + 1); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, CTYPESMSG_VARG_TYPE_ERROR, indexStr.get(), valStr); return false; } [[nodiscard]] JSObject* GetThisObject(JSContext* cx, const CallArgs& args, const char* msg) { if (!args.thisv().isObject()) { IncompatibleThisProto(cx, msg, args.thisv()); return nullptr; } return &args.thisv().toObject(); } static bool DefineToStringTag(JSContext* cx, HandleObject obj, const char* toStringTag) { RootedString toStringTagStr(cx, JS_NewStringCopyZ(cx, toStringTag)); if (!toStringTagStr) { return false; } RootedId toStringTagId( cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::toStringTag)); return JS_DefinePropertyById(cx, obj, toStringTagId, toStringTagStr, JSPROP_READONLY); } 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 (!DefineToStringTag(cx, prototype, "CType")) { 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; } if (!DefineToStringTag(cx, obj, "CABI")) { 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 (!DefineToStringTag(cx, prototype, "CData")) { 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.string(), 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, clasp, nullptr, clasp->name, construct, 0, nullptr, fs, nullptr, static_fs)); if (!prototype) { return nullptr; } if (clasp == &sInt64ProtoClass) { if (!DefineToStringTag(cx, prototype, "Int64")) { return nullptr; } } else { MOZ_ASSERT(clasp == &sUInt64ProtoClass); if (!DefineToStringTag(cx, prototype, "UInt64")) { 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. 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, HandleObjectVector 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' RootedObjectVector protos(cx); if (!protos.resize(CTYPEPROTO_SLOTS)) { return false; } if (!InitTypeConstructor( cx, ctypesObj, CTypeProto, CDataProto, sPointerFunction, nullptr, sPointerProps, sPointerInstanceFunctions, sPointerInstanceProps, protos[SLOT_POINTERPROTO], protos[SLOT_POINTERDATAPROTO])) return false; if (!InitTypeConstructor( cx, ctypesObj, CTypeProto, CDataProto, sArrayFunction, nullptr, sArrayProps, sArrayInstanceFunctions, sArrayInstanceProps, protos[SLOT_ARRAYPROTO], protos[SLOT_ARRAYDATAPROTO])) return false; if (!InitTypeConstructor( cx, ctypesObj, CTypeProto, CDataProto, sStructFunction, sStructFunctions, sStructProps, sStructInstanceFunctions, nullptr, protos[SLOT_STRUCTPROTO], protos[SLOT_STRUCTDATAPROTO])) return false; if (!InitTypeConstructor(cx, ctypesObj, CTypeProto, protos[SLOT_POINTERDATAPROTO], sFunctionFunction, nullptr, sFunctionProps, sFunctionInstanceFunctions, nullptr, protos[SLOT_FUNCTIONPROTO], protos[SLOT_FUNCTIONDATAPROTO])) return false; protos[SLOT_CDATAPROTO].set(CDataProto); // Create and attach the ctypes.{Int64,UInt64} constructors. // Each of these has, respectively: // * [[Class]] "Function" // * __proto__ === Function.prototype // * A constructor that creates a ctypes.{Int64,UInt64} object, // respectively. // * 'prototype' property: // * [[Class]] {"Int64Proto","UInt64Proto"} // * 'constructor' property === ctypes.{Int64,UInt64} protos[SLOT_INT64PROTO].set(InitInt64Class(cx, ctypesObj, &sInt64ProtoClass, Int64::Construct, sInt64Functions, sInt64StaticFunctions)); if (!protos[SLOT_INT64PROTO]) { return false; } protos[SLOT_UINT64PROTO].set( InitInt64Class(cx, ctypesObj, &sUInt64ProtoClass, UInt64::Construct, sUInt64Functions, sUInt64StaticFunctions)); if (!protos[SLOT_UINT64PROTO]) { return false; } // Finally, store a pointer to the global ctypes object. // Note that there is no other reliable manner of locating this object. protos[SLOT_CTYPES].set(ctypesObj); // Attach the prototypes just created to each of ctypes.CType.prototype, // and the special type constructors, so we can access them when constructing // instances of those types. AttachProtos(CTypeProto, protos); AttachProtos(protos[SLOT_POINTERPROTO], protos); AttachProtos(protos[SLOT_ARRAYPROTO], protos); AttachProtos(protos[SLOT_STRUCTPROTO], protos); AttachProtos(protos[SLOT_FUNCTIONPROTO], protos); RootedObject ABIProto(cx, InitABIClass(cx)); if (!ABIProto) { return false; } // Attach objects representing ABI constants. if (!DefineABIConstant(cx, ctypesObj, "default_abi", ABI_DEFAULT, ABIProto) || !DefineABIConstant(cx, ctypesObj, "stdcall_abi", ABI_STDCALL, ABIProto) || !DefineABIConstant(cx, ctypesObj, "thiscall_abi", ABI_THISCALL, ABIProto) || !DefineABIConstant(cx, ctypesObj, "winapi_abi", ABI_WINAPI, ABIProto)) return false; // Create objects representing the builtin types, and attach them to the // ctypes object. Each type object 't' has: // * [[Class]] "CType" // * __proto__ === ctypes.CType.prototype // * A constructor which creates and returns a CData object, containing // binary data of the given type. // * 'prototype' property: // * [[Class]] "CDataProto" // * __proto__ === ctypes.CData.prototype // * 'constructor' property === 't' #define DEFINE_TYPE(name, type, ffiType) \ RootedObject typeObj_##name(cx); \ { \ RootedValue typeVal(cx, Int32Value(sizeof(type))); \ RootedValue alignVal(cx, Int32Value(ffiType.alignment)); \ typeObj_##name = \ CType::DefineBuiltin(cx, ctypesObj, #name, CTypeProto, CDataProto, \ #name, TYPE_##name, typeVal, alignVal, &ffiType); \ if (!typeObj_##name) return false; \ } CTYPES_FOR_EACH_TYPE(DEFINE_TYPE) #undef DEFINE_TYPE // Alias 'ctypes.unsigned' as 'ctypes.unsigned_int', since they represent // the same type in C. if (!JS_DefineProperty(cx, ctypesObj, "unsigned", typeObj_unsigned_int, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) return false; // Alias 'ctypes.jschar' as 'ctypes.char16_t' to prevent breaking addons // that are still using jschar (bug 1064935). if (!JS_DefineProperty(cx, ctypesObj, "jschar", typeObj_char16_t, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) return false; // Create objects representing the special types void_t and voidptr_t. RootedObject typeObj( cx, CType::DefineBuiltin(cx, ctypesObj, "void_t", CTypeProto, CDataProto, "void", TYPE_void_t, JS::UndefinedHandleValue, JS::UndefinedHandleValue, &ffi_type_void)); if (!typeObj) { return false; } typeObj = PointerType::CreateInternal(cx, typeObj); if (!typeObj) { return false; } if (!JS_DefineProperty(cx, ctypesObj, "voidptr_t", typeObj, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) return false; return true; } bool IsCTypesGlobal(JSObject* obj) { return obj->hasClass(&sCTypesGlobalClass); } bool IsCTypesGlobal(HandleValue v) { return v.isObject() && IsCTypesGlobal(&v.toObject()); } // Get the JS::CTypesCallbacks struct from the 'ctypes' object 'obj'. const JS::CTypesCallbacks* GetCallbacks(JSObject* obj) { MOZ_ASSERT(IsCTypesGlobal(obj)); Value result = JS::GetReservedSlot(obj, SLOT_CALLBACKS); if (result.isUndefined()) { return nullptr; } return static_cast(result.toPrivate()); } // Utility function to access a property of an object as an object // returns false and sets the error if the property does not exist // or is not an object static bool GetObjectProperty(JSContext* cx, HandleObject obj, const char* property, MutableHandleObject result) { RootedValue val(cx); if (!JS_GetProperty(cx, obj, property, &val)) { return false; } if (val.isPrimitive()) { JS_ReportErrorASCII(cx, "missing or non-object field"); return false; } result.set(val.toObjectOrNull()); return true; } } // namespace js::ctypes using namespace js; using namespace js::ctypes; JS_PUBLIC_API bool JS::InitCTypesClass(JSContext* cx, Handle global) { // attach ctypes property to global object RootedObject ctypes(cx, JS_NewObject(cx, &sCTypesGlobalClass)); if (!ctypes) { return false; } if (!JS_DefineProperty(cx, global, "ctypes", ctypes, JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } if (!InitTypeClasses(cx, ctypes)) { return false; } // attach API functions and properties if (!JS_DefineFunctions(cx, ctypes, sModuleFunctions) || !JS_DefineProperties(cx, ctypes, sModuleProps)) return false; if (!DefineToStringTag(cx, ctypes, "ctypes")) { 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 (!DefineToStringTag(cx, prototype, "CDataFinalizer")) { 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 CTypesCallbacks* callbacks) { MOZ_ASSERT(callbacks); MOZ_ASSERT(IsCTypesGlobal(ctypesObj)); // Set the callbacks on a reserved slot. JS_SetReservedSlot(ctypesObj, SLOT_CALLBACKS, PrivateValue(const_cast(callbacks))); } namespace js { namespace ctypes { size_t SizeOfDataIfCDataObject(mozilla::MallocSizeOf mallocSizeOf, JSObject* obj) { if (!CData::IsCData(obj)) { return 0; } size_t n = 0; Value slot = JS::GetReservedSlot(obj, ctypes::SLOT_OWNS); if (!slot.isUndefined()) { bool owns = slot.toBoolean(); slot = JS::GetReservedSlot(obj, ctypes::SLOT_DATA); if (!slot.isUndefined()) { char** buffer = static_cast(slot.toPrivate()); n += mallocSizeOf(buffer); if (owns) { n += mallocSizeOf(*buffer); } } } return n; } /******************************************************************************* ** 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.) static_assert(sizeof(bool) == 1 || sizeof(bool) == 4); static_assert(sizeof(char) == 1); static_assert(sizeof(short) == 2); static_assert(sizeof(int) == 4); static_assert(sizeof(unsigned) == 4); static_assert(sizeof(long) == 4 || sizeof(long) == 8); static_assert(sizeof(long long) == 8); static_assert(sizeof(size_t) == sizeof(uintptr_t)); static_assert(sizeof(float) == 4); static_assert(sizeof(PRFuncPtr) == sizeof(void*)); static_assert(numeric_limits::is_signed); template > struct ConvertImpl; template struct ConvertImpl { static MOZ_ALWAYS_INLINE TargetType Convert(FromType input) { return JS::ToSignedOrUnsignedInteger(input); } }; template struct ConvertUnsignedTargetTo { static TargetType convert(std::make_unsigned_t input) { return std::is_signed_v ? mozilla::WrapToSigned(input) : input; } }; template <> struct ConvertUnsignedTargetTo { static char16_t convert(char16_t input) { // mozilla::WrapToSigned can't be used on char16_t. return input; } }; template struct ConvertImpl { static MOZ_ALWAYS_INLINE TargetType Convert(FromType input) { using UnsignedTargetType = std::make_unsigned_t; auto resultUnsigned = static_cast(input); return ConvertUnsignedTargetTo::convert(resultUnsigned); } }; template static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) { static_assert( std::is_integral_v != std::is_floating_point_v, "should only be converting from floating/integral type"); return ConvertImpl::Convert(d); } template static MOZ_ALWAYS_INLINE bool IsAlwaysExact() { // Return 'true' if TargetType can always exactly represent FromType. // This means that: // 1) TargetType must be the same or more bits wide as FromType. For integers // represented in 'n' bits, unsigned variants will have 'n' digits while // signed will have 'n - 1'. For floating point types, 'digits' is the // mantissa width. // 2) If FromType is signed, TargetType must also be signed. (Floating point // types are always signed.) // 3) If TargetType is an exact integral type, FromType must be also. if (numeric_limits::digits < numeric_limits::digits) { return false; } if (numeric_limits::is_signed && !numeric_limits::is_signed) return false; if (!numeric_limits::is_exact && numeric_limits::is_exact) return false; return true; } // Templated helper to determine if FromType 'i' converts losslessly to // TargetType 'j'. Default case where both types are the same signedness. template struct IsExactImpl { static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { static_assert(numeric_limits::is_exact); return FromType(j) == i; } }; // Specialization where TargetType is unsigned, FromType is signed. template struct IsExactImpl { static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { static_assert(numeric_limits::is_exact); return i >= 0 && FromType(j) == i; } }; // Specialization where TargetType is signed, FromType is unsigned. template struct IsExactImpl { static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) { static_assert(numeric_limits::is_exact); return TargetType(i) >= 0 && FromType(j) == i; } }; // Convert FromType 'i' to TargetType 'result', returning true iff 'result' // is an exact representation of 'i'. template static MOZ_ALWAYS_INLINE bool ConvertExact(FromType i, TargetType* result) { static_assert(std::numeric_limits::is_exact, "TargetType must be exact to simplify conversion"); *result = Convert(i); // See if we can avoid a dynamic check. if (IsAlwaysExact()) { return true; } // Return 'true' if 'i' is exactly representable in 'TargetType'. return IsExactImpl::is_signed, numeric_limits::is_signed>::Test(i, *result); } // Templated helper to determine if Type 'i' is negative. Default case // where IntegerType is unsigned. template struct IsNegativeImpl { static MOZ_ALWAYS_INLINE bool Test(Type i) { return false; } }; // Specialization where Type is signed. template struct IsNegativeImpl { static MOZ_ALWAYS_INLINE bool Test(Type i) { return i < 0; } }; // Determine whether Type 'i' is negative. template static MOZ_ALWAYS_INLINE bool IsNegative(Type i) { return IsNegativeImpl::is_signed>::Test(i); } // Implicitly convert val to bool, allowing bool, int, and double // arguments numerically equal to 0 or 1. static bool jsvalToBool(JSContext* cx, HandleValue val, bool* result) { if (val.isBoolean()) { *result = val.toBoolean(); return true; } if (val.isInt32()) { int32_t i = val.toInt32(); *result = i != 0; return i == 0 || i == 1; } if (val.isDouble()) { double d = val.toDouble(); *result = d != 0; // Allow -0. return d == 1 || d == 0; } // Don't silently convert null to bool. It's probably a mistake. return false; } // Implicitly convert val to IntegerType, allowing bool, int, double, // Int64, UInt64, and CData integer types 't' where all values of 't' are // representable by IntegerType. template static bool jsvalToInteger(JSContext* cx, HandleValue val, IntegerType* result) { static_assert(numeric_limits::is_exact); if (val.isInt32()) { // Make sure the integer fits in the alotted precision, and has the right // sign. int32_t i = val.toInt32(); return ConvertExact(i, result); } if (val.isDouble()) { // Don't silently lose bits here -- check that val really is an // integer value, and has the right sign. double d = val.toDouble(); return ConvertExact(d, result); } if (val.isObject()) { RootedObject obj(cx, &val.toObject()); if (CData::IsCDataMaybeUnwrap(&obj)) { JSObject* typeObj = CData::GetCType(obj); void* data = CData::GetData(obj); // Check whether the source type is always representable, with exact // precision, by the target type. If it is, convert the value. switch (CType::GetTypeCode(typeObj)) { #define INTEGER_CASE(name, fromType, ffiType) \ case TYPE_##name: \ if (!IsAlwaysExact()) return false; \ *result = IntegerType(*static_cast(data)); \ return true; CTYPES_FOR_EACH_INT_TYPE(INTEGER_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGER_CASE) #undef INTEGER_CASE case TYPE_void_t: case TYPE_bool: case TYPE_float: case TYPE_double: case TYPE_float32_t: case TYPE_float64_t: case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: case TYPE_char16_t: case TYPE_pointer: case TYPE_function: case TYPE_array: case TYPE_struct: // Not a compatible number type. return false; } } if (Int64::IsInt64(obj)) { // Make sure the integer fits in IntegerType. int64_t i = Int64Base::GetInt(obj); return ConvertExact(i, result); } if (UInt64::IsUInt64(obj)) { // Make sure the integer fits in IntegerType. uint64_t i = Int64Base::GetInt(obj); return ConvertExact(i, result); } if (CDataFinalizer::IsCDataFinalizer(obj)) { RootedValue innerData(cx); if (!CDataFinalizer::GetValue(cx, obj, &innerData)) { return false; // Nothing to convert } return jsvalToInteger(cx, innerData, result); } return false; } if (val.isBoolean()) { // Implicitly promote boolean values to 0 or 1, like C. *result = val.toBoolean(); MOZ_ASSERT(*result == 0 || *result == 1); return true; } // Don't silently convert null to an integer. It's probably a mistake. return false; } // Implicitly convert val to FloatType, allowing int, double, // Int64, UInt64, and CData numeric types 't' where all values of 't' are // representable by FloatType. template static bool jsvalToFloat(JSContext* cx, HandleValue val, FloatType* result) { static_assert(!numeric_limits::is_exact); // The following casts may silently throw away some bits, but there's // no good way around it. Sternly requiring that the 64-bit double // argument be exactly representable as a 32-bit float is // unrealistic: it would allow 1/2 to pass but not 1/3. if (val.isInt32()) { *result = FloatType(val.toInt32()); return true; } if (val.isDouble()) { *result = FloatType(val.toDouble()); return true; } if (val.isObject()) { RootedObject obj(cx, &val.toObject()); if (CData::IsCDataMaybeUnwrap(&obj)) { JSObject* typeObj = CData::GetCType(obj); void* data = CData::GetData(obj); // Check whether the source type is always representable, with exact // precision, by the target type. If it is, convert the value. switch (CType::GetTypeCode(typeObj)) { #define NUMERIC_CASE(name, fromType, ffiType) \ case TYPE_##name: \ if (!IsAlwaysExact()) return false; \ *result = FloatType(*static_cast(data)); \ return true; CTYPES_FOR_EACH_FLOAT_TYPE(NUMERIC_CASE) CTYPES_FOR_EACH_INT_TYPE(NUMERIC_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(NUMERIC_CASE) #undef NUMERIC_CASE case TYPE_void_t: case TYPE_bool: case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: case TYPE_char16_t: case TYPE_pointer: case TYPE_function: case TYPE_array: case TYPE_struct: // Not a compatible number type. return false; } } } // Don't silently convert true to 1.0 or false to 0.0, even though C/C++ // does it. It's likely to be a mistake. return false; } template static bool StringToInteger(JSContext* cx, CharT* cp, size_t length, IntegerType* result, bool* overflow) { static_assert(numeric_limits::is_exact); const CharT* end = cp + length; if (cp == end) { return false; } IntegerType sign = 1; if (cp[0] == '-') { if (!numeric_limits::is_signed) { return false; } sign = -1; ++cp; } // Assume base-10, unless the string begins with '0x' or '0X'. IntegerType base = 10; if (end - cp > 2 && cp[0] == '0' && (cp[1] == 'x' || cp[1] == 'X')) { cp += 2; base = 16; } // Scan the string left to right and build the number, // checking for valid characters 0 - 9, a - f, A - F and overflow. IntegerType i = 0; while (cp != end) { char16_t c = *cp++; uint8_t digit; if (IsAsciiDigit(c)) { digit = c - '0'; } else if (base == 16 && c >= 'a' && c <= 'f') { digit = c - 'a' + 10; } else if (base == 16 && c >= 'A' && c <= 'F') { digit = c - 'A' + 10; } else { return false; } IntegerType ii = i; i = ii * base + sign * digit; if (i / base != ii) { *overflow = true; return false; } } *result = i; return true; } template static bool StringToInteger(JSContext* cx, JSString* string, IntegerType* result, bool* overflow) { JSLinearString* linear = string->ensureLinear(cx); if (!linear) { return false; } AutoCheckCannotGC nogc; size_t length = linear->length(); return string->hasLatin1Chars() ? StringToInteger(cx, linear->latin1Chars(nogc), length, result, overflow) : StringToInteger(cx, linear->twoByteChars(nogc), length, result, overflow); } // Implicitly convert val to IntegerType, allowing int, double, // Int64, UInt64, and optionally a decimal or hexadecimal string argument. // (This is common code shared by jsvalToSize and the Int64/UInt64 // constructors.) template static bool jsvalToBigInteger(JSContext* cx, HandleValue val, bool allowString, IntegerType* result, bool* overflow) { static_assert(numeric_limits::is_exact); if (val.isInt32()) { // Make sure the integer fits in the alotted precision, and has the right // sign. int32_t i = val.toInt32(); return ConvertExact(i, result); } if (val.isDouble()) { // Don't silently lose bits here -- check that val really is an // integer value, and has the right sign. double d = val.toDouble(); return ConvertExact(d, result); } if (allowString && val.isString()) { // Allow conversion from base-10 or base-16 strings, provided the result // fits in IntegerType. (This allows an Int64 or UInt64 object to be passed // to the JS array element operator, which will automatically call // toString() on the object for us.) return StringToInteger(cx, val.toString(), result, overflow); } if (val.isObject()) { // Allow conversion from an Int64 or UInt64 object directly. JSObject* obj = &val.toObject(); if (UInt64::IsUInt64(obj)) { // Make sure the integer fits in IntegerType. uint64_t i = Int64Base::GetInt(obj); return ConvertExact(i, result); } if (Int64::IsInt64(obj)) { // Make sure the integer fits in IntegerType. int64_t i = Int64Base::GetInt(obj); return ConvertExact(i, result); } if (CDataFinalizer::IsCDataFinalizer(obj)) { RootedValue innerData(cx); if (!CDataFinalizer::GetValue(cx, obj, &innerData)) { return false; // Nothing to convert } return jsvalToBigInteger(cx, innerData, allowString, result, overflow); } } return false; } // Implicitly convert val to a size value, where the size value is represented // by size_t but must also fit in a double. static bool jsvalToSize(JSContext* cx, HandleValue val, bool allowString, size_t* result) { bool dummy; if (!jsvalToBigInteger(cx, val, allowString, result, &dummy)) { return false; } // Also check that the result fits in a double. return Convert(double(*result)) == *result; } // Implicitly convert val to IntegerType, allowing int, double, // Int64, UInt64, and optionally a decimal or hexadecimal string argument. // (This is common code shared by jsvalToSize and the Int64/UInt64 // constructors.) template static bool jsidToBigInteger(JSContext* cx, jsid val, bool allowString, IntegerType* result) { static_assert(numeric_limits::is_exact); if (val.isInt()) { // Make sure the integer fits in the alotted precision, and has the right // sign. int32_t i = val.toInt(); return ConvertExact(i, 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.) bool dummy; return StringToInteger(cx, val.toString(), result, &dummy); } return false; } // Implicitly convert val to a size value, where the size value is represented // by size_t but must also fit in a double. static bool jsidToSize(JSContext* cx, jsid val, bool allowString, size_t* result) { if (!jsidToBigInteger(cx, val, allowString, result)) { return false; } // Also check that the result fits in a double. return Convert(double(*result)) == *result; } // Implicitly convert a size value to a Value, ensuring that the size_t value // fits in a double. static bool SizeTojsval(JSContext* cx, size_t size, MutableHandleValue result) { if (Convert(double(size)) != size) { return false; } result.setNumber(double(size)); return true; } // Forcefully convert val to IntegerType when explicitly requested. template static bool jsvalToIntegerExplicit(HandleValue val, IntegerType* result) { static_assert(numeric_limits::is_exact); if (val.isDouble()) { // Convert using ToInt32-style semantics: non-finite numbers become 0, and // everything else rounds toward zero then maps into |IntegerType| with // wraparound semantics. double d = val.toDouble(); *result = JS::ToSignedOrUnsignedInteger(d); return true; } if (val.isObject()) { // Convert Int64 and UInt64 values by C-style cast. JSObject* obj = &val.toObject(); if (Int64::IsInt64(obj)) { int64_t i = Int64Base::GetInt(obj); *result = IntegerType(i); return true; } if (UInt64::IsUInt64(obj)) { uint64_t i = Int64Base::GetInt(obj); *result = IntegerType(i); return true; } } return false; } // Forcefully convert val to a pointer value when explicitly requested. static bool jsvalToPtrExplicit(JSContext* cx, HandleValue val, uintptr_t* result) { if (val.isInt32()) { // int32_t always fits in intptr_t. If the integer is negative, cast through // an intptr_t intermediate to sign-extend. int32_t i = val.toInt32(); *result = i < 0 ? uintptr_t(intptr_t(i)) : uintptr_t(i); return true; } if (val.isDouble()) { double d = val.toDouble(); if (d < 0) { // Cast through an intptr_t intermediate to sign-extend. intptr_t i = Convert(d); if (double(i) != d) { return false; } *result = uintptr_t(i); return true; } // Don't silently lose bits here -- check that val really is an // integer value, and has the right sign. *result = Convert(d); return double(*result) == d; } if (val.isObject()) { JSObject* obj = &val.toObject(); if (Int64::IsInt64(obj)) { int64_t i = Int64Base::GetInt(obj); intptr_t p = intptr_t(i); // Make sure the integer fits in the alotted precision. if (int64_t(p) != i) { return false; } *result = uintptr_t(p); return true; } if (UInt64::IsUInt64(obj)) { uint64_t i = Int64Base::GetInt(obj); // Make sure the integer fits in the alotted precision. *result = uintptr_t(i); return uint64_t(*result) == i; } } return false; } template void IntegerToString(IntegerType i, int radix, StringBuilder& result) { static_assert(numeric_limits::is_exact); // The buffer must be big enough for all the bits of IntegerType to fit, // in base-2, including '-'. CharType buffer[sizeof(IntegerType) * 8 + 1]; CharType* end = std::end(buffer); CharType* cp = end; // Build the string in reverse. We use multiplication and subtraction // instead of modulus because that's much faster. const bool isNegative = IsNegative(i); size_t sign = isNegative ? -1 : 1; do { IntegerType ii = i / IntegerType(radix); size_t index = sign * size_t(i - ii * IntegerType(radix)); *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[index]; i = ii; } while (i != 0); if (isNegative) { *--cp = '-'; } MOZ_ASSERT(cp >= buffer); if (!result.append(cp, end)) { return; } } // Convert C binary value 'data' of CType 'typeObj' to a JS primitive, where // possible; otherwise, construct and return a CData object. The following // semantics apply when constructing a CData object for return: // * If 'wantPrimitive' is true, the caller indicates that 'result' must be // a JS primitive, and ConvertToJS will fail if 'result' would be a CData // object. Otherwise: // * If a CData object 'parentObj' is supplied, the new CData object is // dependent on the given parent and its buffer refers to a slice of the // parent's buffer. // * If 'parentObj' is null, the new CData object may or may not own its // resulting buffer depending on the 'ownResult' argument. static bool ConvertToJS(JSContext* cx, HandleObject typeObj, HandleObject parentObj, void* data, bool wantPrimitive, bool ownResult, MutableHandleValue result) { MOZ_ASSERT(!parentObj || CData::IsCData(parentObj)); MOZ_ASSERT(!parentObj || !ownResult); MOZ_ASSERT(!wantPrimitive || !ownResult); TypeCode typeCode = CType::GetTypeCode(typeObj); switch (typeCode) { case TYPE_void_t: result.setUndefined(); break; case TYPE_bool: result.setBoolean(*static_cast(data)); break; #define INT_CASE(name, type, ffiType) \ case TYPE_##name: { \ type value = *static_cast(data); \ if (sizeof(type) < 4) \ result.setInt32(int32_t(value)); \ else \ result.setDouble(double(value)); \ break; \ } CTYPES_FOR_EACH_INT_TYPE(INT_CASE) #undef INT_CASE #define WRAPPED_INT_CASE(name, type, ffiType) \ case TYPE_##name: { \ /* Return an Int64 or UInt64 object - do not convert to a JS number. */ \ uint64_t value; \ RootedObject proto(cx); \ if (!numeric_limits::is_signed) { \ value = *static_cast(data); \ /* Get ctypes.UInt64.prototype from ctypes.CType.prototype. */ \ proto = CType::GetProtoFromType(cx, typeObj, SLOT_UINT64PROTO); \ if (!proto) return false; \ } else { \ value = int64_t(*static_cast(data)); \ /* Get ctypes.Int64.prototype from ctypes.CType.prototype. */ \ proto = CType::GetProtoFromType(cx, typeObj, SLOT_INT64PROTO); \ if (!proto) return false; \ } \ \ JSObject* obj = Int64Base::Construct(cx, proto, value, \ !numeric_limits::is_signed); \ if (!obj) return false; \ result.setObject(*obj); \ break; \ } CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE) #undef WRAPPED_INT_CASE #define FLOAT_CASE(name, type, ffiType) \ case TYPE_##name: { \ type value = *static_cast(data); \ result.setDouble(double(value)); \ break; \ } CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) #undef FLOAT_CASE #define CHAR_CASE(name, type, ffiType) \ case TYPE_##name: \ /* Convert to an integer. We have no idea what character encoding to */ \ /* use, if any. */ \ result.setInt32(*static_cast(data)); \ break; CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE) #undef CHAR_CASE case TYPE_char16_t: { // Convert the char16_t to a 1-character string. JSString* str = JS_NewUCStringCopyN(cx, static_cast(data), 1); if (!str) { return false; } result.setString(str); break; } case TYPE_pointer: case TYPE_array: case TYPE_struct: { // We're about to create a new CData object to return. If the caller // doesn't want this, return early. if (wantPrimitive) { return NonPrimitiveError(cx, typeObj); } JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult); if (!obj) { return false; } result.setObject(*obj); break; } case TYPE_function: MOZ_CRASH("cannot return a FunctionType"); } return true; } // Determine if the contents of a typed array can be converted without // ambiguity to a C type. Elements of a Int8Array are converted to // ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc. bool CanConvertTypedArrayItemTo(JSObject* baseType, JSObject* valObj, JSContext* cx) { TypeCode baseTypeCode = CType::GetTypeCode(baseType); if (baseTypeCode == TYPE_void_t || baseTypeCode == TYPE_char) { return true; } TypeCode elementTypeCode; switch (JS_GetArrayBufferViewType(valObj)) { case Scalar::Int8: elementTypeCode = TYPE_int8_t; break; case Scalar::Uint8: case Scalar::Uint8Clamped: elementTypeCode = TYPE_uint8_t; break; case Scalar::Int16: elementTypeCode = TYPE_int16_t; break; case Scalar::Uint16: elementTypeCode = TYPE_uint16_t; break; case Scalar::Int32: elementTypeCode = TYPE_int32_t; break; case Scalar::Uint32: elementTypeCode = TYPE_uint32_t; break; case Scalar::Float32: elementTypeCode = TYPE_float32_t; break; case Scalar::Float64: elementTypeCode = TYPE_float64_t; break; default: return false; } return elementTypeCode == baseTypeCode; } static CDataFinalizer::Private* GetFinalizerPrivate(JSObject* obj) { MOZ_ASSERT(CDataFinalizer::IsCDataFinalizer(obj)); using T = CDataFinalizer::Private; return JS::GetMaybePtrFromReservedSlot(obj, SLOT_DATAFINALIZER_PRIVATE); } // Implicitly convert Value 'val' to a C binary representation of CType // 'targetType', storing the result in 'buffer'. Adequate space must be // provided in 'buffer' by the caller. This function generally does minimal // coercion between types. There are two cases in which this function is used: // 1) The target buffer is internal to a CData object; we simply write data // into it. // 2) We are converting an argument for an ffi call, in which case 'convType' // will be 'ConversionType::Argument'. This allows us to handle a special // case: if necessary, we can autoconvert a JS string primitive to a // pointer-to-character type. In this case, ownership of the allocated string // is handed off to the caller; 'freePointer' will be set to indicate this. static bool ImplicitConvert(JSContext* cx, HandleValue val, JSObject* targetType_, void* buffer, ConversionType convType, bool* freePointer, HandleObject funObj = nullptr, unsigned argIndex = 0, HandleObject arrObj = nullptr, unsigned arrIndex = 0) { RootedObject targetType(cx, targetType_); MOZ_ASSERT(CType::IsSizeDefined(targetType)); // First, check if val is either a CData object or a CDataFinalizer // of type targetType. JSObject* sourceData = nullptr; JSObject* sourceType = nullptr; RootedObject valObj(cx, nullptr); if (val.isObject()) { valObj = &val.toObject(); if (CData::IsCDataMaybeUnwrap(&valObj)) { sourceData = valObj; sourceType = CData::GetCType(sourceData); // If the types are equal, copy the buffer contained within the CData. // (Note that the buffers may overlap partially or completely.) if (CType::TypesEqual(sourceType, targetType)) { size_t size = CType::GetSize(sourceType); memmove(buffer, CData::GetData(sourceData), size); return true; } } else if (CDataFinalizer::IsCDataFinalizer(valObj)) { sourceData = valObj; sourceType = CDataFinalizer::GetCType(cx, sourceData); CDataFinalizer::Private* p = GetFinalizerPrivate(sourceData); if (!p) { // We have called |dispose| or |forget| already. return EmptyFinalizerError(cx, convType, funObj, argIndex); } // If the types are equal, copy the buffer contained within the CData. if (CType::TypesEqual(sourceType, targetType)) { memmove(buffer, p->cargs, p->cargs_size); return true; } } } TypeCode targetCode = CType::GetTypeCode(targetType); switch (targetCode) { case TYPE_bool: { // Do not implicitly lose bits, but allow the values 0, 1, and -0. // Programs can convert explicitly, if needed, using `Boolean(v)` or // `!!v`. bool result; if (!jsvalToBool(cx, val, &result)) { return ConvError(cx, "boolean", val, convType, funObj, argIndex, arrObj, arrIndex); } *static_cast(buffer) = result; break; } #define CHAR16_CASE(name, type, ffiType) \ case TYPE_##name: { \ /* Convert from a 1-character string, regardless of encoding, */ \ /* or from an integer, provided the result fits in 'type'. */ \ type result = 0; \ if (val.isString()) { \ JSString* str = val.toString(); \ if (str->length() != 1) \ return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \ arrIndex); \ JSLinearString* linear = str->ensureLinear(cx); \ if (!linear) return false; \ result = linear->latin1OrTwoByteChar(0); \ } else if (!jsvalToInteger(cx, val, &result)) { \ return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \ arrIndex); \ } \ *static_cast(buffer) = result; \ break; \ } CTYPES_FOR_EACH_CHAR16_TYPE(CHAR16_CASE) #undef CHAR16_CASE #define INTEGRAL_CASE(name, type, ffiType) \ case TYPE_##name: { \ /* Do not implicitly lose bits. */ \ type result; \ if (!jsvalToInteger(cx, val, &result)) \ return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \ arrIndex); \ *static_cast(buffer) = result; \ break; \ } CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) // It's hard to believe ctypes.char16_t("f") should work yet // ctypes.char("f") should not. Ditto for ctypes.{un,}signed_char. But // this is how ctypes has always worked, so preserve these semantics, and // don't switch to an algorithm similar to that in DEFINE_CHAR16_TYPE // above, just yet. CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) #undef INTEGRAL_CASE #define FLOAT_CASE(name, type, ffiType) \ case TYPE_##name: { \ type result; \ if (!jsvalToFloat(cx, val, &result)) \ return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \ arrIndex); \ *static_cast(buffer) = result; \ break; \ } CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) #undef FLOAT_CASE case TYPE_pointer: { if (val.isNull()) { // Convert to a null pointer. *static_cast(buffer) = nullptr; break; } JS::Rooted baseType(cx, PointerType::GetBaseType(targetType)); if (sourceData) { // First, determine if the targetType is ctypes.void_t.ptr. TypeCode sourceCode = CType::GetTypeCode(sourceType); void* sourceBuffer = CData::GetData(sourceData); bool voidptrTarget = CType::GetTypeCode(baseType) == TYPE_void_t; if (sourceCode == TYPE_pointer && voidptrTarget) { // Autoconvert if targetType is ctypes.voidptr_t. *static_cast(buffer) = *static_cast(sourceBuffer); break; } if (sourceCode == TYPE_array) { // Autoconvert an array to a ctypes.void_t.ptr or to // sourceType.elementType.ptr, just like C. JSObject* elementType = ArrayType::GetBaseType(sourceType); if (voidptrTarget || CType::TypesEqual(baseType, elementType)) { *static_cast(buffer) = sourceBuffer; break; } } } else if (convType == ConversionType::Argument && val.isString()) { // Convert the string for the ffi call. This requires allocating space // which the caller assumes ownership of. // TODO: Extend this so we can safely convert strings at other times // also. JSString* sourceString = val.toString(); size_t sourceLength = sourceString->length(); Rooted sourceLinear(cx, sourceString->ensureLinear(cx)); if (!sourceLinear) { return false; } switch (CType::GetTypeCode(baseType)) { case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: { // Reject if unpaired surrogate characters are present. if (!ReportErrorIfUnpairedSurrogatePresent(cx, sourceLinear)) { return false; } // Convert from UTF-16 to UTF-8. size_t nbytes = JS::GetDeflatedUTF8StringLength(sourceLinear); char** charBuffer = static_cast(buffer); *charBuffer = cx->pod_malloc(nbytes + 1); if (!*charBuffer) { return false; } nbytes = JS::DeflateStringToUTF8Buffer( sourceLinear, mozilla::Span(*charBuffer, nbytes)); (*charBuffer)[nbytes] = '\0'; *freePointer = true; break; } case TYPE_char16_t: { // Copy the char16_t string data. (We could provide direct access to // the JSString's buffer, but this approach is safer if the caller // happens to modify the string.) char16_t** char16Buffer = static_cast(buffer); *char16Buffer = cx->pod_malloc(sourceLength + 1); if (!*char16Buffer) { return false; } *freePointer = true; CopyChars(*char16Buffer, *sourceLinear); (*char16Buffer)[sourceLength] = '\0'; break; } default: return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } break; } else if (val.isObject() && JS::IsArrayBufferObject(valObj)) { // Convert ArrayBuffer to pointer without any copy. This is only valid // when converting an argument to a function call, as it is possible for // the pointer to be invalidated by anything that runs JS code. (It is // invalid to invoke JS code from a ctypes function call.) if (convType != ConversionType::Argument) { return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } void* ptr; { JS::AutoCheckCannotGC nogc; bool isShared; ptr = JS::GetArrayBufferData(valObj, &isShared, nogc); MOZ_ASSERT(!isShared); // Because ArrayBuffer } if (!ptr) { return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } *static_cast(buffer) = ptr; break; } else if (val.isObject() && JS::IsSharedArrayBufferObject(valObj)) { // CTypes has not yet opted in to allowing shared memory pointers // to escape. Exporting a pointer to the shared buffer without // indicating sharedness would expose client code to races. return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } else if (val.isObject() && JS_IsArrayBufferViewObject(valObj)) { // Same as ArrayBuffer, above, though note that this will take the // offset of the view into account. if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) { return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } if (convType != ConversionType::Argument) { return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } void* ptr; { JS::AutoCheckCannotGC nogc; bool isShared; ptr = JS_GetArrayBufferViewData(valObj, &isShared, nogc); if (isShared) { // Opt out of shared memory, for now. Exporting a // pointer to the shared buffer without indicating // sharedness would expose client code to races. ptr = nullptr; } } if (!ptr) { return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } *static_cast(buffer) = ptr; break; } return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } case TYPE_array: { MOZ_ASSERT(!funObj); RootedObject baseType(cx, ArrayType::GetBaseType(targetType)); size_t targetLength = ArrayType::GetLength(targetType); if (val.isString()) { JSString* sourceString = val.toString(); size_t sourceLength = sourceString->length(); Rooted sourceLinear(cx, sourceString->ensureLinear(cx)); if (!sourceLinear) { return false; } switch (CType::GetTypeCode(baseType)) { case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: { // Reject if unpaired surrogate characters are present. if (!ReportErrorIfUnpairedSurrogatePresent(cx, sourceLinear)) { return false; } // Convert from UTF-16 or Latin1 to UTF-8. size_t nbytes = JS::GetDeflatedUTF8StringLength(sourceLinear); if (targetLength < nbytes) { MOZ_ASSERT(!funObj); return ArrayLengthOverflow(cx, targetLength, targetType, nbytes, val, convType); } char* charBuffer = static_cast(buffer); nbytes = JS::DeflateStringToUTF8Buffer( sourceLinear, mozilla::Span(charBuffer, nbytes)); if (targetLength > nbytes) { charBuffer[nbytes] = '\0'; } break; } case TYPE_char16_t: { // Copy the string data, char16_t for char16_t, including the // terminator if there's space. if (targetLength < sourceLength) { MOZ_ASSERT(!funObj); return ArrayLengthOverflow(cx, targetLength, targetType, sourceLength, val, convType); } char16_t* dest = static_cast(buffer); CopyChars(dest, *sourceLinear); if (targetLength > sourceLength) { dest[sourceLength] = '\0'; } break; } default: return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } } else { ESClass cls; if (!GetClassOfValue(cx, val, &cls)) { return false; } if (cls == ESClass::Array) { // Convert each element of the array by calling ImplicitConvert. uint32_t sourceLength; if (!JS::GetArrayLength(cx, valObj, &sourceLength) || targetLength != size_t(sourceLength)) { MOZ_ASSERT(!funObj); return ArrayLengthMismatch(cx, targetLength, targetType, size_t(sourceLength), val, convType); } // Convert into an intermediate, in case of failure. size_t elementSize = CType::GetSize(baseType); size_t arraySize = elementSize * targetLength; auto intermediate = cx->make_pod_array(arraySize); if (!intermediate) { 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; size_t sourceLength = bufferShared ? JS::GetSharedArrayBufferByteLength(valObj) : JS::GetArrayBufferByteLength(valObj); size_t elementSize = CType::GetSize(baseType); size_t arraySize = elementSize * targetLength; if (arraySize != sourceLength) { MOZ_ASSERT(!funObj); return ArrayLengthMismatch(cx, arraySize, targetType, sourceLength, val, convType); } SharedMem target = SharedMem::unshared(buffer); JS::AutoCheckCannotGC nogc; bool isShared; SharedMem src = (bufferShared ? SharedMem::shared( JS::GetSharedArrayBufferData(valObj, &isShared, nogc)) : SharedMem::unshared( JS::GetArrayBufferData(valObj, &isShared, nogc))); MOZ_ASSERT(isShared == bufferShared); jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength); break; } else if (JS_IsTypedArrayObject(valObj)) { // Check that array is consistent with type, then // copy the array. It is OK to copy from shared to unshared // or vice versa. if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) { return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } size_t sourceLength = JS_GetTypedArrayByteLength(valObj); size_t elementSize = CType::GetSize(baseType); size_t arraySize = elementSize * targetLength; if (arraySize != sourceLength) { MOZ_ASSERT(!funObj); return ArrayLengthMismatch(cx, arraySize, targetType, sourceLength, val, convType); } SharedMem target = SharedMem::unshared(buffer); JS::AutoCheckCannotGC nogc; bool isShared; SharedMem src = SharedMem::shared( JS_GetArrayBufferViewData(valObj, &isShared, nogc)); jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength); break; } else { // Don't implicitly convert to string. Users can implicitly convert // with `String(x)` or `""+x`. return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } } break; } case TYPE_struct: { if (val.isObject() && !sourceData) { // Enumerate the properties of the object; if they match the struct // specification, convert the fields. Rooted props(cx, IdVector(cx)); if (!JS_Enumerate(cx, valObj, &props)) { return false; } // Convert into an intermediate, in case of failure. size_t structSize = CType::GetSize(targetType); auto intermediate = cx->make_pod_array(structSize); if (!intermediate) { 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 (!id.isString()) { return PropNameNonStringError(cx, id, val, convType, funObj, argIndex); } JSLinearString* name = id.toLinearString(); const FieldInfo* field = StructType::LookupField(cx, targetType, name); if (!field) { return false; } RootedValue prop(cx); if (!JS_GetPropertyById(cx, valObj, id, &prop)) { return false; } // Convert the field via ImplicitConvert(). char* fieldData = intermediate.get() + field->mOffset; if (!ImplicitConvert(cx, prop, field->mType, fieldData, convType, nullptr, funObj, argIndex, targetType, i)) return false; } memcpy(buffer, intermediate.get(), structSize); break; } return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj, arrIndex); } case TYPE_void_t: case TYPE_function: MOZ_CRASH("invalid type"); } return true; } // Convert Value 'val' to a C binary representation of CType 'targetType', // storing the result in 'buffer'. This function is more forceful than // ImplicitConvert. static bool ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, void* buffer, ConversionType convType) { // If ImplicitConvert succeeds, use that result. if (ImplicitConvert(cx, val, targetType, buffer, convType, nullptr)) { return true; } // If ImplicitConvert failed, and there is no pending exception, then assume // hard failure (out of memory, or some other similarly serious condition). // We store any pending exception in case we need to re-throw it. RootedValue ex(cx); if (!JS_GetPendingException(cx, &ex)) { return false; } // Otherwise, assume soft failure. Clear the pending exception so that we // can throw a different one as required. JS_ClearPendingException(cx); TypeCode type = CType::GetTypeCode(targetType); switch (type) { case TYPE_bool: { *static_cast(buffer) = ToBoolean(val); break; } #define INTEGRAL_CASE(name, type, ffiType) \ case TYPE_##name: { \ /* Convert numeric values with a C-style cast, and */ \ /* allow conversion from a base-10 or base-16 string. */ \ type result; \ bool overflow = false; \ if (!jsvalToIntegerExplicit(val, &result) && \ (!val.isString() || \ !StringToInteger(cx, val.toString(), &result, &overflow))) { \ if (overflow) { \ return TypeOverflow(cx, #name, val); \ } \ return ConvError(cx, #name, val, convType); \ } \ *static_cast(buffer) = result; \ break; \ } CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) #undef INTEGRAL_CASE case TYPE_pointer: { // Convert a number, Int64 object, or UInt64 object to a pointer. uintptr_t result; if (!jsvalToPtrExplicit(cx, val, &result)) { return ConvError(cx, targetType, val, convType); } *static_cast(buffer) = result; break; } case TYPE_float32_t: case TYPE_float64_t: case TYPE_float: case TYPE_double: case TYPE_array: case TYPE_struct: // ImplicitConvert is sufficient. Re-throw the exception it generated. JS_SetPendingException(cx, ex); return false; case TYPE_void_t: case TYPE_function: MOZ_CRASH("invalid type"); } return true; } // Given a CType 'typeObj', generate a string describing the C type declaration // corresponding to 'typeObj'. For instance, the CType constructed from // 'ctypes.int32_t.ptr.array(4).ptr.ptr' will result in the type string // 'int32_t*(**)[4]'. static JSString* BuildTypeName(JSContext* cx, JSObject* typeObj_) { AutoString result; RootedObject typeObj(cx, typeObj_); // Walk the hierarchy of types, outermost to innermost, building up the type // string. This consists of the base type, which goes on the left. // Derived type modifiers (* and []) build from the inside outward, with // pointers on the left and arrays on the right. An excellent description // of the rules for building C type declarations can be found at: // http://unixwiz.net/techtips/reading-cdecl.html TypeCode prevGrouping = CType::GetTypeCode(typeObj), currentGrouping; while (true) { currentGrouping = CType::GetTypeCode(typeObj); switch (currentGrouping) { case TYPE_pointer: { // Pointer types go on the left. PrependString(cx, result, "*"); typeObj = PointerType::GetBaseType(typeObj); prevGrouping = currentGrouping; continue; } case TYPE_array: { if (prevGrouping == TYPE_pointer) { // Outer type is pointer, inner type is array. Grouping is required. PrependString(cx, result, "("); AppendString(cx, result, ")"); } // Array types go on the right. AppendString(cx, result, "["); size_t length; if (ArrayType::GetSafeLength(typeObj, &length)) { IntegerToString(length, 10, result); } AppendString(cx, result, "]"); typeObj = ArrayType::GetBaseType(typeObj); prevGrouping = currentGrouping; continue; } case TYPE_function: { FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); // Add in the calling convention, if it's not cdecl. // There's no trailing or leading space needed here, as none of the // modifiers can produce a string beginning with an identifier --- // except for TYPE_function itself, which is fine because functions // can't return functions. ABICode abi = GetABICode(fninfo->mABI); if (abi == ABI_STDCALL) { PrependString(cx, result, "__stdcall"); } else if (abi == ABI_THISCALL) { PrependString(cx, result, "__thiscall"); } else if (abi == ABI_WINAPI) { PrependString(cx, result, "WINAPI"); } // Function application binds more tightly than dereferencing, so // wrap pointer types in parens. Functions can't return functions // (only pointers to them), and arrays can't hold functions // (similarly), so we don't need to address those cases. if (prevGrouping == TYPE_pointer) { PrependString(cx, result, "("); AppendString(cx, result, ")"); } // Argument list goes on the right. AppendString(cx, result, "("); for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { RootedObject argType(cx, fninfo->mArgTypes[i]); JSString* argName = CType::GetName(cx, argType); AppendString(cx, result, argName); if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) AppendString(cx, result, ", "); } if (fninfo->mIsVariadic) { AppendString(cx, result, "..."); } AppendString(cx, result, ")"); // Set 'typeObj' to the return type, and let the loop process it. // 'prevGrouping' doesn't matter here, because functions cannot return // arrays -- thus the parenthetical rules don't get tickled. typeObj = fninfo->mReturnType; continue; } default: // Either a basic or struct type. Use the type's name as the base type. break; } break; } // If prepending the base type name directly would splice two // identifiers, insert a space. if (IsAsciiAlpha(result[0]) || result[0] == '_') { PrependString(cx, result, " "); } // Stick the base type and derived type parts together. JSString* baseName = CType::GetName(cx, typeObj); PrependString(cx, result, baseName); if (!result) { return nullptr; } return NewUCString(cx, result.finish()); } // Given a CType 'typeObj', generate a string 'result' such that 'eval(result)' // would construct the same CType. If 'makeShort' is true, assume that any // StructType 't' is bound to an in-scope variable of name 't.name', and use // that variable in place of generating a string to construct the type 't'. // (This means the type comparison function CType::TypesEqual will return true // when comparing the input and output of AppendTypeSource, since struct // equality is determined by strict JSObject pointer equality.) static void BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort, AutoString& result) { RootedObject typeObj(cx, typeObj_); // Walk the types, building up the toSource() string. switch (CType::GetTypeCode(typeObj)) { case TYPE_void_t: #define CASE_FOR_TYPE(name, type, ffiType) case TYPE_##name: CTYPES_FOR_EACH_TYPE(CASE_FOR_TYPE) #undef CASE_FOR_TYPE { AppendString(cx, result, "ctypes."); JSString* nameStr = CType::GetName(cx, typeObj); AppendString(cx, result, nameStr); break; } case TYPE_pointer: { RootedObject baseType(cx, PointerType::GetBaseType(typeObj)); // Specialcase ctypes.voidptr_t. if (CType::GetTypeCode(baseType) == TYPE_void_t) { AppendString(cx, result, "ctypes.voidptr_t"); break; } // Recursively build the source string, and append '.ptr'. BuildTypeSource(cx, baseType, makeShort, result); AppendString(cx, result, ".ptr"); break; } case TYPE_function: { FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); AppendString(cx, result, "ctypes.FunctionType("); switch (GetABICode(fninfo->mABI)) { case ABI_DEFAULT: AppendString(cx, result, "ctypes.default_abi, "); break; case ABI_STDCALL: AppendString(cx, result, "ctypes.stdcall_abi, "); break; case ABI_THISCALL: AppendString(cx, result, "ctypes.thiscall_abi, "); break; case ABI_WINAPI: AppendString(cx, result, "ctypes.winapi_abi, "); break; case INVALID_ABI: MOZ_CRASH("invalid abi"); } // Recursively build the source string describing the function return and // argument types. BuildTypeSource(cx, fninfo->mReturnType, true, result); if (fninfo->mArgTypes.length() > 0) { AppendString(cx, result, ", ["); for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { BuildTypeSource(cx, fninfo->mArgTypes[i], true, result); if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) AppendString(cx, result, ", "); } if (fninfo->mIsVariadic) { AppendString(cx, result, "\"...\""); } AppendString(cx, result, "]"); } AppendString(cx, result, ")"); break; } case TYPE_array: { // Recursively build the source string, and append '.array(n)', // where n is the array length, or the empty string if the array length // is undefined. JSObject* baseType = ArrayType::GetBaseType(typeObj); BuildTypeSource(cx, baseType, makeShort, result); AppendString(cx, result, ".array("); size_t length; if (ArrayType::GetSafeLength(typeObj, &length)) { IntegerToString(length, 10, result); } AppendString(cx, result, ")"); break; } case TYPE_struct: { JSString* name = CType::GetName(cx, typeObj); if (makeShort) { // Shorten the type declaration by assuming that StructType 't' is bound // to an in-scope variable of name 't.name'. AppendString(cx, result, name); break; } // Write the full struct declaration. AppendString(cx, result, "ctypes.StructType(\""); AppendString(cx, result, name); AppendString(cx, result, "\""); // If it's an opaque struct, we're done. if (!CType::IsSizeDefined(typeObj)) { AppendString(cx, result, ")"); break; } AppendString(cx, result, ", ["); const FieldInfoHash* fields = StructType::GetFieldInfo(typeObj); size_t length = fields->count(); Vector fieldsArray; if (!fieldsArray.resize(length)) { break; } for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { fieldsArray[r.front().value().mIndex] = &r.front(); } for (size_t i = 0; i < length; ++i) { const FieldInfoHash::Entry* entry = fieldsArray[i]; AppendString(cx, result, "{ \""); AppendString(cx, result, entry->key()); AppendString(cx, result, "\": "); BuildTypeSource(cx, entry->value().mType, true, result); AppendString(cx, result, " }"); if (i != length - 1) { AppendString(cx, result, ", "); } } AppendString(cx, result, "])"); break; } } } // Given a CData object of CType 'typeObj' with binary value 'data', generate a // string 'result' such that 'eval(result)' would construct a CData object with // the same CType and containing the same binary value. This assumes that any // StructType 't' is bound to an in-scope variable of name 't.name'. (This means // the type comparison function CType::TypesEqual will return true when // comparing the types, since struct equality is determined by strict JSObject // pointer equality.) Further, if 'isImplicit' is true, ensure that the // resulting string can ImplicitConvert successfully if passed to another data // constructor. (This is important when called recursively, since fields of // structs and arrays are converted with ImplicitConvert.) [[nodiscard]] static bool BuildDataSource(JSContext* cx, HandleObject typeObj, void* data, bool isImplicit, AutoString& result) { TypeCode type = CType::GetTypeCode(typeObj); switch (type) { case TYPE_bool: if (*static_cast(data)) { AppendString(cx, result, "true"); } else { AppendString(cx, result, "false"); } break; #define INTEGRAL_CASE(name, type, ffiType) \ case TYPE_##name: \ /* Serialize as a primitive decimal integer. */ \ IntegerToString(*static_cast(data), 10, result); \ break; CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) #undef INTEGRAL_CASE #define WRAPPED_INT_CASE(name, type, ffiType) \ case TYPE_##name: \ /* Serialize as a wrapped decimal integer. */ \ if (!numeric_limits::is_signed) \ AppendString(cx, result, "ctypes.UInt64(\""); \ else \ AppendString(cx, result, "ctypes.Int64(\""); \ \ IntegerToString(*static_cast(data), 10, result); \ AppendString(cx, result, "\")"); \ break; CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE) #undef WRAPPED_INT_CASE #define FLOAT_CASE(name, type, ffiType) \ case TYPE_##name: { \ /* Serialize as a primitive double. */ \ double fp = *static_cast(data); \ ToCStringBuf cbuf; \ size_t strLength; \ char* str = NumberToCString(&cbuf, fp, &strLength); \ MOZ_ASSERT(str); \ if (!result.append(str, strLength)) { \ JS_ReportOutOfMemory(cx); \ return false; \ } \ break; \ } CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) #undef FLOAT_CASE #define CHAR_CASE(name, type, ffiType) \ case TYPE_##name: \ /* Serialize as an integer. */ \ IntegerToString(*static_cast(data), 10, result); \ break; CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE) #undef CHAR_CASE case TYPE_char16_t: { // Serialize as a 1-character JS string. JSString* str = JS_NewUCStringCopyN(cx, static_cast(data), 1); if (!str) { return false; } // Escape characters, and quote as necessary. RootedValue valStr(cx, StringValue(str)); JSString* src = JS_ValueToSource(cx, valStr); if (!src) { return false; } AppendString(cx, result, src); break; } case TYPE_pointer: case TYPE_function: { if (isImplicit) { // The result must be able to ImplicitConvert successfully. // Wrap in a type constructor, then serialize for ExplicitConvert. BuildTypeSource(cx, typeObj, true, result); AppendString(cx, result, "("); } // Serialize the pointer value as a wrapped hexadecimal integer. uintptr_t ptr = *static_cast(data); AppendString(cx, result, "ctypes.UInt64(\"0x"); IntegerToString(ptr, 16, result); AppendString(cx, result, "\")"); if (isImplicit) { AppendString(cx, result, ")"); } break; } case TYPE_array: { // Serialize each element of the array recursively. Each element must // be able to ImplicitConvert successfully. RootedObject baseType(cx, ArrayType::GetBaseType(typeObj)); AppendString(cx, result, "["); size_t length = ArrayType::GetLength(typeObj); size_t elementSize = CType::GetSize(baseType); for (size_t i = 0; i < length; ++i) { char* element = static_cast(data) + elementSize * i; if (!BuildDataSource(cx, baseType, element, true, result)) { return false; } if (i + 1 < length) { AppendString(cx, result, ", "); } } AppendString(cx, result, "]"); break; } case TYPE_struct: { if (isImplicit) { // The result must be able to ImplicitConvert successfully. // Serialize the data as an object with properties, rather than // a sequence of arguments to the StructType constructor. AppendString(cx, result, "{"); } // Serialize each field of the struct recursively. Each field must // be able to ImplicitConvert successfully. const FieldInfoHash* fields = StructType::GetFieldInfo(typeObj); size_t length = fields->count(); Vector fieldsArray; if (!fieldsArray.resize(length)) { return false; } for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { fieldsArray[r.front().value().mIndex] = &r.front(); } for (size_t i = 0; i < length; ++i) { const FieldInfoHash::Entry* entry = fieldsArray[i]; if (isImplicit) { AppendString(cx, result, "\""); AppendString(cx, result, entry->key()); AppendString(cx, result, "\": "); } char* fieldData = static_cast(data) + entry->value().mOffset; RootedObject entryType(cx, entry->value().mType); if (!BuildDataSource(cx, entryType, fieldData, true, result)) { return false; } if (i + 1 != length) { AppendString(cx, result, ", "); } } if (isImplicit) { AppendString(cx, result, "}"); } break; } case TYPE_void_t: MOZ_CRASH("invalid type"); } return true; } /******************************************************************************* ** JSAPI callback function implementations *******************************************************************************/ bool ConstructAbstract(JSContext* cx, unsigned argc, Value* vp) { // Calling an abstract base class constructor is disallowed. return CannotConstructError(cx, "abstract type"); } /******************************************************************************* ** CType implementation *******************************************************************************/ bool CType::ConstructData(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // get the callee object... RootedObject obj(cx, &args.callee()); if (!CType::IsCType(obj)) { return IncompatibleCallee(cx, "CType constructor", obj); } // How we construct the CData object depends on what type we represent. // An instance 'd' of a CData object of type 't' has: // * [[Class]] "CData" // * __proto__ === t.prototype switch (GetTypeCode(obj)) { case TYPE_void_t: return CannotConstructError(cx, "void_t"); case TYPE_function: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, CTYPESMSG_FUNCTION_CONSTRUCT); return false; case TYPE_pointer: return PointerType::ConstructData(cx, obj, args); case TYPE_array: return ArrayType::ConstructData(cx, obj, args); case TYPE_struct: return StructType::ConstructData(cx, obj, args); default: return ConstructBasic(cx, obj, args); } } bool CType::ConstructBasic(JSContext* cx, HandleObject obj, const CallArgs& args) { if (args.length() > 1) { return ArgumentLengthError(cx, "CType constructor", "at most one", ""); } // construct a CData object RootedObject result(cx, CData::Create(cx, obj, nullptr, nullptr, true)); if (!result) { return false; } if (args.length() == 1) { if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result), ConversionType::Construct)) return false; } args.rval().setObject(*result); return true; } JSObject* CType::Create(JSContext* cx, HandleObject typeProto, HandleObject dataProto, TypeCode type, JSString* name_, HandleValue size, HandleValue align, ffi_type* ffiType) { RootedString name(cx, name_); // Create a CType object with the properties and slots common to all CTypes. // Each type object 't' has: // * [[Class]] "CType" // * __proto__ === 'typeProto'; one of ctypes.{CType,PointerType,ArrayType, // StructType}.prototype // * A constructor which creates and returns a CData object, containing // binary data of the given type. // * 'prototype' property: // * [[Class]] "CDataProto" // * __proto__ === 'dataProto'; an object containing properties and // functions common to all CData objects of types derived from // 'typeProto'. (For instance, this could be ctypes.CData.prototype // for simple types, or something representing structs for StructTypes.) // * 'constructor' property === 't' // * Additional properties specified by 'ps', as appropriate for the // specific type instance 't'. RootedObject typeObj(cx, JS_NewObjectWithGivenProto(cx, &sCTypeClass, typeProto)); if (!typeObj) { return nullptr; } // Set up the reserved slots. JS_SetReservedSlot(typeObj, SLOT_TYPECODE, Int32Value(type)); if (ffiType) { JS_SetReservedSlot(typeObj, SLOT_FFITYPE, PrivateValue(ffiType)); if (type == TYPE_struct || type == TYPE_array) { AddCellMemory(typeObj, sizeof(ffi_type), MemoryUse::CTypeFFIType); } } if (name) { JS_SetReservedSlot(typeObj, SLOT_NAME, StringValue(name)); } JS_SetReservedSlot(typeObj, SLOT_SIZE, size); JS_SetReservedSlot(typeObj, SLOT_ALIGN, align); if (dataProto) { // Set up the 'prototype' and 'prototype.constructor' properties. RootedObject prototype( cx, JS_NewObjectWithGivenProto(cx, &sCDataProtoClass, dataProto)); if (!prototype) { return nullptr; } if (!JS_DefineProperty(cx, prototype, "constructor", typeObj, JSPROP_READONLY | JSPROP_PERMANENT)) return nullptr; // Set the 'prototype' object. // if (!JS_FreezeObject(cx, prototype)) // XXX fixme - see bug 541212! // return nullptr; JS_SetReservedSlot(typeObj, SLOT_PROTO, ObjectValue(*prototype)); } if (!JS_FreezeObject(cx, typeObj)) { return nullptr; } // Assert a sanity check on size and alignment: size % alignment should always // be zero. MOZ_ASSERT_IF(IsSizeDefined(typeObj), GetSize(typeObj) % GetAlignment(typeObj) == 0); return typeObj; } JSObject* CType::DefineBuiltin(JSContext* cx, HandleObject ctypesObj, const char* propName, JSObject* typeProto_, JSObject* dataProto_, const char* name, TypeCode type, HandleValue size, HandleValue align, ffi_type* ffiType) { RootedObject typeProto(cx, typeProto_); RootedObject dataProto(cx, dataProto_); RootedString nameStr(cx, JS_NewStringCopyZ(cx, name)); if (!nameStr) { return nullptr; } // Create a new CType object with the common properties and slots. RootedObject typeObj(cx, Create(cx, typeProto, dataProto, type, nameStr, size, align, ffiType)); if (!typeObj) { return nullptr; } // Define the CType as a 'propName' property on 'ctypesObj'. if (!JS_DefineProperty(cx, ctypesObj, propName, typeObj, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) return nullptr; return typeObj; } static void FinalizeFFIType(JS::GCContext* gcx, JSObject* obj, const Value& slot, size_t elementCount) { ffi_type* ffiType = static_cast(slot.toPrivate()); size_t size = elementCount * sizeof(ffi_type*); gcx->free_(obj, ffiType->elements, size, MemoryUse::CTypeFFITypeElements); gcx->delete_(obj, ffiType, MemoryUse::CTypeFFIType); } void CType::Finalize(JS::GCContext* gcx, 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()) { auto fninfo = static_cast(slot.toPrivate()); gcx->delete_(obj, fninfo, MemoryUse::CTypeFunctionInfo); } break; } case TYPE_struct: { size_t fieldCount = 0; // Free the FieldInfoHash table. slot = JS::GetReservedSlot(obj, SLOT_FIELDINFO); if (!slot.isUndefined()) { auto info = static_cast(slot.toPrivate()); fieldCount = info->count(); gcx->delete_(obj, info, MemoryUse::CTypeFieldInfo); } // Free the ffi_type info. Value slot = JS::GetReservedSlot(obj, SLOT_FFITYPE); if (!slot.isUndefined()) { size_t elementCount = fieldCount != 0 ? fieldCount + 1 : 2; FinalizeFFIType(gcx, obj, slot, elementCount); } // Free the ffi_type info. break; } case TYPE_array: { // Free the ffi_type info. Value slot = JS::GetReservedSlot(obj, SLOT_FFITYPE); if (!slot.isUndefined()) { size_t elementCount = ArrayType::GetLength(obj); FinalizeFFIType(gcx, obj, slot, elementCount); } 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().getReservedSlot(SLOT_TYPECODE); if (slot.isUndefined()) { return; } // The contents of our slots depends on what kind of type we are. switch (TypeCode(slot.toInt32())) { case TYPE_struct: { slot = obj->as().getReservedSlot(SLOT_FIELDINFO); if (slot.isUndefined()) { return; } FieldInfoHash* fields = static_cast(slot.toPrivate()); fields->trace(trc); break; } case TYPE_function: { // Check if we have a FunctionInfo. slot = obj->as().getReservedSlot(SLOT_FNINFO); if (slot.isUndefined()) { return; } FunctionInfo* fninfo = static_cast(slot.toPrivate()); MOZ_ASSERT(fninfo); // Identify our objects to the tracer. TraceEdge(trc, &fninfo->mABI, "abi"); TraceEdge(trc, &fninfo->mReturnType, "returnType"); fninfo->mArgTypes.trace(trc); break; } default: // Nothing to do here. break; } } bool CType::IsCType(JSObject* obj) { return obj->hasClass(&sCTypeClass); } bool CType::IsCTypeProto(JSObject* obj) { return obj->hasClass(&sCTypeProtoClass); } TypeCode CType::GetTypeCode(JSObject* typeObj) { MOZ_ASSERT(IsCType(typeObj)); Value result = JS::GetReservedSlot(typeObj, SLOT_TYPECODE); return TypeCode(result.toInt32()); } bool CType::TypesEqual(JSObject* t1, JSObject* t2) { MOZ_ASSERT(IsCType(t1) && IsCType(t2)); // Fast path: check for object equality. if (t1 == t2) { return true; } // First, perform shallow comparison. TypeCode c1 = GetTypeCode(t1); TypeCode c2 = GetTypeCode(t2); if (c1 != c2) { return false; } // Determine whether the types require shallow or deep comparison. switch (c1) { case TYPE_pointer: { // Compare base types. JSObject* b1 = PointerType::GetBaseType(t1); JSObject* b2 = PointerType::GetBaseType(t2); return TypesEqual(b1, b2); } case TYPE_function: { FunctionInfo* f1 = FunctionType::GetFunctionInfo(t1); FunctionInfo* f2 = FunctionType::GetFunctionInfo(t2); // Compare abi, return type, and argument types. if (f1->mABI != f2->mABI) { return false; } if (!TypesEqual(f1->mReturnType, f2->mReturnType)) { return false; } if (f1->mArgTypes.length() != f2->mArgTypes.length()) { return false; } if (f1->mIsVariadic != f2->mIsVariadic) { return false; } for (size_t i = 0; i < f1->mArgTypes.length(); ++i) { if (!TypesEqual(f1->mArgTypes[i], f2->mArgTypes[i])) { return false; } } return true; } case TYPE_array: { // Compare length, then base types. // An undefined length array matches other undefined length arrays. size_t s1 = 0, s2 = 0; bool d1 = ArrayType::GetSafeLength(t1, &s1); bool d2 = ArrayType::GetSafeLength(t2, &s2); if (d1 != d2 || (d1 && s1 != s2)) { return false; } JSObject* b1 = ArrayType::GetBaseType(t1); JSObject* b2 = ArrayType::GetBaseType(t2); return TypesEqual(b1, b2); } case TYPE_struct: // Require exact type object equality. return false; default: // Shallow comparison is sufficient. return true; } } bool CType::GetSafeSize(JSObject* obj, size_t* result) { MOZ_ASSERT(CType::IsCType(obj)); Value size = JS::GetReservedSlot(obj, SLOT_SIZE); // The "size" property can be an int, a double, or JS::UndefinedValue() // (for arrays of undefined length), and must always fit in a size_t. if (size.isInt32()) { *result = size.toInt32(); return true; } if (size.isDouble()) { *result = Convert(size.toDouble()); return true; } MOZ_ASSERT(size.isUndefined()); return false; } size_t CType::GetSize(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); Value size = JS::GetReservedSlot(obj, SLOT_SIZE); MOZ_ASSERT(!size.isUndefined()); // The "size" property can be an int, a double, or JS::UndefinedValue() // (for arrays of undefined length), and must always fit in a size_t. // For callers who know it can never be JS::UndefinedValue(), return a size_t // directly. if (size.isInt32()) { return size.toInt32(); } return Convert(size.toDouble()); } bool CType::IsSizeDefined(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); Value size = JS::GetReservedSlot(obj, SLOT_SIZE); // The "size" property can be an int, a double, or JS::UndefinedValue() // (for arrays of undefined length), and must always fit in a size_t. MOZ_ASSERT(size.isInt32() || size.isDouble() || size.isUndefined()); return !size.isUndefined(); } size_t CType::GetAlignment(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); Value slot = JS::GetReservedSlot(obj, SLOT_ALIGN); return static_cast(slot.toInt32()); } ffi_type* CType::GetFFIType(JSContext* cx, JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); Value slot = JS::GetReservedSlot(obj, SLOT_FFITYPE); if (!slot.isUndefined()) { return static_cast(slot.toPrivate()); } UniquePtrFFIType result; switch (CType::GetTypeCode(obj)) { case TYPE_array: result = ArrayType::BuildFFIType(cx, obj); break; case TYPE_struct: result = StructType::BuildFFIType(cx, obj); break; default: MOZ_CRASH("simple types must have an ffi_type"); } if (!result) { return nullptr; } JS_InitReservedSlot(obj, SLOT_FFITYPE, result.get(), JS::MemoryUse::CTypeFFIType); return result.release(); } JSString* CType::GetName(JSContext* cx, HandleObject obj) { MOZ_ASSERT(CType::IsCType(obj)); Value string = JS::GetReservedSlot(obj, SLOT_NAME); if (!string.isUndefined()) { return string.toString(); } // Build the type name lazily. JSString* name = BuildTypeName(cx, obj); if (!name) { return nullptr; } JS_SetReservedSlot(obj, SLOT_NAME, StringValue(name)); return name; } JSObject* CType::GetProtoFromCtor(JSObject* obj, CTypeProtoSlot slot) { // Get ctypes.{Pointer,Array,Struct}Type.prototype from a reserved slot // on the type constructor. Value protoslot = js::GetFunctionNativeReserved(obj, SLOT_FN_CTORPROTO); JSObject* proto = &protoslot.toObject(); MOZ_ASSERT(proto); MOZ_ASSERT(CType::IsCTypeProto(proto)); // Get the desired prototype. Value result = JS::GetReservedSlot(proto, slot); return &result.toObject(); } JSObject* CType::GetProtoFromType(JSContext* cx, JSObject* objArg, CTypeProtoSlot slot) { MOZ_ASSERT(IsCType(objArg)); RootedObject obj(cx, objArg); // Get the prototype of the type object. RootedObject proto(cx); if (!JS_GetPrototype(cx, obj, &proto)) { return nullptr; } MOZ_ASSERT(proto); MOZ_ASSERT(CType::IsCTypeProto(proto)); // Get the requested ctypes.{Pointer,Array,Struct,Function}Type.prototype. Value result = JS::GetReservedSlot(proto, slot); MOZ_ASSERT(result.isObject()); return &result.toObject(); } bool CType::IsCTypeOrProto(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = &v.toObject(); return CType::IsCType(obj) || CType::IsCTypeProto(obj); } bool CType::PrototypeGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); unsigned slot = CType::IsCTypeProto(obj) ? (unsigned)SLOT_OURDATAPROTO : (unsigned)SLOT_PROTO; args.rval().set(JS::GetReservedSlot(obj, slot)); MOZ_ASSERT(args.rval().isObject() || args.rval().isUndefined()); return true; } bool CType::IsCType(HandleValue v) { return v.isObject() && CType::IsCType(&v.toObject()); } bool CType::NameGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); JSString* name = CType::GetName(cx, obj); if (!name) { return false; } args.rval().setString(name); return true; } bool CType::SizeGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); args.rval().set(JS::GetReservedSlot(obj, SLOT_SIZE)); MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined()); return true; } bool CType::PtrGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); JSObject* pointerType = PointerType::CreateInternal(cx, obj); if (!pointerType) { return false; } args.rval().setObject(*pointerType); return true; } bool CType::CreateArray(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject baseType(cx, GetThisObject(cx, args, "CType.prototype.array")); if (!baseType) { return false; } if (!CType::IsCType(baseType)) { return IncompatibleThisProto(cx, "CType.prototype.array", args.thisv()); } // Construct and return a new ArrayType object. if (args.length() > 1) { return ArgumentLengthError(cx, "CType.prototype.array", "at most one", ""); } // Convert the length argument to a size_t. size_t length = 0; if (args.length() == 1 && !jsvalToSize(cx, args[0], false, &length)) { return ArgumentTypeMismatch(cx, "", "CType.prototype.array", "a nonnegative integer"); } JSObject* result = ArrayType::CreateInternal(cx, baseType, length, args.length() == 1); if (!result) { return false; } args.rval().setObject(*result); return true; } bool CType::ToString(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "CType.prototype.toString")); if (!obj) { return false; } if (!CType::IsCType(obj) && !CType::IsCTypeProto(obj)) { return IncompatibleThisProto(cx, "CType.prototype.toString", InformalValueTypeName(args.thisv())); } // Create the appropriate string depending on whether we're sCTypeClass or // sCTypeProtoClass. JSString* result; if (CType::IsCType(obj)) { AutoString type; AppendString(cx, type, "type "); AppendString(cx, type, GetName(cx, obj)); if (!type) { return false; } result = NewUCString(cx, type.finish()); } else { result = JS_NewStringCopyZ(cx, "[CType proto object]"); } if (!result) { return false; } args.rval().setString(result); return true; } bool CType::ToSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSObject* obj = GetThisObject(cx, args, "CType.prototype.toSource"); if (!obj) { return false; } if (!CType::IsCType(obj) && !CType::IsCTypeProto(obj)) { return IncompatibleThisProto(cx, "CType.prototype.toSource", InformalValueTypeName(args.thisv())); } // Create the appropriate string depending on whether we're sCTypeClass or // sCTypeProtoClass. JSString* result; if (CType::IsCType(obj)) { AutoString source; BuildTypeSource(cx, obj, false, source); if (!source) { return false; } result = NewUCString(cx, source.finish()); } else { result = JS_NewStringCopyZ(cx, "[CType proto object]"); } if (!result) { return false; } args.rval().setString(result); return true; } 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 obj->hasClass(&sCABIClass); } bool ABI::ToSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { return ArgumentLengthError(cx, "ABI.prototype.toSource", "no", "s"); } JSObject* obj = GetThisObject(cx, args, "ABI.prototype.toSource"); if (!obj) { return false; } if (!ABI::IsABI(obj)) { return IncompatibleThisProto(cx, "ABI.prototype.toSource", InformalValueTypeName(args.thisv())); } JSString* result; switch (GetABICode(obj)) { case ABI_DEFAULT: result = JS_NewStringCopyZ(cx, "ctypes.default_abi"); break; case ABI_STDCALL: result = JS_NewStringCopyZ(cx, "ctypes.stdcall_abi"); break; case ABI_THISCALL: result = JS_NewStringCopyZ(cx, "ctypes.thiscall_abi"); break; case ABI_WINAPI: result = JS_NewStringCopyZ(cx, "ctypes.winapi_abi"); break; default: JS_ReportErrorASCII(cx, "not a valid ABICode"); return false; } if (!result) { return false; } args.rval().setString(result); return true; } /******************************************************************************* ** PointerType implementation *******************************************************************************/ bool PointerType::Create(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new PointerType object. if (args.length() != 1) { return ArgumentLengthError(cx, "PointerType", "one", ""); } Value arg = args[0]; RootedObject obj(cx); if (arg.isPrimitive() || !CType::IsCType(obj = &arg.toObject())) { return ArgumentTypeMismatch(cx, "", "PointerType", "a CType"); } JSObject* result = CreateInternal(cx, obj); if (!result) { return false; } args.rval().setObject(*result); return true; } JSObject* PointerType::CreateInternal(JSContext* cx, HandleObject baseType) { // check if we have a cached PointerType on our base CType. Value slot = JS::GetReservedSlot(baseType, SLOT_PTR); if (!slot.isUndefined()) { return &slot.toObject(); } // Get ctypes.PointerType.prototype and the common prototype for CData objects // of this type, or ctypes.FunctionType.prototype for function pointers. CTypeProtoSlot slotId = CType::GetTypeCode(baseType) == TYPE_function ? SLOT_FUNCTIONDATAPROTO : SLOT_POINTERDATAPROTO; RootedObject dataProto(cx, CType::GetProtoFromType(cx, baseType, slotId)); if (!dataProto) { return nullptr; } RootedObject typeProto( cx, CType::GetProtoFromType(cx, baseType, SLOT_POINTERPROTO)); if (!typeProto) { return nullptr; } // Create a new CType object with the common properties and slots. RootedValue sizeVal(cx, Int32Value(sizeof(void*))); RootedValue alignVal(cx, Int32Value(ffi_type_pointer.alignment)); JSObject* typeObj = CType::Create(cx, typeProto, dataProto, TYPE_pointer, nullptr, sizeVal, alignVal, &ffi_type_pointer); if (!typeObj) { return nullptr; } // Set the target type. (This will be 'null' for an opaque pointer type.) JS_SetReservedSlot(typeObj, SLOT_TARGET_T, ObjectValue(*baseType)); // Finally, cache our newly-created PointerType on our pointed-to CType. JS_SetReservedSlot(baseType, SLOT_PTR, ObjectValue(*typeObj)); return typeObj; } bool PointerType::ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args) { if (!CType::IsCType(obj) || CType::GetTypeCode(obj) != TYPE_pointer) { return IncompatibleCallee(cx, "PointerType constructor", obj); } if (args.length() > 3) { return ArgumentLengthError(cx, "PointerType constructor", "0, 1, 2, or 3", "s"); } RootedObject result(cx, CData::Create(cx, obj, nullptr, nullptr, true)); if (!result) { return false; } // Set return value early, must not observe *vp after args.rval().setObject(*result); // There are 3 things that we might be creating here: // 1 - A null pointer (no arguments) // 2 - An initialized pointer (1 argument) // 3 - A closure (1-3 arguments) // // The API doesn't give us a perfect way to distinguish 2 and 3, but the // heuristics we use should be fine. // // Case 1 - Null pointer // if (args.length() == 0) { return true; } // Analyze the arguments a bit to decide what to do next. RootedObject baseObj(cx, PointerType::GetBaseType(obj)); bool looksLikeClosure = CType::GetTypeCode(baseObj) == TYPE_function && args[0].isObject() && JS::IsCallable(&args[0].toObject()); // // Case 2 - Initialized pointer // if (!looksLikeClosure) { if (args.length() != 1) { return ArgumentLengthError(cx, "FunctionType constructor", "one", ""); } return ExplicitConvert(cx, args[0], obj, CData::GetData(result), ConversionType::Construct); } // // Case 3 - Closure // // The second argument is an optional 'this' parameter with which to invoke // the given js function. Callers may leave this blank, or pass null if they // wish to pass the third argument. RootedObject thisObj(cx, nullptr); if (args.length() >= 2) { if (args[1].isNull()) { thisObj = nullptr; } else if (args[1].isObject()) { thisObj = &args[1].toObject(); } else if (!JS_ValueToObject(cx, args[1], &thisObj)) { return false; } } // The third argument is an optional error sentinel that js-ctypes will return // if an exception is raised while executing the closure. The type must match // the return type of the callback. RootedValue errVal(cx); if (args.length() == 3) { errVal = args[2]; } RootedObject fnObj(cx, &args[0].toObject()); return FunctionType::ConstructData(cx, baseObj, result, fnObj, thisObj, errVal); } JSObject* PointerType::GetBaseType(JSObject* obj) { MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_pointer); Value type = JS::GetReservedSlot(obj, SLOT_TARGET_T); MOZ_ASSERT(!type.isNull()); return &type.toObject(); } bool PointerType::IsPointerType(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = &v.toObject(); return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_pointer; } bool PointerType::IsPointer(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = MaybeUnwrapArrayWrapper(&v.toObject()); return CData::IsCData(obj) && CType::GetTypeCode(CData::GetCType(obj)) == TYPE_pointer; } bool PointerType::TargetTypeGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); args.rval().set(JS::GetReservedSlot(obj, SLOT_TARGET_T)); MOZ_ASSERT(args.rval().isObject()); return true; } bool PointerType::IsNull(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "PointerType.prototype.isNull")); if (!obj) { return false; } if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "PointerType.prototype.isNull", args.thisv()); } // Get pointer type and base type. JSObject* typeObj = CData::GetCType(obj); if (CType::GetTypeCode(typeObj) != TYPE_pointer) { return IncompatibleThisType(cx, "PointerType.prototype.isNull", "non-PointerType CData", args.thisv()); } void* data = *static_cast(CData::GetData(obj)); args.rval().setBoolean(data == nullptr); return true; } bool PointerType::OffsetBy(JSContext* cx, const CallArgs& args, int offset, const char* name) { RootedObject obj(cx, GetThisObject(cx, args, name)); if (!obj) { return false; } if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, name, args.thisv()); } RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_pointer) { return IncompatibleThisType(cx, name, "non-PointerType CData", args.thisv()); } RootedObject baseType(cx, PointerType::GetBaseType(typeObj)); if (!CType::IsSizeDefined(baseType)) { return UndefinedSizePointerError(cx, "modify", obj); } size_t elementSize = CType::GetSize(baseType); char* data = static_cast(*static_cast(CData::GetData(obj))); void* address = data + offset * ptrdiff_t(elementSize); // Create a PointerType CData object containing the new address. JSObject* result = CData::Create(cx, typeObj, nullptr, &address, true); if (!result) { return false; } args.rval().setObject(*result); return true; } bool PointerType::Increment(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return OffsetBy(cx, args, 1, "PointerType.prototype.increment"); } bool PointerType::Decrement(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return OffsetBy(cx, args, -1, "PointerType.prototype.decrement"); } bool PointerType::ContentsGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); RootedObject baseType(cx, GetBaseType(CData::GetCType(obj))); if (!CType::IsSizeDefined(baseType)) { return UndefinedSizePointerError(cx, "get contents of", obj); } void* data = *static_cast(CData::GetData(obj)); if (data == nullptr) { return NullPointerError(cx, "read contents of", obj); } RootedValue result(cx); if (!ConvertToJS(cx, baseType, nullptr, data, false, false, &result)) { return false; } args.rval().set(result); return true; } bool PointerType::ContentsSetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); RootedObject baseType(cx, GetBaseType(CData::GetCType(obj))); if (!CType::IsSizeDefined(baseType)) { return UndefinedSizePointerError(cx, "set contents of", obj); } void* data = *static_cast(CData::GetData(obj)); if (data == nullptr) { return NullPointerError(cx, "write contents to", obj); } args.rval().setUndefined(); return ImplicitConvert(cx, args.get(0), baseType, data, ConversionType::Setter, nullptr); } /******************************************************************************* ** ArrayType implementation *******************************************************************************/ bool ArrayType::Create(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new ArrayType object. if (args.length() < 1 || args.length() > 2) { return ArgumentLengthError(cx, "ArrayType", "one or two", "s"); } if (args[0].isPrimitive() || !CType::IsCType(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "first ", "ArrayType", "a CType"); } // Convert the length argument to a size_t. size_t length = 0; if (args.length() == 2 && !jsvalToSize(cx, args[1], false, &length)) { return ArgumentTypeMismatch(cx, "second ", "ArrayType", "a nonnegative integer"); } RootedObject baseType(cx, &args[0].toObject()); JSObject* result = CreateInternal(cx, baseType, length, args.length() == 2); if (!result) { return false; } args.rval().setObject(*result); return true; } JSObject* ArrayType::CreateInternal(JSContext* cx, HandleObject baseType, size_t length, bool lengthDefined) { // Get ctypes.ArrayType.prototype and the common prototype for CData objects // of this type, from ctypes.CType.prototype. RootedObject typeProto( cx, CType::GetProtoFromType(cx, baseType, SLOT_ARRAYPROTO)); if (!typeProto) { return nullptr; } RootedObject dataProto( cx, CType::GetProtoFromType(cx, baseType, SLOT_ARRAYDATAPROTO)); if (!dataProto) { return nullptr; } // Determine the size of the array from the base type, if possible. // The size of the base type must be defined. // If our length is undefined, both our size and length will be undefined. size_t baseSize; if (!CType::GetSafeSize(baseType, &baseSize)) { JS_ReportErrorASCII(cx, "base size must be defined"); return nullptr; } RootedValue sizeVal(cx); RootedValue lengthVal(cx); if (lengthDefined) { // Check for overflow, and convert to an int or double as required. size_t size = length * baseSize; if (length > 0 && size / length != baseSize) { SizeOverflow(cx, "array size", "size_t"); return nullptr; } if (!SizeTojsval(cx, size, &sizeVal)) { SizeOverflow(cx, "array size", "JavaScript number"); return nullptr; } if (!SizeTojsval(cx, length, &lengthVal)) { SizeOverflow(cx, "array length", "JavaScript number"); return nullptr; } } RootedValue alignVal(cx, Int32Value(CType::GetAlignment(baseType))); // Create a new CType object with the common properties and slots. JSObject* typeObj = CType::Create(cx, typeProto, dataProto, TYPE_array, nullptr, sizeVal, alignVal, nullptr); if (!typeObj) { return nullptr; } // Set the element type. JS_SetReservedSlot(typeObj, SLOT_ELEMENT_T, ObjectValue(*baseType)); // Set the length. JS_SetReservedSlot(typeObj, SLOT_LENGTH, lengthVal); return typeObj; } bool ArrayType::ConstructData(JSContext* cx, HandleObject obj_, const CallArgs& args) { RootedObject obj(cx, obj_); // Make a mutable version if (!CType::IsCType(obj) || CType::GetTypeCode(obj) != TYPE_array) { return IncompatibleCallee(cx, "ArrayType constructor", obj); } // Decide whether we have an object to initialize from. We'll override this // if we get a length argument instead. bool convertObject = args.length() == 1; // Check if we're an array of undefined length. If we are, allow construction // with a length argument, or with an actual JS array. if (CType::IsSizeDefined(obj)) { if (args.length() > 1) { return ArgumentLengthError(cx, "size defined ArrayType constructor", "at most one", ""); } } else { if (args.length() != 1) { return ArgumentLengthError(cx, "size undefined ArrayType constructor", "one", ""); } RootedObject baseType(cx, GetBaseType(obj)); size_t length; if (jsvalToSize(cx, args[0], false, &length)) { // Have a length, rather than an object to initialize from. convertObject = false; } else if (args[0].isObject()) { // We were given an object with a .length property. // This could be a JS array, or a CData array. RootedObject arg(cx, &args[0].toObject()); RootedValue lengthVal(cx); if (!JS_GetProperty(cx, arg, "length", &lengthVal) || !jsvalToSize(cx, lengthVal, false, &length)) { return ArgumentTypeMismatch(cx, "", "size undefined ArrayType constructor", "an array object or integer"); } } else if (args[0].isString()) { // We were given a string. Size the array to the appropriate length, // including space for the terminator. JSString* sourceString = args[0].toString(); size_t sourceLength = sourceString->length(); Rooted sourceLinear(cx, sourceString->ensureLinear(cx)); if (!sourceLinear) { return false; } switch (CType::GetTypeCode(baseType)) { case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: { // Reject if unpaired surrogate characters are present. if (!ReportErrorIfUnpairedSurrogatePresent(cx, sourceLinear)) { return false; } // Determine the UTF-8 length. length = JS::GetDeflatedUTF8StringLength(sourceLinear); ++length; break; } case TYPE_char16_t: length = sourceLength + 1; break; default: return ConvError(cx, obj, args[0], ConversionType::Construct); } } else { return ArgumentTypeMismatch(cx, "", "size undefined ArrayType constructor", "an array object or integer"); } // Construct a new ArrayType of defined length, for the new CData object. obj = CreateInternal(cx, baseType, length, true); if (!obj) { return false; } } JSObject* result = CData::Create(cx, obj, nullptr, nullptr, true); if (!result) { return false; } args.rval().setObject(*result); if (convertObject) { if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result), ConversionType::Construct)) return false; } return true; } JSObject* ArrayType::GetBaseType(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); Value type = JS::GetReservedSlot(obj, SLOT_ELEMENT_T); MOZ_ASSERT(!type.isNull()); return &type.toObject(); } bool ArrayType::GetSafeLength(JSObject* obj, size_t* result) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); Value length = JS::GetReservedSlot(obj, SLOT_LENGTH); // The "length" property can be an int, a double, or JS::UndefinedValue() // (for arrays of undefined length), and must always fit in a size_t. if (length.isInt32()) { *result = length.toInt32(); return true; } if (length.isDouble()) { *result = Convert(length.toDouble()); return true; } MOZ_ASSERT(length.isUndefined()); return false; } size_t ArrayType::GetLength(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); Value length = JS::GetReservedSlot(obj, SLOT_LENGTH); MOZ_ASSERT(!length.isUndefined()); // The "length" property can be an int, a double, or JS::UndefinedValue() // (for arrays of undefined length), and must always fit in a size_t. // For callers who know it can never be JS::UndefinedValue(), return a size_t // directly. if (length.isInt32()) { return length.toInt32(); } return Convert(length.toDouble()); } UniquePtrFFIType ArrayType::BuildFFIType(JSContext* cx, JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_array); MOZ_ASSERT(CType::IsSizeDefined(obj)); JSObject* baseType = ArrayType::GetBaseType(obj); ffi_type* ffiBaseType = CType::GetFFIType(cx, baseType); if (!ffiBaseType) { return nullptr; } size_t length = ArrayType::GetLength(obj); // Create an ffi_type to represent the array. This is necessary for the case // where the array is part of a struct. Since libffi has no intrinsic // support for array types, we approximate it by creating a struct type // with elements of type 'baseType' and with appropriate size and alignment // values. It would be nice to not do all the work of setting up 'elements', // but some libffi platforms currently require that it be meaningful. I'm // looking at you, x86_64. auto ffiType = cx->make_unique(); if (!ffiType) { return nullptr; } ffiType->type = FFI_TYPE_STRUCT; ffiType->size = CType::GetSize(obj); ffiType->alignment = CType::GetAlignment(obj); ffiType->elements = cx->pod_malloc(length + 1); if (!ffiType->elements) { return nullptr; } for (size_t i = 0; i < length; ++i) { ffiType->elements[i] = ffiBaseType; } ffiType->elements[length] = nullptr; return ffiType; } bool ArrayType::IsArrayType(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = &v.toObject(); return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array; } bool ArrayType::IsArrayOrArrayType(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = MaybeUnwrapArrayWrapper(&v.toObject()); // Allow both CTypes and CDatas of the ArrayType persuasion by extracting the // CType if we're dealing with a CData. if (CData::IsCData(obj)) { obj = CData::GetCType(obj); } return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array; } bool ArrayType::ElementTypeGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); args.rval().set(JS::GetReservedSlot(obj, SLOT_ELEMENT_T)); MOZ_ASSERT(args.rval().isObject()); return true; } bool ArrayType::LengthGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); // This getter exists for both CTypes and CDatas of the ArrayType persuasion. // If we're dealing with a CData, get the CType from it. if (CData::IsCDataMaybeUnwrap(&obj)) { obj = CData::GetCType(obj); } args.rval().set(JS::GetReservedSlot(obj, SLOT_LENGTH)); MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined()); return true; } bool ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp, bool* handled) { *handled = false; // This should never happen, but we'll check to be safe. if (!CData::IsCData(obj)) { RootedValue objVal(cx, ObjectValue(*obj)); return IncompatibleThisProto(cx, "ArrayType property getter", objVal); } // Bail early if we're not an ArrayType. (This setter is present for all // CData, regardless of CType.) JSObject* typeObj = CData::GetCType(obj); if (CType::GetTypeCode(typeObj) != TYPE_array) { return true; } // Convert the index to a size_t and bounds-check it. size_t index; size_t length = GetLength(typeObj); bool ok = jsidToSize(cx, idval, true, &index); int32_t dummy; if (!ok && idval.isSymbol()) { return true; } bool dummy2; if (!ok && idval.isString() && !StringToInteger(cx, idval.toString(), &dummy, &dummy2)) { // String either isn't a number, or doesn't fit in size_t. // Chances are it's a regular property lookup, so return. return true; } if (!ok) { return InvalidIndexError(cx, idval); } if (index >= length) { return InvalidIndexRangeError(cx, index, length); } *handled = true; RootedObject baseType(cx, GetBaseType(typeObj)); size_t elementSize = CType::GetSize(baseType); char* data = static_cast(CData::GetData(obj)) + elementSize * index; return ConvertToJS(cx, baseType, obj, data, false, false, vp); } bool ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, HandleValue vp, ObjectOpResult& result, bool* handled) { *handled = false; // This should never happen, but we'll check to be safe. if (!CData::IsCData(obj)) { RootedValue objVal(cx, ObjectValue(*obj)); return IncompatibleThisProto(cx, "ArrayType property setter", objVal); } // Bail early if we're not an ArrayType. (This setter is present for all // CData, regardless of CType.) RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_array) { return result.succeed(); } // Convert the index to a size_t and bounds-check it. size_t index; size_t length = GetLength(typeObj); bool ok = jsidToSize(cx, idval, true, &index); int32_t dummy; if (!ok && idval.isSymbol()) { return true; } bool dummy2; if (!ok && idval.isString() && !StringToInteger(cx, idval.toString(), &dummy, &dummy2)) { // String either isn't a number, or doesn't fit in size_t. // Chances are it's a regular property lookup, so return. return result.succeed(); } if (!ok) { return InvalidIndexError(cx, idval); } if (index >= length) { return InvalidIndexRangeError(cx, index, length); } *handled = true; RootedObject baseType(cx, GetBaseType(typeObj)); size_t elementSize = CType::GetSize(baseType); char* data = static_cast(CData::GetData(obj)) + elementSize * index; if (!ImplicitConvert(cx, vp, baseType, data, ConversionType::Setter, nullptr, nullptr, 0, typeObj, index)) return false; return result.succeed(); } bool ArrayType::AddressOfElement(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj( cx, GetThisObject(cx, args, "ArrayType.prototype.addressOfElement")); if (!obj) { return false; } if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "ArrayType.prototype.addressOfElement", args.thisv()); } RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_array) { return IncompatibleThisType(cx, "ArrayType.prototype.addressOfElement", "non-ArrayType CData", args.thisv()); } if (args.length() != 1) { return ArgumentLengthError(cx, "ArrayType.prototype.addressOfElement", "one", ""); } RootedObject baseType(cx, GetBaseType(typeObj)); RootedObject pointerType(cx, PointerType::CreateInternal(cx, baseType)); if (!pointerType) { return false; } // Create a PointerType CData object containing null. RootedObject result(cx, CData::Create(cx, pointerType, nullptr, nullptr, true)); if (!result) { return false; } args.rval().setObject(*result); // Convert the index to a size_t and bounds-check it. size_t index; size_t length = GetLength(typeObj); if (!jsvalToSize(cx, args[0], false, &index)) { return InvalidIndexError(cx, args[0]); } if (index >= length) { return InvalidIndexRangeError(cx, index, length); } // Manually set the pointer inside the object, so we skip the conversion step. void** data = static_cast(CData::GetData(result)); size_t elementSize = CType::GetSize(baseType); *data = static_cast(CData::GetData(obj)) + elementSize * index; return true; } /******************************************************************************* ** StructType implementation *******************************************************************************/ // For a struct field descriptor 'val' of the form { name : type }, extract // 'name' and 'type'. static JSLinearString* ExtractStructField(JSContext* cx, HandleValue val, MutableHandleObject typeObj) { if (val.isPrimitive()) { FieldDescriptorNameTypeError(cx, val); return nullptr; } RootedObject obj(cx, &val.toObject()); Rooted props(cx, IdVector(cx)); if (!JS_Enumerate(cx, obj, &props)) { return nullptr; } // make sure we have one, and only one, property if (props.length() != 1) { FieldDescriptorCountError(cx, val, props.length()); return nullptr; } RootedId nameid(cx, props[0]); if (!nameid.isString()) { FieldDescriptorNameError(cx, nameid); return nullptr; } RootedValue propVal(cx); if (!JS_GetPropertyById(cx, obj, nameid, &propVal)) { return nullptr; } if (propVal.isPrimitive() || !CType::IsCType(&propVal.toObject())) { FieldDescriptorTypeError(cx, propVal, nameid); return nullptr; } // Undefined size or zero size struct members are illegal. // (Zero-size arrays are legal as struct members in C++, but libffi will // choke on a zero-size struct, so we disallow them.) typeObj.set(&propVal.toObject()); size_t size; if (!CType::GetSafeSize(typeObj, &size) || size == 0) { FieldDescriptorSizeError(cx, typeObj, nameid); return nullptr; } return nameid.toLinearString(); } // For a struct field with 'name' and 'type', add an element of the form // { name : type }. static bool AddFieldToArray(JSContext* cx, MutableHandleValue element, JSLinearString* name_, JSObject* typeObj_) { RootedObject typeObj(cx, typeObj_); Rooted name(cx, name_); RootedObject fieldObj(cx, JS_NewPlainObject(cx)); if (!fieldObj) { return false; } element.setObject(*fieldObj); AutoStableStringChars nameChars(cx); if (!nameChars.initTwoByte(cx, name)) { return false; } if (!JS_DefineUCProperty( cx, fieldObj, nameChars.twoByteChars(), name->length(), typeObj, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) return false; return JS_FreezeObject(cx, fieldObj); } bool StructType::Create(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new StructType object. if (args.length() < 1 || args.length() > 2) { return ArgumentLengthError(cx, "StructType", "one or two", "s"); } Value name = args[0]; if (!name.isString()) { return ArgumentTypeMismatch(cx, "first ", "StructType", "a string"); } // Get ctypes.StructType.prototype from the ctypes.StructType constructor. RootedObject typeProto( cx, CType::GetProtoFromCtor(&args.callee(), SLOT_STRUCTPROTO)); // Create a simple StructType with no defined fields. The result will be // non-instantiable as CData, will have no 'prototype' property, and will // have undefined size and alignment and no ffi_type. RootedObject result( cx, CType::Create(cx, typeProto, nullptr, TYPE_struct, name.toString(), JS::UndefinedHandleValue, JS::UndefinedHandleValue, nullptr)); if (!result) { return false; } if (args.length() == 2) { RootedObject arr(cx, args[1].isObject() ? &args[1].toObject() : nullptr); bool isArray; if (!arr) { isArray = false; } else { if (!JS::IsArrayObject(cx, arr, &isArray)) { return false; } } if (!isArray) { return ArgumentTypeMismatch(cx, "second ", "StructType", "an array"); } // Define the struct fields. if (!DefineInternal(cx, result, arr)) { return false; } } args.rval().setObject(*result); return true; } bool StructType::DefineInternal(JSContext* cx, JSObject* typeObj_, JSObject* fieldsObj_) { RootedObject typeObj(cx, typeObj_); RootedObject fieldsObj(cx, fieldsObj_); uint32_t len; MOZ_ALWAYS_TRUE(JS::GetArrayLength(cx, fieldsObj, &len)); // Get the common prototype for CData objects of this type from // ctypes.CType.prototype. RootedObject dataProto( cx, CType::GetProtoFromType(cx, typeObj, SLOT_STRUCTDATAPROTO)); if (!dataProto) { return false; } // Set up the 'prototype' and 'prototype.constructor' properties. // The prototype will reflect the struct fields as properties on CData objects // created from this type. RootedObject prototype( cx, JS_NewObjectWithGivenProto(cx, &sCDataProtoClass, dataProto)); if (!prototype) { return false; } if (!JS_DefineProperty(cx, prototype, "constructor", typeObj, JSPROP_READONLY | JSPROP_PERMANENT)) return false; // Create a FieldInfoHash to stash on the type object. Rooted fields(cx, FieldInfoHash(cx->zone(), len)); // Process the field types. size_t structSize, structAlign; if (len != 0) { structSize = 0; structAlign = 0; for (uint32_t i = 0; i < len; ++i) { RootedValue item(cx); if (!JS_GetElement(cx, fieldsObj, i, &item)) { return false; } RootedObject fieldType(cx, nullptr); Rooted name(cx, ExtractStructField(cx, item, &fieldType)); if (!name) { return false; } // Make sure each field name is unique FieldInfoHash::AddPtr entryPtr = fields.lookupForAdd(name); if (entryPtr) { return DuplicateFieldError(cx, name); } // Add the field to the StructType's 'prototype' property. AutoStableStringChars nameChars(cx); if (!nameChars.initTwoByte(cx, name)) { return false; } RootedFunction getter( cx, NewFunctionWithReserved(cx, StructType::FieldGetter, 0, 0, nullptr)); if (!getter) { return false; } SetFunctionNativeReserved(getter, StructType::SLOT_FIELDNAME, StringValue(JS_FORGET_STRING_LINEARNESS(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_LINEARNESS(name))); RootedObject setterObj(cx, JS_GetFunctionObject(setter)); if (!JS_DefineUCProperty(cx, prototype, nameChars.twoByteChars(), name->length(), getterObj, setterObj, JSPROP_ENUMERATE | JSPROP_PERMANENT)) { return false; } size_t fieldSize = CType::GetSize(fieldType); size_t fieldAlign = CType::GetAlignment(fieldType); size_t fieldOffset = Align(structSize, fieldAlign); // Check for overflow. Since we hold invariant that fieldSize % fieldAlign // be zero, we can safely check fieldOffset + fieldSize without first // checking fieldOffset for overflow. if (fieldOffset + fieldSize < structSize) { SizeOverflow(cx, "struct size", "size_t"); return false; } // Add field name to the hash FieldInfo info; info.mType = fieldType; info.mIndex = i; info.mOffset = fieldOffset; if (!fields.add(entryPtr, name, info)) { JS_ReportOutOfMemory(cx); return false; } structSize = fieldOffset + fieldSize; if (fieldAlign > structAlign) { structAlign = fieldAlign; } } // Pad the struct tail according to struct alignment. size_t structTail = Align(structSize, structAlign); if (structTail < structSize) { SizeOverflow(cx, "struct size", "size_t"); return false; } structSize = structTail; } else { // Empty structs are illegal in C, but are legal and have a size of // 1 byte in C++. We're going to allow them, and trick libffi into // believing this by adding a char member. The resulting struct will have // no getters or setters, and will be initialized to zero. structSize = 1; structAlign = 1; } RootedValue sizeVal(cx); if (!SizeTojsval(cx, structSize, &sizeVal)) { SizeOverflow(cx, "struct size", "double"); return false; } // Move the field hash to the heap and store it in the typeObj. FieldInfoHash* heapHash = cx->new_(std::move(fields.get())); if (!heapHash) { JS_ReportOutOfMemory(cx); return false; } JS_InitReservedSlot(typeObj, SLOT_FIELDINFO, heapHash, JS::MemoryUse::CTypeFieldInfo); JS_SetReservedSlot(typeObj, SLOT_SIZE, sizeVal); JS_SetReservedSlot(typeObj, SLOT_ALIGN, Int32Value(structAlign)); // if (!JS_FreezeObject(cx, prototype)0 // XXX fixme - see bug 541212! // return false; JS_SetReservedSlot(typeObj, SLOT_PROTO, ObjectValue(*prototype)); return true; } UniquePtrFFIType StructType::BuildFFIType(JSContext* cx, JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); MOZ_ASSERT(CType::IsSizeDefined(obj)); const FieldInfoHash* fields = GetFieldInfo(obj); size_t len = fields->count(); size_t structSize = CType::GetSize(obj); size_t structAlign = CType::GetAlignment(obj); auto ffiType = cx->make_unique(); if (!ffiType) { return nullptr; } ffiType->type = FFI_TYPE_STRUCT; size_t count = len != 0 ? len + 1 : 2; auto elements = cx->make_pod_array(count); if (!elements) { 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(); AddCellMemory(obj, count * sizeof(ffi_type*), MemoryUse::CTypeFFITypeElements); #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 ffiType; } bool StructType::Define(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "StructType.prototype.define")); if (!obj) { return false; } if (!CType::IsCType(obj)) { return IncompatibleThisProto(cx, "StructType.prototype.define", args.thisv()); } if (CType::GetTypeCode(obj) != TYPE_struct) { return IncompatibleThisType(cx, "StructType.prototype.define", "non-StructType", args.thisv()); } if (CType::IsSizeDefined(obj)) { JS_ReportErrorASCII(cx, "StructType has already been defined"); return false; } if (args.length() != 1) { return ArgumentLengthError(cx, "StructType.prototype.define", "one", ""); } HandleValue arg = args[0]; if (arg.isPrimitive()) { return ArgumentTypeMismatch(cx, "", "StructType.prototype.define", "an array"); } bool isArray; if (!arg.isObject()) { isArray = false; } else { if (!JS::IsArrayObject(cx, arg, &isArray)) { return false; } } if (!isArray) { return ArgumentTypeMismatch(cx, "", "StructType.prototype.define", "an array"); } RootedObject arr(cx, &arg.toObject()); return DefineInternal(cx, obj, arr); } bool StructType::ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args) { if (!CType::IsCType(obj) || CType::GetTypeCode(obj) != TYPE_struct) { return IncompatibleCallee(cx, "StructType constructor", obj); } if (!CType::IsSizeDefined(obj)) { JS_ReportErrorASCII(cx, "cannot construct an opaque StructType"); return false; } JSObject* result = CData::Create(cx, obj, nullptr, nullptr, true); if (!result) { return false; } args.rval().setObject(*result); if (args.length() == 0) { return true; } char* buffer = static_cast(CData::GetData(result)); const FieldInfoHash* fields = GetFieldInfo(obj); if (args.length() == 1) { // There are two possible interpretations of the argument: // 1) It may be an object '{ ... }' with properties representing the // struct fields intended to ExplicitConvert wholesale to our StructType. // 2) If the struct contains one field, the arg may be intended to // ImplicitConvert directly to that arg's CType. // Thankfully, the conditions for these two possibilities to succeed // are mutually exclusive, so we can pick the right one. // Try option 1) first. if (ExplicitConvert(cx, args[0], obj, buffer, ConversionType::Construct)) { return true; } if (fields->count() != 1) { return false; } // If ExplicitConvert failed, and there is no pending exception, then assume // hard failure (out of memory, or some other similarly serious condition). if (!JS_IsExceptionPending(cx)) { return false; } // Otherwise, assume soft failure, and clear the pending exception so that // we can throw a different one as required. JS_ClearPendingException(cx); // Fall through to try option 2). } // We have a type constructor of the form 'ctypes.StructType(a, b, c, ...)'. // ImplicitConvert each field. if (args.length() == fields->count()) { for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { const FieldInfo& field = r.front().value(); MOZ_ASSERT(field.mIndex < fields->count()); /* Quantified invariant */ if (!ImplicitConvert(cx, args[field.mIndex], field.mType, buffer + field.mOffset, ConversionType::Construct, nullptr, nullptr, 0, obj, field.mIndex)) return false; } return true; } size_t count = fields->count(); if (count >= 2) { char fieldLengthStr[32]; SprintfLiteral(fieldLengthStr, "0, 1, or %zu", count); return ArgumentLengthError(cx, "StructType constructor", fieldLengthStr, "s"); } return ArgumentLengthError(cx, "StructType constructor", "at most one", ""); } const FieldInfoHash* StructType::GetFieldInfo(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); Value slot = JS::GetReservedSlot(obj, SLOT_FIELDINFO); MOZ_ASSERT(!slot.isUndefined() && slot.toPrivate()); return static_cast(slot.toPrivate()); } const FieldInfo* StructType::LookupField(JSContext* cx, JSObject* obj, JSLinearString* name) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); FieldInfoHash::Ptr ptr = GetFieldInfo(obj)->lookup(name); if (ptr) { return &ptr->value(); } FieldMissingError(cx, obj, name); return nullptr; } JSObject* StructType::BuildFieldsArray(JSContext* cx, JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_struct); MOZ_ASSERT(CType::IsSizeDefined(obj)); const FieldInfoHash* fields = GetFieldInfo(obj); size_t len = fields->count(); // Prepare a new array for the 'fields' property of the StructType. JS::RootedValueVector fieldsVec(cx); if (!fieldsVec.resize(len)) { return nullptr; } for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { const FieldInfoHash::Entry& entry = r.front(); // Add the field descriptor to the array. if (!AddFieldToArray(cx, fieldsVec[entry.value().mIndex], entry.key(), entry.value().mType)) return nullptr; } RootedObject fieldsProp(cx, JS::NewArrayObject(cx, fieldsVec)); if (!fieldsProp) { return nullptr; } // Seal the fields array. if (!JS_FreezeObject(cx, fieldsProp)) { return nullptr; } return fieldsProp; } /* static */ bool StructType::IsStruct(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = &v.toObject(); return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_struct; } bool StructType::FieldsArrayGetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); args.rval().set(JS::GetReservedSlot(obj, SLOT_FIELDS)); if (!CType::IsSizeDefined(obj)) { MOZ_ASSERT(args.rval().isUndefined()); return true; } if (args.rval().isUndefined()) { // Build the 'fields' array lazily. JSObject* fields = BuildFieldsArray(cx, obj); if (!fields) { return false; } JS_SetReservedSlot(obj, SLOT_FIELDS, ObjectValue(*fields)); args.rval().setObject(*fields); } MOZ_ASSERT(args.rval().isObject()); return true; } bool StructType::FieldGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject()) { return IncompatibleThisProto(cx, "StructType property getter", args.thisv()); } RootedObject obj(cx, &args.thisv().toObject()); if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "StructType property getter", args.thisv()); } JSObject* typeObj = CData::GetCType(obj); if (CType::GetTypeCode(typeObj) != TYPE_struct) { return IncompatibleThisType(cx, "StructType property getter", "non-StructType CData", args.thisv()); } RootedValue nameVal( cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME)); Rooted name(cx, JS_EnsureLinearString(cx, nameVal.toString())); if (!name) { return false; } const FieldInfo* field = LookupField(cx, typeObj, name); if (!field) { return false; } char* data = static_cast(CData::GetData(obj)) + field->mOffset; RootedObject fieldType(cx, field->mType); return ConvertToJS(cx, fieldType, obj, data, false, false, args.rval()); } bool StructType::FieldSetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject()) { return IncompatibleThisProto(cx, "StructType property setter", args.thisv()); } RootedObject obj(cx, &args.thisv().toObject()); if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "StructType property setter", args.thisv()); } RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_struct) { return IncompatibleThisType(cx, "StructType property setter", "non-StructType CData", args.thisv()); } RootedValue nameVal( cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME)); Rooted name(cx, JS_EnsureLinearString(cx, nameVal.toString())); if (!name) { return false; } const FieldInfo* field = LookupField(cx, typeObj, name); if (!field) { return false; } args.rval().setUndefined(); char* data = static_cast(CData::GetData(obj)) + field->mOffset; return ImplicitConvert(cx, args.get(0), field->mType, data, ConversionType::Setter, nullptr, nullptr, 0, typeObj, field->mIndex); } bool StructType::AddressOfField(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj( cx, GetThisObject(cx, args, "StructType.prototype.addressOfField")); if (!obj) { return false; } if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "StructType.prototype.addressOfField", args.thisv()); } JSObject* typeObj = CData::GetCType(obj); if (CType::GetTypeCode(typeObj) != TYPE_struct) { return IncompatibleThisType(cx, "StructType.prototype.addressOfField", "non-StructType CData", args.thisv()); } if (args.length() != 1) { return ArgumentLengthError(cx, "StructType.prototype.addressOfField", "one", ""); } if (!args[0].isString()) { return ArgumentTypeMismatch(cx, "", "StructType.prototype.addressOfField", "a string"); } JSLinearString* str = JS_EnsureLinearString(cx, args[0].toString()); if (!str) { return false; } const FieldInfo* field = LookupField(cx, typeObj, str); if (!field) { return false; } RootedObject baseType(cx, field->mType); RootedObject pointerType(cx, PointerType::CreateInternal(cx, baseType)); if (!pointerType) { return false; } // Create a PointerType CData object containing null. JSObject* result = CData::Create(cx, pointerType, nullptr, nullptr, true); if (!result) { return false; } args.rval().setObject(*result); // Manually set the pointer inside the object, so we skip the conversion step. void** data = static_cast(CData::GetData(result)); *data = static_cast(CData::GetData(obj)) + field->mOffset; return true; } /******************************************************************************* ** FunctionType implementation *******************************************************************************/ // Helper class for handling allocation of function arguments. struct AutoValue { AutoValue() : mData(nullptr) {} ~AutoValue() { js_free(mData); } bool SizeToType(JSContext* cx, JSObject* type) { // Allocate a minimum of sizeof(ffi_arg) to handle small integers. size_t size = Align(CType::GetSize(type), sizeof(ffi_arg)); mData = js_calloc(size); return mData != nullptr; } void* mData; }; static bool GetABI(JSContext* cx, HandleValue abiType, ffi_abi* result) { if (abiType.isPrimitive()) { return false; } ABICode abi = GetABICode(abiType.toObjectOrNull()); // determine the ABI from the subset of those available on the // given platform. ABI_DEFAULT specifies the default // C calling convention (cdecl) on each platform. switch (abi) { case ABI_DEFAULT: *result = FFI_DEFAULT_ABI; return true; case ABI_THISCALL: #if defined(_WIN64) # if defined(_M_X64) *result = FFI_WIN64; # elif defined(_M_ARM64) *result = FFI_SYSV; # else # error unknown 64-bit Windows platform # endif return true; #elif defined(_WIN32) *result = FFI_THISCALL; return true; #else break; #endif case ABI_STDCALL: case ABI_WINAPI: #if (defined(_WIN32) && !defined(_WIN64)) || defined(_OS2) *result = FFI_STDCALL; return true; #elif (defined(_WIN64)) // We'd like the same code to work across Win32 and Win64, so stdcall_api // and winapi_abi become aliases to the lone Win64 ABI. # if defined(_M_X64) *result = FFI_WIN64; # elif defined(_M_ARM64) *result = FFI_SYSV; # else # error unknown 64-bit Windows platform # endif return true; #endif case INVALID_ABI: break; } return false; } static JSObject* PrepareType(JSContext* cx, uint32_t index, HandleValue type) { if (type.isPrimitive() || !CType::IsCType(type.toObjectOrNull())) { FunctionArgumentTypeError(cx, index, type, "is not a ctypes type"); return nullptr; } JSObject* result = type.toObjectOrNull(); TypeCode typeCode = CType::GetTypeCode(result); if (typeCode == TYPE_array) { // convert array argument types to pointers, just like C. // ImplicitConvert will do the same, when passing an array as data. RootedObject baseType(cx, ArrayType::GetBaseType(result)); result = PointerType::CreateInternal(cx, baseType); if (!result) { return nullptr; } } else if (typeCode == TYPE_void_t || typeCode == TYPE_function) { // disallow void or function argument types FunctionArgumentTypeError(cx, index, type, "cannot be void or function"); return nullptr; } if (!CType::IsSizeDefined(result)) { FunctionArgumentTypeError(cx, index, type, "must have defined size"); return nullptr; } // libffi cannot pass types of zero size by value. MOZ_ASSERT(CType::GetSize(result) != 0); return result; } static JSObject* PrepareReturnType(JSContext* cx, HandleValue type) { if (type.isPrimitive() || !CType::IsCType(type.toObjectOrNull())) { FunctionReturnTypeError(cx, type, "is not a ctypes type"); return nullptr; } JSObject* result = type.toObjectOrNull(); TypeCode typeCode = CType::GetTypeCode(result); // Arrays and functions can never be return types. if (typeCode == TYPE_array || typeCode == TYPE_function) { FunctionReturnTypeError(cx, type, "cannot be an array or function"); return nullptr; } if (typeCode != TYPE_void_t && !CType::IsSizeDefined(result)) { FunctionReturnTypeError(cx, type, "must have defined size"); return nullptr; } // libffi cannot pass types of zero size by value. MOZ_ASSERT(typeCode == TYPE_void_t || CType::GetSize(result) != 0); return result; } static MOZ_ALWAYS_INLINE bool IsEllipsis(JSContext* cx, HandleValue v, bool* isEllipsis) { *isEllipsis = false; if (!v.isString()) { return true; } JSString* str = v.toString(); if (str->length() != 3) { return true; } JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return false; } char16_t dot = '.'; *isEllipsis = (linear->latin1OrTwoByteChar(0) == dot && linear->latin1OrTwoByteChar(1) == dot && linear->latin1OrTwoByteChar(2) == dot); return true; } static bool PrepareCIF(JSContext* cx, FunctionInfo* fninfo) { ffi_abi abi; RootedValue abiType(cx, ObjectOrNullValue(fninfo->mABI)); if (!GetABI(cx, abiType, &abi)) { JS_ReportErrorASCII(cx, "Invalid ABI specification"); return false; } ffi_type* rtype = CType::GetFFIType(cx, fninfo->mReturnType); if (!rtype) { return false; } ffi_status status; if (fninfo->mIsVariadic) { status = ffi_prep_cif_var(&fninfo->mCIF, abi, fninfo->mArgTypes.length(), fninfo->mFFITypes.length(), rtype, fninfo->mFFITypes.begin()); } else { status = ffi_prep_cif(&fninfo->mCIF, abi, fninfo->mFFITypes.length(), rtype, fninfo->mFFITypes.begin()); } switch (status) { case FFI_OK: return true; case FFI_BAD_ABI: JS_ReportErrorASCII(cx, "Invalid ABI specification"); return false; case FFI_BAD_TYPEDEF: JS_ReportErrorASCII(cx, "Invalid type specification"); return false; default: JS_ReportErrorASCII(cx, "Unknown libffi error"); return false; } } void FunctionType::BuildSymbolName(JSContext* cx, JSString* name, JSObject* typeObj, AutoCString& result) { FunctionInfo* fninfo = GetFunctionInfo(typeObj); switch (GetABICode(fninfo->mABI)) { case ABI_DEFAULT: case ABI_THISCALL: case ABI_WINAPI: // For cdecl or WINAPI functions, no mangling is necessary. AppendString(cx, 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(cx, result, "_"); AppendString(cx, result, name); AppendString(cx, 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(cx, 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_(cx->zone())); if (!fninfo) { return false; } // Stash the FunctionInfo in a reserved slot. JS_InitReservedSlot(typeObj, SLOT_FNINFO, fninfo, JS::MemoryUse::CTypeFunctionInfo); ffi_abi abi; if (!GetABI(cx, abiType, &abi)) { JS_ReportErrorASCII(cx, "Invalid ABI specification"); return false; } fninfo->mABI = abiType.toObjectOrNull(); fninfo->mReturnType = returnType; // prepare the argument types if (!fninfo->mArgTypes.reserve(args.length()) || !fninfo->mFFITypes.reserve(args.length())) { JS_ReportOutOfMemory(cx); return false; } fninfo->mIsVariadic = false; for (uint32_t i = 0; i < args.length(); ++i) { bool isEllipsis; if (!IsEllipsis(cx, args[i], &isEllipsis)) { return false; } if (isEllipsis) { fninfo->mIsVariadic = true; if (i < 1) { JS_ReportErrorASCII(cx, "\"...\" may not be the first and only parameter " "type of a variadic function declaration"); return false; } if (i < args.length() - 1) { JS_ReportErrorASCII(cx, "\"...\" must be the last parameter type of a " "variadic function declaration"); return false; } if (GetABICode(fninfo->mABI) != ABI_DEFAULT) { JS_ReportErrorASCII(cx, "Variadic functions must use the __cdecl calling " "convention"); return false; } break; } JSObject* argType = PrepareType(cx, i, args[i]); if (!argType) { return false; } ffi_type* ffiType = CType::GetFFIType(cx, argType); if (!ffiType) { return false; } fninfo->mArgTypes.infallibleAppend(argType); fninfo->mFFITypes.infallibleAppend(ffiType); } if (fninfo->mIsVariadic) { // wait to PrepareCIF until function is called return true; } if (!PrepareCIF(cx, fninfo)) { return false; } return true; } bool FunctionType::Create(JSContext* cx, unsigned argc, Value* vp) { // Construct and return a new FunctionType object. CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 2 || args.length() > 3) { return ArgumentLengthError(cx, "FunctionType", "two or three", "s"); } JS::RootedValueVector 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; MOZ_ALWAYS_TRUE(JS::GetArrayLength(cx, arrayObj, &len)); if (!argTypes.resize(len)) { JS_ReportOutOfMemory(cx); return false; } } // Pull out the argument types from the array, if any. MOZ_ASSERT_IF(argTypes.length(), arrayObj); for (uint32_t i = 0; i < argTypes.length(); ++i) { if (!JS_GetElement(cx, arrayObj, i, argTypes[i])) { return false; } } JSObject* result = CreateInternal(cx, args[0], args[1], argTypes); if (!result) { return false; } args.rval().setObject(*result); return true; } JSObject* FunctionType::CreateInternal(JSContext* cx, HandleValue abi, HandleValue rtype, const HandleValueArray& args) { // Prepare the result type RootedObject returnType(cx, PrepareReturnType(cx, rtype)); if (!returnType) { return nullptr; } // Get ctypes.FunctionType.prototype and the common prototype for CData // objects of this type, from ctypes.CType.prototype. RootedObject typeProto( cx, CType::GetProtoFromType(cx, returnType, SLOT_FUNCTIONPROTO)); if (!typeProto) { return nullptr; } RootedObject dataProto( cx, CType::GetProtoFromType(cx, returnType, SLOT_FUNCTIONDATAPROTO)); if (!dataProto) { return nullptr; } // Create a new CType object with the common properties and slots. RootedObject typeObj( cx, CType::Create(cx, typeProto, dataProto, TYPE_function, nullptr, JS::UndefinedHandleValue, JS::UndefinedHandleValue, nullptr)); if (!typeObj) { return nullptr; } // Determine and check the types, and prepare the function CIF. if (!CreateFunctionInfo(cx, typeObj, abi, returnType, args)) { return nullptr; } return typeObj; } // Construct a function pointer to a JS function (see CClosure::Create()). // Regular function pointers are constructed directly in // PointerType::ConstructData(). bool FunctionType::ConstructData(JSContext* cx, HandleObject typeObj, HandleObject dataObj, HandleObject fnObj, HandleObject thisObj, HandleValue errVal) { MOZ_ASSERT(CType::GetTypeCode(typeObj) == TYPE_function); PRFuncPtr* data = static_cast(CData::GetData(dataObj)); FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); if (fninfo->mIsVariadic) { JS_ReportErrorASCII(cx, "Can't declare a variadic callback function"); return false; } if (GetABICode(fninfo->mABI) == ABI_WINAPI) { JS_ReportErrorASCII(cx, "Can't declare a ctypes.winapi_abi callback function, " "use ctypes.stdcall_abi instead"); return false; } RootedObject closureObj( cx, CClosure::Create(cx, typeObj, fnObj, thisObj, errVal, data)); if (!closureObj) { return false; } // Set the closure object as the referent of the new CData object. JS_SetReservedSlot(dataObj, SLOT_REFERENT, ObjectValue(*closureObj)); // Seal the CData object, to prevent modification of the function pointer. // This permanently associates this object with the closure, and avoids // having to do things like reset SLOT_REFERENT when someone tries to // change the pointer value. // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter // could be called on a frozen object. return JS_FreezeObject(cx, dataObj); } typedef Vector AutoValueAutoArray; static bool ConvertArgument(JSContext* cx, HandleObject funObj, unsigned argIndex, HandleValue arg, JSObject* type, AutoValue* value, AutoValueAutoArray* strings) { if (!value->SizeToType(cx, type)) { JS_ReportAllocationOverflow(cx); return false; } bool freePointer = false; if (!ImplicitConvert(cx, arg, type, value->mData, ConversionType::Argument, &freePointer, funObj, argIndex)) return false; if (freePointer) { // ImplicitConvert converted a string for us, which we have to free. // Keep track of it. if (!strings->growBy(1)) { JS_ReportOutOfMemory(cx); return false; } strings->back().mData = *static_cast(value->mData); } return true; } bool FunctionType::Call(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // get the callee object... RootedObject obj(cx, &args.callee()); if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "FunctionType.prototype.call", args.calleev()); } RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_pointer) { return IncompatibleThisType(cx, "FunctionType.prototype.call", "non-PointerType CData", args.calleev()); } typeObj = PointerType::GetBaseType(typeObj); if (CType::GetTypeCode(typeObj) != TYPE_function) { return IncompatibleThisType(cx, "FunctionType.prototype.call", "non-FunctionType pointer", args.calleev()); } FunctionInfo* fninfo = GetFunctionInfo(typeObj); uint32_t argcFixed = fninfo->mArgTypes.length(); if ((!fninfo->mIsVariadic && args.length() != argcFixed) || (fninfo->mIsVariadic && args.length() < argcFixed)) { return FunctionArgumentLengthMismatch(cx, argcFixed, args.length(), obj, typeObj, fninfo->mIsVariadic); } // Check if we have a Library object. If we do, make sure it's open. Value slot = JS::GetReservedSlot(obj, SLOT_REFERENT); if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { PRLibrary* library = Library::GetLibrary(&slot.toObject()); if (!library) { JS_ReportErrorASCII(cx, "library is not open"); return false; } } // prepare the values for each argument AutoValueAutoArray values; AutoValueAutoArray strings; if (!values.resize(args.length())) { JS_ReportOutOfMemory(cx); return false; } for (unsigned i = 0; i < argcFixed; ++i) { if (!ConvertArgument(cx, obj, i, args[i], fninfo->mArgTypes[i], &values[i], &strings)) { return false; } } if (fninfo->mIsVariadic) { if (!fninfo->mFFITypes.resize(args.length())) { JS_ReportOutOfMemory(cx); return false; } RootedObject obj(cx); // Could reuse obj instead of declaring a second RootedObject type(cx); // RootedObject, but readability would suffer. RootedValue arg(cx); for (uint32_t i = argcFixed; i < args.length(); ++i) { obj = args[i].isObject() ? &args[i].toObject() : nullptr; if (!obj || !CData::IsCDataMaybeUnwrap(&obj)) { // Since we know nothing about the CTypes of the ... arguments, // they absolutely must be CData objects already. return VariadicArgumentTypeError(cx, i, args[i]); } type = CData::GetCType(obj); if (!type) { // These functions report their own errors. return false; } RootedValue typeVal(cx, ObjectValue(*type)); type = PrepareType(cx, i, typeVal); if (!type) { return false; } // Relying on ImplicitConvert only for the limited purpose of // converting one CType to another (e.g., T[] to T*). arg = ObjectValue(*obj); if (!ConvertArgument(cx, obj, i, arg, type, &values[i], &strings)) { return false; } fninfo->mFFITypes[i] = CType::GetFFIType(cx, type); if (!fninfo->mFFITypes[i]) { return false; } } if (!PrepareCIF(cx, fninfo)) { return false; } } // initialize a pointer to an appropriate location, for storing the result AutoValue returnValue; TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType); if (typeCode != TYPE_void_t && !returnValue.SizeToType(cx, fninfo->mReturnType)) { JS_ReportAllocationOverflow(cx); return false; } // Let the runtime callback know that we are about to call into C. AutoCTypesActivityCallback autoCallback(cx, CTypesActivityType::BeginCall, CTypesActivityType::EndCall); uintptr_t fn = *reinterpret_cast(CData::GetData(obj)); #if defined(XP_WIN) int32_t lastErrorStatus; // The status as defined by |GetLastError| int32_t savedLastError = GetLastError(); SetLastError(0); #endif // defined(XP_WIN) int errnoStatus; // The status as defined by |errno| int savedErrno = errno; errno = 0; ffi_call(&fninfo->mCIF, FFI_FN(fn), returnValue.mData, reinterpret_cast(values.begin())); // Save error value. // We need to save it before leaving the scope of |suspend| as destructing // |suspend| has the side-effect of clearing |GetLastError| // (see bug 684017). errnoStatus = errno; #if defined(XP_WIN) lastErrorStatus = GetLastError(); SetLastError(savedLastError); #endif // defined(XP_WIN) errno = savedErrno; // We're no longer calling into C. autoCallback.DoEndCallback(); // Store the error value for later consultation with |ctypes.getStatus| JSObject* objCTypes = CType::GetGlobalCTypes(cx, typeObj); if (!objCTypes) { return false; } JS_SetReservedSlot(objCTypes, SLOT_ERRNO, Int32Value(errnoStatus)); #if defined(XP_WIN) JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, Int32Value(lastErrorStatus)); #endif // defined(XP_WIN) // Small integer types get returned as a word-sized ffi_arg. Coerce it back // into the correct size for ConvertToJS. switch (typeCode) { #define INTEGRAL_CASE(name, type, ffiType) \ case TYPE_##name: \ if (sizeof(type) < sizeof(ffi_arg)) { \ ffi_arg data = *static_cast(returnValue.mData); \ *static_cast(returnValue.mData) = static_cast(data); \ } \ break; CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) #undef INTEGRAL_CASE default: break; } // prepare a JS object from the result RootedObject returnType(cx, fninfo->mReturnType); return ConvertToJS(cx, returnType, nullptr, returnValue.mData, false, true, args.rval()); } FunctionInfo* FunctionType::GetFunctionInfo(JSObject* obj) { MOZ_ASSERT(CType::IsCType(obj)); MOZ_ASSERT(CType::GetTypeCode(obj) == TYPE_function); Value slot = JS::GetReservedSlot(obj, SLOT_FNINFO); MOZ_ASSERT(!slot.isUndefined() && slot.toPrivate()); return static_cast(slot.toPrivate()); } bool FunctionType::IsFunctionType(HandleValue v) { if (!v.isObject()) { return false; } JSObject* obj = &v.toObject(); return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_function; } bool FunctionType::ArgTypesGetter(JSContext* cx, const JS::CallArgs& args) { JS::Rooted obj(cx, &args.thisv().toObject()); args.rval().set(JS::GetReservedSlot(obj, SLOT_ARGS_T)); if (!args.rval().isUndefined()) { return true; } FunctionInfo* fninfo = GetFunctionInfo(obj); size_t len = fninfo->mArgTypes.length(); // Prepare a new array. JS::Rooted argTypes(cx); { JS::RootedValueVector vec(cx); if (!vec.resize(len)) { return false; } for (size_t i = 0; i < len; ++i) { vec[i].setObject(*fninfo->mArgTypes[i]); } argTypes = JS::NewArrayObject(cx, vec); if (!argTypes) { return false; } } // Seal and cache it. if (!JS_FreezeObject(cx, argTypes)) { return false; } JS_SetReservedSlot(obj, SLOT_ARGS_T, JS::ObjectValue(*argTypes)); args.rval().setObject(*argTypes); return true; } bool FunctionType::ReturnTypeGetter(JSContext* cx, const JS::CallArgs& args) { // Get the returnType object from the FunctionInfo. args.rval().setObject( *GetFunctionInfo(&args.thisv().toObject())->mReturnType); return true; } bool FunctionType::ABIGetter(JSContext* cx, const JS::CallArgs& args) { // Get the abi object from the FunctionInfo. args.rval().setObject(*GetFunctionInfo(&args.thisv().toObject())->mABI); return true; } bool FunctionType::IsVariadicGetter(JSContext* cx, const JS::CallArgs& args) { args.rval().setBoolean( GetFunctionInfo(&args.thisv().toObject())->mIsVariadic); return true; } /******************************************************************************* ** CClosure implementation *******************************************************************************/ JSObject* CClosure::Create(JSContext* cx, HandleObject typeObj, HandleObject fnObj, HandleObject thisObj, HandleValue errVal, PRFuncPtr* fnptr) { MOZ_ASSERT(fnObj); RootedObject result(cx, JS_NewObject(cx, &sCClosureClass)); if (!result) { return nullptr; } // Get the FunctionInfo from the FunctionType. FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); MOZ_ASSERT(!fninfo->mIsVariadic); MOZ_ASSERT(GetABICode(fninfo->mABI) != ABI_WINAPI); // Get the prototype of the FunctionType object, of class CTypeProto, // which stores our JSContext for use with the closure. RootedObject proto(cx); if (!JS_GetPrototype(cx, typeObj, &proto)) { return nullptr; } MOZ_ASSERT(proto); MOZ_ASSERT(CType::IsCTypeProto(proto)); // Prepare the error sentinel value. It's important to do this now, because // we might be unable to convert the value to the proper type. If so, we want // the caller to know about it _now_, rather than some uncertain time in the // future when the error sentinel is actually needed. UniquePtr errResult; if (!errVal.isUndefined()) { // Make sure the callback returns something. if (CType::GetTypeCode(fninfo->mReturnType) == TYPE_void_t) { JS_ReportErrorASCII(cx, "A void callback can't pass an error sentinel"); return nullptr; } // With the exception of void, the FunctionType constructor ensures that // the return type has a defined size. MOZ_ASSERT(CType::IsSizeDefined(fninfo->mReturnType)); // Allocate a buffer for the return value. size_t rvSize = CType::GetSize(fninfo->mReturnType); errResult = cx->make_pod_array(rvSize); if (!errResult) { return nullptr; } // Do the value conversion. This might fail, in which case we throw. if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(), ConversionType::Return, nullptr, typeObj)) return nullptr; } ClosureInfo* cinfo = cx->new_(cx); if (!cinfo) { JS_ReportOutOfMemory(cx); return nullptr; } // Copy the important bits of context into cinfo. cinfo->errResult = errResult.release(); cinfo->closureObj = result; cinfo->typeObj = typeObj; cinfo->thisObj = thisObj; cinfo->jsfnObj = fnObj; // Stash the ClosureInfo struct on our new object. JS_InitReservedSlot(result, SLOT_CLOSUREINFO, cinfo, JS::MemoryUse::CClosureInfo); // Create an ffi_closure object and initialize it. void* code; cinfo->closure = static_cast(ffi_closure_alloc(sizeof(ffi_closure), &code)); if (!cinfo->closure || !code) { JS_ReportErrorASCII(cx, "couldn't create closure - libffi error"); return nullptr; } ffi_status status = ffi_prep_closure_loc(cinfo->closure, &fninfo->mCIF, CClosure::ClosureStub, cinfo, code); if (status != FFI_OK) { JS_ReportErrorASCII(cx, "couldn't create closure - libffi error"); return nullptr; } // Casting between void* and a function pointer is forbidden in C and C++. // Do it via an integral type. *fnptr = reinterpret_cast(reinterpret_cast(code)); return result; } void CClosure::Trace(JSTracer* trc, JSObject* obj) { // Make sure our ClosureInfo slot is legit. If it's not, bail. Value slot = JS::GetReservedSlot(obj, SLOT_CLOSUREINFO); if (slot.isUndefined()) { return; } ClosureInfo* cinfo = static_cast(slot.toPrivate()); TraceEdge(trc, &cinfo->closureObj, "closureObj"); TraceEdge(trc, &cinfo->typeObj, "typeObj"); TraceEdge(trc, &cinfo->jsfnObj, "jsfnObj"); TraceNullableEdge(trc, &cinfo->thisObj, "thisObj"); } void CClosure::Finalize(JS::GCContext* gcx, JSObject* obj) { // Make sure our ClosureInfo slot is legit. If it's not, bail. Value slot = JS::GetReservedSlot(obj, SLOT_CLOSUREINFO); if (slot.isUndefined()) { return; } ClosureInfo* cinfo = static_cast(slot.toPrivate()); gcx->delete_(obj, cinfo, MemoryUse::CClosureInfo); } void CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData) { MOZ_ASSERT(cif); MOZ_ASSERT(result); MOZ_ASSERT(args); MOZ_ASSERT(userData); // Retrieve the essentials from our closure object. ArgClosure argClosure(cif, result, args, static_cast(userData)); JSContext* cx = argClosure.cinfo->cx; js::AssertSameCompartment(cx, argClosure.cinfo->jsfnObj); RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); MOZ_ASSERT(global); js::PrepareScriptEnvironmentAndInvoke(cx, global, 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. AutoCTypesActivityCallback autoCallback(cx, CTypesActivityType::BeginCallback, CTypesActivityType::EndCallback); RootedObject typeObj(cx, cinfo->typeObj); RootedObject thisObj(cx, cinfo->thisObj); RootedValue jsfnVal(cx, ObjectValue(*cinfo->jsfnObj)); AssertSameCompartment(cx, cinfo->jsfnObj); JS_AbortIfWrongThread(cx); // Assert that our CIFs agree. FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); MOZ_ASSERT(cif == &fninfo->mCIF); TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType); // Initialize the result to zero, in case something fails. Small integer types // are promoted to a word-sized ffi_arg, so we must be careful to zero the // whole word. size_t rvSize = 0; if (cif->rtype != &ffi_type_void) { rvSize = cif->rtype->size; switch (typeCode) { #define INTEGRAL_CASE(name, type, ffiType) case TYPE_##name: CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) #undef INTEGRAL_CASE rvSize = Align(rvSize, sizeof(ffi_arg)); break; default: break; } memset(result, 0, rvSize); } // Set up an array for converted arguments. JS::RootedValueVector argv(cx); if (!argv.resize(cif->nargs)) { JS_ReportOutOfMemory(cx); return false; } for (uint32_t i = 0; i < cif->nargs; ++i) { // Convert each argument, and have any CData objects created depend on // the existing buffers. RootedObject argType(cx, fninfo->mArgTypes[i]); if (!ConvertToJS(cx, argType, nullptr, args[i], false, false, argv[i])) { return false; } } // Call the JS function. 'thisObj' may be nullptr, in which case the JS // engine will find an appropriate object to use. RootedValue rval(cx); bool success = JS_CallFunctionValue(cx, thisObj, jsfnVal, argv, &rval); // Convert the result. Note that we pass 'ConversionType::Return', such that // ImplicitConvert will *not* autoconvert a JS string into a pointer-to-char // type, which would require an allocation that we can't track. The JS // function must perform this conversion itself and return a PointerType // CData; thusly, the burden of freeing the data is left to the user. if (success && cif->rtype != &ffi_type_void) { success = ImplicitConvert(cx, rval, fninfo->mReturnType, result, ConversionType::Return, nullptr, typeObj); } if (!success) { // Something failed. The callee may have thrown, or it may not have // returned a value that ImplicitConvert() was happy with. Depending on how // prudent the consumer has been, we may or may not have a recovery plan. // // Note that PrepareScriptEnvironmentAndInvoke should take care of reporting // the exception. if (cinfo->errResult) { // Good case: we have a sentinel that we can return. Copy it in place of // the actual return value, and then proceed. // The buffer we're returning might be larger than the size of the return // type, due to libffi alignment issues (see above). But it should never // be smaller. size_t copySize = CType::GetSize(fninfo->mReturnType); MOZ_ASSERT(copySize <= rvSize); memcpy(result, cinfo->errResult, copySize); // We still want to return false here, so that // PrepareScriptEnvironmentAndInvoke will report the exception. } else { // Bad case: not much we can do here. The rv is already zeroed out, so we // just return and hope for the best. } return false; } // Small integer types must be returned as a word-sized ffi_arg. Coerce it // back into the size libffi expects. switch (typeCode) { #define INTEGRAL_CASE(name, type, ffiType) \ case TYPE_##name: \ if (sizeof(type) < sizeof(ffi_arg)) { \ ffi_arg data = *static_cast(result); \ *static_cast(result) = data; \ } \ break; CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_BOOL_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE) CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE) #undef INTEGRAL_CASE default: break; } return true; } /******************************************************************************* ** CData implementation *******************************************************************************/ // Create a new CData object of type 'typeObj' containing binary data supplied // in 'source', optionally with a referent object 'refObj'. // // * 'typeObj' must be a CType of defined (but possibly zero) size. // // * If an object 'refObj' is supplied, the new CData object stores the // referent object in a reserved slot for GC safety, such that 'refObj' will // be held alive by the resulting CData object. 'refObj' may or may not be // a CData object; merely an object we want to keep alive. // * If 'refObj' is a CData object, 'ownResult' must be false. // * Otherwise, 'refObj' is a Library or CClosure object, and 'ownResult' // may be true or false. // * Otherwise 'refObj' is nullptr. In this case, 'ownResult' may be true or // false. // // * If 'ownResult' is true, the CData object will allocate an appropriately // sized buffer, and free it upon finalization. If 'source' data is // supplied, the data will be copied from 'source' into the buffer; // otherwise, the entirety of the new buffer will be initialized to zero. // * If 'ownResult' is false, the new CData's buffer refers to a slice of // another buffer kept alive by 'refObj'. 'source' data must be provided, // and the new CData's buffer will refer to 'source'. JSObject* CData::Create(JSContext* cx, HandleObject typeObj, HandleObject refObj, void* source, bool ownResult) { MOZ_ASSERT(typeObj); MOZ_ASSERT(CType::IsCType(typeObj)); MOZ_ASSERT(CType::IsSizeDefined(typeObj)); MOZ_ASSERT(ownResult || source); MOZ_ASSERT_IF(refObj && CData::IsCData(refObj), !ownResult); // Get the 'prototype' property from the type. Value slot = JS::GetReservedSlot(typeObj, SLOT_PROTO); MOZ_ASSERT(slot.isObject()); RootedObject proto(cx, &slot.toObject()); RootedObject dataObj(cx, JS_NewObjectWithGivenProto(cx, &sCDataClass, proto)); if (!dataObj) { return nullptr; } // set the CData's associated type JS_SetReservedSlot(dataObj, SLOT_CTYPE, ObjectValue(*typeObj)); // Stash the referent object, if any, for GC safety. if (refObj) { JS_SetReservedSlot(dataObj, SLOT_REFERENT, ObjectValue(*refObj)); } // Set our ownership flag. JS_SetReservedSlot(dataObj, SLOT_OWNS, BooleanValue(ownResult)); // attach the buffer. since it might not be 2-byte aligned, we need to // allocate an aligned space for it and store it there. :( UniquePtr buffer(cx->new_()); if (!buffer) { return nullptr; } char* data; if (!ownResult) { data = static_cast(source); } else { // Initialize our own buffer. size_t size = CType::GetSize(typeObj); data = cx->pod_malloc(size); if (!data) { return nullptr; } if (!source) { memset(data, 0, size); } else { memcpy(data, source, size); } AddCellMemory(dataObj, size, MemoryUse::CDataBuffer); } *buffer.get() = data; JS_InitReservedSlot(dataObj, SLOT_DATA, buffer.release(), JS::MemoryUse::CDataBufferPtr); // If this is an array, wrap it in a proxy so we can intercept element // gets/sets. if (CType::GetTypeCode(typeObj) != TYPE_array) { return dataObj; } RootedValue priv(cx, ObjectValue(*dataObj)); ProxyOptions options; options.setLazyProto(true); return NewProxyObject(cx, &CDataArrayProxyHandler::singleton, priv, nullptr, options); } void CData::Finalize(JS::GCContext* gcx, JSObject* obj) { // Delete our buffer, and the data it contains if we own it. Value slot = JS::GetReservedSlot(obj, SLOT_OWNS); if (slot.isUndefined()) { return; } bool owns = slot.toBoolean(); slot = JS::GetReservedSlot(obj, SLOT_DATA); if (slot.isUndefined()) { return; } char** buffer = static_cast(slot.toPrivate()); if (owns) { JSObject* typeObj = &JS::GetReservedSlot(obj, SLOT_CTYPE).toObject(); size_t size = CType::GetSize(typeObj); gcx->free_(obj, *buffer, size, MemoryUse::CDataBuffer); } gcx->delete_(obj, buffer, MemoryUse::CDataBufferPtr); } JSObject* CData::GetCType(JSObject* dataObj) { dataObj = MaybeUnwrapArrayWrapper(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) { dataObj = MaybeUnwrapArrayWrapper(dataObj); MOZ_ASSERT(CData::IsCData(dataObj)); Value slot = JS::GetReservedSlot(dataObj, SLOT_DATA); void** buffer = static_cast(slot.toPrivate()); MOZ_ASSERT(buffer); MOZ_ASSERT(*buffer); return *buffer; } bool CData::IsCData(JSObject* obj) { // Assert we don't have an array wrapper. MOZ_ASSERT(MaybeUnwrapArrayWrapper(obj) == obj); return obj->hasClass(&sCDataClass); } bool CData::IsCDataMaybeUnwrap(MutableHandleObject obj) { obj.set(MaybeUnwrapArrayWrapper(obj)); return IsCData(obj); } bool CData::IsCData(HandleValue v) { return v.isObject() && CData::IsCData(MaybeUnwrapArrayWrapper(&v.toObject())); } bool CData::IsCDataProto(JSObject* obj) { return obj->hasClass(&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, GetThisObject(cx, args, "CData.prototype.address")); if (!obj) { return false; } if (!IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "CData.prototype.address", args.thisv()); } RootedObject typeObj(cx, CData::GetCType(obj)); RootedObject pointerType(cx, PointerType::CreateInternal(cx, typeObj)); if (!pointerType) { return false; } // Create a PointerType CData object containing null. JSObject* result = CData::Create(cx, pointerType, nullptr, nullptr, true); if (!result) { return false; } args.rval().setObject(*result); // Manually set the pointer inside the object, so we skip the conversion step. void** data = static_cast(GetData(result)); *data = GetData(obj); return true; } bool CData::Cast(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { return ArgumentLengthError(cx, "ctypes.cast", "two", "s"); } RootedObject sourceData(cx); if (args[0].isObject()) { sourceData = &args[0].toObject(); } if (!sourceData || !CData::IsCDataMaybeUnwrap(&sourceData)) { return ArgumentTypeMismatch(cx, "first ", "ctypes.cast", "a CData"); } RootedObject sourceType(cx, CData::GetCType(sourceData)); if (args[1].isPrimitive() || !CType::IsCType(&args[1].toObject())) { return ArgumentTypeMismatch(cx, "second ", "ctypes.cast", "a CType"); } RootedObject targetType(cx, &args[1].toObject()); size_t targetSize; if (!CType::GetSafeSize(targetType, &targetSize)) { return UndefinedSizeCastError(cx, targetType); } if (targetSize > CType::GetSize(sourceType)) { return SizeMismatchCastError(cx, sourceType, targetType, CType::GetSize(sourceType), targetSize); } // Construct a new CData object with a type of 'targetType' and a referent // of 'sourceData'. void* data = CData::GetData(sourceData); JSObject* result = CData::Create(cx, targetType, sourceData, data, false); if (!result) { return false; } args.rval().setObject(*result); return true; } bool CData::GetRuntime(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { return ArgumentLengthError(cx, "ctypes.getRuntime", "one", ""); } if (args[0].isPrimitive() || !CType::IsCType(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "", "ctypes.getRuntime", "a CType"); } RootedObject targetType(cx, &args[0].toObject()); size_t targetSize; if (!CType::GetSafeSize(targetType, &targetSize) || targetSize != sizeof(void*)) { JS_ReportErrorASCII(cx, "target CType has non-pointer size"); return false; } void* data = static_cast(cx->runtime()); JSObject* result = CData::Create(cx, targetType, nullptr, &data, true); if (!result) { return false; } args.rval().setObject(*result); return true; } // Unwrap the `this` object to a CData object, or extract a CData object from a // CDataFinalizer. static bool GetThisDataObject(JSContext* cx, const CallArgs& args, const char* funName, MutableHandleObject obj) { obj.set(GetThisObject(cx, args, funName)); if (!obj) { return IncompatibleThisProto(cx, funName, args.thisv()); } if (!CData::IsCDataMaybeUnwrap(obj)) { if (!CDataFinalizer::IsCDataFinalizer(obj)) { return IncompatibleThisProto(cx, funName, args.thisv()); } CDataFinalizer::Private* p = GetFinalizerPrivate(obj); if (!p) { return EmptyFinalizerCallError(cx, funName); } RootedValue dataVal(cx); if (!CDataFinalizer::GetValue(cx, obj, &dataVal)) { return IncompatibleThisProto(cx, funName, args.thisv()); } if (dataVal.isPrimitive()) { return IncompatibleThisProto(cx, funName, args.thisv()); } obj.set(dataVal.toObjectOrNull()); if (!obj || !CData::IsCDataMaybeUnwrap(obj)) { return IncompatibleThisProto(cx, funName, args.thisv()); } } return true; } typedef JS::TwoByteCharsZ (*InflateUTF8Method)(JSContext*, const JS::UTF8Chars, size_t*, arena_id_t); static bool ReadStringCommon(JSContext* cx, InflateUTF8Method inflateUTF8, unsigned argc, Value* vp, const char* funName, arena_id_t destArenaId) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { return ArgumentLengthError(cx, funName, "no", "s"); } RootedObject obj(cx); if (!GetThisDataObject(cx, args, funName, &obj)) { 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(CData::GetData(obj)); if (data == nullptr) { return NullPointerError(cx, "read contents of", obj); } break; case TYPE_array: baseType = ArrayType::GetBaseType(typeObj); data = CData::GetData(obj); maxLength = ArrayType::GetLength(typeObj); break; default: return TypeError(cx, "PointerType or ArrayType", args.thisv()); } // Convert the string buffer, taking care to determine the correct string // length in the case of arrays (which may contain embedded nulls). JSString* result; switch (CType::GetTypeCode(baseType)) { case TYPE_int8_t: case TYPE_uint8_t: case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: { char* bytes = static_cast(data); size_t length = js_strnlen(bytes, maxLength); // Determine the length. UniqueTwoByteChars dst( inflateUTF8(cx, JS::UTF8Chars(bytes, length), &length, destArenaId) .get()); if (!dst) { return false; } result = JS_NewUCString(cx, std::move(dst), length); if (!result) { return false; } break; } case TYPE_int16_t: case TYPE_uint16_t: case TYPE_short: case TYPE_unsigned_short: case TYPE_char16_t: { char16_t* chars = static_cast(data); size_t length = js_strnlen(chars, maxLength); result = JS_NewUCStringCopyN(cx, chars, length); break; } default: return NonStringBaseError(cx, args.thisv()); } if (!result) { return false; } args.rval().setString(result); return true; } bool CData::ReadString(JSContext* cx, unsigned argc, Value* vp) { return ReadStringCommon(cx, JS::UTF8CharsToNewTwoByteCharsZ, argc, vp, "CData.prototype.readString", js::StringBufferArena); } bool CDataFinalizer::Methods::ReadString(JSContext* cx, unsigned argc, Value* vp) { return ReadStringCommon(cx, JS::UTF8CharsToNewTwoByteCharsZ, argc, vp, "CDataFinalizer.prototype.readString", js::StringBufferArena); } bool CData::ReadStringReplaceMalformed(JSContext* cx, unsigned argc, Value* vp) { return ReadStringCommon(cx, JS::LossyUTF8CharsToNewTwoByteCharsZ, argc, vp, "CData.prototype.readStringReplaceMalformed", js::StringBufferArena); } using TypedArrayConstructor = JSObject* (*)(JSContext*, size_t); template TypedArrayConstructor GetTypedArrayConstructorImpl() { if (std::is_floating_point_v) { switch (sizeof(Type)) { case 4: return JS_NewFloat32Array; case 8: return JS_NewFloat64Array; default: return nullptr; } } constexpr bool isSigned = std::is_signed_v; switch (sizeof(Type)) { case 1: return isSigned ? JS_NewInt8Array : JS_NewUint8Array; case 2: return isSigned ? JS_NewInt16Array : JS_NewUint16Array; case 4: return isSigned ? JS_NewInt32Array : JS_NewUint32Array; default: return nullptr; } } static TypedArrayConstructor GetTypedArrayConstructor(TypeCode baseType) { switch (baseType) { #define MACRO(name, ctype, _) \ case TYPE_##name: \ return GetTypedArrayConstructorImpl(); CTYPES_FOR_EACH_TYPE(MACRO) #undef MACRO default: return nullptr; } } static bool ReadTypedArrayCommon(JSContext* cx, unsigned argc, Value* vp, const char* funName) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { return ArgumentLengthError(cx, funName, "no", "s"); } RootedObject obj(cx); if (!GetThisDataObject(cx, args, funName, &obj)) { return false; } // Make sure we are a pointer to, or an array of, a type that is compatible // with a typed array base type. JSObject* baseType; JSObject* typeObj = CData::GetCType(obj); TypeCode typeCode = CType::GetTypeCode(typeObj); void* data; mozilla::Maybe length; switch (typeCode) { case TYPE_pointer: baseType = PointerType::GetBaseType(typeObj); data = *static_cast(CData::GetData(obj)); if (data == nullptr) { return NullPointerError(cx, "read contents of", obj); } break; case TYPE_array: baseType = ArrayType::GetBaseType(typeObj); data = CData::GetData(obj); length.emplace(ArrayType::GetLength(typeObj)); break; default: return TypeError(cx, "PointerType or ArrayType", args.thisv()); } TypeCode baseTypeCode = CType::GetTypeCode(baseType); // For string inputs only, use strlen to determine the length. switch (baseTypeCode) { case TYPE_char: case TYPE_signed_char: case TYPE_unsigned_char: if (!length) { length.emplace(js_strnlen(static_cast(data), INT32_MAX)); } break; case TYPE_char16_t: if (!length) { length.emplace(js_strlen(static_cast(data))); } break; default: break; } if (!length) { return NonStringBaseError(cx, args.thisv()); } auto makeTypedArray = GetTypedArrayConstructor(baseTypeCode); if (!makeTypedArray) { return NonTypedArrayBaseError(cx, args.thisv()); } CheckedInt size = *length; size *= CType::GetSize(baseType); if (!size.isValid() || size.value() > ArrayBufferObject::MaxByteLength) { return SizeOverflow(cx, "data", "typed array"); } JSObject* result = makeTypedArray(cx, *length); if (!result) { return false; } AutoCheckCannotGC nogc(cx); bool isShared; void* buffer = JS_GetArrayBufferViewData(&result->as(), &isShared, nogc); MOZ_ASSERT(!isShared); memcpy(buffer, data, size.value()); args.rval().setObject(*result); return true; } bool CData::ReadTypedArray(JSContext* cx, unsigned argc, Value* vp) { return ReadTypedArrayCommon(cx, argc, vp, "CData.prototype.readTypedArray"); } bool CDataFinalizer::Methods::ReadTypedArray(JSContext* cx, unsigned argc, Value* vp) { return ReadTypedArrayCommon(cx, argc, vp, "CDataFinalizer.prototype.readTypedArray"); } 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(cx, source, "("); if (!BuildDataSource(cx, typeObj, data, false, source)) { source.handle(false); } AppendString(cx, source, ")"); if (!source) { return nullptr; } return NewUCString(cx, source.finish()); } 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"); } RootedObject obj(cx, GetThisObject(cx, args, "CData.prototype.toSource")); if (!obj) { return false; } if (!CData::IsCDataMaybeUnwrap(&obj) && !CData::IsCDataProto(obj)) { return IncompatibleThisProto(cx, "CData.prototype.toSource", InformalValueTypeName(args.thisv())); } JSString* result; if (CData::IsCData(obj)) { RootedObject typeObj(cx, CData::GetCType(obj)); void* data = CData::GetData(obj); result = CData::GetSourceString(cx, typeObj, data); } else { result = JS_NewStringCopyZ(cx, "[CData proto object]"); } if (!result) { return false; } args.rval().setString(result); return true; } bool CData::ErrnoGetter(JSContext* cx, const JS::CallArgs& args) { args.rval().set(JS::GetReservedSlot(&args.thisv().toObject(), SLOT_ERRNO)); return true; } #if defined(XP_WIN) bool CData::LastErrorGetter(JSContext* cx, const JS::CallArgs& args) { args.rval().set( JS::GetReservedSlot(&args.thisv().toObject(), SLOT_LASTERROR)); return true; } #endif // defined(XP_WIN) bool CDataFinalizer::Methods::ToSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject objThis( cx, GetThisObject(cx, args, "CDataFinalizer.prototype.toSource")); if (!objThis) { return false; } if (!CDataFinalizer::IsCDataFinalizer(objThis)) { return IncompatibleThisProto(cx, "CDataFinalizer.prototype.toSource", InformalValueTypeName(args.thisv())); } CDataFinalizer::Private* p = GetFinalizerPrivate(objThis); JSString* strMessage; if (!p) { strMessage = JS_NewStringCopyZ(cx, "ctypes.CDataFinalizer()"); } else { RootedObject objType(cx, CDataFinalizer::GetCType(cx, objThis)); if (!objType) { JS_ReportErrorASCII(cx, "CDataFinalizer has no type"); return false; } AutoString source; AppendString(cx, source, "ctypes.CDataFinalizer("); JSString* srcValue = CData::GetSourceString(cx, objType, p->cargs); if (!srcValue) { return false; } AppendString(cx, source, srcValue); AppendString(cx, 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(cx, source, srcDispose); AppendString(cx, source, ")"); if (!source) { return false; } strMessage = NewUCString(cx, source.finish()); } 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 = GetThisObject(cx, args, "CDataFinalizer.prototype.toString"); if (!objThis) { return false; } if (!CDataFinalizer::IsCDataFinalizer(objThis)) { return IncompatibleThisProto(cx, "CDataFinalizer.prototype.toString", InformalValueTypeName(args.thisv())); } JSString* strMessage; RootedValue value(cx); if (!GetFinalizerPrivate(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 obj->hasClass(&sCDataFinalizerClass); } JSObject* CDataFinalizer::GetCType(JSContext* cx, JSObject* obj) { MOZ_ASSERT(IsCDataFinalizer(obj)); Value valData = JS::GetReservedSlot(obj, SLOT_DATAFINALIZER_VALTYPE); if (valData.isUndefined()) { return nullptr; } return valData.toObjectOrNull(); } bool CDataFinalizer::GetValue(JSContext* cx, JSObject* obj, MutableHandleValue aResult) { MOZ_ASSERT(IsCDataFinalizer(obj)); CDataFinalizer::Private* p = GetFinalizerPrivate(obj); if (!p) { // We have called |dispose| or |forget| already. JS_ReportErrorASCII( cx, "Attempting to get the value of an empty CDataFinalizer"); return false; } RootedObject ctype(cx, GetCType(cx, obj)); return ConvertToJS(cx, ctype, /*parent*/ nullptr, p->cargs, false, true, aResult); } /* * Attach a C function as a finalizer to a JS object. * * Pseudo-JS signature: * function(CData, CData U>): CDataFinalizer * value, finalizer * * This function attaches strong references to the following values: * - the CType of |value| * * Note: This function takes advantage of the fact that non-variadic * CData functions are initialized during creation. */ bool CDataFinalizer::Construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject objSelf(cx, &args.callee()); RootedObject objProto(cx); if (!GetObjectProperty(cx, objSelf, "prototype", &objProto)) { JS_ReportErrorASCII(cx, "CDataFinalizer.prototype does not exist"); return false; } // Get arguments if (args.length() == 0) { // Special case: the empty (already finalized) object JSObject* objResult = JS_NewObjectWithGivenProto(cx, &sCDataFinalizerClass, objProto); args.rval().setObject(*objResult); return true; } if (args.length() != 2) { return ArgumentLengthError(cx, "CDataFinalizer constructor", "two", "s"); } JS::HandleValue valCodePtr = args[1]; if (!valCodePtr.isObject()) { return TypeError(cx, "_a CData object_ of a function pointer type", valCodePtr); } RootedObject objCodePtr(cx, &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::IsCDataMaybeUnwrap(&objCodePtr)) { return TypeError(cx, "a _CData_ object of a function pointer type", valCodePtr); } RootedObject objCodePtrType(cx, CData::GetCType(objCodePtr)); RootedValue valCodePtrType(cx, ObjectValue(*objCodePtrType)); MOZ_ASSERT(objCodePtrType); TypeCode typCodePtr = CType::GetTypeCode(objCodePtrType); if (typCodePtr != TYPE_pointer) { return TypeError(cx, "a CData object of a function _pointer_ type", valCodePtr); } JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType); MOZ_ASSERT(objCodeType); TypeCode typCode = CType::GetTypeCode(objCodeType); if (typCode != TYPE_function) { return TypeError(cx, "a CData object of a _function_ pointer type", valCodePtr); } uintptr_t code = *reinterpret_cast(CData::GetData(objCodePtr)); if (!code) { return TypeError(cx, "a CData object of a _non-NULL_ function pointer type", valCodePtr); } FunctionInfo* funInfoFinalizer = FunctionType::GetFunctionInfo(objCodeType); MOZ_ASSERT(funInfoFinalizer); if ((funInfoFinalizer->mArgTypes.length() != 1) || (funInfoFinalizer->mIsVariadic)) { RootedValue valCodeType(cx, ObjectValue(*objCodeType)); return TypeError(cx, "a function accepting exactly one argument", valCodeType); } RootedObject objArgType(cx, funInfoFinalizer->mArgTypes[0]); RootedObject returnType(cx, funInfoFinalizer->mReturnType); // Invariant: At this stage, we know that funInfoFinalizer->mIsVariadic // is |false|. Therefore, funInfoFinalizer->mCIF has already been initialized. bool freePointer = false; // 3. Perform dynamic cast of |args[0]| into |objType|, store it in |cargs| size_t sizeArg; RootedValue valData(cx, args[0]); if (!CType::GetSafeSize(objArgType, &sizeArg)) { RootedValue valCodeType(cx, ObjectValue(*objCodeType)); return TypeError(cx, "a function with one known size argument", valCodeType); } UniquePtr cargs(malloc(sizeArg)); if (!ImplicitConvert(cx, valData, objArgType, cargs.get(), ConversionType::Finalizer, &freePointer, objCodePtrType, 0)) { return false; } if (freePointer) { // Note: We could handle that case, if necessary. JS_ReportErrorASCII( cx, "Internal Error during CDataFinalizer. Object cannot be represented"); return false; } // 4. Prepare buffer for holding return value UniquePtr rvalue; if (CType::GetTypeCode(returnType) != TYPE_void_t) { rvalue.reset(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()) { RootedObject objData(cx, &valData.toObject()); if (CData::IsCDataMaybeUnwrap(&objData)) { objBestArgType = CData::GetCType(objData); size_t sizeBestArg; if (!CType::GetSafeSize(objBestArgType, &sizeBestArg)) { MOZ_CRASH("object with unknown size"); } if (sizeBestArg != sizeArg) { return FinalizerSizeError(cx, objCodePtrType, valData); } } } // Used by GetCType JS_SetReservedSlot(objResult, SLOT_DATAFINALIZER_VALTYPE, ObjectOrNullValue(objBestArgType)); // Used by ToSource JS_SetReservedSlot(objResult, SLOT_DATAFINALIZER_CODETYPE, ObjectValue(*objCodePtrType)); RootedValue abiType(cx, ObjectOrNullValue(funInfoFinalizer->mABI)); ffi_abi abi; if (!GetABI(cx, abiType, &abi)) { JS_ReportErrorASCII(cx, "Internal Error: " "Invalid ABI specification in CDataFinalizer"); return false; } ffi_type* rtype = CType::GetFFIType(cx, funInfoFinalizer->mReturnType); if (!rtype) { JS_ReportErrorASCII(cx, "Internal Error: " "Could not access ffi type of CDataFinalizer"); return false; } // 7. Store C information as private UniquePtr p( (CDataFinalizer::Private*)malloc(sizeof(CDataFinalizer::Private))); memmove(&p->CIF, &funInfoFinalizer->mCIF, sizeof(ffi_cif)); p->cargs = cargs.release(); p->rvalue = rvalue.release(); p->cargs_size = sizeArg; p->code = code; JS::SetReservedSlot(objResult, SLOT_DATAFINALIZER_PRIVATE, JS::PrivateValue(p.release())); args.rval().setObject(*objResult); return true; } /* * Actually call the finalizer. Does not perform any cleanup on the object. * * Preconditions: |this| must be a |CDataFinalizer|, |p| must be non-null. * The function fails if |this| has gone through |Forget|/|Dispose| * or |Finalize|. * * This function does not alter the value of |errno|/|GetLastError|. * * If argument |errnoStatus| is non-nullptr, it receives the value of |errno| * immediately after the call. Under Windows, if argument |lastErrorStatus| * is non-nullptr, it receives the value of |GetLastError| immediately after * the call. On other platforms, |lastErrorStatus| is ignored. */ void CDataFinalizer::CallFinalizer(CDataFinalizer::Private* p, int* errnoStatus, int32_t* lastErrorStatus) { int savedErrno = errno; errno = 0; #if defined(XP_WIN) int32_t savedLastError = GetLastError(); SetLastError(0); #endif // defined(XP_WIN) void* args[1] = {p->cargs}; ffi_call(&p->CIF, FFI_FN(p->code), p->rvalue, args); if (errnoStatus) { *errnoStatus = errno; } errno = savedErrno; #if defined(XP_WIN) if (lastErrorStatus) { *lastErrorStatus = GetLastError(); } SetLastError(savedLastError); #endif // defined(XP_WIN) } /* * Forget the value. * * Preconditions: |this| must be a |CDataFinalizer|. * The function fails if |this| has gone through |Forget|/|Dispose| * or |Finalize|. * * Does not call the finalizer. Cleans up the Private memory and releases all * strong references. */ bool CDataFinalizer::Methods::Forget(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { return ArgumentLengthError(cx, "CDataFinalizer.prototype.forget", "no", "s"); } RootedObject obj(cx, GetThisObject(cx, args, "CDataFinalizer.prototype.forget")); if (!obj) { return false; } if (!CDataFinalizer::IsCDataFinalizer(obj)) { return IncompatibleThisProto(cx, "CDataFinalizer.prototype.forget", args.thisv()); } CDataFinalizer::Private* p = GetFinalizerPrivate(obj); if (!p) { return EmptyFinalizerCallError(cx, "CDataFinalizer.prototype.forget"); } RootedValue valJSData(cx); RootedObject ctype(cx, GetCType(cx, obj)); if (!ConvertToJS(cx, ctype, nullptr, p->cargs, false, true, &valJSData)) { JS_ReportErrorASCII(cx, "CDataFinalizer value cannot be represented"); return false; } CDataFinalizer::Cleanup(p, obj); args.rval().set(valJSData); return true; } /* * Clean up the value. * * Preconditions: |this| must be a |CDataFinalizer|. * The function fails if |this| has gone through |Forget|/|Dispose| * or |Finalize|. * * Calls the finalizer, cleans up the Private memory and releases all * strong references. */ bool CDataFinalizer::Methods::Dispose(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { return ArgumentLengthError(cx, "CDataFinalizer.prototype.dispose", "no", "s"); } RootedObject obj(cx, GetThisObject(cx, args, "CDataFinalizer.prototype.dispose")); if (!obj) { return false; } if (!CDataFinalizer::IsCDataFinalizer(obj)) { return IncompatibleThisProto(cx, "CDataFinalizer.prototype.dispose", args.thisv()); } CDataFinalizer::Private* p = GetFinalizerPrivate(obj); if (!p) { return EmptyFinalizerCallError(cx, "CDataFinalizer.prototype.dispose"); } Value valType = JS::GetReservedSlot(obj, SLOT_DATAFINALIZER_VALTYPE); MOZ_ASSERT(valType.isObject()); RootedObject objCTypes(cx, CType::GetGlobalCTypes(cx, &valType.toObject())); if (!objCTypes) { return false; } Value valCodePtrType = JS::GetReservedSlot(obj, SLOT_DATAFINALIZER_CODETYPE); MOZ_ASSERT(valCodePtrType.isObject()); JSObject* objCodePtrType = &valCodePtrType.toObject(); JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType); MOZ_ASSERT(objCodeType); MOZ_ASSERT(CType::GetTypeCode(objCodeType) == TYPE_function); RootedObject resultType( cx, FunctionType::GetFunctionInfo(objCodeType)->mReturnType); RootedValue result(cx); int errnoStatus; #if defined(XP_WIN) int32_t lastErrorStatus; CDataFinalizer::CallFinalizer(p, &errnoStatus, &lastErrorStatus); #else CDataFinalizer::CallFinalizer(p, &errnoStatus, nullptr); #endif // defined(XP_WIN) JS_SetReservedSlot(objCTypes, SLOT_ERRNO, Int32Value(errnoStatus)); #if defined(XP_WIN) JS_SetReservedSlot(objCTypes, SLOT_LASTERROR, Int32Value(lastErrorStatus)); #endif // defined(XP_WIN) if (ConvertToJS(cx, resultType, nullptr, p->rvalue, false, true, &result)) { CDataFinalizer::Cleanup(p, obj); args.rval().set(result); return true; } CDataFinalizer::Cleanup(p, obj); return false; } /* * Perform finalization. * * Preconditions: |this| must be the result of |CDataFinalizer|. * It may have gone through |Forget|/|Dispose|. * * If |this| has not gone through |Forget|/|Dispose|, calls the * finalizer, cleans up the Private memory and releases all * strong references. */ void CDataFinalizer::Finalize(JS::GCContext* gcx, JSObject* obj) { CDataFinalizer::Private* p = GetFinalizerPrivate(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)); static_assert(CDATAFINALIZER_SLOTS == 3, "Code below must clear all slots"); JS::SetReservedSlot(obj, SLOT_DATAFINALIZER_PRIVATE, JS::UndefinedValue()); JS::SetReservedSlot(obj, SLOT_DATAFINALIZER_VALTYPE, JS::NullValue()); JS::SetReservedSlot(obj, SLOT_DATAFINALIZER_CODETYPE, JS::NullValue()); } /******************************************************************************* ** Int64 and UInt64 implementation *******************************************************************************/ JSObject* Int64Base::Construct(JSContext* cx, HandleObject proto, uint64_t data, bool isUnsigned) { const JSClass* clasp = isUnsigned ? &sUInt64Class : &sInt64Class; RootedObject result(cx, JS_NewObjectWithGivenProto(cx, clasp, proto)); if (!result) { return nullptr; } // attach the Int64's data uint64_t* buffer = cx->new_(data); if (!buffer) { return nullptr; } JS_InitReservedSlot(result, SLOT_INT64, buffer, JS::MemoryUse::CTypesInt64); if (!JS_FreezeObject(cx, result)) { return nullptr; } return result; } void Int64Base::Finalize(JS::GCContext* gcx, JSObject* obj) { Value slot = JS::GetReservedSlot(obj, SLOT_INT64); if (slot.isUndefined()) { return; } uint64_t* buffer = static_cast(slot.toPrivate()); gcx->delete_(obj, buffer, MemoryUse::CTypesInt64); } uint64_t Int64Base::GetInt(JSObject* obj) { MOZ_ASSERT(Int64::IsInt64(obj) || UInt64::IsUInt64(obj)); Value slot = JS::GetReservedSlot(obj, SLOT_INT64); return *static_cast(slot.toPrivate()); } bool Int64Base::ToString(JSContext* cx, JSObject* obj, const CallArgs& args, bool isUnsigned) { if (args.length() > 1) { if (isUnsigned) { return ArgumentLengthError(cx, "UInt64.prototype.toString", "at most one", ""); } return ArgumentLengthError(cx, "Int64.prototype.toString", "at most one", ""); } int radix = 10; if (args.length() == 1) { Value arg = args[0]; if (arg.isInt32()) { radix = arg.toInt32(); } if (!arg.isInt32() || radix < 2 || radix > 36) { if (isUnsigned) { return ArgumentRangeMismatch( cx, "UInt64.prototype.toString", "an integer at least 2 and no greater than 36"); } return ArgumentRangeMismatch( cx, "Int64.prototype.toString", "an integer at least 2 and no greater than 36"); } } AutoString intString; if (isUnsigned) { IntegerToString(GetInt(obj), radix, intString); } else { IntegerToString(static_cast(GetInt(obj)), radix, intString); } if (!intString) { return false; } JSString* result = NewUCString(cx, intString.finish()); 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(cx, source, "ctypes.UInt64(\""); IntegerToString(GetInt(obj), 10, source); } else { AppendString(cx, source, "ctypes.Int64(\""); IntegerToString(static_cast(GetInt(obj)), 10, source); } AppendString(cx, source, "\")"); if (!source) { return false; } JSString* result = NewUCString(cx, source.finish()); if (!result) { return false; } args.rval().setString(result); return true; } bool Int64::Construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new Int64 object. if (args.length() != 1) { return ArgumentLengthError(cx, "Int64 constructor", "one", ""); } int64_t i = 0; bool overflow = false; if (!jsvalToBigInteger(cx, args[0], true, &i, &overflow)) { if (overflow) { return TypeOverflow(cx, "int64", args[0]); } return ArgumentConvError(cx, args[0], "Int64", 0); } // Get ctypes.Int64.prototype from the 'prototype' property of the ctor. RootedValue slot(cx); RootedObject callee(cx, &args.callee()); MOZ_ALWAYS_TRUE(JS_GetProperty(cx, callee, "prototype", &slot)); RootedObject proto(cx, slot.toObjectOrNull()); MOZ_ASSERT(proto->hasClass(&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 obj->hasClass(&sInt64Class); } bool Int64::ToString(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "Int64.prototype.toString")); if (!obj) { return false; } if (!Int64::IsInt64(obj)) { if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "Int64.prototype.toString", InformalValueTypeName(args.thisv())); } return IncompatibleThisType(cx, "Int64.prototype.toString", "non-Int64 CData"); } return Int64Base::ToString(cx, obj, args, false); } bool Int64::ToSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "Int64.prototype.toSource")); if (!obj) { return false; } if (!Int64::IsInt64(obj)) { if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "Int64.prototype.toSource", InformalValueTypeName(args.thisv())); } return IncompatibleThisType(cx, "Int64.prototype.toSource", "non-Int64 CData"); } return Int64Base::ToSource(cx, obj, args, false); } bool Int64::Compare(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { return ArgumentLengthError(cx, "Int64.compare", "two", "s"); } if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "first ", "Int64.compare", "a Int64"); } if (args[1].isPrimitive() || !Int64::IsInt64(&args[1].toObject())) { return ArgumentTypeMismatch(cx, "second ", "Int64.compare", "a Int64"); } JSObject* obj1 = &args[0].toObject(); JSObject* obj2 = &args[1].toObject(); int64_t i1 = Int64Base::GetInt(obj1); int64_t i2 = Int64Base::GetInt(obj2); if (i1 == i2) { args.rval().setInt32(0); } else if (i1 < i2) { args.rval().setInt32(-1); } else { args.rval().setInt32(1); } return true; } #define LO_MASK ((uint64_t(1) << 32) - 1) #define INT64_LO(i) ((i)&LO_MASK) #define INT64_HI(i) ((i) >> 32) bool Int64::Lo(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { return ArgumentLengthError(cx, "Int64.lo", "one", ""); } if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "", "Int64.lo", "a Int64"); } JSObject* obj = &args[0].toObject(); int64_t u = Int64Base::GetInt(obj); double d = uint32_t(INT64_LO(u)); args.rval().setNumber(d); return true; } bool Int64::Hi(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { return ArgumentLengthError(cx, "Int64.hi", "one", ""); } if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "", "Int64.hi", "a Int64"); } JSObject* obj = &args[0].toObject(); int64_t u = Int64Base::GetInt(obj); double d = int32_t(INT64_HI(u)); args.rval().setDouble(d); return true; } bool Int64::Join(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { return ArgumentLengthError(cx, "Int64.join", "two", "s"); } int32_t hi; uint32_t lo; if (!jsvalToInteger(cx, args[0], &hi)) { return ArgumentConvError(cx, args[0], "Int64.join", 0); } if (!jsvalToInteger(cx, args[1], &lo)) { return ArgumentConvError(cx, args[1], "Int64.join", 1); } int64_t i = mozilla::WrapToSigned((uint64_t(hi) << 32) + 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(proto->hasClass(&sInt64ProtoClass)); JSObject* result = Int64Base::Construct(cx, proto, i, false); if (!result) { return false; } args.rval().setObject(*result); return true; } bool UInt64::Construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new UInt64 object. if (args.length() != 1) { return ArgumentLengthError(cx, "UInt64 constructor", "one", ""); } uint64_t u = 0; bool overflow = false; if (!jsvalToBigInteger(cx, args[0], true, &u, &overflow)) { if (overflow) { return TypeOverflow(cx, "uint64", args[0]); } return ArgumentConvError(cx, args[0], "UInt64", 0); } // Get ctypes.UInt64.prototype from the 'prototype' property of the ctor. RootedValue slot(cx); RootedObject callee(cx, &args.callee()); MOZ_ALWAYS_TRUE(JS_GetProperty(cx, callee, "prototype", &slot)); RootedObject proto(cx, &slot.toObject()); MOZ_ASSERT(proto->hasClass(&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 obj->hasClass(&sUInt64Class); } bool UInt64::ToString(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "UInt64.prototype.toString")); if (!obj) { return false; } if (!UInt64::IsUInt64(obj)) { if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "UInt64.prototype.toString", InformalValueTypeName(args.thisv())); } return IncompatibleThisType(cx, "UInt64.prototype.toString", "non-UInt64 CData"); } return Int64Base::ToString(cx, obj, args, true); } bool UInt64::ToSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, GetThisObject(cx, args, "UInt64.prototype.toSource")); if (!obj) { return false; } if (!UInt64::IsUInt64(obj)) { if (!CData::IsCDataMaybeUnwrap(&obj)) { return IncompatibleThisProto(cx, "UInt64.prototype.toSource", InformalValueTypeName(args.thisv())); } return IncompatibleThisType(cx, "UInt64.prototype.toSource", "non-UInt64 CData"); } return Int64Base::ToSource(cx, obj, args, true); } bool UInt64::Compare(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { return ArgumentLengthError(cx, "UInt64.compare", "two", "s"); } if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "first ", "UInt64.compare", "a UInt64"); } if (args[1].isPrimitive() || !UInt64::IsUInt64(&args[1].toObject())) { return ArgumentTypeMismatch(cx, "second ", "UInt64.compare", "a UInt64"); } JSObject* obj1 = &args[0].toObject(); JSObject* obj2 = &args[1].toObject(); uint64_t u1 = Int64Base::GetInt(obj1); uint64_t u2 = Int64Base::GetInt(obj2); if (u1 == u2) { args.rval().setInt32(0); } else if (u1 < u2) { args.rval().setInt32(-1); } else { args.rval().setInt32(1); } return true; } bool UInt64::Lo(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { return ArgumentLengthError(cx, "UInt64.lo", "one", ""); } if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "", "UInt64.lo", "a UInt64"); } JSObject* obj = &args[0].toObject(); uint64_t u = Int64Base::GetInt(obj); double d = uint32_t(INT64_LO(u)); args.rval().setDouble(d); return true; } bool UInt64::Hi(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { return ArgumentLengthError(cx, "UInt64.hi", "one", ""); } if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { return ArgumentTypeMismatch(cx, "", "UInt64.hi", "a UInt64"); } JSObject* obj = &args[0].toObject(); uint64_t u = Int64Base::GetInt(obj); double d = uint32_t(INT64_HI(u)); args.rval().setDouble(d); return true; } bool UInt64::Join(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { return ArgumentLengthError(cx, "UInt64.join", "two", "s"); } uint32_t hi; uint32_t lo; if (!jsvalToInteger(cx, args[0], &hi)) { return ArgumentConvError(cx, args[0], "UInt64.join", 0); } if (!jsvalToInteger(cx, args[1], &lo)) { return ArgumentConvError(cx, args[1], "UInt64.join", 1); } uint64_t u = (uint64_t(hi) << 32) + uint64_t(lo); // Get UInt64.prototype from the function's reserved slot. JSObject* callee = &args.callee(); Value slot = js::GetFunctionNativeReserved(callee, SLOT_FN_INT64PROTO); RootedObject proto(cx, &slot.toObject()); MOZ_ASSERT(proto->hasClass(&sUInt64ProtoClass)); JSObject* result = Int64Base::Construct(cx, proto, u, true); if (!result) { return false; } args.rval().setObject(*result); return true; } } // namespace ctypes } // namespace js