diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 2c0fa6fac..82a87191f 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -22,19 +22,31 @@ Value TestFunction(const CallbackInfo& info) { } Void Init(Env env) { - // Accessor - PropertyDescriptor pd1 = PropertyDescriptor::Accessor("pd1", TestGetter); - PropertyDescriptor pd2 = PropertyDescriptor::Accessor("pd2", TestGetter, TestSetter); - - // Function - PropertyDescriptor pd3 = PropertyDescriptor::Function("function", TestFunction); - - // Value - Boolean true_bool = Boolean::New(env, true); - PropertyDescriptor pd4 = PropertyDescriptor::Value("boolean value", TestFunction, napi_writable); - - // Assign to an Object + // Create an object. Object obj = Object::New(env); + + // Accessor + PropertyDescriptor pd1 = PropertyDescriptor::Accessor(env, + obj, + "pd1", + TestGetter); + PropertyDescriptor pd2 = PropertyDescriptor::Accessor(env, + obj, + "pd2", + TestGetter, + TestSetter); + // Function + PropertyDescriptor pd3 = PropertyDescriptor::Function(env, + "function", + TestFunction); + // Value + Boolean true_bool = Boolean::New(env, true); + PropertyDescriptor pd4 = + PropertyDescriptor::Value("boolean value", + Napi::Boolean::New(env, true), + napi_writable); + + // Assign properties to the object. obj.DefineProperties({pd1, pd2, pd3, pd4}); } ``` @@ -71,6 +83,32 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` +**This signature is deprecated. It will result in a memory leak if used.** + +```cpp +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( + Napi::Env env, + Napi::Object object, + ___ name, + Getter getter, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] env`: The environemnt in which to create this accessor. +* `[in] object`: The object on which the accessor will be defined. +* `[in] name`: The name used for the getter function. +* `[in] getter`: A getter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a `Napi::PropertyDescriptor` that contains a `Getter` accessor. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `Napi::Name` + ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, Getter getter, @@ -93,6 +131,34 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` +**This signature is deprecated. It will result in a memory leak if used.** + +```cpp +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( + Napi::Env env, + Napi::Object object, + ___ name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] env`: The environemnt in which to create this accessor. +* `[in] object`: The object on which the accessor will be defined. +* `[in] name`: The name of the getter and setter function. +* `[in] getter`: The getter function. +* `[in] setter`: The setter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a `Napi::PropertyDescriptor` that contains a `Getter` and `Setter` function. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `Napi::Name` + ### Function ```cpp @@ -115,6 +181,30 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` +**This signature is deprecated. It will result in a memory leak if used.** + +```cpp +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( + Napi::Env env, + ___ name, + Callable cb, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] env`: The environemnt in which to create this accessor. +* `[in] name`: The name of the Callable function. +* `[in] cb`: The function +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a `Napi::PropertyDescriptor` that contains a callable `Napi::Function`. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `Napi::Name` + ### Value ```cpp diff --git a/napi-inl.deprecated.h b/napi-inl.deprecated.h new file mode 100644 index 000000000..d00174357 --- /dev/null +++ b/napi-inl.deprecated.h @@ -0,0 +1,192 @@ +#ifndef SRC_NAPI_INL_DEPRECATED_H_ +#define SRC_NAPI_INL_DEPRECATED_H_ + +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +inline PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, nullptr }); + + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, nullptr }); + + return PropertyDescriptor({ + nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::AccessorCallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, setter }); + + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::AccessorCallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, setter }); + + return PropertyDescriptor({ + nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ cb, nullptr }); + + return PropertyDescriptor({ + utf8name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ cb, nullptr }); + + return PropertyDescriptor({ + nullptr, + name, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Function(nameValue, cb, attributes, data); +} + +#endif // !SRC_NAPI_INL_DEPRECATED_H_ diff --git a/napi-inl.h b/napi-inl.h index a4b1d426b..1ce27b264 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -60,6 +60,37 @@ namespace details { } \ } while (0) +template +static inline napi_status AttachData(napi_env env, + napi_value obj, + FreeType* data) { + napi_value symbol, external; + napi_status status = napi_create_symbol(env, nullptr, &symbol); + if (status == napi_ok) { + status = napi_create_external(env, + data, + [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }, + nullptr, + &external); + if (status == napi_ok) { + napi_property_descriptor desc = { + nullptr, + symbol, + nullptr, + nullptr, + nullptr, + external, + napi_default, + nullptr + }; + status = napi_define_properties(env, obj, 1, &desc); + } + } + return status; +} + // For use in JS to C++ callback wrappers to catch any Napi::Error exceptions // and rethrow them as JavaScript exceptions before returning from the callback. template @@ -162,6 +193,10 @@ struct AccessorCallbackData { } // namespace details +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED +# include "napi-inl.deprecated.h" +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + //////////////////////////////////////////////////////////////////////////////// // Module registration //////////////////////////////////////////////////////////////////////////////// @@ -1587,6 +1622,22 @@ inline const T* TypedArrayOf::Data() const { // Function class //////////////////////////////////////////////////////////////////////////////// +template +static inline napi_status +CreateFunction(napi_env env, + const char* utf8name, + napi_callback cb, + CbData* data, + napi_value* result) { + napi_status status = + napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); + if (status == napi_ok) { + status = Napi::details::AttachData(env, *result, data); + } + + return status; +} + template inline Function Function::New(napi_env env, Callable cb, @@ -1594,12 +1645,14 @@ inline Function Function::New(napi_env env, void* data) { typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ cb, data }); napi_value value; - napi_status status = napi_create_function( - env, utf8name, NAPI_AUTO_LENGTH, CbData::Wrapper, callbackData, &value); + napi_status status = CreateFunction(env, + utf8name, + CbData::Wrapper, + callbackData, + &value); NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, value); } @@ -2527,14 +2580,18 @@ inline void CallbackInfo::SetData(void* data) { template inline PropertyDescriptor -PropertyDescriptor::Accessor(const char* utf8name, +PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, Getter getter, napi_property_attributes attributes, void* /*data*/) { typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, nullptr }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ utf8name, nullptr, @@ -2548,22 +2605,28 @@ PropertyDescriptor::Accessor(const char* utf8name, } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, Getter getter, napi_property_attributes attributes, void* data) { - return Accessor(utf8name.c_str(), getter, attributes, data); + return Accessor(env, object, utf8name.c_str(), getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + Name name, Getter getter, napi_property_attributes attributes, void* /*data*/) { typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, nullptr }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ nullptr, name, @@ -2576,25 +2639,20 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, }); } -template -inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, - Getter getter, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); -} - template -inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, Getter getter, Setter setter, napi_property_attributes attributes, void* /*data*/) { typedef details::AccessorCallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, setter }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ utf8name, nullptr, @@ -2608,24 +2666,30 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, Getter getter, Setter setter, napi_property_attributes attributes, void* data) { - return Accessor(utf8name.c_str(), getter, setter, attributes, data); + return Accessor(env, object, utf8name.c_str(), getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + Name name, Getter getter, Setter setter, napi_property_attributes attributes, void* /*data*/) { typedef details::AccessorCallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, setter }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ nullptr, name, @@ -2638,77 +2702,51 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, }); } -template -inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Accessor(nameValue, getter, setter, attributes, data); -} - template -inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, +inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, + const char* utf8name, Callable cb, napi_property_attributes attributes, - void* /*data*/) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ cb, nullptr }); - + void* data) { return PropertyDescriptor({ utf8name, nullptr, - CbData::Wrapper, nullptr, nullptr, nullptr, + Napi::Function::New(env, cb, utf8name, data), attributes, - callbackData + nullptr }); } template -inline PropertyDescriptor PropertyDescriptor::Function(const std::string& utf8name, +inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, + const std::string& utf8name, Callable cb, napi_property_attributes attributes, void* data) { - return Function(utf8name.c_str(), cb, attributes, data); + return Function(env, utf8name.c_str(), cb, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, +inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, + Name name, Callable cb, napi_property_attributes attributes, - void* /*data*/) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ cb, nullptr }); - + void* data) { return PropertyDescriptor({ nullptr, name, - CbData::Wrapper, nullptr, nullptr, nullptr, + Napi::Function::New(env, cb, nullptr, data), attributes, - callbackData + nullptr }); } -template -inline PropertyDescriptor PropertyDescriptor::Function(Name name, - Callable cb, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Function(nameValue, cb, attributes, data); -} - inline PropertyDescriptor PropertyDescriptor::Value(const char* utf8name, napi_value value, napi_property_attributes attributes) { @@ -2777,20 +2815,138 @@ inline T* ObjectWrap::Unwrap(Object wrapper) { return unwrapped; } +template +inline Function +ObjectWrap::DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data) { + napi_status status; + + // Before defining the class we can replace static method property descriptors + // with value property descriptors such that the value is a function-valued + // `napi_value` created with `CreateFunction()`. Note that we have to break + // constness to do this. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + napi_property_descriptor* prop = + const_cast(&props[index]); + if (prop->method == T::StaticMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props, + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + // + // TODO: On some engines it might be possible to detach instance methods from + // the prototype. This would mean that the detached prototype method would + // become a plain function and so its data should be associated with itself + // rather than with the class. If this is the case then in this loop, and for + // prototype methods, we should retrieve the `napi_value` representing the + // resulting function and attach the data to *it* rather than the class. + // + // IOW if in JavaScript one does this, + // + // let MyClass = binding.defineMyClass(); + // const someMethod = MyClass.prototype.someMethod; + // MyClass = null; + // global.gc(); + // someMethod(); + // + // then the class, onto which we attached the data, would be gone, and the + // data that will be passed to the native implementation of `someMethod()` + // would be stale and would cause a segfault if accessed, and so in the + // following loop, we may have to + // + // napi_get_named_property(env, value, "prototype", &proto); + // once at the top, and then, for each property, if it is an instance method, + // napi_get_named_property(env, value, prop->utf8name, &proto_method); + // or + // napi_get_property(env, prop->name, &proto_method); + // and then + // Napi::details::AttachData(env, + // proto_method, + // static_cast<...*>(prop->data)); + // + // instead of attaching the data to the class. The downside is that all this + // retrieving of prototype properties would be very expensive. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else if (prop->getter == T::InstanceGetterCallbackWrapper || + prop->setter == T::InstanceSetterCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else if (prop->method != nullptr && !(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } + } + } + + return Function(env, value); +} + template inline Function ObjectWrap::DefineClass( Napi::Env env, const char* utf8name, const std::initializer_list>& properties, void* data) { - napi_value value; - napi_status status = napi_define_class( - env, utf8name, NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, data, properties.size(), - reinterpret_cast(properties.begin()), &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - return Function(env, value); + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); } template @@ -2799,14 +2955,11 @@ inline Function ObjectWrap::DefineClass( const char* utf8name, const std::vector>& properties, void* data) { - napi_value value; - napi_status status = napi_define_class( - env, utf8name, NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, data, properties.size(), - reinterpret_cast(properties.data()), &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - return Function(env, value); + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); } template @@ -2815,7 +2968,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2832,7 +2984,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2849,7 +3000,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2866,7 +3016,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2884,7 +3033,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticAccessorCallbackData* callbackData = new StaticAccessorCallbackData({ getter, setter, data }); @@ -2904,7 +3052,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticAccessorCallbackData* callbackData = new StaticAccessorCallbackData({ getter, setter, data }); @@ -2923,7 +3070,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceVoidMethodCallbackData* callbackData = new InstanceVoidMethodCallbackData({ method, data}); @@ -2941,7 +3087,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2958,7 +3103,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceVoidMethodCallbackData* callbackData = new InstanceVoidMethodCallbackData({ method, data}); @@ -2976,7 +3120,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2994,7 +3137,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceAccessorCallbackData* callbackData = new InstanceAccessorCallbackData({ getter, setter, data }); @@ -3014,7 +3156,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceAccessorCallbackData* callbackData = new InstanceAccessorCallbackData({ getter, setter, data }); diff --git a/napi.h b/napi.h index 2e3753091..5dba128e4 100644 --- a/napi.h +++ b/napi.h @@ -1298,6 +1298,7 @@ namespace Napi { class PropertyDescriptor { public: +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED template static PropertyDescriptor Accessor(const char* utf8name, Getter getter, @@ -1362,6 +1363,71 @@ namespace Napi { Callable cb, napi_property_attributes attributes = napi_default, void* data = nullptr); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Napi::Env env, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Napi::Env env, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Napi::Env env, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor Value(const char* utf8name, napi_value value, napi_property_attributes attributes = napi_default); @@ -1529,6 +1595,11 @@ namespace Napi { static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); static void FinalizeCallback(napi_env env, void* data, void* hint); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data = nullptr); template struct MethodCallbackData { diff --git a/test/binding.cc b/test/binding.cc index c2bd101f3..e374ac124 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -28,6 +28,7 @@ Object InitTypedArray(Env env); Object InitObjectWrap(Env env); Object InitObjectReference(Env env); Object InitVersionManagement(Env env); +Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); @@ -56,6 +57,7 @@ Object Init(Env env, Object exports) { exports.Set("objectwrap", InitObjectWrap(env)); exports.Set("objectreference", InitObjectReference(env)); exports.Set("version_management", InitVersionManagement(env)); + exports.Set("thunking_manual", InitThunkingManual(env)); return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 7bff35e8a..1dcea9bd1 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -30,7 +30,8 @@ 'typedarray.cc', 'objectwrap.cc', 'objectreference.cc', - 'version_management.cc' + 'version_management.cc', + 'thunking_manual.cc', ], 'conditions': [ ['NAPI_VERSION!=""', { 'defines': ['NAPI_VERSION=<@(NAPI_VERSION)'] } ] @@ -39,6 +40,7 @@ 'dependencies': ["(); String nameType = info[1].As(); + Env env = info.Env(); - Boolean trueValue = Boolean::New(info.Env(), true); + Boolean trueValue = Boolean::New(env, true); if (nameType.Utf8Value() == "literal") { obj.DefineProperties({ - PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), - PropertyDescriptor::Accessor("readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, "readonlyAccessor", TestGetter), + PropertyDescriptor::Accessor(env, obj, "readwriteAccessor", TestGetter, TestSetter), PropertyDescriptor::Value("readonlyValue", trueValue), PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), PropertyDescriptor::Value("configurableValue", trueValue, napi_configurable), - PropertyDescriptor::Function("function", TestFunction), + PropertyDescriptor::Function(env, "function", TestFunction), }); } else if (nameType.Utf8Value() == "string") { // VS2013 has lifetime issues when passing temporary objects into the constructor of another @@ -82,30 +83,30 @@ void DefineProperties(const CallbackInfo& info) { std::string str7("function"); obj.DefineProperties({ - PropertyDescriptor::Accessor(str1, TestGetter), - PropertyDescriptor::Accessor(str2, TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, str1, TestGetter), + PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), PropertyDescriptor::Value(str3, trueValue), PropertyDescriptor::Value(str4, trueValue, napi_writable), PropertyDescriptor::Value(str5, trueValue, napi_enumerable), PropertyDescriptor::Value(str6, trueValue, napi_configurable), - PropertyDescriptor::Function(str7, TestFunction), + PropertyDescriptor::Function(env, str7, TestFunction), }); } else if (nameType.Utf8Value() == "value") { obj.DefineProperties({ - PropertyDescriptor::Accessor( - Napi::String::New(info.Env(), "readonlyAccessor"), TestGetter), - PropertyDescriptor::Accessor( - Napi::String::New(info.Env(), "readwriteAccessor"), TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, + Napi::String::New(env, "readonlyAccessor"), TestGetter), + PropertyDescriptor::Accessor(env, obj, + Napi::String::New(env, "readwriteAccessor"), TestGetter, TestSetter), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "readonlyValue"), trueValue), + Napi::String::New(env, "readonlyValue"), trueValue), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "readwriteValue"), trueValue, napi_writable), + Napi::String::New(env, "readwriteValue"), trueValue, napi_writable), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "enumerableValue"), trueValue, napi_enumerable), + Napi::String::New(env, "enumerableValue"), trueValue, napi_enumerable), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "configurableValue"), trueValue, napi_configurable), - PropertyDescriptor::Function( - Napi::String::New(info.Env(), "function"), TestFunction), + Napi::String::New(env, "configurableValue"), trueValue, napi_configurable), + PropertyDescriptor::Function(env, + Napi::String::New(env, "function"), TestFunction), }); } } diff --git a/test/objectwrap.js b/test/objectwrap.js index 72805000d..1f888234d 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -207,4 +207,4 @@ const test = (binding) => { } test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); \ No newline at end of file +test(require(`./build/${buildType}/binding_noexcept.node`)); diff --git a/test/thunking_manual.cc b/test/thunking_manual.cc new file mode 100644 index 000000000..d52302ea3 --- /dev/null +++ b/test/thunking_manual.cc @@ -0,0 +1,140 @@ +#include + +// The formulaic comment below should accompany any code that results in an +// internal piece of heap data getting created, because each such piece of heap +// data must be attached to an object by way of a deleter which gets called when +// the object gets garbage-collected. +// +// At the very least, you can add a fprintf(stderr, ...) to the deleter in +// napi-inl.h and then count the number of times the deleter prints by running +// node --expose-gc test/thunking_manual.js and counting the number of prints +// between the two rows of dashes. That number should coincide with the number +// of formulaic comments below. +// +// Note that currently this result can only be achieved with node-chakracore, +// because V8 does not garbage-collect classes. + +static Napi::Value TestMethod(const Napi::CallbackInfo& /*info*/) { + return Napi::Value(); +} + +static Napi::Value TestGetter(const Napi::CallbackInfo& /*info*/) { + return Napi::Value(); +} + +static void TestSetter(const Napi::CallbackInfo& /*info*/) { +} + +class TestClass : public Napi::ObjectWrap { + public: + TestClass(const Napi::CallbackInfo& info): + ObjectWrap(info) { + } + static Napi::Value TestClassStaticMethod(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + static void TestClassStaticVoidMethod(const Napi::CallbackInfo& /*info*/) { + } + + Napi::Value TestClassInstanceMethod(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + void TestClassInstanceVoidMethod(const Napi::CallbackInfo& /*info*/) { + } + + Napi::Value TestClassInstanceGetter(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + void TestClassInstanceSetter(const Napi::CallbackInfo& /*info*/, + const Napi::Value& /*new_value*/) { + } + + static Napi::Function NewClass(Napi::Env env) { + return DefineClass(env, "TestClass", { + // Make sure to check that the deleter gets called. + StaticMethod("staticMethod", TestClassStaticMethod), + // Make sure to check that the deleter gets called. + StaticMethod("staticVoidMethod", TestClassStaticVoidMethod), + // Make sure to check that the deleter gets called. + StaticMethod(Napi::Symbol::New(env, "staticMethod"), + TestClassStaticMethod), + // Make sure to check that the deleter gets called. + StaticMethod(Napi::Symbol::New(env, "staticVoidMethod"), + TestClassStaticVoidMethod), + // Make sure to check that the deleter gets called. + InstanceMethod("instanceMethod", &TestClass::TestClassInstanceMethod), + // Make sure to check that the deleter gets called. + InstanceMethod("instanceVoidMethod", + &TestClass::TestClassInstanceVoidMethod), + // Make sure to check that the deleter gets called. + InstanceMethod(Napi::Symbol::New(env, "instanceMethod"), + &TestClass::TestClassInstanceMethod), + // Make sure to check that the deleter gets called. + InstanceMethod(Napi::Symbol::New(env, "instanceVoidMethod"), + &TestClass::TestClassInstanceVoidMethod), + // Make sure to check that the deleter gets called. + InstanceAccessor("instanceAccessor", + &TestClass::TestClassInstanceGetter, + &TestClass::TestClassInstanceSetter) + }); + } +}; + +static Napi::Value CreateTestObject(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Object item = Napi::Object::New(env); + + // Make sure to check that the deleter gets called. + item["testMethod"] = Napi::Function::New(env, TestMethod, "testMethod"); + + item.DefineProperties({ + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + "accessor_1", + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + std::string("accessor_1_std_string"), + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + Napi::String::New(info.Env(), + "accessor_1_js_string"), + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + "accessor_2", + TestGetter, + TestSetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + std::string("accessor_2_std_string"), + TestGetter, + TestSetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + Napi::String::New(env, + "accessor_2_js_string"), + TestGetter, + TestSetter), + Napi::PropertyDescriptor::Value("TestClass", TestClass::NewClass(env)), + }); + + return item; +} + +Napi::Object InitThunkingManual(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + exports["createTestObject"] = + Napi::Function::New(env, CreateTestObject, "createTestObject"); + return exports; +} diff --git a/test/thunking_manual.js b/test/thunking_manual.js new file mode 100644 index 000000000..22fb8877d --- /dev/null +++ b/test/thunking_manual.js @@ -0,0 +1,18 @@ +// Flags: --expose-gc +'use strict'; +const buildType = 'Debug'; +const assert = require('assert'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + console.log("Thunking: Performing initial GC"); + global.gc(); + console.log("Thunking: Creating test object"); + let object = binding.thunking_manual.createTestObject(); + object = null; + console.log("Thunking: About to GC\n--------"); + global.gc(); + console.log("--------\nThunking: GC complete"); +}