-
-
Notifications
You must be signed in to change notification settings - Fork 493
fixes v8 GC access violation for zombie objects #638
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
Changes from 4 commits
7c59fab
beb4920
80d6607
41c7a89
253a9e1
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 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3120,7 +3120,7 @@ inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { | |||||||||||||||
| napi_status status; | ||||||||||||||||
| napi_ref ref; | ||||||||||||||||
| T* instance = static_cast<T*>(this); | ||||||||||||||||
| status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); | ||||||||||||||||
| status = napi_wrap(env, wrapper, instance, FinalizeCallback, (void*)callbackInfo.zombie, &ref); | ||||||||||||||||
| NAPI_THROW_IF_FAILED_VOID(env, status); | ||||||||||||||||
gabrielschulhof marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
|
|
||||||||||||||||
| Reference<Object>* instanceRef = instance; | ||||||||||||||||
|
|
@@ -3697,10 +3697,26 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper( | |||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| T* instance; | ||||||||||||||||
| napi_value wrapper = details::WrapCallback([&] { | ||||||||||||||||
| napi_value wrapper = details::WrapCallback([&] () -> napi_value { | ||||||||||||||||
|
Contributor
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.
Suggested change
We don't need this change, because it's OK to return |
||||||||||||||||
| CallbackInfo callbackInfo(env, info); | ||||||||||||||||
| callbackInfo.zombie = new Zombie(); | ||||||||||||||||
|
||||||||||||||||
| callbackInfo.zombie = new Zombie(); | |
| ObjectWrapPingPong ping_pong({callbackInfo.Data(), false}); | |
| callbackInfo.SetData(&ping_pong); |
Outdated
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.
| callbackInfo.zombie->isZombie = true; | |
| if (ping_pong.object_wrapping_failed == false) { | |
| napi_status status = napi_remove_wrap(callbackInfo.Env(), callbackInfo.This(), nullptr); | |
| NAPI_FATAL_IF_FAILED(status, | |
| "ObjectWrap<T>::ConstructorCallbackWrapper", | |
| "Failed to remove wrap from failed ObjectWrap instance construction"); | |
| } |
Outdated
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.
| callbackInfo.zombie->isZombie = true; | |
| if (ping_pong.object_wrapping_failed == false) { | |
| napi_status status = napi_remove_wrap(callbackInfo.Env(), callbackInfo.This(), nullptr); | |
| NAPI_FATAL_IF_FAILED(status, | |
| "ObjectWrap<T>::ConstructorCallbackWrapper", | |
| "Failed to remove wrap from failed ObjectWrap instance construction"); | |
| } |
I guess this part should probably be factored out and a call made to it from above and from here.
Outdated
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.
| return nullptr; |
It's OK to fall through to the return below.
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 becomes unnecessary.
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -1396,6 +1396,10 @@ namespace Napi { | |||
| RangeError(napi_env env, napi_value value); | ||||
| }; | ||||
|
|
||||
| struct Zombie { | ||||
| bool isZombie = false; | ||||
| }; | ||||
|
|
||||
|
||||
| class CallbackInfo { | ||||
| public: | ||||
| CallbackInfo(napi_env env, napi_callback_info info); | ||||
|
|
@@ -1414,6 +1418,8 @@ namespace Napi { | |||
| void* Data() const; | ||||
| void SetData(void* data); | ||||
|
|
||||
| Zombie* zombie; | ||||
|
||||
| Zombie* zombie; |
Let's not expose more public fields. We can do a ping-pong with SetData() and Data().
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -172,11 +172,30 @@ class Test : public Napi::ObjectWrap<Test> { | |
|
|
||
| std::string Test::s_staticMethodText; | ||
|
|
||
| #ifdef NAPI_CPP_EXCEPTIONS | ||
|
||
| class TestConstructorExceptions : public Napi::ObjectWrap<TestConstructorExceptions> { | ||
| public: | ||
| TestConstructorExceptions(const Napi::CallbackInfo& info) : | ||
| Napi::ObjectWrap<TestConstructorExceptions>(info) { | ||
| throw Napi::Error::New(info.Env(), "Constructor exceptions should not cause v8 GC to fail"); | ||
| } | ||
|
|
||
| static void Initialize(Napi::Env env, Napi::Object exports) { | ||
| exports.Set("TestConstructorExceptions", DefineClass(env, "TestConstructorExceptions", {})); | ||
| } | ||
| }; | ||
| #endif | ||
|
|
||
|
|
||
| Napi::Object InitObjectWrap(Napi::Env env) { | ||
| testStaticContextRef = Napi::Persistent(Napi::Object::New(env)); | ||
| testStaticContextRef.SuppressDestruct(); | ||
|
|
||
| Napi::Object exports = Napi::Object::New(env); | ||
| Test::Initialize(env, exports); | ||
|
|
||
| #ifdef NAPI_CPP_EXCEPTIONS | ||
| TestConstructorExceptions::Initialize(env, exports); | ||
| #endif | ||
|
Comment on lines
+202
to
+204
Contributor
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 initialization should be unconditional. |
||
| return exports; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -256,9 +256,21 @@ const test = (binding) => { | |||||||
| testFinalize(clazz); | ||||||||
| }; | ||||||||
|
|
||||||||
| const testConstructorExceptions = () => { | ||||||||
| const TestConstructorExceptions = binding.objectwrap.TestConstructorExceptions; | ||||||||
| if (TestConstructorExceptions) { | ||||||||
| console.log("Runnig test testConstructorExceptions"); | ||||||||
| assert.throws(() => { new TestConstructorExceptions(); }); | ||||||||
| global.gc(); | ||||||||
| console.log("Test testConstructorExceptions complete"); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| // `Test` is needed for accessing exposed symbols | ||||||||
| testObj(new Test(), Test); | ||||||||
| testClass(Test); | ||||||||
|
|
||||||||
| testConstructorExceptions(); | ||||||||
| } | ||||||||
|
|
||||||||
| test(require(`./build/${buildType}/binding.node`)); | ||||||||
|
Contributor
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.
Suggested change
Please test both scenarios! |
||||||||
|
|
||||||||
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.
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.
We can declare
in the private section of
ObjectWrap<T>.