Skip to content

Commit 204f072

Browse files
author
Gabriel Schulhof
committed
objectwrap: remove wrap only on failure
`napi_remove_wrap()` was intended for objects that are alive for which the native addon wishes to withdraw its native pointer, and perhaps replace it with another. Therefore we need not `napi_remove_wrap()` during gc/env-cleanup. It is sufficient to `napi_delete_reference()`, as `Reference<Object>` already does. We need only `napi_remove_wrap()` if the construction failed and therefore no gc callback will ever happen. This change also removes references to `ObjectWrapConstructionContext` from the header because the class is not used anymore. Fixes: #660 Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Kevin Eady <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent a552a38 commit 204f072

File tree

4 files changed

+33
-13
lines changed

4 files changed

+33
-13
lines changed

napi-inl.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,8 +2983,9 @@ inline ObjectWrap<T>::~ObjectWrap() {
29832983
Object object = Value();
29842984
// It is not valid to call `napi_remove_wrap()` with an empty `object`.
29852985
// This happens e.g. during garbage collection.
2986-
if (!object.IsEmpty())
2986+
if (!object.IsEmpty() && _construction_failed) {
29872987
napi_remove_wrap(Env(), object, nullptr);
2988+
}
29882989
}
29892990
}
29902991

@@ -3380,15 +3381,17 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
33803381

33813382
napi_value wrapper = details::WrapCallback([&] {
33823383
CallbackInfo callbackInfo(env, info);
3384+
T* instance = new T(callbackInfo);
33833385
#ifdef NAPI_CPP_EXCEPTIONS
3384-
new T(callbackInfo);
3386+
instance->_construction_failed = false;
33853387
#else
3386-
T* instance = new T(callbackInfo);
33873388
if (callbackInfo.Env().IsExceptionPending()) {
33883389
// We need to clear the exception so that removing the wrap might work.
33893390
Error e = callbackInfo.Env().GetAndClearPendingException();
33903391
delete instance;
33913392
e.ThrowAsJavaScriptException();
3393+
} else {
3394+
instance->_construction_failed = false;
33923395
}
33933396
# endif // NAPI_CPP_EXCEPTIONS
33943397
return callbackInfo.This();

napi.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ namespace Napi {
132132
template <typename T> class Reference;
133133
class TypedArray;
134134
template <typename T> class TypedArrayOf;
135-
class ObjectWrapConstructionContext;
136135

137136
typedef TypedArrayOf<int8_t> Int8Array; ///< Typed-array of signed 8-bit integers
138137
typedef TypedArrayOf<uint8_t> Uint8Array; ///< Typed-array of unsigned 8-bit integers
@@ -1378,7 +1377,6 @@ namespace Napi {
13781377

13791378
class CallbackInfo {
13801379
public:
1381-
friend class ObjectWrapConstructionContext;
13821380
CallbackInfo(napi_env env, napi_callback_info info);
13831381
~CallbackInfo();
13841382

@@ -1405,7 +1403,6 @@ namespace Napi {
14051403
napi_value _staticArgs[6];
14061404
napi_value* _dynamicArgs;
14071405
void* _data;
1408-
ObjectWrapConstructionContext* _objectWrapConstructionContext;
14091406
};
14101407

14111408
class PropertyDescriptor {
@@ -1738,6 +1735,8 @@ namespace Napi {
17381735
StaticAccessorCallbackData;
17391736
typedef AccessorCallbackData<InstanceGetterCallback, InstanceSetterCallback>
17401737
InstanceAccessorCallbackData;
1738+
1739+
bool _construction_failed = true;
17411740
};
17421741

17431742
class HandleScope {

test/objectwrap-removewrap.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include <napi.h>
22
#include <assert.h>
33

4-
#ifdef NAPI_CPP_EXCEPTIONS
54
namespace {
65

76
static int dtor_called = 0;
@@ -21,7 +20,11 @@ Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) {
2120
class Test : public Napi::ObjectWrap<Test> {
2221
public:
2322
Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
23+
#ifdef NAPI_CPP_EXCEPTIONS
2424
throw Napi::Error::New(Env(), "Some error");
25+
#else
26+
Napi::Error::New(Env(), "Some error").ThrowAsJavaScriptException();
27+
#endif
2528
}
2629

2730
static void Initialize(Napi::Env env, Napi::Object exports) {
@@ -30,16 +33,13 @@ class Test : public Napi::ObjectWrap<Test> {
3033
}
3134

3235
private:
33-
DtorCounter dtor_ounter_;
36+
DtorCounter dtor_counter_;
3437
};
3538

3639
} // anonymous namespace
37-
#endif // NAPI_CPP_EXCEPTIONS
3840

3941
Napi::Object InitObjectWrapRemoveWrap(Napi::Env env) {
4042
Napi::Object exports = Napi::Object::New(env);
41-
#ifdef NAPI_CPP_EXCEPTIONS
4243
Test::Initialize(env, exports);
43-
#endif
4444
return exports;
4545
}

test/objectwrap-removewrap.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
'use strict';
2+
3+
if (process.argv[2] === 'child') {
4+
// Create a single wrapped instance then exit.
5+
return new (require(process.argv[3]).objectwrap.Test)();
6+
}
7+
28
const buildType = process.config.target_defaults.default_configuration;
39
const assert = require('assert');
10+
const { spawnSync } = require('child_process');
411

5-
const test = (binding) => {
12+
const test = (bindingName) => {
13+
const binding = require(bindingName);
614
const Test = binding.objectwrap_removewrap.Test;
715
const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled;
816

@@ -12,6 +20,16 @@ const test = (binding) => {
1220
});
1321
assert.strictEqual(getDtorCalled(), 1);
1422
global.gc(); // Does not crash.
23+
24+
// Start a child process that creates a single wrapped instance to ensure that
25+
// it is properly freed at its exit. It must not segfault.
26+
// Re: https://github.com/nodejs/node-addon-api/issues/660
27+
const child = spawnSync(process.execPath, [
28+
__filename, 'child', bindingName
29+
]);
30+
assert.strictEqual(child.signal, null);
31+
assert.strictEqual(child.status, 0);
1532
}
1633

17-
test(require(`./build/${buildType}/binding.node`));
34+
test(`./build/${buildType}/binding.node`);
35+
test(`./build/${buildType}/binding_noexcept.node`);

0 commit comments

Comments
 (0)