Skip to content

Commit 470f130

Browse files
author
Gabriel Schulhof
committed
objectwrap: avoid double-free on old Node.js
On Node.js versions earlier than 10.15.3 references were being cleaned up differently. This has resulted in a segfault because of a double free when the `ObjectWrap<T>` subclass constructor throws. We need a fix that only runs on Node.js < 10.15.3. We use `Napi::VersionManagement::GetNodeVersion()` at addon load time to determine whether we need the workaround. We store the result in an atomic boolean and consult its value whenever we destroy an `ObjectWrap<T>` instance.
1 parent 81e2eac commit 470f130

File tree

2 files changed

+30
-5
lines changed

2 files changed

+30
-5
lines changed

napi-inl.h

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// Note: Do not include this file directly! Include "napi.h" instead.
1111

1212
#include <algorithm>
13+
#include <atomic>
1314
#include <cstring>
1415
#include <mutex>
1516
#include <type_traits>
@@ -19,6 +20,8 @@ namespace Napi {
1920
// Helpers to handle functions exposed from C++.
2021
namespace details {
2122

23+
extern std::atomic_bool needs_objectwrap_destructor_fix;
24+
2225
// Attach a data item to an object and delete it when the object gets
2326
// garbage-collected.
2427
// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes
@@ -251,11 +254,16 @@ struct AccessorCallbackData {
251254
// Module registration
252255
////////////////////////////////////////////////////////////////////////////////
253256

254-
#define NODE_API_MODULE(modname, regfunc) \
255-
napi_value __napi_ ## regfunc(napi_env env, \
256-
napi_value exports) { \
257-
return Napi::RegisterModule(env, exports, regfunc); \
258-
} \
257+
#define NODE_API_MODULE(modname, regfunc) \
258+
namespace Napi { \
259+
namespace details { \
260+
std::atomic_bool needs_objectwrap_destructor_fix(false); \
261+
} \
262+
} \
263+
napi_value __napi_ ## regfunc(napi_env env, \
264+
napi_value exports) { \
265+
return Napi::RegisterModule(env, exports, regfunc); \
266+
} \
259267
NAPI_MODULE(modname, __napi_ ## regfunc)
260268

261269
// Adapt the NAPI_MODULE registration function:
@@ -264,6 +272,12 @@ struct AccessorCallbackData {
264272
inline napi_value RegisterModule(napi_env env,
265273
napi_value exports,
266274
ModuleRegisterCallback registerCallback) {
275+
const napi_node_version* nver = Napi::VersionManagement::GetNodeVersion(env);
276+
Napi::details::needs_objectwrap_destructor_fix =
277+
(nver->major < 10 ||
278+
(nver->major == 10 && nver->minor < 15) ||
279+
(nver->major == 10 && nver->minor == 15 && nver->patch < 3));
280+
267281
return details::WrapCallback([&] {
268282
return napi_value(registerCallback(Napi::Env(env),
269283
Napi::Object(env, exports)));
@@ -2985,6 +2999,16 @@ inline ObjectWrap<T>::~ObjectWrap() {
29852999
// This happens e.g. during garbage collection.
29863000
if (!object.IsEmpty() && _construction_failed) {
29873001
napi_remove_wrap(Env(), object, nullptr);
3002+
3003+
if (Napi::details::needs_objectwrap_destructor_fix) {
3004+
// If construction failed we delete the reference via
3005+
// `napi_remove_wrap()`, not via `napi_delete_reference()` in the
3006+
// `Reference<Object>` destructor. This will prevent the
3007+
// `Reference<Object>` destructor from doing a double delete of this
3008+
// reference.
3009+
_ref = nullptr;
3010+
_env = nullptr;
3011+
}
29883012
}
29893013
}
29903014
}

test/objectwrap_constructor_exception.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class ConstructorExceptionTest :
55
public:
66
ConstructorExceptionTest(const Napi::CallbackInfo& info) :
77
Napi::ObjectWrap<ConstructorExceptionTest>(info) {
8+
fprintf(stderr, "That was the interesting reference\n");
89
Napi::Error error = Napi::Error::New(info.Env(), "an exception");
910
#ifdef NAPI_DISABLE_CPP_EXCEPTIONS
1011
error.ThrowAsJavaScriptException();

0 commit comments

Comments
 (0)