/* -*- 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): * Mark Finkle , * Fredrik Larsson * 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 "Function.h" #include "Library.h" #include "nsAutoPtr.h" #include "jscntxt.h" namespace mozilla { 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); // determine the ABI from the subset of those available on the // given platform. TYPE_DEFAULT specifies the default // C calling convention (cdecl) on each platform. switch (abi) { case ABI_default_abi: aResult = FFI_DEFAULT_ABI; return true; #if defined(_WIN32) && !defined(_WIN64) case ABI_stdcall_abi: aResult = FFI_STDCALL; return true; #endif default: 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"); return false; } return true; } static bool PrepareValue(JSContext* aContext, const Type& aType, jsval aValue, Value& 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"); return false; } return true; } 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"); return false; } return true; } /******************************************************************************* ** Function implementation *******************************************************************************/ Function::Function() : mNext(NULL) { } Function::~Function() { } bool Function::Init(JSContext* aContext, PRFuncPtr aFunc, jsval aCallType, jsval aResultType, jsval* aArgTypes, uintN aArgLength) { mFunc = aFunc; // determine the ABI if (!GetABI(aContext, aCallType, mCallType)) { JS_ReportError(aContext, "Invalid ABI specification"); return false; } // prepare the result type if (!PrepareType(aContext, aResultType, mResultType)) return false; // prepare the argument types mArgTypes.SetCapacity(aArgLength); for (PRUint32 i = 0; i < aArgLength; ++i) { 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); } ffi_status status = ffi_prep_cif(&mCIF, mCallType, mFFITypes.Length(), &mResultType.mFFIType, mFFITypes.Elements()); switch (status) { case FFI_OK: return true; case FFI_BAD_ABI: JS_ReportError(aContext, "Invalid ABI specification"); return false; case FFI_BAD_TYPEDEF: JS_ReportError(aContext, "Invalid type specification"); return false; default: JS_ReportError(aContext, "Unknown libffi error"); return false; } } bool Function::Execute(JSContext* cx, PRUint32 argc, jsval* vp) { if (argc != mArgTypes.Length()) { JS_ReportError(cx, "Number of arguments does not match declaration"); return false; } // prepare the values for each argument nsAutoTArray values; for (PRUint32 i = 0; i < mArgTypes.Length(); ++i) { if (!PrepareValue(cx, mArgTypes[i], JS_ARGV(cx, vp)[i], *values.AppendElement())) 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); } // initialize a pointer to an appropriate location, for storing the result Value resultValue; PrepareReturnValue(mResultType, resultValue); // 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()); JS_ResumeRequest(cx, rc); // prepare a JS object from the result jsval rval; if (!ConvertReturnValue(cx, mResultType, resultValue, &rval)) return false; JS_SET_RVAL(cx, vp, rval); return true; } /******************************************************************************* ** JSObject implementation *******************************************************************************/ JSObject* Function::Create(JSContext* aContext, JSObject* aLibrary, PRFuncPtr aFunc, const char* aName, jsval aCallType, jsval aResultType, jsval* aArgTypes, uintN aArgLength) { // create new Function instance nsAutoPtr self(new Function()); if (!self) return NULL; // deduce and check the ABI and parameter types if (!self->Init(aContext, aFunc, aCallType, aResultType, aArgTypes, aArgLength)) return NULL; // create and root the new JS function object JSFunction* fn = JS_NewFunction(aContext, JSNative(Function::Call), aArgLength, JSFUN_FAST_NATIVE, NULL, aName); if (!fn) return NULL; JSObject* fnObj = JS_GetFunctionObject(fn); 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()))) return NULL; // make a strong reference to the library for GC-safety if (!JS_SetReservedSlot(aContext, fnObj, 1, 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.) if (!Library::AddFunction(aContext, aLibrary, self)) return NULL; self.forget(); return fnObj; } static Function* GetFunction(JSContext* cx, JSObject* obj) { jsval slot; JS_GetReservedSlot(cx, obj, 0, &slot); return static_cast(JSVAL_TO_PRIVATE(slot)); } JSBool 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); PRLibrary* library = Library::GetLibrary(cx, JSVAL_TO_OBJECT(slot)); if (!library) { JS_ReportError(cx, "library is not open"); return JS_FALSE; } return GetFunction(cx, callee)->Execute(cx, argc, vp); } } }