Repository: nodejs/node-addon-api

Issue #599: GC Crash With ObjectWrap + Exceptions
Created: 2019-11-12T20:43:07Z
I am seeing crashes in the V8 GC when I have ObjectWraps that throw in subclass constructors. Example:
class MyObject : public ObjectWrap<MyObject> { MyObject(const CallbackInfo& info) : ObjectWrap<MyObject>(info) { throw Error::New(info.Env(), "something exceptional happened!"); } }
The ObjectWrap constructor will create a napi_ref for the object, associate this pointer and register a finalizer. If the subclass throws, the C++ runtime invalidates the this pointer. However, the pointer is still registered with the napi_ref. When the GC gets to the object, the finalizer tries to delete the invalid this and the system may crash.

Duplicate issue #635: Exceptions in constructors causes v8 GC to fail with access violations
Created: 2019-12-16T18:40:26Z
Hey folks,
If an exception is thrown from inside a constructor it seems it causes v8 GC to fail with an access violation in this case below. I am starting to think that this is caused by ObjectWrap<T>::FinalizeCallback being called when it shouldn't be.
Here is the example code if you want to try it out quickly blagoev/node-addon-examples@351752f
The highlights are these.
There is a static factory function on the exposed object
Napi::Value create(const Napi::CallbackInfo& info) {
	Napi::EscapableHandleScope scope(info.Env());
	Napi::Value val = MyObject::constructor.New({}); //this will throw an exception

	return scope.Escape(info.Env().Undefined());
}
then the ctor throws some error
MyObject::MyObject(const Napi::CallbackInfo& info)
	: Napi::ObjectWrap<MyObject>(info) {
	Napi::Env env = info.Env();
	Napi::HandleScope scope(env);
	throw Napi::Error::New(env, "TEST");
......
and we have this private field in the class
struct Data {
};

class MyObject : public Napi::ObjectWrap<MyObject> {
private:
	std::shared_ptr<Data> m_data;
....
and this is the JS code that tries to call that
var addon = require('bindings')('addon');
try {
    var obj = addon.MyObject.create();
    }
    catch (e) {
        console.error("Incorrect expected exception message:" + e.message);
    }
  
    global.gc()
    global.gc()
    global.gc()
then there is an exception here
>	addon.node!std::_Ref_count_base::_Decref() Line 845	C++
 	addon.node!std::_Ptr_base<Data>::_Decref() Line 1122	C++
 	addon.node!std::shared_ptr<Data>::~shared_ptr<Data>() Line 1403	C++
 	addon.node!MyObject::~MyObject()	C++
 	addon.node!MyObject::`scalar deleting destructor'(unsigned int)	C++
 	addon.node!Napi::ObjectWrap<MyObject>::FinalizeCallback(napi_env__ * __formal, void * data, void * __formal) Line 3433	C++
 	node.exe!`anonymous namespace'::v8impl::Reference::SecondPassCallback(const v8::WeakCallbackInfo<`anonymous namespace'::v8impl::Reference> & data) Line 496	C++
 	[Inline Frame] node.exe!v8::internal::GlobalHandles::PendingPhantomCallback::Invoke(v8::internal::Isolate *) Line 905	C++
 	node.exe!v8::internal::GlobalHandles::InvokeSecondPassPhantomCallbacks(std::vector<v8::internal::GlobalHandles::PendingPhantomCallback,std::allocator<v8::internal::GlobalHandles::PendingPhantomCallback>> * callbacks, v8::internal::Isolate * isolate) Line 772	C++
 	node.exe!v8::internal::GlobalHandles::DispatchPendingPhantomCallbacks(bool synchronous_second_pass) Line 881	C++
 	node.exe!v8::internal::GlobalHandles::PostGarbageCollectionProcessing(v8::internal::GarbageCollector collector, const v8::GCCallbackFlags gc_callback_flags) Line 930	C++
 	node.exe!v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector collector, const v8::GCCallbackFlags gc_callback_flags) Line 1775	C++
 	node.exe!v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace space, v8::internal::GarbageCollectionReason gc_reason, const v8::GCCallbackFlags gc_callback_flags) Line 1392	C++
 	[Inline Frame] node.exe!v8::internal::Heap::CollectAllGarbage(int) Line 1156	C++
 	node.exe!v8::Isolate::RequestGarbageCollectionForTesting(v8::Isolate::GarbageCollectionType type) Line 8273	C++
 	node.exe!v8::internal::GCExtension::GC(const v8::FunctionCallbackInfo<v8::Value> & args) Line 26	C++
 	node.exe!v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo * handler) Line 95	C++
 	node.exe!v8::internal::`anonymous namespace'::HandleApiCallHelper<0>(v8::internal::Isolate * isolate, v8::internal::Handle<v8::internal::HeapObject> new_target, v8::internal::Handle<v8::internal::HeapObject> fun_data, v8::internal::Handle<v8::internal::FunctionTemplateInfo> receiver, v8::internal::Handle<v8::internal::Object> args, v8::internal::BuiltinArguments) Line 111	C++
 	node.exe!v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments args, v8::internal::Isolate * isolate) Line 139	C++
 	node.exe!v8::internal::Builtin_HandleApiCall(int args_length, v8::internal::Object * * args_object, v8::internal::Isolate * isolate) Line 127	C++


Note: you need to expose v8 GC for that to work with
node --expose_gc addon.js

It seems to me this is cause the ObjectWrap<T>::FinalizeCallback is called but the native object did not get constructed correctly (ie the native object's ctor did not complete). Or is it that my assumption is wrong.
cheers

Closing comment (2019-12-25T04:05:02Z): IINM this is a duplicate of #599.