diff --git a/napi-inl.h b/napi-inl.h index 457396342..b86170661 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -555,13 +555,13 @@ inline std::string String::Utf8Value() const { inline std::u16string String::Utf16Value() const { size_t length; napi_status status = napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); - NAPI_THROW_IF_FAILED(_env, status, u""); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); std::u16string value; value.reserve(length + 1); value.resize(length); status = napi_get_value_string_utf16(_env, _value, &value[0], value.capacity(), nullptr); - NAPI_THROW_IF_FAILED(_env, status, u""); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); return value; } @@ -1490,7 +1490,7 @@ inline Error& Error::operator =(Error&& other) { return *this; } -inline Error::Error(const Error& other) : Error(other.Env(), other.Value()) { +inline Error::Error(const Error& other) : ObjectReference(other) { } inline Error& Error::operator =(Error& other) { @@ -1644,6 +1644,22 @@ inline Reference& Reference::operator =(Reference&& other) { return *this; } +template +inline Reference::Reference(const Reference& other) { + _env = other.Env(); + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + // Copying is a limited scenario (currently only used for Error object) and always creates a + // strong reference to the given value even if the incoming reference is weak. + napi_status status = napi_create_reference(_env, value, 1, &_ref); + + // TODO - Switch to napi_fatal_error() once it exists. + assert(status == napi_ok); + } +} + template inline Reference::operator napi_ref() const { return _ref; @@ -1778,6 +1794,10 @@ inline ObjectReference& ObjectReference::operator =(ObjectReference&& other) { return *this; } +inline ObjectReference::ObjectReference(const ObjectReference& other) + : Reference(other) { +} + inline Napi::Value ObjectReference::Get(const char* utf8name) const { EscapableHandleScope scope(_env); return scope.Escape(Value().Get(utf8name)); @@ -2697,11 +2717,11 @@ inline FunctionReference& AsyncWorker::Callback() { } inline void AsyncWorker::OnOK() { - _callback.MakeCallback(_receiver.Value(), {}); + _callback.MakeCallback(_receiver.Value(), std::initializer_list{}); } inline void AsyncWorker::OnError(const Error& e) { - _callback.MakeCallback(_receiver.Value(), { e.Value() }); + _callback.MakeCallback(_receiver.Value(), std::initializer_list{ e.Value() }); } inline void AsyncWorker::SetError(const std::string& error) { diff --git a/napi.h b/napi.h index c2b943ca3..07cdf82e8 100644 --- a/napi.h +++ b/napi.h @@ -7,6 +7,20 @@ #include #include +// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known good version) +#if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 +#define NAPI_HAS_CONSTEXPR 1 +#endif + +// VS2013 does not support char16_t literal strings, so we'll work around it using wchar_t strings +// and casting them. This is safe as long as the character sizes are the same. +#if defined(_MSC_VER) && _MSC_VER <= 1800 +static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16_t and wchar_t"); +#define NAPI_WIDE_TEXT(x) reinterpret_cast(L ## x) +#else +#define NAPI_WIDE_TEXT(x) u ## x +#endif + // If C++ exceptions are not explicitly enabled or disabled, enable them // if exceptions were enabled in the compiler settings. #if !defined(NAPI_CPP_EXCEPTIONS) && !defined(NAPI_DISABLE_CPP_EXCEPTIONS) @@ -60,7 +74,7 @@ namespace Napi { typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values /// Defines the signature of a N-API C++ module's registration callback (init) function. - typedef void ModuleRegisterCallback(Env env, Object exports, Object module); + typedef void (*ModuleRegisterCallback)(Env env, Object exports, Object module); /// Environment for N-API values and operations. /// @@ -671,7 +685,11 @@ namespace Napi { static const napi_typedarray_type unknown_array_type = static_cast(-1); template - static constexpr napi_typedarray_type TypedArrayTypeForPrimitiveType() { + static +#if defined(NAPI_HAS_CONSTEXPR) + constexpr +#endif + napi_typedarray_type TypedArrayTypeForPrimitiveType() { return std::is_same::value ? napi_int8_array : std::is_same::value ? napi_uint8_array : std::is_same::value ? napi_int16_array @@ -701,7 +719,11 @@ namespace Napi { static TypedArrayOf New( napi_env env, ///< N-API environment size_t elementLength, ///< Length of the created array, as a number of elements +#if defined(NAPI_HAS_CONSTEXPR) napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif ///< Type of array, if different from the default array type for the template parameter T. ); @@ -716,7 +738,11 @@ namespace Napi { size_t elementLength, ///< Length of the created array, as a number of elements Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use size_t bufferOffset, ///< Offset into the array buffer where the typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif ///< Type of array, if different from the default array type for the template parameter T. ); @@ -830,7 +856,6 @@ namespace Napi { // A reference can be moved but cannot be copied. Reference(Reference&& other); Reference& operator =(Reference&& other); - Reference(const Reference&) = delete; Reference& operator =(Reference&) = delete; operator napi_ref() const; @@ -855,6 +880,8 @@ namespace Napi { void SuppressDestruct(); protected: + Reference(const Reference&); + /// !cond INTERNAL napi_env _env; napi_ref _ref; @@ -874,7 +901,6 @@ namespace Napi { ObjectReference& operator =(Reference&& other); ObjectReference(ObjectReference&& other); ObjectReference& operator =(ObjectReference&& other); - ObjectReference(const ObjectReference&) = delete; ObjectReference& operator =(ObjectReference&) = delete; Napi::Value Get(const char* utf8name) const; @@ -897,6 +923,9 @@ namespace Napi { void Set(uint32_t index, const std::string& utf8value); void Set(uint32_t index, bool boolValue); void Set(uint32_t index, double numberValue); + + protected: + ObjectReference(const ObjectReference&); }; class FunctionReference: public Reference { diff --git a/test/error.cc b/test/error.cc index ffe1331bb..169c2f2ed 100644 --- a/test/error.cc +++ b/test/error.cc @@ -11,7 +11,7 @@ void DoNotCatch(const CallbackInfo& info) { void ThrowApiError(const CallbackInfo& info) { // Attempting to call an empty function value will throw an API error. - Function(info.Env(), nullptr).Call({}); + Function(info.Env(), nullptr).Call(std::initializer_list{}); } #ifdef NAPI_CPP_EXCEPTIONS diff --git a/test/function.cc b/test/function.cc index 0df54b7a8..805a7515b 100644 --- a/test/function.cc +++ b/test/function.cc @@ -62,7 +62,7 @@ Value CallWithVector(const CallbackInfo& info) { Value CallWithReceiverAndArgs(const CallbackInfo& info) { Function func = info[0].As(); Value receiver = info[1]; - return func.Call(receiver, { info[2], info[3], info[4] }); + return func.Call(receiver, std::initializer_list{ info[2], info[3], info[4] }); } Value CallWithReceiverAndVector(const CallbackInfo& info) { @@ -78,12 +78,12 @@ Value CallWithReceiverAndVector(const CallbackInfo& info) { Value CallWithInvalidReceiver(const CallbackInfo& info) { Function func = info[0].As(); - return func.Call(Value(), {}); + return func.Call(Value(), std::initializer_list{}); } Value CallConstructorWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return func.New({ info[1], info[2], info[3] }); + return func.New(std::initializer_list{ info[1], info[2], info[3] }); } Value CallConstructorWithVector(const CallbackInfo& info) { diff --git a/test/index.js b/test/index.js index 6aa8bdbd5..5a61d6707 100644 --- a/test/index.js +++ b/test/index.js @@ -13,10 +13,15 @@ let testModules = [ ]; if (typeof global.gc === 'function') { + console.log('Starting test suite\n'); + // Requiring each module runs tests in the module. testModules.forEach(name => { + console.log(`Running test '${name}'`); require('./' + name); }); + + console.log('\nAll tests passed!'); } else { // Make it easier to run with the correct (version-dependent) command-line args. const args = [ '--expose-gc', __filename ]; diff --git a/test/name.cc b/test/name.cc index 4474df685..3a296ec58 100644 --- a/test/name.cc +++ b/test/name.cc @@ -3,7 +3,7 @@ using namespace Napi; const char* testValueUtf8 = "123456789"; -const char16_t* testValueUtf16 = u"123456789"; +const char16_t* testValueUtf16 = NAPI_WIDE_TEXT("123456789"); Value EchoString(const CallbackInfo& info) { String value = info[0].As(); diff --git a/test/object.cc b/test/object.cc index bfe58b919..361830117 100644 --- a/test/object.cc +++ b/test/object.cc @@ -39,14 +39,26 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Function("function", TestFunction), }); } else if (nameType.Utf8Value() == "string") { + // VS2013 has lifetime issues when passing temporary objects into the constructor of another + // object. It generates code to destruct the object as soon as the constructor call returns. + // Since this isn't a common case for using std::string objects, I'm refactoring the test to + // work around the issue. + std::string str1("readonlyAccessor"); + std::string str2("readwriteAccessor"); + std::string str3("readonlyValue"); + std::string str4("readwriteValue"); + std::string str5("enumerableValue"); + std::string str6("configurableValue"); + std::string str7("function"); + obj.DefineProperties({ - PropertyDescriptor::Accessor(std::string("readonlyAccessor"), TestGetter), - PropertyDescriptor::Accessor(std::string("readwriteAccessor"), TestGetter, TestSetter), - PropertyDescriptor::Value(std::string("readonlyValue"), trueValue), - PropertyDescriptor::Value(std::string("readwriteValue"), trueValue, napi_writable), - PropertyDescriptor::Value(std::string("enumerableValue"), trueValue, napi_enumerable), - PropertyDescriptor::Value(std::string("configurableValue"), trueValue, napi_configurable), - PropertyDescriptor::Function(std::string("function"), TestFunction), + PropertyDescriptor::Accessor(str1, TestGetter), + PropertyDescriptor::Accessor(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), }); } else if (nameType.Utf8Value() == "value") { obj.DefineProperties({ diff --git a/test/typedarray.cc b/test/typedarray.cc index f005a4013..b556a80a6 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -2,6 +2,16 @@ using namespace Napi; +#if defined(NAPI_HAS_CONSTEXPR) +#define NAPI_TYPEDARRAY_NEW(className, env, length, type) className::New(env, length) +#define NAPI_TYPEDARRAY_NEW_BUFFER(className, env, length, buffer, bufferOffset, type) \ + className::New(env, length, buffer, bufferOffset) +#else +#define NAPI_TYPEDARRAY_NEW(className, env, length, type) className::New(env, length, type) +#define NAPI_TYPEDARRAY_NEW_BUFFER(className, env, length, buffer, bufferOffset, type) \ + className::New(env, length, buffer, bufferOffset, type) +#endif + namespace { Value CreateTypedArray(const CallbackInfo& info) { @@ -11,32 +21,49 @@ Value CreateTypedArray(const CallbackInfo& info) { size_t bufferOffset = info[3].IsUndefined() ? 0 : info[3].As().Uint32Value(); if (arrayType == "int8") { - return buffer.IsUndefined() ? Int8Array::New(info.Env(), length) - : Int8Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Int8Array, info.Env(), length, napi_int8_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, info.Env(), length, buffer, bufferOffset, + napi_int8_array); } else if (arrayType == "uint8") { - return buffer.IsUndefined() ? Uint8Array::New(info.Env(), length) - : Uint8Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Uint8Array, info.Env(), length, napi_uint8_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Uint8Array, info.Env(), length, buffer, bufferOffset, + napi_uint8_array); } else if (arrayType == "uint8_clamped") { - return buffer.IsUndefined() ? Uint8Array::New(info.Env(), length, napi_uint8_clamped_array) - : Uint8Array::New(info.Env(), length, buffer, bufferOffset, napi_uint8_clamped_array); + return buffer.IsUndefined() ? + Uint8Array::New(info.Env(), length, napi_uint8_clamped_array) : + Uint8Array::New(info.Env(), length, buffer, bufferOffset, napi_uint8_clamped_array); } else if (arrayType == "int16") { - return buffer.IsUndefined() ? Int16Array::New(info.Env(), length) - : Int16Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Int16Array, info.Env(), length, napi_int16_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Int16Array, info.Env(), length, buffer, bufferOffset, + napi_int16_array); } else if (arrayType == "uint16") { - return buffer.IsUndefined() ? Uint16Array::New(info.Env(), length) - : Uint16Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Uint16Array, info.Env(), length, napi_uint16_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Uint16Array, info.Env(), length, buffer, bufferOffset, + napi_uint16_array); } else if (arrayType == "int32") { - return buffer.IsUndefined() ? Int32Array::New(info.Env(), length) - : Int32Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Int32Array, info.Env(), length, napi_int32_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Int32Array, info.Env(), length, buffer, bufferOffset, + napi_int32_array); } else if (arrayType == "uint32") { - return buffer.IsUndefined() ? Uint32Array::New(info.Env(), length) - : Uint32Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Uint32Array, info.Env(), length, napi_uint32_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Uint32Array, info.Env(), length, buffer, bufferOffset, + napi_uint32_array); } else if (arrayType == "float32") { - return buffer.IsUndefined() ? Float32Array::New(info.Env(), length) - : Float32Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Float32Array, info.Env(), length, napi_float32_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Float32Array, info.Env(), length, buffer, bufferOffset, + napi_float32_array); } else if (arrayType == "float64") { - return buffer.IsUndefined() ? Float64Array::New(info.Env(), length) - : Float64Array::New(info.Env(), length, buffer, bufferOffset); + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset, + napi_float64_array); } else { Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); return Value(); @@ -44,7 +71,7 @@ Value CreateTypedArray(const CallbackInfo& info) { } Value CreateInvalidTypedArray(const CallbackInfo& info) { - return Int8Array::New(info.Env(), 1, ArrayBuffer(), 0); + return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, info.Env(), 1, ArrayBuffer(), 0, napi_int8_array); } Value GetTypedArrayType(const CallbackInfo& info) {