Skip to content

Commit

Permalink
Add support for VS2013
Browse files Browse the repository at this point in the history
VS2013 issues
* No constexpr support
  - Conditionally use constexpr if the compiler supports it
  - Updated tests to use a macro for this purpose
* Requires copy constructor support for base classes of thrown objects
  - Added a protected copy constructor to ObjectReference and
    Reference<T>
* Incorrect overload resolution for std::initializer_list
  - Used explicit types to invoke the correct overload
* No literal support for char16_t
  - Added macro to convert the text properly
* Lambdas are unable to capture function non-pointers
  - Updated the typedef to create a function pointer
* Short lifetime for temporary objects passed to constructors
  - Updated the tests to create the objects and pass them to the
    constructor. This is closer to how they would be used anyway

General improvements
* Added console.log output to the test runner to make it more clear
  when and where tests are failing
  • Loading branch information
kfarnung committed Jun 23, 2017
1 parent 244abc3 commit ae2c05f
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 40 deletions.
30 changes: 25 additions & 5 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1644,6 +1644,22 @@ inline Reference<T>& Reference<T>::operator =(Reference<T>&& other) {
return *this;
}

template <typename T>
inline Reference<T>::Reference(const Reference<T>& 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 <typename T>
inline Reference<T>::operator napi_ref() const {
return _ref;
Expand Down Expand Up @@ -1778,6 +1794,10 @@ inline ObjectReference& ObjectReference::operator =(ObjectReference&& other) {
return *this;
}

inline ObjectReference::ObjectReference(const ObjectReference& other)
: Reference<Object>(other) {
}

inline Napi::Value ObjectReference::Get(const char* utf8name) const {
EscapableHandleScope scope(_env);
return scope.Escape(Value().Get(utf8name));
Expand Down Expand Up @@ -2697,11 +2717,11 @@ inline FunctionReference& AsyncWorker::Callback() {
}

inline void AsyncWorker::OnOK() {
_callback.MakeCallback(_receiver.Value(), {});
_callback.MakeCallback(_receiver.Value(), std::initializer_list<napi_value>{});
}

inline void AsyncWorker::OnError(const Error& e) {
_callback.MakeCallback(_receiver.Value(), { e.Value() });
_callback.MakeCallback(_receiver.Value(), std::initializer_list<napi_value>{ e.Value() });
}

inline void AsyncWorker::SetError(const std::string& error) {
Expand Down
37 changes: 33 additions & 4 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@
#include <string>
#include <vector>

// 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<char16_t*>(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)
Expand Down Expand Up @@ -60,7 +74,7 @@ namespace Napi {
typedef TypedArrayOf<double> 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.
///
Expand Down Expand Up @@ -671,7 +685,11 @@ namespace Napi {
static const napi_typedarray_type unknown_array_type = static_cast<napi_typedarray_type>(-1);

template <typename T>
static constexpr napi_typedarray_type TypedArrayTypeForPrimitiveType() {
static
#if defined(NAPI_HAS_CONSTEXPR)
constexpr
#endif
napi_typedarray_type TypedArrayTypeForPrimitiveType() {
return std::is_same<T, int8_t>::value ? napi_int8_array
: std::is_same<T, uint8_t>::value ? napi_uint8_array
: std::is_same<T, int16_t>::value ? napi_int16_array
Expand Down Expand Up @@ -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<T>()
#else
napi_typedarray_type type
#endif
///< Type of array, if different from the default array type for the template parameter T.
);

Expand All @@ -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<T>()
#else
napi_typedarray_type type
#endif
///< Type of array, if different from the default array type for the template parameter T.
);

Expand Down Expand Up @@ -830,7 +856,6 @@ namespace Napi {
// A reference can be moved but cannot be copied.
Reference(Reference<T>&& other);
Reference<T>& operator =(Reference<T>&& other);
Reference(const Reference<T>&) = delete;
Reference<T>& operator =(Reference<T>&) = delete;

operator napi_ref() const;
Expand All @@ -855,6 +880,8 @@ namespace Napi {
void SuppressDestruct();

protected:
Reference(const Reference<T>&);

/// !cond INTERNAL
napi_env _env;
napi_ref _ref;
Expand All @@ -874,7 +901,6 @@ namespace Napi {
ObjectReference& operator =(Reference<Object>&& other);
ObjectReference(ObjectReference&& other);
ObjectReference& operator =(ObjectReference&& other);
ObjectReference(const ObjectReference&) = delete;
ObjectReference& operator =(ObjectReference&) = delete;

Napi::Value Get(const char* utf8name) const;
Expand All @@ -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<Function> {
Expand Down
2 changes: 1 addition & 1 deletion test/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<napi_value>{});
}

#ifdef NAPI_CPP_EXCEPTIONS
Expand Down
6 changes: 3 additions & 3 deletions test/function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Value CallWithVector(const CallbackInfo& info) {
Value CallWithReceiverAndArgs(const CallbackInfo& info) {
Function func = info[0].As<Function>();
Value receiver = info[1];
return func.Call(receiver, { info[2], info[3], info[4] });
return func.Call(receiver, std::initializer_list<napi_value>{ info[2], info[3], info[4] });
}

Value CallWithReceiverAndVector(const CallbackInfo& info) {
Expand All @@ -78,12 +78,12 @@ Value CallWithReceiverAndVector(const CallbackInfo& info) {

Value CallWithInvalidReceiver(const CallbackInfo& info) {
Function func = info[0].As<Function>();
return func.Call(Value(), {});
return func.Call(Value(), std::initializer_list<napi_value>{});
}

Value CallConstructorWithArgs(const CallbackInfo& info) {
Function func = info[0].As<Function>();
return func.New({ info[1], info[2], info[3] });
return func.New(std::initializer_list<napi_value>{ info[1], info[2], info[3] });
}

Value CallConstructorWithVector(const CallbackInfo& info) {
Expand Down
5 changes: 5 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ];
Expand Down
2 changes: 1 addition & 1 deletion test/name.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>();
Expand Down
26 changes: 19 additions & 7 deletions test/object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
65 changes: 46 additions & 19 deletions test/typedarray.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -11,40 +21,57 @@ Value CreateTypedArray(const CallbackInfo& info) {
size_t bufferOffset = info[3].IsUndefined() ? 0 : info[3].As<Number>().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();
}
}

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) {
Expand Down

0 comments on commit ae2c05f

Please sign in to comment.