-
Notifications
You must be signed in to change notification settings - Fork 468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
src: fix ObjectWrap destruction #475
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2702,37 +2702,6 @@ inline Object FunctionReference::New(const std::vector<napi_value>& args) const | |
// CallbackInfo class | ||
//////////////////////////////////////////////////////////////////////////////// | ||
|
||
class ObjectWrapConstructionContext { | ||
public: | ||
ObjectWrapConstructionContext(CallbackInfo* info) { | ||
info->_objectWrapConstructionContext = this; | ||
} | ||
|
||
static inline void SetObjectWrapped(const CallbackInfo& info) { | ||
if (info._objectWrapConstructionContext == nullptr) { | ||
Napi::Error::Fatal("ObjectWrapConstructionContext::SetObjectWrapped", | ||
"_objectWrapConstructionContext is NULL"); | ||
} | ||
info._objectWrapConstructionContext->_objectWrapped = true; | ||
} | ||
|
||
inline void Cleanup(const CallbackInfo& info) { | ||
if (_objectWrapped) { | ||
napi_status status = napi_remove_wrap(info.Env(), info.This(), nullptr); | ||
|
||
// There's already a pending exception if we are at this point, so we have | ||
// no choice but to fatally fail here. | ||
NAPI_FATAL_IF_FAILED(status, | ||
"ObjectWrapConstructionContext::Cleanup", | ||
"Failed to remove wrap from unsuccessfully " | ||
"constructed ObjectWrap instance"); | ||
} | ||
} | ||
|
||
private: | ||
bool _objectWrapped = false; | ||
}; | ||
|
||
inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) | ||
: _env(env), _info(info), _this(nullptr), _dynamicArgs(nullptr), _data(nullptr) { | ||
_argc = _staticArgCount; | ||
|
@@ -3140,13 +3109,22 @@ inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { | |
status = napi_wrap(env, wrapper, this, FinalizeCallback, nullptr, &ref); | ||
NAPI_THROW_IF_FAILED_VOID(env, status); | ||
|
||
ObjectWrapConstructionContext::SetObjectWrapped(callbackInfo); | ||
Reference<Object>* instanceRef = this; | ||
*instanceRef = Reference<Object>(env, ref); | ||
} | ||
|
||
template<typename T> | ||
inline ObjectWrap<T>::~ObjectWrap() {} | ||
template <typename T> | ||
inline ObjectWrap<T>::~ObjectWrap() { | ||
// If the JS object still exists at this point, remove the finalizer added | ||
// through `napi_wrap()`. | ||
if (!IsEmpty()) { | ||
Object object = Value(); | ||
// It is not valid to call `napi_remove_wrap()` with an empty `object`. | ||
// This happens e.g. during garbage collection. | ||
if (!object.IsEmpty()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code could probably benefit from a comment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @digitalinfinity Yup, added a comment! |
||
napi_remove_wrap(Env(), object, nullptr); | ||
} | ||
} | ||
|
||
template<typename T> | ||
inline T* ObjectWrap<T>::Unwrap(Object wrapper) { | ||
|
@@ -3716,23 +3694,15 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper( | |
|
||
napi_value wrapper = details::WrapCallback([&] { | ||
CallbackInfo callbackInfo(env, info); | ||
ObjectWrapConstructionContext constructionContext(&callbackInfo); | ||
#ifdef NAPI_CPP_EXCEPTIONS | ||
try { | ||
new T(callbackInfo); | ||
} catch (const Error& e) { | ||
// Re-throw the error after removing the failed wrap. | ||
constructionContext.Cleanup(callbackInfo); | ||
throw e; | ||
} | ||
new T(callbackInfo); | ||
#else | ||
T* instance = new T(callbackInfo); | ||
if (callbackInfo.Env().IsExceptionPending()) { | ||
// We need to clear the exception so that removing the wrap might work. | ||
Error e = callbackInfo.Env().GetAndClearPendingException(); | ||
constructionContext.Cleanup(callbackInfo); | ||
e.ThrowAsJavaScriptException(); | ||
delete instance; | ||
e.ThrowAsJavaScriptException(); | ||
} | ||
# endif // NAPI_CPP_EXCEPTIONS | ||
return callbackInfo.This(); | ||
|
@@ -3859,7 +3829,7 @@ inline napi_value ObjectWrap<T>::InstanceSetterCallbackWrapper( | |
|
||
template <typename T> | ||
inline void ObjectWrap<T>::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { | ||
T* instance = reinterpret_cast<T*>(data); | ||
ObjectWrap<T>* instance = static_cast<ObjectWrap<T>*>(data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I propose using the inline void ObjectWrap<T>::FinalizeCallback(napi_env /*env*/, void * instance, void * /*hint*/) {
delete static_cast<ObjectWrap<T>*>(instance);
} Any of two options are alright from my viewpoint, though it may just be a matter of style. |
||
instance->Finalize(Napi::Env(env)); | ||
delete instance; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#include <napi.h> | ||
#include <assert.h> | ||
|
||
#ifdef NAPI_CPP_EXCEPTIONS | ||
namespace { | ||
|
||
static int dtor_called = 0; | ||
|
||
class DtorCounter { | ||
public: | ||
~DtorCounter() { | ||
assert(dtor_called == 0); | ||
dtor_called++; | ||
} | ||
}; | ||
|
||
Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) { | ||
return Napi::Number::New(info.Env(), dtor_called); | ||
} | ||
|
||
class Test : public Napi::ObjectWrap<Test> { | ||
public: | ||
Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) { | ||
throw Napi::Error::New(Env(), "Some error"); | ||
} | ||
|
||
static void Initialize(Napi::Env env, Napi::Object exports) { | ||
exports.Set("Test", DefineClass(env, "Test", {})); | ||
exports.Set("getDtorCalled", Napi::Function::New(env, GetDtorCalled)); | ||
} | ||
|
||
private: | ||
DtorCounter dtor_ounter_; | ||
}; | ||
|
||
} // anonymous namespace | ||
#endif // NAPI_CPP_EXCEPTIONS | ||
|
||
Napi::Object InitObjectWrapRemoveWrap(Napi::Env env) { | ||
Napi::Object exports = Napi::Object::New(env); | ||
#ifdef NAPI_CPP_EXCEPTIONS | ||
Test::Initialize(env, exports); | ||
#endif | ||
return exports; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
'use strict'; | ||
const buildType = process.config.target_defaults.default_configuration; | ||
const assert = require('assert'); | ||
|
||
const test = (binding) => { | ||
const Test = binding.objectwrap_removewrap.Test; | ||
const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; | ||
|
||
assert.strictEqual(getDtorCalled(), 0); | ||
assert.throws(() => { | ||
new Test(); | ||
}); | ||
assert.strictEqual(getDtorCalled(), 1); | ||
global.gc(); // Does not crash. | ||
} | ||
|
||
test(require(`./build/${buildType}/binding.node`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two statements though work, as intended but seem to be a little bit misleading. Having a fast peek over the code you could think that we re-move-assign the whole *this object.
I suggest making an explicit call to parent move-assignment operator, namely:
Reference::operator=({env, ref});
It is more laconic and clear from my point of view.