-
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
fixes v8 GC access violation for zombie objects #638
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 { | ||||||||||||||||
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(); | ||||||||||||||||
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
|
||||||||||||||||
#ifdef NAPI_CPP_EXCEPTIONS | ||||||||||||||||
try { | ||||||||||||||||
instance = new T(callbackInfo); | ||||||||||||||||
return callbackInfo.This(); | ||||||||||||||||
} | ||||||||||||||||
catch (...) { | ||||||||||||||||
callbackInfo.zombie->isZombie = true; | ||||||||||||||||
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
|
||||||||||||||||
throw; | ||||||||||||||||
} | ||||||||||||||||
#else | ||||||||||||||||
instance = new T(callbackInfo); | ||||||||||||||||
if (callbackInfo.Env().IsExceptionPending()) { | ||||||||||||||||
callbackInfo.zombie->isZombie = true; | ||||||||||||||||
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
I guess this part should probably be factored out and a call made to it from above and from here. |
||||||||||||||||
return nullptr; | ||||||||||||||||
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
It's OK to fall through to the return below. |
||||||||||||||||
} | ||||||||||||||||
return callbackInfo.This(); | ||||||||||||||||
#endif | ||||||||||||||||
}); | ||||||||||||||||
|
||||||||||||||||
return wrapper; | ||||||||||||||||
|
@@ -3823,7 +3839,16 @@ inline napi_value ObjectWrap<T>::InstanceSetterCallbackWrapper( | |||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
template <typename T> | ||||||||||||||||
inline void ObjectWrap<T>::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { | ||||||||||||||||
inline void ObjectWrap<T>::FinalizeCallback(napi_env env, void* data, void* hint) { | ||||||||||||||||
if (hint != nullptr) { | ||||||||||||||||
Zombie* zombie = (Zombie*)hint; | ||||||||||||||||
bool isZombie = zombie->isZombie; | ||||||||||||||||
delete zombie; | ||||||||||||||||
if (isZombie) { | ||||||||||||||||
return; | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
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 becomes unnecessary. |
||||||||||||||||
T* instance = reinterpret_cast<T*>(data); | ||||||||||||||||
instance->Finalize(Napi::Env(env)); | ||||||||||||||||
delete instance; | ||||||||||||||||
|
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; | ||||
}; | ||||
|
||||
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 becomes unnecessary. |
||||
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; | ||||
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
Let's not expose more public fields. We can do a ping-pong with |
||||
|
||||
private: | ||||
const size_t _staticArgCount = 6; | ||||
napi_env _env; | ||||
|
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 | ||
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. We should perform this test in both cases – if C++ exceptions are enabled, as well as if they are not enabled. |
||
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
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`)); | ||||||||
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>
.