Skip to content

Commit f9a8178

Browse files
author
Gabriel Schulhof
committed
objectwrap: gracefully handle constructor exceptions
Ensure that no native instance pointer is associated with the JavaScript object under construction if the native constructor causes a JavaScript exception to be thrown. Fixes: #599
1 parent df75e08 commit f9a8178

File tree

6 files changed

+73
-0
lines changed

6 files changed

+73
-0
lines changed

napi-inl.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,9 +3370,32 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
33703370
}
33713371

33723372
T* instance;
3373+
auto removeFailedWrap = [](const CallbackInfo& info) {
3374+
napi_status status = napi_remove_wrap(info.Env(), info.This(), nullptr);
3375+
NAPI_FATAL_IF_FAILED(status,
3376+
"ObjectWrap<T>::ConstructorCallbackWrapper",
3377+
"Failed to remove wrap from failed ObjectWrap instance construction");
3378+
};
33733379
napi_value wrapper = details::WrapCallback([&] {
33743380
CallbackInfo callbackInfo(env, info);
3381+
#ifdef NAPI_CPP_EXCEPTIONS
3382+
try {
3383+
instance = new T(callbackInfo);
3384+
} catch (const Error& e) {
3385+
removeFailedWrap(callbackInfo);
3386+
// re-throw the error after removing the failed wrap.
3387+
throw e;
3388+
}
3389+
#else
33753390
instance = new T(callbackInfo);
3391+
if (callbackInfo.Env().IsExceptionPending()) {
3392+
// We need to clear the exception so that `napi_remove_wrap()` might work.
3393+
Error e = callbackInfo.Env().GetAndClearPendingException();
3394+
removeFailedWrap(callbackInfo);
3395+
e.ThrowAsJavaScriptException();
3396+
delete instance;
3397+
}
3398+
# endif // NAPI_CPP_EXCEPTIONS
33763399
return callbackInfo.This();
33773400
});
33783401

test/binding.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Object InitThreadSafeFunction(Env env);
4848
#endif
4949
Object InitTypedArray(Env env);
5050
Object InitObjectWrap(Env env);
51+
Object InitObjectWrapConstructorException(Env env);
5152
Object InitObjectReference(Env env);
5253
Object InitVersionManagement(Env env);
5354
Object InitThunkingManual(Env env);
@@ -100,6 +101,8 @@ Object Init(Env env, Object exports) {
100101
#endif
101102
exports.Set("typedarray", InitTypedArray(env));
102103
exports.Set("objectwrap", InitObjectWrap(env));
104+
exports.Set("objectwrapConstructorException",
105+
InitObjectWrapConstructorException(env));
103106
exports.Set("objectreference", InitObjectReference(env));
104107
exports.Set("version_management", InitVersionManagement(env));
105108
exports.Set("thunking_manual", InitThunkingManual(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
'threadsafe_function/threadsafe_function.cc',
4444
'typedarray.cc',
4545
'objectwrap.cc',
46+
'objectwrap_constructor_exception.cc',
4647
'objectreference.cc',
4748
'version_management.cc',
4849
'thunking_manual.cc',

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ let testModules = [
4747
'typedarray',
4848
'typedarray-bigint',
4949
'objectwrap',
50+
'objectwrap_constructor_exception',
5051
'objectreference',
5152
'version_management'
5253
];
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <napi.h>
2+
3+
class ConstructorExceptionTest :
4+
public Napi::ObjectWrap<ConstructorExceptionTest> {
5+
public:
6+
ConstructorExceptionTest(const Napi::CallbackInfo& info) :
7+
Napi::ObjectWrap<ConstructorExceptionTest>(info) {
8+
Napi::Error error = Napi::Error::New(info.Env(), "an exception");
9+
#ifdef NAPI_DISABLE_CPP_EXCEPTIONS
10+
error.ThrowAsJavaScriptException();
11+
#else
12+
throw error;
13+
#endif // NAPI_DISABLE_CPP_EXCEPTIONS
14+
}
15+
16+
static void Initialize(Napi::Env env, Napi::Object exports) {
17+
const char* name = "ConstructorExceptionTest";
18+
exports.Set(name, DefineClass(env, name, {}));
19+
}
20+
};
21+
22+
Napi::Object InitObjectWrapConstructorException(Napi::Env env) {
23+
Napi::Object exports = Napi::Object::New(env);
24+
ConstructorExceptionTest::Initialize(env, exports);
25+
return exports;
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
const buildType = process.config.target_defaults.default_configuration;
3+
const assert = require('assert');
4+
5+
const test = (binding) => {
6+
const { ConstructorExceptionTest } = binding.objectwrapConstructorException;
7+
let gotException = false;
8+
try {
9+
new ConstructorExceptionTest();
10+
} catch (anException) {
11+
gotException = true;
12+
}
13+
global.gc();
14+
15+
assert.strictEqual(gotException, true);
16+
}
17+
18+
test(require(`./build/${buildType}/binding.node`));
19+
test(require(`./build/${buildType}/binding_noexcept.node`));

0 commit comments

Comments
 (0)