Skip to content

Commit 63bb634

Browse files
Gabriel Schulhofcodebytere
Gabriel Schulhof
authored andcommitted
n-api: free instance data as reference
Instance data associated with a `napi_env` is no longer stored on the env itself but is instead rendered as a reference. Since `v8impl::Reference` is tied to a JS object, this modification factors out the `v8impl::Reference` refcounting and the deletion process into a base class for `v8impl::Reference`, called `v8impl::RefBase`. The instance data is then stored as a `v8impl::RefBase`, along with other references, preventing a segfault that arises from the fact that, up until now, upon `napi_env` destruction, the instance data was freed after all references had already been forcefully freed. If the addon freed a reference during the `napi_set_instance_data` finalizer callback, such a reference had already been freed during environment teardown, causing a double free. Re: nodejs/node-addon-api#663 PR-URL: #31638 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: David Carlier <devnexen@gmail.com>
1 parent 71c1858 commit 63bb634

8 files changed

+232
-111
lines changed

src/js_native_api_v8.cc

+108-61
Original file line numberDiff line numberDiff line change
@@ -186,49 +186,41 @@ inline static napi_status ConcludeDeferred(napi_env env,
186186
}
187187

188188
// Wrapper around v8impl::Persistent that implements reference counting.
189-
class Reference : protected Finalizer, RefTracker {
189+
class RefBase : protected Finalizer, RefTracker {
190190
protected:
191-
Reference(napi_env env,
192-
v8::Local<v8::Value> value,
193-
uint32_t initial_refcount,
194-
bool delete_self,
195-
napi_finalize finalize_callback,
196-
void* finalize_data,
197-
void* finalize_hint)
191+
RefBase(napi_env env,
192+
uint32_t initial_refcount,
193+
bool delete_self,
194+
napi_finalize finalize_callback,
195+
void* finalize_data,
196+
void* finalize_hint)
198197
: Finalizer(env, finalize_callback, finalize_data, finalize_hint),
199-
_persistent(env->isolate, value),
200198
_refcount(initial_refcount),
201199
_delete_self(delete_self) {
202-
if (initial_refcount == 0) {
203-
_persistent.SetWeak(
204-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
205-
}
206200
Link(finalize_callback == nullptr
207201
? &env->reflist
208202
: &env->finalizing_reflist);
209203
}
210204

211205
public:
212-
void* Data() {
206+
static RefBase* New(napi_env env,
207+
uint32_t initial_refcount,
208+
bool delete_self,
209+
napi_finalize finalize_callback,
210+
void* finalize_data,
211+
void* finalize_hint) {
212+
return new RefBase(env,
213+
initial_refcount,
214+
delete_self,
215+
finalize_callback,
216+
finalize_data,
217+
finalize_hint);
218+
}
219+
220+
inline void* Data() {
213221
return _finalize_data;
214222
}
215223

216-
static Reference* New(napi_env env,
217-
v8::Local<v8::Value> value,
218-
uint32_t initial_refcount,
219-
bool delete_self,
220-
napi_finalize finalize_callback = nullptr,
221-
void* finalize_data = nullptr,
222-
void* finalize_hint = nullptr) {
223-
return new Reference(env,
224-
value,
225-
initial_refcount,
226-
delete_self,
227-
finalize_callback,
228-
finalize_data,
229-
finalize_hint);
230-
}
231-
232224
// Delete is called in 2 ways. Either from the finalizer or
233225
// from one of Unwrap or napi_delete_reference.
234226
//
@@ -244,7 +236,7 @@ class Reference : protected Finalizer, RefTracker {
244236
// The second way this is called is from
245237
// the finalizer and _delete_self is set. In this case we
246238
// know we need to do the deletion so just do it.
247-
static void Delete(Reference* reference) {
239+
static inline void Delete(RefBase* reference) {
248240
reference->Unlink();
249241
if ((reference->RefCount() != 0) ||
250242
(reference->_delete_self) ||
@@ -257,40 +249,23 @@ class Reference : protected Finalizer, RefTracker {
257249
}
258250
}
259251

260-
uint32_t Ref() {
261-
if (++_refcount == 1) {
262-
_persistent.ClearWeak();
263-
}
264-
265-
return _refcount;
252+
inline uint32_t Ref() {
253+
return ++_refcount;
266254
}
267255

268-
uint32_t Unref() {
256+
inline uint32_t Unref() {
269257
if (_refcount == 0) {
270258
return 0;
271259
}
272-
if (--_refcount == 0) {
273-
_persistent.SetWeak(
274-
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
275-
}
276-
277-
return _refcount;
260+
return --_refcount;
278261
}
279262

280-
uint32_t RefCount() {
263+
inline uint32_t RefCount() {
281264
return _refcount;
282265
}
283266

284-
v8::Local<v8::Value> Get() {
285-
if (_persistent.IsEmpty()) {
286-
return v8::Local<v8::Value>();
287-
} else {
288-
return v8::Local<v8::Value>::New(_env->isolate, _persistent);
289-
}
290-
}
291-
292267
protected:
293-
void Finalize(bool is_env_teardown = false) override {
268+
inline void Finalize(bool is_env_teardown = false) override {
294269
if (_finalize_callback != nullptr) {
295270
_env->CallIntoModuleThrow([&](napi_env env) {
296271
_finalize_callback(
@@ -310,6 +285,68 @@ class Reference : protected Finalizer, RefTracker {
310285
}
311286
}
312287

288+
private:
289+
uint32_t _refcount;
290+
bool _delete_self;
291+
};
292+
293+
class Reference : public RefBase {
294+
protected:
295+
template <typename... Args>
296+
Reference(napi_env env,
297+
v8::Local<v8::Value> value,
298+
Args&&... args)
299+
: RefBase(env, std::forward<Args>(args)...),
300+
_persistent(env->isolate, value) {
301+
if (RefCount() == 0) {
302+
_persistent.SetWeak(
303+
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
304+
}
305+
}
306+
307+
public:
308+
static inline Reference* New(napi_env env,
309+
v8::Local<v8::Value> value,
310+
uint32_t initial_refcount,
311+
bool delete_self,
312+
napi_finalize finalize_callback = nullptr,
313+
void* finalize_data = nullptr,
314+
void* finalize_hint = nullptr) {
315+
return new Reference(env,
316+
value,
317+
initial_refcount,
318+
delete_self,
319+
finalize_callback,
320+
finalize_data,
321+
finalize_hint);
322+
}
323+
324+
inline uint32_t Ref() {
325+
uint32_t refcount = RefBase::Ref();
326+
if (refcount == 1) {
327+
_persistent.ClearWeak();
328+
}
329+
return refcount;
330+
}
331+
332+
inline uint32_t Unref() {
333+
uint32_t old_refcount = RefCount();
334+
uint32_t refcount = RefBase::Unref();
335+
if (old_refcount == 1 && refcount == 0) {
336+
_persistent.SetWeak(
337+
this, FinalizeCallback, v8::WeakCallbackType::kParameter);
338+
}
339+
return refcount;
340+
}
341+
342+
inline v8::Local<v8::Value> Get() {
343+
if (_persistent.IsEmpty()) {
344+
return v8::Local<v8::Value>();
345+
} else {
346+
return v8::Local<v8::Value>::New(_env->isolate, _persistent);
347+
}
348+
}
349+
313350
private:
314351
// The N-API finalizer callback may make calls into the engine. V8's heap is
315352
// not in a consistent state during the weak callback, and therefore it does
@@ -332,8 +369,6 @@ class Reference : protected Finalizer, RefTracker {
332369
}
333370

334371
v8impl::Persistent<v8::Value> _persistent;
335-
uint32_t _refcount;
336-
bool _delete_self;
337372
};
338373

339374
class ArrayBufferReference final : public Reference {
@@ -354,7 +389,7 @@ class ArrayBufferReference final : public Reference {
354389
}
355390

356391
private:
357-
void Finalize(bool is_env_teardown) override {
392+
inline void Finalize(bool is_env_teardown) override {
358393
if (is_env_teardown) {
359394
v8::HandleScope handle_scope(_env->isolate);
360395
v8::Local<v8::Value> ab = Get();
@@ -3023,9 +3058,19 @@ napi_status napi_set_instance_data(napi_env env,
30233058
void* finalize_hint) {
30243059
CHECK_ENV(env);
30253060

3026-
env->instance_data.data = data;
3027-
env->instance_data.finalize_cb = finalize_cb;
3028-
env->instance_data.hint = finalize_hint;
3061+
v8impl::RefBase* old_data = static_cast<v8impl::RefBase*>(env->instance_data);
3062+
if (old_data != nullptr) {
3063+
// Our contract so far has been to not finalize any old data there may be.
3064+
// So we simply delete it.
3065+
v8impl::RefBase::Delete(old_data);
3066+
}
3067+
3068+
env->instance_data = v8impl::RefBase::New(env,
3069+
0,
3070+
true,
3071+
finalize_cb,
3072+
data,
3073+
finalize_hint);
30293074

30303075
return napi_clear_last_error(env);
30313076
}
@@ -3035,7 +3080,9 @@ napi_status napi_get_instance_data(napi_env env,
30353080
CHECK_ENV(env);
30363081
CHECK_ARG(env, data);
30373082

3038-
*data = env->instance_data.data;
3083+
v8impl::RefBase* idata = static_cast<v8impl::RefBase*>(env->instance_data);
3084+
3085+
*data = (idata == nullptr ? nullptr : idata->Data());
30393086

30403087
return napi_clear_last_error(env);
30413088
}

src/js_native_api_v8.h

+44-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,49 @@
88

99
static napi_status napi_clear_last_error(napi_env env);
1010

11+
namespace v8impl {
12+
13+
class RefTracker {
14+
public:
15+
RefTracker() {}
16+
virtual ~RefTracker() {}
17+
virtual void Finalize(bool isEnvTeardown) {}
18+
19+
typedef RefTracker RefList;
20+
21+
inline void Link(RefList* list) {
22+
prev_ = list;
23+
next_ = list->next_;
24+
if (next_ != nullptr) {
25+
next_->prev_ = this;
26+
}
27+
list->next_ = this;
28+
}
29+
30+
inline void Unlink() {
31+
if (prev_ != nullptr) {
32+
prev_->next_ = next_;
33+
}
34+
if (next_ != nullptr) {
35+
next_->prev_ = prev_;
36+
}
37+
prev_ = nullptr;
38+
next_ = nullptr;
39+
}
40+
41+
static void FinalizeAll(RefList* list) {
42+
while (list->next_ != nullptr) {
43+
list->next_->Finalize(true);
44+
}
45+
}
46+
47+
private:
48+
RefList* next_ = nullptr;
49+
RefList* prev_ = nullptr;
50+
};
51+
52+
} // end of namespace v8impl
53+
1154
struct napi_env__ {
1255
explicit napi_env__(v8::Local<v8::Context> context)
1356
: isolate(context->GetIsolate()),
@@ -22,11 +65,6 @@ struct napi_env__ {
2265
// `napi_finalizer` deleted them subsequently.
2366
v8impl::RefTracker::FinalizeAll(&finalizing_reflist);
2467
v8impl::RefTracker::FinalizeAll(&reflist);
25-
if (instance_data.finalize_cb != nullptr) {
26-
CallIntoModuleThrow([&](napi_env env) {
27-
instance_data.finalize_cb(env, instance_data.data, instance_data.hint);
28-
});
29-
}
3068
}
3169
v8::Isolate* const isolate; // Shortcut for context()->GetIsolate()
3270
v8impl::Persistent<v8::Context> context_persistent;
@@ -76,11 +114,7 @@ struct napi_env__ {
76114
int open_handle_scopes = 0;
77115
int open_callback_scopes = 0;
78116
int refs = 1;
79-
struct {
80-
void* data = nullptr;
81-
void* hint = nullptr;
82-
napi_finalize finalize_cb = nullptr;
83-
} instance_data;
117+
void* instance_data = nullptr;
84118
};
85119

86120
static inline napi_status napi_clear_last_error(napi_env env) {

src/js_native_api_v8_internals.h

-39
Original file line numberDiff line numberDiff line change
@@ -28,45 +28,6 @@
2828

2929
namespace v8impl {
3030

31-
class RefTracker {
32-
public:
33-
RefTracker() {}
34-
virtual ~RefTracker() {}
35-
virtual void Finalize(bool isEnvTeardown) {}
36-
37-
typedef RefTracker RefList;
38-
39-
inline void Link(RefList* list) {
40-
prev_ = list;
41-
next_ = list->next_;
42-
if (next_ != nullptr) {
43-
next_->prev_ = this;
44-
}
45-
list->next_ = this;
46-
}
47-
48-
inline void Unlink() {
49-
if (prev_ != nullptr) {
50-
prev_->next_ = next_;
51-
}
52-
if (next_ != nullptr) {
53-
next_->prev_ = prev_;
54-
}
55-
prev_ = nullptr;
56-
next_ = nullptr;
57-
}
58-
59-
static void FinalizeAll(RefList* list) {
60-
while (list->next_ != nullptr) {
61-
list->next_->Finalize(true);
62-
}
63-
}
64-
65-
private:
66-
RefList* next_ = nullptr;
67-
RefList* prev_ = nullptr;
68-
};
69-
7031
template <typename T>
7132
using Persistent = v8::Global<T>;
7233

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#define NAPI_EXPERIMENTAL
4+
#include <node_api.h>
5+
6+
static void addon_free(napi_env env, void* data, void* hint) {
7+
napi_ref* ref = data;
8+
napi_delete_reference(env, *ref);
9+
free(ref);
10+
fprintf(stderr, "addon_free");
11+
}
12+
13+
napi_value addon_new(napi_env env, napi_value exports, bool ref_first) {
14+
napi_ref* ref = malloc(sizeof(*ref));
15+
if (ref_first) {
16+
napi_create_reference(env, exports, 1, ref);
17+
napi_set_instance_data(env, ref, addon_free, NULL);
18+
} else {
19+
napi_set_instance_data(env, ref, addon_free, NULL);
20+
napi_create_reference(env, exports, 1, ref);
21+
}
22+
return exports;
23+
}

0 commit comments

Comments
 (0)