From 648df97ce647b6ba546cc918e9de6aed9ed8f94c Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 17 Jan 2023 00:38:40 +0800 Subject: [PATCH] src: enforce type checks on Napi::Value::As() --- doc/value.md | 8 +- napi-inl.h | 180 ++++++++++++++++++++++++++++++++++++++++ napi.h | 37 +++++++++ test/binding.gyp | 9 ++ test/value_type_cast.cc | 61 ++++++++++++++ test/value_type_cast.js | 106 +++++++++++++++++++++++ 6 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 test/value_type_cast.cc create mode 100644 test/value_type_cast.js diff --git a/doc/value.md b/doc/value.md index 505b7945f..a835cdc41 100644 --- a/doc/value.md +++ b/doc/value.md @@ -78,7 +78,13 @@ Casts to another type of `Napi::Value`, when the actual type is known or assumed. This conversion does not coerce the type. Calling any methods inappropriate for -the actual value type will throw `Napi::Error`. +the actual value type will throw `Napi::Error`. When C++ exceptions are +disabled, the thrown error will not be reflected before control returns to +JavaScript. + +In order to enforce expected type, use `Napi::Value::Is*()` methods to check +the type before calling `Napi::Value::As()`, or compile with definition +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` to enforce type checks. ### Env diff --git a/napi-inl.h b/napi-inl.h index ddd366c2a..c6ef0fbb0 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -742,6 +742,9 @@ inline bool Value::IsExternal() const { template inline T Value::As() const { +#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS + T::CheckCast(_env, _value); +#endif return T(_env, _value); } @@ -784,6 +787,16 @@ inline Boolean Boolean::New(napi_env env, bool val) { return Boolean(env, value); } +inline void Boolean::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Boolean::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_boolean, "Boolean::CheckCast", "value is not napi_boolean"); +} + inline Boolean::Boolean() : Napi::Value() {} inline Boolean::Boolean(napi_env env, napi_value value) @@ -811,6 +824,16 @@ inline Number Number::New(napi_env env, double val) { return Number(env, value); } +inline void Number::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Number::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_number, "Number::CheckCast", "value is not napi_number"); +} + inline Number::Number() : Value() {} inline Number::Number(napi_env env, napi_value value) : Value(env, value) {} @@ -897,6 +920,16 @@ inline BigInt BigInt::New(napi_env env, return BigInt(env, value); } +inline void BigInt::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "BigInt::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_bigint, "BigInt::CheckCast", "value is not napi_bigint"); +} + inline BigInt::BigInt() : Value() {} inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {} @@ -946,6 +979,15 @@ inline Date Date::New(napi_env env, double val) { return Date(env, value); } +inline void Date::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_date(env, value, &result); + NAPI_CHECK(status == napi_ok, "Date::CheckCast", "napi_is_date failed"); + NAPI_CHECK(result, "Date::CheckCast", "value is not date"); +} + inline Date::Date() : Value() {} inline Date::Date(napi_env env, napi_value value) : Value(env, value) {} @@ -965,6 +1007,16 @@ inline double Date::ValueOf() const { //////////////////////////////////////////////////////////////////////////////// // Name class //////////////////////////////////////////////////////////////////////////////// +inline void Name::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Name::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); + NAPI_CHECK(type == napi_string || type == napi_symbol, + "Name::CheckCast", + "value is not napi_string or napi_symbol"); +} inline Name::Name() : Value() {} @@ -1024,6 +1076,16 @@ inline String String::New(napi_env env, const char16_t* val, size_t length) { return String(env, value); } +inline void String::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "String::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_string, "String::CheckCast", "value is not napi_string"); +} + inline String::String() : Name() {} inline String::String(napi_env env, napi_value value) : Name(env, value) {} @@ -1151,6 +1213,16 @@ inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { #endif } +inline void Symbol::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Symbol::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_symbol, "Symbol::CheckCast", "value is not napi_symbol"); +} + inline Symbol::Symbol() : Name() {} inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) {} @@ -1313,6 +1385,16 @@ inline Object Object::New(napi_env env) { return Object(env, value); } +inline void Object::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_object, "Object::CheckCast", "value is not napi_object"); +} + inline Object::Object() : TypeTaggable() {} inline Object::Object(napi_env env, napi_value value) @@ -1719,6 +1801,18 @@ inline External External::New(napi_env env, return External(env, value); } +template +inline void External::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "External::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); + NAPI_CHECK(type == napi_external, + "External::CheckCast", + "value is not napi_external"); +} + template inline External::External() : TypeTaggable() {} @@ -1752,6 +1846,15 @@ inline Array Array::New(napi_env env, size_t length) { return Array(env, value); } +inline void Array::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Array::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_array(env, value, &result); + NAPI_CHECK(status == napi_ok, "Array::CheckCast", "napi_is_array failed"); + NAPI_CHECK(result, "Array::CheckCast", "value is not array"); +} + inline Array::Array() : Object() {} inline Array::Array(napi_env env, napi_value value) : Object(env, value) {} @@ -1838,6 +1941,17 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, } #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "ArrayBuffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_arraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "ArrayBuffer::CheckCast", + "napi_is_arraybuffer failed"); + NAPI_CHECK(result, "ArrayBuffer::CheckCast", "value is not arraybuffer"); +} + inline ArrayBuffer::ArrayBuffer() : Object() {} inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) @@ -1905,6 +2019,16 @@ inline DataView DataView::New(napi_env env, return DataView(env, value); } +inline void DataView::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_dataview(env, value, &result); + NAPI_CHECK( + status == napi_ok, "DataView::CheckCast", "napi_is_dataview failed"); + NAPI_CHECK(result, "DataView::CheckCast", "value is not dataview"); +} + inline DataView::DataView() : Object() {} inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { @@ -2039,6 +2163,15 @@ inline void DataView::WriteData(size_t byteOffset, T value) const { //////////////////////////////////////////////////////////////////////////////// // TypedArray class //////////////////////////////////////////////////////////////////////////////// +inline void TypedArray::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "TypedArray::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_typedarray(env, value, &result); + NAPI_CHECK( + status == napi_ok, "TypedArray::CheckCast", "napi_is_typedarray failed"); + NAPI_CHECK(result, "TypedArray::CheckCast", "value is not typedarray"); +} inline TypedArray::TypedArray() : Object(), _type(napi_typedarray_type::napi_int8_array), _length(0) {} @@ -2121,6 +2254,23 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { //////////////////////////////////////////////////////////////////////////////// // TypedArrayOf class //////////////////////////////////////////////////////////////////////////////// +template +inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { + TypedArray::CheckCast(env, value); + napi_typedarray_type type; + napi_status status = napi_get_typedarray_info( + env, value, &type, nullptr, nullptr, nullptr, nullptr); + NAPI_CHECK(status == napi_ok, + "TypedArrayOf::CheckCast", + "napi_is_typedarray failed"); + + NAPI_CHECK( + (type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && std::is_same::value)), + "TypedArrayOf::CheckCast", + "Array type must match the template parameter. (Uint8 arrays may " + "optionally have the \"clamped\" array type.)"); +} template inline TypedArrayOf TypedArrayOf::New(napi_env env, @@ -2294,6 +2444,17 @@ inline Function Function::New(napi_env env, return New(env, cb, utf8name.c_str(), data); } +inline void Function::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Function::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); + NAPI_CHECK(type == napi_function, + "Function::CheckCast", + "value is not napi_function"); +} + inline Function::Function() : Object() {} inline Function::Function(napi_env env, napi_value value) @@ -2440,6 +2601,15 @@ inline void Promise::Deferred::Reject(napi_value value) const { NAPI_THROW_IF_FAILED_VOID(_env, status); } +inline void Promise::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Promise::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_promise(env, value, &result); + NAPI_CHECK(status == napi_ok, "Promise::CheckCast", "napi_is_promise failed"); + NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); +} + inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} //////////////////////////////////////////////////////////////////////////////// @@ -2612,6 +2782,16 @@ inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { return Buffer(env, value); } +template +inline void Buffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_buffer(env, value, &result); + NAPI_CHECK(status == napi_ok, "Buffer::CheckCast", "napi_is_buffer failed"); + NAPI_CHECK(result, "Buffer::CheckCast", "value is not buffer"); +} + template inline Buffer::Buffer() : Uint8Array(), _length(0), _data(nullptr) {} diff --git a/napi.h b/napi.h index b651db55b..f3119d7d1 100644 --- a/napi.h +++ b/napi.h @@ -463,6 +463,9 @@ class Value { /// /// This conversion does NOT coerce the type. Calling any methods /// inappropriate for the actual value type will throw `Napi::Error`. + /// + /// If `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined, this method + /// asserts that the actual type is the expected type. template T As() const; @@ -489,6 +492,8 @@ class Boolean : public Value { bool value ///< Boolean value ); + static void CheckCast(napi_env env, napi_value value); + Boolean(); ///< Creates a new _empty_ Boolean instance. Boolean(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -504,6 +509,8 @@ class Number : public Value { double value ///< Number value ); + static void CheckCast(napi_env env, napi_value value); + Number(); ///< Creates a new _empty_ Number instance. Number(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -552,6 +559,8 @@ class BigInt : public Value { const uint64_t* words ///< Array of words ); + static void CheckCast(napi_env env, napi_value value); + BigInt(); ///< Creates a new _empty_ BigInt instance. BigInt(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -583,6 +592,8 @@ class Date : public Value { double value ///< Number value ); + static void CheckCast(napi_env env, napi_value value); + Date(); ///< Creates a new _empty_ Date instance. Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. operator double() const; ///< Converts a Date value to double primitive @@ -594,6 +605,8 @@ class Date : public Value { /// A JavaScript string or symbol value (that can be used as a property name). class Name : public Value { public: + static void CheckCast(napi_env env, napi_value value); + Name(); ///< Creates a new _empty_ Name instance. Name(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -651,6 +664,8 @@ class String : public Name { template static String From(napi_env env, const T& value); + static void CheckCast(napi_env env, napi_value value); + String(); ///< Creates a new _empty_ String instance. String(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -709,6 +724,8 @@ class Symbol : public Name { // Create a symbol in the global registry, napi_value describing the symbol static MaybeOrValue For(napi_env env, napi_value description); + static void CheckCast(napi_env env, napi_value value); + Symbol(); ///< Creates a new _empty_ Symbol instance. Symbol(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -767,6 +784,8 @@ class Object : public TypeTaggable { static Object New(napi_env env ///< Node-API environment ); + static void CheckCast(napi_env env, napi_value value); + Object(); ///< Creates a new _empty_ Object instance. Object(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -1020,6 +1039,8 @@ class External : public TypeTaggable { Finalizer finalizeCallback, Hint* finalizeHint); + static void CheckCast(napi_env env, napi_value value); + External(); External(napi_env env, napi_value value); @@ -1031,6 +1052,8 @@ class Array : public Object { static Array New(napi_env env); static Array New(napi_env env, size_t length); + static void CheckCast(napi_env env, napi_value value); + Array(); Array(napi_env env, napi_value value); @@ -1142,6 +1165,8 @@ class ArrayBuffer : public Object { ); #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + static void CheckCast(napi_env env, napi_value value); + ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. ArrayBuffer(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -1166,6 +1191,8 @@ class ArrayBuffer : public Object { /// } class TypedArray : public Object { public: + static void CheckCast(napi_env env, napi_value value); + TypedArray(); ///< Creates a new _empty_ TypedArray instance. TypedArray(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -1266,6 +1293,8 @@ class TypedArrayOf : public TypedArray { ///< template parameter T. ); + static void CheckCast(napi_env env, napi_value value); + TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. TypedArrayOf(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -1310,6 +1339,8 @@ class DataView : public Object { size_t byteOffset, size_t byteLength); + static void CheckCast(napi_env env, napi_value value); + DataView(); ///< Creates a new _empty_ DataView instance. DataView(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. @@ -1390,6 +1421,8 @@ class Function : public Object { const std::string& utf8name, void* data = nullptr); + static void CheckCast(napi_env env, napi_value value); + Function(); Function(napi_env env, napi_value value); @@ -1446,6 +1479,8 @@ class Promise : public Object { napi_value _promise; }; + static void CheckCast(napi_env env, napi_value value); + Promise(napi_env env, napi_value value); }; @@ -1488,6 +1523,8 @@ class Buffer : public Uint8Array { static Buffer Copy(napi_env env, const T* data, size_t length); + static void CheckCast(napi_env env, napi_value value); + Buffer(); Buffer(napi_env env, napi_value value); size_t Length() const; diff --git a/test/binding.gyp b/test/binding.gyp index 3817c2c9d..959434cc2 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -81,6 +81,9 @@ 'binding-swallowexcept.cc', 'error.cc', ], + 'build_sources_type_check': [ + 'value_type_cast.cc' + ], 'conditions': [ ['disable_deprecated!="true"', { 'build_sources': ['object/object_deprecated.cc'] @@ -117,6 +120,12 @@ 'sources': ['>@(build_sources_swallowexcept)'], 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'] }, + { + 'target_name': 'binding_type_check', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources_type_check)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] + }, { 'target_name': 'binding_custom_namespace', 'includes': ['../noexcept.gypi'], diff --git a/test/value_type_cast.cc b/test/value_type_cast.cc new file mode 100644 index 000000000..66ef1277e --- /dev/null +++ b/test/value_type_cast.cc @@ -0,0 +1,61 @@ +#include "common/test_helper.h" +#include "napi.h" + +using namespace Napi; + +#define TYPE_CAST_TYPES(V) \ + V(Boolean) \ + V(Number) \ + V(BigInt) \ + V(Date) \ + V(String) \ + V(Symbol) \ + V(Object) \ + V(Array) \ + V(ArrayBuffer) \ + V(TypedArray) \ + V(DataView) \ + V(Function) \ + V(Promise) + +// The following types are tested individually. +// External +// TypedArrayOf +// Buffer + +namespace { +#define V(Type) \ + void TypeCast##Type(const CallbackInfo& info) { USE(info[0].As()); } +TYPE_CAST_TYPES(V) +#undef V + +void TypeCastBuffer(const CallbackInfo& info) { + USE(info[0].As>()); +} + +void TypeCastExternal(const CallbackInfo& info) { + USE(info[0].As>()); +} + +void TypeCastTypeArrayOfUint8(const CallbackInfo& info) { + USE(info[0].As>()); +} +} // namespace + +Object InitValueTypeCast(Env env) { + Object exports = Object::New(env); + exports["external"] = External::New(env, nullptr); + +#define V(Type) exports["typeCast" #Type] = Function::New(env, TypeCast##Type); + TYPE_CAST_TYPES(V) +#undef V + + exports["typeCastBuffer"] = Function::New(env, TypeCastBuffer); + exports["typeCastExternal"] = Function::New(env, TypeCastExternal); + exports["typeCastTypeArrayOfUint8"] = + Function::New(env, TypeCastTypeArrayOfUint8); + + return exports; +} + +NODE_API_MODULE(addon, InitValueTypeCast) diff --git a/test/value_type_cast.js b/test/value_type_cast.js new file mode 100644 index 000000000..cdebd4e03 --- /dev/null +++ b/test/value_type_cast.js @@ -0,0 +1,106 @@ +'use strict'; + +const assert = require('assert'); +const napiChild = require('./napi_child'); + +module.exports = require('./common').runTestWithBuildType(test); + +function test (buildType) { + const binding = require(`./build/${buildType}/binding_type_check.node`); + const testTable = { + typeCastBoolean: { + positiveValues: [true, false], + negativeValues: [{}, [], 1, 1n, 'true', null, undefined] + }, + typeCastNumber: { + positiveValues: [1, NaN], + negativeValues: [{}, [], true, 1n, '1', null, undefined] + }, + typeCastBigInt: { + positiveValues: [1n], + negativeValues: [{}, [], true, 1, '1', null, undefined] + }, + typeCastDate: { + positiveValues: [new Date()], + negativeValues: [{}, [], true, 1, 1n, '1', null, undefined] + }, + typeCastString: { + positiveValues: ['', '1'], + negativeValues: [{}, [], true, 1, 1n, null, undefined] + }, + typeCastSymbol: { + positiveValues: [Symbol('1')], + negativeValues: [{}, [], true, 1, 1n, '1', null, undefined] + }, + typeCastObject: { + positiveValues: [{}, new Date(), []], + negativeValues: [true, 1, 1n, '1', null, undefined] + }, + typeCastArray: { + positiveValues: [[1]], + negativeValues: [{}, true, 1, 1n, '1', null, undefined] + }, + typeCastArrayBuffer: { + positiveValues: [new ArrayBuffer(0)], + negativeValues: [new Uint8Array(1), {}, [], null, undefined] + }, + typeCastTypedArray: { + positiveValues: [new Uint8Array(0)], + negativeValues: [new ArrayBuffer(1), {}, [], null, undefined] + }, + typeCastDataView: { + positiveValues: [new DataView(new ArrayBuffer(0))], + negativeValues: [new ArrayBuffer(1), null, undefined] + }, + typeCastFunction: { + positiveValues: [() => {}], + negativeValues: [{}, null, undefined] + }, + typeCastPromise: { + positiveValues: [Promise.resolve()], + // napi_is_promise distinguishes Promise and thenable. + negativeValues: [{ then: () => {} }, null, undefined] + }, + typeCastBuffer: { + positiveValues: [Buffer.from('')], + // napi_is_buffer doesn't distinguish between Buffer and TypedArrays. + negativeValues: [new ArrayBuffer(1), null, undefined] + }, + typeCastExternal: { + positiveValues: [binding.external], + negativeValues: [{}, null, undefined] + }, + typeCastTypeArrayOfUint8: { + // TypedArrayOf::CheckCast doesn't distinguish between Uint8ClampedArray and Uint8Array. + positiveValues: [new Uint8Array(0), new Uint8ClampedArray(0)], + negativeValues: [new Int8Array(1), null, undefined] + } + }; + + if (process.argv[2] === 'child') { + child(binding, testTable, process.argv[3], process.argv[4], parseInt(process.argv[5])); + return; + } + + for (const [methodName, { positiveValues, negativeValues }] of Object.entries(testTable)) { + for (const idx of positiveValues.keys()) { + const { status } = napiChild.spawnSync(process.execPath, [__filename, 'child', methodName, 'positiveValues', idx]); + assert.strictEqual(status, 0, `${methodName} positive value ${idx} test failed`); + } + for (const idx of negativeValues.keys()) { + const { status, signal, stderr } = napiChild.spawnSync(process.execPath, [__filename, 'child', methodName, 'negativeValues', idx], { + encoding: 'utf8' + }); + if (process.platform === 'win32') { + assert.strictEqual(status, 128 + 6 /* SIGABRT */, `${methodName} negative value ${idx} test failed`); + } else { + assert.strictEqual(signal, 'SIGABRT', `${methodName} negative value ${idx} test failed`); + } + assert.ok(stderr.match(/FATAL ERROR: .*::CheckCast.*/)); + } + } +} + +async function child (binding, testTable, methodName, type, idx) { + binding[methodName](testTable[methodName][type][idx]); +}