Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: enforce type checks on Napi::Value::As() #1281

Merged
merged 1 commit into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion doc/value.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
180 changes: 180 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,9 @@ inline bool Value::IsExternal() const {

template <typename T>
inline T Value::As() const {
#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS
T::CheckCast(_env, _value);
#endif
return T(_env, _value);
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -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) {}
Expand All @@ -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() {}

Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -1151,6 +1213,16 @@ inline MaybeOrValue<Symbol> 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) {}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1719,6 +1801,18 @@ inline External<T> External<T>::New(napi_env env,
return External(env, value);
}

template <typename T>
inline void External<T>::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 <typename T>
inline External<T>::External() : TypeTaggable() {}

Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -2121,6 +2254,23 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const {
////////////////////////////////////////////////////////////////////////////////
// TypedArrayOf<T> class
////////////////////////////////////////////////////////////////////////////////
template <typename T>
inline void TypedArrayOf<T>::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<T>() ||
(type == napi_uint8_clamped_array && std::is_same<T, uint8_t>::value)),
"TypedArrayOf::CheckCast",
"Array type must match the template parameter. (Uint8 arrays may "
"optionally have the \"clamped\" array type.)");
}

template <typename T>
inline TypedArrayOf<T> TypedArrayOf<T>::New(napi_env env,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {}

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -2612,6 +2782,16 @@ inline Buffer<T> Buffer<T>::Copy(napi_env env, const T* data, size_t length) {
return Buffer<T>(env, value);
}

template <typename T>
inline void Buffer<T>::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 <typename T>
inline Buffer<T>::Buffer() : Uint8Array(), _length(0), _data(nullptr) {}

Expand Down
Loading