/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ /* 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/. */ #ifndef ctypes_CTypes_h #define ctypes_CTypes_h #include "mozilla/Sprintf.h" #include "mozilla/Vector.h" #include "ffi.h" #include "prlink.h" #include "ctypes/typedefs.h" #include "gc/ZoneAllocator.h" #include "js/AllocPolicy.h" #include "js/GCHashTable.h" #include "js/UniquePtr.h" #include "js/Vector.h" #include "vm/JSObject.h" #include "vm/StringType.h" namespace JS { struct CTypesCallbacks; } // namespace JS namespace js { namespace ctypes { /******************************************************************************* ** Utility classes *******************************************************************************/ // CTypes builds a number of strings. StringBuilder allows repeated appending // with a single error check at the end. Only the Vector methods required for // building the string are exposed. template class StringBuilder { Vector v; // Have any (OOM) errors been encountered while constructing this string? bool errored{false}; #ifdef DEBUG // Have we finished building this string? bool finished{false}; // Did we check for errors? mutable bool checked{false}; #endif public: explicit operator bool() const { #ifdef DEBUG checked = true; #endif return !errored; } // Handle the result of modifying the string, by remembering the persistent // errored status. bool handle(bool result) { MOZ_ASSERT(!finished); if (!result) { errored = true; } return result; } bool resize(size_t n) { return handle(v.resize(n)); } CharT& operator[](size_t index) { return v[index]; } const CharT& operator[](size_t index) const { return v[index]; } size_t length() const { return v.length(); } template [[nodiscard]] bool append(U&& u) { return handle(v.append(u)); } template [[nodiscard]] bool append(const U* begin, const U* end) { return handle(v.append(begin, end)); } template [[nodiscard]] bool append(const U* begin, size_t len) { return handle(v.append(begin, len)); } CharT* begin() { MOZ_ASSERT(!finished); return v.begin(); } // finish() produces the results of the string building, and is required as // the last thing before the string contents are used. The StringBuilder must // be checked for errors before calling this, however. Vector&& finish() { MOZ_ASSERT(!errored); MOZ_ASSERT(!finished); MOZ_ASSERT(checked); #ifdef DEBUG finished = true; #endif return std::move(v); } }; // Note that these strings do not have any inline storage, because we use move // constructors to pass the data around and inline storage would necessitate // copying. typedef StringBuilder AutoString; typedef StringBuilder AutoCString; typedef Vector AutoStringChars; typedef Vector AutoCStringChars; // Convenience functions to append, insert, and compare Strings. template void AppendString(JSContext* cx, StringBuilder& v, const char (&array)[ArrayLength]) { // Don't include the trailing '\0'. size_t alen = ArrayLength - 1; size_t vlen = v.length(); if (!v.resize(vlen + alen)) { return; } for (size_t i = 0; i < alen; ++i) { v[i + vlen] = array[i]; } } template void AppendChars(StringBuilder& v, const char c, size_t count) { size_t vlen = v.length(); if (!v.resize(vlen + count)) { return; } for (size_t i = 0; i < count; ++i) { v[i + vlen] = c; } } template void AppendUInt(StringBuilder& v, unsigned n) { char array[16]; size_t alen = SprintfLiteral(array, "%u", n); size_t vlen = v.length(); if (!v.resize(vlen + alen)) { return; } for (size_t i = 0; i < alen; ++i) { v[i + vlen] = array[i]; } } template void AppendString(JSContext* cx, StringBuilder& v, mozilla::Vector& w) { if (!v.append(w.begin(), w.length())) { return; } } template void AppendString(JSContext* cx, StringBuilder& v, JSString* str) { MOZ_ASSERT(str); JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return; } JS::AutoCheckCannotGC nogc; if (linear->hasLatin1Chars()) { if (!v.append(linear->latin1Chars(nogc), linear->length())) { return; } } else { if (!v.append(linear->twoByteChars(nogc), linear->length())) { return; } } } template void AppendString(JSContext* cx, StringBuilder& v, JSString* str) { MOZ_ASSERT(str); size_t vlen = v.length(); size_t alen = str->length(); if (!v.resize(vlen + alen)) { return; } JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return; } JS::AutoCheckCannotGC nogc; if (linear->hasLatin1Chars()) { const Latin1Char* chars = linear->latin1Chars(nogc); for (size_t i = 0; i < alen; ++i) { v[i + vlen] = char(chars[i]); } } else { const char16_t* chars = linear->twoByteChars(nogc); for (size_t i = 0; i < alen; ++i) { v[i + vlen] = char(chars[i]); } } } template void PrependString(JSContext* cx, StringBuilder& v, const char (&array)[ArrayLength]) { // Don't include the trailing '\0'. size_t alen = ArrayLength - 1; size_t vlen = v.length(); if (!v.resize(vlen + alen)) { return; } // Move vector data forward. This is safe since we've already resized. memmove(v.begin() + alen, v.begin(), vlen * sizeof(T)); // Copy data to insert. for (size_t i = 0; i < alen; ++i) { v[i] = array[i]; } } template void PrependString(JSContext* cx, StringBuilder& v, JSString* str) { MOZ_ASSERT(str); size_t vlen = v.length(); size_t alen = str->length(); if (!v.resize(vlen + alen)) { return; } JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return; } // Move vector data forward. This is safe since we've already resized. memmove(v.begin() + alen, v.begin(), vlen * sizeof(char16_t)); // Copy data to insert. CopyChars(v.begin(), *linear); } [[nodiscard]] bool ReportErrorIfUnpairedSurrogatePresent(JSContext* cx, JSLinearString* str); [[nodiscard]] JSObject* GetThisObject(JSContext* cx, const CallArgs& args, const char* msg); /******************************************************************************* ** Function and struct API definitions *******************************************************************************/ // for JS error reporting enum ErrorNum { #define MSG_DEF(name, count, exception, format) name, #include "ctypes/ctypes.msg" #undef MSG_DEF CTYPESERR_LIMIT }; /** * ABI constants that specify the calling convention to use. * ctypes.default_abi corresponds to the cdecl convention, and in almost all * cases is the correct choice. ctypes.stdcall_abi is provided for calling * stdcall functions on Win32, and implies stdcall symbol name decoration; * ctypes.winapi_abi is just stdcall but without decoration. */ enum ABICode { ABI_DEFAULT, ABI_STDCALL, ABI_THISCALL, ABI_WINAPI, INVALID_ABI }; enum TypeCode { TYPE_void_t, #define DEFINE_TYPE(name, type, ffiType) TYPE_##name, CTYPES_FOR_EACH_TYPE(DEFINE_TYPE) #undef DEFINE_TYPE TYPE_pointer, TYPE_function, TYPE_array, TYPE_struct }; // Descriptor of one field in a StructType. The name of the field is stored // as the key to the hash entry. struct FieldInfo { HeapPtr mType; // CType of the field size_t mIndex; // index of the field in the struct (first is 0) size_t mOffset; // offset of the field in the struct, in bytes void trace(JSTracer* trc) { TraceEdge(trc, &mType, "fieldType"); } }; struct UnbarrieredFieldInfo { JSObject* mType; // CType of the field size_t mIndex; // index of the field in the struct (first is 0) size_t mOffset; // offset of the field in the struct, in bytes }; static_assert(sizeof(UnbarrieredFieldInfo) == sizeof(FieldInfo), "UnbarrieredFieldInfo should be the same as FieldInfo but with " "unbarriered mType"); // Hash policy for FieldInfos. struct FieldHashPolicy { using Key = JSLinearString*; using Lookup = Key; static HashNumber hash(const Lookup& l) { return js::HashStringChars(l); } static bool match(const Key& k, const Lookup& l) { return js::EqualStrings(k, l); } }; using FieldInfoHash = GCHashMap, FieldInfo, FieldHashPolicy, CellAllocPolicy>; // Descriptor of ABI, return type, argument types, and variadicity for a // FunctionType. struct FunctionInfo { explicit FunctionInfo(JS::Zone* zone) : mArgTypes(zone), mFFITypes(zone) {} // Initialized in NewFunctionInfo when !mIsVariadic, but only later, in // FunctionType::Call, when mIsVariadic. Not always consistent with // mFFITypes, due to lazy initialization when mIsVariadic. ffi_cif mCIF; // Calling convention of the function. Convert to ffi_abi using GetABI // and ObjectValue. Stored as a JSObject* for ease of tracing. HeapPtr mABI; // The CType of the value returned by the function. HeapPtr mReturnType; // A fixed array of known parameter types, excluding any variadic // parameters (if mIsVariadic). GCVector, 0, CellAllocPolicy> mArgTypes; // A variable array of ffi_type*s corresponding to both known parameter // types and dynamic (variadic) parameter types. Longer than mArgTypes // only if mIsVariadic. Vector mFFITypes; // Flag indicating whether the function behaves like a C function with // ... as the final formal parameter. bool mIsVariadic; }; // Parameters necessary for invoking a JS function from a C closure. struct ClosureInfo { JSContext* cx; HeapPtr closureObj; // CClosure object HeapPtr typeObj; // FunctionType describing the C function HeapPtr thisObj; // 'this' object to use for the JS function call HeapPtr jsfnObj; // JS function void* errResult; // Result that will be returned if the closure throws ffi_closure* closure; // The C closure itself // Anything conditionally freed in the destructor should be initialized to // nullptr here. explicit ClosureInfo(JSContext* context) : cx(context), errResult(nullptr), closure(nullptr) {} ~ClosureInfo() { if (closure) { ffi_closure_free(closure); } js_free(errResult); } }; bool IsCTypesGlobal(HandleValue v); bool IsCTypesGlobal(JSObject* obj); const JS::CTypesCallbacks* GetCallbacks(JSObject* obj); /******************************************************************************* ** JSClass reserved slot definitions *******************************************************************************/ enum CTypesGlobalSlot { SLOT_CALLBACKS = 0, // pointer to JS::CTypesCallbacks struct SLOT_ERRNO = 1, // Value for latest |errno| SLOT_LASTERROR = 2, // Value for latest |GetLastError|, used only with Windows CTYPESGLOBAL_SLOTS }; enum CABISlot { SLOT_ABICODE = 0, // ABICode of the CABI object CABI_SLOTS }; enum CTypeProtoSlot { SLOT_POINTERPROTO = 0, // ctypes.PointerType.prototype object SLOT_ARRAYPROTO = 1, // ctypes.ArrayType.prototype object SLOT_STRUCTPROTO = 2, // ctypes.StructType.prototype object SLOT_FUNCTIONPROTO = 3, // ctypes.FunctionType.prototype object SLOT_CDATAPROTO = 4, // ctypes.CData.prototype object SLOT_POINTERDATAPROTO = 5, // common ancestor of all CData objects of PointerType SLOT_ARRAYDATAPROTO = 6, // common ancestor of all CData objects of ArrayType SLOT_STRUCTDATAPROTO = 7, // common ancestor of all CData objects of StructType SLOT_FUNCTIONDATAPROTO = 8, // common ancestor of all CData objects of FunctionType SLOT_INT64PROTO = 9, // ctypes.Int64.prototype object SLOT_UINT64PROTO = 10, // ctypes.UInt64.prototype object SLOT_CTYPES = 11, // ctypes object SLOT_OURDATAPROTO = 12, // the data prototype corresponding to this object CTYPEPROTO_SLOTS }; enum CTypeSlot { SLOT_PROTO = 0, // 'prototype' property of the CType object SLOT_TYPECODE = 1, // TypeCode of the CType object SLOT_FFITYPE = 2, // ffi_type representing the type SLOT_NAME = 3, // name of the type SLOT_SIZE = 4, // size of the type, in bytes SLOT_ALIGN = 5, // alignment of the type, in bytes SLOT_PTR = 6, // cached PointerType object for type.ptr // Note that some of the slots below can overlap, since they're for // mutually exclusive types. SLOT_TARGET_T = 7, // (PointerTypes only) 'targetType' property SLOT_ELEMENT_T = 7, // (ArrayTypes only) 'elementType' property SLOT_LENGTH = 8, // (ArrayTypes only) 'length' property SLOT_FIELDS = 7, // (StructTypes only) 'fields' property SLOT_FIELDINFO = 8, // (StructTypes only) FieldInfoHash table SLOT_FNINFO = 7, // (FunctionTypes only) FunctionInfo struct SLOT_ARGS_T = 8, // (FunctionTypes only) 'argTypes' property (cached) CTYPE_SLOTS }; enum CDataSlot { SLOT_CTYPE = 0, // CType object representing the underlying type SLOT_REFERENT = 1, // JSObject this object must keep alive, if any SLOT_DATA = 2, // pointer to a buffer containing the binary data SLOT_OWNS = 3, // TrueValue() if this CData owns its own buffer SLOT_FUNNAME = 4, // JSString representing the function name CDATA_SLOTS }; enum CClosureSlot { SLOT_CLOSUREINFO = 0, // ClosureInfo struct CCLOSURE_SLOTS }; enum CDataFinalizerSlot { // PrivateValue storing CDataFinalizer::Private* pointer or UndefinedValue. SLOT_DATAFINALIZER_PRIVATE = 0, // The type of the value (a CType JSObject). // We hold it to permit ImplicitConvert and ToSource. SLOT_DATAFINALIZER_VALTYPE = 1, // The type of the function used at finalization (a CType JSObject). // We hold it to permit |ToSource|. SLOT_DATAFINALIZER_CODETYPE = 2, CDATAFINALIZER_SLOTS }; enum TypeCtorSlot { SLOT_FN_CTORPROTO = 0 // ctypes.{Pointer,Array,Struct}Type.prototype // JSFunction objects always get exactly two slots. }; enum Int64Slot { SLOT_INT64 = 0, // pointer to a 64-bit buffer containing the integer INT64_SLOTS }; enum Int64FunctionSlot { SLOT_FN_INT64PROTO = 0 // ctypes.{Int64,UInt64}.prototype object // JSFunction objects always get exactly two slots. }; /******************************************************************************* ** Object API definitions *******************************************************************************/ namespace CType { JSObject* Create(JSContext* cx, HandleObject typeProto, HandleObject dataProto, TypeCode type, JSString* name, HandleValue size, HandleValue align, ffi_type* ffiType); JSObject* DefineBuiltin(JSContext* cx, HandleObject ctypesObj, const char* propName, JSObject* typeProto, JSObject* dataProto, const char* name, TypeCode type, HandleValue size, HandleValue align, ffi_type* ffiType); bool IsCType(JSObject* obj); bool IsCTypeProto(JSObject* obj); TypeCode GetTypeCode(JSObject* typeObj); bool TypesEqual(JSObject* t1, JSObject* t2); size_t GetSize(JSObject* obj); [[nodiscard]] bool GetSafeSize(JSObject* obj, size_t* result); bool IsSizeDefined(JSObject* obj); size_t GetAlignment(JSObject* obj); ffi_type* GetFFIType(JSContext* cx, JSObject* obj); JSString* GetName(JSContext* cx, HandleObject obj); JSObject* GetProtoFromCtor(JSObject* obj, CTypeProtoSlot slot); JSObject* GetProtoFromType(JSContext* cx, JSObject* obj, CTypeProtoSlot slot); const JS::CTypesCallbacks* GetCallbacksFromType(JSObject* obj); } // namespace CType namespace PointerType { JSObject* CreateInternal(JSContext* cx, HandleObject baseType); JSObject* GetBaseType(JSObject* obj); } // namespace PointerType using UniquePtrFFIType = UniquePtr; namespace ArrayType { JSObject* CreateInternal(JSContext* cx, HandleObject baseType, size_t length, bool lengthDefined); JSObject* GetBaseType(JSObject* obj); size_t GetLength(JSObject* obj); [[nodiscard]] bool GetSafeLength(JSObject* obj, size_t* result); UniquePtrFFIType BuildFFIType(JSContext* cx, JSObject* obj); } // namespace ArrayType namespace StructType { [[nodiscard]] bool DefineInternal(JSContext* cx, JSObject* typeObj, JSObject* fieldsObj); const FieldInfoHash* GetFieldInfo(JSObject* obj); const FieldInfo* LookupField(JSContext* cx, JSObject* obj, JSLinearString* name); JSObject* BuildFieldsArray(JSContext* cx, JSObject* obj); UniquePtrFFIType BuildFFIType(JSContext* cx, JSObject* obj); } // namespace StructType namespace FunctionType { JSObject* CreateInternal(JSContext* cx, HandleValue abi, HandleValue rtype, const HandleValueArray& args); JSObject* ConstructWithObject(JSContext* cx, JSObject* typeObj, JSObject* refObj, PRFuncPtr fnptr, JSObject* result); FunctionInfo* GetFunctionInfo(JSObject* obj); void BuildSymbolName(JSContext* cx, JSString* name, JSObject* typeObj, AutoCString& result); } // namespace FunctionType namespace CClosure { JSObject* Create(JSContext* cx, HandleObject typeObj, HandleObject fnObj, HandleObject thisObj, HandleValue errVal, PRFuncPtr* fnptr); } // namespace CClosure namespace CData { JSObject* Create(JSContext* cx, HandleObject typeObj, HandleObject refObj, void* data, bool ownResult); JSObject* GetCType(JSObject* dataObj); void* GetData(JSObject* dataObj); bool IsCData(JSObject* obj); bool IsCDataMaybeUnwrap(MutableHandleObject obj); bool IsCData(HandleValue v); bool IsCDataProto(JSObject* obj); // Attached by JSAPI as the function 'ctypes.cast' [[nodiscard]] bool Cast(JSContext* cx, unsigned argc, Value* vp); // Attached by JSAPI as the function 'ctypes.getRuntime' [[nodiscard]] bool GetRuntime(JSContext* cx, unsigned argc, Value* vp); } // namespace CData namespace Int64 { bool IsInt64(JSObject* obj); } // namespace Int64 namespace UInt64 { bool IsUInt64(JSObject* obj); } // namespace UInt64 } // namespace ctypes } // namespace js #endif /* ctypes_CTypes_h */