Skip to content

Commit f73d84a

Browse files
committed
Merge branch 'add-napi_remove_wrap' of github.com:addaleax/node-api
Fix crashes when the ctor of an ObjectWrap subclass throws. See nodejs#475
2 parents 2e1769e + 0fd142b commit f73d84a

File tree

7 files changed

+84
-6
lines changed

7 files changed

+84
-6
lines changed

napi-inl.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,16 +2920,25 @@ inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
29202920
napi_value wrapper = callbackInfo.This();
29212921
napi_status status;
29222922
napi_ref ref;
2923-
T* instance = static_cast<T*>(this);
2924-
status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref);
2923+
status = napi_wrap(env, wrapper, this, FinalizeCallback, nullptr, &ref);
29252924
NAPI_THROW_IF_FAILED_VOID(env, status);
29262925

2927-
Reference<Object>* instanceRef = instance;
2926+
Reference<Object>* instanceRef = this;
29282927
*instanceRef = Reference<Object>(env, ref);
29292928
}
29302929

2931-
template<typename T>
2932-
inline ObjectWrap<T>::~ObjectWrap() {}
2930+
template <typename T>
2931+
inline ObjectWrap<T>::~ObjectWrap() {
2932+
// If the JS object still exists at this point, remove the finalizer added
2933+
// through `napi_wrap()`.
2934+
if (!IsEmpty()) {
2935+
Object object = Value();
2936+
// It is not valid to call `napi_remove_wrap()` with an empty `object`.
2937+
// This happens e.g. during garbage collection.
2938+
if (!object.IsEmpty())
2939+
napi_remove_wrap(Env(), object, nullptr);
2940+
}
2941+
}
29332942

29342943
template<typename T>
29352944
inline T* ObjectWrap<T>::Unwrap(Object wrapper) {
@@ -3449,7 +3458,7 @@ inline napi_value ObjectWrap<T>::InstanceSetterCallbackWrapper(
34493458

34503459
template <typename T>
34513460
inline void ObjectWrap<T>::FinalizeCallback(napi_env env, void* data, void* /*hint*/) {
3452-
T* instance = reinterpret_cast<T*>(data);
3461+
ObjectWrap<T>* instance = static_cast<ObjectWrap<T>*>(data);
34533462
instance->Finalize(Napi::Env(env));
34543463
delete instance;
34553464
}

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Object InitThreadSafeFunction(Env env);
4242
#endif
4343
Object InitTypedArray(Env env);
4444
Object InitObjectWrap(Env env);
45+
Object InitObjectWrapRemoveWrap(Env env);
4546
Object InitObjectReference(Env env);
4647
Object InitVersionManagement(Env env);
4748
Object InitThunkingManual(Env env);
@@ -87,6 +88,7 @@ Object Init(Env env, Object exports) {
8788
#endif
8889
exports.Set("typedarray", InitTypedArray(env));
8990
exports.Set("objectwrap", InitObjectWrap(env));
91+
exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env));
9092
exports.Set("objectreference", InitObjectReference(env));
9193
exports.Set("version_management", InitVersionManagement(env));
9294
exports.Set("thunking_manual", InitThunkingManual(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
'threadsafe_function/threadsafe_function.cc',
3838
'typedarray.cc',
3939
'objectwrap.cc',
40+
'objectwrap-removewrap.cc',
4041
'objectreference.cc',
4142
'version_management.cc',
4243
'thunking_manual.cc',

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ let testModules = [
4242
'typedarray',
4343
'typedarray-bigint',
4444
'objectwrap',
45+
'objectwrap-removewrap',
4546
'objectreference',
4647
'version_management'
4748
];

test/objectwrap-removewrap.cc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <napi.h>
2+
#include <assert.h>
3+
4+
#ifdef NAPI_CPP_EXCEPTIONS
5+
namespace {
6+
7+
static int dtor_called = 0;
8+
9+
class DtorCounter {
10+
public:
11+
~DtorCounter() {
12+
assert(dtor_called == 0);
13+
dtor_called++;
14+
}
15+
};
16+
17+
Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) {
18+
return Napi::Number::New(info.Env(), dtor_called);
19+
}
20+
21+
class Test : public Napi::ObjectWrap<Test> {
22+
public:
23+
Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
24+
throw Napi::Error::New(Env(), "Some error");
25+
}
26+
27+
static void Initialize(Napi::Env env, Napi::Object exports) {
28+
exports.Set("Test", DefineClass(env, "Test", {}));
29+
exports.Set("getDtorCalled", Napi::Function::New(env, GetDtorCalled));
30+
}
31+
32+
private:
33+
DtorCounter dtor_ounter_;
34+
};
35+
36+
} // anonymous namespace
37+
#endif // NAPI_CPP_EXCEPTIONS
38+
39+
Napi::Object InitObjectWrapRemoveWrap(Napi::Env env) {
40+
Napi::Object exports = Napi::Object::New(env);
41+
#ifdef NAPI_CPP_EXCEPTIONS
42+
Test::Initialize(env, exports);
43+
#endif
44+
return exports;
45+
}

test/objectwrap-removewrap.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
const buildType = process.config.target_defaults.default_configuration;
3+
const assert = require('assert');
4+
5+
const test = (binding) => {
6+
const Test = binding.objectwrap_removewrap.Test;
7+
const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled;
8+
9+
assert.strictEqual(getDtorCalled(), 0);
10+
assert.throws(() => {
11+
new Test();
12+
});
13+
assert.strictEqual(getDtorCalled(), 1);
14+
global.gc(); // Does not crash.
15+
}
16+
17+
test(require(`./build/${buildType}/binding.node`));

test/objectwrap.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ const test = (binding) => {
222222
// `Test` is needed for accessing exposed symbols
223223
testObj(new Test(), Test);
224224
testClass(Test);
225+
226+
// Make sure the C++ object can be garbage collected without issues.
227+
setImmediate(global.gc);
225228
}
226229

227230
test(require(`./build/${buildType}/binding.node`));

0 commit comments

Comments
 (0)