Skip to content

Commit

Permalink
node-api: avoid calling virtual methods in base's dtor
Browse files Browse the repository at this point in the history
Derived classes' fields are already destroyed if the virtual methods are
invoked in the base class's destructor. It is not safe to call virtual
methods in base's dtor.

PR-URL: nodejs#44424
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
  • Loading branch information
legendecas authored and Fyko committed Sep 15, 2022
1 parent d945bd0 commit 2250bb1
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 8 deletions.
16 changes: 11 additions & 5 deletions src/js_native_api_v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,14 @@ struct napi_env__ {
CHECK_EQ(isolate, context->GetIsolate());
napi_clear_last_error(this);
}
virtual ~napi_env__() { FinalizeAll(); }
v8::Isolate* const isolate; // Shortcut for context()->GetIsolate()
v8impl::Persistent<v8::Context> context_persistent;

inline v8::Local<v8::Context> context() const {
return v8impl::PersistentToLocal::Strong(context_persistent);
}

inline void Ref() { refs++; }
inline void Unref() {
if (--refs == 0) delete this;
if (--refs == 0) DeleteMe();
}

virtual bool can_call_into_js() const { return true; }
Expand Down Expand Up @@ -99,16 +96,20 @@ struct napi_env__ {
CallIntoModule([&](napi_env env) { cb(env, data, hint); });
}

void FinalizeAll() {
virtual void DeleteMe() {
// First we must finalize those references that have `napi_finalizer`
// callbacks. The reason is that addons might store other references which
// they delete during their `napi_finalizer` callbacks. If we deleted such
// references here first, they would be doubly deleted when the
// `napi_finalizer` deleted them subsequently.
v8impl::RefTracker::FinalizeAll(&finalizing_reflist);
v8impl::RefTracker::FinalizeAll(&reflist);
delete this;
}

v8::Isolate* const isolate; // Shortcut for context()->GetIsolate()
v8impl::Persistent<v8::Context> context_persistent;

v8impl::Persistent<v8::Value> last_exception;

// We store references in two different lists, depending on whether they have
Expand All @@ -121,6 +122,11 @@ struct napi_env__ {
int open_callback_scopes = 0;
int refs = 1;
void* instance_data = nullptr;

protected:
// Should not be deleted directly. Delete with `napi_env__::DeleteMe()`
// instead.
virtual ~napi_env__() = default;
};

// This class is used to keep a napi_env live in a way that
Expand Down
4 changes: 2 additions & 2 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
CHECK_NOT_NULL(node_env());
}

node_napi_env__::~node_napi_env__() {
void node_napi_env__::DeleteMe() {
destructing = true;
FinalizeAll();
napi_env__::DeleteMe();
}

bool node_napi_env__::can_call_into_js() const {
Expand Down
3 changes: 2 additions & 1 deletion src/node_api_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
struct node_napi_env__ : public napi_env__ {
node_napi_env__(v8::Local<v8::Context> context,
const std::string& module_filename);
~node_napi_env__();

bool can_call_into_js() const override;
v8::Maybe<bool> mark_arraybuffer_as_untransferable(
Expand All @@ -24,6 +23,8 @@ struct node_napi_env__ : public napi_env__ {
template <bool enforceUncaughtExceptionPolicy, typename T>
void CallbackIntoModule(T&& call);

void DeleteMe() override;

inline node::Environment* node_env() const {
return node::Environment::GetCurrent(context());
}
Expand Down

0 comments on commit 2250bb1

Please sign in to comment.