-
Notifications
You must be signed in to change notification settings - Fork 465
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
Conversation
4c7f8ba
to
f4bfde2
Compare
inline ObjectWrap<T>::~ObjectWrap() { | ||
if (!IsEmpty()) { | ||
Object object = Value(); | ||
if (!object.IsEmpty()) |
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.
This code could probably benefit from a comment
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.
@digitalinfinity Yup, added a comment!
- Removed NodeJS v10.x.x support Waiting for nodejs/node-addon-api#475 to implement the provided solution and to bump a new version of the module `node-addon-api`
Looks like this may cause issues in Node.js versions that don’t have nodejs/node#24494 … I’ll try and see if there’s some way to detect/work around that. |
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.
LGTM assuming we get it passing on all LTS versions.
Added |
Hey guys, funny enough I encountered the issue anticipated right here. When I throw |
NAPI_THROW_IF_FAILED_VOID(env, status); | ||
|
||
Reference<Object>* instanceRef = instance; | ||
Reference<Object>* instanceRef = this; | ||
*instanceRef = Reference<Object>(env, ref); |
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.
@@ -3329,7 +3337,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 comment
The reason will be displayed to describe this comment to others. Learn more.
I propose using the auto
keyword here in order to decrease verbosity. Or you could simply rename argument data
to instance
and do it in one statement:
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.
Also seeing this issue (double call to FinalizeCallback due to exception in subclass constructor), and this PR fixed the SEGV. |
Fix crashes when the ctor of an ObjectWrap subclass throws. See nodejs#475
We addressed the portion whereby an exception in the |
@gabrielschulhof I’ll look into rebasing this 👍 |
0fd142b
to
c243f71
Compare
@gabrielschulhof I’ve rebased this, but I don’t quite understand #600 and the added complexity there – the destructor should undo what the constructor did in case that there’s an exception, and I don’t really see why that’s enough. This also doesn’t pass tests yet. |
@addaleax I think you're right. |
Sorry, wrong button 😳 |
diff --git a/napi-inl.h b/napi-inl.h
index fde0a66..2855e80 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -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,7 +3109,6 @@ 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);
}
@@ -3726,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(); this makes the tests pass by basically reverting most of #600, but with one important caveat: The last Please feel free to use the diff! |
c243f71
to
10a2698
Compare
@gabrielschulhof Thanks, done! PTAL |
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.
LGTM
CI:
|
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 Co-authored-by: Gabriel Schulhof <[email protected]>
@gabrielschulhof Thanks, done! |
10a2698
to
257393d
Compare
CI:
|
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: #475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
Landed in 4e88506. |
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: nodejs#475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: nodejs#475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: nodejs/node-addon-api#475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: nodejs/node-addon-api#475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: nodejs/node-addon-api#475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: node-ffi-napi/weak-napi#16 PR-URL: nodejs/node-addon-api#475 Reviewed-By: Hitesh Kanwathirtha <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Co-authored-by: Gabriel Schulhof <[email protected]>
src: call
napi_remove_wrap()
inObjectWrap
dtorCurrently, when the
ObjectWrap
constructor runs, it callsnapi_wrap()
, adding a finalize callback to the freshly createdJS object.
However, if the
ObjectWrap
instance is prematurely deleted,for example because a subclass constructor throws – which seems
like a reasonable scenario – that finalize callback was not removed,
possibly leading to a use-after-free crash.
This commit adds a call
napi_remove_wrap()
from theObjectWrap
destructor, and a test for that scenario.
Fixes: node-ffi-napi/weak-napi#16
(As you can see, there is a fixup commit attached. I’m not strictly sureSee nodejs/node#27470whether it’s necessary, but just from looking at the code, it seems that
calling
napi_get_reference_value()
from the finalize callback is invalid;that seems like a bug in itself, but one that needs to be addressed in
nodejs/node.)
src: make ObjectWrap dtor virtual
Otherwise, subclasses of
ObjectWrap
would not be deletedcorrectly by the
delete instance;
line inFinalizeCallback()
.(This is also just the right thing to do for classes from which
subclasses are supposed to be created.)