Skip to content

Commit af87a67

Browse files
committed
n-api: handle reference delete before finalize
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
1 parent 092ab7a commit af87a67

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

Diff for: src/js_native_api_v8.cc

+28-5
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,29 @@ class Reference : private Finalizer {
226226
finalize_hint);
227227
}
228228

229+
// Delete is called in 2 ways. Either from the finalizer or
230+
// from one of Unwrap or napi_delete_reference.
231+
//
232+
// When it is called from Unwrap or napi_delete_reference we only
233+
// want to do the delete if the finalizer has already run,
234+
// otherwise we may crash when the finalizer does run.
235+
// If the finalizer has not already run delay the delete until
236+
// the finalizer runs by not doing the delete
237+
// and setting _delete_self to true so that the finalizer will
238+
// delete it when it runs.
239+
//
240+
// The second way this is called is from
241+
// the finalizer and _delete_self is set. In this case we
242+
// know we need to do the deletion so just do it.
229243
static void Delete(Reference* reference) {
230-
delete reference;
244+
if ((reference->_delete_self) || (reference->_finalize_ran)) {
245+
delete reference;
246+
} else {
247+
// reduce the reference count to 0 and defer until
248+
// finalizer runs
249+
reference->_delete_self = true;
250+
while (reference->Unref() != 0) {}
251+
}
231252
}
232253

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

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

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

283-
if (delete_self) {
301+
// this is safe because if a request to delete the reference
302+
// is made in the finalize_callback it will defer deletion
303+
// to this block and set _delete_self to true
304+
if (reference->_delete_self) {
284305
Delete(reference);
306+
} else {
307+
reference->_finalize_ran = true;
285308
}
286309
}
287310

Diff for: src/js_native_api_v8.h

+1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class Finalizer {
180180
napi_finalize _finalize_callback;
181181
void* _finalize_data;
182182
void* _finalize_hint;
183+
bool _finalize_ran = false;
183184
};
184185

185186
class TryCatch : public v8::TryCatch {

0 commit comments

Comments
 (0)