diff --git a/AUTHORS b/AUTHORS index 34ad185bda1ce..dc0b419ab7423 100644 --- a/AUTHORS +++ b/AUTHORS @@ -407,3 +407,4 @@ a license to everyone to use it as detailed in LICENSE.) * Adam Bujalski (copyright owner by Samsung Electronics) * Guanzhong Chen (copyright owned by Google, Inc.) * Denis Serebro +* Andy Wingo (copyright owned by Igalia) diff --git a/src/embind/embind.js b/src/embind/embind.js index c98e26ac5d4a0..96d7ff9b73303 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -18,12 +18,13 @@ /*global typeDependencies, flushPendingDeletes, getTypeName, getBasestPointer, throwBindingError, UnboundTypeError, _embind_repr, registeredInstances, registeredTypes, getShiftFromSize*/ /*global ensureOverloadTable, embind__requireFunction, awaitingDependencies, makeLegalFunctionName, embind_charCodes:true, registerType, createNamedFunction, RegisteredPointer, throwInternalError*/ /*global simpleReadValueFromPointer, floatReadValueFromPointer, integerReadValueFromPointer, enumReadValueFromPointer, replacePublicSymbol, craftInvokerFunction, tupleRegistrations*/ +/*global finalizationGroup, attachFinalizer, detachFinalizer, releaseClassHandle, runDestructor*/ /*global ClassHandle, makeClassHandle, structRegistrations, whenDependentTypesAreResolved, BindingError, deletionQueue, delayFunction:true, upcastPointer*/ /*global exposePublicSymbol, heap32VectorToArray, new_, RegisteredPointer_getPointee, RegisteredPointer_destructor, RegisteredPointer_deleteObject, char_0, char_9*/ /*global getInheritedInstanceCount, getLiveInheritedInstances, setDelayFunction, InternalError, runDestructors*/ /*global requireRegisteredType, unregisterInheritedInstance, registerInheritedInstance, PureVirtualError, throwUnboundTypeError*/ /*global assert, validateThis, downcastPointer, registeredPointers, RegisteredClass, getInheritedInstance, ClassHandle_isAliasOf, ClassHandle_clone, ClassHandle_isDeleted, ClassHandle_deleteLater*/ -/*global throwInstanceAlreadyDeleted, runDestructor, shallowCopyInternalPointer*/ +/*global throwInstanceAlreadyDeleted, shallowCopyInternalPointer*/ /*global RegisteredPointer_fromWireType, constNoSmartPtrRawPointerToWireType, nonConstNoSmartPtrRawPointerToWireType, genericPointerToWireType*/ var LibraryEmbind = { @@ -1626,7 +1627,68 @@ var LibraryEmbind = { } }, - $makeClassHandle__deps: ['$throwInternalError'], + $runDestructor: function($$) { + if ($$.smartPtr) { + $$.smartPtrType.rawDestructor($$.smartPtr); + } else { + $$.ptrType.registeredClass.rawDestructor($$.ptr); + } + }, + + $releaseClassHandle__deps: ['$runDestructor'], + $releaseClassHandle: function($$) { + $$.count.value -= 1; + var toDelete = 0 === $$.count.value; + if (toDelete) { + runDestructor($$); + } + }, + + $finalizationGroup: false, + + $detachFinalizer_deps: ['$finalizationGroup'], + $detachFinalizer: function(handle) {}, + + $attachFinalizer__deps: ['$finalizationGroup', '$detachFinalizer', + '$releaseClassHandle'], + $attachFinalizer: function(handle) { + if ('undefined' === typeof FinalizationGroup) { +#if EMBIND_LEAKCHECK + console.warn("FinalizationGroup unavailable; leak checking disabled"); +#endif + attachFinalizer = function (handle) { return handle; }; + return handle; + } + // If the running environment has a FinalizationGroup (see + // https://github.com/tc39/proposal-weakrefs), then attach finalizers + // for class handles. We check for the presence of FinalizationGroup + // at run-time, not build-time. + finalizationGroup = new FinalizationGroup(function (iter) { + for (var result = iter.next(); !result.done; result = iter.next()) { + var $$ = result.value; + if (!$$.ptr) { + console.warn('object already deleted: ' + $$.ptr); + } else { +#if EMBIND_LEAKCHECK + console.warn('leaked object'); + console.warn($$); +#else + releaseClassHandle($$); +#endif + } + } + }); + attachFinalizer = function(handle) { + finalizationGroup.register(handle, handle.$$, handle.$$); + return handle; + }; + detachFinalizer = function(handle) { + finalizationGroup.unregister(handle.$$); + }; + return attachFinalizer(handle); + }, + + $makeClassHandle__deps: ['$throwInternalError', '$attachFinalizer'], $makeClassHandle: function(prototype, record) { if (!record.ptrType || !record.ptr) { throwInternalError('makeClassHandle requires ptr and ptrType'); @@ -1637,11 +1699,11 @@ var LibraryEmbind = { throwInternalError('Both smartPtrType and smartPtr must be specified'); } record.count = { value: 1 }; - return Object.create(prototype, { + return attachFinalizer(Object.create(prototype, { $$: { value: record, }, - }); + })); }, $init_ClassHandle__deps: [ @@ -1695,7 +1757,7 @@ var LibraryEmbind = { throwBindingError(getInstanceTypeName(obj) + ' instance already deleted'); }, - $ClassHandle_clone__deps: ['$shallowCopyInternalPointer', '$throwInstanceAlreadyDeleted'], + $ClassHandle_clone__deps: ['$shallowCopyInternalPointer', '$throwInstanceAlreadyDeleted', '$attachFinalizer'], $ClassHandle_clone: function() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); @@ -1705,11 +1767,11 @@ var LibraryEmbind = { this.$$.count.value += 1; return this; } else { - var clone = Object.create(Object.getPrototypeOf(this), { + var clone = attachFinalizer(Object.create(Object.getPrototypeOf(this), { $$: { value: shallowCopyInternalPointer(this.$$), } - }); + })); clone.$$.count.value += 1; clone.$$.deleteScheduled = false; @@ -1717,17 +1779,8 @@ var LibraryEmbind = { } }, - $runDestructor: function(handle) { - var $$ = handle.$$; - if ($$.smartPtr) { - $$.smartPtrType.rawDestructor($$.smartPtr); - } else { - $$.ptrType.registeredClass.rawDestructor($$.ptr); - } - }, - - $ClassHandle_delete__deps: [ - '$runDestructor', '$throwBindingError', '$throwInstanceAlreadyDeleted'], + $ClassHandle_delete__deps: ['$releaseClassHandle', '$throwBindingError', + '$detachFinalizer', '$throwInstanceAlreadyDeleted'], $ClassHandle_delete: function() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); @@ -1737,11 +1790,9 @@ var LibraryEmbind = { throwBindingError('Object already scheduled for deletion'); } - this.$$.count.value -= 1; - var toDelete = 0 === this.$$.count.value; - if (toDelete) { - runDestructor(this); - } + detachFinalizer(this); + releaseClassHandle(this.$$); + if (!this.$$.preservePointerOnDelete) { this.$$.smartPtr = undefined; this.$$.ptr = undefined; @@ -2294,7 +2345,7 @@ var LibraryEmbind = { '$PureVirtualError', '$readLatin1String', '$registerInheritedInstance', '$requireHandle', '$requireRegisteredType', '$throwBindingError', - '$unregisterInheritedInstance'], + '$unregisterInheritedInstance', '$detachFinalizer', '$attachFinalizer'], _embind_create_inheriting_constructor: function(constructorName, wrapperType, properties) { constructorName = readLatin1String(constructorName); wrapperType = requireRegisteredType(wrapperType, 'wrapper'); @@ -2330,12 +2381,14 @@ var LibraryEmbind = { var inner = baseConstructor["implement"].apply( undefined, [this].concat(arraySlice.call(arguments))); + detachFinalizer(inner); var $$ = inner.$$; inner["notifyOnDestruction"](); $$.preservePointerOnDelete = true; Object.defineProperties(this, { $$: { value: $$ }}); + attachFinalizer(this); registerInheritedInstance(registeredClass, $$.ptr, this); }; @@ -2344,6 +2397,7 @@ var LibraryEmbind = { throwBindingError("Pass correct 'this' to __destruct"); } + detachFinalizer(this); unregisterInheritedInstance(registeredClass, this.$$.ptr); }; diff --git a/src/settings.js b/src/settings.js index 4a6355d2741b6..a6e0116cdf7c3 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1221,6 +1221,12 @@ var TEXTDECODER = 1; // Disable this to support binary data transfer. var EMBIND_STD_STRING_IS_UTF8 = 1; +// Embind will attach finalizers to objects with destructors. If you +// set EMBIND_LEAKCHECK to 1, instead of invoking the destructor, embind +// will issue a warning to the console if a user forgets to invoke an +// object's destructor before it goes out of scope. +var EMBIND_LEAKCHECK = 0; + // If set to 1, enables support for transferring canvases to pthreads and // creating WebGL contexts in them, as well as explicit swap control for GL // contexts. This needs browser support for the OffscreenCanvas specification.