From afa52d32f7c897512704ec120adbee7ec4c0fc7d Mon Sep 17 00:00:00 2001 From: Dan Witte Date: Thu, 11 Mar 2010 17:17:36 -0800 Subject: [PATCH] Revise js-facing API for js-ctypes, patch v1. b=513788, r=jorendorff --- js/ctypes/CTypes.cpp | 4284 ++++++++++++++++++++++ js/ctypes/CTypes.h | 277 ++ js/ctypes/Function.cpp | 527 +-- js/ctypes/Function.h | 52 +- js/ctypes/Library.cpp | 62 +- js/ctypes/Library.h | 13 +- js/ctypes/Makefile.in | 1 + js/ctypes/Module.cpp | 82 +- js/ctypes/Module.h | 24 - js/ctypes/tests/jsctypes-test.cpp | 243 +- js/ctypes/tests/jsctypes-test.h | 92 +- js/ctypes/tests/unit/test_jsctypes.js.in | 1549 ++++++-- js/ctypes/typedefs.h | 141 + js/ctypes/types.h | 72 - 14 files changed, 6427 insertions(+), 992 deletions(-) create mode 100644 js/ctypes/CTypes.cpp create mode 100644 js/ctypes/CTypes.h create mode 100644 js/ctypes/typedefs.h delete mode 100644 js/ctypes/types.h diff --git a/js/ctypes/CTypes.cpp b/js/ctypes/CTypes.cpp new file mode 100644 index 000000000000..7fc15a5224db --- /dev/null +++ b/js/ctypes/CTypes.cpp @@ -0,0 +1,4284 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is js-ctypes. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation . + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dan Witte + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "CTypes.h" +#include "nsAutoPtr.h" +#include "nsUTF8Utils.h" +#include "nsCRTGlue.h" +#include "prlog.h" +#include "prdtoa.h" +#include "jscntxt.h" +#include "jsnum.h" + +namespace mozilla { +namespace ctypes { + +/******************************************************************************* +** JSClass definitions and initialization functions +*******************************************************************************/ + +static JSClass sCABIClass = { + "CABI", + JSCLASS_HAS_RESERVED_SLOTS(CABI_SLOTS), + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +// Class representing ctypes.CType.prototype. +// This exists to provide three reserved slots for stashing +// ctypes.{Pointer,Array,Struct}Type.prototype. +static JSClass sCTypeProto = { + "CType", + JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS), + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static JSClass sCTypeClass = { + "CType", + JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS), + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, CType::Finalize, + NULL, NULL, CType::ConstructData, CType::ConstructData, NULL, NULL, NULL, NULL +}; + +static JSClass sCDataClass = { + "CData", + JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS), + JS_PropertyStub, JS_PropertyStub, ArrayType::Getter, ArrayType::Setter, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, CData::Finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#define CTYPESFN_FLAGS \ + (JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) + +static JSFunctionSpec sCTypeFunctions[] = { + JS_FN("array", CType::Array, 0, CTYPESFN_FLAGS), + JS_FN("toString", CType::ToString, 0, CTYPESFN_FLAGS), + JS_FN("toSource", CType::ToSource, 0, CTYPESFN_FLAGS), + JS_FS_END +}; + +static JSFunctionSpec sCDataFunctions[] = { + JS_FN("address", CData::Address, 0, CTYPESFN_FLAGS), + JS_FN("readString", CData::ReadString, 0, CTYPESFN_FLAGS), + JS_FN("toSource", CData::ToSource, 0, CTYPESFN_FLAGS), + JS_FN("toString", CData::ToSource, 0, CTYPESFN_FLAGS), + JS_FS_END +}; + +static JSFunctionSpec sPointerFunction = + JS_FN("PointerType", PointerType::Create, 1, CTYPESFN_FLAGS); + +static JSFunctionSpec sArrayFunction = + JS_FN("ArrayType", ArrayType::Create, 1, CTYPESFN_FLAGS); + +static JSFunctionSpec sStructFunction = + JS_FN("StructType", StructType::Create, 2, CTYPESFN_FLAGS); + +static JSFunctionSpec sArrayInstanceFunctions[] = { + JS_FN("addressOfElement", ArrayType::AddressOfElement, 1, CTYPESFN_FLAGS), + JS_FS_END +}; + +static JSFunctionSpec sStructInstanceFunctions[] = { + JS_FN("addressOfField", StructType::AddressOfField, 1, CTYPESFN_FLAGS), + JS_FS_END +}; + +static JSClass sInt64Proto = { + "Int64", + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSClass sUInt64Proto = { + "UInt64", + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSClass sInt64Class = { + "Int64", + JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS), + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Int64Base::Finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSClass sUInt64Class = { + "UInt64", + JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS), + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Int64Base::Finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static 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), + JS_FN("join", Int64::Join, 2, CTYPESFN_FLAGS), + JS_FS_END +}; + +static 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), + JS_FN("join", UInt64::Join, 2, CTYPESFN_FLAGS), + JS_FS_END +}; + +static JSFunctionSpec sInt64Functions[] = { + JS_FN("toString", Int64::ToString, 0, CTYPESFN_FLAGS), + JS_FN("toSource", Int64::ToSource, 0, CTYPESFN_FLAGS), + JS_FS_END +}; + +static JSFunctionSpec sUInt64Functions[] = { + JS_FN("toString", UInt64::ToString, 0, CTYPESFN_FLAGS), + JS_FN("toSource", UInt64::ToSource, 0, CTYPESFN_FLAGS), + JS_FS_END +}; + +ABICode +GetABICode(JSContext* cx, JSObject* obj) +{ + // make sure we have an object representing a CABI class, + // and extract the enumerated class type from the reserved slot. + if (JS_GET_CLASS(cx, obj) != &sCABIClass) + return INVALID_ABI; + + jsval result; + JS_GetReservedSlot(cx, obj, SLOT_ABICODE, &result); + + return ABICode(JSVAL_TO_INT(result)); +} + +JSErrorFormatString ErrorFormatString[CTYPESERR_LIMIT] = { +#define MSG_DEF(name, number, count, exception, format) \ + { format, count, exception } , +#include "ctypes.msg" +#undef MSG_DEF +}; + +const JSErrorFormatString* +GetErrorMessage(void* userRef, const char* locale, const uintN errorNumber) +{ + if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT) + return &ErrorFormatString[errorNumber]; + return NULL; +} + +static const char* +ToSource(JSContext* cx, jsval vp) +{ + JSString* str = JS_ValueToSource(cx, vp); + if (str) + return JS_GetStringBytesZ(cx, str); + + JS_ClearPendingException(cx); + return "<>"; +} + +bool +TypeError(JSContext* cx, const char* expected, jsval actual) +{ + const char* src = ToSource(cx, actual); + JS_ReportErrorNumber(cx, GetErrorMessage, NULL, + CTYPESMSG_TYPE_ERROR, expected, src); + return false; +} + +static bool +DefineABIConstant(JSContext* cx, + JSObject* parent, + const char* name, + ABICode code) +{ + JSObject* obj = JS_DefineObject(cx, parent, name, &sCABIClass, NULL, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + if (!obj) + return false; + if (!JS_SetReservedSlot(cx, obj, SLOT_ABICODE, INT_TO_JSVAL(code))) + return false; + return JS_SealObject(cx, obj, JS_FALSE) != JS_FALSE; +} + +static JSObject* +InitSpecialType(JSContext* cx, + JSObject* parent, + JSObject* CTypeProto, + JSFunctionSpec spec) +{ + JSFunction* fun = JS_DefineFunction(cx, parent, spec.name, spec.call, + spec.nargs, spec.flags); + if (!fun) + return NULL; + + JSObject* obj = JS_GetFunctionObject(fun); + if (!obj) + return NULL; + + // Set up the .prototype and .prototype.constructor properties. + JSObject* prototype = JS_NewObject(cx, NULL, CTypeProto, obj); + if (!prototype) + return NULL; + + if (!JS_DefineProperty(cx, obj, "prototype", OBJECT_TO_JSVAL(prototype), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + if (!JS_DefineProperty(cx, prototype, "constructor", OBJECT_TO_JSVAL(obj), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + if (!JS_SealObject(cx, obj, JS_FALSE) || + !JS_SealObject(cx, prototype, JS_FALSE)) + return NULL; + + return prototype; +} + +JSObject* +InitInt64Class(JSContext* cx, + JSObject* parent, + JSClass* clasp, + JSNative construct, + JSFunctionSpec* fs, + JSFunctionSpec* static_fs) +{ + // Init type class and constructor + JSObject* prototype = JS_InitClass(cx, parent, NULL, clasp, construct, + 0, NULL, fs, NULL, static_fs); + if (!prototype) + return NULL; + + // Set up ctypes.{Int64,UInt64}.prototype.__proto__ === Function.prototype. + // (This is the same as ctypes.{Int64,UInt64}.prototype.constructor.__proto__.) + JSObject* ctor = JS_GetConstructor(cx, prototype); + if (!ctor) + return NULL; + if (!JS_SealObject(cx, ctor, JS_FALSE)) + return NULL; + JSObject* proto = JS_GetPrototype(cx, ctor); + if (!proto) + return NULL; + if (!JS_SetPrototype(cx, prototype, proto)) + return NULL; + + // Stash ctypes.{Int64,UInt64}.prototype on a reserved slot of the 'join' + // function. + jsval join; + JS_GetProperty(cx, ctor, "join", &join); + if (!JS_SetReservedSlot(cx, JSVAL_TO_OBJECT(join), SLOT_FN_INT64PROTO, + OBJECT_TO_JSVAL(prototype))) + return NULL; + + if (!JS_SealObject(cx, prototype, JS_FALSE)) + return NULL; + + return prototype; +} + +bool +InitTypeClasses(JSContext* cx, JSObject* parent) +{ + // 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 === ctypes.CType.__proto__ + // * 'constructor' property === ctypes.CType + JSObject* CTypeProto = JS_InitClass(cx, parent, NULL, &sCTypeProto, + CType::ConstructAbstract, 0, NULL, sCTypeFunctions, NULL, NULL); + if (!CTypeProto) + return false; + + // Set up CTypeProto.__proto__ === Function.prototype. + // (This is the same as CTypeProto.constructor.__proto__.) + JSObject* ctor = JS_GetConstructor(cx, CTypeProto); + if (!ctor) + return false; + if (!JS_SealObject(cx, ctor, JS_FALSE)) + return false; + JSObject* proto = JS_GetPrototype(cx, ctor); + if (!proto) + return false; + if (!JS_SetPrototype(cx, CTypeProto, proto)) + return false; + if (!JS_SealObject(cx, CTypeProto, JS_FALSE)) + return false; + + // Attach objects representing ABI constants. + if (!DefineABIConstant(cx, parent, "default_abi", ABI_DEFAULT) || + !DefineABIConstant(cx, parent, "stdcall_abi", ABI_STDCALL)) + 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 [[Object]] + // * __proto__ === Object.prototype + // * 'constructor' property === 't' + JSObject* typeObj; +#define DEFINE_TYPE(name, type, ffiType) \ + typeObj = CType::DefineBuiltin(cx, parent, #name, CTypeProto, #name, \ + TYPE_##name, INT_TO_JSVAL(sizeof(type)), \ + INT_TO_JSVAL(ffiType.alignment), &ffiType); \ + if (!typeObj) \ + return false; +#include "typedefs.h" + + // Create and attach the special class constructors: + // ctypes.PointerType, ctypes.ArrayType, and ctypes.StructType. + // Each of these has, respectively: + // * Class [[Function]] + // * __proto__ === Function.prototype + // * A constructor that creates a user-defined type. + // * 'prototype' property: + // * Class [[Object]] + // * __proto__ === ctypes.CType.prototype + // * 'constructor' property === ctypes.{Pointer,Array,Struct}Type + JSObject* pointerProto = InitSpecialType(cx, parent, CTypeProto, sPointerFunction); + JSObject* arrayProto = InitSpecialType(cx, parent, CTypeProto, sArrayFunction); + JSObject* structProto = InitSpecialType(cx, parent, CTypeProto, sStructFunction); + + // 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}]] + // * __proto__ === Function.prototype === ctypes.{Int64,UInt64}.__proto__ + // * 'constructor' property === ctypes.{Int64,UInt64} + JSObject* Int64Proto = InitInt64Class(cx, parent, &sInt64Proto, + Int64::Construct, sInt64Functions, sInt64StaticFunctions); + if (!Int64Proto) + return false; + + JSObject* UInt64Proto = InitInt64Class(cx, parent, &sUInt64Proto, + UInt64::Construct, sUInt64Functions, sUInt64StaticFunctions); + if (!UInt64Proto) + return false; + + // Attach the five prototypes just created to ctypes.CType.prototype, + // so we can access them when constructing instances of those types. An + // instance 't' of a ctypes.{Pointer,Array,Struct}Type will have, resp.: + // * Class [[CType]] + // * __proto__ === ctypes.{Pointer,Array,Struct}Type.prototype + // * A constructor which creates and returns a CData object, containing + // binary data of the given type. + // * 'prototype' property: + // * Class [[Object]] + // * __proto__ === Object.prototype + // * 'constructor' property === t + if (!JS_SetReservedSlot(cx, CTypeProto, SLOT_POINTERPROTO, OBJECT_TO_JSVAL(pointerProto)) || + !JS_SetReservedSlot(cx, CTypeProto, SLOT_ARRAYPROTO, OBJECT_TO_JSVAL(arrayProto)) || + !JS_SetReservedSlot(cx, CTypeProto, SLOT_STRUCTPROTO, OBJECT_TO_JSVAL(structProto)) || + !JS_SetReservedSlot(cx, CTypeProto, SLOT_INT64PROTO, OBJECT_TO_JSVAL(Int64Proto)) || + !JS_SetReservedSlot(cx, CTypeProto, SLOT_UINT64PROTO, OBJECT_TO_JSVAL(UInt64Proto))) + return false; + + // Create objects representing the special types void_t and voidptr_t. + typeObj = CType::DefineBuiltin(cx, parent, "void_t", CTypeProto, "void", + TYPE_void_t, JSVAL_VOID, JSVAL_VOID, &ffi_type_void); + if (!typeObj) + return false; + + typeObj = PointerType::CreateInternal(cx, NULL, typeObj, NULL); + if (!typeObj) + return false; + if (!JS_DefineProperty(cx, parent, "voidptr_t", OBJECT_TO_JSVAL(typeObj), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return false; + + return true; +} + +/******************************************************************************* +** Type conversion functions +*******************************************************************************/ + +// Enforce some sanity checks on type widths. +// 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.) +PR_STATIC_ASSERT(sizeof(bool) == 1 || sizeof(bool) == 4); +PR_STATIC_ASSERT(sizeof(char) == 1); +PR_STATIC_ASSERT(sizeof(short) == 2); +PR_STATIC_ASSERT(sizeof(int) == 4); +PR_STATIC_ASSERT(sizeof(unsigned) == 4); +PR_STATIC_ASSERT(sizeof(long) == 4 || sizeof(long) == 8); +PR_STATIC_ASSERT(sizeof(long long) == 8); +PR_STATIC_ASSERT(sizeof(size_t) == sizeof(uintptr_t)); +PR_STATIC_ASSERT(sizeof(float) == 4); + +template +static JS_ALWAYS_INLINE IntegerType +Convert(jsdouble d) +{ + return IntegerType(d); +} + +#ifdef _MSC_VER +// MSVC can't perform double to unsigned __int64 conversion when the +// double is greater than 2^63 - 1. Help it along a little. +template<> +JS_ALWAYS_INLINE PRUint64 +Convert(jsdouble d) +{ + return d > 0x7fffffffffffffffui64 ? + PRUint64(d - 0x8000000000000000ui64) + 0x8000000000000000ui64 : + PRUint64(d); +} +#endif + +template static JS_ALWAYS_INLINE bool IsUnsigned() { return (Type(0) - Type(1)) > Type(0); } + +template static JS_ALWAYS_INLINE bool IsDoublePrecision(); +template<> JS_ALWAYS_INLINE bool IsDoublePrecision () { return false; } +template<> JS_ALWAYS_INLINE bool IsDoublePrecision() { return true; } + +// Implicitly convert val to bool, allowing JSBool, jsint, and jsdouble +// arguments numerically equal to 0 or 1. +static bool +jsvalToBool(JSContext* cx, jsval val, bool* result) +{ + if (JSVAL_IS_BOOLEAN(val)) { + *result = JSVAL_TO_BOOLEAN(val) != JS_FALSE; + return true; + } + if (JSVAL_IS_INT(val)) { + jsint i = JSVAL_TO_INT(val); + *result = i != 0; + return i == 0 || i == 1; + } + if (JSVAL_IS_DOUBLE(val)) { + jsdouble d = *JSVAL_TO_DOUBLE(val); + *result = d != 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 JSBool, jsint, jsdouble, +// Int64, UInt64, and CData integer types 't' where all values of 't' are +// representable by IntegerType. +template +static bool +jsvalToInteger(JSContext* cx, jsval val, IntegerType* result) +{ + if (JSVAL_IS_INT(val)) { + jsint i = JSVAL_TO_INT(val); + *result = IntegerType(i); + + // Make sure the integer fits in the alotted precision, and has the right + // sign. + if (IsUnsigned() && i < 0) + return false; + return jsint(*result) == i; + } + if (JSVAL_IS_DOUBLE(val)) { + jsdouble d = *JSVAL_TO_DOUBLE(val); + *result = Convert(d); + + // Don't silently lose bits here -- check that val really is an + // integer value, and has the right sign. + if (IsUnsigned() && d < 0) + return false; + return jsdouble(*result) == d; + } + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val)) { + JSObject* obj = JSVAL_TO_OBJECT(val); + if (CData::IsCData(cx, obj)) { + JSObject* typeObj = CData::GetCType(cx, obj); + void* data = CData::GetData(cx, 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(cx, typeObj)) { +#define DEFINE_INT_TYPE(name, fromType, ffiType) \ + case TYPE_##name: \ + if (IsUnsigned() && IsUnsigned() && \ + sizeof(IntegerType) < sizeof(fromType)) \ + return false; \ + if (!IsUnsigned() && !IsUnsigned() && \ + sizeof(IntegerType) < sizeof(fromType)) \ + return false; \ + if (IsUnsigned() && !IsUnsigned() && \ + sizeof(IntegerType) <= sizeof(fromType)) \ + return false; \ + if (!IsUnsigned() && IsUnsigned()) \ + return false; \ + *result = *static_cast(data); \ + break; +#define DEFINE_WRAPPED_INT_TYPE(x, y, z) DEFINE_INT_TYPE(x, y, z) +#include "typedefs.h" + 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_jschar: + case TYPE_pointer: + case TYPE_array: + case TYPE_struct: + // Not a compatible number type. + return false; + } + + return true; + } + + if (Int64::IsInt64(cx, obj)) { + PRInt64 i = Int64Base::GetInt(cx, obj); + *result = IntegerType(i); + + // Make sure the integer fits in IntegerType, and is nonnegative. + if (IsUnsigned() && i < 0) + return false; + return PRInt64(*result) == i; + } + + if (UInt64::IsUInt64(cx, obj)) { + PRUint64 i = Int64Base::GetInt(cx, obj); + *result = IntegerType(i); + + // Make sure the integer fits in IntegerType. + if (!IsUnsigned() && *result < 0) + return false; + return PRUint64(*result) == i; + } + + return false; + } + if (JSVAL_IS_BOOLEAN(val)) { + // Implicitly promote boolean values to 0 or 1, like C. + *result = JSVAL_TO_BOOLEAN(val); + JS_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 jsint, jsdouble, +// Int64, UInt64, and CData numeric types 't' where all values of 't' are +// representable by FloatType. +template +static bool +jsvalToFloat(JSContext *cx, jsval val, FloatType* result) +{ + // 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 (JSVAL_IS_INT(val)) { + *result = FloatType(JSVAL_TO_INT(val)); + return true; + } + if (JSVAL_IS_DOUBLE(val)) { + *result = FloatType(*JSVAL_TO_DOUBLE(val)); + return true; + } + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val)) { + JSObject* obj = JSVAL_TO_OBJECT(val); + if (CData::IsCData(cx, obj)) { + JSObject* typeObj = CData::GetCType(cx, obj); + void* data = CData::GetData(cx, 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(cx, typeObj)) { +#define DEFINE_FLOAT_TYPE(name, fromType, ffiType) \ + case TYPE_##name: \ + if (!IsDoublePrecision() && IsDoublePrecision()) \ + return false; \ + *result = *static_cast(data); \ + break; +#define DEFINE_INT_TYPE(name, fromType, ffiType) \ + case TYPE_##name: \ + if (sizeof(fromType) > 4) \ + return false; \ + if (sizeof(fromType) == 4 && !IsDoublePrecision()) \ + return false; \ + *result = *static_cast(data); \ + break; +#define DEFINE_WRAPPED_INT_TYPE(x, y, z) DEFINE_INT_TYPE(x, y, z) +#include "typedefs.h" + case TYPE_void_t: + case TYPE_bool: + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: + case TYPE_jschar: + case TYPE_pointer: + case TYPE_array: + case TYPE_struct: + // Not a compatible number type. + return false; + } + + return true; + } + + if (Int64::IsInt64(cx, obj)) { + PRInt64 i = Int64Base::GetInt(cx, obj); + *result = FloatType(i); + return true; + } + + if (UInt64::IsUInt64(cx, obj)) { + PRUint64 i = Int64Base::GetInt(cx, obj); + *result = FloatType(i); + return true; + } + } + // 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; +} + +// Implicitly convert val to IntegerType, allowing jsint, jsdouble, +// Int64, UInt64, and optionally a decimal or hexadecimal string argument. +// (This is used where a primitive is expected and we are converting to a +// size_t value or constructing an Int64 or UInt64 object, and thus it is +// implied that IntegerType is one of the wrapped integer types.) +template +static bool +jsvalToBigInteger(JSContext* cx, + jsval val, + bool allowString, + IntegerType* result) +{ + if (JSVAL_IS_INT(val)) { + jsint i = JSVAL_TO_INT(val); + *result = IntegerType(i); + + // Make sure the integer fits in the alotted precision, and has the right + // sign. + if (IsUnsigned() && i < 0) + return false; + return jsint(*result) == i; + } + if (JSVAL_IS_DOUBLE(val)) { + jsdouble d = *JSVAL_TO_DOUBLE(val); + *result = Convert(d); + + // Don't silently lose bits here -- check that val really is an + // integer value, and has the right sign. + if (IsUnsigned() && d < 0) + return false; + return jsdouble(*result) == d; + } + if (allowString && JSVAL_IS_STRING(val)) { + // Allow conversion from base-10 or base-16 strings, provided the result + // fits in IntegerType. (This allows an Int64 or UInt64 object to be passed + // to the JS array element operator, which will automatically call + // toString() on the object for us.) + return StringToInteger(cx, JSVAL_TO_STRING(val), result); + } + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val)) { + // Allow conversion from an Int64 or UInt64 object directly. + JSObject* obj = JSVAL_TO_OBJECT(val); + if (UInt64::IsUInt64(cx, obj)) { + PRUint64 i = Int64Base::GetInt(cx, obj); + *result = IntegerType(i); + + // Make sure the integer fits in IntegerType. + if (!IsUnsigned() && *result < 0) + return false; + return PRUint64(*result) == i; + } + + if (Int64::IsInt64(cx, obj)) { + PRInt64 i = Int64Base::GetInt(cx, obj); + *result = IntegerType(i); + + // Make sure the integer is nonnegative and fits in IntegerType. + if (IsUnsigned() && i < 0) + return false; + return PRInt64(*result) == i; + } + } + return false; +} + +// Implicitly convert val to a size value, where the size value is represented +// by size_t but must also fit in a jsdouble. +static bool +jsvalToSize(JSContext* cx, jsval val, bool allowString, size_t* result) +{ + if (!jsvalToBigInteger(cx, val, allowString, result)) + return false; + + // Also check that the result fits in a jsdouble. + return Convert(jsdouble(*result)) == *result; +} + +// Implicitly convert a size value to a jsval, ensuring that the size_t value +// fits in a jsdouble. +static bool +SizeTojsval(JSContext* cx, size_t size, jsval* result) +{ + if (Convert(jsdouble(size)) != size) { + JS_ReportError(cx, "size overflow"); + return false; + } + + return JS_NewNumberValue(cx, jsdouble(size), result) != JS_FALSE; +} + +// Forcefully convert val to IntegerType when explicitly requested. +template +static bool +jsvalToIntegerExplicit(JSContext* cx, jsval val, IntegerType* result) +{ + if (JSVAL_IS_DOUBLE(val)) { + // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast. + jsdouble d = *JSVAL_TO_DOUBLE(val); + *result = JSDOUBLE_IS_FINITE(d) ? IntegerType(d) : 0; + return true; + } + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val)) { + // Convert Int64 and UInt64 values by C-style cast. + JSObject* obj = JSVAL_TO_OBJECT(val); + if (Int64::IsInt64(cx, obj)) { + PRInt64 i = Int64Base::GetInt(cx, obj); + *result = IntegerType(i); + return true; + } + if (UInt64::IsUInt64(cx, obj)) { + PRUint64 i = Int64Base::GetInt(cx, obj); + *result = IntegerType(i); + return true; + } + } + return false; +} + +// Forcefully convert val to a pointer value when explicitly requested. +static bool +jsvalToPtrExplicit(JSContext* cx, jsval val, uintptr_t* result) +{ + if (JSVAL_IS_INT(val)) { + // jsint always fits in intptr_t. If the integer is negative, cast through + // an intptr_t intermediate to sign-extend. + jsint i = JSVAL_TO_INT(val); + *result = i < 0 ? uintptr_t(intptr_t(i)) : uintptr_t(i); + return true; + } + if (JSVAL_IS_DOUBLE(val)) { + jsdouble d = *JSVAL_TO_DOUBLE(val); + if (d < 0) { + // Cast through an intptr_t intermediate to sign-extend. + intptr_t i = Convert(d); + if (jsdouble(i) != d) + return false; + + *result = uintptr_t(i); + return true; + } + + // Don't silently lose bits here -- check that val really is an + // integer value, and has the right sign. + *result = Convert(d); + return jsdouble(*result) == d; + } + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val)) { + JSObject* obj = JSVAL_TO_OBJECT(val); + if (Int64::IsInt64(cx, obj)) { + PRInt64 i = Int64Base::GetInt(cx, obj); + if (i < 0) { + // Cast through an intptr_t intermediate to sign-extend. + if (PRInt64(intptr_t(i)) != i) + return false; + + *result = uintptr_t(intptr_t(i)); + return true; + } + + // Make sure the integer fits in the alotted precision. + *result = uintptr_t(i); + return PRInt64(*result) == i; + } + + if (UInt64::IsUInt64(cx, obj)) { + PRUint64 i = Int64Base::GetInt(cx, obj); + + // Make sure the integer fits in the alotted precision. + *result = uintptr_t(i); + return PRUint64(*result) == i; + } + } + return false; +} + +template +nsCAutoString +IntegerToString(IntegerType i, jsuint radix) +{ + // The buffer must be big enough for all the bits of IntegerType to fit, + // in base-2, including '-'. + char buffer[sizeof(IntegerType) * 8 + 1]; + char *cp = buffer + sizeof(buffer); + + // Build the string in reverse. We use multiplication and subtraction + // instead of modulus because that's much faster. + bool isNegative = !IsUnsigned() && i < 0; + 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 = '-'; + + JS_ASSERT(cp >= buffer); + return nsCAutoString(cp, buffer + sizeof(buffer) - cp); +} + +template +static bool +StringToInteger(JSContext* cx, JSString* string, IntegerType* result) +{ + const char* cp = JS_GetStringBytesZ(cx, string); + if (!cp) + return false; + + const char* end = cp + JS_GetStringLength(string); + if (cp == end) + return false; + + IntegerType sign = 1; + if (cp[0] == '-') { + if (IsUnsigned()) + 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) { + unsigned char c = *cp++; + if (c >= '0' && c <= '9') + c -= '0'; + else if (base == 16 && c >= 'a' && c <= 'f') + c = c - 'a' + 10; + else if (base == 16 && c >= 'A' && c <= 'F') + c = c - 'A' + 10; + else + return false; + + IntegerType ii = i; + i = ii * base + sign * c; + if (i / base != ii) // overflow + return false; + } + + *result = i; + return true; +} + +static bool +IsUTF16(const jschar* string, size_t length) +{ + PRBool error; + const PRUnichar* buffer = reinterpret_cast(string); + const PRUnichar* end = buffer + length; + while (buffer != end) { + UTF16CharEnumerator::NextChar(&buffer, end, &error); + if (error) + return false; + } + + return true; +} + +template +static size_t +strnlen(const CharType* begin, size_t max) +{ + for (const CharType* s = begin; s != begin + max; ++s) + if (*s == 0) + return s - begin; + + return max; +} + +// Convert C binary value 'data' of CType 'typeObj' to a JS primitive, where +// possible; otherwise, construct and return a CData object. The following +// semantics apply when constructing a CData object for return: +// * If 'wantPrimitive' is true, the caller indicates that 'result' must be +// a JS primitive, and ConvertToJS will fail if 'result' would be a CData +// object. Otherwise: +// * If a CData object 'parentObj' is supplied, the new CData object is +// dependent on the given parent and its buffer refers to the parent's buffer. +// * If 'parentObj' is null, the new CData object will make an owning copy of +// 'data'. +bool +ConvertToJS(JSContext* cx, + JSObject* typeObj, + JSObject* parentObj, + void* data, + bool wantPrimitive, + jsval* result) +{ + JS_ASSERT(!parentObj || CData::IsCData(cx, parentObj)); + + TypeCode typeCode = CType::GetTypeCode(cx, typeObj); + + switch (typeCode) { + case TYPE_void_t: + *result = JSVAL_VOID; + break; + case TYPE_bool: + *result = *static_cast(data) ? JSVAL_TRUE : JSVAL_FALSE; + break; +#define DEFINE_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + type value = *static_cast(data); \ + if (sizeof(type) < 4) \ + *result = INT_TO_JSVAL(jsint(value)); \ + else if (!JS_NewNumberValue(cx, jsdouble(value), result)) \ + return false; \ + break; \ + } +#define DEFINE_WRAPPED_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + /* Return an Int64 or UInt64 object - do not convert to a JS number. */ \ + PRUint64 value; \ + JSObject* proto; \ + if (IsUnsigned()) { \ + value = *static_cast(data); \ + /* Get ctypes.UInt64.prototype from ctypes.CType.prototype. */ \ + proto = CType::GetProtoFromType(cx, typeObj, SLOT_UINT64PROTO); \ + } else { \ + value = PRInt64(*static_cast(data)); \ + /* Get ctypes.Int64.prototype from ctypes.CType.prototype. */ \ + proto = CType::GetProtoFromType(cx, typeObj, SLOT_INT64PROTO); \ + } \ + \ + JSObject* obj = Int64Base::Construct(cx, proto, value, IsUnsigned());\ + if (!obj) \ + return false; \ + *result = OBJECT_TO_JSVAL(obj); \ + break; \ + } +#define DEFINE_FLOAT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + type value = *static_cast(data); \ + if (!JS_NewNumberValue(cx, jsdouble(value), result)) \ + return false; \ + break; \ + } +#include "typedefs.h" + case TYPE_jschar: { + // Convert the jschar to a 1-character string. + JSString* str = JS_NewUCStringCopyN(cx, static_cast(data), 1); + if (!str) + return false; + + *result = STRING_TO_JSVAL(str); + break; + } + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Promote the unsigned 1-byte character type to a jschar, regardless of + // encoding, and convert to a 1-character string. + // TODO: Check IsASCII(data)? + jschar promoted = *static_cast(data); + JSString* str = JS_NewUCStringCopyN(cx, &promoted, 1); + if (!str) + return false; + + *result = STRING_TO_JSVAL(str); + break; + } + case TYPE_pointer: { + // We're about to create a new CData object to return. If the caller doesn't + // want this, return early. + if (wantPrimitive) { + JS_ReportError(cx, "cannot convert to primitive value"); + return false; + } + + JSObject* obj = PointerType::ConstructInternal(cx, typeObj, parentObj, data); + if (!obj) + return false; + + *result = OBJECT_TO_JSVAL(obj); + break; + } + case TYPE_array: { + // We're about to create a new CData object to return. If the caller doesn't + // want this, return early. + if (wantPrimitive) { + JS_ReportError(cx, "cannot convert to primitive value"); + return false; + } + + JSObject* obj = ArrayType::ConstructInternal(cx, typeObj, parentObj, data); + if (!obj) + return false; + + *result = OBJECT_TO_JSVAL(obj); + break; + } + 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) { + JS_ReportError(cx, "cannot convert to primitive value"); + return false; + } + + JSObject* obj = StructType::ConstructInternal(cx, typeObj, parentObj, data); + if (!obj) + return false; + + *result = OBJECT_TO_JSVAL(obj); + break; + } + } + + return true; +} + +// Implicitly convert jsval '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 'isArgument' +// will be true. 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. +bool +ImplicitConvert(JSContext* cx, + jsval val, + JSObject* targetType, + void* buffer, + bool isArgument, + bool* freePointer) +{ + JS_ASSERT(CType::IsSizeDefined(cx, targetType)); + + // First, check if val is a CData object of type targetType. + JSObject* sourceData = NULL; + JSObject* sourceType = NULL; + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && + CData::IsCData(cx, JSVAL_TO_OBJECT(val))) { + sourceData = JSVAL_TO_OBJECT(val); + sourceType = CData::GetCType(cx, 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(cx, sourceType, targetType)) { + size_t size = CType::GetSize(cx, sourceType); + memmove(buffer, CData::GetData(cx, sourceData), size); + return true; + } + } + + TypeCode targetCode = CType::GetTypeCode(cx, 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 TypeError(cx, "boolean", val); + *static_cast(buffer) = result; + break; + } +#define DEFINE_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + /* Do not implicitly lose bits. */ \ + type result; \ + if (!jsvalToInteger(cx, val, &result)) \ + return TypeError(cx, #name, val); \ + *static_cast(buffer) = result; \ + break; \ + } +#define DEFINE_WRAPPED_INT_TYPE(x, y, z) DEFINE_INT_TYPE(x, y, z) +#define DEFINE_FLOAT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + type result; \ + if (!jsvalToFloat(cx, val, &result)) \ + return TypeError(cx, #name, val); \ + *static_cast(buffer) = result; \ + break; \ + } +#define DEFINE_CHAR_TYPE(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'. */ \ + /* TODO: Check IsASCII(chars) for 8-bit char types? */ \ + type result; \ + if (JSVAL_IS_STRING(val)) { \ + JSString* str = JSVAL_TO_STRING(val); \ + if (JS_GetStringLength(str) != 1) \ + return TypeError(cx, #name, val); \ + \ + jschar c = *JS_GetStringCharsZ(cx, str); \ + result = c; \ + if (jschar(result) != c) \ + return TypeError(cx, #name, val); \ + \ + } else if (!jsvalToInteger(cx, val, &result)) { \ + return TypeError(cx, #name, val); \ + } \ + *static_cast(buffer) = result; \ + break; \ + } +#include "typedefs.h" + case TYPE_pointer: { + JSObject* baseType = PointerType::GetBaseType(cx, targetType); + + if (JSVAL_IS_NULL(val)) { + // Convert to a null pointer. + *static_cast(buffer) = NULL; + break; + + } else if (sourceData) { + // First, determine if the targetType is ctypes.void_t.ptr. + TypeCode sourceCode = CType::GetTypeCode(cx, sourceType); + void* sourceBuffer = CData::GetData(cx, sourceData); + bool voidptrTarget = baseType && + CType::GetTypeCode(cx, baseType) == TYPE_void_t; + + if (sourceCode == TYPE_pointer && voidptrTarget) { + // Autoconvert if targetType is ctypes.voidptr_t. + *static_cast(buffer) = *static_cast(sourceBuffer); + break; + } + if (sourceCode == TYPE_array) { + // Autoconvert an array to a ctypes.void_t.ptr or to + // sourceType.elementType.ptr, just like C. + JSObject* elementType = ArrayType::GetBaseType(cx, sourceType); + if (voidptrTarget || CType::TypesEqual(cx, baseType, elementType)) { + *static_cast(buffer) = sourceBuffer; + break; + } + } + + } else if (isArgument && baseType && JSVAL_IS_STRING(val)) { + // 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 = JSVAL_TO_STRING(val); + const jschar* sourceChars = JS_GetStringCharsZ(cx, sourceString); + size_t sourceLength = JS_GetStringLength(sourceString); + + switch (CType::GetTypeCode(cx, baseType)) { + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Convert from UTF-16 to UTF-8. + if (!IsUTF16(sourceChars, sourceLength)) + return TypeError(cx, "UTF-16 string", val); + + NS_ConvertUTF16toUTF8 converted( + reinterpret_cast(sourceChars), sourceLength); + + char** charBuffer = static_cast(buffer); + *charBuffer = new char[converted.Length() + 1]; + if (!*charBuffer) { + JS_ReportAllocationOverflow(cx); + return false; + } + + *freePointer = true; + memcpy(*charBuffer, converted.get(), converted.Length() + 1); + break; + } + case TYPE_jschar: { + // Copy the jschar 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.) + jschar** jscharBuffer = static_cast(buffer); + *jscharBuffer = new jschar[sourceLength + 1]; + if (!*jscharBuffer) { + JS_ReportAllocationOverflow(cx); + return false; + } + + *freePointer = true; + memcpy(*jscharBuffer, sourceChars, (sourceLength + 1) * sizeof(jschar)); + break; + } + default: + return TypeError(cx, "pointer", val); + } + break; + } + return TypeError(cx, "pointer", val); + } + case TYPE_array: { + JSObject* baseType = ArrayType::GetBaseType(cx, targetType); + size_t targetLength = ArrayType::GetLength(cx, targetType); + + if (JSVAL_IS_STRING(val)) { + JSString* sourceString = JSVAL_TO_STRING(val); + const jschar* sourceChars = JS_GetStringCharsZ(cx, sourceString); + size_t sourceLength = JS_GetStringLength(sourceString); + + switch (CType::GetTypeCode(cx, baseType)) { + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Convert from UTF-16 to UTF-8. + if (!IsUTF16(sourceChars, sourceLength)) + return TypeError(cx, "UTF-16 string", val); + + NS_ConvertUTF16toUTF8 converted( + reinterpret_cast(sourceChars), sourceLength); + + if (targetLength < converted.Length()) { + JS_ReportError(cx, "ArrayType has insufficient length"); + return false; + } + + memcpy(buffer, converted.get(), converted.Length()); + if (targetLength > converted.Length()) + static_cast(buffer)[converted.Length()] = 0; + + break; + } + case TYPE_jschar: { + // Copy the string data, jschar for jschar, including the terminator + // if there's space. + if (targetLength < sourceLength) { + JS_ReportError(cx, "ArrayType has insufficient length"); + return false; + } + + memcpy(buffer, sourceChars, sourceLength * sizeof(jschar)); + if (targetLength > sourceLength) + static_cast(buffer)[sourceLength] = 0; + + break; + } + default: + return TypeError(cx, "array", val); + } + + } else if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && + JS_IsArrayObject(cx, JSVAL_TO_OBJECT(val))) { + // Convert each element of the array by calling ImplicitConvert. + JSObject* sourceArray = JSVAL_TO_OBJECT(val); + jsuint sourceLength; + if (!JS_GetArrayLength(cx, sourceArray, &sourceLength) || + targetLength != size_t(sourceLength)) { + JS_ReportError(cx, "ArrayType length does not match source array length"); + return false; + } + + // Convert into an intermediate, in case of failure. + size_t elementSize = CType::GetSize(cx, baseType); + size_t arraySize = elementSize * targetLength; + nsAutoPtr intermediate(new char[arraySize]); + if (!intermediate) { + JS_ReportAllocationOverflow(cx); + return false; + } + + for (jsuint i = 0; i < sourceLength; ++i) { + jsval item; + if (!JS_GetElement(cx, sourceArray, i, &item)) + return false; + + char* data = intermediate + elementSize * i; + if (!ImplicitConvert(cx, item, baseType, data, false, NULL)) + return false; + } + + memcpy(buffer, intermediate, arraySize); + + } else { + // Don't implicitly convert to string. Users can implicitly convert + // with `String(x)` or `""+x`. + return TypeError(cx, "array", val); + } + break; + } + case TYPE_struct: { + if (JSVAL_IS_OBJECT(val) && !JSVAL_IS_NULL(val) && !sourceData) { + nsTArray* fields = StructType::GetFieldInfo(cx, targetType); + + // Enumerate the properties of the object; if they match the struct + // specification, convert the fields. + JSObject* obj = JSVAL_TO_OBJECT(val); + JSObject* iter = JS_NewPropertyIterator(cx, obj); + if (!iter) + return false; + JSAutoTempValueRooter iterroot(cx, iter); + + // Convert into an intermediate, in case of failure. + size_t structSize = CType::GetSize(cx, targetType); + nsAutoPtr intermediate(new char[structSize]); + if (!intermediate) { + JS_ReportAllocationOverflow(cx); + return false; + } + + jsid id; + jsuint i = 0; + while (JS_NextProperty(cx, iter, &id) && !JSVAL_IS_VOID(id)) { + jsval fieldVal; + if (!JS_IdToValue(cx, id, &fieldVal) || !JSVAL_IS_STRING(fieldVal)) { + JS_ReportError(cx, "property name is not a string"); + return false; + } + JSAutoTempValueRooter nameroot(cx, fieldVal); + + const char* name = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(fieldVal)); + FieldInfo* field = StructType::LookupField(cx, targetType, fieldVal); + if (!field) { + JS_ReportError(cx, "couldn't locate field %s", name); + return false; + } + + jsval prop; + if (!JS_GetProperty(cx, obj, name, &prop)) + return false; + + // Convert the field via ImplicitConvert(). + char* fieldData = intermediate + field->mOffset; + if (!ImplicitConvert(cx, prop, field->mType, fieldData, false, NULL)) + return false; + + ++i; + } + + if (i != fields->Length()) { + JS_ReportError(cx, "missing fields"); + return false; + } + + memcpy(buffer, intermediate, structSize); + break; + } + + return TypeError(cx, "struct", val); + } + case TYPE_void_t: + JS_NOT_REACHED("invalid type"); + return false; + } + + return true; +} + +// Convert jsval 'val' to a C binary representation of CType 'targetType', +// storing the result in 'buffer'. This function is more forceful than +// ImplicitConvert. +bool +ExplicitConvert(JSContext* cx, jsval val, JSObject* targetType, void* buffer) +{ + // If ImplicitConvert succeeds, use that result. + if (ImplicitConvert(cx, val, targetType, buffer, false, NULL)) + 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. + jsval ex; + 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. + JSAutoTempValueRooter root(cx, ex); + JS_ClearPendingException(cx); + + TypeCode type = CType::GetTypeCode(cx, targetType); + + switch (type) { + case TYPE_bool: { + // Convert according to the ECMAScript ToBoolean() function. + JSBool result; + JS_ValueToBoolean(cx, val, &result); + *static_cast(buffer) = result != JS_FALSE; + break; + } +#define DEFINE_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + /* Convert numeric values with a C-style cast. */ \ + type result; \ + if (!jsvalToIntegerExplicit(cx, val, &result)) \ + return TypeError(cx, #name, val); \ + *static_cast(buffer) = result; \ + break; \ + } +#define DEFINE_CHAR_TYPE(x, y, z) DEFINE_INT_TYPE(x, y, z) +#define DEFINE_WRAPPED_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + /* Convert numeric values with a C-style cast, and */ \ + /* allow conversion from a base-10 or base-16 string. */ \ + type result; \ + if (!jsvalToIntegerExplicit(cx, val, &result) && \ + (!JSVAL_IS_STRING(val) || \ + !StringToInteger(cx, JSVAL_TO_STRING(val), &result))) \ + return TypeError(cx, #name, val); \ + *static_cast(buffer) = result; \ + break; \ + } +#include "typedefs.h" + case TYPE_pointer: { + // Convert a number, Int64 object, or UInt64 object to a pointer. + uintptr_t result; + if (!jsvalToPtrExplicit(cx, val, &result)) + return TypeError(cx, "pointer", val); + *static_cast(buffer) = result; + break; + } + case TYPE_float32_t: + case TYPE_float64_t: + case TYPE_float: + case TYPE_double: + case TYPE_array: + case TYPE_struct: + // ImplicitConvert is sufficient. Re-throw the exception it generated. + JS_SetPendingException(cx, ex); + return false; + case TYPE_void_t: + JS_NOT_REACHED("invalid type"); + return false; + } + 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 nsCAutoString +BuildTypeName(JSContext* cx, JSObject* typeObj) +{ + // Walk the hierarchy of types, outermost to innermost, building up the type + // string. This consists of the base type, which goes on the left. + // Derived type modifiers (* and []) build from the inside outward, with + // pointers on the left and arrays on the right. An excellent description + // of the rules for building C type declarations can be found at: + // http://unixwiz.net/techtips/reading-cdecl.html + nsCAutoString result; + JSObject* currentType = typeObj; + JSObject* nextType; + TypeCode prevGrouping = CType::GetTypeCode(cx, currentType), currentGrouping; + while (1) { + currentGrouping = CType::GetTypeCode(cx, currentType); + switch (currentGrouping) { + case TYPE_pointer: { + nextType = PointerType::GetBaseType(cx, currentType); + if (!nextType) { + // Opaque pointer type. Use the type's name as the base type. + break; + } + + // Pointer types go on the left. + result.Insert('*', 0); + + currentType = nextType; + prevGrouping = currentGrouping; + continue; + } + case TYPE_array: { + if (prevGrouping == TYPE_pointer) { + // Outer type is pointer, inner type is array. Grouping is required. + result.Insert('(', 0); + result.Append(')'); + } + + // Array types go on the right. + result.Append('['); + size_t length; + if (ArrayType::GetSafeLength(cx, currentType, &length)) { + result.Append(IntegerToString(length, 10)); + } + result.Append(']'); + + currentType = ArrayType::GetBaseType(cx, currentType); + prevGrouping = currentGrouping; + continue; + } + default: + // Either a basic or struct type. Use the type's name as the base type. + break; + } + break; + } + + // Stick the base type and derived type parts together. + JSString* baseName = CType::GetName(cx, currentType); + result.Insert(JS_GetStringBytesZ(cx, baseName), 0); + return result; +} + +// Given a CType 'typeObj', generate a string 'result' such that 'eval(result)' +// would construct the same CType. If 'makeShort' is true, assume that any +// StructType 't' is bound to an in-scope variable of name 't.name', and use +// that variable in place of generating a string to construct the type 't'. +// (This means the type comparison function CType::TypesEqual will return true +// when comparing the input and output of BuildTypeSource, since struct +// equality is determined by strict JSObject pointer equality.) +static nsCAutoString +BuildTypeSource(JSContext* cx, JSObject* typeObj, bool makeShort) +{ + // Walk the types, building up the toSource() string. + nsCAutoString result; + switch (CType::GetTypeCode(cx, typeObj)) { + case TYPE_void_t: + result.Append("ctypes.void_t"); + break; +#define DEFINE_TYPE(name, type, ffiType) \ + case TYPE_##name: \ + result.Append("ctypes." #name); \ + break; +#include "typedefs.h" + case TYPE_pointer: { + JSObject* baseType = PointerType::GetBaseType(cx, typeObj); + if (!baseType) { + // Opaque pointer type. Use the type's name. + JSString* baseName = CType::GetName(cx, typeObj); + result.Append("ctypes.PointerType(\""); + result.Append(JS_GetStringBytesZ(cx, baseName)); + result.Append('"'); + break; + } + + // Specialcase ctypes.voidptr_t. + if (CType::GetTypeCode(cx, baseType) == TYPE_void_t) { + result.Append("ctypes.voidptr_t"); + break; + } + + // Recursively build the source string, and append '.ptr'. + result.Append(BuildTypeSource(cx, baseType, makeShort)); + result.Append(".ptr"); + 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(cx, typeObj); + result.Append(BuildTypeSource(cx, baseType, makeShort)); + result.Append(".array("); + + size_t length; + if (ArrayType::GetSafeLength(cx, typeObj, &length)) + result.Append(IntegerToString(length, 10)); + + result.Append(')'); + 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'. + result.Append(JS_GetStringBytesZ(cx, name)); + break; + } + + // Write the full struct declaration. + result.Append("ctypes.StructType(\""); + result.Append(JS_GetStringBytesZ(cx, name)); + result.Append("\", ["); + + nsTArray* fields = StructType::GetFieldInfo(cx, typeObj); + for (PRUint32 i = 0; i < fields->Length(); ++i) { + const FieldInfo& field = fields->ElementAt(i); + result.Append("{ \""); + result.Append(field.mName); + result.Append("\": "); + result.Append(BuildTypeSource(cx, field.mType, makeShort)); + result.Append(" }"); + if (i != fields->Length() - 1) + result.Append(", "); + } + + result.Append("])"); + break; + } + } + + return result; +} + +// Given a CData object of CType 'typeObj' with binary value 'data', generate a +// string 'result' such that 'eval(result)' would construct a CData object with +// the same CType and containing the same binary value. This assumes that any +// StructType 't' is bound to an in-scope variable of name 't.name'. (This means +// the type comparison function CType::TypesEqual will return true when +// comparing the types, since struct equality is determined by strict JSObject +// pointer equality.) Further, if 'isImplicit' is true, ensure that the +// resulting string can ImplicitConvert successfully if passed to another data +// constructor. (This is important when called recursively, since fields of +// structs and arrays are converted with ImplicitConvert.) +static nsCAutoString +BuildDataSource(JSContext* cx, JSObject* typeObj, void* data, bool isImplicit) +{ + nsCAutoString result; + TypeCode type = CType::GetTypeCode(cx, typeObj); + switch (type) { + case TYPE_bool: + result.Append(*static_cast(data) ? "true" : "false"); + break; +#define DEFINE_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: \ + /* Serialize as a primitive decimal integer. */ \ + result.Append(IntegerToString(*static_cast(data), 10)); \ + break; +#define DEFINE_WRAPPED_INT_TYPE(name, type, ffiType) \ + case TYPE_##name: \ + /* Serialize as a wrapped decimal integer. */ \ + if (IsUnsigned()) \ + result.Append("ctypes.UInt64(\""); \ + else \ + result.Append("ctypes.Int64(\""); \ + \ + result.Append(IntegerToString(*static_cast(data), 10)); \ + result.Append("\")"); \ + break; +#define DEFINE_FLOAT_TYPE(name, type, ffiType) \ + case TYPE_##name: { \ + /* Serialize as a primitive double. */ \ + PRFloat64 fp = *static_cast(data); \ + PRIntn decpt, sign; \ + char buf[128]; \ + PR_dtoa(fp, 0, 0, &decpt, &sign, NULL, buf, sizeof(buf)); \ + result.Append(buf); \ + break; \ + } +#define DEFINE_CHAR_TYPE(name, type, ffiType) \ + case TYPE_##name: \ + /* Serialize as an integer. */ \ + result.Append(IntegerToString(*static_cast(data), 10)); \ + break; +#include "typedefs.h" + case TYPE_pointer: { + if (isImplicit) { + // The result must be able to ImplicitConvert successfully. + // Wrap in a type constructor, then serialize for ExplicitConvert. + result.Append(BuildTypeSource(cx, typeObj, true)); + result.Append('('); + } + + // Serialize the pointer value as a wrapped hexadecimal integer. + uintptr_t ptr = *static_cast(data); + result.Append("ctypes.UInt64(\"0x"); + result.Append(IntegerToString(ptr, 16)); + result.Append("\")"); + + if (isImplicit) + result.Append(')'); + + break; + } + case TYPE_array: { + // Serialize each element of the array recursively. Each element must + // be able to ImplicitConvert successfully. + JSObject* baseType = ArrayType::GetBaseType(cx, typeObj); + result.Append("[ "); + + + size_t length = ArrayType::GetLength(cx, typeObj); + size_t elementSize = CType::GetSize(cx, baseType); + for (size_t i = 0; i < length; ++i) { + char* element = static_cast(data) + elementSize * i; + result.Append(BuildDataSource(cx, baseType, element, true)); + if (i + 1 < length) + result.Append(", "); + } + result.Append(" ]"); + 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. + result.Append("{ "); + } + + // Serialize each field of the struct recursively. Each field must + // be able to ImplicitConvert successfully. + nsTArray* fields = StructType::GetFieldInfo(cx, typeObj); + for (size_t i = 0; i < fields->Length(); ++i) { + const FieldInfo& field = fields->ElementAt(i); + char* fieldData = static_cast(data) + field.mOffset; + + if (isImplicit) { + result.Append('"'); + result.Append(field.mName); + result.Append("\": "); + } + + result.Append(BuildDataSource(cx, field.mType, fieldData, true)); + if (i + 1 != fields->Length()) + result.Append(", "); + } + + if (isImplicit) + result.Append(" }"); + + break; + } + case TYPE_void_t: + JS_NOT_REACHED("invalid type"); + break; + } + + return result; +} + +/******************************************************************************* +** CType implementation +*******************************************************************************/ + +JSBool +CType::ConstructAbstract(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + // Calling the CType abstract base class constructor is disallowed. + JS_ReportError(cx, "cannot construct from abstract type"); + return JS_FALSE; +} + +JSBool +CType::ConstructData(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + // get the callee object... + obj = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)); + if (!CType::IsCType(cx, obj)) { + JS_ReportError(cx, "not a CType"); + return JS_FALSE; + } + + // How we construct the CData object depends on what type we represent. + // An instance 'd' of a CData object of type 't' has: + // * Class [[CData]] + // * __proto__ === t.prototype + switch (GetTypeCode(cx, obj)) { + case TYPE_void_t: + JS_ReportError(cx, "cannot construct from void_t"); + return JS_FALSE; + case TYPE_pointer: + return PointerType::ConstructData(cx, obj, argc, argv, rval); + case TYPE_array: + return ArrayType::ConstructData(cx, obj, argc, argv, rval); + case TYPE_struct: + return StructType::ConstructData(cx, obj, argc, argv, rval); + default: + return ConstructBasic(cx, obj, argc, argv, rval); + } +} + +JSBool +CType::ConstructBasic(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + if (argc > 1) { + JS_ReportError(cx, "CType constructor takes zero or one argument"); + return JS_FALSE; + } + + // construct a CData object + JSObject* result = CData::Create(cx, obj, NULL, NULL); + if (!result) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(result); + + if (argc == 1) { + // perform explicit conversion + if (!ExplicitConvert(cx, argv[0], obj, CData::GetData(cx, result))) + return JS_FALSE; + } + + return JS_TRUE; +} + +JSObject* +CType::Create(JSContext* cx, + JSObject* proto, + JSString* name, + TypeCode type, + jsval size, + jsval align, + ffi_type* ffiType) +{ + // Create a CType object with the properties and slots common to all CTypes. + // Each type object 't' has: + // * Class [[CType]] + // * __proto__ === 'proto'; 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 [[Object]] + // * __proto__ === Object.prototype + // * 'constructor' property === 't' + JSObject* typeObj = JS_NewObject(cx, &sCTypeClass, proto, NULL); + if (!typeObj) + return NULL; + JSAutoTempValueRooter root(cx, typeObj); + + // Define properties common to all CTypes. + if (name && + !JS_DefineProperty(cx, typeObj, "name", STRING_TO_JSVAL(name), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + if (!JS_DefineProperty(cx, typeObj, "ptr", JSVAL_VOID, + PtrGetter, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + if (!JS_DefineProperty(cx, typeObj, "size", size, + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + // Set the TypeCode. + if (!JS_SetReservedSlot(cx, typeObj, SLOT_TYPECODE, INT_TO_JSVAL(type))) + return NULL; + + // Set the ffi_type. + if (!JS_SetReservedSlot(cx, typeObj, SLOT_FFITYPE, PRIVATE_TO_JSVAL(ffiType))) + return NULL; + + // Set the type alignment. + if (!JS_SetReservedSlot(cx, typeObj, SLOT_ALIGN, align)) + return NULL; + + // Set up the 'prototype' and 'prototype.constructor' properties. + JSObject* prototype = JS_NewObject(cx, NULL, NULL, typeObj); + if (!prototype) + return NULL; + + if (!JS_DefineProperty(cx, typeObj, "prototype", OBJECT_TO_JSVAL(prototype), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + if (!JS_DefineProperty(cx, prototype, "constructor", OBJECT_TO_JSVAL(typeObj), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + // Seal the 'prototype' property. + if (!JS_SealObject(cx, prototype, JS_FALSE)) + return NULL; + + return typeObj; +} + +JSObject* +CType::DefineBuiltin(JSContext* cx, + JSObject* parent, + const char* propName, + JSObject* proto, + const char* name, + TypeCode type, + jsval size, + jsval align, + ffi_type* ffiType) +{ + JSString* nameStr = JS_NewStringCopyZ(cx, name); + if (!nameStr) + return NULL; + JSAutoTempValueRooter nameRoot(cx, nameStr); + + // Create a new CType object with the common properties and slots. + JSObject* typeObj = Create(cx, proto, nameStr, type, size, align, ffiType); + if (!typeObj) + return NULL; + + // Define the CType as a 'propName' property on 'parent'. + if (!JS_DefineProperty(cx, parent, propName, OBJECT_TO_JSVAL(typeObj), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + if (!JS_SealObject(cx, typeObj, JS_FALSE)) + return NULL; + + return typeObj; +} + +void +CType::Finalize(JSContext* cx, JSObject* obj) +{ + // Make sure our TypeCode slot is legit. If it's not, bail. + jsval slot; + if (!JS_GetReservedSlot(cx, obj, SLOT_TYPECODE, &slot) || JSVAL_IS_VOID(slot)) + return; + + // The contents of our slots depends on what kind of type we are. + switch (GetTypeCode(cx, obj)) { + case TYPE_struct: + // Free the FieldInfo array. + JS_GetReservedSlot(cx, obj, SLOT_FIELDINFO, &slot); + if (!JSVAL_IS_VOID(slot)) + delete static_cast*>(JSVAL_TO_PRIVATE(slot)); + + // Fall through. + case TYPE_array: { + // Free the ffi_type info. + jsval slot; + JS_GetReservedSlot(cx, obj, SLOT_FFITYPE, &slot); + if (!JSVAL_IS_VOID(slot) && JSVAL_TO_PRIVATE(slot)) { + ffi_type* ffiType = static_cast(JSVAL_TO_PRIVATE(slot)); + delete ffiType->elements; + delete ffiType; + } + + break; + } + default: + // Nothing to do here. + break; + } +} + +bool +CType::IsCType(JSContext* cx, JSObject* obj) +{ + return JS_GET_CLASS(cx, obj) == &sCTypeClass; +} + +TypeCode +CType::GetTypeCode(JSContext* cx, JSObject* typeObj) +{ + JS_ASSERT(IsCType(cx, typeObj)); + + jsval result; + JS_GetReservedSlot(cx, typeObj, SLOT_TYPECODE, &result); + return TypeCode(JSVAL_TO_INT(result)); +} + +bool +CType::TypesEqual(JSContext* cx, JSObject* t1, JSObject* t2) +{ + JS_ASSERT(IsCType(cx, t1) && IsCType(cx, t2)); + + // First, perform shallow comparison. + TypeCode c1 = GetTypeCode(cx, t1); + TypeCode c2 = GetTypeCode(cx, t2); + if (c1 != c2) + return false; + + // Determine whether the types require shallow or deep comparison. + switch (c1) { + case TYPE_pointer: { + JSObject* b1 = PointerType::GetBaseType(cx, t1); + JSObject* b2 = PointerType::GetBaseType(cx, t2); + + if (!b1 || !b2) { + // One or both pointers are opaque. + // If both are opaque, compare names. + JSString* n1 = GetName(cx, t1); + JSString* n2 = GetName(cx, t2); + return b1 == b2 && JS_CompareStrings(n1, n2) == 0; + } + + // Compare base types. + return TypesEqual(cx, b1, b2); + } + case TYPE_array: { + // Compare length, then base types. + // An undefined length array matches any length. + size_t s1, s2; + bool d1 = ArrayType::GetSafeLength(cx, t1, &s1); + bool d2 = ArrayType::GetSafeLength(cx, t2, &s2); + if (d1 && d2 && s1 != s2) + return false; + + JSObject* b1 = ArrayType::GetBaseType(cx, t1); + JSObject* b2 = ArrayType::GetBaseType(cx, t2); + return TypesEqual(cx, b1, b2); + } + case TYPE_struct: + // Require exact type object equality. + return t1 == t2; + default: + // Shallow comparison is sufficient. + return true; + } +} + +bool +CType::GetSafeSize(JSContext* cx, JSObject* obj, size_t* result) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + + jsval size; + JS_GetProperty(cx, obj, "size", &size); + + // The "size" property can be a jsint, a jsdouble, or JSVAL_VOID + // (for arrays of undefined length), and must always fit in a size_t. + if (JSVAL_IS_INT(size)) { + *result = JSVAL_TO_INT(size); + return true; + } + if (JSVAL_IS_DOUBLE(size)) { + *result = Convert(*JSVAL_TO_DOUBLE(size)); + return true; + } + + JS_ASSERT(JSVAL_IS_VOID(size)); + return false; +} + +size_t +CType::GetSize(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + + jsval size; + JS_GetProperty(cx, obj, "size", &size); + + JS_ASSERT(!JSVAL_IS_VOID(size)); + + // The "size" property can be a jsint, a jsdouble, or JSVAL_VOID + // (for arrays of undefined length), and must always fit in a size_t. + // For callers who know it can never be JSVAL_VOID, return a size_t directly. + if (JSVAL_IS_INT(size)) + return JSVAL_TO_INT(size); + return Convert(*JSVAL_TO_DOUBLE(size)); +} + +bool +CType::IsSizeDefined(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + + jsval size; + JS_GetProperty(cx, obj, "size", &size); + + // The "size" property can be a jsint, a jsdouble, or JSVAL_VOID + // (for arrays of undefined length), and must always fit in a size_t. + JS_ASSERT(JSVAL_IS_INT(size) || JSVAL_IS_DOUBLE(size) || JSVAL_IS_VOID(size)); + return !JSVAL_IS_VOID(size); +} + +size_t +CType::GetAlignment(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + + jsval slot; + JS_GetReservedSlot(cx, obj, SLOT_ALIGN, &slot); + return static_cast(JSVAL_TO_INT(slot)); +} + +ffi_type* +CType::GetFFIType(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + + jsval slot; + JS_GetReservedSlot(cx, obj, SLOT_FFITYPE, &slot); + + ffi_type* result = static_cast(JSVAL_TO_PRIVATE(slot)); + JS_ASSERT(result); + return result; +} + +JSString* +CType::GetName(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + + jsval string; + JS_GetProperty(cx, obj, "name", &string); + return JSVAL_TO_STRING(string); +} + +JSObject* +CType::GetProtoFromCtor(JSContext* cx, JSObject* obj, CTypeProtoSlot slot) +{ + // Look at the 'prototype' property of the type constructor... + jsval prototype; + JS_GetProperty(cx, obj, "prototype", &prototype); + JS_ASSERT(JSVAL_IS_OBJECT(prototype) && !JSVAL_IS_NULL(prototype)); + + // ... and get ctypes.CType.prototype. + JSObject* proto = JS_GetPrototype(cx, JSVAL_TO_OBJECT(prototype)); + JS_ASSERT(proto); + JS_ASSERT(JS_GET_CLASS(cx, proto) == &sCTypeProto); + + // Finally, get the requested ctypes.{Pointer,Array,Struct}Type.prototype. + jsval result; + JS_GetReservedSlot(cx, proto, slot, &result); + return JSVAL_TO_OBJECT(result); +} + +JSObject* +CType::GetProtoFromType(JSContext* cx, JSObject* obj, CTypeProtoSlot slot) +{ + JS_ASSERT(IsCType(cx, obj)); + + // Get the prototype of the type object. + JSObject* proto = JS_GetPrototype(cx, obj); + JS_ASSERT(proto); + + if (JS_GET_CLASS(cx, proto) != &sCTypeProto) { + // We have a special type constructor. Get ctypes.CType.prototype from it. + proto = JS_GetPrototype(cx, proto); + JS_ASSERT(proto); + JS_ASSERT(JS_GET_CLASS(cx, proto) == &sCTypeProto); + } + + // Finally, get the requested ctypes.{Pointer,Array,Struct}Type.prototype. + jsval result; + JS_GetReservedSlot(cx, proto, slot, &result); + return JSVAL_TO_OBJECT(result); +} + +JSBool +CType::PtrGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!CType::IsCType(cx, obj)) { + JS_ReportError(cx, "not a CType"); + return JS_FALSE; + } + + JSObject* pointerType = PointerType::CreateInternal(cx, NULL, obj, NULL); + if (!pointerType) + return JS_FALSE; + + *vp = OBJECT_TO_JSVAL(pointerType); + return JS_TRUE; +} + +JSBool +CType::Array(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* baseType = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(baseType); + + if (!CType::IsCType(cx, baseType)) { + JS_ReportError(cx, "not a CType"); + return JS_FALSE; + } + + // Construct and return a new ArrayType object. + if (argc > 1) { + JS_ReportError(cx, "array takes zero or one argument"); + return JS_FALSE; + } + + // Convert the length argument to a size_t. + jsval* argv = JS_ARGV(cx, vp); + size_t length = 0; + if (argc == 1 && !jsvalToSize(cx, argv[0], false, &length)) { + JS_ReportError(cx, "argument must be a nonnegative integer"); + return JS_FALSE; + } + + JSObject* result = ArrayType::CreateInternal(cx, baseType, length, argc == 1); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +CType::ToString(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(obj); + + if (!CType::IsCType(cx, obj)) { + JS_ReportError(cx, "not a CType"); + return JS_FALSE; + } + + nsCAutoString type("type "); + JSString* right = GetName(cx, obj); + type.Append(JS_GetStringBytesZ(cx, right)); + + JSString* result = JS_NewStringCopyN(cx, type.get(), type.Length()); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +CType::ToSource(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(obj); + + if (!CType::IsCType(cx, obj)) { + JS_ReportError(cx, "not a CType"); + return JS_FALSE; + } + + nsCAutoString source = BuildTypeSource(cx, obj, false); + JSString* result = JS_NewStringCopyN(cx, source.get(), source.Length()); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result)); + return JS_TRUE; +} + +/******************************************************************************* +** PointerType implementation +*******************************************************************************/ + +JSBool +PointerType::Create(JSContext* cx, uintN argc, jsval* vp) +{ + // Construct and return a new PointerType object. + if (argc != 1) { + JS_ReportError(cx, "PointerType takes one argument"); + return JS_FALSE; + } + + jsval arg = JS_ARGV(cx, vp)[0]; + JSObject* baseType = NULL; + JSString* name = NULL; + if (JSVAL_IS_OBJECT(arg) && !JSVAL_IS_NULL(arg) && + CType::IsCType(cx, JSVAL_TO_OBJECT(arg))) { + baseType = JSVAL_TO_OBJECT(arg); + + } else if (JSVAL_IS_STRING(arg)) { + // Construct an opaque pointer type from a string. + name = JSVAL_TO_STRING(arg); + + } else { + JS_ReportError(cx, "first argument must be a CType or a string"); + return JS_FALSE; + } + + JSObject* callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)); + JSObject* result = CreateInternal(cx, callee, baseType, name); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + return JS_TRUE; +} + +JSObject* +PointerType::CreateInternal(JSContext* cx, + JSObject* ctor, + JSObject* baseType, + JSString* name) +{ + JS_ASSERT(ctor || baseType); + JS_ASSERT((baseType && !name) || (!baseType && name)); + + if (baseType) { + // check if we have a cached PointerType on our base CType. + jsval slot; + JS_GetReservedSlot(cx, baseType, SLOT_PTR, &slot); + if (JSVAL_IS_OBJECT(slot)) + return JSVAL_TO_OBJECT(slot); + } + + // Get ctypes.PointerType.prototype, either from ctor or the baseType, + // whichever was provided. + JSObject* proto; + if (ctor) + proto = CType::GetProtoFromCtor(cx, ctor, SLOT_POINTERPROTO); + else + proto = CType::GetProtoFromType(cx, baseType, SLOT_POINTERPROTO); + + // Create a new CType object with the common properties and slots. + JSObject* typeObj = CType::Create(cx, proto, name, TYPE_pointer, + INT_TO_JSVAL(sizeof(void*)), + INT_TO_JSVAL(ffi_type_pointer.alignment), + &ffi_type_pointer); + if (!typeObj) + return NULL; + JSAutoTempValueRooter root(cx, typeObj); + + // Define the 'targetType' property. + if (!JS_DefineProperty(cx, typeObj, "targetType", OBJECT_TO_JSVAL(baseType), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + if (baseType) { + // Determine the name of the PointerType, if it wasn't supplied. + nsCAutoString typeName = BuildTypeName(cx, typeObj); + name = JS_NewStringCopyN(cx, typeName.get(), typeName.Length()); + if (!name || + !JS_DefineProperty(cx, typeObj, "name", STRING_TO_JSVAL(name), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + } + + // Finally, cache our newly-created PointerType on our pointed-to CType, + // if we have one. + if (baseType && + !JS_SetReservedSlot(cx, baseType, SLOT_PTR, OBJECT_TO_JSVAL(typeObj))) + return NULL; + + if (!JS_SealObject(cx, typeObj, JS_FALSE)) + return NULL; + + return typeObj; +} + +JSBool +PointerType::ConstructData(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + if (!CType::IsCType(cx, obj) || CType::GetTypeCode(cx, obj) != TYPE_pointer) { + JS_ReportError(cx, "not a PointerType"); + return JS_FALSE; + } + + if (argc > 1) { + JS_ReportError(cx, "constructor takes zero or one argument"); + return JS_FALSE; + } + + JSObject* result = ConstructInternal(cx, obj, NULL, NULL); + if (!result) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(result); + + if (argc == 1) { + // perform explicit conversion + if (!ExplicitConvert(cx, argv[0], obj, CData::GetData(cx, result))) + return JS_FALSE; + } + + return JS_TRUE; +} + +JSObject* +PointerType::ConstructInternal(JSContext* cx, + JSObject* typeObj, + JSObject* parentObj, + void* data) +{ + // construct a CData object + JSObject* result = CData::Create(cx, typeObj, parentObj, data); + if (!result) + return NULL; + JSAutoTempValueRooter root(cx, result); + + if (!JS_DefineProperty(cx, result, "contents", JSVAL_VOID, + PointerType::ContentsGetter, PointerType::ContentsSetter, + JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return NULL; + + return result; +} + +JSObject* +PointerType::GetBaseType(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::GetTypeCode(cx, obj) == TYPE_pointer); + + jsval type; + JS_GetProperty(cx, obj, "targetType", &type); + return JSVAL_TO_OBJECT(type); +} + +JSBool +PointerType::ContentsGetter(JSContext* cx, + JSObject* obj, + jsval idval, + jsval* vp) +{ + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + // Get pointer type and base type. + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_pointer) { + JS_ReportError(cx, "not a PointerType"); + return JS_FALSE; + } + + JSObject* baseType = GetBaseType(cx, typeObj); + if (!baseType) { + JS_ReportError(cx, "cannot get contents of an opaque pointer type"); + return JS_FALSE; + } + + if (!CType::IsSizeDefined(cx, baseType)) { + JS_ReportError(cx, "cannot get contents of undefined size"); + return JS_FALSE; + } + + void* data = *static_cast(CData::GetData(cx, obj)); + if (data == NULL) { + JS_ReportError(cx, "cannot read contents of null pointer"); + return JS_FALSE; + } + + jsval result; + if (!ConvertToJS(cx, baseType, obj, data, false, &result)) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, result); + return JS_TRUE; +} + +JSBool +PointerType::ContentsSetter(JSContext* cx, + JSObject* obj, + jsval idval, + jsval* vp) +{ + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + // Get pointer type and base type. + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_struct) { + JS_ReportError(cx, "not a PointerType"); + return JS_FALSE; + } + + JSObject* baseType = GetBaseType(cx, typeObj); + if (!baseType) { + JS_ReportError(cx, "cannot set contents of an opaque pointer type"); + return JS_FALSE; + } + + if (!CType::IsSizeDefined(cx, baseType)) { + JS_ReportError(cx, "cannot get contents of undefined size"); + return JS_FALSE; + } + + void* data = *static_cast(CData::GetData(cx, obj)); + if (data == NULL) { + JS_ReportError(cx, "cannot write contents to null pointer"); + return JS_FALSE; + } + + return ImplicitConvert(cx, *vp, baseType, data, false, NULL); +} + +/******************************************************************************* +** ArrayType implementation +*******************************************************************************/ + +JSBool +ArrayType::Create(JSContext* cx, uintN argc, jsval* vp) +{ + // Construct and return a new ArrayType object. + if (argc > 2) { + JS_ReportError(cx, "ArrayType takes one or two arguments"); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(cx, vp); + if (!JSVAL_IS_OBJECT(argv[0]) || JSVAL_IS_NULL(argv[0] || + !CType::IsCType(cx, JSVAL_TO_OBJECT(argv[0])))) { + JS_ReportError(cx, "first argument must be a CType"); + return JS_FALSE; + } + + // Convert the length argument to a size_t. + size_t length = 0; + if (argc == 2 && !jsvalToSize(cx, argv[1], false, &length)) { + JS_ReportError(cx, "second argument must be a nonnegative integer"); + return JS_FALSE; + } + + JSObject* baseType = JSVAL_TO_OBJECT(argv[0]); + JSObject* result = CreateInternal(cx, baseType, length, argc == 2); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + return JS_TRUE; +} + +JSObject* +ArrayType::CreateInternal(JSContext* cx, + JSObject* baseType, + size_t length, + bool lengthDefined) +{ + // Get ctypes.ArrayType.prototype from ctypes.CType.prototype. + JSObject* proto = CType::GetProtoFromType(cx, baseType, SLOT_ARRAYPROTO); + + // Determine the size of the array from the base type, if possible. + // The size of the base type must be defined. + // If our length is undefined, both our size and length will be undefined. + size_t baseSize; + if (!CType::GetSafeSize(cx, baseType, &baseSize)) { + JS_ReportError(cx, "base size must be defined"); + return NULL; + } + + size_t size; + jsval sizeVal = JSVAL_VOID; + jsval lengthVal = JSVAL_VOID; + if (lengthDefined) { + // Check for overflow, and convert to a jsint or jsdouble as required. + size = length * baseSize; + if (length > 0 && size / length != baseSize) { + JS_ReportError(cx, "size overflow"); + return NULL; + } + if (!SizeTojsval(cx, size, &sizeVal) || + !SizeTojsval(cx, length, &lengthVal)) + return NULL; + } + + size_t align = CType::GetAlignment(cx, baseType); + + ffi_type* ffiType = NULL; + if (lengthDefined) { + // Create an ffi_type to represent the array. This is necessary for the case + // where the array is part of a struct. Since libffi has no intrinsic + // support for array types, we approximate it by creating a struct type + // with elements of type 'baseType' and with appropriate size and alignment + // values. + ffiType = new ffi_type; + if (!ffiType) { + JS_ReportOutOfMemory(cx); + return NULL; + } + + ffiType->type = FFI_TYPE_STRUCT; + ffiType->size = size; + ffiType->alignment = align; + ffiType->elements = new ffi_type*[length + 1]; + if (!ffiType->elements) { + delete ffiType; + JS_ReportAllocationOverflow(cx); + return NULL; + } + + ffi_type* ffiBaseType = CType::GetFFIType(cx, baseType); + for (size_t i = 0; i < length; ++i) + ffiType->elements[i] = ffiBaseType; + ffiType->elements[length] = NULL; + } + + // Create a new CType object with the common properties and slots. + JSObject* typeObj = CType::Create(cx, proto, NULL, TYPE_array, + sizeVal, INT_TO_JSVAL(align), ffiType); + if (!typeObj) + return NULL; + JSAutoTempValueRooter root(cx, typeObj); + + // Define additional properties. + if (!JS_DefineProperty(cx, typeObj, "length", lengthVal, + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + if (!JS_DefineProperty(cx, typeObj, "elementType", OBJECT_TO_JSVAL(baseType), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + // Determine the name of the ArrayType. + nsCAutoString typeName = BuildTypeName(cx, typeObj); + JSString* name = JS_NewStringCopyN(cx, typeName.get(), typeName.Length()); + if (!name || + !JS_DefineProperty(cx, typeObj, "name", STRING_TO_JSVAL(name), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + if (!JS_SealObject(cx, typeObj, JS_FALSE)) + return NULL; + + return typeObj; +} + +JSBool +ArrayType::ConstructData(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + if (!CType::IsCType(cx, obj) || CType::GetTypeCode(cx, obj) != TYPE_array) { + JS_ReportError(cx, "not an ArrayType"); + return JS_FALSE; + } + + // Decide whether we have an object to initialize from. We'll override this + // if we get a length argument instead. + bool convertObject = argc == 1; + + // Check if we're an array of undefined length. If we are, allow construction + // with a length argument, or with an actual JS array. + if (CType::IsSizeDefined(cx, obj)) { + if (argc > 1) { + JS_ReportError(cx, "constructor takes zero or one argument"); + return JS_FALSE; + } + + } else { + if (argc != 1) { + JS_ReportError(cx, "constructor takes one argument"); + return JS_FALSE; + } + + JSObject* baseType = GetBaseType(cx, obj); + + size_t length; + if (jsvalToSize(cx, argv[0], false, &length)) { + // Have a length, rather than an object to initialize from. + convertObject = false; + + } else if (JSVAL_IS_OBJECT(argv[0])) { + // We were given an object with a .length property. + // This could be a JS array, or a CData array. + JSObject* arg = JSVAL_TO_OBJECT(argv[0]); + jsval lengthVal; + if (!JS_GetProperty(cx, arg, "length", &lengthVal) || + !jsvalToSize(cx, lengthVal, false, &length)) { + JS_ReportError(cx, "argument must be an array object or length"); + return JS_FALSE; + } + + } else if (JSVAL_IS_STRING(argv[0])) { + // We were given a string. Size the array to the appropriate length, + // including space for the terminator. + JSString* sourceString = JSVAL_TO_STRING(argv[0]); + const jschar* sourceChars = JS_GetStringCharsZ(cx, sourceString); + size_t sourceLength = JS_GetStringLength(sourceString); + + switch (CType::GetTypeCode(cx, baseType)) { + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + // Convert from UTF-16 to UTF-8 to determine the length. :( + if (!IsUTF16(sourceChars, sourceLength)) + return TypeError(cx, "UTF-16 string", argv[0]); + + NS_ConvertUTF16toUTF8 converted( + reinterpret_cast(sourceChars), sourceLength); + + length = converted.Length() + 1; + break; + } + case TYPE_jschar: + length = sourceLength + 1; + break; + default: + return TypeError(cx, "array", argv[0]); + } + + } else { + JS_ReportError(cx, "argument must be an array object or length"); + return JS_FALSE; + } + + // Construct a new ArrayType of defined length, for the new CData object. + obj = CreateInternal(cx, baseType, length, true); + if (!obj) + return JS_FALSE; + } + + // Root the CType object, in case we created one above. + JSAutoTempValueRooter root(cx, obj); + + JSObject* result = ConstructInternal(cx, obj, NULL, NULL); + if (!result) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(result); + + if (convertObject) { + // perform explicit conversion + if (!ExplicitConvert(cx, argv[0], obj, CData::GetData(cx, result))) + return JS_FALSE; + } + + return JS_TRUE; +} + +JSObject* +ArrayType::ConstructInternal(JSContext* cx, + JSObject* typeObj, + JSObject* parentObj, + void* data) +{ + // construct a CData object + JSObject* result = CData::Create(cx, typeObj, parentObj, data); + if (!result) + return NULL; + JSAutoTempValueRooter root(cx, result); + + if (!JS_DefineFunctions(cx, result, sArrayInstanceFunctions)) + return NULL; + + // Duplicate the 'constructor.length' property as a 'length' property, + // for consistency with JS array objects. + jsval lengthVal; + if (!JS_GetProperty(cx, typeObj, "length", &lengthVal) || + !JS_DefineProperty(cx, result, "length", lengthVal, + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return NULL; + + return result; +} + +JSObject* +ArrayType::GetBaseType(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + JS_ASSERT(CType::GetTypeCode(cx, obj) == TYPE_array); + + jsval type; + JS_GetProperty(cx, obj, "elementType", &type); + return JSVAL_TO_OBJECT(type); +} + +bool +ArrayType::GetSafeLength(JSContext* cx, JSObject* obj, size_t* result) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + JS_ASSERT(CType::GetTypeCode(cx, obj) == TYPE_array); + + jsval length; + JS_GetProperty(cx, obj, "length", &length); + + // The "length" property can be a jsint, a jsdouble, or JSVAL_VOID + // (for arrays of undefined length), and must always fit in a size_t. + if (JSVAL_IS_INT(length)) { + *result = JSVAL_TO_INT(length); + return true; + } + if (JSVAL_IS_DOUBLE(length)) { + *result = Convert(*JSVAL_TO_DOUBLE(length)); + return true; + } + + JS_ASSERT(JSVAL_IS_VOID(length)); + return false; +} + +size_t +ArrayType::GetLength(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + JS_ASSERT(CType::GetTypeCode(cx, obj) == TYPE_array); + + jsval length; + JS_GetProperty(cx, obj, "length", &length); + + JS_ASSERT(!JSVAL_IS_VOID(length)); + + // The "length" property can be a jsint, a jsdouble, or JSVAL_VOID + // (for arrays of undefined length), and must always fit in a size_t. + // For callers who know it can never be JSVAL_VOID, return a size_t directly. + if (JSVAL_IS_INT(length)) + return JSVAL_TO_INT(length); + return Convert(*JSVAL_TO_DOUBLE(length)); +} + +JSBool +ArrayType::Getter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!CData::IsCData(cx, obj)) + return JS_TRUE; + + // Bail early if we're not an ArrayType. (This setter is present for all + // CData, regardless of CType.) + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_array) + return JS_TRUE; + + // Convert the index to a size_t and bounds-check it. + size_t index; + size_t length = GetLength(cx, typeObj); + bool ok = jsvalToSize(cx, idval, true, &index); + if (!ok && JSVAL_IS_STRING(idval)) { + // String either isn't a number, or doesn't fit in size_t. + // Chances are it's a regular property lookup, so return. + return JS_TRUE; + } + if (!ok || index >= length) { + JS_ReportError(cx, "invalid index"); + return JS_FALSE; + } + + JSObject* baseType = GetBaseType(cx, typeObj); + size_t elementSize = CType::GetSize(cx, baseType); + char* data = static_cast(CData::GetData(cx, obj)) + elementSize * index; + return ConvertToJS(cx, baseType, obj, data, false, vp); +} + +JSBool +ArrayType::Setter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!CData::IsCData(cx, obj)) + return JS_TRUE; + + // Bail early if we're not an ArrayType. (This setter is present for all + // CData, regardless of CType.) + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_array) + return JS_TRUE; + + // Convert the index to a size_t and bounds-check it. + size_t index; + size_t length = GetLength(cx, typeObj); + bool ok = jsvalToSize(cx, idval, true, &index); + if (!ok && JSVAL_IS_STRING(idval)) { + // String either isn't a number, or doesn't fit in size_t. + // Chances are it's a regular property lookup, so return. + return JS_TRUE; + } + if (!ok || index >= length) { + JS_ReportError(cx, "invalid index"); + return JS_FALSE; + } + + JSObject* baseType = GetBaseType(cx, typeObj); + size_t elementSize = CType::GetSize(cx, baseType); + char* data = static_cast(CData::GetData(cx, obj)) + elementSize * index; + return ImplicitConvert(cx, *vp, baseType, data, false, NULL); +} + +JSBool +ArrayType::AddressOfElement(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(obj); + + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_array) { + JS_ReportError(cx, "not an ArrayType"); + return JS_FALSE; + } + + if (argc != 1) { + JS_ReportError(cx, "addressOfElement takes one argument"); + return JS_FALSE; + } + + JSObject* baseType = GetBaseType(cx, typeObj); + JSObject* pointerType = PointerType::CreateInternal(cx, NULL, baseType, NULL); + if (!pointerType) + return JS_FALSE; + JSAutoTempValueRooter root(cx, pointerType); + + // Create a PointerType CData object containing null. + JSObject* result = PointerType::ConstructInternal(cx, pointerType, NULL, NULL); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + + // Convert the index to a size_t and bounds-check it. + size_t index; + size_t length = GetLength(cx, typeObj); + if (!jsvalToSize(cx, JS_ARGV(cx, vp)[0], false, &index) || + index >= length) { + JS_ReportError(cx, "invalid index"); + return JS_FALSE; + } + + // Manually set the pointer inside the object, so we skip the conversion step. + void** data = static_cast(CData::GetData(cx, result)); + size_t elementSize = CType::GetSize(cx, baseType); + *data = static_cast(CData::GetData(cx, obj)) + elementSize * index; + return JS_TRUE; +} + +/******************************************************************************* +** StructType implementation +*******************************************************************************/ + +// For a struct field descriptor 'val' of the form { name : type }, extract +// 'name' and 'type', and populate 'field' with the information. +static bool +ExtractStructField(JSContext* cx, jsval val, FieldInfo* field) +{ + if (!JSVAL_IS_OBJECT(val) || JSVAL_IS_NULL(val)) + return false; + + JSObject* obj = JSVAL_TO_OBJECT(val); + JSObject* iter = JS_NewPropertyIterator(cx, obj); + if (!iter) + return false; + JSAutoTempValueRooter iterroot(cx, iter); + + jsid id; + if (!JS_NextProperty(cx, iter, &id)) + return false; + + jsval nameVal; + if (!JS_IdToValue(cx, id, &nameVal) || !JSVAL_IS_STRING(nameVal)) + return false; + JSAutoTempValueRooter nameroot(cx, nameVal); + + // make sure we have one, and only one, property + if (JS_NextProperty(cx, iter, &id) && !JSVAL_IS_VOID(id)) + return false; + + const char* name = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(nameVal)); + field->mName = name; + + jsval propVal; + if (!JS_GetProperty(cx, obj, name, &propVal) || + !JSVAL_IS_OBJECT(propVal) || JSVAL_IS_NULL(propVal) || + !CType::IsCType(cx, JSVAL_TO_OBJECT(propVal))) + return false; + + // Undefined size or zero size struct members are illegal. + // (Zero-size arrays are legal as struct members in C++, but libffi will + // choke on a zero-size struct, so we disallow them.) + field->mType = JSVAL_TO_OBJECT(propVal); + size_t size; + if (!CType::GetSafeSize(cx, field->mType, &size) || size == 0) + return false; + + return true; +} + +// For a struct field with 'name' and 'type', add an element to field +// descriptor array 'arrayObj' of the form { name : type }. +static bool +AddFieldToArray(JSContext* cx, + JSObject* arrayObj, + jsuint index, + const char* name, + JSObject* typeObj) +{ + JSObject* fieldObj = JS_NewObject(cx, NULL, NULL, NULL); + if (!fieldObj) + return false; + + if (!JS_DefineElement(cx, arrayObj, index, OBJECT_TO_JSVAL(fieldObj), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return false; + + if (!JS_DefineProperty(cx, fieldObj, name, OBJECT_TO_JSVAL(typeObj), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return false; + + return JS_SealObject(cx, fieldObj, JS_FALSE) != JS_FALSE; +} + +JSBool +StructType::Create(JSContext* cx, uintN argc, jsval* vp) +{ + // Construct and return a new StructType object. + if (argc < 2) { + JS_ReportError(cx, "StructType takes at least two arguments"); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(cx, vp); + jsval name = argv[0]; + if (!JSVAL_IS_STRING(name)) { + JS_ReportError(cx, "first argument must be a string"); + return JS_FALSE; + } + + if (!JSVAL_IS_OBJECT(argv[1]) || + !JS_IsArrayObject(cx, JSVAL_TO_OBJECT(argv[1]))) { + JS_ReportError(cx, "second argument must be an array"); + return JS_FALSE; + } + + // Prepare a new array for the .fields property of the StructType. + JSObject* fieldsProp = JS_NewArrayObject(cx, 0, NULL); + if (!fieldsProp) + return JS_FALSE; + JSAutoTempValueRooter root(cx, fieldsProp); + + // Process the field types and fill in the ffi_type fields. + JSObject* fieldsObj = JSVAL_TO_OBJECT(argv[1]); + jsuint len; + JS_GetArrayLength(cx, fieldsObj, &len); + + nsAutoPtr ffiType(new ffi_type); + if (!ffiType) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + ffiType->type = FFI_TYPE_STRUCT; + + nsAutoPtr< nsTArray > fields(new nsTArray(len)); + if (!fields) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + nsAutoPtr elements; + + size_t structSize = 0, structAlign = 0; + if (len != 0) { + elements = new ffi_type*[len + 1]; + if (!elements) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + elements[len] = NULL; + + for (jsuint i = 0; i < len; ++i) { + jsval item; + JS_GetElement(cx, fieldsObj, i, &item); + + FieldInfo* info = fields->AppendElement(); + if (!info) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + if (!ExtractStructField(cx, item, info)) { + JS_ReportError(cx, "struct field descriptors require a valid name and type"); + return JS_FALSE; + } + + // Duplicate the object for the fields property. + if (!AddFieldToArray(cx, fieldsProp, i, info->mName.get(), info->mType)) + return JS_FALSE; + + // Make sure each field name is unique. + for (PRUint32 j = 0; j < fields->Length() - 1; ++j) { + if (fields->ElementAt(j).mName == info->mName) { + JS_ReportError(cx, "struct fields must have unique names"); + return JS_FALSE; + } + } + + elements[i] = CType::GetFFIType(cx, info->mType); + + size_t fieldAlign = CType::GetAlignment(cx, info->mType); + size_t padding = (fieldAlign - structSize % fieldAlign) % fieldAlign; + info->mOffset = structSize + padding; + size_t delta = padding + CType::GetSize(cx, info->mType); + size_t oldSize = structSize; + structSize = structSize + delta; + if (structSize - delta != oldSize) { + JS_ReportError(cx, "size overflow"); + return JS_FALSE; + } + + if (fieldAlign > structAlign) + structAlign = fieldAlign; + } + + // Pad the struct tail according to struct alignment. + size_t oldSize = structSize; + size_t delta = (structAlign - structSize % structAlign) % structAlign; + structSize = structSize + delta; + if (structSize - delta != oldSize) { + JS_ReportError(cx, "size overflow"); + return JS_FALSE; + } + + } else { + // Empty structs are illegal in C, but are legal and have a size of + // 1 byte in C++. We're going to allow them, and trick libffi into + // believing this by adding a char member. The resulting struct will have + // no getters or setters, and will be initialized to zero. + structSize = 1; + structAlign = 1; + elements = new ffi_type*[2]; + if (!elements) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + elements[0] = &ffi_type_uint8; + elements[1] = NULL; + } + + ffiType->elements = elements; + +#ifdef DEBUG + // Perform a sanity check: the result of our struct size and alignment + // calculations should match libffi's. We force it to do this calculation + // by calling ffi_prep_cif. + ffi_cif cif; + ffiType->size = 0; + ffiType->alignment = 0; + ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 0, ffiType, NULL); + JS_ASSERT(status == FFI_OK); + JS_ASSERT(structSize == ffiType->size); + JS_ASSERT(structAlign == ffiType->alignment); +#else + // Fill in the ffi_type's size and align fields. This makes libffi treat the + // type as initialized; it will not recompute the values. (We assume + // everything agrees; if it doesn't, we really want to know about it, which + // is the purpose of the above debug-only check.) + ffiType->size = structSize; + ffiType->alignment = structAlign; +#endif + + jsval sizeVal; + if (!SizeTojsval(cx, structSize, &sizeVal)) + return JS_FALSE; + + // Get ctypes.StructType.prototype from the ctypes.StructType constructor. + JSObject* callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)); + JSObject* proto = CType::GetProtoFromCtor(cx, callee, SLOT_STRUCTPROTO); + + // Create a new CType object with the common properties and slots. + JSObject* typeObj = CType::Create(cx, proto, JSVAL_TO_STRING(name), + TYPE_struct, sizeVal, INT_TO_JSVAL(structAlign), ffiType); + if (!typeObj) + return JS_FALSE; + ffiType.forget(); + elements.forget(); + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(typeObj)); + + // Seal and attach the fields array. (The fields array also prevents the + // type objects we depend on from being GC'ed). + if (!JS_SealObject(cx, fieldsProp, JS_FALSE) || + !JS_DefineProperty(cx, typeObj, "fields", OBJECT_TO_JSVAL(fieldsProp), + NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) + return JS_FALSE; + + // Stash the FieldInfo array in a reserved slot. + if (!JS_SetReservedSlot(cx, typeObj, SLOT_FIELDINFO, + PRIVATE_TO_JSVAL(fields.get()))) + return JS_FALSE; + fields.forget(); + + if (!JS_SealObject(cx, typeObj, JS_FALSE)) + return JS_FALSE; + + return JS_TRUE; +} + +JSBool +StructType::ConstructData(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + if (!CType::IsCType(cx, obj) || CType::GetTypeCode(cx, obj) != TYPE_struct) { + JS_ReportError(cx, "not a StructType"); + return JS_FALSE; + } + + nsTArray* fields = GetFieldInfo(cx, obj); + + if (argc != 0 && argc != fields->Length()) { + JS_ReportError(cx, "constructor takes zero or %u arguments", fields->Length()); + return JS_FALSE; + } + + JSObject* result = ConstructInternal(cx, obj, NULL, NULL); + if (!result) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(result); + + if (argc != 0) { + // convert each field + char* buffer = static_cast(CData::GetData(cx, result)); + for (PRUint32 i = 0; i < fields->Length(); ++i) { + FieldInfo& field = fields->ElementAt(i); + if (!ExplicitConvert(cx, argv[i], field.mType, buffer + field.mOffset)) + return JS_FALSE; + } + } + + return JS_TRUE; +} + +JSObject* +StructType::ConstructInternal(JSContext* cx, + JSObject* typeObj, + JSObject* parentObj, + void* data) +{ + // construct a CData object + JSObject* result = CData::Create(cx, typeObj, parentObj, data); + if (!result) + return NULL; + JSAutoTempValueRooter root(cx, result); + + nsTArray* fields = GetFieldInfo(cx, typeObj); + + // add getters/setters for the fields + for (PRUint32 i = 0; i < fields->Length(); ++i) { + FieldInfo& field = fields->ElementAt(i); + + if (!JS_DefineProperty(cx, result, field.mName.get(), JSVAL_VOID, + StructType::FieldGetter, StructType::FieldSetter, + JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return NULL; + } + + if (!JS_DefineFunctions(cx, result, sStructInstanceFunctions)) + return NULL; + + return result; +} + +nsTArray* +StructType::GetFieldInfo(JSContext* cx, JSObject* obj) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + JS_ASSERT(CType::GetTypeCode(cx, obj) == TYPE_struct); + + jsval slot; + JS_GetReservedSlot(cx, obj, SLOT_FIELDINFO, &slot); + JS_ASSERT(!JSVAL_IS_VOID(slot) && JSVAL_TO_PRIVATE(slot)); + + return static_cast*>(JSVAL_TO_PRIVATE(slot)); +} + +FieldInfo* +StructType::LookupField(JSContext* cx, JSObject* obj, jsval idval) +{ + JS_ASSERT(CType::IsCType(cx, obj)); + JS_ASSERT(CType::GetTypeCode(cx, obj) == TYPE_struct); + + nsTArray* fields = GetFieldInfo(cx, obj); + + PRUint32 i; + const char* name = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(idval)); + for (i = 0; i < fields->Length(); ++i) { + if (fields->ElementAt(i).mName.Equals(name)) + break; + } + + if (i == fields->Length()) + return NULL; + return &fields->ElementAt(i); +} + +JSBool +StructType::FieldGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_struct) { + JS_ReportError(cx, "not a StructType"); + return JS_FALSE; + } + + FieldInfo* field = LookupField(cx, typeObj, idval); + JS_ASSERT(field); + + char* data = static_cast(CData::GetData(cx, obj)) + field->mOffset; + return ConvertToJS(cx, field->mType, obj, data, false, vp); +} + +JSBool +StructType::FieldSetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_struct) { + JS_ReportError(cx, "not a StructType"); + return JS_FALSE; + } + + FieldInfo* field = LookupField(cx, typeObj, idval); + JS_ASSERT(field); + + char* data = static_cast(CData::GetData(cx, obj)) + field->mOffset; + return ImplicitConvert(cx, *vp, field->mType, data, false, NULL); +} + +JSBool +StructType::AddressOfField(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(obj); + + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + JSObject* typeObj = CData::GetCType(cx, obj); + if (CType::GetTypeCode(cx, typeObj) != TYPE_struct) { + JS_ReportError(cx, "not a StructType"); + return JS_FALSE; + } + + if (argc != 1) { + JS_ReportError(cx, "addressOfField takes one argument"); + return JS_FALSE; + } + + FieldInfo* field = LookupField(cx, typeObj, JS_ARGV(cx, vp)[0]); + if (!field) { + JS_ReportError(cx, "argument does not name a field"); + return JS_FALSE; + } + + JSObject* baseType = field->mType; + JSObject* pointerType = PointerType::CreateInternal(cx, NULL, baseType, NULL); + if (!pointerType) + return JS_FALSE; + JSAutoTempValueRooter root(cx, pointerType); + + // Create a PointerType CData object containing null. + JSObject* result = PointerType::ConstructInternal(cx, pointerType, NULL, NULL); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + + // Manually set the pointer inside the object, so we skip the conversion step. + void** data = static_cast(CData::GetData(cx, result)); + *data = static_cast(CData::GetData(cx, obj)) + field->mOffset; + return JS_TRUE; +} + +/******************************************************************************* +** CData implementation +*******************************************************************************/ + +// Create a new CData object of type 'typeObj' containing binary data supplied +// in 'source', optionally with a referent CData object 'baseObj'. The following +// semantics apply: +// * 'typeObj' must be a CType of defined (but possibly zero) size. +// * If a CData object 'parentObj' is supplied, the new CData object becomes +// dependent on the given parent and its buffer refers to the parent's buffer, +// supplied in 'source'. 'parentObj' will be held alive by the resulting CData +// object. +// * If 'parentObj' is null, the new CData object will create a new buffer of +// size given by 'typeObj'. If 'source' data is supplied, the data will be +// copied from 'source' into the new buffer; otherwise, the entirety of the +// new buffer will be initialized to zero. +JSObject* +CData::Create(JSContext* cx, JSObject* typeObj, JSObject* baseObj, void* source) +{ + JS_ASSERT(typeObj); + JS_ASSERT(CType::IsCType(cx, typeObj)); + JS_ASSERT(CType::IsSizeDefined(cx, typeObj)); + JS_ASSERT(!baseObj || CData::IsCData(cx, baseObj)); + JS_ASSERT(!baseObj || source); + + // Get the 'prototype' property from the type. + jsval protoVal; + JS_GetProperty(cx, typeObj, "prototype", &protoVal); + JS_ASSERT(JSVAL_IS_OBJECT(protoVal) && !JSVAL_IS_NULL(protoVal)); + + JSObject* proto = JSVAL_TO_OBJECT(protoVal); + JSObject* dataObj = JS_NewObject(cx, &sCDataClass, proto, NULL); + if (!dataObj) + return NULL; + JSAutoTempValueRooter root(cx, dataObj); + + if (!JS_DefineFunctions(cx, dataObj, sCDataFunctions)) + return NULL; + + if (!JS_DefineProperty(cx, dataObj, "value", JSVAL_VOID, + ValueGetter, ValueSetter, JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return NULL; + + // set the CData's associated type + if (!JS_SetReservedSlot(cx, dataObj, SLOT_CTYPE, OBJECT_TO_JSVAL(typeObj))) + return NULL; + + // root the base object, if any + if (!JS_SetReservedSlot(cx, dataObj, SLOT_REFERENT, OBJECT_TO_JSVAL(baseObj))) + return NULL; + + // attach the buffer. since it might not be 2-byte aligned, we need to + // allocate an aligned space for it and store it there. :( + char** buffer = new char*; + if (!buffer) { + JS_ReportOutOfMemory(cx); + return NULL; + } + + char* data; + if (baseObj) { + data = static_cast(source); + } else { + // There is no parent object to depend on; initialize our own buffer. + size_t size = CType::GetSize(cx, typeObj); + data = new char[size]; + if (!data) { + // Report a catchable allocation error. + JS_ReportAllocationOverflow(cx); + delete buffer; + return NULL; + } + + if (!source) + memset(data, 0, size); + else + memcpy(data, source, size); + } + + *buffer = data; + if (!JS_SetReservedSlot(cx, dataObj, SLOT_DATA, PRIVATE_TO_JSVAL(buffer))) { + if (!baseObj) + delete data; + delete buffer; + return NULL; + } + + return dataObj; +} + +void +CData::Finalize(JSContext* cx, JSObject* obj) +{ + // Delete our buffer, and the data it contains if we own it. + jsval slot; + if (!JS_GetReservedSlot(cx, obj, SLOT_REFERENT, &slot) || JSVAL_IS_VOID(slot)) + return; + JSBool owns = JSVAL_IS_NULL(slot); + + if (!JS_GetReservedSlot(cx, obj, SLOT_DATA, &slot) || JSVAL_IS_VOID(slot)) + return; + char** buffer = static_cast(JSVAL_TO_PRIVATE(slot)); + + if (owns) + delete *buffer; + delete buffer; +} + +JSObject* +CData::GetCType(JSContext* cx, JSObject* dataObj) +{ + JS_ASSERT(CData::IsCData(cx, dataObj)); + + jsval slot; + JS_GetReservedSlot(cx, dataObj, SLOT_CTYPE, &slot); + JSObject* typeObj = JSVAL_TO_OBJECT(slot); + JS_ASSERT(CType::IsCType(cx, typeObj)); + return typeObj; +} + +void* +CData::GetData(JSContext* cx, JSObject* dataObj) +{ + JS_ASSERT(CData::IsCData(cx, dataObj)); + + jsval slot; + JS_GetReservedSlot(cx, dataObj, SLOT_DATA, &slot); + + void** buffer = static_cast(JSVAL_TO_PRIVATE(slot)); + JS_ASSERT(buffer); + JS_ASSERT(*buffer); + return *buffer; +} + +bool +CData::IsCData(JSContext* cx, JSObject* obj) +{ + return JS_GET_CLASS(cx, obj) == &sCDataClass; +} + +JSBool +CData::ValueGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + // Convert the value to a primitive; do not create a new CData object. + if (!ConvertToJS(cx, GetCType(cx, obj), NULL, GetData(cx, obj), true, vp)) + return JS_FALSE; + + return JS_TRUE; +} + +JSBool +CData::ValueSetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + if (!IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + return ImplicitConvert(cx, *vp, GetCType(cx, obj), GetData(cx, obj), false, NULL); +} + +JSBool +CData::Address(JSContext* cx, uintN argc, jsval *vp) +{ + if (argc != 0) { + JS_ReportError(cx, "address takes zero arguments"); + return JS_FALSE; + } + + JSObject* obj = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(obj); + + if (!IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + JSObject* typeObj = CData::GetCType(cx, obj); + JSObject* pointerType = PointerType::CreateInternal(cx, NULL, typeObj, NULL); + if (!pointerType) + return JS_FALSE; + JSAutoTempValueRooter root(cx, pointerType); + + // Create a PointerType CData object containing null. + JSObject* result = PointerType::ConstructInternal(cx, pointerType, NULL, NULL); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + + // Manually set the pointer inside the object, so we skip the conversion step. + void** data = static_cast(GetData(cx, result)); + *data = GetData(cx, obj); + return JS_TRUE; +} + +JSBool +CData::Cast(JSContext* cx, uintN argc, jsval *vp) +{ + if (argc != 2) { + JS_ReportError(cx, "cast takes two arguments"); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(cx, vp); + if (!JSVAL_IS_OBJECT(argv[0]) || JSVAL_IS_NULL(argv[0]) || + !CData::IsCData(cx, JSVAL_TO_OBJECT(argv[0]))) { + JS_ReportError(cx, "first argument must be a CData"); + return JS_FALSE; + } + JSObject* sourceData = JSVAL_TO_OBJECT(argv[0]); + JSObject* sourceType = CData::GetCType(cx, sourceData); + + if (!JSVAL_IS_OBJECT(argv[1]) || JSVAL_IS_NULL(argv[1]) || + !CType::IsCType(cx, JSVAL_TO_OBJECT(argv[1]))) { + JS_ReportError(cx, "second argument must be a CType"); + return JS_FALSE; + } + + JSObject* targetType = JSVAL_TO_OBJECT(argv[1]); + size_t targetSize; + if (!CType::GetSafeSize(cx, targetType, &targetSize) || + targetSize > CType::GetSize(cx, sourceType)) { + JS_ReportError(cx, + "target CType has undefined or larger size than source CType"); + return JS_FALSE; + } + + // Construct a new CData object with a type of 'targetType' and a referent + // of 'sourceData'. + JSObject* result; + void* data = CData::GetData(cx, sourceData); + switch (CType::GetTypeCode(cx, targetType)) { + case TYPE_pointer: + result = PointerType::ConstructInternal(cx, targetType, sourceData, data); + break; + case TYPE_array: + result = ArrayType::ConstructInternal(cx, targetType, sourceData, data); + break; + case TYPE_struct: + result = StructType::ConstructInternal(cx, targetType, sourceData, data); + break; + default: + result = CData::Create(cx, targetType, sourceData, data); + break; + } + + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +CData::ReadString(JSContext* cx, uintN argc, jsval *vp) +{ + if (argc != 0) { + JS_ReportError(cx, "readString takes zero arguments"); + return JS_FALSE; + } + + JSObject* obj = JS_THIS_OBJECT(cx, vp); + JS_ASSERT(obj); + + if (!IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + // Make sure we are a pointer to, or an array of, an 8-bit or 16-bit + // character or integer type. + JSObject* baseType; + JSObject* typeObj = GetCType(cx, obj); + TypeCode typeCode = CType::GetTypeCode(cx, typeObj); + void* data; + size_t maxLength = -1; + switch (typeCode) { + case TYPE_pointer: + baseType = PointerType::GetBaseType(cx, typeObj); + if (!baseType) { + JS_ReportError(cx, "cannot read contents of pointer to opaque type"); + return JS_FALSE; + } + + data = *static_cast(GetData(cx, obj)); + if (data == NULL) { + JS_ReportError(cx, "cannot read contents of null pointer"); + return JS_FALSE; + } + break; + case TYPE_array: + baseType = ArrayType::GetBaseType(cx, typeObj); + data = GetData(cx, obj); + maxLength = ArrayType::GetLength(cx, typeObj); + break; + default: + JS_ReportError(cx, "not a PointerType or ArrayType"); + return JS_FALSE; + } + + // Convert the string buffer, taking care to determine the correct string + // length in the case of arrays (which may contain embedded nulls). + JSString* result; + switch (CType::GetTypeCode(cx, baseType)) { + case TYPE_int8_t: + case TYPE_uint8_t: + case TYPE_char: + case TYPE_signed_char: + case TYPE_unsigned_char: { + char* bytes = static_cast(data); + size_t length = strnlen(bytes, maxLength); + nsDependentCSubstring string(bytes, bytes + length); + if (!IsUTF8(string)) { + JS_ReportError(cx, "not a UTF-8 string"); + return JS_FALSE; + } + + NS_ConvertUTF8toUTF16 converted(string); + result = JS_NewUCStringCopyN(cx, converted.get(), converted.Length()); + break; + } + case TYPE_int16_t: + case TYPE_uint16_t: + case TYPE_short: + case TYPE_unsigned_short: + case TYPE_jschar: { + jschar* chars = static_cast(data); + size_t length = strnlen(chars, maxLength); + result = JS_NewUCStringCopyN(cx, chars, length); + break; + } + default: + JS_ReportError(cx, + "base type is not an 8-bit or 16-bit integer or character type"); + return JS_FALSE; + } + + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +CData::ToSource(JSContext* cx, uintN argc, jsval *vp) +{ + if (argc != 0) { + JS_ReportError(cx, "toSource takes zero arguments"); + return JS_FALSE; + } + + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!CData::IsCData(cx, obj)) { + JS_ReportError(cx, "not a CData"); + return JS_FALSE; + } + + JSObject* typeObj = CData::GetCType(cx, obj); + void* data = CData::GetData(cx, obj); + + // Walk the types, building up the toSource() string. + // First, we build up the type expression: + // 't.ptr' for pointers; + // 't.array([n])' for arrays; + // 'n' for structs, where n = t.name, the struct's name. (We assume this is + // bound to a variable in the current scope.) + nsCAutoString source = BuildTypeSource(cx, typeObj, true); + source.Append('('); + source.Append(BuildDataSource(cx, typeObj, data, false)); + source.Append(')'); + + JSString* result = JS_NewStringCopyN(cx, source.get(), source.Length()); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result)); + return JS_TRUE; +} + +/******************************************************************************* +** Int64 and UInt64 implementation +*******************************************************************************/ + +JSObject* +Int64Base::Construct(JSContext* cx, + JSObject* proto, + PRUint64 data, + bool isUnsigned) +{ + JSClass* clasp = isUnsigned ? &sUInt64Class : &sInt64Class; + JSObject* result = JS_NewObject(cx, clasp, proto, NULL); + if (!result) + return NULL; + JSAutoTempValueRooter root(cx, result); + + // attach the Int64's data + PRUint64* buffer = new PRUint64(data); + if (!buffer) { + JS_ReportOutOfMemory(cx); + return NULL; + } + + if (!JS_SetReservedSlot(cx, result, SLOT_INT64, PRIVATE_TO_JSVAL(buffer))) { + delete buffer; + return NULL; + } + + if (!JS_SealObject(cx, result, JS_FALSE)) + return NULL; + + return result; +} + +void +Int64Base::Finalize(JSContext* cx, JSObject* obj) +{ + jsval slot; + if (!JS_GetReservedSlot(cx, obj, SLOT_INT64, &slot) || JSVAL_IS_VOID(slot)) + return; + + delete static_cast(JSVAL_TO_PRIVATE(slot)); +} + +PRUint64 +Int64Base::GetInt(JSContext* cx, JSObject* obj) { + JS_ASSERT(Int64::IsInt64(cx, obj) || UInt64::IsUInt64(cx, obj)); + + jsval slot; + JS_GetReservedSlot(cx, obj, SLOT_INT64, &slot); + return *static_cast(JSVAL_TO_PRIVATE(slot)); +} + +JSBool +Int64Base::ToString(JSContext* cx, + JSObject* obj, + uintN argc, + jsval *vp, + bool isUnsigned) +{ + if (argc > 1) { + JS_ReportError(cx, "toString takes zero or one argument"); + return JS_FALSE; + } + + jsuint radix = 10; + if (argc == 1) { + jsval arg = JS_ARGV(cx, vp)[0]; + if (JSVAL_IS_INT(arg)) + radix = JSVAL_TO_INT(arg); + if (!JSVAL_IS_INT(arg) || radix < 2 || radix > 36) { + JS_ReportError(cx, "radix argument must be an integer between 2 and 36"); + return JS_FALSE; + } + } + + nsCAutoString intString; + if (isUnsigned) { + intString = IntegerToString(GetInt(cx, obj), radix); + } else { + intString = IntegerToString(static_cast(GetInt(cx, obj)), radix); + } + + JSString *result = JS_NewStringCopyN(cx, intString.get(), intString.Length()); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +Int64Base::ToSource(JSContext* cx, + JSObject* obj, + uintN argc, + jsval *vp, + bool isUnsigned) +{ + if (argc != 0) { + JS_ReportError(cx, "toSource takes zero arguments"); + return JS_FALSE; + } + + // Return a decimal string suitable for constructing the number. + nsCAutoString source; + if (isUnsigned) { + source.Append("ctypes.UInt64(\""); + source.Append(IntegerToString(GetInt(cx, obj), 10)); + } else { + source.Append("ctypes.Int64(\""); + source.Append(IntegerToString(static_cast(GetInt(cx, obj)), 10)); + } + source.Append(')'); + + JSString *result = JS_NewStringCopyN(cx, source.get(), source.Length()); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +Int64::Construct(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + // Construct and return a new Int64 object. + if (argc != 1) { + JS_ReportError(cx, "Int64 takes one argument"); + return JS_FALSE; + } + + PRInt64 i; + if (!jsvalToBigInteger(cx, argv[0], true, &i)) + return TypeError(cx, "int64", argv[0]); + + // Get ctypes.Int64.prototype from the 'prototype' property of the ctor. + jsval slot; + JS_GetProperty(cx, JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)), "prototype", &slot); + JSObject* proto = JSVAL_TO_OBJECT(slot); + JS_ASSERT(JS_GET_CLASS(cx, proto) == &sInt64Proto); + + JSObject* result = Int64Base::Construct(cx, proto, i, false); + if (!result) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(result); + return JS_TRUE; +} + +bool +Int64::IsInt64(JSContext* cx, JSObject* obj) +{ + return JS_GET_CLASS(cx, obj) == &sInt64Class; +} + +JSBool +Int64::ToString(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!Int64::IsInt64(cx, obj)) { + JS_ReportError(cx, "not an Int64"); + return JS_FALSE; + } + + return Int64Base::ToString(cx, obj, argc, vp, false); +} + +JSBool +Int64::ToSource(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!Int64::IsInt64(cx, obj)) { + JS_ReportError(cx, "not an Int64"); + return JS_FALSE; + } + + return Int64Base::ToSource(cx, obj, argc, vp, false); +} + +JSBool +Int64::Compare(JSContext* cx, uintN argc, jsval* vp) +{ + jsval* argv = JS_ARGV(cx, vp); + if (argc != 2 || + !JSVAL_IS_OBJECT(argv[0]) || JSVAL_IS_NULL(argv[0]) || + !JSVAL_IS_OBJECT(argv[1]) || JSVAL_IS_NULL(argv[1]) || + !Int64::IsInt64(cx, JSVAL_TO_OBJECT(argv[0])) || + !Int64::IsInt64(cx, JSVAL_TO_OBJECT(argv[1]))) { + JS_ReportError(cx, "compare takes two Int64 arguments"); + return JS_FALSE; + } + + JSObject* obj1 = JSVAL_TO_OBJECT(argv[0]); + JSObject* obj2 = JSVAL_TO_OBJECT(argv[1]); + + PRInt64 i1 = GetInt(cx, obj1); + PRInt64 i2 = GetInt(cx, obj2); + + if (i1 == i2) + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(0)); + else if (i1 < i2) + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(-1)); + else + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(1)); + + return JS_TRUE; +} + +#define LO_MASK ((PRUint64(1) << 32) - 1) +#define INT64_LO(i) ((i) & LO_MASK) +#define INT64_HI(i) ((i) >> 32) + +JSBool +Int64::Lo(JSContext* cx, uintN argc, jsval* vp) +{ + jsval arg = JS_ARGV(cx, vp)[0]; + if (argc != 1 || !JSVAL_IS_OBJECT(arg) || JSVAL_IS_NULL(arg) || + !Int64::IsInt64(cx, JSVAL_TO_OBJECT(arg))) { + JS_ReportError(cx, "lo takes one Int64 argument"); + return JS_FALSE; + } + + JSObject* obj = JSVAL_TO_OBJECT(arg); + PRInt64 u = GetInt(cx, obj); + jsdouble d = PRUint32(INT64_LO(u)); + + jsval result; + if (!JS_NewNumberValue(cx, d, &result)) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, result); + return JS_TRUE; +} + +JSBool +Int64::Hi(JSContext* cx, uintN argc, jsval* vp) +{ + jsval arg = JS_ARGV(cx, vp)[0]; + if (argc != 1 || !JSVAL_IS_OBJECT(arg) || JSVAL_IS_NULL(arg) || + !Int64::IsInt64(cx, JSVAL_TO_OBJECT(arg))) { + JS_ReportError(cx, "lo takes one Int64 argument"); + return JS_FALSE; + } + + JSObject* obj = JSVAL_TO_OBJECT(arg); + PRInt64 u = GetInt(cx, obj); + jsdouble d = PRInt32(INT64_HI(u)); + + jsval result; + if (!JS_NewNumberValue(cx, d, &result)) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, result); + return JS_TRUE; +} + +JSBool +Int64::Join(JSContext* cx, uintN argc, jsval* vp) +{ + if (argc != 2) { + JS_ReportError(cx, "join takes two arguments"); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(cx, vp); + PRInt32 hi; + PRUint32 lo; + if (!jsvalToInteger(cx, argv[0], &hi)) + return TypeError(cx, "int32", argv[0]); + if (!jsvalToInteger(cx, argv[1], &lo)) + return TypeError(cx, "uint32", argv[1]); + + PRInt64 i = (PRInt64(hi) << 32) + PRInt64(lo); + + // Get Int64.prototype from the function's reserved slot. + JSObject* callee = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)); + + jsval slot; + JS_GetReservedSlot(cx, callee, SLOT_FN_INT64PROTO, &slot); + JSObject* proto = JSVAL_TO_OBJECT(slot); + JS_ASSERT(JS_GET_CLASS(cx, proto) == &sInt64Proto); + + JSObject* result = Int64Base::Construct(cx, proto, i, false); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + return JS_TRUE; +} + +JSBool +UInt64::Construct(JSContext* cx, + JSObject* obj, + uintN argc, + jsval* argv, + jsval* rval) +{ + // Construct and return a new UInt64 object. + if (argc != 1) { + JS_ReportError(cx, "UInt64 takes one argument"); + return JS_FALSE; + } + + PRUint64 u; + if (!jsvalToBigInteger(cx, argv[0], true, &u)) + return TypeError(cx, "uint64", argv[0]); + + // Get ctypes.UInt64.prototype from the 'prototype' property of the ctor. + jsval slot; + JS_GetProperty(cx, JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)), "prototype", &slot); + JSObject* proto = JSVAL_TO_OBJECT(slot); + JS_ASSERT(JS_GET_CLASS(cx, proto) == &sUInt64Proto); + + JSObject* result = Int64Base::Construct(cx, proto, u, true); + if (!result) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(result); + return JS_TRUE; +} + +bool +UInt64::IsUInt64(JSContext* cx, JSObject* obj) +{ + return JS_GET_CLASS(cx, obj) == &sUInt64Class; +} + +JSBool +UInt64::ToString(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!UInt64::IsUInt64(cx, obj)) { + JS_ReportError(cx, "not a UInt64"); + return JS_FALSE; + } + + return Int64Base::ToString(cx, obj, argc, vp, true); +} + +JSBool +UInt64::ToSource(JSContext* cx, uintN argc, jsval *vp) +{ + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!UInt64::IsUInt64(cx, obj)) { + JS_ReportError(cx, "not a UInt64"); + return JS_FALSE; + } + + return Int64Base::ToSource(cx, obj, argc, vp, true); +} + +JSBool +UInt64::Compare(JSContext* cx, uintN argc, jsval* vp) +{ + jsval* argv = JS_ARGV(cx, vp); + if (argc != 2 || + !JSVAL_IS_OBJECT(argv[0]) || JSVAL_IS_NULL(argv[0]) || + !JSVAL_IS_OBJECT(argv[1]) || JSVAL_IS_NULL(argv[1]) || + !UInt64::IsUInt64(cx, JSVAL_TO_OBJECT(argv[0])) || + !UInt64::IsUInt64(cx, JSVAL_TO_OBJECT(argv[1]))) { + JS_ReportError(cx, "compare takes two UInt64 arguments"); + return JS_FALSE; + } + + JSObject* obj1 = JSVAL_TO_OBJECT(argv[0]); + JSObject* obj2 = JSVAL_TO_OBJECT(argv[1]); + + PRUint64 u1 = GetInt(cx, obj1); + PRUint64 u2 = GetInt(cx, obj2); + + if (u1 == u2) + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(0)); + else if (u1 < u2) + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(-1)); + else + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(1)); + + return JS_TRUE; +} + +JSBool +UInt64::Lo(JSContext* cx, uintN argc, jsval* vp) +{ + jsval arg = JS_ARGV(cx, vp)[0]; + if (argc != 1 || !JSVAL_IS_OBJECT(arg) || JSVAL_IS_NULL(arg) || + !UInt64::IsUInt64(cx, JSVAL_TO_OBJECT(arg))) { + JS_ReportError(cx, "lo takes one UInt64 argument"); + return JS_FALSE; + } + + JSObject* obj = JSVAL_TO_OBJECT(arg); + PRUint64 u = GetInt(cx, obj); + jsdouble d = PRUint32(INT64_LO(u)); + + jsval result; + if (!JS_NewNumberValue(cx, d, &result)) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, result); + return JS_TRUE; +} + +JSBool +UInt64::Hi(JSContext* cx, uintN argc, jsval* vp) +{ + jsval arg = JS_ARGV(cx, vp)[0]; + if (argc != 1 || !JSVAL_IS_OBJECT(arg) || JSVAL_IS_NULL(arg) || + !UInt64::IsUInt64(cx, JSVAL_TO_OBJECT(arg))) { + JS_ReportError(cx, "lo takes one UInt64 argument"); + return JS_FALSE; + } + + JSObject* obj = JSVAL_TO_OBJECT(arg); + PRUint64 u = GetInt(cx, obj); + jsdouble d = PRUint32(INT64_HI(u)); + + jsval result; + if (!JS_NewNumberValue(cx, d, &result)) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, result); + return JS_TRUE; +} + +JSBool +UInt64::Join(JSContext* cx, uintN argc, jsval* vp) +{ + if (argc != 2) { + JS_ReportError(cx, "join takes two arguments"); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(cx, vp); + PRUint32 hi; + PRUint32 lo; + if (!jsvalToInteger(cx, argv[0], &hi)) + return TypeError(cx, "uint32_t", argv[0]); + if (!jsvalToInteger(cx, argv[1], &lo)) + return TypeError(cx, "uint32_t", argv[1]); + + PRUint64 u = (PRUint64(hi) << 32) + PRUint64(lo); + + // Get Int64.prototype from the function's reserved slot. + JSObject* callee = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)); + + jsval slot; + JS_GetReservedSlot(cx, callee, SLOT_FN_INT64PROTO, &slot); + JSObject* proto = JSVAL_TO_OBJECT(slot); + JS_ASSERT(JS_GET_CLASS(cx, proto) == &sUInt64Proto); + + JSObject* result = Int64Base::Construct(cx, proto, u, true); + if (!result) + return JS_FALSE; + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); + return JS_TRUE; +} + +} +} + diff --git a/js/ctypes/CTypes.h b/js/ctypes/CTypes.h new file mode 100644 index 000000000000..e2e984a542a0 --- /dev/null +++ b/js/ctypes/CTypes.h @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is js-ctypes. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation . + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dan Witte + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef CTYPES_H +#define CTYPES_H + +#include "jsapi.h" +#include "nsString.h" +#include "ffi.h" + +namespace mozilla { +namespace ctypes { + +// for JS error reporting +enum ErrorNum { +#define MSG_DEF(name, number, count, exception, format) \ + name = number, +#include "ctypes.msg" +#undef MSG_DEF + CTYPESERR_LIMIT +}; + +const JSErrorFormatString* +GetErrorMessage(void* userRef, const char* locale, const uintN errorNumber); +bool TypeError(JSContext* cx, const char* expected, jsval actual); + +/** + * ABI constants that specify the calling convention to use. + * ctypes.default_abi corresponds to the cdecl convention, and in almost all + * cases is the correct choice. ctypes.stdcall_abi is provided for calling + * functions in the Microsoft Win32 API. + */ +enum ABICode { + ABI_DEFAULT, + ABI_STDCALL, + INVALID_ABI +}; + +enum TypeCode { + TYPE_void_t, +#define DEFINE_TYPE(name, type, ffiType) TYPE_##name, +#include "typedefs.h" + TYPE_pointer, + TYPE_array, + TYPE_struct +}; + +ABICode GetABICode(JSContext* cx, JSObject* obj); + +struct FieldInfo +{ + nsCString mName; + JSObject* mType; + size_t mOffset; +}; + +bool InitTypeClasses(JSContext* cx, JSObject* parent); + +bool ConvertToJS(JSContext* cx, JSObject* typeObj, JSObject* dataObj, void* data, bool wantPrimitive, jsval* result); +bool ImplicitConvert(JSContext* cx, jsval val, JSObject* targetType, void* buffer, bool isArgument, bool* freePointer); +bool ExplicitConvert(JSContext* cx, jsval val, JSObject* targetType, void* buffer); + +// Contents of the various slots on each JSClass. The slot indexes are given +// enumerated names for readability. +enum CABISlot { + SLOT_ABICODE = 0, // ABICode of the CABI object + CABI_SLOTS +}; + +enum CTypeProtoSlot { + SLOT_POINTERPROTO = 0, // ctypes.PointerType.prototype object + SLOT_ARRAYPROTO = 1, // ctypes.ArrayType.prototype object + SLOT_STRUCTPROTO = 2, // ctypes.StructType.prototype object + SLOT_INT64PROTO = 3, // ctypes.Int64.prototype object + SLOT_UINT64PROTO = 4, // ctypes.UInt64.prototype object + CTYPEPROTO_SLOTS +}; + +enum CTypeSlot { + SLOT_TYPECODE = 0, // TypeCode of the CType object + SLOT_FFITYPE = 1, // ffi_type representing the type + SLOT_ALIGN = 2, // alignment of the data type, in bytes + SLOT_PTR = 3, // cached PointerType object for type.ptr + SLOT_FIELDINFO = 4, // (StructTypes only) FieldInfo array + CTYPE_SLOTS +}; + +enum CDataSlot { + SLOT_CTYPE = 0, // CType object representing the underlying type + SLOT_REFERENT = 1, // CData object this object refers to, if any + SLOT_DATA = 2, // pointer to a buffer containing the binary data + CDATA_SLOTS +}; + +enum Int64Slot { + SLOT_INT64 = 0, // pointer to a 64-bit buffer containing the integer + INT64_SLOTS +}; + +enum Int64FunctionSlot { + SLOT_FN_INT64PROTO = 0 // ctypes.{Int64,UInt64}.prototype object + // JSFunction objects always get exactly two slots. +}; + +class CType { +public: + static JSObject* Create(JSContext* cx, JSObject* proto, JSString* name, TypeCode type, jsval size, jsval align, ffi_type* ffiType); + static JSObject* DefineBuiltin(JSContext* cx, JSObject* parent, const char* propName, JSObject* proto, const char* name, TypeCode type, jsval size, jsval align, ffi_type* ffiType); + static void Finalize(JSContext* cx, JSObject* obj); + + static JSBool ConstructAbstract(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + static JSBool ConstructData(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + static JSBool ConstructBasic(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + + static bool IsCType(JSContext* cx, JSObject* obj); + static TypeCode GetTypeCode(JSContext* cx, JSObject* typeObj); + static bool TypesEqual(JSContext* cx, JSObject* t1, JSObject* t2); + static size_t GetSize(JSContext* cx, JSObject* obj); + static bool GetSafeSize(JSContext* cx, JSObject* obj, size_t* result); + static bool IsSizeDefined(JSContext* cx, JSObject* obj); + static size_t GetAlignment(JSContext* cx, JSObject* obj); + static ffi_type* GetFFIType(JSContext* cx, JSObject* obj); + static JSString* GetName(JSContext* cx, JSObject* obj); + static JSObject* GetProtoFromCtor(JSContext* cx, JSObject* obj, CTypeProtoSlot slot); + static JSObject* GetProtoFromType(JSContext* cx, JSObject* obj, CTypeProtoSlot slot); + + static JSBool PtrGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool Array(JSContext* cx, uintN argc, jsval* vp); + static JSBool ToString(JSContext* cx, uintN argc, jsval* vp); + static JSBool ToSource(JSContext* cx, uintN argc, jsval* vp); +}; + +class PointerType { +public: + static JSBool Create(JSContext* cx, uintN argc, jsval* vp); + static JSObject* CreateInternal(JSContext* cx, JSObject* ctor, JSObject* baseType, JSString* name); + + static JSBool ConstructData(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + static JSObject* ConstructInternal(JSContext* cx, JSObject* typeObj, JSObject* parentObj, void* data); + + static JSObject* GetBaseType(JSContext* cx, JSObject* obj); + + static JSBool ContentsGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool ContentsSetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); +}; + +class ArrayType { +public: + static JSBool Create(JSContext* cx, uintN argc, jsval* vp); + static JSObject* CreateInternal(JSContext* cx, JSObject* baseType, size_t length, bool lengthDefined); + + static JSBool ConstructData(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + static JSObject* ConstructInternal(JSContext* cx, JSObject* typeObj, JSObject* parentObj, void* data); + + static JSObject* GetBaseType(JSContext* cx, JSObject* obj); + static size_t GetLength(JSContext* cx, JSObject* obj); + static bool GetSafeLength(JSContext* cx, JSObject* obj, size_t* result); + + static JSBool Getter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool Setter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool AddressOfElement(JSContext* cx, uintN argc, jsval* vp); +}; + +class StructType { +public: + static JSBool Create(JSContext* cx, uintN argc, jsval* vp); + + static JSBool ConstructData(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + static JSObject* ConstructInternal(JSContext* cx, JSObject* typeObj, JSObject* parentObj, void* data); + + static nsTArray* GetFieldInfo(JSContext* cx, JSObject* obj); + static FieldInfo* LookupField(JSContext* cx, JSObject* obj, jsval idval); + + static JSBool FieldGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool FieldSetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool AddressOfField(JSContext* cx, uintN argc, jsval* vp); +}; + +class CData { +public: + static JSObject* Create(JSContext* cx, JSObject* type, JSObject* base, void* data); + static void Finalize(JSContext* cx, JSObject* obj); + + static JSObject* GetCType(JSContext* cx, JSObject* dataObj); + static void* GetData(JSContext* cx, JSObject* dataObj); + static bool IsCData(JSContext* cx, JSObject* obj); + + static JSBool ValueGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool ValueSetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp); + static JSBool Address(JSContext* cx, uintN argc, jsval* vp); + static JSBool Cast(JSContext* cx, uintN argc, jsval* vp); + static JSBool ReadString(JSContext* cx, uintN argc, jsval* vp); + static JSBool ToSource(JSContext* cx, uintN argc, jsval* vp); +}; + +class Int64Base { +public: + static JSObject* Construct(JSContext* cx, JSObject* proto, PRUint64 data, bool isUnsigned); + static void Finalize(JSContext* cx, JSObject* obj); + + static PRUint64 GetInt(JSContext* cx, JSObject* obj); + + static JSBool ToString(JSContext* cx, JSObject* obj, uintN argc, jsval* vp, bool isUnsigned); + static JSBool ToSource(JSContext* cx, JSObject* obj, uintN argc, jsval* vp, bool isUnsigned); +}; + +class Int64 : public Int64Base { +public: + static JSBool Construct(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + + static bool IsInt64(JSContext* cx, JSObject* obj); + + static JSBool ToString(JSContext* cx, uintN argc, jsval* vp); + static JSBool ToSource(JSContext* cx, uintN argc, jsval* vp); + + // ctypes.Int64 static functions + static JSBool Compare(JSContext* cx, uintN argc, jsval* vp); + static JSBool Lo(JSContext* cx, uintN argc, jsval* vp); + static JSBool Hi(JSContext* cx, uintN argc, jsval* vp); + static JSBool Join(JSContext* cx, uintN argc, jsval* vp); +}; + +class UInt64 : public Int64Base { +public: + static JSBool Construct(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval); + + static bool IsUInt64(JSContext* cx, JSObject* obj); + + static JSBool ToString(JSContext* cx, uintN argc, jsval* vp); + static JSBool ToSource(JSContext* cx, uintN argc, jsval* vp); + + // ctypes.UInt64 static functions + static JSBool Compare(JSContext* cx, uintN argc, jsval* vp); + static JSBool Lo(JSContext* cx, uintN argc, jsval* vp); + static JSBool Hi(JSContext* cx, uintN argc, jsval* vp); + static JSBool Join(JSContext* cx, uintN argc, jsval* vp); +}; + +} +} + +#endif diff --git a/js/ctypes/Function.cpp b/js/ctypes/Function.cpp index c333094d12df..0747be44d9b7 100644 --- a/js/ctypes/Function.cpp +++ b/js/ctypes/Function.cpp @@ -50,439 +50,101 @@ namespace ctypes { ** Static helpers *******************************************************************************/ -template -static IntegerType -Convert(jsdouble d) -{ - return IntegerType(d); -} - -#ifdef _MSC_VER -// MSVC can't perform double to unsigned __int64 conversion when the -// double is greater than 2^63 - 1. Help it along a little. -template<> -static PRUint64 -Convert(jsdouble d) -{ - return d > 0x7fffffffffffffffui64 ? - PRUint64(d - 0x8000000000000000ui64) + 0x8000000000000000ui64 : - PRUint64(d); -} -#endif - -template -static bool -jsvalToIntStrict(jsval aValue, IntegerType *aResult) -{ - if (JSVAL_IS_INT(aValue)) { - jsint i = JSVAL_TO_INT(aValue); - *aResult = IntegerType(i); - - // Make sure the integer fits in the alotted precision, and has the right sign. - return jsint(*aResult) == i && - (i < 0) == (*aResult < 0); - } - if (JSVAL_IS_DOUBLE(aValue)) { - jsdouble d = *JSVAL_TO_DOUBLE(aValue); - *aResult = Convert(d); - - // Don't silently lose bits here -- check that aValue really is an - // integer value, and has the right sign. - return jsdouble(*aResult) == d && - (d < 0) == (*aResult < 0); - } - if (JSVAL_IS_BOOLEAN(aValue)) { - // Implicitly promote boolean values to 0 or 1, like C. - *aResult = JSVAL_TO_BOOLEAN(aValue); - NS_ASSERTION(*aResult == 0 || *aResult == 1, "invalid boolean"); - return true; - } - // Don't silently convert null to an integer. It's probably a mistake. - return false; -} - -static bool -jsvalToDoubleStrict(jsval aValue, jsdouble *dp) -{ - // 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. - if (JSVAL_IS_INT(aValue)) { - *dp = JSVAL_TO_INT(aValue); - return true; - } - if (JSVAL_IS_DOUBLE(aValue)) { - *dp = *JSVAL_TO_DOUBLE(aValue); - return true; - } - return false; -} - -JSErrorFormatString ErrorFormatString[CTYPESERR_LIMIT] = { -#define MSG_DEF(name, number, count, exception, format) \ - { format, count, exception } , -#include "ctypes.msg" -#undef MSG_DEF -}; - -const JSErrorFormatString* -GetErrorMessage(void* userRef, const char* locale, const uintN errorNumber) -{ - if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT) - return &ErrorFormatString[errorNumber]; - return NULL; -} - -static const char* -ToSource(JSContext* cx, jsval vp) -{ - JSString* str = JS_ValueToSource(cx, vp); - if (str) - return JS_GetStringBytes(str); - - JS_ClearPendingException(cx); - return "<>"; -} - -static bool -TypeError(JSContext* cx, const char* expected, jsval actual) -{ - const char* src = ToSource(cx, actual); - JS_ReportErrorNumber(cx, GetErrorMessage, NULL, - CTYPESMSG_TYPE_ERROR, expected, src); - return false; -} - static bool GetABI(JSContext* cx, jsval aCallType, ffi_abi& aResult) { - ABICode abi = Module::GetABICode(cx, aCallType); + if (!JSVAL_IS_OBJECT(aCallType) || JSVAL_IS_NULL(aCallType)) + return false; + + ABICode abi = GetABICode(cx, JSVAL_TO_OBJECT(aCallType)); // determine the ABI from the subset of those available on the - // given platform. TYPE_DEFAULT specifies the default + // given platform. ABI_DEFAULT specifies the default // C calling convention (cdecl) on each platform. switch (abi) { - case ABI_default_abi: + case ABI_DEFAULT: aResult = FFI_DEFAULT_ABI; return true; + case ABI_STDCALL: #if (defined(_WIN32) && !defined(_WIN64)) || defined(_OS2) - case ABI_stdcall_abi: aResult = FFI_STDCALL; return true; #endif - default: - return false; + case INVALID_ABI: + break; } + return false; } static bool PrepareType(JSContext* aContext, jsval aType, Type& aResult) { - aResult.mType = Module::GetTypeCode(aContext, aType); - - switch (aResult.mType) { - case TYPE_void_t: - aResult.mFFIType = ffi_type_void; - break; - case TYPE_int8_t: - aResult.mFFIType = ffi_type_sint8; - break; - case TYPE_int16_t: - aResult.mFFIType = ffi_type_sint16; - break; - case TYPE_int32_t: - aResult.mFFIType = ffi_type_sint32; - break; - case TYPE_int64_t: - aResult.mFFIType = ffi_type_sint64; - break; - case TYPE_bool: - case TYPE_uint8_t: - aResult.mFFIType = ffi_type_uint8; - break; - case TYPE_uint16_t: - aResult.mFFIType = ffi_type_uint16; - break; - case TYPE_uint32_t: - aResult.mFFIType = ffi_type_uint32; - break; - case TYPE_uint64_t: - aResult.mFFIType = ffi_type_uint64; - break; - case TYPE_float: - aResult.mFFIType = ffi_type_float; - break; - case TYPE_double: - aResult.mFFIType = ffi_type_double; - break; - case TYPE_string: - case TYPE_ustring: - aResult.mFFIType = ffi_type_pointer; - break; - default: - JS_ReportError(aContext, "Invalid type specification"); + if (!JSVAL_IS_OBJECT(aType) || + JSVAL_IS_NULL(aType) || + !CType::IsCType(aContext, JSVAL_TO_OBJECT(aType))) { + JS_ReportError(aContext, "not a ctypes type"); return false; } + JSObject* typeObj = JSVAL_TO_OBJECT(aType); + TypeCode typeCode = CType::GetTypeCode(aContext, typeObj); + + if (typeCode == TYPE_array) { + // convert array argument types to pointers, just like C. + // ImplicitConvert will do the same, when passing an array as data. + JSObject* baseType = ArrayType::GetBaseType(aContext, typeObj); + typeObj = PointerType::CreateInternal(aContext, NULL, baseType, NULL); + if (!typeObj) { + JS_ReportError(aContext, "couldn't create pointer type from array"); + return false; + } + } else if (typeCode == TYPE_void_t) { + // disallow void argument types + JS_ReportError(aContext, "Cannot have void argument type"); + return false; + } + + // libffi cannot pass types of zero size by value. + JS_ASSERT(CType::GetSize(aContext, typeObj) != 0); + + aResult.mType = typeObj; + aResult.mFFIType = *CType::GetFFIType(aContext, typeObj); return true; } static bool -PrepareValue(JSContext* aContext, const Type& aType, jsval aValue, Value& aResult) +PrepareResultType(JSContext* aContext, jsval aType, Type& aResult) { - jsdouble d; - - switch (aType.mType) { - 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`. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mUint8) || - aResult.mValue.mUint8 > 1) - return TypeError(aContext, "boolean", aValue); - - aResult.mData = &aResult.mValue.mUint8; - break; - case TYPE_int8_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mInt8)) - return TypeError(aContext, "int8", aValue); - - aResult.mData = &aResult.mValue.mInt8; - break; - case TYPE_int16_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mInt16)) - return TypeError(aContext, "int16", aValue); - - aResult.mData = &aResult.mValue.mInt16; - break; - case TYPE_int32_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mInt32)) - return TypeError(aContext, "int32", aValue); - - aResult.mData = &aResult.mValue.mInt32; - break; - case TYPE_int64_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mInt64)) - return TypeError(aContext, "int64", aValue); - - aResult.mData = &aResult.mValue.mInt64; - break; - case TYPE_uint8_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mUint8)) - return TypeError(aContext, "uint8", aValue); - - aResult.mData = &aResult.mValue.mUint8; - break; - case TYPE_uint16_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mUint16)) - return TypeError(aContext, "uint16", aValue); - - aResult.mData = &aResult.mValue.mUint16; - break; - case TYPE_uint32_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mUint32)) - return TypeError(aContext, "uint32", aValue); - - aResult.mData = &aResult.mValue.mUint32; - break; - case TYPE_uint64_t: - // Do not implicitly lose bits. - if (!jsvalToIntStrict(aValue, &aResult.mValue.mUint64)) - return TypeError(aContext, "uint64", aValue); - - aResult.mData = &aResult.mValue.mUint64; - break; - case TYPE_float: - if (!jsvalToDoubleStrict(aValue, &d)) - return TypeError(aContext, "float", aValue); - - // The following cast silently throws 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. - aResult.mValue.mFloat = float(d); - aResult.mData = &aResult.mValue.mFloat; - break; - case TYPE_double: - if (!jsvalToDoubleStrict(aValue, &d)) - return TypeError(aContext, "double", aValue); - - aResult.mValue.mDouble = d; - aResult.mData = &aResult.mValue.mDouble; - break; - case TYPE_string: - if (JSVAL_IS_NULL(aValue)) { - // Allow passing a null pointer. - aResult.mValue.mPointer = nsnull; - } else if (JSVAL_IS_STRING(aValue)) { - aResult.mValue.mPointer = JS_GetStringBytes(JSVAL_TO_STRING(aValue)); - } else { - // Don't implicitly convert to string. Users can implicitly convert - // with `String(x)` or `""+x`. - return TypeError(aContext, "string", aValue); - } - - aResult.mData = &aResult.mValue.mPointer; - break; - case TYPE_ustring: - if (JSVAL_IS_NULL(aValue)) { - // Allow passing a null pointer. - aResult.mValue.mPointer = nsnull; - } else if (JSVAL_IS_STRING(aValue)) { - aResult.mValue.mPointer = JS_GetStringChars(JSVAL_TO_STRING(aValue)); - } else { - // Don't implicitly convert to string. Users can implicitly convert - // with `String(x)` or `""+x`. - return TypeError(aContext, "ustring", aValue); - } - - aResult.mData = &aResult.mValue.mPointer; - break; - default: - NS_NOTREACHED("invalid type"); + if (!JSVAL_IS_OBJECT(aType) || + JSVAL_IS_NULL(aType) || + !CType::IsCType(aContext, JSVAL_TO_OBJECT(aType))) { + JS_ReportError(aContext, "not a ctypes type"); return false; } - return true; -} + JSObject* typeObj = JSVAL_TO_OBJECT(aType); + TypeCode typeCode = CType::GetTypeCode(aContext, typeObj); -static void -PrepareReturnValue(const Type& aType, Value& aResult) -{ - switch (aType.mType) { - case TYPE_void_t: - aResult.mData = nsnull; - break; - case TYPE_int8_t: - aResult.mData = &aResult.mValue.mInt8; - break; - case TYPE_int16_t: - aResult.mData = &aResult.mValue.mInt16; - break; - case TYPE_int32_t: - aResult.mData = &aResult.mValue.mInt32; - break; - case TYPE_int64_t: - aResult.mData = &aResult.mValue.mInt64; - break; - case TYPE_bool: - case TYPE_uint8_t: - aResult.mData = &aResult.mValue.mUint8; - break; - case TYPE_uint16_t: - aResult.mData = &aResult.mValue.mUint16; - break; - case TYPE_uint32_t: - aResult.mData = &aResult.mValue.mUint32; - break; - case TYPE_uint64_t: - aResult.mData = &aResult.mValue.mUint64; - break; - case TYPE_float: - aResult.mData = &aResult.mValue.mFloat; - break; - case TYPE_double: - aResult.mData = &aResult.mValue.mDouble; - break; - case TYPE_string: - case TYPE_ustring: - aResult.mData = &aResult.mValue.mPointer; - break; - default: - NS_NOTREACHED("invalid type"); - break; - } -} - -static bool -ConvertReturnValue(JSContext* aContext, - const Type& aResultType, - const Value& aResultValue, - jsval* aValue) -{ - switch (aResultType.mType) { - case TYPE_void_t: - *aValue = JSVAL_VOID; - break; - case TYPE_bool: - *aValue = aResultValue.mValue.mUint8 ? JSVAL_TRUE : JSVAL_FALSE; - break; - case TYPE_int8_t: - *aValue = INT_TO_JSVAL(aResultValue.mValue.mInt8); - break; - case TYPE_int16_t: - *aValue = INT_TO_JSVAL(aResultValue.mValue.mInt16); - break; - case TYPE_int32_t: - if (!JS_NewNumberValue(aContext, jsdouble(aResultValue.mValue.mInt32), aValue)) - return false; - break; - case TYPE_int64_t: - // Implicit conversion with loss of bits. :-[ - if (!JS_NewNumberValue(aContext, jsdouble(aResultValue.mValue.mInt64), aValue)) - return false; - break; - case TYPE_uint8_t: - *aValue = INT_TO_JSVAL(aResultValue.mValue.mUint8); - break; - case TYPE_uint16_t: - *aValue = INT_TO_JSVAL(aResultValue.mValue.mUint16); - break; - case TYPE_uint32_t: - if (!JS_NewNumberValue(aContext, jsdouble(aResultValue.mValue.mUint32), aValue)) - return false; - break; - case TYPE_uint64_t: - // Implicit conversion with loss of bits. :-[ - if (!JS_NewNumberValue(aContext, jsdouble(aResultValue.mValue.mUint64), aValue)) - return false; - break; - case TYPE_float: - if (!JS_NewNumberValue(aContext, jsdouble(aResultValue.mValue.mFloat), aValue)) - return false; - break; - case TYPE_double: - if (!JS_NewNumberValue(aContext, jsdouble(aResultValue.mValue.mDouble), aValue)) - return false; - break; - case TYPE_string: { - if (!aResultValue.mValue.mPointer) { - // Allow returning a null pointer. - *aValue = JSVAL_NULL; - } else { - JSString *jsstring = JS_NewStringCopyZ(aContext, - reinterpret_cast(aResultValue.mValue.mPointer)); - if (!jsstring) - return false; - - *aValue = STRING_TO_JSVAL(jsstring); - } - break; - } - case TYPE_ustring: { - if (!aResultValue.mValue.mPointer) { - // Allow returning a null pointer. - *aValue = JSVAL_NULL; - } else { - JSString *jsstring = JS_NewUCStringCopyZ(aContext, - reinterpret_cast(aResultValue.mValue.mPointer)); - if (!jsstring) - return false; - - *aValue = STRING_TO_JSVAL(jsstring); - } - break; - } - default: - NS_NOTREACHED("invalid type"); + // Arrays can never be return types. + if (typeCode == TYPE_array) { + JS_ReportError(aContext, "Result type cannot be an array"); return false; } +#ifdef _MSC_VER + // Our libffi_msvc fork doesn't support returning structs by value yet. + if (typeCode == TYPE_struct) { + JS_ReportError(aContext, + "Returning structs by value is unsupported on Windows"); + return false; + } +#endif + + // libffi cannot pass types of zero size by value. + JS_ASSERT(typeCode == TYPE_void_t || CType::GetSize(aContext, typeObj) != 0); + + aResult.mType = typeObj; + aResult.mFFIType = *CType::GetFFIType(aContext, typeObj); return true; } @@ -516,7 +178,7 @@ Function::Init(JSContext* aContext, } // prepare the result type - if (!PrepareType(aContext, aResultType, mResultType)) + if (!PrepareResultType(aContext, aResultType, mResultType)) return false; // prepare the argument types @@ -525,12 +187,6 @@ Function::Init(JSContext* aContext, if (!PrepareType(aContext, aArgTypes[i], *mArgTypes.AppendElement())) return false; - // disallow void argument types - if (mArgTypes[i].mType == TYPE_void_t) { - JS_ReportError(aContext, "Cannot have void argument type"); - return false; - } - // ffi_prep_cif requires an array of ffi_types; prepare it separately. mFFITypes.AppendElement(&mArgTypes[i].mFFIType); } @@ -561,39 +217,63 @@ Function::Execute(JSContext* cx, PRUint32 argc, jsval* vp) } // prepare the values for each argument - nsAutoTArray values; + nsAutoTArray values; + nsAutoTArray strings; for (PRUint32 i = 0; i < mArgTypes.Length(); ++i) { - if (!PrepareValue(cx, mArgTypes[i], JS_ARGV(cx, vp)[i], *values.AppendElement())) + AutoValue& value = *values.AppendElement(); + jsval arg = JS_ARGV(cx, vp)[i]; + bool freePointer = false; + if (!value.SizeToType(cx, mArgTypes[i].mType)) { + JS_ReportAllocationOverflow(cx); return false; - } + } - // create an array of pointers to each value, for passing to ffi_call - nsAutoTArray ffiValues; - for (PRUint32 i = 0; i < mArgTypes.Length(); ++i) { - ffiValues.AppendElement(values[i].mData); + if (!ImplicitConvert(cx, arg, mArgTypes[i].mType, value.mData, true, &freePointer)) + return false; + + if (freePointer) { + // ImplicitConvert converted a string for us, which we have to free. + // Keep track of it. + strings.AppendElement()->mData = *static_cast(value.mData); + } } // initialize a pointer to an appropriate location, for storing the result - Value resultValue; - PrepareReturnValue(mResultType, resultValue); + AutoValue resultValue; + if (CType::GetTypeCode(cx, mResultType.mType) != TYPE_void_t && + !resultValue.SizeToType(cx, mResultType.mType)) { + JS_ReportAllocationOverflow(cx); + return false; + } // suspend the request before we call into the function, since the call // may block or otherwise take a long time to return. jsrefcount rc = JS_SuspendRequest(cx); - ffi_call(&mCIF, FFI_FN(mFunc), resultValue.mData, ffiValues.Elements()); + ffi_call(&mCIF, FFI_FN(mFunc), resultValue.mData, reinterpret_cast(values.Elements())); JS_ResumeRequest(cx, rc); // prepare a JS object from the result jsval rval; - if (!ConvertReturnValue(cx, mResultType, resultValue, &rval)) + if (!ConvertToJS(cx, mResultType.mType, NULL, resultValue.mData, false, &rval)) return false; JS_SET_RVAL(cx, vp, rval); return true; } +void +Function::Trace(JSTracer *trc) +{ + // Identify the result CType to the tracer. + JS_CALL_TRACER(trc, mResultType.mType, JSTRACE_OBJECT, "CType"); + + // Identify each argument CType to the tracer. + for (PRUint32 i = 0; i < mArgTypes.Length(); ++i) + JS_CALL_TRACER(trc, mArgTypes[i].mType, JSTRACE_OBJECT, "CType"); +} + /******************************************************************************* ** JSObject implementation *******************************************************************************/ @@ -627,15 +307,16 @@ Function::Create(JSContext* aContext, JSAutoTempValueRooter fnRoot(aContext, fnObj); // stash a pointer to self, which Function::Call will need at call time - if (!JS_SetReservedSlot(aContext, fnObj, 0, PRIVATE_TO_JSVAL(self.get()))) + if (!JS_SetReservedSlot(aContext, fnObj, SLOT_FUNCTION, PRIVATE_TO_JSVAL(self.get()))) return NULL; // make a strong reference to the library for GC-safety - if (!JS_SetReservedSlot(aContext, fnObj, 1, OBJECT_TO_JSVAL(aLibrary))) + if (!JS_SetReservedSlot(aContext, fnObj, SLOT_LIBRARYOBJ, OBJECT_TO_JSVAL(aLibrary))) return NULL; - // tell the library we exist, so it can delete our Function instance - // when it comes time to finalize. (JS functions don't have finalizers.) + // Tell the library we exist, so it can (a) identify our CTypes to the tracer + // and (b) delete our Function instance when it comes time to finalize. + // (JS functions don't have finalizers.) if (!Library::AddFunction(aContext, aLibrary, self)) return NULL; @@ -647,7 +328,7 @@ static Function* GetFunction(JSContext* cx, JSObject* obj) { jsval slot; - JS_GetReservedSlot(cx, obj, 0, &slot); + JS_GetReservedSlot(cx, obj, SLOT_FUNCTION, &slot); return static_cast(JSVAL_TO_PRIVATE(slot)); } @@ -657,7 +338,7 @@ Function::Call(JSContext* cx, uintN argc, jsval* vp) JSObject* callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)); jsval slot; - JS_GetReservedSlot(cx, callee, 1, &slot); + JS_GetReservedSlot(cx, callee, SLOT_LIBRARYOBJ, &slot); PRLibrary* library = Library::GetLibrary(cx, JSVAL_TO_OBJECT(slot)); if (!library) { diff --git a/js/ctypes/Function.h b/js/ctypes/Function.h index 7986bdeb2280..33a8d08ac716 100644 --- a/js/ctypes/Function.h +++ b/js/ctypes/Function.h @@ -40,48 +40,45 @@ #ifndef FUNCTION_H #define FUNCTION_H -#include "Module.h" +#include "CTypes.h" #include "nsTArray.h" #include "prlink.h" -#include "ffi.h" namespace mozilla { namespace ctypes { -// for JS error reporting -enum ErrorNum { -#define MSG_DEF(name, number, count, exception, format) \ - name = number, -#include "ctypes.msg" -#undef MSG_DEF - CTYPESERR_LIMIT +enum FunctionSlot +{ + SLOT_FUNCTION = 0, + SLOT_LIBRARYOBJ = 1 + // JSFunction objects always get exactly two slots. }; -const JSErrorFormatString* -GetErrorMessage(void* userRef, const char* locale, const uintN errorNumber); - struct Type { ffi_type mFFIType; - TypeCode mType; + JSObject* mType; }; -struct Value +struct AutoValue { + AutoValue() : mData(NULL) { } + + ~AutoValue() + { + delete static_cast(mData); + } + + bool SizeToType(JSContext* cx, JSObject* type) + { + size_t size = CType::GetSize(cx, type); + mData = new char[size]; + if (mData) + memset(mData, 0, size); + return mData != NULL; + } + void* mData; - union { - PRInt8 mInt8; - PRInt16 mInt16; - PRInt32 mInt32; - PRInt64 mInt64; - PRUint8 mUint8; - PRUint16 mUint16; - PRUint32 mUint32; - PRUint64 mUint64; - float mFloat; - double mDouble; - void* mPointer; - } mValue; }; class Function @@ -90,6 +87,7 @@ public: Function(); Function*& Next() { return mNext; } + void Trace(JSTracer *trc); static JSObject* Create(JSContext* aContext, JSObject* aLibrary, PRFuncPtr aFunc, const char* aName, jsval aCallType, jsval aResultType, jsval* aArgTypes, uintN aArgLength); static JSBool Call(JSContext* cx, uintN argc, jsval* vp); diff --git a/js/ctypes/Library.cpp b/js/ctypes/Library.cpp index edbd2f2fdb97..9aded58465d9 100644 --- a/js/ctypes/Library.cpp +++ b/js/ctypes/Library.cpp @@ -38,6 +38,7 @@ * * ***** END LICENSE BLOCK ***** */ +#include "jscntxt.h" #include "Library.h" #include "Function.h" #include "nsServiceManagerUtils.h" @@ -55,15 +56,18 @@ namespace ctypes { static JSClass sLibraryClass = { "Library", - JSCLASS_HAS_RESERVED_SLOTS(2), + JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS) | JSCLASS_MARK_IS_TRACE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub,JS_ResolveStub, JS_ConvertStub, Library::Finalize, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL + NULL, NULL, NULL, NULL, NULL, NULL, JS_CLASS_TRACE(Library::Trace), NULL }; +#define CTYPESFN_FLAGS \ + (JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) + static JSFunctionSpec sLibraryFunctions[] = { - JS_FN("close", Library::Close, 0, JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT), - JS_FN("declare", Library::Declare, 0, JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT), + JS_FN("close", Library::Close, 0, CTYPESFN_FLAGS), + JS_FN("declare", Library::Declare, 0, CTYPESFN_FLAGS), JS_FS_END }; @@ -73,13 +77,15 @@ Library::Create(JSContext* cx, jsval aPath) JSObject* libraryObj = JS_NewObject(cx, &sLibraryClass, NULL, NULL); if (!libraryObj) return NULL; + JSAutoTempValueRooter root(cx, libraryObj); // initialize the library - if (!JS_SetReservedSlot(cx, libraryObj, 0, PRIVATE_TO_JSVAL(NULL))) + if (!JS_SetReservedSlot(cx, libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(NULL))) return NULL; // initialize our Function list to empty - if (!JS_SetReservedSlot(cx, libraryObj, 1, PRIVATE_TO_JSVAL(NULL))) + if (!JS_SetReservedSlot(cx, libraryObj, SLOT_FUNCTIONLIST, + PRIVATE_TO_JSVAL(NULL))) return NULL; // attach API functions @@ -92,21 +98,22 @@ Library::Create(JSContext* cx, jsval aPath) // get the path argument. we accept either an nsILocalFile or a string path. // determine which we have... if (JSVAL_IS_STRING(aPath)) { - const jschar* path = JS_GetStringChars(JSVAL_TO_STRING(aPath)); + const PRUnichar* path = reinterpret_cast( + JS_GetStringCharsZ(cx, JSVAL_TO_STRING(aPath))); if (!path) return NULL; - // We don't use nsILocalFile. - // Because this interface doesn't resolve library path to use system search rule. + // We don't use nsILocalFile, because it doesn't use the system search + // rules when resolving library path. PRLibSpec libSpec; #ifdef XP_WIN // On Windows, converting to native charset may corrupt path string. // So, we have to use Unicode path directly. - libSpec.value.pathname_u = reinterpret_cast(path); + libSpec.value.pathname_u = path; libSpec.type = PR_LibSpec_PathnameU; #else nsCAutoString nativePath; - NS_CopyUnicodeToNative(nsDependentString(reinterpret_cast(path)), nativePath); + NS_CopyUnicodeToNative(nsDependentString(path), nativePath); libSpec.value.pathname = nativePath.get(); libSpec.type = PR_LibSpec_Pathname; #endif @@ -132,7 +139,8 @@ Library::Create(JSContext* cx, jsval aPath) } // stash the library - if (!JS_SetReservedSlot(cx, libraryObj, 0, PRIVATE_TO_JSVAL(library))) + if (!JS_SetReservedSlot(cx, libraryObj, SLOT_LIBRARY, + PRIVATE_TO_JSVAL(library))) return NULL; return libraryObj; @@ -144,7 +152,7 @@ Library::GetLibrary(JSContext* cx, JSObject* obj) JS_ASSERT(JS_GET_CLASS(cx, obj) == &sLibraryClass); jsval slot; - JS_GetReservedSlot(cx, obj, 0, &slot); + JS_GetReservedSlot(cx, obj, SLOT_LIBRARY, &slot); return static_cast(JSVAL_TO_PRIVATE(slot)); } @@ -154,7 +162,7 @@ GetFunctionList(JSContext* cx, JSObject* obj) JS_ASSERT(JS_GET_CLASS(cx, obj) == &sLibraryClass); jsval slot; - JS_GetReservedSlot(cx, obj, 1, &slot); + JS_GetReservedSlot(cx, obj, SLOT_FUNCTIONLIST, &slot); return static_cast(JSVAL_TO_PRIVATE(slot)); } @@ -163,7 +171,20 @@ Library::AddFunction(JSContext* cx, JSObject* aLibrary, Function* aFunction) { // add the new Function instance to the head of the list aFunction->Next() = GetFunctionList(cx, aLibrary); - return JS_SetReservedSlot(cx, aLibrary, 1, PRIVATE_TO_JSVAL(aFunction)); + return JS_SetReservedSlot(cx, aLibrary, SLOT_FUNCTIONLIST, + PRIVATE_TO_JSVAL(aFunction)) != JS_FALSE; +} + +void +Library::Trace(JSTracer *trc, JSObject* obj) +{ + // Walk the Function list and for each Function, identify each CType + // associated with it to the tracer. + Function* current = GetFunctionList(trc->context, obj); + while (current) { + current->Trace(trc); + current = current->Next(); + } } void @@ -186,7 +207,7 @@ Library::Finalize(JSContext* cx, JSObject* obj) JSBool Library::Open(JSContext* cx, uintN argc, jsval *vp) { - if (argc != 1) { + if (argc != 1 || JSVAL_IS_VOID(JS_ARGV(cx, vp)[0])) { JS_ReportError(cx, "open requires a single argument"); return JS_FALSE; } @@ -217,8 +238,8 @@ Library::Close(JSContext* cx, uintN argc, jsval* vp) // delete our internal objects Finalize(cx, obj); - JS_SetReservedSlot(cx, obj, 0, PRIVATE_TO_JSVAL(NULL)); - JS_SetReservedSlot(cx, obj, 1, PRIVATE_TO_JSVAL(NULL)); + JS_SetReservedSlot(cx, obj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(NULL)); + JS_SetReservedSlot(cx, obj, SLOT_FUNCTIONLIST, PRIVATE_TO_JSVAL(NULL)); JS_SET_RVAL(cx, vp, JSVAL_VOID); return JS_TRUE; @@ -251,14 +272,15 @@ Library::Declare(JSContext* cx, uintN argc, jsval* vp) return JS_FALSE; } - const char* name = JS_GetStringBytes(JSVAL_TO_STRING(argv[0])); + const char* name = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(argv[0])); PRFuncPtr func = PR_FindFunctionSymbol(library, name); if (!func) { JS_ReportError(cx, "couldn't find function symbol in library"); return JS_FALSE; } - JSObject* fn = Function::Create(cx, obj, func, name, argv[1], argv[2], &argv[3], argc - 3); + JSObject* fn = Function::Create(cx, obj, func, name, argv[1], argv[2], + &argv[3], argc - 3); if (!fn) return JS_FALSE; diff --git a/js/ctypes/Library.h b/js/ctypes/Library.h index 8f3bb25f2943..87f67421800d 100644 --- a/js/ctypes/Library.h +++ b/js/ctypes/Library.h @@ -40,19 +40,24 @@ #ifndef LIBRARY_H #define LIBRARY_H -#include "Function.h" -#include "jsapi.h" - struct PRLibrary; -class Function; namespace mozilla { namespace ctypes { +class Function; + +enum LibrarySlot { + SLOT_LIBRARY = 0, + SLOT_FUNCTIONLIST = 1, + LIBRARY_SLOTS +}; + class Library { public: static JSObject* Create(JSContext* cx, jsval aPath); + static void Trace(JSTracer *trc, JSObject* obj); static void Finalize(JSContext* cx, JSObject* obj); static PRLibrary* GetLibrary(JSContext* cx, JSObject* obj); diff --git a/js/ctypes/Makefile.in b/js/ctypes/Makefile.in index 49bacbc89aa5..f7309589832a 100644 --- a/js/ctypes/Makefile.in +++ b/js/ctypes/Makefile.in @@ -62,6 +62,7 @@ IS_COMPONENT = 1 CPPSRCS = \ Function.cpp \ Library.cpp \ + CTypes.cpp \ Module.cpp \ $(NULL) diff --git a/js/ctypes/Module.cpp b/js/ctypes/Module.cpp index 01a099afaa3d..7b9bcc57792a 100644 --- a/js/ctypes/Module.cpp +++ b/js/ctypes/Module.cpp @@ -39,6 +39,7 @@ #include "Module.h" #include "Library.h" +#include "CTypes.h" #include "nsIGenericFactory.h" #include "nsMemory.h" @@ -83,74 +84,15 @@ Module::Call(nsIXPConnectWrappedNative* wrapper, return NS_OK; } -static JSClass sCABIClass = { - "CABI", - JSCLASS_HAS_RESERVED_SLOTS(1), - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub,JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; - -static JSClass sCTypeClass = { - "CType", - JSCLASS_HAS_RESERVED_SLOTS(1), - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub,JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; +#define CTYPESFN_FLAGS \ + (JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) static JSFunctionSpec sModuleFunctions[] = { - JS_FN("open", Library::Open, 0, JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT), + JS_FN("open", Library::Open, 1, CTYPESFN_FLAGS), + JS_FN("cast", CData::Cast, 2, CTYPESFN_FLAGS), JS_FS_END }; -ABICode -Module::GetABICode(JSContext* cx, jsval val) -{ - // make sure we have an object representing a CABI class, - // and extract the enumerated class type from the reserved slot. - if (!JSVAL_IS_OBJECT(val)) - return INVALID_ABI; - - JSObject* obj = JSVAL_TO_OBJECT(val); - if (JS_GET_CLASS(cx, obj) != &sCABIClass) - return INVALID_ABI; - - jsval result; - JS_GetReservedSlot(cx, obj, 0, &result); - - return ABICode(JSVAL_TO_INT(result)); -} - -TypeCode -Module::GetTypeCode(JSContext* cx, jsval val) -{ - // make sure we have an object representing a CType class, - // and extract the enumerated class type from the reserved slot. - if (!JSVAL_IS_OBJECT(val)) - return INVALID_TYPE; - - JSObject* obj = JSVAL_TO_OBJECT(val); - if (JS_GET_CLASS(cx, obj) != &sCTypeClass) - return INVALID_TYPE; - - jsval result; - JS_GetReservedSlot(cx, obj, 0, &result); - - return TypeCode(JSVAL_TO_INT(result)); -} - -static bool -DefineConstant(JSContext* cx, JSObject* parent, JSClass* clasp, const char* name, jsint code) -{ - JSObject* obj = JS_DefineObject(cx, parent, name, clasp, NULL, - JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - if (!obj) - return false; - - return JS_SetReservedSlot(cx, obj, 0, INT_TO_JSVAL(code)); -} - bool Module::Init(JSContext* cx, JSObject* aGlobal) { @@ -163,22 +105,16 @@ Module::Init(JSContext* cx, JSObject* aGlobal) NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) return false; - // attach classes representing ABI and type constants -#define DEFINE_ABI(name) \ - if (!DefineConstant(cx, ctypes, &sCABIClass, #name, ABI_##name)) \ + if (!InitTypeClasses(cx, ctypes)) return false; -#define DEFINE_TYPE(name) \ - if (!DefineConstant(cx, ctypes, &sCTypeClass, #name, TYPE_##name)) \ - return false; -#include "types.h" -#undef DEFINE_ABI -#undef DEFINE_TYPE // attach API functions if (!JS_DefineFunctions(cx, ctypes, sModuleFunctions)) return false; - return true; + // Seal the ctypes object, to prevent modification. (This single object + // instance is shared amongst everyone who imports the ctypes module.) + return JS_SealObject(cx, ctypes, JS_FALSE) != JS_FALSE; } } diff --git a/js/ctypes/Module.h b/js/ctypes/Module.h index a4a8cc897cdc..5f1ccfdff678 100644 --- a/js/ctypes/Module.h +++ b/js/ctypes/Module.h @@ -44,27 +44,6 @@ namespace mozilla { namespace ctypes { -// Each internal CABI and CType class (representing ABI and type constants, -// respectively) has a unique identifier, stored in a reserved slot on the -// JSObject. -enum ABICode { -#define DEFINE_ABI(name) ABI_##name, -#define DEFINE_TYPE(name) -#include "types.h" -#undef DEFINE_ABI -#undef DEFINE_TYPE - INVALID_ABI -}; - -enum TypeCode { -#define DEFINE_ABI(name) -#define DEFINE_TYPE(name) TYPE_##name, -#include "types.h" -#undef DEFINE_ABI -#undef DEFINE_TYPE - INVALID_TYPE -}; - class Module : public nsIXPCScriptable { public: @@ -76,9 +55,6 @@ public: // Creates the ctypes object and attaches it to the global object. bool Init(JSContext* aContext, JSObject* aGlobal); - static ABICode GetABICode(JSContext* cx, jsval val); - static TypeCode GetTypeCode(JSContext* cx, jsval val); - private: ~Module(); }; diff --git a/js/ctypes/tests/jsctypes-test.cpp b/js/ctypes/tests/jsctypes-test.cpp index 6194196c3cf8..621e128449da 100644 --- a/js/ctypes/tests/jsctypes-test.cpp +++ b/js/ctypes/tests/jsctypes-test.cpp @@ -44,123 +44,127 @@ #include void -test_v() +test_void_t_cdecl() { // do nothing return; } -PRInt8 -test_i8() -{ - return 123; +#define DEFINE_TYPE(name, type, ffiType) \ +type \ +get_##name##_cdecl() \ +{ \ + return 109.25; \ +} \ + \ +type \ +set_##name##_cdecl(type x) \ +{ \ + return x; \ +} \ + \ +type \ +sum_##name##_cdecl(type x, type y) \ +{ \ + return x + y; \ +} \ + \ +type \ +sum_alignb_##name##_cdecl(char a, type x, char b, type y, char c) \ +{ \ + return x + y; \ +} \ + \ +type \ +sum_alignf_##name##_cdecl(float a, type x, float b, type y, float c) \ +{ \ + return x + y; \ +} \ + \ +type \ +sum_many_##name##_cdecl( \ + type a, type b, type c, type d, type e, type f, type g, type h, type i, \ + type j, type k, type l, type m, type n, type o, type p, type q, type r) \ +{ \ + return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r;\ +} \ + \ +struct align_##name { \ + char x; \ + type y; \ +}; \ +struct nested_##name { \ + char a; \ + align_##name b; \ + char c; \ +}; \ + \ +void \ +get_##name##_stats(size_t* align, size_t* size, size_t* nalign, size_t* nsize, \ + size_t offsets[]) \ +{ \ + *align = offsetof(align_##name, y); \ + *size = sizeof(align_##name); \ + *nalign = offsetof(nested_##name, b); \ + *nsize = sizeof(nested_##name); \ + offsets[0] = offsetof(align_##name, y); \ + offsets[1] = offsetof(nested_##name, b); \ + offsets[2] = offsetof(nested_##name, c); \ } -PRInt8 -test_i8_i8(PRInt8 number) -{ - return number; +#include "../typedefs.h" + +#if defined(_WIN32) && !defined(__WIN64) + +#define DEFINE_TYPE(name, type, ffiType) \ +type NS_STDCALL \ +get_##name##_stdcall() \ +{ \ + return 109.25; \ +} \ + \ +type NS_STDCALL \ +set_##name##_stdcall(type x) \ +{ \ + return x; \ +} \ + \ +type NS_STDCALL \ +sum_##name##_stdcall(type x, type y) \ +{ \ + return x + y; \ +} \ + \ +type NS_STDCALL \ +sum_alignb_##name##_stdcall(char a, type x, char b, type y, char c) \ +{ \ + return x + y; \ +} \ + \ +type NS_STDCALL \ +sum_alignf_##name##_stdcall(float a, type x, float b, type y, float c) \ +{ \ + return x + y; \ +} \ + \ +type NS_STDCALL \ +sum_many_##name##_stdcall( \ + type a, type b, type c, type d, type e, type f, type g, type h, type i, \ + type j, type k, type l, type m, type n, type o, type p, type q, type r) \ +{ \ + return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r;\ } -PRInt8 -test_i8_i8_sum(PRInt8 number1, PRInt8 number2) +#include "../typedefs.h" + +void NS_STDCALL +test_void_t_stdcall() { - return number1 + number2; + // do nothing + return; } -PRInt16 -test_i16() -{ - return 12345; -} - -PRInt16 -test_i16_i16(PRInt16 number) -{ - return number; -} - -PRInt16 -test_i16_i16_sum(PRInt16 number1, PRInt16 number2) -{ - return number1 + number2; -} - -PRInt32 -test_i32() -{ - return 123456789; -} - -PRInt32 -test_i32_i32(PRInt32 number) -{ - return number; -} - -PRInt32 -test_i32_i32_sum(PRInt32 number1, PRInt32 number2) -{ - return number1 + number2; -} - -PRInt64 -test_i64() -{ -#if defined(WIN32) && !defined(__GNUC__) - return 0x28590a1c921de000i64; -#else - return 0x28590a1c921de000LL; -#endif -} - -PRInt64 -test_i64_i64(PRInt64 number) -{ - return number; -} - -PRInt64 -test_i64_i64_sum(PRInt64 number1, PRInt64 number2) -{ - return number1 + number2; -} - -float -test_f() -{ - return 123456.5f; -} - -float -test_f_f(float number) -{ - return number; -} - -float -test_f_f_sum(float number1, float number2) -{ - return (number1 + number2); -} - -double -test_d() -{ - return 1234567890123456789.5; -} - -double -test_d_d(double number) -{ - return number; -} - -double -test_d_d_sum(double number1, double number2) -{ - return (number1 + number2); -} +#endif /* defined(_WIN32) && !defined(__WIN64) */ PRInt32 test_ansi_len(const char* string) @@ -194,8 +198,33 @@ test_ansi_echo(const char* string) } PRInt32 -test_floor(PRInt32 number1, float number2) +test_pt_in_rect(RECT rc, POINT pt) { - return PRInt32(floor(float(number1) + number2)); + if (pt.x < rc.left || pt.x > rc.right) + return 0; + if (pt.y < rc.bottom || pt.y > rc.top) + return 0; + return 1; +} + +void +test_init_pt(POINT* pt, PRInt32 x, PRInt32 y) +{ + pt->x = x; + pt->y = y; +} + +PRInt32 +test_nested_struct(NESTED n) +{ + return PRInt32(n.n1 + n.n2 + n.inner.i1 + n.inner.i2 + n.inner.i3 + n.n3 + n.n4); +} + +POINT +test_struct_return(RECT r) +{ + POINT p; + p.x = r.left; p.y = r.top; + return p; } diff --git a/js/ctypes/tests/jsctypes-test.h b/js/ctypes/tests/jsctypes-test.h index 7e1f81080672..9d5ac30af906 100644 --- a/js/ctypes/tests/jsctypes-test.h +++ b/js/ctypes/tests/jsctypes-test.h @@ -40,34 +40,56 @@ #include "nscore.h" #include "prtypes.h" +#include "jsapi.h" + +#define EXPORT_CDECL(type) NS_EXPORT type +#define EXPORT_STDCALL(type) NS_EXPORT type NS_STDCALL NS_EXTERN_C { - NS_EXPORT void test_v(); + EXPORT_CDECL(void) test_void_t_cdecl(); - NS_EXPORT PRInt8 test_i8(); - NS_EXPORT PRInt8 test_i8_i8(PRInt8); - NS_EXPORT PRInt8 test_i8_i8_sum(PRInt8, PRInt8); + EXPORT_CDECL(void*) get_voidptr_t_cdecl(); + EXPORT_CDECL(void*) set_voidptr_t_cdecl(void*); - NS_EXPORT PRInt16 test_i16(); - NS_EXPORT PRInt16 test_i16_i16(PRInt16); - NS_EXPORT PRInt16 test_i16_i16_sum(PRInt16, PRInt16); +#define DEFINE_TYPE(name, type, ffiType) \ + EXPORT_CDECL(type) get_##name##_cdecl(); \ + EXPORT_CDECL(type) set_##name##_cdecl(type); \ + EXPORT_CDECL(type) sum_##name##_cdecl(type, type); \ + EXPORT_CDECL(type) sum_alignb_##name##_cdecl(char, type, char, type, char); \ + EXPORT_CDECL(type) sum_alignf_##name##_cdecl( \ + float, type, float, type, float); \ + EXPORT_CDECL(type) sum_many_##name##_cdecl( \ + type, type, type, type, type, type, type, type, type, \ + type, type, type, type, type, type, type, type, type); \ + \ + EXPORT_CDECL(void) get_##name##_stats(size_t* align, size_t* size, \ + size_t* nalign, size_t* nsize, \ + size_t offsets[]); - NS_EXPORT PRInt32 test_i32(); - NS_EXPORT PRInt32 test_i32_i32(PRInt32); - NS_EXPORT PRInt32 test_i32_i32_sum(PRInt32, PRInt32); +#include "../typedefs.h" - NS_EXPORT PRInt64 test_i64(); - NS_EXPORT PRInt64 test_i64_i64(PRInt64); - NS_EXPORT PRInt64 test_i64_i64_sum(PRInt64, PRInt64); +#if defined(_WIN32) && !defined(__WIN64) + EXPORT_STDCALL(void) test_void_t_stdcall(); - NS_EXPORT float test_f(); - NS_EXPORT float test_f_f(float); - NS_EXPORT float test_f_f_sum(float, float); + EXPORT_STDCALL(void*) get_voidptr_t_stdcall(); + EXPORT_STDCALL(void*) set_voidptr_t_stdcall(void*); - NS_EXPORT double test_d(); - NS_EXPORT double test_d_d(double); - NS_EXPORT double test_d_d_sum(double, double); +#define DEFINE_TYPE(name, type, ffiType) \ + EXPORT_STDCALL(type) get_##name##_stdcall(); \ + EXPORT_STDCALL(type) set_##name##_stdcall(type); \ + EXPORT_STDCALL(type) sum_##name##_stdcall(type, type); \ + EXPORT_STDCALL(type) sum_alignb_##name##_stdcall( \ + char, type, char, type, char); \ + EXPORT_STDCALL(type) sum_alignf_##name##_stdcall( \ + float, type, float, type, float); \ + EXPORT_STDCALL(type) sum_many_##name##_stdcall( \ + type, type, type, type, type, type, type, type, type, \ + type, type, type, type, type, type, type, type, type); + +#include "../typedefs.h" + +#endif /* defined(_WIN32) && !defined(__WIN64) */ NS_EXPORT PRInt32 test_ansi_len(const char*); NS_EXPORT PRInt32 test_wide_len(const PRUnichar*); @@ -75,6 +97,36 @@ NS_EXTERN_C NS_EXPORT const PRUnichar* test_wide_ret(); NS_EXPORT char* test_ansi_echo(const char*); - NS_EXPORT PRInt32 test_floor(PRInt32, float); + struct POINT { + PRInt32 x; + PRInt32 y; + }; + + struct RECT { + PRInt32 top; + PRInt32 left; + PRInt32 bottom; + PRInt32 right; + }; + + struct INNER { + PRUint8 i1; + PRInt64 i2; + PRUint8 i3; + }; + + struct NESTED { + PRInt32 n1; + PRInt16 n2; + INNER inner; + PRInt64 n3; + PRInt32 n4; + }; + + NS_EXPORT PRInt32 test_pt_in_rect(RECT, POINT); + NS_EXPORT void test_init_pt(POINT* pt, PRInt32 x, PRInt32 y); + + NS_EXPORT PRInt32 test_nested_struct(NESTED); + NS_EXPORT POINT test_struct_return(RECT); } diff --git a/js/ctypes/tests/unit/test_jsctypes.js.in b/js/ctypes/tests/unit/test_jsctypes.js.in index ddeebe67261e..c01d3aee6645 100644 --- a/js/ctypes/tests/unit/test_jsctypes.js.in +++ b/js/ctypes/tests/unit/test_jsctypes.js.in @@ -62,281 +62,1386 @@ function do_check_throws(f, type, stack) function run_test() { - var libfile = do_get_file(CTYPES_TEST_LIB); - // open the library with an nsILocalFile - var library = ctypes.open(libfile); + let libfile = do_get_file(CTYPES_TEST_LIB); + let library = ctypes.open(libfile); + // Make sure we can call a function in the library. run_void_tests(library); - run_int8_tests(library); - run_int16_tests(library); - run_int32_tests(library); - run_int64_tests(library); - run_float_tests(library); - run_double_tests(library); + + // Test Int64 and UInt64. + run_Int64_tests(); + run_UInt64_tests(); + + // Test that ctypes.CType behaves like an abstract type. + do_check_throws(function() { ctypes.CType(); }, Error); + do_check_throws(function() { new ctypes.CType() }, Error); + do_check_throws(function() { ctypes.CType(12); }, Error); + do_check_throws(function() { new ctypes.CType(12) }, Error); + + // Test the basic bool, integer, and float types. + run_bool_tests(library); + + run_integer_tests(library, ctypes.int8_t, "int8_t", 1, true, [-0x80, 0x7f]); + run_integer_tests(library, ctypes.int16_t, "int16_t", 2, true, [-0x8000, 0x7fff]); + run_integer_tests(library, ctypes.int32_t, "int32_t", 4, true, [-0x80000000, 0x7fffffff]); + run_integer_tests(library, ctypes.uint8_t, "uint8_t", 1, false, [0, 0xff]); + run_integer_tests(library, ctypes.uint16_t, "uint16_t", 2, false, [0, 0xffff]); + run_integer_tests(library, ctypes.uint32_t, "uint32_t", 4, false, [0, 0xffffffff]); + run_integer_tests(library, ctypes.short, "short", 2, true, [-0x8000, 0x7fff]); + run_integer_tests(library, ctypes.unsigned_short, "unsigned_short", 2, false, [0, 0xffff]); + run_integer_tests(library, ctypes.int, "int", 4, true, [-0x80000000, 0x7fffffff]); + run_integer_tests(library, ctypes.unsigned_int, "unsigned_int", 4, false, [0, 0xffffffff]); + run_integer_tests(library, ctypes.unsigned, "unsigned", 4, false, [0, 0xffffffff]); + + run_float_tests(library, ctypes.float32_t, "float32_t", 4); + run_float_tests(library, ctypes.float64_t, "float64_t", 8); + run_float_tests(library, ctypes.float, "float", 4); + run_float_tests(library, ctypes.double, "double", 8); + + // Test the wrapped integer types. + s64limits = ["-9223372036854775808", "9223372036854775807", + "-9223372036854775809", "9223372036854775808"]; + u64limits = ["0", "18446744073709551615", "-1", "18446744073709551616"]; + + run_wrapped_integer_tests(library, ctypes.int64_t, "int64_t", 8, true, + ctypes.Int64, "ctypes.Int64", s64limits); + run_wrapped_integer_tests(library, ctypes.uint64_t, "uint64_t", 8, false, + ctypes.UInt64, "ctypes.UInt64", u64limits); + run_wrapped_integer_tests(library, ctypes.long_long, "long_long", 8, true, + ctypes.Int64, "ctypes.Int64", s64limits); + run_wrapped_integer_tests(library, ctypes.unsigned_long_long, "unsigned_long_long", 8, false, + ctypes.UInt64, "ctypes.UInt64", u64limits); + + s32limits = [-0x80000000, 0x7fffffff, -0x80000001, 0x80000000]; + u32limits = [0, 0xffffffff, -1, 0x100000000]; + + let slimits, ulimits; + if (ctypes.long.size == 8) { + slimits = s64limits; + ulimits = u64limits; + } else if (ctypes.long.size == 4) { + slimits = s32limits; + ulimits = u32limits; + } else { + do_throw("ctypes.long is not 4 or 8 bytes"); + } + + run_wrapped_integer_tests(library, ctypes.long, "long", ctypes.long.size, true, + ctypes.Int64, "ctypes.Int64", slimits); + run_wrapped_integer_tests(library, ctypes.unsigned_long, "unsigned_long", ctypes.long.size, false, + ctypes.UInt64, "ctypes.UInt64", ulimits); + + if (ctypes.size_t.size == 8) { + slimits = s64limits; + ulimits = u64limits; + } else if (ctypes.size_t.size == 4) { + slimits = s32limits; + ulimits = u32limits; + } else { + do_throw("ctypes.size_t is not 4 or 8 bytes"); + } + + run_wrapped_integer_tests(library, ctypes.size_t, "size_t", ctypes.size_t.size, false, + ctypes.UInt64, "ctypes.UInt64", ulimits); + run_wrapped_integer_tests(library, ctypes.ssize_t, "ssize_t", ctypes.size_t.size, true, + ctypes.Int64, "ctypes.Int64", slimits); + run_wrapped_integer_tests(library, ctypes.uintptr_t, "uintptr_t", ctypes.size_t.size, false, + ctypes.UInt64, "ctypes.UInt64", ulimits); + run_wrapped_integer_tests(library, ctypes.intptr_t, "intptr_t", ctypes.size_t.size, true, + ctypes.Int64, "ctypes.Int64", slimits); + + // Test the character types. + run_char_tests(library, ctypes.char, "char", 1, true, [-0x80, 0x7f]); + run_char_tests(library, ctypes.signed_char, "signed_char", 1, true, [-0x80, 0x7f]); + run_char_tests(library, ctypes.unsigned_char, "unsigned_char", 1, false, [0, 0xff]); + run_char_tests(library, ctypes.jschar, "jschar", 2, false, [0, 0xffff]); + + // Test the special types. + run_StructType_tests(); + run_PointerType_tests(); + run_ArrayType_tests(); + + // Test the 'name' and 'toSource' of a long typename. + let ptrTo_ptrTo_arrayOf4_ptrTo_int32s = + new ctypes.PointerType( + new ctypes.PointerType( + new ctypes.ArrayType( + new ctypes.PointerType(ctypes.int32_t), 4))); + do_check_eq(ptrTo_ptrTo_arrayOf4_ptrTo_int32s.name, "int32_t*(**)[4]"); + + let source_t = new ctypes.StructType("source", + [{ a: ptrTo_ptrTo_arrayOf4_ptrTo_int32s }, { b: ctypes.int64_t }]); + do_check_eq(source_t.toSource(), + 'ctypes.StructType("source", [{ "a": ctypes.int32_t.ptr.array(4).ptr.ptr }, ' + + '{ "b": ctypes.int64_t }])'); + + // Test ctypes.cast. + run_cast_tests(); + run_string_tests(library); - run_mixed_tests(library); + run_struct_tests(library); // test the string version of ctypes.open() as well - var libpath = libfile.path; + let libpath = libfile.path; library = ctypes.open(libpath); run_void_tests(library); // test library.close - var test_v = library.declare("test_v", ctypes.default_abi, ctypes.void_t); library.close(); - do_check_throws(function () { test_v(); }, Error); - do_check_throws(function () { library.declare("test_v", ctypes.default_abi, ctypes.void_t); }, Error); + do_check_throws(function () { run_void_tests(library); }, Error); // test that library functions throw when bound to other objects library = ctypes.open(libpath); let obj = {}; obj.declare = library.declare; - do_check_throws(function () { obj.declare("test_v", ctypes.default_abi, ctypes.void_t); }, Error); + do_check_throws(function () { run_void_tests(obj); }, Error); obj.close = library.close; do_check_throws(function () { obj.close(); }, Error); // test that functions work as properties of other objects - var test_i8 = library.declare("test_i8", ctypes.default_abi, ctypes.int8_t); - do_check_eq(test_i8(), 123); - obj.t = test_i8; - do_check_eq(test_i8(), 123); - do_check_eq(obj.t(), 123); + let getter = library.declare("get_int8_t_cdecl", ctypes.default_abi, ctypes.int8_t); + do_check_eq(getter(), 109); + obj.t = getter; + do_check_eq(obj.t(), 109); // bug 521937 - do_check_throws(function () { var nolib = ctypes.open("notfoundlibrary.dll"); nolib.close(); }, Error); + do_check_throws(function () { let nolib = ctypes.open("notfoundlibrary.dll"); nolib.close(); }, Error); // bug 522360 do_check_eq(run_load_system_library(), true); } +function run_Int64_tests() { + do_check_throws(function() { ctypes.Int64(); }, Error); + + let i = ctypes.Int64(0); + do_check_true(i instanceof ctypes.Int64); + + // Test Int64.toString([radix]). + do_check_eq(i.toString(), "0"); + for (let radix = 2; radix <= 36; ++radix) + do_check_eq(i.toString(radix), "0"); + do_check_throws(function() { i.toString(0); }, Error); + do_check_throws(function() { i.toString(1); }, Error); + do_check_throws(function() { i.toString(37); }, Error); + + i = ctypes.Int64("0x28590a1c921def71"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "2907366152271163249"); + do_check_eq(i.toString(16), "28590a1c921def71"); + do_check_eq(i.toString(2), "10100001011001000010100001110010010010000111011110111101110001"); + + i = ctypes.Int64("-0x28590a1c921def71"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "-2907366152271163249"); + do_check_eq(i.toString(16), "-28590a1c921def71"); + do_check_eq(i.toString(2), "-10100001011001000010100001110010010010000111011110111101110001"); + + i = ctypes.Int64("-0X28590A1c921DEf71"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "-2907366152271163249"); + do_check_eq(i.toString(16), "-28590a1c921def71"); + do_check_eq(i.toString(2), "-10100001011001000010100001110010010010000111011110111101110001"); + + // Test Int64(primitive double) constructor. + i = ctypes.Int64(-0); + do_check_eq(i.toString(), "0"); + + i = ctypes.Int64(0x7ffffffffffff000); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "9223372036854771712"); + do_check_eq(i.toString(16), "7ffffffffffff000"); + do_check_eq(i.toString(2), "111111111111111111111111111111111111111111111111111000000000000"); + + i = ctypes.Int64(-0x8000000000000000); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "-9223372036854775808"); + do_check_eq(i.toString(16), "-8000000000000000"); + do_check_eq(i.toString(2), "-1000000000000000000000000000000000000000000000000000000000000000"); + + // Test Int64(string) constructor. + i = ctypes.Int64("0x7fffffffffffffff"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "9223372036854775807"); + do_check_eq(i.toString(16), "7fffffffffffffff"); + do_check_eq(i.toString(2), "111111111111111111111111111111111111111111111111111111111111111"); + + i = ctypes.Int64("-0x8000000000000000"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "-9223372036854775808"); + do_check_eq(i.toString(16), "-8000000000000000"); + do_check_eq(i.toString(2), "-1000000000000000000000000000000000000000000000000000000000000000"); + + i = ctypes.Int64("9223372036854775807"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "9223372036854775807"); + do_check_eq(i.toString(16), "7fffffffffffffff"); + do_check_eq(i.toString(2), "111111111111111111111111111111111111111111111111111111111111111"); + + i = ctypes.Int64("-9223372036854775808"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "-9223372036854775808"); + do_check_eq(i.toString(16), "-8000000000000000"); + do_check_eq(i.toString(2), "-1000000000000000000000000000000000000000000000000000000000000000"); + + // Test Int64(other Int64) constructor. + i = ctypes.Int64(ctypes.Int64(0)); + do_check_eq(i.toString(), "0"); + + i = ctypes.Int64(ctypes.Int64("0x7fffffffffffffff")); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "9223372036854775807"); + do_check_eq(i.toString(16), "7fffffffffffffff"); + do_check_eq(i.toString(2), "111111111111111111111111111111111111111111111111111111111111111"); + + i = ctypes.Int64(ctypes.Int64("-0x8000000000000000")); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "-9223372036854775808"); + do_check_eq(i.toString(16), "-8000000000000000"); + do_check_eq(i.toString(2), "-1000000000000000000000000000000000000000000000000000000000000000"); + + // Test Int64(other UInt64) constructor. + i = ctypes.Int64(ctypes.UInt64(0)); + do_check_eq(i.toString(), "0"); + + i = ctypes.Int64(ctypes.UInt64("0x7fffffffffffffff")); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "9223372036854775807"); + do_check_eq(i.toString(16), "7fffffffffffffff"); + do_check_eq(i.toString(2), "111111111111111111111111111111111111111111111111111111111111111"); + + let vals = [-0x8000000000001000, 0x8000000000000000, + "-0x8000000000000001", "0x8000000000000000", + ctypes.UInt64("0x8000000000000000"), + Infinity, -Infinity, NaN, 0.1, + 5.68e21, null, undefined, "", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { ctypes.Int64(vals[i]); }, TypeError); + + // Test ctypes.Int64.compare. + do_check_eq(ctypes.Int64.compare(ctypes.Int64(5), ctypes.Int64(5)), 0); + do_check_eq(ctypes.Int64.compare(ctypes.Int64(5), ctypes.Int64(4)), 1); + do_check_eq(ctypes.Int64.compare(ctypes.Int64(4), ctypes.Int64(5)), -1); + do_check_eq(ctypes.Int64.compare(ctypes.Int64(-5), ctypes.Int64(-5)), 0); + do_check_eq(ctypes.Int64.compare(ctypes.Int64(-5), ctypes.Int64(-4)), -1); + do_check_eq(ctypes.Int64.compare(ctypes.Int64(-4), ctypes.Int64(-5)), 1); + do_check_throws(function() { ctypes.Int64.compare(ctypes.Int64(4), ctypes.UInt64(4)); }, Error); + do_check_throws(function() { ctypes.Int64.compare(4, 5); }, Error); + + // Test ctypes.Int64.{lo,hi}. + do_check_eq(ctypes.Int64.lo(ctypes.Int64(0x28590a1c921de000)), 0x921de000); + do_check_eq(ctypes.Int64.hi(ctypes.Int64(0x28590a1c921de000)), 0x28590a1c); + do_check_eq(ctypes.Int64.lo(ctypes.Int64(-0x28590a1c921de000)), 0x6de22000); + do_check_eq(ctypes.Int64.hi(ctypes.Int64(-0x28590a1c921de000)), -0x28590a1d); + do_check_throws(function() { ctypes.Int64.lo(ctypes.UInt64(0)); }, Error); + do_check_throws(function() { ctypes.Int64.hi(ctypes.UInt64(0)); }, Error); + do_check_throws(function() { ctypes.Int64.lo(0); }, Error); + do_check_throws(function() { ctypes.Int64.hi(0); }, Error); + + // Test ctypes.Int64.join. + do_check_eq(ctypes.Int64.join(0, 0).toString(), "0"); + do_check_eq(ctypes.Int64.join(0x28590a1c, 0x921de000).toString(16), "28590a1c921de000"); + do_check_eq(ctypes.Int64.join(-0x28590a1d, 0x6de22000).toString(16), "-28590a1c921de000"); + do_check_eq(ctypes.Int64.join(0x7fffffff, 0xffffffff).toString(16), "7fffffffffffffff"); + do_check_eq(ctypes.Int64.join(-0x80000000, 0x00000000).toString(16), "-8000000000000000"); + do_check_throws(function() { ctypes.Int64.join(-0x80000001, 0); }, TypeError); + do_check_throws(function() { ctypes.Int64.join(0x80000000, 0); }, TypeError); + do_check_throws(function() { ctypes.Int64.join(0, -0x1); }, TypeError); + do_check_throws(function() { ctypes.Int64.join(0, 0x800000000); }, TypeError); +} + +function run_UInt64_tests() { + do_check_throws(function() { ctypes.UInt64(); }, Error); + + let i = ctypes.UInt64(0); + do_check_true(i instanceof ctypes.UInt64); + + // Test UInt64.toString([radix]). + do_check_eq(i.toString(), "0"); + for (let radix = 2; radix <= 36; ++radix) + do_check_eq(i.toString(radix), "0"); + do_check_throws(function() { i.toString(0); }, Error); + do_check_throws(function() { i.toString(1); }, Error); + do_check_throws(function() { i.toString(37); }, Error); + + i = ctypes.UInt64("0x28590a1c921def71"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "2907366152271163249"); + do_check_eq(i.toString(16), "28590a1c921def71"); + do_check_eq(i.toString(2), "10100001011001000010100001110010010010000111011110111101110001"); + + i = ctypes.UInt64("0X28590A1c921DEf71"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "2907366152271163249"); + do_check_eq(i.toString(16), "28590a1c921def71"); + do_check_eq(i.toString(2), "10100001011001000010100001110010010010000111011110111101110001"); + + // Test UInt64(primitive double) constructor. + i = ctypes.UInt64(-0); + do_check_eq(i.toString(), "0"); + + i = ctypes.UInt64(0xfffffffffffff000); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "18446744073709547520"); + do_check_eq(i.toString(16), "fffffffffffff000"); + do_check_eq(i.toString(2), "1111111111111111111111111111111111111111111111111111000000000000"); + + // Test UInt64(string) constructor. + i = ctypes.UInt64("0xffffffffffffffff"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "18446744073709551615"); + do_check_eq(i.toString(16), "ffffffffffffffff"); + do_check_eq(i.toString(2), "1111111111111111111111111111111111111111111111111111111111111111"); + + i = ctypes.UInt64("0x0"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "0"); + do_check_eq(i.toString(16), "0"); + do_check_eq(i.toString(2), "0"); + + i = ctypes.UInt64("18446744073709551615"); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "18446744073709551615"); + do_check_eq(i.toString(16), "ffffffffffffffff"); + do_check_eq(i.toString(2), "1111111111111111111111111111111111111111111111111111111111111111"); + + i = ctypes.UInt64("0"); + do_check_eq(i.toString(), "0"); + + // Test UInt64(other UInt64) constructor. + i = ctypes.UInt64(ctypes.UInt64(0)); + do_check_eq(i.toString(), "0"); + + i = ctypes.UInt64(ctypes.UInt64("0xffffffffffffffff")); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "18446744073709551615"); + do_check_eq(i.toString(16), "ffffffffffffffff"); + do_check_eq(i.toString(2), "1111111111111111111111111111111111111111111111111111111111111111"); + + i = ctypes.UInt64(ctypes.UInt64("0x0")); + do_check_eq(i.toString(), "0"); + + // Test UInt64(other Int64) constructor. + i = ctypes.UInt64(ctypes.Int64(0)); + do_check_eq(i.toString(), "0"); + + i = ctypes.UInt64(ctypes.Int64("0x7fffffffffffffff")); + do_check_eq(i.toString(), i.toString(10)); + do_check_eq(i.toString(10), "9223372036854775807"); + do_check_eq(i.toString(16), "7fffffffffffffff"); + do_check_eq(i.toString(2), "111111111111111111111111111111111111111111111111111111111111111"); + + let vals = [-1, 0x10000000000000000, "-1", "-0x1", "0x10000000000000000", + ctypes.Int64("-1"), Infinity, -Infinity, NaN, 0.1, + 5.68e21, null, undefined, "", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { ctypes.UInt64(vals[i]); }, TypeError); + + // Test ctypes.UInt64.compare. + do_check_eq(ctypes.UInt64.compare(ctypes.UInt64(5), ctypes.UInt64(5)), 0); + do_check_eq(ctypes.UInt64.compare(ctypes.UInt64(5), ctypes.UInt64(4)), 1); + do_check_eq(ctypes.UInt64.compare(ctypes.UInt64(4), ctypes.UInt64(5)), -1); + do_check_throws(function() { ctypes.UInt64.compare(ctypes.UInt64(4), ctypes.Int64(4)); }, Error); + do_check_throws(function() { ctypes.UInt64.compare(4, 5); }, Error); + + // Test ctypes.UInt64.{lo,hi}. + do_check_eq(ctypes.UInt64.lo(ctypes.UInt64(0x28590a1c921de000)), 0x921de000); + do_check_eq(ctypes.UInt64.hi(ctypes.UInt64(0x28590a1c921de000)), 0x28590a1c); + do_check_eq(ctypes.UInt64.lo(ctypes.UInt64(0xa8590a1c921de000)), 0x921de000); + do_check_eq(ctypes.UInt64.hi(ctypes.UInt64(0xa8590a1c921de000)), 0xa8590a1c); + do_check_throws(function() { ctypes.UInt64.lo(ctypes.Int64(0)); }, Error); + do_check_throws(function() { ctypes.UInt64.hi(ctypes.Int64(0)); }, Error); + do_check_throws(function() { ctypes.UInt64.lo(0); }, Error); + do_check_throws(function() { ctypes.UInt64.hi(0); }, Error); + + // Test ctypes.UInt64.join. + do_check_eq(ctypes.UInt64.join(0, 0).toString(), "0"); + do_check_eq(ctypes.UInt64.join(0x28590a1c, 0x921de000).toString(16), "28590a1c921de000"); + do_check_eq(ctypes.UInt64.join(0xa8590a1c, 0x921de000).toString(16), "a8590a1c921de000"); + do_check_eq(ctypes.UInt64.join(0xffffffff, 0xffffffff).toString(16), "ffffffffffffffff"); + do_check_eq(ctypes.UInt64.join(0, 0).toString(16), "0"); + do_check_throws(function() { ctypes.UInt64.join(-0x1, 0); }, TypeError); + do_check_throws(function() { ctypes.UInt64.join(0x100000000, 0); }, TypeError); + do_check_throws(function() { ctypes.UInt64.join(0, -0x1); }, TypeError); + do_check_throws(function() { ctypes.UInt64.join(0, 0x1000000000); }, TypeError); +} + +function run_basic_abi_tests(library, t, name, toprimitive, + get_test, set_tests, sum_tests, sum_many_tests) { + // Test the function call ABI for calls involving the type. + run_single_abi_tests(library, ctypes.default_abi, t, name + "_cdecl", + toprimitive, get_test, set_tests, sum_tests, sum_many_tests); +#ifdef _WIN32 +#ifndef _WIN64 + run_single_abi_tests(library, ctypes.stdcall_abi, t, name + "_stdcall", + toprimitive, get_test, set_tests, sum_tests, sum_many_tests); +#endif +#endif + + // Check the alignment of the type, and its behavior in a struct, + // against what C says. + check_struct_stats(library, t); +} + +function run_single_abi_tests(library, abi, t, name, toprimitive, + get_test, set_tests, sum_tests, sum_many_tests) { + let getter = library.declare("get_" + name, abi, t); + do_check_eq(toprimitive(getter()), get_test); + + let setter = library.declare("set_" + name, ctypes.default_abi, t, t); + for each (let i in set_tests) + do_check_eq(toprimitive(setter(i)), i); + + let sum = library.declare("sum_" + name, ctypes.default_abi, t, t, t); + for each (let a in sum_tests) + do_check_eq(toprimitive(sum(a[0], a[1])), a[2]); + + let sum_alignb = library.declare("sum_alignb_" + name, ctypes.default_abi, t, + ctypes.char, t, ctypes.char, t, ctypes.char); + let sum_alignf = library.declare("sum_alignf_" + name, ctypes.default_abi, t, + ctypes.float, t, ctypes.float, t, ctypes.float); + for each (let a in sum_tests) { + do_check_eq(toprimitive(sum_alignb(0, a[0], 0, a[1], 0)), a[2]); + do_check_eq(toprimitive(sum_alignb(1, a[0], 1, a[1], 1)), a[2]); + do_check_eq(toprimitive(sum_alignf(0, a[0], 0, a[1], 0)), a[2]); + do_check_eq(toprimitive(sum_alignf(1, a[0], 1, a[1], 1)), a[2]); + } + + let sum_many = library.declare("sum_many_" + name, ctypes.default_abi, t, + t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t); + for each (let a in sum_many_tests) + do_check_eq(toprimitive( + sum_many(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], + a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], + a[16], a[17])), a[18]); +} + +function check_struct_stats(library, t) { + let s_t = ctypes.StructType("s_t", [{ x: ctypes.char }, { y: t }]); + let n_t = ctypes.StructType("n_t", [{ a: ctypes.char }, { b: s_t }, { c: ctypes.char }]); + let get_stats = library.declare("get_" + t.name + "_stats", + ctypes.default_abi, ctypes.void_t, + ctypes.size_t.ptr, ctypes.size_t.ptr, ctypes.size_t.ptr, ctypes.size_t.ptr, + ctypes.size_t.array().ptr); + + let align = ctypes.size_t(); + let size = ctypes.size_t(); + let nalign = ctypes.size_t(); + let nsize = ctypes.size_t(); + let offsets = ctypes.size_t.array(3)(); + get_stats(align.address(), size.address(), nalign.address(), nsize.address(), + offsets.address()); + + do_check_eq(size.value, s_t.size); + do_check_eq(align.value, s_t.size - t.size); + do_check_eq(align.value, offsetof(s_t, "y")); + do_check_eq(nsize.value, n_t.size); + do_check_eq(nalign.value, offsetof(n_t, "b")); + do_check_eq(offsets[0], offsetof(s_t, "y")); + do_check_eq(offsets[1], offsetof(n_t, "b")); + do_check_eq(offsets[2], offsetof(n_t, "c")); +} + +// Determine the offset, in bytes, of 'member' within 'struct'. +function offsetof(struct, member) { + let instance = struct(); + let memberptr = ptrValue(instance.addressOfField(member)); + let chararray = ctypes.cast(instance, ctypes.char.array(struct.size)); + let offset = 0; + while (memberptr != ptrValue(chararray.addressOfElement(offset))) + ++offset; + return offset; +} + +function run_bool_tests(library) { + let t = ctypes.bool; + let name = "bool"; + do_check_eq(t.name, name); + do_check_true(t.size == 1 || t.size == 4); + + do_check_true(t instanceof ctypes.CType); + do_check_true(t.constructor === ctypes.CType); + do_check_true(t.__proto__ === ctypes.CType.prototype); + do_check_true(t.prototype.constructor === t); + + do_check_eq(t.toString(), "type " + name); + do_check_eq(t.toSource(), "ctypes." + name); + do_check_true(t.ptr === ctypes.PointerType(t)); + do_check_eq(t.array().name, name + "[]"); + do_check_eq(t.array(5).name, name + "[5]"); + + let d = t(); + do_check_true(d.__proto__ === t.prototype); + do_check_true(d.constructor === t); + do_check_eq(d.value, 0); + d.value = 1; + do_check_eq(d.value, 1); + d.value = -0; + do_check_eq(1/d.value, 1/0); + d.value = false; + do_check_eq(d.value, 0); + d.value = true; + do_check_eq(d.value, 1); + d = new t(1); + do_check_eq(d.value, 1); + + // don't convert anything else + let vals = [-1, 2, Infinity, -Infinity, NaN, 0.1, + ctypes.Int64(0), ctypes.UInt64(0), + null, undefined, "", "0", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { d.value = vals[i]; }, TypeError); + + do_check_true(d.address().constructor === t.ptr); + do_check_eq(d.address().contents, d.value); + do_check_eq(d.toSource(), "ctypes." + name + "(" + d.value + ")"); + do_check_eq(d.toSource(), d.toString()); + + // Test the function call ABI for calls involving the type, + // and check the alignment of the type against what C says. + function toprimitive(a) { return a; } + run_basic_abi_tests(library, t, name, toprimitive, + true, + [ false, true ], + [ [ false, false, false ], [ false, true, true ], + [ true, false, true ], [true, true, true ] ], + [ [ false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, + false ], + [ true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, + true ] ]); +} + +function run_integer_tests(library, t, name, size, signed, limits) { + do_check_eq(t.name, name); + do_check_eq(t.size, size); + + do_check_true(t instanceof ctypes.CType); + do_check_true(t.constructor === ctypes.CType); + do_check_true(t.__proto__ === ctypes.CType.prototype); + do_check_true(t.prototype.constructor === t); + + do_check_eq(t.toString(), "type " + name); + do_check_eq(t.toSource(), "ctypes." + name); + do_check_true(t.ptr === ctypes.PointerType(t)); + do_check_eq(t.array().name, name + "[]"); + do_check_eq(t.array(5).name, name + "[5]"); + + // Check the alignment of the type, and its behavior in a struct, + // against what C says. + check_struct_stats(library, t); + + let d = t(); + // TODO: should this work? + // do_check_true(d instanceof t); + do_check_true(d.__proto__ === t.prototype); + do_check_true(d.constructor === t); + do_check_eq(d.value, 0); + d.value = 5; + do_check_eq(d.value, 5); + d = t(10); + do_check_eq(d.value, 10); + if (signed) { + d.value = -10; + do_check_eq(d.value, -10); + } + d = new t(20); + do_check_eq(d.value, 20); + + d.value = ctypes.Int64(5); + do_check_eq(d.value, 5); + if (signed) { + d.value = ctypes.Int64(-5); + do_check_eq(d.value, -5); + } + d.value = ctypes.UInt64(5); + do_check_eq(d.value, 5); + + d.value = limits[0]; + do_check_eq(d.value, limits[0]); + d.value = limits[1]; + do_check_eq(d.value, limits[1]); + d.value = 0; + do_check_eq(d.value, 0); + d.value = -0; + do_check_eq(1/d.value, 1/0); + d.value = false; + do_check_eq(d.value, 0); + d.value = true; + do_check_eq(d.value, 1); + + // don't convert anything else + let vals = [limits[0] - 1, limits[1] + 1, Infinity, -Infinity, NaN, 0.1, + null, undefined, "", "0", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { d.value = vals[i]; }, TypeError); + + do_check_true(d.address().constructor === t.ptr); + do_check_eq(d.address().contents, d.value); + do_check_eq(d.toSource(), "ctypes." + name + "(" + d.value + ")"); + do_check_eq(d.toSource(), d.toString()); + + // Test the function call ABI for calls involving the type, + // and check the alignment of the type against what C says. + function toprimitive(a) { return a; } + run_basic_abi_tests(library, t, name, toprimitive, + 109, + [ 0, limits[0], limits[1] ], + [ [ 0, 0, 0 ], [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 2 ], + [ limits[0], 1, limits[0] + 1 ], + [ limits[1], 1, limits[0] ] ], + [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 ], + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 18 ] ]); +} + +function run_float_tests(library, t, name, size) { + do_check_eq(t.name, name); + do_check_eq(t.size, size); + + do_check_true(t instanceof ctypes.CType); + do_check_true(t.constructor === ctypes.CType); + do_check_true(t.__proto__ === ctypes.CType.prototype); + do_check_true(t.prototype.constructor === t); + + do_check_eq(t.toString(), "type " + name); + do_check_eq(t.toSource(), "ctypes." + name); + do_check_true(t.ptr === ctypes.PointerType(t)); + do_check_eq(t.array().name, name + "[]"); + do_check_eq(t.array(5).name, name + "[5]"); + + let d = t(); + do_check_true(d.__proto__ === t.prototype); + do_check_true(d.constructor === t); + do_check_eq(d.value, 0); + d.value = 5; + do_check_eq(d.value, 5); + d.value = 5.25; + do_check_eq(d.value, 5.25); + d = t(10); + do_check_eq(d.value, 10); + d.value = -10; + do_check_eq(d.value, -10); + d = new t(20); + do_check_eq(d.value, 20); + + d.value = ctypes.Int64(5); + do_check_eq(d.value, 5); + d.value = ctypes.Int64(-5); + do_check_eq(d.value, -5); + d.value = ctypes.UInt64(5); + do_check_eq(d.value, 5); + + if (size == 4) { + d.value = 0x7fffff; + do_check_eq(d.value, 0x7fffff); + + // allow values that can't be represented precisely as a float + d.value = 0xffffffff; + let delta = 1 - d.value/0xffffffff; + do_check_true(delta != 0); + do_check_true(delta > -0.01 && delta < 0.01); + d.value = 1 + 1/0x80000000; + do_check_eq(d.value, 1); + } else { + d.value = 0xfffffffffffff000; + do_check_eq(d.value, 0xfffffffffffff000); + + // allow values that can't be represented precisely as a double + d.value = ctypes.Int64("0x7fffffffffffffff"); + do_check_eq(d.value, 0x7fffffffffffffff); + } + + d.value = Infinity; + do_check_eq(d.value, Infinity); + d.value = -Infinity; + do_check_eq(d.value, -Infinity); + d.value = NaN; + do_check_true(isNaN(d.value)); + d.value = 0; + do_check_eq(d.value, 0); + d.value = -0; + do_check_eq(1/d.value, 1/-0); + + // don't convert anything else + let vals = [true, false, null, undefined, "", "0", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { d.value = vals[i]; }, TypeError); + + do_check_true(d.address().constructor === t.ptr); + do_check_eq(d.address().contents, d.value); + do_check_eq(d.toSource(), "ctypes." + name + "(" + d.value + ")"); + do_check_eq(d.toSource(), d.toString()); + + // Test the function call ABI for calls involving the type, + // and check the alignment of the type against what C says. + let operand = []; + if (size == 4) { + operand[0] = 503859.75; + operand[1] = 1012385.25; + operand[2] = 1516245; + } else { + operand[0] = 501823873859.75; + operand[1] = 171290577385.25; + operand[2] = 673114451245; + } + function toprimitive(a) { return a; } + run_basic_abi_tests(library, t, name, toprimitive, + 109.25, + [ 0, operand[0], operand[1] ], + [ [ 0, 0, 0 ], [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 2 ], + [ operand[0], operand[1], operand[2] ] ], + [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 ], + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 18 ] ]); +} + +function run_wrapped_integer_tests(library, t, name, size, signed, w, wname, limits) { + do_check_eq(t.name, name); + do_check_eq(t.size, size); + + do_check_true(t instanceof ctypes.CType); + do_check_true(t.constructor === ctypes.CType); + do_check_true(t.__proto__ === ctypes.CType.prototype); + do_check_true(t.prototype.constructor === t); + + do_check_eq(t.toString(), "type " + name); + do_check_eq(t.toSource(), "ctypes." + name); + do_check_true(t.ptr === ctypes.PointerType(t)); + do_check_eq(t.array().name, name + "[]"); + do_check_eq(t.array(5).name, name + "[5]"); + + let d = t(); + do_check_true(d.__proto__ === t.prototype); + do_check_true(d.constructor === t); + do_check_true(d.value instanceof w); + do_check_eq(d.value, 0); + d.value = 5; + do_check_eq(d.value, 5); + d = t(10); + do_check_eq(d.value, 10); + if (signed) { + d.value = -10; + do_check_eq(d.value, -10); + } + d = new t(20); + do_check_eq(d.value, 20); + + d.value = ctypes.Int64(5); + do_check_eq(d.value, 5); + if (signed) { + d.value = ctypes.Int64(-5); + do_check_eq(d.value, -5); + } + d.value = ctypes.UInt64(5); + do_check_eq(d.value, 5); + + d.value = w(limits[0]); + do_check_eq(d.value, limits[0]); + d.value = w(limits[1]); + do_check_eq(d.value, limits[1]); + d.value = 0; + do_check_eq(d.value, 0); + d.value = -0; + do_check_eq(1/d.value, 1/0); + d.value = false; + do_check_eq(d.value, 0); + d.value = true; + do_check_eq(d.value, 1); + + // don't convert anything else + let vals = [limits[2], limits[3], Infinity, -Infinity, NaN, 0.1, + null, undefined, "", "0", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { d.value = vals[i]; }, TypeError); + + do_check_true(d.address().constructor === t.ptr); + do_check_eq(d.address().contents.toString(), d.value.toString()); + do_check_eq(d.toSource(), "ctypes." + name + "(" + wname + "(\"" + d.value + "\"))"); + do_check_eq(d.toSource(), d.toString()); + + // Test the function call ABI for calls involving the type, + // and check the alignment of the type against what C says. + function toprimitive(a) { return a.toString(); } + run_basic_abi_tests(library, t, name, toprimitive, + 109, + [ 0, w(limits[0]), w(limits[1]) ], + [ [ 0, 0, 0 ], [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 2 ], + signed ? [ w(limits[0]), -1, w(limits[1]) ] + : [ w(limits[1]), 1, w(limits[0]) ] ], + [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 ], + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 18 ] ]); +} + +function run_char_tests(library, t, name, size, signed, limits) { + do_check_eq(t.name, name); + do_check_eq(t.size, size); + + do_check_true(t instanceof ctypes.CType); + do_check_true(t.constructor === ctypes.CType); + do_check_true(t.__proto__ === ctypes.CType.prototype); + do_check_true(t.prototype.constructor === t); + + do_check_eq(t.toString(), "type " + name); + do_check_eq(t.toSource(), "ctypes." + name); + do_check_true(t.ptr === ctypes.PointerType(t)); + do_check_eq(t.array().name, name + "[]"); + do_check_eq(t.array(5).name, name + "[5]"); + + let d = t(); + do_check_true(d.__proto__ === t.prototype); + do_check_true(d.constructor === t); + do_check_eq(d.value.length, 1); + do_check_eq(d.value.charCodeAt(0), 0); + d.value = 5; + do_check_eq(d.value.length, 1); + do_check_eq(d.value.charCodeAt(0), 5); + d = t(10); + do_check_eq(d.value.charCodeAt(0), 10); + + function toprimitive(a) { + let r = a.charCodeAt(0); + if (signed) { + if (t.size == 1) + return r > 0x7f ? r - 0x100 : r; + return r > 0x7fff ? r - 0x10000 : r; + } + return r; + } + + if (signed) { + d.value = -10; + do_check_eq(toprimitive(d.value), -10); + } + d = new t(20); + do_check_eq(d.value.charCodeAt(0), 20); + + d.value = ctypes.Int64(5); + do_check_eq(d.value.charCodeAt(0), 5); + if (signed) { + d.value = ctypes.Int64(-10); + do_check_eq(toprimitive(d.value), -10); + } + d.value = ctypes.UInt64(5); + do_check_eq(d.value.charCodeAt(0), 5); + + d.value = limits[0]; + do_check_eq(toprimitive(d.value), limits[0]); + d.value = limits[1]; + do_check_eq(d.value.charCodeAt(0), limits[1]); + d.value = 0; + do_check_eq(d.value.charCodeAt(0), 0); + d.value = -0; + do_check_eq(1/d.value.charCodeAt(0), 1/0); + d.value = false; + do_check_eq(d.value.charCodeAt(0), 0); + d.value = true; + do_check_eq(d.value.charCodeAt(0), 1); + + d.value = "\0"; + do_check_eq(d.value.charCodeAt(0), 0); + d.value = "a"; + do_check_eq(d.value, "a"); + + // don't convert anything else + let vals = [limits[0] - 1, limits[1] + 1, Infinity, -Infinity, NaN, 0.1, + null, undefined, "", "aa", {}, [], new Number(16), + {toString: function () { return 7; }}, + {valueOf: function () { return 7; }}]; + for (let i = 0; i < vals.length; i++) + do_check_throws(function () { d.value = vals[i]; }, TypeError); + + do_check_true(d.address().constructor === t.ptr); + do_check_eq(d.address().contents, "a"); + do_check_eq(d.toSource(), "ctypes." + name + "(" + d.value.charCodeAt(0) + ")"); + do_check_eq(d.toSource(), d.toString()); + + // Test string autoconversion (and lack thereof). + let literal = "autoconverted"; + let s = t.array()(literal); + do_check_eq(s.readString(), literal); + do_check_eq(s.constructor.length, literal.length + 1); + s = t.array(50)(literal); + do_check_eq(s.readString(), literal); + do_check_throws(function() { t.array(3)(literal); }, Error); + + do_check_throws(function() { t.ptr(literal); }, TypeError); + let p = t.ptr(s); + do_check_eq(p.readString(), literal); + + // Test the function call ABI for calls involving the type, + // and check the alignment of the type against what C says. + run_basic_abi_tests(library, t, name, toprimitive, + 109, + [ 0, limits[0], limits[1] ], + [ [ 0, 0, 0 ], [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 2 ], + [ limits[0], 1, limits[0] + 1 ], + [ limits[1], 1, limits[0] ] ], + [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 ], + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 18 ] ]); +} + +function run_StructType_tests() { + do_check_throws(function() { ctypes.StructType(); }, Error); + do_check_throws(function() { ctypes.StructType("a"); }, Error); + + // StructType size, alignment, and offset calculations have already been + // checked for each basic type. We do not need to check them again. + let name = "g_t"; + let g_t = ctypes.StructType(name, [{ a: ctypes.int32_t }, { b: ctypes.double }]); + do_check_eq(g_t.name, name); + + do_check_true(g_t instanceof ctypes.CType); + do_check_true(g_t instanceof ctypes.StructType); + do_check_true(g_t.constructor === ctypes.StructType); + do_check_true(g_t.__proto__ === ctypes.StructType.prototype); + do_check_true(g_t.prototype.constructor === g_t); + + do_check_eq(g_t.toString(), "type " + name); + do_check_eq(g_t.toSource(), + "ctypes.StructType(\"g_t\", [{ \"a\": ctypes.int32_t }, { \"b\": ctypes.double }])"); + do_check_true(g_t.ptr === ctypes.PointerType(g_t)); + do_check_eq(g_t.array().name, name + "[]"); + do_check_eq(g_t.array(5).name, name + "[5]"); + + let h_t = ctypes.StructType("h_t", [{ a: ctypes.int32_t }, { b: ctypes.int16_t }]); + let s_t = new ctypes.StructType("s_t", [{ a: ctypes.int32_t }, { b: g_t }, { c: ctypes.int8_t }]); + + let fields = [{ a: ctypes.int32_t }, { b: ctypes.int8_t }, { c: g_t }, { d: ctypes.int8_t }]; + let t_t = new ctypes.StructType("t_t", fields); + do_check_eq(t_t.fields.length, 4); + do_check_true(t_t.fields[0].a === ctypes.int32_t); + do_check_true(t_t.fields[1].b === ctypes.int8_t); + do_check_true(t_t.fields[2].c === g_t); + do_check_true(t_t.fields[3].d === ctypes.int8_t); + do_check_throws(function() { t_t.fields.z = 0; }, Error); + do_check_throws(function() { t_t.fields[4] = 0; }, Error); + do_check_throws(function() { t_t.fields[4].a = 0; }, Error); + do_check_throws(function() { t_t.fields[4].e = 0; }, Error); + + let g = g_t(); + do_check_eq(g.a, 0); + do_check_eq(g.b, 0); + g = new g_t(1, 2); + // TODO: should this work? + // do_check_true(g instanceof g_t); + do_check_true(g.__proto__ === g_t.prototype); + do_check_true(g.constructor === g_t); + do_check_eq(g.a, 1); + do_check_eq(g.b, 2); + do_check_throws(function() { g_t(1); }, Error); + + let g_a = g.address(); + do_check_true(g_a.constructor === g_t.ptr); + do_check_eq(g_a.contents.a, g.a); + + let s = new s_t(4, g, 10); + do_check_eq(s.a, 4); + do_check_eq(s.b.a, 1); + do_check_eq(s.b.b, 2); + do_check_eq(s.c, 10); + let g2 = s.b; + g2.a = 7; + do_check_eq(s.b.a, 7); + + g_a = s.addressOfField("b"); + do_check_true(g_a.constructor === g_t.ptr); + do_check_eq(g_a.contents.a, s.b.a); + do_check_throws(function() { s.addressOfField(); }, Error); + do_check_throws(function() { s.addressOfField("d"); }, Error); + do_check_throws(function() { s.addressOfField("a", 2); }, Error); + + do_check_eq(s.toSource(), "s_t(4, { \"a\": 7, \"b\": 2 }, 10)"); + do_check_eq(s.toSource(), s.toString()); + eval("let s2 = " + s.toSource()); + do_check_true(s2.constructor === s_t); + do_check_eq(s.b.b, s2.b.b); + + // Test that structs can be constructed through ImplicitConvert. + do_check_throws(function() { s.value; }, Error); + s.value = { "a": 2, "b": { "a": 9, "b": 5 }, "c": 13 }; + do_check_eq(s.b.a, 9); + do_check_eq(s.c, 13); + do_check_throws(function() { s.value = 5; }, Error); + do_check_throws(function() { s.value = { "a": 2 }; }, Error); + do_check_throws(function() { s.value = { "a": 2, "b": 5, "c": 10 }; }, Error); + do_check_throws(function() { + s.value = { "a": 2, "b": { "a": 9, "b": 5 }, "c": 13, "d": 17 }; + }, Error); + do_check_throws(function() { + s.value = { "a": 2, "b": { "a": 9, "b": 5, "e": 9 }, "c": 13 }; + }, Error); + + // Check that the empty struct has size 1. + let z_t = ctypes.StructType("z_t", []); + do_check_eq(z_t.size, 1); + do_check_eq(z_t.fields.length, 0); + + // Check that structs containing arrays of undefined or zero length + // are illegal, but arrays of defined length work. + do_check_throws(function() { + ctypes.StructType("z_t", [{ a: ctypes.int32_t.array() }]); + }, Error); + do_check_throws(function() { + ctypes.StructType("z_t", [{ a: ctypes.int32_t.array(0) }]); + }, Error); + z_t = ctypes.StructType("z_t", [{ a: ctypes.int32_t.array(6) }]); + do_check_eq(z_t.size, ctypes.int32_t.size * 6); + let z = z_t([1, 2, 3, 4, 5, 6]); + do_check_eq(z.a[3], 4); +} + +function ptrValue(p) { + return ctypes.cast(p, ctypes.uintptr_t).value.toString(); +} + +function run_PointerType_tests() { + do_check_throws(function() { ctypes.PointerType(); }, Error); + + let name = "g_t"; + let g_t = ctypes.StructType(name, [{ a: ctypes.int32_t }, { b: ctypes.double }]); + let g = g_t(1, 2); + + let p_t = ctypes.PointerType(g_t); + do_check_eq(p_t.name, name + "*"); + do_check_eq(p_t.size, ctypes.uintptr_t.size); + do_check_true(p_t.targetType === g_t); + do_check_true(p_t === g_t.ptr); + + do_check_true(p_t instanceof ctypes.CType); + do_check_true(p_t instanceof ctypes.PointerType); + do_check_true(p_t.constructor === ctypes.PointerType); + do_check_true(p_t.__proto__ === ctypes.PointerType.prototype); + do_check_true(p_t.prototype.constructor === p_t); + + do_check_eq(p_t.toString(), "type " + name + "*"); + do_check_eq(p_t.toSource(), + "ctypes.StructType(\"g_t\", [{ \"a\": ctypes.int32_t }, { \"b\": ctypes.double }]).ptr"); + do_check_true(p_t.ptr === ctypes.PointerType(p_t)); + do_check_eq(p_t.array().name, name + "*[]"); + do_check_eq(p_t.array(5).name, name + "*[5]"); + + // Test ExplicitConvert. + let p = p_t(); + do_check_throws(function() { p.value; }, Error); + do_check_eq(ptrValue(p), 0); + do_check_throws(function() { p.contents; }, Error); + p = p_t(5); + do_check_eq(ptrValue(p), 5); + p = p_t(ctypes.UInt64(10)); + do_check_eq(ptrValue(p), 10); + + // Test ImplicitConvert. + p.value = null; + do_check_eq(ptrValue(p), 0); + do_check_throws(function() { p.value = 5; }, Error); + + // Test opaque pointers. + let f_t = ctypes.PointerType("FILE*"); + do_check_eq(f_t.name, "FILE*"); + let f = new f_t(); + do_check_throws(function() { f.contents; }, Error); + + do_check_throws(function() { f_t(p); }, Error); + do_check_throws(function() { f.value = p; }, Error); + do_check_throws(function() { p.value = f; }, Error); + + // Test void pointers. + let v_t = ctypes.PointerType(ctypes.void_t); + do_check_true(v_t === ctypes.voidptr_t); + let v = v_t(p); + do_check_eq(ptrValue(v), ptrValue(p)); + + // Test 'contents'. + let i = ctypes.int32_t(9); + p = i.address(); + do_check_eq(p.contents, i.value); + + // Check that pointers to arrays of undefined or zero length are legal, + // but that the former cannot be dereferenced. + let z_t = ctypes.int32_t.array().ptr; + do_check_eq(ptrValue(z_t()), 0); + do_check_throws(function() { z_t().contents }, Error); + z_t = ctypes.int32_t.array(0).ptr; + do_check_eq(ptrValue(z_t()), 0); + let z = ctypes.int32_t.array(0)().address(); + do_check_eq(z.contents.length, 0); +} + +function run_ArrayType_tests() { + do_check_throws(function() { ctypes.ArrayType(); }, Error); + + let name = "g_t"; + let g_t = ctypes.StructType(name, [{ a: ctypes.int32_t }, { b: ctypes.double }]); + let g = g_t(1, 2); + + let a_t = ctypes.ArrayType(g_t, 10); + do_check_eq(a_t.name, name + "[10]"); + do_check_eq(a_t.length, 10); + do_check_eq(a_t.size, g_t.size * 10); + do_check_true(a_t.elementType === g_t); + + do_check_true(a_t instanceof ctypes.CType); + do_check_true(a_t instanceof ctypes.ArrayType); + do_check_true(a_t.constructor === ctypes.ArrayType); + do_check_true(a_t.__proto__ === ctypes.ArrayType.prototype); + do_check_true(a_t.prototype.constructor === a_t); + + do_check_eq(a_t.toString(), "type " + name + "[10]"); + do_check_eq(a_t.toSource(), + "ctypes.StructType(\"g_t\", [{ \"a\": ctypes.int32_t }, { \"b\": ctypes.double }]).array(10)"); + do_check_eq(a_t.array().name, name + "[][10]"); + do_check_eq(a_t.array(5).name, name + "[5][10]"); + do_check_throws(function() { ctypes.int32_t.array().array(); }, Error); + + let a = new a_t(); + do_check_eq(a[0].a, 0); + do_check_eq(a[0].b, 0); + a[0] = g; + do_check_eq(a[0].a, 1); + do_check_eq(a[0].b, 2); + do_check_throws(function() { a[-1]; }, Error); + do_check_eq(a[9].a, 0); + do_check_throws(function() { a[10]; }, Error); + + do_check_eq(a[ctypes.Int64(0)].a, 1); + do_check_eq(a[ctypes.UInt64(0)].b, 2); + + let a_p = a.addressOfElement(0); + do_check_true(a_p.constructor.targetType === g_t); + do_check_true(a_p.constructor === g_t.ptr); + do_check_eq(a_p.contents.a, a[0].a); + do_check_eq(a_p.contents.b, a[0].b); + a_p.contents.a = 5; + do_check_eq(a[0].a, 5); + + let a2_t = ctypes.ArrayType(g_t); + do_check_eq(a2_t.name, "g_t[]"); + do_check_eq(a2_t.length, undefined); + do_check_eq(a2_t.size, undefined); + let a2 = new a2_t(5); + do_check_eq(a2.constructor.length, 5); + do_check_eq(a2.length, 5); + do_check_eq(a2.constructor.size, g_t.size * 5); + do_check_throws(function() { new a2_t(); }, Error); + do_check_throws(function() { ctypes.ArrayType(ctypes.ArrayType(g_t)); }, Error); + do_check_throws(function() { ctypes.ArrayType(ctypes.ArrayType(g_t), 5); }, Error); + + let b_t = ctypes.int32_t.array(ctypes.UInt64(0xffff)); + do_check_eq(b_t.length, 0xffff); + b_t = ctypes.int32_t.array(ctypes.Int64(0xffff)); + do_check_eq(b_t.length, 0xffff); + do_check_throws(function() { + ctypes.int32_t.array(ctypes.UInt64(0xffffffffffffffff)); + }, Error); + if (ctypes.size_t.size == 8) { + b_t = ctypes.int32_t.array(0xfffffffffffff000); + do_check_eq(b_t.length, 0xfffffffffffff000); + } else { + do_check_throws(function() { ctypes.int32_t.array(0xffffffffff); }, Error); + } + + // Test that arrays ImplicitConvert to pointers. + let b = ctypes.int32_t.array(10)(); + let p = ctypes.int32_t.ptr(); + p.value = b; + do_check_eq(ptrValue(b.addressOfElement(0)), ptrValue(p)); + p = ctypes.voidptr_t(); + p.value = b; + do_check_eq(ptrValue(b.addressOfElement(0)), ptrValue(p)); + + // Test that arrays can be constructed through ImplicitConvert. + let c_t = ctypes.int32_t.array(6); + let c = c_t(); + c.value = [1, 2, 3, 4, 5, 6]; + do_check_eq(c.toSource(), "ctypes.int32_t.array(6)([ 1, 2, 3, 4, 5, 6 ])"); + do_check_eq(c.toSource(), c.toString()); + eval("let c2 = " + c.toSource()); + do_check_eq(c2.constructor.name, "int32_t[6]"); + do_check_eq(c2.length, 6); + do_check_eq(c2[3], c[3]); + + c.value = c; + do_check_eq(c[3], 4); + do_check_throws(function() { c.value; }, Error); + do_check_throws(function() { c.value = [1, 2, 3, 4, 5]; }, Error); + do_check_throws(function() { c.value = [1, 2, 3, 4, 5, 6, 7]; }, Error); + do_check_throws(function() { c.value = [1, 2, 7.4, 4, 5, 6]; }, Error); + do_check_throws(function() { c.value = []; }, Error); +} + +function run_cast_tests() { + // Test casting between basic types. + let i = ctypes.int32_t(); + let j = ctypes.cast(i, ctypes.int16_t); + do_check_eq(ptrValue(i.address()), ptrValue(j.address())); + do_check_eq(i.value, j.value); + let k = ctypes.cast(i, ctypes.uint32_t); + do_check_eq(ptrValue(i.address()), ptrValue(k.address())); + do_check_eq(i.value, k.value); + + // Test casting to a type of undefined or larger size. + do_check_throws(function() { ctypes.cast(i, ctypes.void_t); }, Error); + do_check_throws(function() { ctypes.cast(i, ctypes.int32_t.array()); }, Error); + do_check_throws(function() { ctypes.cast(i, ctypes.int64_t); }, Error); + + // Test casting between special types. + let g_t = ctypes.StructType("g_t", [{ a: ctypes.int32_t }, { b: ctypes.double }]); + let a_t = ctypes.ArrayType(g_t, 4); + let p_t = ctypes.PointerType(g_t); + + let a = a_t(); + a[0] = { a: 5, b: 7.5 }; + let g = ctypes.cast(a, g_t); + do_check_eq(ptrValue(a.address()), ptrValue(g.address())); + do_check_eq(a[0].a, g.a); + + let a2 = ctypes.cast(g, g_t.array(1)); + do_check_eq(ptrValue(a2.address()), ptrValue(g.address())); + do_check_eq(a2[0].a, g.a); + let a3 = ctypes.cast(g, g_t.array(0)); + + let p = g.address(); + let ip = ctypes.cast(p, ctypes.int32_t.ptr); + do_check_eq(ptrValue(ip), ptrValue(p)); + do_check_eq(ptrValue(ip.address()), ptrValue(p.address())); + do_check_eq(ip.contents, g.a); +} + function run_void_tests(library) { - var test_v = library.declare("test_v", ctypes.default_abi, ctypes.void_t); - do_check_eq(test_v(), undefined); -} + let test_void_t = library.declare("test_void_t_cdecl", ctypes.default_abi, ctypes.void_t); + do_check_eq(test_void_t(), undefined); -function run_int8_tests(library) { - var test_i8 = library.declare("test_i8", ctypes.default_abi, ctypes.int8_t); - do_check_eq(test_i8(), 123); - - var test_i8_i8 = library.declare("test_i8_i8", ctypes.default_abi, ctypes.int8_t, ctypes.int8_t); - do_check_eq(test_i8_i8(5), 5); - do_check_eq(test_i8_i8(0), 0); - do_check_eq(test_i8_i8(0x7f), 0x7f); - do_check_eq(test_i8_i8(-0x80), -0x80); - do_check_eq(1/test_i8_i8(-0), 1/0); // that is, test_i8_i8(-0) is +0 - do_check_eq(test_i8_i8(true), 1); - do_check_eq(test_i8_i8(false), 0); - - // don't convert anything else to an int8 - var vals = [0x80, -0x81, 0x100000000, Infinity, -Infinity, NaN, - null, undefined, "", "0", {}, [], new Number(16), - {toString: function () { return 7; }}, - {valueOf: function () { return 7; }}]; - for (var i = 0; i < vals.length; i++) - do_check_throws(function () { test_i8_i8(vals[i]); }, TypeError); - - var test_i8_i8_sum = library.declare("test_i8_i8_sum", ctypes.default_abi, ctypes.int8_t, ctypes.int8_t, ctypes.int8_t); - do_check_eq(test_i8_i8_sum(5, 5), 10); - - // test the range of unsigned. (we can reuse the signed C function - // here, since it's binary-compatible.) - var test_ui8_ui8 = library.declare("test_i8_i8", ctypes.default_abi, ctypes.uint8_t, ctypes.uint8_t); - do_check_eq(test_ui8_ui8(0xff), 0xff); - do_check_throws(function () { test_ui8_ui8(0x100); }, TypeError); - do_check_throws(function () { test_ui8_ui8(-1); }, TypeError); -} - -function run_int16_tests(library) { - var test_i16 = library.declare("test_i16", ctypes.default_abi, ctypes.int16_t); - do_check_eq(test_i16(), 12345); - - var test_i16_i16 = library.declare("test_i16_i16", ctypes.default_abi, ctypes.int16_t, ctypes.int16_t); - do_check_eq(test_i16_i16(5), 5); - do_check_eq(test_i16_i16(0), 0); - do_check_eq(test_i16_i16(0x7fff), 0x7fff); - do_check_eq(test_i16_i16(-0x8000), -0x8000); - do_check_eq(1/test_i16_i16(-0), 1/0); // that is, test_i16_i16(-0) is +0 - do_check_eq(test_i16_i16(true), 1); - do_check_eq(test_i16_i16(false), 0); - - // don't convert anything else to an int16 - var vals = [0x8000, -0x8001, 0x100000000, Infinity, -Infinity, NaN, - null, undefined, "", "0", {}, [], new Number(16), - {toString: function () { return 7; }}, - {valueOf: function () { return 7; }}]; - for (var i = 0; i < vals.length; i++) - do_check_throws(function () { test_i16_i16(vals[i]); }, TypeError); - - var test_i16_i16_sum = library.declare("test_i16_i16_sum", ctypes.default_abi, ctypes.int16_t, ctypes.int16_t, ctypes.int16_t); - do_check_eq(test_i16_i16_sum(5, 5), 10); - - // test the range of unsigned. (we can reuse the signed C function - // here, since it's binary-compatible.) - var test_ui16_ui16 = library.declare("test_i16_i16", ctypes.default_abi, ctypes.uint16_t, ctypes.uint16_t); - do_check_eq(test_ui16_ui16(0xffff), 0xffff); - do_check_throws(function () { test_ui16_ui16(0x10000); }, TypeError); - do_check_throws(function () { test_ui16_ui16(-1); }, TypeError); -} - -function run_int32_tests(library) { - var test_i32 = library.declare("test_i32", ctypes.default_abi, ctypes.int32_t); - do_check_eq(test_i32(), 123456789); - - var test_i32_i32 = library.declare("test_i32_i32", ctypes.default_abi, ctypes.int32_t, ctypes.int32_t); - do_check_eq(test_i32_i32(5), 5); - do_check_eq(test_i32_i32(0), 0); - do_check_eq(test_i32_i32(0x7fffffff), 0x7fffffff); - do_check_eq(test_i32_i32(-0x80000000), -0x80000000); - do_check_eq(1/test_i32_i32(-0), 1/0); // that is, test_i32_i32(-0) is +0 - do_check_eq(test_i32_i32(true), 1); - do_check_eq(test_i32_i32(false), 0); - - // don't convert anything else to an int32 - var vals = [0x80000000, -0x80000001, Infinity, -Infinity, NaN, - null, undefined, "", "0", {}, [], new Number(16), - {toString: function () { return 7; }}, - {valueOf: function () { return 7; }}]; - for (var i = 0; i < vals.length; i++) - do_check_throws(function () { test_i32_i32(vals[i]); }, TypeError); - - var test_i32_i32_sum = library.declare("test_i32_i32_sum", ctypes.default_abi, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t); - do_check_eq(test_i32_i32_sum(5, 5), 10); - - // test the range of unsigned. (we can reuse the signed C function - // here, since it's binary-compatible.) - var test_ui32_ui32 = library.declare("test_i32_i32", ctypes.default_abi, ctypes.uint32_t, ctypes.uint32_t); - do_check_eq(test_ui32_ui32(0xffffffff), 0xffffffff); - do_check_throws(function () { test_ui32_ui32(0x100000000); }, TypeError); - do_check_throws(function () { test_ui32_ui32(-1); }, TypeError); -} - -function run_int64_tests(library) { - var test_i64 = library.declare("test_i64", ctypes.default_abi, ctypes.int64_t); - // JS represents 64 bit ints as doubles, so we have to be careful how many - // significant digits we use - do_check_eq(test_i64(), 0x28590a1c921de000); - - var test_i64_i64 = library.declare("test_i64_i64", ctypes.default_abi, ctypes.int64_t, ctypes.int64_t); - do_check_eq(test_i64_i64(5), 5); - do_check_eq(test_i64_i64(0), 0); - do_check_eq(test_i64_i64(0x7ffffffffffff000), 0x7ffffffffffff000); - do_check_eq(test_i64_i64(-0x8000000000000000), -0x8000000000000000); - do_check_eq(1/test_i64_i64(-0), 1/0); // that is, test_i64_i64(-0) is +0 - do_check_eq(test_i64_i64(true), 1); - do_check_eq(test_i64_i64(false), 0); - - // don't convert anything else to an int64 - var vals = [0x8000000000000000, -0x8000000000001000, Infinity, -Infinity, NaN, - null, undefined, "", "0", {}, [], new Number(16), - {toString: function () { return 7; }}, - {valueOf: function () { return 7; }}]; - for (var i = 0; i < vals.length; i++) - do_check_throws(function () { test_i64_i64(vals[i]); }, TypeError); - - var test_i64_i64_sum = library.declare("test_i64_i64_sum", ctypes.default_abi, ctypes.int64_t, ctypes.int64_t, ctypes.int64_t); - do_check_eq(test_i64_i64_sum(5, 5), 10); - - // test the range of unsigned. (we can reuse the signed C function - // here, since it's binary-compatible.) - var test_ui64_ui64 = library.declare("test_i64_i64", ctypes.default_abi, ctypes.uint64_t, ctypes.uint64_t); - do_check_eq(test_ui64_ui64(0xfffffffffffff000), 0xfffffffffffff000); - do_check_throws(function () { test_ui64_ui64(0x10000000000000000); }, TypeError); - do_check_throws(function () { test_ui64_ui64(-1); }, TypeError); -} - -function run_float_tests(library) { - var test_f = library.declare("test_f", ctypes.default_abi, ctypes.float); - do_check_eq(test_f(), 123456.5); - - var test_f_f = library.declare("test_f_f", ctypes.default_abi, ctypes.float, ctypes.float); - do_check_eq(test_f_f(5), 5); - do_check_eq(test_f_f(5.25), 5.25); - do_check_eq(test_f_f(Infinity), Infinity); - do_check_eq(test_f_f(-Infinity), -Infinity); - do_check_eq(isNaN(test_f_f(NaN)), true); - do_check_eq(1/test_f_f(-0), 1/-0); // that is, test_f_f(-0) is -0 - - // allow values that can't be represented precisely as a float - do_check_eq(test_f_f(1 + 1/0x80000000), 1); - - // don't convert anything else to a float - var vals = [true, false, null, undefined, "", "0", {}, [], new Number(16), - {toString: function () { return 7; }}, - {valueOf: function () { return 7; }}]; - for (var i = 0; i < vals.length; i++) - do_check_throws(function () { test_f_f(vals[i]); }, TypeError); - - var test_f_f_sum = library.declare("test_f_f_sum", ctypes.default_abi, ctypes.float, ctypes.float, ctypes.float); - do_check_eq(test_f_f_sum(5, 5), 10); - do_check_eq(test_f_f_sum(5.5, 5.5), 11); -} - -function run_double_tests(library) { - var test_d = library.declare("test_d", ctypes.default_abi, ctypes.double); - do_check_eq(test_d(), 1234567890123456789.5); - - var test_d_d = library.declare("test_d_d", ctypes.default_abi, ctypes.double, ctypes.double); - do_check_eq(test_d_d(5), 5); - do_check_eq(test_d_d(5.25), 5.25); - do_check_eq(test_d_d(Infinity), Infinity); - do_check_eq(test_d_d(-Infinity), -Infinity); - do_check_eq(isNaN(test_d_d(NaN)), true); - do_check_eq(1/test_d_d(-0), 1/-0); // that is, test_d_d(-0) is -0 - - // don't convert anything else to a double - var vals = [true, false, null, undefined, "", "0", {}, [], new Number(16), - {toString: function () { return 7; }}, - {valueOf: function () { return 7; }}]; - for (var i = 0; i < vals.length; i++) - do_check_throws(function () { test_d_d(vals[i]); }, TypeError); - - var test_d_d_sum = library.declare("test_d_d_sum", ctypes.default_abi, ctypes.double, ctypes.double, ctypes.double); - do_check_eq(test_d_d_sum(5, 5), 10); - do_check_eq(test_d_d_sum(5.5, 5.5), 11); +#ifdef _WIN32 +#ifndef _WIN64 + test_void_t = library.declare("test_void_t_stdcall", ctypes.stdcall_abi, ctypes.void_t); + do_check_eq(test_void_t(), undefined); +#endif +#endif } function run_string_tests(library) { - var test_ansi_len = library.declare("test_ansi_len", ctypes.default_abi, ctypes.int32_t, ctypes.string); + let test_ansi_len = library.declare("test_ansi_len", ctypes.default_abi, ctypes.int32_t, ctypes.char.ptr); do_check_eq(test_ansi_len(""), 0); do_check_eq(test_ansi_len("hello world"), 11); // don't convert anything else to a string - var vals = [true, 0, 1/3, undefined, {}, {toString: function () { return "bad"; }}, []]; - for (var i = 0; i < vals.length; i++) + let vals = [true, 0, 1/3, undefined, {}, {toString: function () { return "bad"; }}, []]; + for (let i = 0; i < vals.length; i++) do_check_throws(function() { test_ansi_len(vals[i]); }, TypeError); - var test_wide_len = library.declare("test_wide_len", ctypes.default_abi, ctypes.int32_t, ctypes.ustring); + let test_wide_len = library.declare("test_wide_len", ctypes.default_abi, ctypes.int32_t, ctypes.jschar.ptr); do_check_eq(test_wide_len("hello world"), 11); - var test_ansi_ret = library.declare("test_ansi_ret", ctypes.default_abi, ctypes.string); - do_check_eq(test_ansi_ret(), "success"); + let test_ansi_ret = library.declare("test_ansi_ret", ctypes.default_abi, ctypes.char.ptr); + do_check_eq(test_ansi_ret().readString(), "success"); - var test_wide_ret = library.declare("test_wide_ret", ctypes.default_abi, ctypes.ustring); - do_check_eq(test_wide_ret(), "success"); + let test_wide_ret = library.declare("test_wide_ret", ctypes.default_abi, ctypes.jschar.ptr); + do_check_eq(test_wide_ret().readString(), "success"); - var test_ansi_echo = library.declare("test_ansi_echo", ctypes.default_abi, ctypes.string, ctypes.string); - do_check_eq(test_ansi_echo("anybody in there?"), "anybody in there?"); - do_check_eq(test_ansi_echo(null), null); + let test_ansi_echo = library.declare("test_ansi_echo", ctypes.default_abi, ctypes.char.ptr, ctypes.char.ptr); + // We cannot pass a string literal directly into test_ansi_echo, since the + // conversion to ctypes.char.ptr is only valid for the duration of the ffi + // call. The escaped pointer that's returned will point to freed memory. + let arg = ctypes.char.array()("anybody in there?"); + do_check_eq(test_ansi_echo(arg).readString(), "anybody in there?"); + do_check_eq(ptrValue(test_ansi_echo(null)), 0); } -function run_mixed_tests(library) { - var test_floor = library.declare("test_floor", ctypes.default_abi, ctypes.int32_t, ctypes.int32_t, ctypes.float); - do_check_eq(test_floor(5, 5.5), 10); - do_check_throws(function() { test_floor(5.5, 5); }, TypeError); +function run_struct_tests(library) { + const point_t = new ctypes.StructType("POINT", + [{ x: ctypes.int32_t }, + { y: ctypes.int32_t }]); + const rect_t = new ctypes.StructType("RECT", + [{ top : ctypes.int32_t }, + { left : ctypes.int32_t }, + { bottom: ctypes.int32_t }, + { right : ctypes.int32_t }]); + + let test_pt_in_rect = library.declare("test_pt_in_rect", ctypes.default_abi, ctypes.int32_t, rect_t, point_t); + let rect = new rect_t(10, 5, 5, 10); + let pt1 = new point_t(6, 6); + do_check_eq(test_pt_in_rect(rect, pt1), 1); + let pt2 = new point_t(2, 2); + do_check_eq(test_pt_in_rect(rect, pt2), 0); + + const inner_t = new ctypes.StructType("INNER", + [{ i1: ctypes.uint8_t }, + { i2: ctypes.int64_t }, + { i3: ctypes.uint8_t }]); + const nested_t = new ctypes.StructType("NESTED", + [{ n1 : ctypes.int32_t }, + { n2 : ctypes.int16_t }, + { inner: inner_t }, + { n3 : ctypes.int64_t }, + { n4 : ctypes.int32_t }]); + + let test_nested_struct = library.declare("test_nested_struct", ctypes.default_abi, ctypes.int32_t, nested_t); + let inner = new inner_t(161, 523412, 43); + let nested = new nested_t(13155, 1241, inner, 24512115, 1234111); + // add up all the numbers and make sure the C function agrees + do_check_eq(test_nested_struct(nested), 26284238); + +#ifdef XP_WIN + // Returning structs by value doesn't yet work on MSVC/Win32. + // Make sure it throws. + do_check_throws(function() { + library.declare("test_struct_return", ctypes.default_abi, point_t, rect_t); + }, Error); +#else + // test returning a struct by value + let test_struct_return = library.declare("test_struct_return", ctypes.default_abi, point_t, rect_t); + let ret = test_struct_return(rect); + do_check_eq(ret.x, rect.left); + do_check_eq(ret.y, rect.top); +#endif + + // test passing a struct by pointer + let test_init_pt = library.declare("test_init_pt", ctypes.default_abi, ctypes.void_t, point_t.ptr, ctypes.int32_t, ctypes.int32_t); + test_init_pt(pt1.address(), 9, 10); + do_check_eq(pt1.x, 9); + do_check_eq(pt1.y, 10); } // bug 522360 - try loading system library without full path function run_load_system_library() { #ifdef XP_WIN - var syslib = ctypes.open("user32.dll"); + let syslib = ctypes.open("user32.dll"); #elifdef XP_MACOSX - var syslib = ctypes.open("libm.dylib"); + let syslib = ctypes.open("libm.dylib"); #elifdef XP_UNIX - var syslib = ctypes.open("libm.so"); + let syslib = ctypes.open("libm.so"); #elifdef XP_OS2 - var syslib = ctypes.open("libc063.dll"); + let syslib = ctypes.open("libc063.dll"); #else do_throw("please add a system library for this test") #endif diff --git a/js/ctypes/typedefs.h b/js/ctypes/typedefs.h new file mode 100644 index 000000000000..de678988a914 --- /dev/null +++ b/js/ctypes/typedefs.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is js-ctypes. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation . + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dan Witte + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// If we're not breaking the types out, combine them together under one +// DEFINE_TYPE macro. Otherwise, turn off whichever ones we're not using. +#if defined(DEFINE_TYPE) +# define DEFINE_CHAR_TYPE(x, y, z) DEFINE_TYPE(x, y, z) +# define DEFINE_BOOL_TYPE(x, y, z) DEFINE_TYPE(x, y, z) +# define DEFINE_INT_TYPE(x, y, z) DEFINE_TYPE(x, y, z) +# define DEFINE_WRAPPED_INT_TYPE(x, y, z) DEFINE_TYPE(x, y, z) +# define DEFINE_FLOAT_TYPE(x, y, z) DEFINE_TYPE(x, y, z) +#else +# ifndef DEFINE_BOOL_TYPE +# define DEFINE_BOOL_TYPE(x, y, z) +# endif +# ifndef DEFINE_CHAR_TYPE +# define DEFINE_CHAR_TYPE(x, y, z) +# endif +# ifndef DEFINE_INT_TYPE +# define DEFINE_INT_TYPE(x, y, z) +# endif +# ifndef DEFINE_WRAPPED_INT_TYPE +# define DEFINE_WRAPPED_INT_TYPE(x, y, z) +# endif +# ifndef DEFINE_FLOAT_TYPE +# define DEFINE_FLOAT_TYPE(x, y, z) +# endif +#endif + +// MSVC doesn't have ssize_t. Help it along a little. +#ifndef _MSC_VER +#define CTYPES_SSIZE_T ssize_t +#else +#define CTYPES_SSIZE_T intptr_t +#endif + +// Some #defines to make handling of types whose length varies by platform +// easier. These could be implemented as configure tests, but the expressions +// are all statically resolvable so there's no need. +#define CTYPES_FFI_BOOL (sizeof(bool) == 1 ? ffi_type_uint8 : ffi_type_uint32) +#define CTYPES_FFI_LONG (sizeof(long) == 4 ? ffi_type_sint32 : ffi_type_sint64) +#define CTYPES_FFI_ULONG (sizeof(long) == 4 ? ffi_type_uint32 : ffi_type_uint64) +#define CTYPES_FFI_SIZE_T (sizeof(size_t) == 4 ? ffi_type_uint32 : ffi_type_uint64) +#define CTYPES_FFI_SSIZE_T (sizeof(size_t) == 4 ? ffi_type_sint32 : ffi_type_sint64) +#define CTYPES_FFI_INTPTR_T (sizeof(uintptr_t) == 4 ? ffi_type_sint32 : ffi_type_sint64) +#define CTYPES_FFI_UINTPTR_T (sizeof(uintptr_t) == 4 ? ffi_type_uint32 : ffi_type_uint64) + +/** + * Builtin types available for arguments and return values, representing + * their C counterparts. Format is: + * + * DEFINE_X_TYPE(typename, ctype, ffitype) + * + * where 'typename' is the name of the type constructor (accessible as + * ctypes.typename), 'ctype' is the corresponding C type declaration (from + * which sizeof(ctype) and templated type conversions will be derived), and + * 'ffitype' is the ffi_type to use. (Special types, such as 'void' and the + * pointer, array, and struct types are handled separately.) + */ +DEFINE_BOOL_TYPE (bool, bool, CTYPES_FFI_BOOL) +DEFINE_INT_TYPE (int8_t, PRInt8, ffi_type_sint8) +DEFINE_INT_TYPE (int16_t, PRInt16, ffi_type_sint16) +DEFINE_INT_TYPE (int32_t, PRInt32, ffi_type_sint32) +DEFINE_INT_TYPE (uint8_t, PRUint8, ffi_type_uint8) +DEFINE_INT_TYPE (uint16_t, PRUint16, ffi_type_uint16) +DEFINE_INT_TYPE (uint32_t, PRUint32, ffi_type_uint32) +DEFINE_INT_TYPE (short, short, ffi_type_sint16) +DEFINE_INT_TYPE (unsigned_short, unsigned short, ffi_type_uint16) +DEFINE_INT_TYPE (int, int, ffi_type_sint32) +DEFINE_INT_TYPE (unsigned_int, unsigned int, ffi_type_uint32) +DEFINE_INT_TYPE (unsigned, unsigned, ffi_type_uint32) +DEFINE_WRAPPED_INT_TYPE(int64_t, PRInt64, ffi_type_sint64) +DEFINE_WRAPPED_INT_TYPE(uint64_t, PRUint64, ffi_type_uint64) +DEFINE_WRAPPED_INT_TYPE(long, long, CTYPES_FFI_LONG) +DEFINE_WRAPPED_INT_TYPE(unsigned_long, unsigned long, CTYPES_FFI_ULONG) +DEFINE_WRAPPED_INT_TYPE(long_long, long long, ffi_type_sint64) +DEFINE_WRAPPED_INT_TYPE(unsigned_long_long, unsigned long long, ffi_type_uint64) +DEFINE_WRAPPED_INT_TYPE(size_t, size_t, CTYPES_FFI_SIZE_T) +DEFINE_WRAPPED_INT_TYPE(ssize_t, CTYPES_SSIZE_T, CTYPES_FFI_SSIZE_T) +DEFINE_WRAPPED_INT_TYPE(intptr_t, intptr_t, CTYPES_FFI_INTPTR_T) +DEFINE_WRAPPED_INT_TYPE(uintptr_t, uintptr_t, CTYPES_FFI_UINTPTR_T) +DEFINE_FLOAT_TYPE (float32_t, float, ffi_type_float) +DEFINE_FLOAT_TYPE (float64_t, double, ffi_type_double) +DEFINE_FLOAT_TYPE (float, float, ffi_type_float) +DEFINE_FLOAT_TYPE (double, double, ffi_type_double) +DEFINE_CHAR_TYPE (char, char, ffi_type_uint8) +DEFINE_CHAR_TYPE (signed_char, signed char, ffi_type_sint8) +DEFINE_CHAR_TYPE (unsigned_char, unsigned char, ffi_type_uint8) +DEFINE_CHAR_TYPE (jschar, jschar, ffi_type_uint16) + +#undef CTYPES_SSIZE_T +#undef CTYPES_FFI_BOOL +#undef CTYPES_FFI_LONG +#undef CTYPES_FFI_ULONG +#undef CTYPES_FFI_SIZE_T +#undef CTYPES_FFI_SSIZE_T +#undef CTYPES_FFI_INTPTR_T +#undef CTYPES_FFI_UINTPTR_T + +#undef DEFINE_TYPE +#undef DEFINE_CHAR_TYPE +#undef DEFINE_BOOL_TYPE +#undef DEFINE_INT_TYPE +#undef DEFINE_WRAPPED_INT_TYPE +#undef DEFINE_FLOAT_TYPE + diff --git a/js/ctypes/types.h b/js/ctypes/types.h deleted file mode 100644 index 10bdc5043a56..000000000000 --- a/js/ctypes/types.h +++ /dev/null @@ -1,72 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is js-ctypes. - * - * The Initial Developer of the Original Code is - * The Mozilla Foundation . - * Portions created by the Initial Developer are Copyright (C) 2009 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dan Witte - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/** - * This file defines the constants available on the ctypes.types object (e.g. - * ctypes.types.VOID). They do not have any interesting properties; they simply - * exist as unique identifiers for the type they represent. - */ - -/** - * ABI constants that specify the calling convention to use. - * DEFAULT corresponds to the cdecl convention, and in almost all - * cases is the correct choice. STDCALL is provided for calling - * functions in the Microsoft Win32 API. - */ -DEFINE_ABI(default_abi) // corresponds to cdecl -DEFINE_ABI(stdcall_abi) // for calling Win32 API functions - -/** - * Types available for arguments and return values, representing - * their C counterparts. - */ -DEFINE_TYPE(void_t) // Only allowed for return types. -DEFINE_TYPE(bool) // _Bool type (assumed 8 bits wide). -DEFINE_TYPE(int8_t) // int8_t (signed char) type. -DEFINE_TYPE(int16_t) // int16_t (short) type. -DEFINE_TYPE(int32_t) // int32_t (int) type. -DEFINE_TYPE(int64_t) // int64_t (long long) type. -DEFINE_TYPE(uint8_t) // uint8_t (unsigned char) type. -DEFINE_TYPE(uint16_t) // uint16_t (unsigned short) type. -DEFINE_TYPE(uint32_t) // uint32_t (unsigned int) type. -DEFINE_TYPE(uint64_t) // uint64_t (unsigned long long) type. -DEFINE_TYPE(float) // float type. -DEFINE_TYPE(double) // double type. -DEFINE_TYPE(string) // C string (char *). -DEFINE_TYPE(ustring) // 16-bit string (char16_t *). -