diff options
-rw-r--r-- | src/embind/embind.js | 301 | ||||
-rw-r--r-- | src/embind/emval.js | 16 | ||||
-rw-r--r-- | system/include/emscripten/bind.h | 158 | ||||
-rw-r--r-- | system/include/emscripten/val.h | 14 | ||||
-rw-r--r-- | tests/embind/embind.test.js | 414 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 277 |
6 files changed, 1051 insertions, 129 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js index 4821c77b..124ea569 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -4,10 +4,12 @@ /*global readLatin1String*/ /*global __emval_register, _emval_handle_array, __emval_decref*/ /*global ___getTypeName*/ +/*global requireHandle*/ /*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */ var InternalError = Module['InternalError'] = extendError(Error, 'InternalError'); var BindingError = Module['BindingError'] = extendError(Error, 'BindingError'); var UnboundTypeError = Module['UnboundTypeError'] = extendError(BindingError, 'UnboundTypeError'); +var PureVirtualError = Module['PureVirtualError'] = extendError(BindingError, 'PureVirtualError'); function throwInternalError(message) { throw new InternalError(message); @@ -151,6 +153,59 @@ function _embind_repr(v) { } } +// raw pointer -> instance +var registeredInstances = {}; + +function getBasestPointer(class_, ptr) { + if (ptr === undefined) { + throwBindingError('ptr should not be undefined'); + } + while (class_.baseClass) { + ptr = class_.upcast(ptr); + class_ = class_.baseClass; + } + return ptr; +} + +function registerInheritedInstance(class_, ptr, instance) { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + throwBindingError('Tried to register registered instance: ' + ptr); + } else { + registeredInstances[ptr] = instance; + } +} + +function unregisterInheritedInstance(class_, ptr) { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + delete registeredInstances[ptr]; + } else { + throwBindingError('Tried to unregister unregistered instance: ' + ptr); + } +} + +function getInheritedInstance(class_, ptr) { + ptr = getBasestPointer(class_, ptr); + return registeredInstances[ptr]; +} + +function getInheritedInstanceCount() { + return Object.keys(registeredInstances).length; +} +Module['getInheritedInstanceCount'] = getInheritedInstanceCount; + +function getLiveInheritedInstances() { + var rv = []; + for (var k in registeredInstances) { + if (registeredInstances.hasOwnProperty(k)) { + rv.push(registeredInstances[k]); + } + } + return rv; +} +Module['getLiveInheritedInstances'] = getLiveInheritedInstances; + // typeID -> { toWireType: ..., fromWireType: ... } var registeredTypes = {}; @@ -535,6 +590,9 @@ function __embind_register_emval(rawType, name) { 'argPackAdvance': 8, 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: null, // This type does not need a destructor + + // TODO: do we need a deleteObject here? write a test where + // emval is passed into JS via an interface }); } @@ -630,7 +688,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp var argsList = ""; var argsListWired = ""; - for(var i = 0; i < argCount-2; ++i) { + for(var i = 0; i < argCount - 2; ++i) { argsList += (i!==0?", ":"")+"arg"+i; argsListWired += (i!==0?", ":"")+"arg"+i+"Wired"; } @@ -665,7 +723,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += "var thisWired = classParam.toWireType("+dtorStack+", this);\n"; } - for(var i = 0; i < argCount-2; ++i) { + for(var i = 0; i < argCount - 2; ++i) { invokerFnBody += "var arg"+i+"Wired = argType"+i+".toWireType("+dtorStack+", arg"+i+"); // "+argTypes[i+2].name+"\n"; args1.push("argType"+i); args2.push(argTypes[i+2]); @@ -684,7 +742,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += "runDestructors(destructors);\n"; } else { for(var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. - var paramName = (i === 1 ? "thisWired" : ("arg"+(i-2)+"Wired")); + var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired")); if (argTypes[i].destructorFunction !== null) { invokerFnBody += paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n"; args1.push(paramName+"_dtor"); @@ -1124,14 +1182,14 @@ function RegisteredPointer( } } -RegisteredPointer.prototype.getPointee = function(ptr) { +RegisteredPointer.prototype.getPointee = function getPointee(ptr) { if (this.rawGetPointee) { ptr = this.rawGetPointee(ptr); } return ptr; }; -RegisteredPointer.prototype.destructor = function(ptr) { +RegisteredPointer.prototype.destructor = function destructor(ptr) { if (this.rawDestructor) { this.rawDestructor(ptr); } @@ -1140,7 +1198,13 @@ RegisteredPointer.prototype.destructor = function(ptr) { RegisteredPointer.prototype['argPackAdvance'] = 8; RegisteredPointer.prototype['readValueFromPointer'] = simpleReadValueFromPointer; -RegisteredPointer.prototype['fromWireType'] = function(ptr) { +RegisteredPointer.prototype['deleteObject'] = function deleteObject(handle) { + if (handle !== null) { + handle['delete'](); + } +}; + +RegisteredPointer.prototype['fromWireType'] = function fromWireType(ptr) { // ptr is a raw pointer (or a raw smartpointer) // rawPointer is a maybe-null raw pointer @@ -1150,6 +1214,13 @@ RegisteredPointer.prototype['fromWireType'] = function(ptr) { return null; } + var registeredInstance = getInheritedInstance(this.registeredClass, rawPointer); + if (undefined !== registeredInstance) { + var rv = registeredInstance['clone'](); + this.destructor(ptr); + return rv; + } + function makeDefaultHandle() { if (this.isSmartPointer) { return makeClassHandle(this.registeredClass.instancePrototype, { @@ -1225,7 +1296,7 @@ function getInstanceTypeName(handle) { return handle.$$.ptrType.registeredClass.name; } -ClassHandle.prototype['isAliasOf'] = function(other) { +ClassHandle.prototype['isAliasOf'] = function isAliasOf(other) { if (!(this instanceof ClassHandle)) { return false; } @@ -1255,19 +1326,24 @@ function throwInstanceAlreadyDeleted(obj) { throwBindingError(getInstanceTypeName(obj) + ' instance already deleted'); } -ClassHandle.prototype['clone'] = function() { +ClassHandle.prototype['clone'] = function clone() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - var clone = Object.create(Object.getPrototypeOf(this), { - $$: { - value: shallowCopy(this.$$), - } - }); + if (this.$$.preservePointerOnDelete) { + this.$$.count.value += 1; + return this; + } else { + var clone = Object.create(Object.getPrototypeOf(this), { + $$: { + value: shallowCopy(this.$$), + } + }); - clone.$$.count.value += 1; - return clone; + clone.$$.count.value += 1; + return clone; + } }; function runDestructor(handle) { @@ -1283,16 +1359,20 @@ ClassHandle.prototype['delete'] = function ClassHandle_delete() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - if (this.$$.deleteScheduled) { + + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { throwBindingError('Object already scheduled for deletion'); } this.$$.count.value -= 1; - if (0 === this.$$.count.value) { + var toDelete = 0 === this.$$.count.value; + if (toDelete) { runDestructor(this); } - this.$$.smartPtr = undefined; - this.$$.ptr = undefined; + if (!this.$$.preservePointerOnDelete) { + this.$$.smartPtr = undefined; + this.$$.ptr = undefined; + } }; var deletionQueue = []; @@ -1305,7 +1385,7 @@ ClassHandle.prototype['deleteLater'] = function deleteLater() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - if (this.$$.deleteScheduled) { + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { throwBindingError('Object already scheduled for deletion'); } deletionQueue.push(this); @@ -1351,12 +1431,15 @@ function RegisteredClass( this.getActualType = getActualType; this.upcast = upcast; this.downcast = downcast; + this.pureVirtualFunctions = []; } function shallowCopy(o) { var rv = {}; for (var k in o) { - rv[k] = o[k]; + if (Object.prototype.hasOwnProperty.call(o, k)) { + rv[k] = o[k]; + } } return rv; } @@ -1491,12 +1574,12 @@ function __embind_register_class_constructor( if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) { throw new BindingError("Cannot register multiple constructors with identical number of parameters (" + (argCount-1) + ") for class '" + classType.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!"); } - classType.registeredClass.constructor_body[argCount - 1] = function() { + classType.registeredClass.constructor_body[argCount - 1] = function unboundTypeHandler() { throwUnboundTypeError('Cannot construct ' + classType.name + ' due to unbound types', rawArgTypes); }; whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - classType.registeredClass.constructor_body[argCount - 1] = function() { + classType.registeredClass.constructor_body[argCount - 1] = function constructor_body() { if (arguments.length !== argCount - 1) { throwBindingError(humanName + ' called with ' + arguments.length + ' arguments, expected ' + (argCount-1)); } @@ -1569,7 +1652,8 @@ function __embind_register_class_function( rawArgTypesAddr, // [ReturnType, ThisType, Args...] invokerSignature, rawInvoker, - context + context, + isPureVirtual ) { var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); methodName = readLatin1String(methodName); @@ -1579,21 +1663,25 @@ function __embind_register_class_function( classType = classType[0]; var humanName = classType.name + '.' + methodName; - var unboundTypesHandler = function() { + if (isPureVirtual) { + classType.registeredClass.pureVirtualFunctions.push(methodName); + } + + function unboundTypesHandler() { throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; + } var proto = classType.registeredClass.instancePrototype; var method = proto[methodName]; - if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount-2)) { + if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount - 2)) { // This is the first overload to be registered, OR we are replacing a function in the base class with a function in the derived class. - unboundTypesHandler.argCount = argCount-2; + unboundTypesHandler.argCount = argCount - 2; unboundTypesHandler.className = classType.name; proto[methodName] = unboundTypesHandler; } else { // There was an existing function with the same name registered. Set up a function overload routing table. ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-2] = unboundTypesHandler; + proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler; } whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { @@ -1605,7 +1693,7 @@ function __embind_register_class_function( if (undefined === proto[methodName].overloadTable) { proto[methodName] = memberFunction; } else { - proto[methodName].overloadTable[argCount-2] = memberFunction; + proto[methodName].overloadTable[argCount - 2] = memberFunction; } return []; @@ -1614,53 +1702,6 @@ function __embind_register_class_function( }); } -function __embind_register_class_class_function( - rawClassType, - methodName, - argCount, - rawArgTypesAddr, - invokerSignature, - rawInvoker, - fn -) { - var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = readLatin1String(methodName); - rawInvoker = requireFunction(invokerSignature, rawInvoker); - whenDependentTypesAreResolved([], [rawClassType], function(classType) { - classType = classType[0]; - var humanName = classType.name + '.' + methodName; - - var unboundTypesHandler = function() { - throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; - - var proto = classType.registeredClass.constructor; - if (undefined === proto[methodName]) { - // This is the first function to be registered with this name. - unboundTypesHandler.argCount = argCount-1; - proto[methodName] = unboundTypesHandler; - } else { - // There was an existing function with the same name registered. Set up a function overload routing table. - ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; - } - - whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, - // the function handlers go into an overload table. - var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); - var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); - if (undefined === proto[methodName].overloadTable) { - proto[methodName] = func; - } else { - proto[methodName].overloadTable[argCount-1] = func; - } - return []; - }); - return []; - }); -} - function __embind_register_class_property( classType, fieldName, @@ -1730,6 +1771,112 @@ function __embind_register_class_property( }); } +function __embind_register_class_class_function( + rawClassType, + methodName, + argCount, + rawArgTypesAddr, + invokerSignature, + rawInvoker, + fn +) { + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = readLatin1String(methodName); + rawInvoker = requireFunction(invokerSignature, rawInvoker); + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + var humanName = classType.name + '.' + methodName; + + function unboundTypesHandler() { + throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); + } + + var proto = classType.registeredClass.constructor; + if (undefined === proto[methodName]) { + // This is the first function to be registered with this name. + unboundTypesHandler.argCount = argCount-1; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; + } + + whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { + // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, + // the function handlers go into an overload table. + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); + if (undefined === proto[methodName].overloadTable) { + proto[methodName] = func; + } else { + proto[methodName].overloadTable[argCount-1] = func; + } + return []; + }); + return []; + }); +} + +function __embind_create_inheriting_constructor(constructorName, wrapperType, properties) { + constructorName = readLatin1String(constructorName); + wrapperType = requireRegisteredType(wrapperType, 'wrapper'); + properties = requireHandle(properties); + + var arraySlice = [].slice; + + var registeredClass = wrapperType.registeredClass; + var wrapperPrototype = registeredClass.instancePrototype; + var baseClass = registeredClass.baseClass; + var baseClassPrototype = baseClass.instancePrototype; + var baseConstructor = registeredClass.baseClass.constructor; + var ctor = createNamedFunction(constructorName, function() { + registeredClass.baseClass.pureVirtualFunctions.forEach(function(name) { + if (this[name] === baseClassPrototype[name]) { + throw new PureVirtualError('Pure virtual function ' + name + ' must be implemented in JavaScript'); + } + }.bind(this)); + + Object.defineProperty(this, '__parent', { + value: wrapperPrototype + }); + this.__construct.apply(this, arraySlice.call(arguments)); + }); + + // It's a little nasty that we're modifying the wrapper prototype here. + + wrapperPrototype.__construct = function __construct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __construct"); + } + + var inner = baseConstructor.implement.apply( + undefined, + [this].concat(arraySlice.call(arguments))); + var $$ = inner.$$; + inner.notifyOnDestruction(); + $$.preservePointerOnDelete = true; + Object.defineProperty(this, '$$', { + value: $$ + }); + registerInheritedInstance(registeredClass, $$.ptr, this); + }; + + wrapperPrototype.__destruct = function __destruct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __destruct"); + } + + unregisterInheritedInstance(registeredClass, this.$$.ptr); + }; + + ctor.prototype = Object.create(wrapperPrototype); + for (var p in properties) { + ctor.prototype[p] = properties[p]; + } + return __emval_register(ctor); +} + var char_0 = '0'.charCodeAt(0); var char_9 = '9'.charCodeAt(0); function makeLegalFunctionName(name) { diff --git a/src/embind/emval.js b/src/embind/emval.js index ebec3881..1661bc02 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -265,7 +265,14 @@ function __emval_get_method_caller(argCount, argTypes) { " args += argType" + i + ".argPackAdvance;\n"; } functionBody += - " var rv = handle[name](" + argsList + ");\n" + + " var rv = handle[name](" + argsList + ");\n"; + for (var i = 0; i < argCount - 1; ++i) { + if (types[i + 1]['deleteObject']) { + functionBody += + " argType" + i + ".deleteObject(arg" + i + ");\n"; + } + } + functionBody += " return retType.toWireType(destructors, rv);\n" + "};\n"; @@ -281,10 +288,13 @@ function __emval_call_method(caller, handle, methodName, destructorsRef, args) { return caller(handle, methodName, allocateDestructors(destructorsRef), args); } -function __emval_has_function(handle, name) { +function __emval_has_function(handle, name, classType) { handle = requireHandle(handle); name = getStringOrSymbol(name); - return handle[name] instanceof Function; + classType = requireRegisteredType(classType, 'class wrapper filter'); + + var filter = classType.registeredClass.instancePrototype[name]; + return (handle[name] instanceof Function) && (filter === undefined || handle[name] !== filter); } function __emval_typeof(handle) { diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index eede0755..7bc28ff6 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -149,7 +149,8 @@ namespace emscripten { TYPEID argTypes[], const char* invokerSignature, GenericFunction invoker, - void* context); + void* context, + unsigned isPureVirtual); void _embind_register_class_property( TYPEID classType, @@ -172,6 +173,11 @@ namespace emscripten { GenericFunction invoker, GenericFunction method); + EM_VAL _embind_create_inheriting_constructor( + const char* constructorName, + TYPEID wrapperType, + EM_VAL properties); + void _embind_register_enum( TYPEID enumType, const char* name, @@ -276,6 +282,33 @@ namespace emscripten { return method; } + namespace internal { + // this should be in <type_traits>, but alas, it's not + template<typename T> struct remove_class; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...)> { using type = R(A...); }; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...) const> { using type = R(A...); }; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); }; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); }; + + template<typename LambdaType> + struct CalculateLambdaSignature { + using type = typename std::add_pointer< + typename remove_class< + decltype(&LambdaType::operator()) + >::type + >::type; + }; + } + + template<typename LambdaType> + typename internal::CalculateLambdaSignature<LambdaType>::type optional_override(const LambdaType& fp) { + return fp; + } + //////////////////////////////////////////////////////////////////////////////// // Invoker //////////////////////////////////////////////////////////////////////////////// @@ -873,40 +906,55 @@ namespace emscripten { }; }; + //////////////////////////////////////////////////////////////////////////////// // CLASSES //////////////////////////////////////////////////////////////////////////////// + namespace internal { + class WrapperBase { + public: + void setNotifyJSOnDestruction(bool notify) { + notifyJSOnDestruction = notify; + } + + protected: + bool notifyJSOnDestruction = false; + }; + } + // abstract classes template<typename T> - class wrapper : public T { + class wrapper : public T, public internal::WrapperBase { public: typedef T class_type; - explicit wrapper(val&& wrapped) - : wrapped(std::forward<val>(wrapped)) + template<typename... Args> + explicit wrapper(val&& wrapped, Args&&... args) + : T(std::forward<Args>(args)...) + , wrapped(std::forward<val>(wrapped)) {} + ~wrapper() { + if (notifyJSOnDestruction) { + call<void>("__destruct"); + } + } + template<typename ReturnType, typename... Args> ReturnType call(const char* name, Args&&... args) const { return wrapped.call<ReturnType>(name, std::forward<Args>(args)...); } - template<typename ReturnType, typename... Args, typename Default> - ReturnType optional_call(const char* name, Default def, Args&&... args) const { - if (wrapped.has_function(name)) { - return call<ReturnType>(name, std::forward<Args>(args)...); - } else { - return def(); - } - } - private: val wrapped; }; -#define EMSCRIPTEN_WRAPPER(T) \ - T(::emscripten::val&& v): wrapper(std::forward<::emscripten::val>(v)) {} +#define EMSCRIPTEN_WRAPPER(T) \ + template<typename... Args> \ + T(::emscripten::val&& v, Args&&... args) \ + : wrapper(std::forward<::emscripten::val>(v), std::forward<Args>(args)...) \ + {} namespace internal { struct NoBaseClass { @@ -987,6 +1035,45 @@ namespace emscripten { SmartPtrIfNeeded(U&, const char*) { } }; + + template<typename WrapperType> + val wrapped_extend(const std::string& name, const val& properties) { + return val::take_ownership(_embind_create_inheriting_constructor( + name.c_str(), + TypeID<WrapperType>::get(), + properties.__get_handle())); + } + }; + + struct pure_virtual { + template<typename InputType, int Index> + struct Transform { + typedef InputType type; + }; + }; + + namespace internal { + template<typename... Policies> + struct isPureVirtual; + + template<typename... Rest> + struct isPureVirtual<pure_virtual, Rest...> { + static constexpr bool value = true; + }; + + template<typename T, typename... Rest> + struct isPureVirtual<T, Rest...> { + static constexpr bool value = isPureVirtual<Rest...>::value; + }; + + template<> + struct isPureVirtual<> { + static constexpr bool value = false; + }; + } + + template<typename... ConstructorArgs> + struct constructor { }; template<typename ClassType, typename BaseSpecifier = internal::NoBaseClass> @@ -1095,18 +1182,38 @@ namespace emscripten { return *this; } - template<typename WrapperType, typename PointerType = WrapperType*> - const class_& allow_subclass(const char* wrapperClassName, const char* pointerName = "<UnknownPointerName>") const { + template<typename WrapperType, typename PointerType = WrapperType*, typename... ConstructorArgs> + const class_& allow_subclass( + const char* wrapperClassName, + const char* pointerName = "<UnknownPointerName>", + ::emscripten::constructor<ConstructorArgs...> = ::emscripten::constructor<ConstructorArgs...>() + ) const { using namespace internal; auto cls = class_<WrapperType, base<ClassType>>(wrapperClassName) + .function("notifyOnDestruction", select_overload<void(WrapperType&)>([](WrapperType& wrapper) { + wrapper.setNotifyJSOnDestruction(true); + })) ; SmartPtrIfNeeded<PointerType> _(cls, pointerName); - return class_function( - "implement", - &wrapped_new<PointerType, WrapperType, val>, - allow_raw_pointer<ret_val>()); + return + class_function( + "implement", + &wrapped_new<PointerType, WrapperType, val, ConstructorArgs...>, + allow_raw_pointer<ret_val>()) + .class_function( + "extend", + &wrapped_extend<WrapperType>) + ; + } + + template<typename WrapperType, typename... ConstructorArgs> + const class_& allow_subclass( + const char* wrapperClassName, + ::emscripten::constructor<ConstructorArgs...> constructor + ) const { + return allow_subclass<WrapperType, WrapperType*>(wrapperClassName, "<UnknownPointerName>", constructor); } template<typename ReturnType, typename... Args, typename... Policies> @@ -1123,7 +1230,8 @@ namespace emscripten { args.types, getSignature(invoker), reinterpret_cast<GenericFunction>(invoker), - getContext(memberFunction)); + getContext(memberFunction), + isPureVirtual<Policies...>::value); return *this; } @@ -1141,7 +1249,8 @@ namespace emscripten { args.types, getSignature(invoker), reinterpret_cast<GenericFunction>(invoker), - getContext(memberFunction)); + getContext(memberFunction), + isPureVirtual<Policies...>::value); return *this; } @@ -1158,7 +1267,8 @@ namespace emscripten { args.types, getSignature(invoke), reinterpret_cast<GenericFunction>(invoke), - getContext(function)); + getContext(function), + false); return *this; } diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index bfd8610a..31f5923e 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -61,7 +61,8 @@ namespace emscripten { EM_VAR_ARGS argv); bool _emval_has_function( EM_VAL value, - const char* methodName); + const char* methodName, + internal::TYPEID filter); EM_VAL _emval_typeof(EM_VAL value); } @@ -392,8 +393,10 @@ namespace emscripten { return MethodCaller<ReturnValue, Args...>::call(handle, name, std::forward<Args>(args)...); } - bool has_function(const char* name) const { - return _emval_has_function(handle, name); + template<typename ClassType> + bool has_implementation_defined_function(const char* name) const { + using namespace internal; + return _emval_has_function(handle, name, TypeID<ClassType>::get()); } template<typename T> @@ -411,6 +414,11 @@ namespace emscripten { return fromGenericWireType<T>(result); } + // private: TODO: use a friend? + internal::EM_VAL __get_handle() const { + return handle; + } + val typeof() const { return val(_emval_typeof(handle)); } diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 53d3988a..a6b2e98c 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -1548,7 +1548,7 @@ module({ }); }); - BaseFixture.extend("abstract methods", function() { + BaseFixture.extend("implementing abstract methods with JS objects", function() { test("can call abstract methods", function() { var obj = cm.getAbstractClass(); assert.equal("from concrete", obj.abstractMethod()); @@ -1580,7 +1580,8 @@ module({ }; var impl = cm.AbstractClass.implement(new MyImplementation); - assert.equal(expected, impl.optionalMethod(expected)); + // TODO: remove .implement() as a public API. It interacts poorly with Class.extend. + //assert.equal(expected, impl.optionalMethod(expected)); assert.equal(expected, cm.callOptionalMethod(impl, expected)); impl.delete(); }); @@ -1588,7 +1589,8 @@ module({ test("if not implemented then optional method runs default", function() { var impl = cm.AbstractClass.implement({}); assert.equal("optionalfoo", impl.optionalMethod("foo")); - assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); + // TODO: remove .implement() as a public API. It interacts poorly with Class.extend. + //assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); impl.delete(); }); @@ -1638,6 +1640,366 @@ module({ impl.delete(); }); + + test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { + var derived = cm.embind_test_return_smart_derived_ptr(); + var impl = cm.AbstractClass.implement({ + returnsSharedPtr: function() { + return derived; + } + }); + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + derived.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + }); + + BaseFixture.extend("constructor prototype class inheritance", function() { + var Empty = cm.AbstractClass.extend("Empty", { + abstractMethod: function() { + } + }); + + test("can extend, construct, and delete", function() { + var instance = new Empty; + instance.delete(); + }); + + test("properties set in constructor are externally visible", function() { + var HasProperty = cm.AbstractClass.extend("HasProperty", { + __construct: function(x) { + this.__parent.__construct.call(this); + this.property = x; + }, + abstractMethod: function() { + } + }); + var instance = new HasProperty(10); + assert.equal(10, instance.property); + instance.delete(); + }); + + test("pass derived object to c++", function() { + var Implementation = cm.AbstractClass.extend("Implementation", { + abstractMethod: function() { + return "abc"; + }, + }); + var instance = new Implementation; + var result = cm.callAbstractMethod(instance); + instance.delete(); + assert.equal("abc", result); + }); + + test("properties set in constructor are visible in overridden methods", function() { + var HasProperty = cm.AbstractClass.extend("HasProperty", { + __construct: function(x) { + this.__parent.__construct.call(this); + this.x = x; + }, + abstractMethod: function() { + return this.x; + }, + }); + var instance = new HasProperty("xyz"); + var result = cm.callAbstractMethod(instance); + instance.delete(); + assert.equal("xyz", result); + }); + + test("interface methods are externally visible", function() { + var instance = new Empty; + var result = instance.concreteMethod(); + instance.delete(); + assert.equal("concrete", result); + }); + + test("optional methods are externally visible", function() { + var instance = new Empty; + var result = instance.optionalMethod("_123"); + instance.delete(); + assert.equal("optional_123", result); + }); + + test("optional methods: not defined", function() { + var instance = new Empty; + var result = cm.callOptionalMethod(instance, "_123"); + instance.delete(); + assert.equal("optional_123", result); + }); + + // Calling C++ implementations of optional functions can be + // made to work, but requires an interface change on the C++ + // side, using a technique similar to the one described at + // https://wiki.python.org/moin/boost.python/OverridableVirtualFunctions + // + // The issue is that, in a standard binding, calling + // parent.prototype.optionalMethod invokes the wrapper + // function, which checks that the JS object implements + // 'optionalMethod', which it does. Thus, C++ calls back into + // JS, resulting in an infinite loop. + // + // The solution, for optional methods, is to bind a special + // concrete implementation that specifically calls the base + // class's implementation. See the binding of + // AbstractClass::optionalMethod in embind_test.cpp. + + test("can call parent implementation from within derived implementation", function() { + var parent = cm.AbstractClass; + var ExtendsOptionalMethod = parent.extend("ExtendsOptionalMethod", { + abstractMethod: function() { + }, + optionalMethod: function(s) { + return "optionaljs_" + parent.prototype.optionalMethod.call(this, s); + }, + }); + var instance = new ExtendsOptionalMethod; + var result = cm.callOptionalMethod(instance, "_123"); + instance.delete(); + assert.equal("optionaljs_optional_123", result); + }); + + // TODO: deriving from classes with constructors? + + test("instanceof", function() { + var instance = new Empty; + assert.instanceof(instance, Empty); + assert.instanceof(instance, cm.AbstractClass); + instance.delete(); + }); + + test("returning null shared pointer from interfaces implemented in JS code does not leak", function() { + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return null; + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("returning a new shared pointer from interfaces implemented in JS code does not leak", function() { + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return cm.embind_test_return_smart_derived_ptr().deleteLater(); + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("void methods work", function() { + var saved = {}; + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + differentArguments: function(i, d, f, q, s) { + saved.i = i; + saved.d = d; + saved.f = f; + saved.q = q; + saved.s = s; + } + }); + var impl = new C; + + cm.callDifferentArguments(impl, 1, 2, 3, 4, "foo"); + + assert.deepEqual(saved, { + i: 1, + d: 2, + f: 3, + q: 4, + s: "foo", + }); + + impl.delete(); + }); + + test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { + var derived = cm.embind_test_return_smart_derived_ptr(); + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return derived; + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + derived.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("calling pure virtual function gives good error message", function() { + var C = cm.AbstractClass.extend("C", {}); + var error = assert.throws(cm.PureVirtualError, function() { + new C; + }); + assert.equal('Pure virtual function abstractMethod must be implemented in JavaScript', error.message); + }); + + test("can extend from C++ class with constructor arguments", function() { + var parent = cm.AbstractClassWithConstructor; + var C = parent.extend("C", { + __construct: function(x) { + this.__parent.__construct.call(this, x); + }, + abstractMethod: function() { + return this.concreteMethod(); + } + }); + + var impl = new C("hi"); + var rv = cm.callAbstractMethod2(impl); + impl.delete(); + + assert.equal("hi", rv); + }); + + test("__destruct is called when object is destroyed", function() { + var parent = cm.HeldAbstractClass; + var calls = []; + var C = parent.extend("C", { + method: function() { + }, + __destruct: function() { + calls.push("__destruct"); + this.__parent.__destruct.call(this); + } + }); + var impl = new C; + var copy = impl.clone(); + impl.delete(); + assert.deepEqual([], calls); + copy.delete(); + assert.deepEqual(["__destruct"], calls); + }); + + test("if JavaScript implementation of interface is returned, don't wrap in new handle", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.delete(); + assert.equal(impl, rv); + rv.delete(); + }); + + test("can instantiate two wrappers with constructors", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + __construct: function() { + this.__parent.__construct.call(this); + }, + method: function() { + } + }); + var a = new C; + var b = new C; + a.delete(); + b.delete(); + }); + + test("incorrectly calling parent is an error", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + __construct: function() { + this.__parent.__construct(); + }, + method: function() { + } + }); + assert.throws(cm.BindingError, function() { + new C; + }); + }); + + test("deleteLater() works for JavaScript implementations", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.deleteLater(); + rv.deleteLater(); + cm.flushPendingDeletes(); + }); + + test("deleteLater() combined with delete() works for JavaScript implementations", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.deleteLater(); + rv.delete(); + cm.flushPendingDeletes(); + }); + + test("method arguments with pointer ownership semantics are cleaned up after call", function() { + var parent = cm.AbstractClass; + var C = parent.extend("C", { + abstractMethod: function() { + }, + }); + var impl = new C; + cm.passShared(impl); + impl.delete(); + }); + + test("method arguments with pointer ownership semantics can be cloned", function() { + var parent = cm.AbstractClass; + var owned; + var C = parent.extend("C", { + abstractMethod: function() { + }, + passShared: function(p) { + owned = p.clone(); + } + }); + var impl = new C; + cm.passShared(impl); + impl.delete(); + + assert.equal("Derived", owned.getClassName()); + owned.delete(); + }); + + test("emscripten::val method arguments don't leak", function() { + var parent = cm.AbstractClass; + var got; + var C = parent.extend("C", { + abstractMethod: function() { + }, + passVal: function(g) { + got = g; + } + }); + var impl = new C; + var v = {}; + cm.passVal(impl, v); + impl.delete(); + + assert.equal(v, got); + }); }); BaseFixture.extend("registration order", function() { @@ -1945,19 +2307,6 @@ module({ }); }); - test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { - var derived = cm.embind_test_return_smart_derived_ptr(); - var impl = cm.AbstractClass.implement({ - returnsSharedPtr: function() { - return derived; - } - }); - cm.callReturnsSharedPtrMethod(impl); - impl.delete(); - derived.delete(); - // Let the memory leak test superfixture check that no leaks occurred. - }); - BaseFixture.extend("val::as", function() { test("built-ins", function() { assert.equal(true, cm.val_as_bool(true)); @@ -2031,6 +2380,39 @@ module({ }); }); + BaseFixture.extend("intrusive pointers", function() { + test("can pass intrusive pointers", function() { + var ic = new cm.IntrusiveClass; + var d = cm.passThroughIntrusiveClass(ic); + assert.true(ic.isAliasOf(d)); + ic.delete(); + d.delete(); + }); + + test("can hold intrusive pointers", function() { + var ic = new cm.IntrusiveClass; + var holder = new cm.IntrusiveClassHolder; + holder.set(ic); + ic.delete(); + var d = holder.get(); + d.delete(); + holder.delete(); + }); + + test("can extend from intrusive pointer class and still preserve reference in JavaScript", function() { + var C = cm.IntrusiveClass.extend("C", { + }); + var instance = new C; + var holder = new cm.IntrusiveClassHolder; + holder.set(instance); + instance.delete(); + + var back = holder.get(); + assert.equal(back, instance); + holder.delete(); + }); + }); + BaseFixture.extend("typeof", function() { test("typeof", function() { assert.equal("object", cm.getTypeOfVal(null)); diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 52103bbb..30267994 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -1090,6 +1090,16 @@ public: virtual std::shared_ptr<Derived> returnsSharedPtr() = 0; virtual void differentArguments(int i, double d, unsigned char f, double q, std::string) = 0; + + std::string concreteMethod() const { + return "concrete"; + } + + virtual void passShared(const std::shared_ptr<Derived>&) { + } + + virtual void passVal(const val& v) { + } }; EMSCRIPTEN_SYMBOL(optionalMethod); @@ -1103,9 +1113,10 @@ public: } std::string optionalMethod(std::string s) const { - return optional_call<std::string>(optionalMethod_symbol, [&] { - return AbstractClass::optionalMethod(s); - }, s); + return call<std::string>("optionalMethod", s); + //return optional_call<std::string>(optionalMethod_symbol, [&] { + // return AbstractClass::optionalMethod(s); + //}, s); } std::shared_ptr<Derived> returnsSharedPtr() { @@ -1115,6 +1126,14 @@ public: void differentArguments(int i, double d, unsigned char f, double q, std::string s) { return call<void>("differentArguments", i, d, f, q, s); } + + virtual void passShared(const std::shared_ptr<Derived>& p) override { + return call<void>("passShared", p); + } + + virtual void passVal(const val& v) override { + return call<void>("passVal", v); + } }; class ConcreteClass : public AbstractClass { @@ -1122,7 +1141,6 @@ class ConcreteClass : public AbstractClass { return "from concrete"; } - void differentArguments(int i, double d, unsigned char f, double q, std::string s) { } @@ -1152,12 +1170,74 @@ void callDifferentArguments(AbstractClass& ac, int i, double d, unsigned char f, return ac.differentArguments(i, d, f, q, s); } +struct AbstractClassWithConstructor { + explicit AbstractClassWithConstructor(std::string s) + : s(s) + {} + + virtual std::string abstractMethod() = 0; + std::string concreteMethod() { + return s; + } + + std::string s; +}; + +struct AbstractClassWithConstructorWrapper : public wrapper<AbstractClassWithConstructor> { + EMSCRIPTEN_WRAPPER(AbstractClassWithConstructorWrapper); + + virtual std::string abstractMethod() override { + return call<std::string>("abstractMethod"); + } +}; + +std::string callAbstractMethod2(AbstractClassWithConstructor& ac) { + return ac.abstractMethod(); +} + +struct HeldAbstractClass : public PolyBase, public PolySecondBase { + virtual void method() = 0; +}; +struct HeldAbstractClassWrapper : wrapper<HeldAbstractClass> { + EMSCRIPTEN_WRAPPER(HeldAbstractClassWrapper); + + virtual void method() override { + return call<void>("method"); + } +}; + +std::shared_ptr<PolySecondBase> passHeldAbstractClass(std::shared_ptr<HeldAbstractClass> p) { + return p; +} + +void passShared(AbstractClass& ac) { + auto p = std::make_shared<Derived>(); + ac.passShared(p); +} + +void passVal(AbstractClass& ac, val v) { + return ac.passVal(v); +} + EMSCRIPTEN_BINDINGS(interface_tests) { class_<AbstractClass>("AbstractClass") .smart_ptr<std::shared_ptr<AbstractClass>>("shared_ptr<AbstractClass>") .allow_subclass<AbstractClassWrapper>("AbstractClassWrapper") - .function("abstractMethod", &AbstractClass::abstractMethod) - .function("optionalMethod", &AbstractClass::optionalMethod) + .function("abstractMethod", &AbstractClass::abstractMethod, pure_virtual()) + // The select_overload is necessary because, otherwise, the C++ compiler + // cannot deduce the signature of the lambda function. + .function("optionalMethod", optional_override( + [](AbstractClass& this_, std::string s) { + return this_.AbstractClass::optionalMethod(s); + } + )) + .function("concreteMethod", &AbstractClass::concreteMethod) + .function("passShared", select_overload<void(AbstractClass&, const std::shared_ptr<Derived>&)>([](AbstractClass& self, const std::shared_ptr<Derived>& derived) { + self.AbstractClass::passShared(derived); + })) + .function("passVal", select_overload<void(AbstractClass&, const val&)>([](AbstractClass& self, const val& v) { + self.AbstractClass::passVal(v); + })) ; function("getAbstractClass", &getAbstractClass); @@ -1165,6 +1245,22 @@ EMSCRIPTEN_BINDINGS(interface_tests) { function("callOptionalMethod", &callOptionalMethod); function("callReturnsSharedPtrMethod", &callReturnsSharedPtrMethod); function("callDifferentArguments", &callDifferentArguments); + function("passShared", &passShared); + function("passVal", &passVal); + + class_<AbstractClassWithConstructor>("AbstractClassWithConstructor") + .allow_subclass<AbstractClassWithConstructorWrapper>("AbstractClassWithConstructorWrapper", constructor<std::string>()) + .function("abstractMethod", &AbstractClassWithConstructor::abstractMethod, pure_virtual()) + .function("concreteMethod", &AbstractClassWithConstructor::concreteMethod) + ; + function("callAbstractMethod2", &callAbstractMethod2); + + class_<HeldAbstractClass, base<PolySecondBase>>("HeldAbstractClass") + .smart_ptr<std::shared_ptr<HeldAbstractClass>>("shared_ptr<HeldAbstractClass>") + .allow_subclass<HeldAbstractClassWrapper, std::shared_ptr<HeldAbstractClassWrapper>>("HeldAbstractClassWrapper") + .function("method", &HeldAbstractClass::method, pure_virtual()) + ; + function("passHeldAbstractClass", &passHeldAbstractClass); } template<typename T, size_t sizeOfArray> @@ -2369,6 +2465,175 @@ EMSCRIPTEN_BINDINGS(val_new_) { function("construct_with_ints_and_float", &construct_with_ints_and_float); } +template <typename T> +class intrusive_ptr { +public: + typedef T element_type; + + intrusive_ptr(std::nullptr_t = nullptr) + : px(nullptr) + {} + + template <typename U> + explicit intrusive_ptr(U* px) + : px(px) + { + addRef(px); + } + + intrusive_ptr(const intrusive_ptr& that) + : px(that.px) + { + addRef(px); + } + + template<typename U> + intrusive_ptr(const intrusive_ptr<U>& that) + : px(that.get()) + { + addRef(px); + } + + intrusive_ptr& operator=(const intrusive_ptr& that) { + reset(that.get()); + return *this; + } + + intrusive_ptr& operator=(intrusive_ptr&& that) { + release(px); + px = that.px; + that.px = 0; + return *this; + } + + template<typename U> + intrusive_ptr& operator=(const intrusive_ptr<U>& that) { + reset(that.get()); + return *this; + } + + template<typename U> + intrusive_ptr& operator=(intrusive_ptr<U>&& that) { + release(px); + px = that.px; + that.px = 0; + return *this; + } + + ~intrusive_ptr() { + release(px); + } + + void reset(T* nx = nullptr) { + addRef(nx); + release(px); + px = nx; + } + + T* get() const { + return px; + } + + T& operator*() const { + return *px; + } + + T* operator->() const { + return px; + } + + explicit operator bool() const { + return px != nullptr; + } + + void swap(intrusive_ptr& rhs) { + std::swap(px, rhs.px); + } + +private: + void addRef(T* px) { + if (px) { + ++px->referenceCount; + } + } + + void release(T* px) { + if (--px->referenceCount == 0) { + delete px; + } + } + + T* px; + + template<typename U> + friend class intrusive_ptr; +}; + +namespace emscripten { + template<typename T> + struct smart_ptr_trait<intrusive_ptr<T>> { + typedef intrusive_ptr<T> pointer_type; + typedef T element_type; + + static sharing_policy get_sharing_policy() { + return sharing_policy::INTRUSIVE; + } + + static T* get(const intrusive_ptr<T>& p) { + return p.get(); + } + + static intrusive_ptr<T> share(const intrusive_ptr<T>& r, T* ptr) { + return intrusive_ptr<T>(ptr); + } + + static pointer_type* construct_null() { + return new pointer_type; + } + }; +} + +template<typename T> +intrusive_ptr<T> make_intrusive_ptr() { + return intrusive_ptr<T>(new T); +} + +struct IntrusiveClass { + virtual ~IntrusiveClass() {} + long referenceCount = 0; +}; + +struct IntrusiveClassWrapper : public wrapper<IntrusiveClass> { + EMSCRIPTEN_WRAPPER(IntrusiveClassWrapper); +}; + +template<typename T> +struct Holder { + void set(const T& v) { + value = v; + } + const T& get() const { + return value; + } + T value; +}; + +EMSCRIPTEN_BINDINGS(intrusive_pointers) { + class_<IntrusiveClass>("IntrusiveClass") + .smart_ptr_constructor("intrusive_ptr<IntrusiveClass>", &make_intrusive_ptr<IntrusiveClass>) + .allow_subclass<IntrusiveClassWrapper, intrusive_ptr<IntrusiveClassWrapper>>("IntrusiveClassWrapper") + ; + + typedef Holder<intrusive_ptr<IntrusiveClass>> IntrusiveClassHolder; + class_<IntrusiveClassHolder>("IntrusiveClassHolder") + .constructor<>() + .function("set", &IntrusiveClassHolder::set) + .function("get", &IntrusiveClassHolder::get) + ; + + function("passThroughIntrusiveClass", &passThrough<intrusive_ptr<IntrusiveClass>>); +} + std::string getTypeOfVal(const val& v) { return v.typeof().as<std::string>(); } |