-
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 all 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 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2711,6 +2711,27 @@ inline Object FunctionReference::New(const std::vector<napi_value>& args) const | |||||||||||
return scope.Escape(Value().New(args)).As<Object>(); | ||||||||||||
} | ||||||||||||
|
||||||||||||
class FinalizerHint { | ||||||||||||
private: | ||||||||||||
FinalizerHint() {} | ||||||||||||
public: | ||||||||||||
bool shouldFinalize; | ||||||||||||
|
||||||||||||
static void Init(CallbackInfo& info, bool shouldFinalize = true) { | ||||||||||||
info.finalizerHint = new FinalizerHint(); | ||||||||||||
info.finalizerHint->shouldFinalize = shouldFinalize; | ||||||||||||
} | ||||||||||||
|
||||||||||||
static void Clear(CallbackInfo& info) { | ||||||||||||
info.finalizerHint = nullptr; | ||||||||||||
} | ||||||||||||
|
||||||||||||
static FinalizerHint* Get(const CallbackInfo& info) { | ||||||||||||
return info.finalizerHint; | ||||||||||||
} | ||||||||||||
}; | ||||||||||||
|
||||||||||||
|
||||||||||||
//////////////////////////////////////////////////////////////////////////////// | ||||||||||||
// CallbackInfo class | ||||||||||||
//////////////////////////////////////////////////////////////////////////////// | ||||||||||||
|
@@ -3120,7 +3141,12 @@ 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); | ||||||||||||
FinalizerHint* finalizerHint = FinalizerHint::Get(callbackInfo); | ||||||||||||
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
The hint is not optional. It should die if it's absent. |
||||||||||||
status = napi_wrap(env, wrapper, instance, FinalizeCallback, (void*)finalizerHint, &ref); | ||||||||||||
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. Please avoid C-style casts! |
||||||||||||
if (status != napi_ok && finalizerHint != nullptr) { | ||||||||||||
FinalizerHint::Clear(const_cast<Napi::CallbackInfo&>(callbackInfo)); | ||||||||||||
delete finalizerHint; | ||||||||||||
} | ||||||||||||
NAPI_THROW_IF_FAILED_VOID(env, status); | ||||||||||||
gabrielschulhof marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
|
||||||||||||
Reference<Object>* instanceRef = instance; | ||||||||||||
|
@@ -3697,10 +3723,31 @@ 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); | ||||||||||||
FinalizerHint::Init(callbackInfo); | ||||||||||||
#ifdef NAPI_CPP_EXCEPTIONS | ||||||||||||
try { | ||||||||||||
instance = new T(callbackInfo); | ||||||||||||
return callbackInfo.This(); | ||||||||||||
} | ||||||||||||
catch (...) { | ||||||||||||
FinalizerHint* finalizerHint = FinalizerHint::Get(callbackInfo); | ||||||||||||
if (finalizerHint != nullptr) { | ||||||||||||
finalizerHint->shouldFinalize = false; | ||||||||||||
} | ||||||||||||
throw; | ||||||||||||
} | ||||||||||||
#else | ||||||||||||
instance = new T(callbackInfo); | ||||||||||||
if (callbackInfo.Env().IsExceptionPending()) { | ||||||||||||
FinalizerHint* finalizerHint = FinalizerHint::Get(callbackInfo); | ||||||||||||
if (finalizerHint != 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. This check should be a hard check. If |
||||||||||||
finalizerHint->shouldFinalize = false; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
return callbackInfo.This(); | ||||||||||||
#endif | ||||||||||||
}); | ||||||||||||
|
||||||||||||
return wrapper; | ||||||||||||
|
@@ -3823,7 +3870,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) { | ||||||||||||
FinalizerHint* finalizerHint = (FinalizerHint*)hint; | ||||||||||||
bool shouldFinalize = finalizerHint->shouldFinalize; | ||||||||||||
delete finalizerHint; | ||||||||||||
if (!shouldFinalize) { | ||||||||||||
return; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
T* instance = reinterpret_cast<T*>(data); | ||||||||||||
instance->Finalize(Napi::Env(env)); | ||||||||||||
delete instance; | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1397,6 +1397,7 @@ namespace Napi { | |
}; | ||
|
||
class CallbackInfo { | ||
friend class FinalizerHint; | ||
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. You need to forward-declare this class at the top of the file. |
||
public: | ||
CallbackInfo(napi_env env, napi_callback_info info); | ||
~CallbackInfo(); | ||
|
@@ -1424,6 +1425,7 @@ namespace Napi { | |
napi_value _staticArgs[6]; | ||
napi_value* _dynamicArgs; | ||
void* _data; | ||
FinalizerHint* finalizerHint; | ||
}; | ||
|
||
class PropertyDescriptor { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -172,11 +172,35 @@ class Test : public Napi::ObjectWrap<Test> { | |
|
||
std::string Test::s_staticMethodText; | ||
|
||
|
||
class TestConstructorExceptions : public Napi::ObjectWrap<TestConstructorExceptions> { | ||
public: | ||
TestConstructorExceptions(const Napi::CallbackInfo& info) : | ||
Napi::ObjectWrap<TestConstructorExceptions>(info) { | ||
#ifdef NAPI_CPP_EXCEPTIONS | ||
throw Napi::Error::New(info.Env(), "Constructor exceptions should not cause v8 GC to fail"); | ||
#else | ||
Napi::Error::New(info.Env(), "Constructor exceptions should not cause v8 GC to fail").ThrowAsJavaScriptException(); | ||
return; | ||
#endif | ||
} | ||
|
||
static void Initialize(Napi::Env env, Napi::Object exports) { | ||
exports.Set("TestConstructorExceptions", DefineClass(env, "TestConstructorExceptions", {})); | ||
} | ||
}; | ||
|
||
|
||
|
||
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,19 @@ const test = (binding) => { | |||||||
testFinalize(clazz); | ||||||||
}; | ||||||||
|
||||||||
const testConstructorExceptions = () => { | ||||||||
const TestConstructorExceptions = binding.objectwrap.TestConstructorExceptions; | ||||||||
console.log("Runnig test testConstructorExceptions"); | ||||||||
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
The console log is unnecessary. |
||||||||
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.
We don't use non-const references in Node.js. Only const references and pointers. So, please turn this into a pointer!