Skip to content

Commit

Permalink
n-api: handle reference delete before finalize
Browse files Browse the repository at this point in the history
Crashes were reported during finalization due to
the memory for a reference being deleted and the
finalizer running after the deletion.

This change ensures the deletion of the memory for
the reference only occurs after the finalizer has run.

Fixes: nodejs/node-addon-api#393
  • Loading branch information
mhdawson committed Nov 19, 2018
1 parent 092ab7a commit af87a67
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 5 deletions.
33 changes: 28 additions & 5 deletions src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,29 @@ class Reference : private Finalizer {
finalize_hint);
}

// Delete is called in 2 ways. Either from the finalizer or
// from one of Unwrap or napi_delete_reference.
//
// When it is called from Unwrap or napi_delete_reference we only
// want to do the delete if the finalizer has already run,
// otherwise we may crash when the finalizer does run.
// If the finalizer has not already run delay the delete until
// the finalizer runs by not doing the delete
// and setting _delete_self to true so that the finalizer will
// delete it when it runs.
//
// The second way this is called is from
// the finalizer and _delete_self is set. In this case we
// know we need to do the deletion so just do it.
static void Delete(Reference* reference) {
delete reference;
if ((reference->_delete_self) || (reference->_finalize_ran)) {
delete reference;
} else {
// reduce the reference count to 0 and defer until
// finalizer runs
reference->_delete_self = true;
while (reference->Unref() != 0) {}
}
}

uint32_t Ref() {
Expand Down Expand Up @@ -267,9 +288,6 @@ class Reference : private Finalizer {
Reference* reference = data.GetParameter();
reference->_persistent.Reset();

// Check before calling the finalize callback, because the callback might
// delete it.
bool delete_self = reference->_delete_self;
napi_env env = reference->_env;

if (reference->_finalize_callback != nullptr) {
Expand All @@ -280,8 +298,13 @@ class Reference : private Finalizer {
reference->_finalize_hint));
}

if (delete_self) {
// this is safe because if a request to delete the reference
// is made in the finalize_callback it will defer deletion
// to this block and set _delete_self to true
if (reference->_delete_self) {
Delete(reference);
} else {
reference->_finalize_ran = true;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/js_native_api_v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class Finalizer {
napi_finalize _finalize_callback;
void* _finalize_data;
void* _finalize_hint;
bool _finalize_ran = false;
};

class TryCatch : public v8::TryCatch {
Expand Down

0 comments on commit af87a67

Please sign in to comment.