From fd9970657f210cb8b83a9e932a5a4e11e046ac85 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 4 Jan 2023 14:26:06 +0000 Subject: [PATCH 1/7] Buffer support --- packages/emnapi/include/napi-inl.h | 212 ++++++++++++++-------------- packages/emnapi/include/napi.h | 60 ++++---- packages/emnapi/src/value/create.ts | 92 ++++++++++-- 3 files changed, 214 insertions(+), 150 deletions(-) diff --git a/packages/emnapi/include/napi-inl.h b/packages/emnapi/include/napi-inl.h index 823c2b38..0ac8e6c8 100644 --- a/packages/emnapi/include/napi-inl.h +++ b/packages/emnapi/include/napi-inl.h @@ -2427,122 +2427,122 @@ inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) { // Buffer class //////////////////////////////////////////////////////////////////////////////// -// template -// inline Buffer Buffer::New(napi_env env, size_t length) { -// napi_value value; -// void* data; -// napi_status status = napi_create_buffer(env, length * sizeof (T), &data, &value); -// NAPI_THROW_IF_FAILED(env, status, Buffer()); -// return Buffer(env, value, length, static_cast(data)); -// } +template +inline Buffer Buffer::New(napi_env env, size_t length) { + napi_value value; + void* data; + napi_status status = napi_create_buffer(env, length * sizeof (T), &data, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value, length, static_cast(data)); +} -// template -// inline Buffer Buffer::New(napi_env env, T* data, size_t length) { -// napi_value value; -// napi_status status = napi_create_external_buffer( -// env, length * sizeof (T), data, nullptr, nullptr, &value); -// NAPI_THROW_IF_FAILED(env, status, Buffer()); -// return Buffer(env, value, length, data); -// } +template +inline Buffer Buffer::New(napi_env env, T* data, size_t length) { + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof (T), data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value, length, data); +} -// template -// template -// inline Buffer Buffer::New(napi_env env, -// T* data, -// size_t length, -// Finalizer finalizeCallback) { -// napi_value value; -// details::FinalizeData* finalizeData = -// new details::FinalizeData( -// {std::move(finalizeCallback), nullptr}); -// napi_status status = napi_create_external_buffer( -// env, -// length * sizeof (T), -// data, -// details::FinalizeData::Wrapper, -// finalizeData, -// &value); -// if (status != napi_ok) { -// delete finalizeData; -// NAPI_THROW_IF_FAILED(env, status, Buffer()); -// } -// return Buffer(env, value, length, data); -// } +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = napi_create_external_buffer( + env, + length * sizeof (T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value, length, data); +} -// template -// template -// inline Buffer Buffer::New(napi_env env, -// T* data, -// size_t length, -// Finalizer finalizeCallback, -// Hint* finalizeHint) { -// napi_value value; -// details::FinalizeData* finalizeData = -// new details::FinalizeData( -// {std::move(finalizeCallback), finalizeHint}); -// napi_status status = napi_create_external_buffer( -// env, -// length * sizeof (T), -// data, -// details::FinalizeData::WrapperWithHint, -// finalizeData, -// &value); -// if (status != napi_ok) { -// delete finalizeData; -// NAPI_THROW_IF_FAILED(env, status, Buffer()); -// } -// return Buffer(env, value, length, data); -// } +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_buffer( + env, + length * sizeof (T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value, length, data); +} -// template -// inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { -// napi_value value; -// napi_status status = napi_create_buffer_copy( -// env, length * sizeof (T), data, nullptr, &value); -// NAPI_THROW_IF_FAILED(env, status, Buffer()); -// return Buffer(env, value); -// } +template +inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { + napi_value value; + napi_status status = napi_create_buffer_copy( + env, length * sizeof (T), data, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} -// template -// inline Buffer::Buffer() : Uint8Array(), _length(0), _data(nullptr) { -// } +template +inline Buffer::Buffer() : Uint8Array(), _length(0), _data(nullptr) { +} -// template -// inline Buffer::Buffer(napi_env env, napi_value value) -// : Uint8Array(env, value), _length(0), _data(nullptr) { -// } +template +inline Buffer::Buffer(napi_env env, napi_value value) + : Uint8Array(env, value), _length(0), _data(nullptr) { +} -// template -// inline Buffer::Buffer(napi_env env, napi_value value, size_t length, T* data) -// : Uint8Array(env, value), _length(length), _data(data) { -// } +template +inline Buffer::Buffer(napi_env env, napi_value value, size_t length, T* data) + : Uint8Array(env, value), _length(length), _data(data) { +} -// template -// inline size_t Buffer::Length() const { -// EnsureInfo(); -// return _length; -// } +template +inline size_t Buffer::Length() const { + EnsureInfo(); + return _length; +} -// template -// inline T* Buffer::Data() const { -// EnsureInfo(); -// return _data; -// } +template +inline T* Buffer::Data() const { + EnsureInfo(); + return _data; +} -// template -// inline void Buffer::EnsureInfo() const { -// // The Buffer instance may have been constructed from a napi_value whose -// // length/data are not yet known. Fetch and cache these values just once, -// // since they can never change during the lifetime of the Buffer. -// if (_data == nullptr) { -// size_t byteLength; -// void* voidData; -// napi_status status = napi_get_buffer_info(_env, _value, &voidData, &byteLength); -// NAPI_THROW_IF_FAILED_VOID(_env, status); -// _length = byteLength / sizeof (T); -// _data = static_cast(voidData); -// } -// } +template +inline void Buffer::EnsureInfo() const { + // The Buffer instance may have been constructed from a napi_value whose + // length/data are not yet known. Fetch and cache these values just once, + // since they can never change during the lifetime of the Buffer. + if (_data == nullptr) { + size_t byteLength; + void* voidData; + napi_status status = napi_get_buffer_info(_env, _value, &voidData, &byteLength); + NAPI_THROW_IF_FAILED_VOID(_env, status); + _length = byteLength / sizeof (T); + _data = static_cast(voidData); + } +} //////////////////////////////////////////////////////////////////////////////// // Error class diff --git a/packages/emnapi/include/napi.h b/packages/emnapi/include/napi.h index 3a5a5af5..2aa7c148 100644 --- a/packages/emnapi/include/napi.h +++ b/packages/emnapi/include/napi.h @@ -1414,38 +1414,38 @@ namespace NAPI_CPP_CUSTOM_NAMESPACE { Promise(napi_env env, napi_value value); }; - // template - // class Buffer : public Uint8Array { - // public: - // static Buffer New(napi_env env, size_t length); - // static Buffer New(napi_env env, T* data, size_t length); - - // // Finalizer must implement `void operator()(Env env, T* data)`. - // template - // static Buffer New(napi_env env, T* data, - // size_t length, - // Finalizer finalizeCallback); - // // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. - // template - // static Buffer New(napi_env env, T* data, - // size_t length, - // Finalizer finalizeCallback, - // Hint* finalizeHint); - - // static Buffer Copy(napi_env env, const T* data, size_t length); - - // Buffer(); - // Buffer(napi_env env, napi_value value); - // size_t Length() const; - // T* Data() const; + template + class Buffer : public Uint8Array { + public: + static Buffer New(napi_env env, size_t length); + static Buffer New(napi_env env, T* data, size_t length); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer New(napi_env env, T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer New(napi_env env, T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static Buffer Copy(napi_env env, const T* data, size_t length); + + Buffer(); + Buffer(napi_env env, napi_value value); + size_t Length() const; + T* Data() const; - // private: - // mutable size_t _length; - // mutable T* _data; + private: + mutable size_t _length; + mutable T* _data; - // Buffer(napi_env env, napi_value value, size_t length, T* data); - // void EnsureInfo() const; - // }; + Buffer(napi_env env, napi_value value, size_t length, T* data); + void EnsureInfo() const; + }; /// Holds a counted reference to a value; initially a weak reference unless otherwise specified, /// may be changed to/from a strong reference by adjusting the refcount. diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index d14ea565..a9694fa4 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -1,3 +1,5 @@ +declare const Buffer: any + function napi_create_array (env: napi_env, result: Pointer): napi_status { $CHECK_ENV!(env) const envObject = emnapiCtx.envStore.get(env)! @@ -22,25 +24,29 @@ function napi_create_array_with_length (env: napi_env, length: size_t, result: P return envObject.clearLastError() } +function $createArrayBuffer(byte_length: size_t, data: void_pp) { + $from64('byte_length') + byte_length = byte_length >>> 0 + const arrayBuffer = new ArrayBuffer(byte_length) + + if (data) { + $from64('data') + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const p = emnapiExternalMemory.getArrayBufferPointer(arrayBuffer, true).address + $makeSetValue('data', 0, 'p', '*') + } + + return arrayBuffer +} + // @ts-expect-error function napi_create_arraybuffer (env: napi_env, byte_length: size_t, data: void_pp, result: Pointer): napi_status { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let value: number, p: number - $PREAMBLE!(env, (envObject) => { $CHECK_ARG!(envObject, result) - $from64('byte_length') $from64('result') - byte_length = byte_length >>> 0 - const arrayBuffer = new ArrayBuffer(byte_length) - - if (data) { - $from64('data') - p = emnapiExternalMemory.getArrayBufferPointer(arrayBuffer, true).address - $makeSetValue('data', 0, 'p', '*') - } - - value = emnapiCtx.addToCurrentScope(arrayBuffer).id + const arrayBuffer = $createArrayBuffer(byte_length, data) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const value = emnapiCtx.addToCurrentScope(arrayBuffer).id $makeSetValue('result', 0, 'value', '*') return envObject.getReturnStatus() }) @@ -258,6 +264,64 @@ function napi_create_typedarray ( }) } +function napi_create_buffer ( + env: napi_env, + size: size_t, + data: Pointer>, + result: Pointer +) { + $PREAMBLE!(env, (envObject) => { + $CHECK_ARG!(envObject, result) + $from64('result') + const arrayBuffer = $createArrayBuffer(size, data) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const value = emnapiCtx.addToCurrentScope(arrayBuffer).id + $makeSetValue('result', 0, 'value', '*') + return envObject.getReturnStatus() + }) +} + +function napi_create_buffer_copy( + env: napi_env, + length: size_t, + data: Pointer, + result_data: Pointer>, + result: Pointer +) { + $PREAMBLE!(env, (envObject) => { + $CHECK_ARG!(envObject, result) + $from64('result_data') + $from64('result') + const arrayBuffer = $createArrayBuffer(length, result_data) + new Uint8Array(arrayBuffer).set(HEAPU8.subarray(data, data + length)) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const value = emnapiCtx.addToCurrentScope(arrayBuffer).id + $makeSetValue('result', 0, 'value', '*') + return envObject.getReturnStatus() + }) +} + +function napi_create_external_buffer( + env: napi_env, + length: size_t, + data: Pointer, + finalize_cb: napi_finalize, + finalize_hint: Pointer, + result: Pointer +) { + $PREAMBLE!(env, (envObject) => { + const status = napi_create_external_arraybuffer(env, data, length, finalize_cb, finalize_hint, result) + if (status !== napi_status.napi_ok) { + return status + } + const handleId = $makeGetValue('result', 0, '*') + const handle = emnapiCtx.handleStore.get(handleId)! + // I know I'm the only owner of handle, so just wrap value in-place. + handle.value = Buffer.from(handle.value) + return envObject.getReturnStatus() + }) +} + function napi_create_dataview ( env: napi_env, byte_length: size_t, From bb44bf6e566b3fd102aa68eefca68c28a0957ad9 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 4 Jan 2023 15:28:31 +0000 Subject: [PATCH 2/7] More Buffer methods & fixes --- packages/emnapi/include/napi-inl.h | 18 ++++++------- packages/emnapi/include/napi.h | 1 + packages/emnapi/include/node_api.h | 35 ++++++++++++++++++++++++++ packages/emnapi/src/value-operation.ts | 12 +++++++++ packages/emnapi/src/value/convert2c.ts | 17 +++++++++++++ packages/emnapi/src/value/create.ts | 5 +++- packages/runtime/src/Handle.ts | 4 +++ 7 files changed, 82 insertions(+), 10 deletions(-) diff --git a/packages/emnapi/include/napi-inl.h b/packages/emnapi/include/napi-inl.h index 0ac8e6c8..77a1241e 100644 --- a/packages/emnapi/include/napi-inl.h +++ b/packages/emnapi/include/napi-inl.h @@ -731,16 +731,16 @@ inline bool Value::IsDataView() const { return result; } -// inline bool Value::IsBuffer() const { -// if (IsEmpty()) { -// return false; -// } +inline bool Value::IsBuffer() const { + if (IsEmpty()) { + return false; + } -// bool result; -// napi_status status = napi_is_buffer(_env, _value, &result); -// NAPI_THROW_IF_FAILED(_env, status, false); -// return result; -// } + bool result; + napi_status status = napi_is_buffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} inline bool Value::IsExternal() const { return Type() == napi_external; diff --git a/packages/emnapi/include/napi.h b/packages/emnapi/include/napi.h index 2aa7c148..ece65a74 100644 --- a/packages/emnapi/include/napi.h +++ b/packages/emnapi/include/napi.h @@ -434,6 +434,7 @@ namespace NAPI_CPP_CUSTOM_NAMESPACE { bool IsArray() const; ///< Tests if a value is a JavaScript array. bool IsArrayBuffer() const; ///< Tests if a value is a JavaScript array buffer. bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. + bool IsBuffer() const; ///< Tests if a value is a Node buffer. bool IsObject() const; ///< Tests if a value is a JavaScript object. bool IsFunction() const; ///< Tests if a value is a JavaScript function. bool IsPromise() const; ///< Tests if a value is a JavaScript promise. diff --git a/packages/emnapi/include/node_api.h b/packages/emnapi/include/node_api.h index b9f19e11..d0e08774 100644 --- a/packages/emnapi/include/node_api.h +++ b/packages/emnapi/include/node_api.h @@ -125,6 +125,41 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); #endif +// Methods to provide node::Buffer functionality with napi types +NAPI_EXTERN napi_status +napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result); + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status +napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +NAPI_EXTERN napi_status +napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result); + +NAPI_EXTERN napi_status +napi_is_buffer(napi_env env, + napi_value value, + bool* result); + +NAPI_EXTERN napi_status +napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length); + #endif EXTERN_C_END diff --git a/packages/emnapi/src/value-operation.ts b/packages/emnapi/src/value-operation.ts index 9969f79e..9bdb2c67 100644 --- a/packages/emnapi/src/value-operation.ts +++ b/packages/emnapi/src/value-operation.ts @@ -188,6 +188,17 @@ function napi_is_typedarray (env: napi_env, value: napi_value, result: Pointer): napi_status { + $CHECK_ENV!(env) + const envObject = emnapiCtx.envStore.get(env)! + $CHECK_ARG!(envObject, value) + $CHECK_ARG!(envObject, result) + const h = emnapiCtx.handleStore.get(value)! + $from64('result') + HEAPU8[result] = h.isBuffer() ? 1 : 0 + return envObject.clearLastError() +} + function napi_is_dataview (env: napi_env, value: napi_value, result: Pointer): napi_status { $CHECK_ENV!(env) const envObject = emnapiCtx.envStore.get(env)! @@ -267,6 +278,7 @@ emnapiImplement('napi_is_arraybuffer', 'ippp', napi_is_arraybuffer) emnapiImplement('napi_is_date', 'ippp', napi_is_date) emnapiImplement('napi_is_error', 'ippp', napi_is_error) emnapiImplement('napi_is_typedarray', 'ippp', napi_is_typedarray) +emnapiImplement('napi_is_buffer', 'ippp', napi_is_buffer) emnapiImplement('napi_is_dataview', 'ippp', napi_is_dataview) emnapiImplement('napi_strict_equals', 'ipppp', napi_strict_equals) emnapiImplement('napi_detach_arraybuffer', 'ipp', napi_detach_arraybuffer, ['napi_set_last_error']) diff --git a/packages/emnapi/src/value/convert2c.ts b/packages/emnapi/src/value/convert2c.ts index 0eb6d509..8dc867af 100644 --- a/packages/emnapi/src/value/convert2c.ts +++ b/packages/emnapi/src/value/convert2c.ts @@ -189,6 +189,22 @@ function napi_get_typedarray_info ( return envObject.clearLastError() } +function napi_get_buffer_info ( + env: napi_env, + buffer: napi_value, + data: void_pp, + length: Pointer +): napi_status { + $CHECK_ENV!(env) + const envObject = emnapiCtx.envStore.get(env)! + $CHECK_ARG!(envObject, buffer) + const handle = emnapiCtx.handleStore.get(buffer)! + if (!handle.isBuffer()) { + return envObject.setLastError(napi_status.napi_invalid_arg) + } + return napi_get_typedarray_info(env, buffer, 0, length, data, 0, 0) +} + function napi_get_dataview_info ( env: napi_env, dataview: napi_value, @@ -568,6 +584,7 @@ emnapiImplement('napi_get_array_length', 'ippp', napi_get_array_length) emnapiImplement('napi_get_arraybuffer_info', 'ipppp', napi_get_arraybuffer_info, ['$emnapiExternalMemory']) emnapiImplement('napi_get_prototype', 'ippp', napi_get_prototype) emnapiImplement('napi_get_typedarray_info', 'ippppppp', napi_get_typedarray_info, ['$emnapiExternalMemory']) +emnapiImplement('napi_get_buffer_info', 'ipppp', napi_get_buffer_info, ['napi_get_typedarray_info']) emnapiImplement('napi_get_dataview_info', 'ipppppp', napi_get_dataview_info, ['$emnapiExternalMemory']) emnapiImplement('napi_get_date_value', 'ippp', napi_get_date_value) emnapiImplement('napi_get_value_bool', 'ippp', napi_get_value_bool) diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index a9694fa4..41deaf20 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -381,7 +381,10 @@ function node_api_symbol_for (env: napi_env, utf8description: const_char_p, leng emnapiImplement('napi_create_array', 'ipp', napi_create_array) emnapiImplement('napi_create_array_with_length', 'ippp', napi_create_array_with_length) -emnapiImplement('napi_create_arraybuffer', 'ipppp', napi_create_arraybuffer) +emnapiImplement('napi_create_arraybuffer', 'ipppp', napi_create_arraybuffer, ['$createArrayBuffer']) +emnapiImplement('napi_create_buffer', 'ippp', napi_create_buffer, ['$createArrayBuffer']) +emnapiImplement('napi_create_buffer_copy', 'ippppp', napi_create_buffer_copy, ['$createArrayBuffer']) +emnapiImplement('napi_create_external_buffer', 'ipppppp', napi_create_external_buffer, ['napi_create_external_arraybuffer']) emnapiImplement('napi_create_date', 'ipdp', napi_create_date) emnapiImplement('napi_create_external', 'ippppp', napi_create_external) emnapiImplement('napi_create_external_arraybuffer', 'ipppppp', napi_create_external_arraybuffer, ['$emnapiWrap']) diff --git a/packages/runtime/src/Handle.ts b/packages/runtime/src/Handle.ts index 33f46d5a..76d8931a 100644 --- a/packages/runtime/src/Handle.ts +++ b/packages/runtime/src/Handle.ts @@ -63,6 +63,10 @@ export class Handle { return !this.isEmpty() && (ArrayBuffer.isView(this.value)) && !(this.value instanceof DataView) } + public isBuffer (): boolean { + return !this.isEmpty() && (this.value instanceof Buffer) + } + public isDataView (): boolean { return !this.isEmpty() && (this.value instanceof DataView) } From e78fe3b46fef73dbd20718944f682011a8fb52c4 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 4 Jan 2023 15:46:10 +0000 Subject: [PATCH 3/7] Fix createArrayBuffer registration --- packages/emnapi/src/value/create.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index 41deaf20..33ad3a79 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -24,7 +24,8 @@ function napi_create_array_with_length (env: napi_env, length: size_t, result: P return envObject.clearLastError() } -function $createArrayBuffer(byte_length: size_t, data: void_pp) { +declare const createArrayBuffer: typeof _$createArrayBuffer +function _$createArrayBuffer(byte_length: size_t, data: void_pp) { $from64('byte_length') byte_length = byte_length >>> 0 const arrayBuffer = new ArrayBuffer(byte_length) @@ -41,12 +42,14 @@ function $createArrayBuffer(byte_length: size_t, data: void_pp) { // @ts-expect-error function napi_create_arraybuffer (env: napi_env, byte_length: size_t, data: void_pp, result: Pointer): napi_status { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let value: number + $PREAMBLE!(env, (envObject) => { $CHECK_ARG!(envObject, result) $from64('result') - const arrayBuffer = $createArrayBuffer(byte_length, data) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const value = emnapiCtx.addToCurrentScope(arrayBuffer).id + const arrayBuffer = createArrayBuffer(byte_length, data) + value = emnapiCtx.addToCurrentScope(arrayBuffer).id $makeSetValue('result', 0, 'value', '*') return envObject.getReturnStatus() }) @@ -273,7 +276,7 @@ function napi_create_buffer ( $PREAMBLE!(env, (envObject) => { $CHECK_ARG!(envObject, result) $from64('result') - const arrayBuffer = $createArrayBuffer(size, data) + const arrayBuffer = createArrayBuffer(size, data) // eslint-disable-next-line @typescript-eslint/no-unused-vars const value = emnapiCtx.addToCurrentScope(arrayBuffer).id $makeSetValue('result', 0, 'value', '*') @@ -288,14 +291,16 @@ function napi_create_buffer_copy( result_data: Pointer>, result: Pointer ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let value: number + $PREAMBLE!(env, (envObject) => { $CHECK_ARG!(envObject, result) $from64('result_data') $from64('result') - const arrayBuffer = $createArrayBuffer(length, result_data) + const arrayBuffer = createArrayBuffer(length, result_data) new Uint8Array(arrayBuffer).set(HEAPU8.subarray(data, data + length)) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const value = emnapiCtx.addToCurrentScope(arrayBuffer).id + value = emnapiCtx.addToCurrentScope(arrayBuffer).id $makeSetValue('result', 0, 'value', '*') return envObject.getReturnStatus() }) @@ -379,6 +384,7 @@ function node_api_symbol_for (env: napi_env, utf8description: const_char_p, leng return envObject.clearLastError() } +emnapiImplement('$createArrayBuffer', undefined, _$createArrayBuffer) emnapiImplement('napi_create_array', 'ipp', napi_create_array) emnapiImplement('napi_create_array_with_length', 'ippp', napi_create_array_with_length) emnapiImplement('napi_create_arraybuffer', 'ipppp', napi_create_arraybuffer, ['$createArrayBuffer']) From c2dd1ca0369775acc445c822e2cc004aba73709d Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Thu, 5 Jan 2023 11:26:51 +0800 Subject: [PATCH 4/7] test: add buffer test --- packages/test/buffer/binding.c | 170 ++++++++++++++++++ packages/test/buffer/buffer.finalizer.test.js | 31 ++++ packages/test/buffer/buffer.test.js | 32 ++++ packages/test/cgen.config.js | 1 + packages/test/script/test.js | 3 +- packages/test/tick.js | 13 ++ 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 packages/test/buffer/binding.c create mode 100644 packages/test/buffer/buffer.finalizer.test.js create mode 100644 packages/test/buffer/buffer.test.js create mode 100644 packages/test/tick.js diff --git a/packages/test/buffer/binding.c b/packages/test/buffer/binding.c new file mode 100644 index 00000000..7f251865 --- /dev/null +++ b/packages/test/buffer/binding.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include "../common.h" + +static const char theText[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + +static int deleterCallCount = 0; +static void deleteTheText(napi_env env, void* data, void* finalize_hint) { + NAPI_ASSERT_RETURN_VOID( + env, data != NULL && strcmp(data, theText) == 0, "invalid data"); + (void)finalize_hint; + free(data); + deleterCallCount++; +} + +static void noopDeleter(napi_env env, void* data, void* finalize_hint) { + NAPI_ASSERT_RETURN_VOID( + env, data != NULL && strcmp(data, theText) == 0, "invalid data"); + (void)finalize_hint; + deleterCallCount++; +} + +static void malignDeleter(napi_env env, void* data, void* finalize_hint) { + NAPI_ASSERT_RETURN_VOID(env, data != NULL && strcmp(data, theText) == 0, "invalid data"); + napi_ref finalizer_ref = (napi_ref)finalize_hint; + napi_value js_finalizer; + napi_value recv; + NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, finalizer_ref, &js_finalizer)); + NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &recv)); + NAPI_CALL_RETURN_VOID(env, napi_call_function(env, recv, js_finalizer, 0, NULL, NULL)); + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, finalizer_ref)); +} + +static napi_value newBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + char* theCopy; + const unsigned int kBufferSize = sizeof(theText); + + NAPI_CALL(env, + napi_create_buffer( + env, sizeof(theText), (void**)(&theCopy), &theBuffer)); + NAPI_ASSERT(env, theCopy, "Failed to copy static text for newBuffer"); + memcpy(theCopy, theText, kBufferSize); + + return theBuffer; +} + +static napi_value newExternalBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + char* theCopy = strdup(theText); + NAPI_ASSERT( + env, theCopy, "Failed to copy static text for newExternalBuffer"); + NAPI_CALL(env, + napi_create_external_buffer( + env, sizeof(theText), theCopy, deleteTheText, + NULL /* finalize_hint */, &theBuffer)); + + return theBuffer; +} + +static napi_value getDeleterCallCount(napi_env env, napi_callback_info info) { + napi_value callCount; + NAPI_CALL(env, napi_create_int32(env, deleterCallCount, &callCount)); + return callCount; +} + +static napi_value copyBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + NAPI_CALL(env, napi_create_buffer_copy( + env, sizeof(theText), theText, NULL, &theBuffer)); + return theBuffer; +} + +static napi_value bufferHasInstance(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NAPI_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value theBuffer = args[0]; + bool hasInstance; + napi_valuetype theType; + NAPI_CALL(env, napi_typeof(env, theBuffer, &theType)); + NAPI_ASSERT(env, + theType == napi_object, "bufferHasInstance: instance is not an object"); + NAPI_CALL(env, napi_is_buffer(env, theBuffer, &hasInstance)); + NAPI_ASSERT(env, hasInstance, "bufferHasInstance: instance is not a buffer"); + napi_value returnValue; + NAPI_CALL(env, napi_get_boolean(env, hasInstance, &returnValue)); + return returnValue; +} + +static napi_value bufferInfo(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NAPI_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value theBuffer = args[0]; + char *bufferData; + napi_value returnValue; + size_t bufferLength; + NAPI_CALL(env, + napi_get_buffer_info( + env, theBuffer, (void**)(&bufferData), &bufferLength)); + NAPI_CALL(env, napi_get_boolean(env, + !strcmp(bufferData, theText) && bufferLength == sizeof(theText), + &returnValue)); + return returnValue; +} + +static napi_value staticBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + NAPI_CALL(env, + napi_create_external_buffer( + env, sizeof(theText), (void*)theText, noopDeleter, + NULL /* finalize_hint */, &theBuffer)); + return theBuffer; +} + +static napi_value malignFinalizerBuffer(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NAPI_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value finalizer = args[0]; + napi_valuetype finalizer_valuetype; + NAPI_CALL(env, napi_typeof(env, finalizer, &finalizer_valuetype)); + NAPI_ASSERT(env, finalizer_valuetype == napi_function, "Wrong type of first argument"); + napi_ref finalizer_ref; + NAPI_CALL(env, napi_create_reference(env, finalizer, 1, &finalizer_ref)); + + napi_value theBuffer; + NAPI_CALL( + env, + napi_create_external_buffer(env, + sizeof(theText), + (void*)theText, + malignDeleter, + finalizer_ref, // finalize_hint + &theBuffer)); + return theBuffer; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_value theValue; + + NAPI_CALL(env, + napi_create_string_utf8(env, theText, sizeof(theText), &theValue)); + NAPI_CALL(env, + napi_set_named_property(env, exports, "theText", theValue)); + + napi_property_descriptor methods[] = { + DECLARE_NAPI_PROPERTY("newBuffer", newBuffer), + DECLARE_NAPI_PROPERTY("newExternalBuffer", newExternalBuffer), + DECLARE_NAPI_PROPERTY("getDeleterCallCount", getDeleterCallCount), + DECLARE_NAPI_PROPERTY("copyBuffer", copyBuffer), + DECLARE_NAPI_PROPERTY("bufferHasInstance", bufferHasInstance), + DECLARE_NAPI_PROPERTY("bufferInfo", bufferInfo), + DECLARE_NAPI_PROPERTY("staticBuffer", staticBuffer), + DECLARE_NAPI_PROPERTY("malignFinalizerBuffer", malignFinalizerBuffer), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(methods) / sizeof(methods[0]), methods)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/packages/test/buffer/buffer.finalizer.test.js b/packages/test/buffer/buffer.finalizer.test.js new file mode 100644 index 00000000..0efb049b --- /dev/null +++ b/packages/test/buffer/buffer.finalizer.test.js @@ -0,0 +1,31 @@ +/* eslint-disable camelcase */ +'use strict' +const common = require('../common') +const tick = require('util').promisify(require('../tick')) +const assert = require('assert') +const { load } = require('../util') + +process.on('uncaughtException', common.mustCall((err) => { + assert.throws(() => { throw err }, /finalizer error/) +})) + +async function main () { + const binding = await load('buffer') + + await (async function () { + // eslint-disable-next-line no-lone-blocks + { + binding.malignFinalizerBuffer(common.mustCall(() => { + throw new Error('finalizer error') + })) + } + global.gc({ type: 'minor' }) + await tick(common.platformTimeout(100)) + global.gc() + await tick(common.platformTimeout(100)) + global.gc() + await tick(common.platformTimeout(100)) + })().then(common.mustCall()) +} + +module.exports = main() diff --git a/packages/test/buffer/buffer.test.js b/packages/test/buffer/buffer.test.js new file mode 100644 index 00000000..1736331d --- /dev/null +++ b/packages/test/buffer/buffer.test.js @@ -0,0 +1,32 @@ +'use strict' +const assert = require('assert') +const common = require('../common') +const { load } = require('../util') +const tick = require('util').promisify(require('../tick')) + +// eslint-disable-next-line camelcase +module.exports = load('buffer').then(async binding => { + await (async function () { + assert.strictEqual(binding.newBuffer().toString(), binding.theText) + assert.strictEqual(binding.newExternalBuffer().toString(), binding.theText) + console.log('gc1') + global.gc() + assert.strictEqual(binding.getDeleterCallCount(), 0) + await tick(10) + assert.strictEqual(binding.getDeleterCallCount(), 1) + assert.strictEqual(binding.copyBuffer().toString(), binding.theText) + + let buffer = binding.staticBuffer() + assert.strictEqual(binding.bufferHasInstance(buffer), true) + assert.strictEqual(binding.bufferInfo(buffer), true) + buffer = null + global.gc() + assert.strictEqual(binding.getDeleterCallCount(), 1) + await tick(10) + console.log('gc2') + assert.strictEqual(binding.getDeleterCallCount(), 2) + })().then(common.mustCall()) + + process.externalBuffer = binding.newExternalBuffer() + assert.strictEqual(process.externalBuffer.toString(), binding.theText) +}) diff --git a/packages/test/cgen.config.js b/packages/test/cgen.config.js index 32b94da2..3704ce38 100644 --- a/packages/test/cgen.config.js +++ b/packages/test/cgen.config.js @@ -145,6 +145,7 @@ module.exports = function (_options, { isDebug, isEmscripten }) { createTarget('number', ['./number/binding.c'], true), createTarget('symbol', ['./symbol/binding.c'], true), createTarget('typedarray', ['./typedarray/binding.c'], true), + createTarget('buffer', ['./buffer/binding.c'], false), ...(isEmscripten ? [createTarget('emnapitest', ['./emnapitest/binding.c'], true)] : []), createTarget('version', ['./version/binding.c']), diff --git a/packages/test/script/test.js b/packages/test/script/test.js index abbbf820..e49e7c83 100644 --- a/packages/test/script/test.js +++ b/packages/test/script/test.js @@ -2,8 +2,9 @@ const { spawnSync } = require('child_process') const glob = require('glob') const cwd = require('path').join(__dirname, '..') +const subdir = process.argv[2] || '**' -const files = glob.sync('**/*.test.js', { +const files = glob.sync(`${subdir}/*.test.js`, { cwd, ignore: process.env.EMNAPI_TEST_NATIVE ? ['**/{emnapitest,node-addon-api}/**/*'] diff --git a/packages/test/tick.js b/packages/test/tick.js new file mode 100644 index 00000000..3d9cf659 --- /dev/null +++ b/packages/test/tick.js @@ -0,0 +1,13 @@ +'use strict' +require('./common') + +module.exports = function tick (x, cb) { + function ontick () { + if (--x === 0) { + if (typeof cb === 'function') cb() + } else { + setImmediate(ontick) + } + } + setImmediate(ontick) +} From f04333809b9f992c95a4d7c85c3b2ff79df9139e Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 5 Jan 2023 14:29:19 +0000 Subject: [PATCH 5/7] Various fixes --- packages/emnapi/src/value/convert2c.ts | 3 ++- packages/emnapi/src/value/create.ts | 19 ++++++++++++------- packages/test/buffer/binding.c | 19 ++++++++++++++----- packages/test/buffer/buffer.test.js | 11 ++--------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/emnapi/src/value/convert2c.ts b/packages/emnapi/src/value/convert2c.ts index 8dc867af..f511c8f7 100644 --- a/packages/emnapi/src/value/convert2c.ts +++ b/packages/emnapi/src/value/convert2c.ts @@ -189,6 +189,7 @@ function napi_get_typedarray_info ( return envObject.clearLastError() } +declare const _napi_get_typedarray_info: typeof napi_get_typedarray_info function napi_get_buffer_info ( env: napi_env, buffer: napi_value, @@ -202,7 +203,7 @@ function napi_get_buffer_info ( if (!handle.isBuffer()) { return envObject.setLastError(napi_status.napi_invalid_arg) } - return napi_get_typedarray_info(env, buffer, 0, length, data, 0, 0) + return _napi_get_typedarray_info(env, buffer, 0, length, data, 0, 0) } function napi_get_dataview_info ( diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index 33ad3a79..5a544767 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -267,18 +267,21 @@ function napi_create_typedarray ( }) } -function napi_create_buffer ( +function napi_create_buffer( env: napi_env, size: size_t, data: Pointer>, result: Pointer ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let value: number + $PREAMBLE!(env, (envObject) => { $CHECK_ARG!(envObject, result) $from64('result') const arrayBuffer = createArrayBuffer(size, data) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const value = emnapiCtx.addToCurrentScope(arrayBuffer).id + const buffer = Buffer.from(arrayBuffer) + value = emnapiCtx.addToCurrentScope(buffer).id $makeSetValue('result', 0, 'value', '*') return envObject.getReturnStatus() }) @@ -299,13 +302,15 @@ function napi_create_buffer_copy( $from64('result_data') $from64('result') const arrayBuffer = createArrayBuffer(length, result_data) - new Uint8Array(arrayBuffer).set(HEAPU8.subarray(data, data + length)) - value = emnapiCtx.addToCurrentScope(arrayBuffer).id + const buffer = Buffer.from(arrayBuffer) + buffer.set(HEAPU8.subarray(data, data + length)) + value = emnapiCtx.addToCurrentScope(buffer).id $makeSetValue('result', 0, 'value', '*') return envObject.getReturnStatus() }) } +declare const _napi_create_external_arraybuffer: typeof napi_create_external_arraybuffer function napi_create_external_buffer( env: napi_env, length: size_t, @@ -315,7 +320,7 @@ function napi_create_external_buffer( result: Pointer ) { $PREAMBLE!(env, (envObject) => { - const status = napi_create_external_arraybuffer(env, data, length, finalize_cb, finalize_hint, result) + const status = _napi_create_external_arraybuffer(env, data, length, finalize_cb, finalize_hint, result) if (status !== napi_status.napi_ok) { return status } @@ -390,10 +395,10 @@ emnapiImplement('napi_create_array_with_length', 'ippp', napi_create_array_with_ emnapiImplement('napi_create_arraybuffer', 'ipppp', napi_create_arraybuffer, ['$createArrayBuffer']) emnapiImplement('napi_create_buffer', 'ippp', napi_create_buffer, ['$createArrayBuffer']) emnapiImplement('napi_create_buffer_copy', 'ippppp', napi_create_buffer_copy, ['$createArrayBuffer']) -emnapiImplement('napi_create_external_buffer', 'ipppppp', napi_create_external_buffer, ['napi_create_external_arraybuffer']) emnapiImplement('napi_create_date', 'ipdp', napi_create_date) emnapiImplement('napi_create_external', 'ippppp', napi_create_external) emnapiImplement('napi_create_external_arraybuffer', 'ipppppp', napi_create_external_arraybuffer, ['$emnapiWrap']) +emnapiImplement('napi_create_external_buffer', 'ipppppp', napi_create_external_buffer, ['napi_create_external_arraybuffer']) emnapiImplement('napi_create_object', 'ipp', napi_create_object) emnapiImplement('napi_create_symbol', 'ippp', napi_create_symbol) emnapiImplement('napi_create_typedarray', 'ipipppp', napi_create_typedarray) diff --git a/packages/test/buffer/binding.c b/packages/test/buffer/binding.c index 7f251865..5cb8fd4f 100644 --- a/packages/test/buffer/binding.c +++ b/packages/test/buffer/binding.c @@ -1,3 +1,7 @@ +#ifdef __EMSCRIPTEN__ +#include +#endif + #include #include #include @@ -6,6 +10,9 @@ static const char theText[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; +// TODO: figure out how upstream tests manage without truncating the final \0 here. +const unsigned int theTextSize = sizeof(theText) - 1; + static int deleterCallCount = 0; static void deleteTheText(napi_env env, void* data, void* finalize_hint) { NAPI_ASSERT_RETURN_VOID( @@ -36,13 +43,15 @@ static void malignDeleter(napi_env env, void* data, void* finalize_hint) { static napi_value newBuffer(napi_env env, napi_callback_info info) { napi_value theBuffer; char* theCopy; - const unsigned int kBufferSize = sizeof(theText); NAPI_CALL(env, napi_create_buffer( - env, sizeof(theText), (void**)(&theCopy), &theBuffer)); + env, theTextSize, (void**)(&theCopy), &theBuffer)); NAPI_ASSERT(env, theCopy, "Failed to copy static text for newBuffer"); - memcpy(theCopy, theText, kBufferSize); + memcpy(theCopy, theText, theTextSize); +#ifdef __EMSCRIPTEN__ + emnapi_sync_memory(env, theBuffer, 0, theCopy, NAPI_AUTO_LENGTH, false); +#endif return theBuffer; } @@ -54,7 +63,7 @@ static napi_value newExternalBuffer(napi_env env, napi_callback_info info) { env, theCopy, "Failed to copy static text for newExternalBuffer"); NAPI_CALL(env, napi_create_external_buffer( - env, sizeof(theText), theCopy, deleteTheText, + env, theTextSize, theCopy, deleteTheText, NULL /* finalize_hint */, &theBuffer)); return theBuffer; @@ -69,7 +78,7 @@ static napi_value getDeleterCallCount(napi_env env, napi_callback_info info) { static napi_value copyBuffer(napi_env env, napi_callback_info info) { napi_value theBuffer; NAPI_CALL(env, napi_create_buffer_copy( - env, sizeof(theText), theText, NULL, &theBuffer)); + env, theTextSize, theText, NULL, &theBuffer)); return theBuffer; } diff --git a/packages/test/buffer/buffer.test.js b/packages/test/buffer/buffer.test.js index 1736331d..b270a2aa 100644 --- a/packages/test/buffer/buffer.test.js +++ b/packages/test/buffer/buffer.test.js @@ -9,22 +9,15 @@ module.exports = load('buffer').then(async binding => { await (async function () { assert.strictEqual(binding.newBuffer().toString(), binding.theText) assert.strictEqual(binding.newExternalBuffer().toString(), binding.theText) - console.log('gc1') - global.gc() assert.strictEqual(binding.getDeleterCallCount(), 0) - await tick(10) - assert.strictEqual(binding.getDeleterCallCount(), 1) + await common.gcUntil(() => binding.getDeleterCallCount() === 1) assert.strictEqual(binding.copyBuffer().toString(), binding.theText) let buffer = binding.staticBuffer() assert.strictEqual(binding.bufferHasInstance(buffer), true) assert.strictEqual(binding.bufferInfo(buffer), true) buffer = null - global.gc() - assert.strictEqual(binding.getDeleterCallCount(), 1) - await tick(10) - console.log('gc2') - assert.strictEqual(binding.getDeleterCallCount(), 2) + await common.gcUntil(() => binding.getDeleterCallCount() === 2) })().then(common.mustCall()) process.externalBuffer = binding.newExternalBuffer() From dabe0624957451c8c028c7a8d223a716d71760c2 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 5 Jan 2023 17:06:05 +0000 Subject: [PATCH 6/7] Remove the -1 from text length --- packages/test/buffer/binding.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/test/buffer/binding.c b/packages/test/buffer/binding.c index 5cb8fd4f..cd23eeff 100644 --- a/packages/test/buffer/binding.c +++ b/packages/test/buffer/binding.c @@ -10,8 +10,7 @@ static const char theText[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; -// TODO: figure out how upstream tests manage without truncating the final \0 here. -const unsigned int theTextSize = sizeof(theText) - 1; +const unsigned int theTextSize = sizeof(theText); static int deleterCallCount = 0; static void deleteTheText(napi_env env, void* data, void* finalize_hint) { From 67dfc496aaef86ce58de085646489689a54ecfb1 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 6 Jan 2023 03:05:49 +0000 Subject: [PATCH 7/7] Check for Buffer presence --- packages/runtime/src/Handle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/Handle.ts b/packages/runtime/src/Handle.ts index 76d8931a..b9399a2e 100644 --- a/packages/runtime/src/Handle.ts +++ b/packages/runtime/src/Handle.ts @@ -64,7 +64,7 @@ export class Handle { } public isBuffer (): boolean { - return !this.isEmpty() && (this.value instanceof Buffer) + return !this.isEmpty() && typeof Buffer === 'function' && (this.value instanceof Buffer) } public isDataView (): boolean {