Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <errno.h>
#endif
#if defined(XP_WIN)
# include <float.h>
#endif
#if defined(SOLARIS)
# include <ieeefp.h>
#endif
#include <iterator>
#include <limits>
#include <stdint.h>
#include <sys/types.h>
#include <type_traits>
#include "jsapi.h"
#include "builtin/Number.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/ArrayBufferMaybeShared.h" // JS::IsImmutableArrayBufferMaybeShared
#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/ErrorObject.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 <JS::IsAcceptableThis Test, JS::NativeImpl Impl>
struct Property {
static bool Fun(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
return JS::CallNonGenericMethod<Test, Impl>(cx, args);
}
};
static bool ConstructAbstract(JSContext* cx, unsigned argc, Value* vp);
namespace CType {
static bool ConstructData(JSContext* cx, unsigned argc, Value* vp);
static bool ConstructBasic(JSContext* cx, HandleObject obj,
const CallArgs& args);
static void Trace(JSTracer* trc, JSObject* obj);
static void Finalize(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<CType::IsCType, CType::NameGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG("size", (Property<CType::IsCType, CType::SizeGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG("ptr", (Property<CType::IsCType, CType::PtrGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG("prototype",
(Property<CType::IsCTypeOrProto, CType::PrototypeGetter>::Fun),
CTYPESACC_FLAGS),
JS_PS_END,
};
static const JSFunctionSpec sCTypeFunctions[] = {
JS_FN("array", CType::CreateArray, 0, CTYPESFN_FLAGS),
JS_FN("toString", CType::ToString, 0, CTYPESFN_FLAGS),
JS_FN("toSource", CType::ToSource, 0, CTYPESFN_FLAGS),
JS_FS_END,
};
static const JSFunctionSpec sCABIFunctions[] = {
JS_FN("toSource", ABI::ToSource, 0, CABIFN_FLAGS),
JS_FN("toString", ABI::ToSource, 0, CABIFN_FLAGS),
JS_FS_END,
};
static const JSPropertySpec sCDataProps[] = {
JS_PSGS("value", (Property<CData::IsCData, CData::ValueGetter>::Fun),
(Property<CData::IsCData, CData::ValueSetter>::Fun),
JSPROP_PERMANENT),
JS_PS_END,
};
static const JSFunctionSpec sCDataFunctions[] = {
JS_FN("address", CData::Address, 0, CDATAFN_FLAGS),
JS_FN("readString", CData::ReadString, 0, CDATAFN_FLAGS),
JS_FN("readStringReplaceMalformed", CData::ReadStringReplaceMalformed, 0,
CDATAFN_FLAGS),
JS_FN("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<PointerType::IsPointerType,
PointerType::TargetTypeGetter>::Fun),
CTYPESACC_FLAGS),
JS_PS_END,
};
static const JSFunctionSpec sPointerInstanceFunctions[] = {
JS_FN("isNull", PointerType::IsNull, 0, CTYPESFN_FLAGS),
JS_FN("increment", PointerType::Increment, 0, CTYPESFN_FLAGS),
JS_FN("decrement", PointerType::Decrement, 0, CTYPESFN_FLAGS),
JS_FS_END,
};
static const JSPropertySpec sPointerInstanceProps[] = {
JS_PSGS(
"contents",
(Property<PointerType::IsPointer, PointerType::ContentsGetter>::Fun),
(Property<PointerType::IsPointer, PointerType::ContentsSetter>::Fun),
JSPROP_PERMANENT),
JS_PS_END,
};
static const JSFunctionSpec sArrayFunction =
JS_FN("ArrayType", ArrayType::Create, 1, CTYPESCTOR_FLAGS);
static const JSPropertySpec sArrayProps[] = {
JS_PSG(
"elementType",
(Property<ArrayType::IsArrayType, ArrayType::ElementTypeGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG(
"length",
(Property<ArrayType::IsArrayOrArrayType, ArrayType::LengthGetter>::Fun),
CTYPESACC_FLAGS),
JS_PS_END,
};
static const JSFunctionSpec sArrayInstanceFunctions[] = {
JS_FN("addressOfElement", ArrayType::AddressOfElement, 1, CDATAFN_FLAGS),
JS_FS_END,
};
static const JSPropertySpec sArrayInstanceProps[] = {
JS_PSG(
"length",
(Property<ArrayType::IsArrayOrArrayType, ArrayType::LengthGetter>::Fun),
JSPROP_PERMANENT),
JS_PS_END,
};
static const JSFunctionSpec sStructFunction =
JS_FN("StructType", StructType::Create, 2, CTYPESCTOR_FLAGS);
static const JSPropertySpec sStructProps[] = {
JS_PSG("fields",
(Property<StructType::IsStruct, StructType::FieldsArrayGetter>::Fun),
CTYPESACC_FLAGS),
JS_PS_END,
};
static const JSFunctionSpec sStructFunctions[] = {
JS_FN("define", StructType::Define, 1, CDATAFN_FLAGS),
JS_FS_END,
};
static const JSFunctionSpec sStructInstanceFunctions[] = {
JS_FN("addressOfField", StructType::AddressOfField, 1, CDATAFN_FLAGS),
JS_FS_END,
};
static const JSFunctionSpec sFunctionFunction =
JS_FN("FunctionType", FunctionType::Create, 2, CTYPESCTOR_FLAGS);
static const JSPropertySpec sFunctionProps[] = {
JS_PSG("argTypes",
(Property<FunctionType::IsFunctionType,
FunctionType::ArgTypesGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG("returnType",
(Property<FunctionType::IsFunctionType,
FunctionType::ReturnTypeGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG(
"abi",
(Property<FunctionType::IsFunctionType, FunctionType::ABIGetter>::Fun),
CTYPESACC_FLAGS),
JS_PSG("isVariadic",
(Property<FunctionType::IsFunctionType,
FunctionType::IsVariadicGetter>::Fun),
CTYPESACC_FLAGS),
JS_PS_END,
};
static const JSFunctionSpec sFunctionInstanceFunctions[] = {
JS_FN("call", js::fun_call, 1, CDATAFN_FLAGS),
JS_FN("apply", js::fun_apply, 2, CDATAFN_FLAGS),
JS_FS_END,
};
static const JSClass sInt64ProtoClass = {
"Int64",
0,
};
static const JSClass sUInt64ProtoClass = {
"UInt64",
0,
};
static const JSClassOps sInt64ClassOps = {
nullptr, // 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<IsCTypesGlobal, CData::ErrnoGetter>::Fun),
JSPROP_PERMANENT),
#if defined(XP_WIN)
JS_PSG("winLastError",
(Property<IsCTypesGlobal, CData::LastErrorGetter>::Fun),
JSPROP_PERMANENT),
#endif // defined(XP_WIN)
JS_PS_END,
};
static const JSFunctionSpec sModuleFunctions[] = {
JS_FN("CDataFinalizer", CDataFinalizer::Construct, 2, CTYPESFN_FLAGS),
JS_FN("open", Library::Open, 1, CTYPESFN_FLAGS),
JS_FN("cast", CData::Cast, 2, CTYPESFN_FLAGS),
JS_FN("getRuntime", CData::GetRuntime, 1, CTYPESFN_FLAGS),
JS_FN("libraryName", Library::Name, 1, CTYPESFN_FLAGS),
JS_FS_END,
};
// 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<ProxyObject>().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<ProxyObject>().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<ProxyObject>() &&
obj->as<ProxyObject>().handler() == &CDataArrayProxyHandler::singleton) {
return obj->as<ProxyObject>().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, "<<error converting function to string>>");
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<JSLinearString*> 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<const JS::CTypesCallbacks*>(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<JSObject*> 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<CTypesCallbacks*>(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<char**>(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<double>::is_signed);
template <typename TargetType, typename FromType,
bool FromIsIntegral = std::is_integral_v<FromType>>
struct ConvertImpl;
template <typename TargetType, typename FromType>
struct ConvertImpl<TargetType, FromType, false> {
static MOZ_ALWAYS_INLINE TargetType Convert(FromType input) {
return JS::ToSignedOrUnsignedInteger<TargetType>(input);
}
};
template <typename TargetType>
struct ConvertUnsignedTargetTo {
static TargetType convert(std::make_unsigned_t<TargetType> input) {
return std::is_signed_v<TargetType> ? mozilla::WrapToSigned(input) : input;
}
};
template <>
struct ConvertUnsignedTargetTo<char16_t> {
static char16_t convert(char16_t input) {
// mozilla::WrapToSigned can't be used on char16_t.
return input;
}
};
template <typename TargetType, typename FromType>
struct ConvertImpl<TargetType, FromType, true> {
static MOZ_ALWAYS_INLINE TargetType Convert(FromType input) {
using UnsignedTargetType = std::make_unsigned_t<TargetType>;
auto resultUnsigned = static_cast<UnsignedTargetType>(input);
return ConvertUnsignedTargetTo<TargetType>::convert(resultUnsigned);
}
};
template <class TargetType, class FromType>
static MOZ_ALWAYS_INLINE TargetType Convert(FromType d) {
static_assert(
std::is_integral_v<FromType> != std::is_floating_point_v<FromType>,
"should only be converting from floating/integral type");
return ConvertImpl<TargetType, FromType>::Convert(d);
}
template <class TargetType, class FromType>
static MOZ_ALWAYS_INLINE bool IsAlwaysExact() {
// Return 'true' if TargetType can always exactly represent FromType.
// This means that:
// 1) TargetType must be the same or more bits wide as FromType. For integers
// represented in 'n' bits, unsigned variants will have 'n' digits while
// signed will have 'n - 1'. For floating point types, 'digits' is the
// mantissa width.
// 2) If FromType is signed, TargetType must also be signed. (Floating point
// types are always signed.)
// 3) If TargetType is an exact integral type, FromType must be also.
if (numeric_limits<TargetType>::digits < numeric_limits<FromType>::digits) {
return false;
}
if (numeric_limits<FromType>::is_signed &&
!numeric_limits<TargetType>::is_signed)
return false;
if (!numeric_limits<FromType>::is_exact &&
numeric_limits<TargetType>::is_exact)
return false;
return true;
}
// Templated helper to determine if FromType 'i' converts losslessly to
// TargetType 'j'. Default case where both types are the same signedness.
template <class TargetType, class FromType, bool TargetSigned, bool FromSigned>
struct IsExactImpl {
static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) {
static_assert(numeric_limits<TargetType>::is_exact);
return FromType(j) == i;
}
};
// Specialization where TargetType is unsigned, FromType is signed.
template <class TargetType, class FromType>
struct IsExactImpl<TargetType, FromType, false, true> {
static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) {
static_assert(numeric_limits<TargetType>::is_exact);
return i >= 0 && FromType(j) == i;
}
};
// Specialization where TargetType is signed, FromType is unsigned.
template <class TargetType, class FromType>
struct IsExactImpl<TargetType, FromType, true, false> {
static MOZ_ALWAYS_INLINE bool Test(FromType i, TargetType j) {
static_assert(numeric_limits<TargetType>::is_exact);
return TargetType(i) >= 0 && FromType(j) == i;
}
};
// Convert FromType 'i' to TargetType 'result', returning true iff 'result'
// is an exact representation of 'i'.
template <class TargetType, class FromType>
static MOZ_ALWAYS_INLINE bool ConvertExact(FromType i, TargetType* result) {
static_assert(std::numeric_limits<TargetType>::is_exact,
"TargetType must be exact to simplify conversion");
*result = Convert<TargetType>(i);
// See if we can avoid a dynamic check.
if (IsAlwaysExact<TargetType, FromType>()) {
return true;
}
// Return 'true' if 'i' is exactly representable in 'TargetType'.
return IsExactImpl<TargetType, FromType,
numeric_limits<TargetType>::is_signed,
numeric_limits<FromType>::is_signed>::Test(i, *result);
}
// Templated helper to determine if Type 'i' is negative. Default case
// where IntegerType is unsigned.
template <class Type, bool IsSigned>
struct IsNegativeImpl {
static MOZ_ALWAYS_INLINE bool Test(Type i) { return false; }
};
// Specialization where Type is signed.
template <class Type>
struct IsNegativeImpl<Type, true> {
static MOZ_ALWAYS_INLINE bool Test(Type i) { return i < 0; }
};
// Determine whether Type 'i' is negative.
template <class Type>
static MOZ_ALWAYS_INLINE bool IsNegative(Type i) {
return IsNegativeImpl<Type, numeric_limits<Type>::is_signed>::Test(i);
}
// Implicitly convert val to bool, allowing bool, int, and double
// arguments numerically equal to 0 or 1.
static bool jsvalToBool(JSContext* cx, HandleValue val, bool* result) {
if (val.isBoolean()) {
*result = val.toBoolean();
return true;
}
if (val.isInt32()) {
int32_t i = val.toInt32();
*result = i != 0;
return i == 0 || i == 1;
}
if (val.isDouble()) {
double d = val.toDouble();
*result = d != 0;
// Allow -0.
return d == 1 || d == 0;
}
// Don't silently convert null to bool. It's probably a mistake.
return false;
}
// Implicitly convert val to IntegerType, allowing bool, int, double,
// Int64, UInt64, and CData integer types 't' where all values of 't' are
// representable by IntegerType.
template <class IntegerType>
static bool jsvalToInteger(JSContext* cx, HandleValue val,
IntegerType* result) {
static_assert(numeric_limits<IntegerType>::is_exact);
if (val.isInt32()) {
// Make sure the integer fits in the alotted precision, and has the right
// sign.
int32_t i = val.toInt32();
return ConvertExact(i, result);
}
if (val.isDouble()) {
// Don't silently lose bits here -- check that val really is an
// integer value, and has the right sign.
double d = val.toDouble();
return ConvertExact(d, result);
}
if (val.isObject()) {
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<IntegerType, fromType>()) return false; \
*result = IntegerType(*static_cast<fromType*>(data)); \
return true;
CTYPES_FOR_EACH_INT_TYPE(INTEGER_CASE)
CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGER_CASE)
#undef INTEGER_CASE
case TYPE_void_t:
case TYPE_bool:
case TYPE_float:
case TYPE_double:
case TYPE_float32_t:
case TYPE_float64_t:
case TYPE_char:
case TYPE_signed_char:
case TYPE_unsigned_char:
case TYPE_char16_t:
case TYPE_pointer:
case TYPE_function:
case TYPE_array:
case TYPE_struct:
// Not a compatible number type.
return false;
}
}
if (Int64::IsInt64(obj)) {
// Make sure the integer fits in IntegerType.
int64_t i = Int64Base::GetInt(obj);
return ConvertExact(i, result);
}
if (UInt64::IsUInt64(obj)) {
// Make sure the integer fits in IntegerType.
uint64_t i = Int64Base::GetInt(obj);
return ConvertExact(i, result);
}
if (CDataFinalizer::IsCDataFinalizer(obj)) {
RootedValue innerData(cx);
if (!CDataFinalizer::GetValue(cx, obj, &innerData)) {
return false; // Nothing to convert
}
return jsvalToInteger(cx, innerData, result);
}
return false;
}
if (val.isBoolean()) {
// Implicitly promote boolean values to 0 or 1, like C.
*result = val.toBoolean();
MOZ_ASSERT(*result == 0 || *result == 1);
return true;
}
// Don't silently convert null to an integer. It's probably a mistake.
return false;
}
// Implicitly convert val to FloatType, allowing int, double,
// Int64, UInt64, and CData numeric types 't' where all values of 't' are
// representable by FloatType.
template <class FloatType>
static bool jsvalToFloat(JSContext* cx, HandleValue val, FloatType* result) {
static_assert(!numeric_limits<FloatType>::is_exact);
// The following casts may silently throw away some bits, but there's
// no good way around it. Sternly requiring that the 64-bit double
// argument be exactly representable as a 32-bit float is
// unrealistic: it would allow 1/2 to pass but not 1/3.
if (val.isInt32()) {
*result = FloatType(val.toInt32());
return true;
}
if (val.isDouble()) {
*result = FloatType(val.toDouble());
return true;
}
if (val.isObject()) {
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<FloatType, fromType>()) return false; \
*result = FloatType(*static_cast<fromType*>(data)); \
return true;
CTYPES_FOR_EACH_FLOAT_TYPE(NUMERIC_CASE)
CTYPES_FOR_EACH_INT_TYPE(NUMERIC_CASE)
CTYPES_FOR_EACH_WRAPPED_INT_TYPE(NUMERIC_CASE)
#undef NUMERIC_CASE
case TYPE_void_t:
case TYPE_bool:
case TYPE_char:
case TYPE_signed_char:
case TYPE_unsigned_char:
case TYPE_char16_t:
case TYPE_pointer:
case TYPE_function:
case TYPE_array:
case TYPE_struct:
// Not a compatible number type.
return false;
}
}
}
// Don't silently convert true to 1.0 or false to 0.0, even though C/C++
// does it. It's likely to be a mistake.
return false;
}
template <class IntegerType, class CharT>
static bool StringToInteger(JSContext* cx, CharT* cp, size_t length,
IntegerType* result, bool* overflow) {
static_assert(numeric_limits<IntegerType>::is_exact);
const CharT* end = cp + length;
if (cp == end) {
return false;
}
IntegerType sign = 1;
if (cp[0] == '-') {
if (!numeric_limits<IntegerType>::is_signed) {
return false;
}
sign = -1;
++cp;
}
// Assume base-10, unless the string begins with '0x' or '0X'.
IntegerType base = 10;
if (end - cp > 2 && cp[0] == '0' && (cp[1] == 'x' || cp[1] == 'X')) {
cp += 2;
base = 16;
}
// Scan the string left to right and build the number,
// checking for valid characters 0 - 9, a - f, A - F and overflow.
IntegerType i = 0;
while (cp != end) {
char16_t c = *cp++;
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 <class IntegerType>
static bool StringToInteger(JSContext* cx, JSString* string,
IntegerType* result, bool* overflow) {
JSLinearString* linear = string->ensureLinear(cx);
if (!linear) {
return false;
}
AutoCheckCannotGC nogc;
size_t length = linear->length();
return string->hasLatin1Chars()
? StringToInteger<IntegerType>(cx, linear->latin1Chars(nogc),
length, result, overflow)
: StringToInteger<IntegerType>(cx, linear->twoByteChars(nogc),
length, result, overflow);
}
// Implicitly convert val to IntegerType, allowing int, double,
// Int64, UInt64, and optionally a decimal or hexadecimal string argument.
// (This is common code shared by jsvalToSize and the Int64/UInt64
// constructors.)
template <class IntegerType>
static bool jsvalToBigInteger(JSContext* cx, HandleValue val, bool allowString,
IntegerType* result, bool* overflow) {
static_assert(numeric_limits<IntegerType>::is_exact);
if (val.isInt32()) {
// Make sure the integer fits in the alotted precision, and has the right
// sign.
int32_t i = val.toInt32();
return ConvertExact(i, result);
}
if (val.isDouble()) {
// Don't silently lose bits here -- check that val really is an
// integer value, and has the right sign.
double d = val.toDouble();
return ConvertExact(d, result);
}
if (allowString && val.isString()) {
// Allow conversion from base-10 or base-16 strings, provided the result
// fits in IntegerType. (This allows an Int64 or UInt64 object to be passed
// to the JS array element operator, which will automatically call
// toString() on the object for us.)
return StringToInteger(cx, val.toString(), result, overflow);
}
if (val.isObject()) {
// Allow conversion from an Int64 or UInt64 object directly.
JSObject* obj = &val.toObject();
if (UInt64::IsUInt64(obj)) {
// Make sure the integer fits in IntegerType.
uint64_t i = Int64Base::GetInt(obj);
return ConvertExact(i, result);
}
if (Int64::IsInt64(obj)) {
// Make sure the integer fits in IntegerType.
int64_t i = Int64Base::GetInt(obj);
return ConvertExact(i, result);
}
if (CDataFinalizer::IsCDataFinalizer(obj)) {
RootedValue innerData(cx);
if (!CDataFinalizer::GetValue(cx, obj, &innerData)) {
return false; // Nothing to convert
}
return jsvalToBigInteger(cx, innerData, allowString, result, overflow);
}
}
return false;
}
// Implicitly convert val to a size value, where the size value is represented
// by size_t but must also fit in a double.
static bool jsvalToSize(JSContext* cx, HandleValue val, bool allowString,
size_t* result) {
bool dummy;
if (!jsvalToBigInteger(cx, val, allowString, result, &dummy)) {
return false;
}
// Also check that the result fits in a double.
return Convert<size_t>(double(*result)) == *result;
}
// Implicitly convert val to IntegerType, allowing int, double,
// Int64, UInt64, and optionally a decimal or hexadecimal string argument.
// (This is common code shared by jsvalToSize and the Int64/UInt64
// constructors.)
template <class IntegerType>
static bool jsidToBigInteger(JSContext* cx, jsid val, bool allowString,
IntegerType* result) {
static_assert(numeric_limits<IntegerType>::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<size_t>(double(*result)) == *result;
}
// Implicitly convert a size value to a Value, ensuring that the size_t value
// fits in a double.
static bool SizeTojsval(JSContext* cx, size_t size, MutableHandleValue result) {
if (Convert<size_t>(double(size)) != size) {
return false;
}
result.setNumber(double(size));
return true;
}
// Forcefully convert val to IntegerType when explicitly requested.
template <class IntegerType>
static bool jsvalToIntegerExplicit(HandleValue val, IntegerType* result) {
static_assert(numeric_limits<IntegerType>::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<IntegerType>(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<intptr_t>(d);
if (double(i) != d) {
return false;
}
*result = uintptr_t(i);
return true;
}
// Don't silently lose bits here -- check that val really is an
// integer value, and has the right sign.
*result = Convert<uintptr_t>(d);
return double(*result) == d;
}
if (val.isObject()) {
JSObject* obj = &val.toObject();
if (Int64::IsInt64(obj)) {
int64_t i = Int64Base::GetInt(obj);
intptr_t p = intptr_t(i);
// Make sure the integer fits in the alotted precision.
if (int64_t(p) != i) {
return false;
}
*result = uintptr_t(p);
return true;
}
if (UInt64::IsUInt64(obj)) {
uint64_t i = Int64Base::GetInt(obj);
// Make sure the integer fits in the alotted precision.
*result = uintptr_t(i);
return uint64_t(*result) == i;
}
}
return false;
}
template <class IntegerType, class CharType, size_t N>
void IntegerToString(IntegerType i, int radix,
StringBuilder<CharType, N>& result) {
static_assert(numeric_limits<IntegerType>::is_exact);
// The buffer must be big enough for all the bits of IntegerType to fit,
// in base-2, including '-'.
CharType buffer[sizeof(IntegerType) * 8 + 1];
CharType* end = 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<bool*>(data));
break;
#define INT_CASE(name, type, ffiType) \
case TYPE_##name: { \
type value = *static_cast<type*>(data); \
if (sizeof(type) < 4) \
result.setInt32(int32_t(value)); \
else \
result.setDouble(double(value)); \
break; \
}
CTYPES_FOR_EACH_INT_TYPE(INT_CASE)
#undef INT_CASE
#define WRAPPED_INT_CASE(name, type, ffiType) \
case TYPE_##name: { \
/* Return an Int64 or UInt64 object - do not convert to a JS number. */ \
uint64_t value; \
RootedObject proto(cx); \
if (!numeric_limits<type>::is_signed) { \
value = *static_cast<type*>(data); \
/* Get ctypes.UInt64.prototype from ctypes.CType.prototype. */ \
proto = CType::GetProtoFromType(cx, typeObj, SLOT_UINT64PROTO); \
if (!proto) return false; \
} else { \
value = int64_t(*static_cast<type*>(data)); \
/* Get ctypes.Int64.prototype from ctypes.CType.prototype. */ \
proto = CType::GetProtoFromType(cx, typeObj, SLOT_INT64PROTO); \
if (!proto) return false; \
} \
\
JSObject* obj = Int64Base::Construct(cx, proto, value, \
!numeric_limits<type>::is_signed); \
if (!obj) return false; \
result.setObject(*obj); \
break; \
}
CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE)
#undef WRAPPED_INT_CASE
#define FLOAT_CASE(name, type, ffiType) \
case TYPE_##name: { \
type value = *static_cast<type*>(data); \
result.setDouble(double(value)); \
break; \
}
CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE)
#undef FLOAT_CASE
#define CHAR_CASE(name, type, ffiType) \
case TYPE_##name: \
/* Convert to an integer. We have no idea what character encoding to */ \
/* use, if any. */ \
result.setInt32(*static_cast<type*>(data)); \
break;
CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE)
#undef CHAR_CASE
case TYPE_char16_t: {
// Convert the char16_t to a 1-character string.
JSString* str = JS_NewUCStringCopyN(cx, static_cast<char16_t*>(data), 1);
if (!str) {
return false;
}
result.setString(str);
break;
}
case TYPE_pointer:
case TYPE_array:
case TYPE_struct: {
// We're about to create a new CData object to return. If the caller
// doesn't want this, return early.
if (wantPrimitive) {
return NonPrimitiveError(cx, typeObj);
}
JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult);
if (!obj) {
return false;
}
result.setObject(*obj);
break;
}
case TYPE_function:
MOZ_CRASH("cannot return a FunctionType");
}
return true;
}
// Determine if the contents of a typed array can be converted without
// ambiguity to a C type. Elements of a Int8Array are converted to
// ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc.
bool CanConvertTypedArrayItemTo(JSObject* baseType, JSObject* valObj,
JSContext* cx) {
TypeCode baseTypeCode = CType::GetTypeCode(baseType);
if (baseTypeCode == TYPE_void_t || baseTypeCode == TYPE_char) {
return true;
}
TypeCode elementTypeCode;
switch (JS_GetArrayBufferViewType(valObj)) {
case Scalar::Int8:
elementTypeCode = TYPE_int8_t;
break;
case Scalar::Uint8:
case Scalar::Uint8Clamped:
elementTypeCode = TYPE_uint8_t;
break;
case Scalar::Int16:
elementTypeCode = TYPE_int16_t;
break;
case Scalar::Uint16:
elementTypeCode = TYPE_uint16_t;
break;
case Scalar::Int32:
elementTypeCode = TYPE_int32_t;
break;
case Scalar::Uint32:
elementTypeCode = TYPE_uint32_t;
break;
case Scalar::Float32:
elementTypeCode = TYPE_float32_t;
break;
case Scalar::Float64:
elementTypeCode = TYPE_float64_t;
break;
default:
return false;
}
return elementTypeCode == baseTypeCode;
}
static CDataFinalizer::Private* GetFinalizerPrivate(JSObject* obj) {
MOZ_ASSERT(CDataFinalizer::IsCDataFinalizer(obj));
using T = CDataFinalizer::Private;
return JS::GetMaybePtrFromReservedSlot<T>(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<bool*>(buffer) = result;
break;
}
#define CHAR16_CASE(name, type, ffiType) \
case TYPE_##name: { \
/* Convert from a 1-character string, regardless of encoding, */ \
/* or from an integer, provided the result fits in 'type'. */ \
type result = 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<type*>(buffer) = result; \
break; \
}
CTYPES_FOR_EACH_CHAR16_TYPE(CHAR16_CASE)
#undef CHAR16_CASE
#define INTEGRAL_CASE(name, type, ffiType) \
case TYPE_##name: { \
/* Do not implicitly lose bits. */ \
type result; \
if (!jsvalToInteger(cx, val, &result)) \
return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \
arrIndex); \
*static_cast<type*>(buffer) = result; \
break; \
}
CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE)
// It's hard to believe ctypes.char16_t("f") should work yet
// ctypes.char("f") should not. Ditto for ctypes.{un,}signed_char. But
// this is how ctypes has always worked, so preserve these semantics, and
// don't switch to an algorithm similar to that in DEFINE_CHAR16_TYPE
// above, just yet.
CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE)
#undef INTEGRAL_CASE
#define FLOAT_CASE(name, type, ffiType) \
case TYPE_##name: { \
type result; \
if (!jsvalToFloat(cx, val, &result)) \
return ConvError(cx, #name, val, convType, funObj, argIndex, arrObj, \
arrIndex); \
*static_cast<type*>(buffer) = result; \
break; \
}
CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE)
#undef FLOAT_CASE
case TYPE_pointer: {
if (val.isNull()) {
// Convert to a null pointer.
*static_cast<void**>(buffer) = nullptr;
break;
}
JS::Rooted<JSObject*> baseType(cx, PointerType::GetBaseType(targetType));
if (sourceData) {
// First, determine if the targetType is ctypes.void_t.ptr.
TypeCode sourceCode = CType::GetTypeCode(sourceType);
void* sourceBuffer = CData::GetData(sourceData);
bool voidptrTarget = CType::GetTypeCode(baseType) == TYPE_void_t;
if (sourceCode == TYPE_pointer && voidptrTarget) {
// Autoconvert if targetType is ctypes.voidptr_t.
*static_cast<void**>(buffer) = *static_cast<void**>(sourceBuffer);
break;
}
if (sourceCode == TYPE_array) {
// Autoconvert an array to a ctypes.void_t.ptr or to
// sourceType.elementType.ptr, just like C.
JSObject* elementType = ArrayType::GetBaseType(sourceType);
if (voidptrTarget || CType::TypesEqual(baseType, elementType)) {
*static_cast<void**>(buffer) = sourceBuffer;
break;
}
}
} else if (convType == ConversionType::Argument && val.isString()) {
// Convert the string for the ffi call. This requires allocating space
// which the caller assumes ownership of.
// TODO: Extend this so we can safely convert strings at other times
// also.
JSString* sourceString = val.toString();
size_t sourceLength = sourceString->length();
Rooted<JSLinearString*> 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<char**>(buffer);
*charBuffer = cx->pod_malloc<char>(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<char16_t**>(buffer);
*char16Buffer = cx->pod_malloc<char16_t>(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)) {
// Immutable ArrayBuffers not yet supported.
if (JS::IsImmutableArrayBufferMaybeShared(valObj)) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
// Convert ArrayBuffer to pointer without any copy. This is only valid
// when converting an argument to a function call, as it is possible for
// the pointer to be invalidated by anything that runs JS code. (It is
// invalid to invoke JS code from a ctypes function call.)
if (convType != ConversionType::Argument) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
void* ptr;
{
JS::AutoCheckCannotGC nogc;
bool isShared;
ptr = JS::GetArrayBufferData(valObj, &isShared, nogc);
MOZ_ASSERT(!isShared); // Because ArrayBuffer
}
if (!ptr) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
*static_cast<void**>(buffer) = ptr;
break;
} else if (val.isObject() && JS::IsSharedArrayBufferObject(valObj)) {
// CTypes has not yet opted in to allowing shared memory pointers
// to escape. Exporting a pointer to the shared buffer without
// indicating sharedness would expose client code to races.
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
} else if (val.isObject() && JS_IsArrayBufferViewObject(valObj)) {
// Immutable ArrayBuffers not yet supported.
if (JS::IsImmutableArrayBufferView(valObj)) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
// Same as ArrayBuffer, above, though note that this will take the
// offset of the view into account.
if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
if (convType != ConversionType::Argument) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
void* ptr;
{
JS::AutoCheckCannotGC nogc;
bool isShared;
ptr = JS_GetArrayBufferViewData(valObj, &isShared, nogc);
if (isShared) {
// Opt out of shared memory, for now. Exporting a
// pointer to the shared buffer without indicating
// sharedness would expose client code to races.
ptr = nullptr;
}
}
if (!ptr) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
*static_cast<void**>(buffer) = ptr;
break;
}
return ConvError(cx, targetType, val, convType, funObj, argIndex, arrObj,
arrIndex);
}
case TYPE_array: {
MOZ_ASSERT(!funObj);
RootedObject baseType(cx, ArrayType::GetBaseType(targetType));
size_t targetLength = ArrayType::GetLength(targetType);
if (val.isString()) {
JSString* sourceString = val.toString();
size_t sourceLength = sourceString->length();
Rooted<JSLinearString*> 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<char*>(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<char16_t*>(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<char>(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<void*> target = SharedMem<void*>::unshared(buffer);
JS::AutoCheckCannotGC nogc;
bool isShared;
SharedMem<void*> src =
(bufferShared
? SharedMem<void*>::shared(
JS::GetSharedArrayBufferData(valObj, &isShared, nogc))
: SharedMem<void*>::unshared(
JS::GetArrayBufferData(valObj, &isShared, nogc)));
MOZ_ASSERT(isShared == bufferShared);
jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength);
break;
} else if (JS_IsTypedArrayObject(valObj)) {
// Check that array is consistent with type, then
// copy the array. It is OK to copy from shared to unshared
// or vice versa.
if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
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<void*> target = SharedMem<void*>::unshared(buffer);
JS::AutoCheckCannotGC nogc;
bool isShared;
SharedMem<void*> src = SharedMem<void*>::shared(
JS_GetArrayBufferViewData(valObj, &isShared, nogc));
jit::AtomicOperations::memcpySafeWhenRacy(target, src, sourceLength);
break;
} else {
// Don't implicitly convert to string. Users can implicitly convert
// with `String(x)` or `""+x`.
return ConvError(cx, targetType, val, convType, funObj, argIndex,
arrObj, arrIndex);
}
}
break;
}
case TYPE_struct: {
if (val.isObject() && !sourceData) {
// Enumerate the properties of the object; if they match the struct
// specification, convert the fields.
Rooted<IdVector> props(cx, IdVector(cx));
if (!JS_Enumerate(cx, valObj, &props)) {
return false;
}
// Convert into an intermediate, in case of failure.
size_t structSize = CType::GetSize(targetType);
auto intermediate = cx->make_pod_array<char>(structSize);
if (!intermediate) {
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<bool*>(buffer) = ToBoolean(val);
break;
}
#define INTEGRAL_CASE(name, type, ffiType) \
case TYPE_##name: { \
/* Convert numeric values with a C-style cast, and */ \
/* allow conversion from a base-10 or base-16 string. */ \
type result; \
bool overflow = false; \
if (!jsvalToIntegerExplicit(val, &result) && \
(!val.isString() || \
!StringToInteger(cx, val.toString(), &result, &overflow))) { \
if (overflow) { \
return TypeOverflow(cx, #name, val); \
} \
return ConvError(cx, #name, val, convType); \
} \
*static_cast<type*>(buffer) = result; \
break; \
}
CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE)
CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE)
CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE)
#undef INTEGRAL_CASE
case TYPE_pointer: {
// Convert a number, Int64 object, or UInt64 object to a pointer.
uintptr_t result;
if (!jsvalToPtrExplicit(cx, val, &result)) {
return ConvError(cx, targetType, val, convType);
}
*static_cast<uintptr_t*>(buffer) = result;
break;
}
case TYPE_float32_t:
case TYPE_float64_t:
case TYPE_float:
case TYPE_double:
case TYPE_array:
case TYPE_struct:
// ImplicitConvert is sufficient. Re-throw the exception it generated.
JS_SetPendingException(cx, ex);
return false;
case TYPE_void_t:
case TYPE_function:
MOZ_CRASH("invalid type");
}
return true;
}
// Given a CType 'typeObj', generate a string describing the C type declaration
// corresponding to 'typeObj'. For instance, the CType constructed from
// 'ctypes.int32_t.ptr.array(4).ptr.ptr' will result in the type string
// 'int32_t*(**)[4]'.
static JSString* BuildTypeName(JSContext* cx, JSObject* typeObj_) {
AutoString result;
RootedObject typeObj(cx, typeObj_);
// Walk the hierarchy of types, outermost to innermost, building up the type
// string. This consists of the base type, which goes on the left.
// Derived type modifiers (* and []) build from the inside outward, with
// pointers on the left and arrays on the right. An excellent description
// of the rules for building C type declarations can be found at:
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<const FieldInfoHash::Entry*, 64, SystemAllocPolicy> fieldsArray;
if (!fieldsArray.resize(length)) {
break;
}
for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
fieldsArray[r.front().value().mIndex] = &r.front();
}
for (size_t i = 0; i < length; ++i) {
const FieldInfoHash::Entry* entry = fieldsArray[i];
AppendString(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<bool*>(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<type*>(data), 10, result); \
break;
CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
#undef INTEGRAL_CASE
#define WRAPPED_INT_CASE(name, type, ffiType) \
case TYPE_##name: \
/* Serialize as a wrapped decimal integer. */ \
if (!numeric_limits<type>::is_signed) \
AppendString(cx, result, "ctypes.UInt64(\""); \
else \
AppendString(cx, result, "ctypes.Int64(\""); \
\
IntegerToString(*static_cast<type*>(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<type*>(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<type*>(data), 10, result); \
break;
CTYPES_FOR_EACH_CHAR_TYPE(CHAR_CASE)
#undef CHAR_CASE
case TYPE_char16_t: {
// Serialize as a 1-character JS string.
JSString* str = JS_NewUCStringCopyN(cx, static_cast<char16_t*>(data), 1);
if (!str) {
return false;
}
// Escape characters, and quote as necessary.
RootedValue valStr(cx, StringValue(str));
JSString* src = JS_ValueToSource(cx, valStr);
if (!src) {
return false;
}
AppendString(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<uintptr_t*>(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<char*>(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<const FieldInfoHash::Entry*, 64, SystemAllocPolicy> fieldsArray;
if (!fieldsArray.resize(length)) {
return false;
}
for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
fieldsArray[r.front().value().mIndex] = &r.front();
}
for (size_t i = 0; i < length; ++i) {
const FieldInfoHash::Entry* entry = fieldsArray[i];
if (isImplicit) {
AppendString(cx, result, "\"");
AppendString(cx, result, entry->key());
AppendString(cx, result, "\": ");
}
char* fieldData = static_cast<char*>(data) + entry->value().mOffset;
RootedObject entryType(cx, entry->value().mType);
if (!BuildDataSource(cx, entryType, fieldData, true, result)) {
return false;
}
if (i + 1 != length) {
AppendString(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<ffi_type*>(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<FunctionInfo*>(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<FieldInfoHash*>(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 =