Skip to content

Commit 7f56a78

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 4d81618 commit 7f56a78

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
@@ -3121,8 +3121,9 @@ inline ObjectWrap<T>::~ObjectWrap() {
31213121
Object object = Value();
31223122
// It is not valid to call `napi_remove_wrap()` with an empty `object`.
31233123
// This happens e.g. during garbage collection.
3124-
if (!object.IsEmpty())
3124+
if (!object.IsEmpty() && _construction_failed) {
31253125
napi_remove_wrap(Env(), object, nullptr);
3126+
}
31263127
}
31273128
}
31283129

@@ -3694,15 +3695,17 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
36943695

36953696
napi_value wrapper = details::WrapCallback([&] {
36963697
CallbackInfo callbackInfo(env, info);
3698+
T* instance = new T(callbackInfo);
36973699
#ifdef NAPI_CPP_EXCEPTIONS
3698-
new T(callbackInfo);
3700+
instance->_construction_failed = false;
36993701
#else
3700-
T* instance = new T(callbackInfo);
37013702
if (callbackInfo.Env().IsExceptionPending()) {
37023703
// We need to clear the exception so that removing the wrap might work.
37033704
Error e = callbackInfo.Env().GetAndClearPendingException();
37043705
delete instance;
37053706
e.ThrowAsJavaScriptException();
3707+
} else {
3708+
instance->_construction_failed = false;
37063709
}
37073710
# endif // NAPI_CPP_EXCEPTIONS
37083711
return callbackInfo.This();

napi.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ namespace Napi {
138138
class CallbackInfo;
139139
class TypedArray;
140140
template <typename T> class TypedArrayOf;
141-
class ObjectWrapConstructionContext;
142141

143142
typedef TypedArrayOf<int8_t> Int8Array; ///< Typed-array of signed 8-bit integers
144143
typedef TypedArrayOf<uint8_t> Uint8Array; ///< Typed-array of unsigned 8-bit integers
@@ -1403,7 +1402,6 @@ namespace Napi {
14031402

14041403
class CallbackInfo {
14051404
public:
1406-
friend class ObjectWrapConstructionContext;
14071405
CallbackInfo(napi_env env, napi_callback_info info);
14081406
~CallbackInfo();
14091407

@@ -1429,7 +1427,6 @@ namespace Napi {
14291427
napi_value _staticArgs[6];
14301428
napi_value* _dynamicArgs;
14311429
void* _data;
1432-
ObjectWrapConstructionContext* _objectWrapConstructionContext;
14331430
};
14341431

14351432
class PropertyDescriptor {
@@ -1888,6 +1885,8 @@ namespace Napi {
18881885
template <InstanceSetterCallback setter>
18891886
static napi_callback WrapSetter(SetterTag<setter>) noexcept { return &This::WrappedMethod<setter>; }
18901887
static napi_callback WrapSetter(SetterTag<nullptr>) noexcept { return nullptr; }
1888+
1889+
bool _construction_failed = true;
18911890
};
18921891

18931892
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)