Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leak checker #8687

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -407,3 +407,4 @@ a license to everyone to use it as detailed in LICENSE.)
* Adam Bujalski <a.bujalski@samsung.com> (copyright owner by Samsung Electronics)
* Guanzhong Chen <gzchen@google.com> (copyright owned by Google, Inc.)
* Denis Serebro <densilver3000@gmail.com>
* Andy Wingo <wingo@igalia.com> (copyright owned by Igalia)
102 changes: 78 additions & 24 deletions src/embind/embind.js
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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');
Expand All @@ -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: [
Expand Down Expand Up @@ -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);
Expand All @@ -1705,29 +1767,20 @@ 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;
return clone;
}
},

$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);
Expand All @@ -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;
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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);
};

Expand All @@ -2344,6 +2397,7 @@ var LibraryEmbind = {
throwBindingError("Pass correct 'this' to __destruct");
}

detachFinalizer(this);
unregisterInheritedInstance(registeredClass, this.$$.ptr);
};

Expand Down
6 changes: 6 additions & 0 deletions src/settings.js
Expand Up @@ -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.
Expand Down