From c26e67dfae6ce6aa595e31af68a377e62d3d698c Mon Sep 17 00:00:00 2001 From: dave-365 Date: Wed, 4 Nov 2026 15:57:33 +0530 Subject: [PATCH 1/4] feat(buffer): support C++20 std::span overloads --- napi-inl.h | 121 +++++++++++++++++++++++++++++++++++++++++++ napi.h | 73 ++++++++++++++++++++++++++ test/array_buffer.cc | 45 ++++++++++++++++ test/array_buffer.js | 13 +++++ test/binding.gyp | 15 ++++++ test/buffer.cc | 61 ++++++++++++++++++++++ test/buffer.js | 20 +++++++ test/common/index.js | 14 +++-- 8 files changed, 358 insertions(+), 4 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 59f1ca14e..6078b02a6 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2278,6 +2278,33 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, return ArrayBuffer(env, value); } + +#if defined(__cpp_lib_span) +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, std::span span) { + return ArrayBuffer::New(env, span.data(), span.size_bytes()); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback) { + return ArrayBuffer::New( + env, span.data(), span.size_bytes(), std::move(finalizeCallback)); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint) { + return ArrayBuffer::New(env, + span.data(), + span.size_bytes(), + std::move(finalizeCallback), + finalizeHint); +} +#endif // defined(__cpp_lib_span) #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { @@ -2311,6 +2338,13 @@ inline size_t ArrayBuffer::ByteLength() { return length; } +#if defined(__cpp_lib_span) +template +inline std::span ArrayBuffer::Span() { + return std::span(reinterpret_cast(Data()), ByteLength() / sizeof(T)); +} +#endif // defined(__cpp_lib_span) + #if NAPI_VERSION >= 7 inline bool ArrayBuffer::IsDetached() const { bool detached; @@ -2764,6 +2798,18 @@ inline const T* TypedArrayOf::Data() const { return _data; } +#if defined(__cpp_lib_span) +template +inline std::span TypedArrayOf::Span() { + return std::span(Data(), ElementLength()); +} + +template +inline std::span TypedArrayOf::Span() const { + return std::span(Data(), ElementLength()); +} +#endif // defined(__cpp_lib_span) + //////////////////////////////////////////////////////////////////////////////// // Function class //////////////////////////////////////////////////////////////////////////////// @@ -3176,6 +3222,34 @@ inline Buffer Buffer::New(napi_env env, } return Buffer(env, value); } + +#if defined(__cpp_lib_span) +template +template +inline Buffer Buffer::New(napi_env env, std::span span) { + return Buffer::New(env, span.data(), span.size()); +} + +template +template +inline Buffer Buffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback) { + return Buffer::New( + env, span.data(), span.size(), std::move(finalizeCallback)); +} + +template +template +inline Buffer Buffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint) { + return Buffer::New( + env, span.data(), span.size(), std::move(finalizeCallback), finalizeHint); +} +#endif // defined(__cpp_lib_span) + #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED template @@ -3274,6 +3348,46 @@ inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { return Buffer(env, value); } +#if defined(__cpp_lib_span) +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, std::span span) { + return Buffer::NewOrCopy(env, span.data(), span.size()); +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback) { + return Buffer::NewOrCopy( + env, span.data(), span.size(), std::move(finalizeCallback)); +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint) { + return Buffer::NewOrCopy( + env, span.data(), span.size(), std::move(finalizeCallback), finalizeHint); +} + +template +template +inline Buffer Buffer::Copy(napi_env env, + std::span span) { + return Buffer::Copy(env, span.data(), span.size()); +} + +template +template +inline Buffer Buffer::Copy(napi_env env, std::span span) { + return Buffer::Copy(env, span.data(), span.size()); +} +#endif // defined(__cpp_lib_span) + template inline void Buffer::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); @@ -3301,6 +3415,13 @@ inline T* Buffer::Data() const { return reinterpret_cast(const_cast(Uint8Array::Data())); } +#if defined(__cpp_lib_span) +template +inline std::span Buffer::Span() const { + return std::span(Data(), Length()); +} +#endif // defined(__cpp_lib_span) + //////////////////////////////////////////////////////////////////////////////// // Error class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 6b4692c7d..28dd0946d 100644 --- a/napi.h +++ b/napi.h @@ -23,6 +23,11 @@ #include #include #include +#if defined(__has_include) +#if __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +#include +#endif +#endif // VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known // good version) @@ -1364,6 +1369,22 @@ class ArrayBuffer : public Object { Hint* finalizeHint ///< Hint (second parameter) to be passed to the ///< finalize callback ); + +#if defined(__cpp_lib_span) + template + static ArrayBuffer New(napi_env env, std::span span); + + template + static ArrayBuffer New(napi_env env, + std::span span, + Finalizer finalizeCallback); + + template + static ArrayBuffer New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // defined(__cpp_lib_span) #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED static void CheckCast(napi_env env, napi_value value); @@ -1375,6 +1396,11 @@ class ArrayBuffer : public Object { void* Data(); ///< Gets a pointer to the data buffer. size_t ByteLength(); ///< Gets the length of the array buffer in bytes. +#if defined(__cpp_lib_span) + template + std::span Span(); +#endif // defined(__cpp_lib_span) + #if NAPI_VERSION >= 7 bool IsDetached() const; void Detach(); @@ -1557,6 +1583,11 @@ class TypedArrayOf : public TypedArray { /// `ArrayBuffer`. const T* Data() const; +#if defined(__cpp_lib_span) + std::span Span(); + std::span Span() const; +#endif // defined(__cpp_lib_span) + private: T* _data; @@ -1781,6 +1812,22 @@ class Buffer : public Uint8Array { size_t length, Finalizer finalizeCallback, Hint* finalizeHint); + +#if defined(__cpp_lib_span) + template + static Buffer New(napi_env env, std::span span); + + template + static Buffer New(napi_env env, + std::span span, + Finalizer finalizeCallback); + + template + static Buffer New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // defined(__cpp_lib_span) #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED static Buffer NewOrCopy(napi_env env, T* data, size_t length); @@ -1799,6 +1846,28 @@ class Buffer : public Uint8Array { Finalizer finalizeCallback, Hint* finalizeHint); +#if defined(__cpp_lib_span) + template + static Buffer NewOrCopy(napi_env env, std::span span); + + template + static Buffer NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback); + + template + static Buffer NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); + + template + static Buffer Copy(napi_env env, std::span span); + + template + static Buffer Copy(napi_env env, std::span span); +#endif // defined(__cpp_lib_span) + static Buffer Copy(napi_env env, const T* data, size_t length); static void CheckCast(napi_env env, napi_value value); @@ -1808,6 +1877,10 @@ class Buffer : public Uint8Array { size_t Length() const; T* Data() const; +#if defined(__cpp_lib_span) + std::span Span() const; +#endif // defined(__cpp_lib_span) + private: }; diff --git a/test/array_buffer.cc b/test/array_buffer.cc index 2b07bd250..5e26caef0 100644 --- a/test/array_buffer.cc +++ b/test/array_buffer.cc @@ -220,6 +220,42 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { } } +#if defined(__cpp_lib_span) +Value CreateBufferFromSpan(const CallbackInfo& info) { + std::span span(testData, testLength); + ArrayBuffer buffer = ArrayBuffer::New(info.Env(), span); + if (buffer.ByteLength() != testLength || buffer.Data() != testData) { + Error::New(info.Env(), "Incorrect array buffer created from span.") + .ThrowAsJavaScriptException(); + return Value(); + } + InitData(testData, testLength); + return buffer; +} + +Value CheckArrayBufferSpan(const CallbackInfo& info) { + ArrayBuffer buffer = info[0].As(); + std::span span = buffer.Span(); + if (span.size() != testLength || span.data() != buffer.Data()) { + Error::New(info.Env(), "Incorrect array buffer span.") + .ThrowAsJavaScriptException(); + return Boolean::New(info.Env(), false); + } + return Boolean::New(info.Env(), true); +} + +Value CheckTypedArraySpan(const CallbackInfo& info) { + Uint8Array array = info[0].As(); + std::span span = array.Span(); + if (span.size() != testLength || span.data() != array.Data()) { + Error::New(info.Env(), "Incorrect typed array span.") + .ThrowAsJavaScriptException(); + return Boolean::New(info.Env(), false); + } + return Boolean::New(info.Env(), true); +} +#endif // defined(__cpp_lib_span) + } // end anonymous namespace Object InitArrayBuffer(Env env) { @@ -239,5 +275,14 @@ Object InitArrayBuffer(Env env) { exports["checkDetachUpdatesData"] = Function::New(env, CheckDetachUpdatesData); +#if defined(__cpp_lib_span) + exports["hasSpanSupport"] = Boolean::New(env, true); + exports["createBufferFromSpan"] = Function::New(env, CreateBufferFromSpan); + exports["checkArrayBufferSpan"] = Function::New(env, CheckArrayBufferSpan); + exports["checkTypedArraySpan"] = Function::New(env, CheckTypedArraySpan); +#else + exports["hasSpanSupport"] = Boolean::New(env, false); +#endif + return exports; } diff --git a/test/array_buffer.js b/test/array_buffer.js index 08547ed7e..d44107db5 100644 --- a/test/array_buffer.js +++ b/test/array_buffer.js @@ -65,6 +65,19 @@ function test (binding) { // Let C++ detach the ArrayBuffer. const extBuffer = binding.arraybuffer.createExternalBuffer(); binding.arraybuffer.checkDetachUpdatesData(extBuffer); + }, + + 'std::span ArrayBuffer and TypedArray tests', + () => { + if (!binding.arraybuffer.hasSpanSupport) return; + + const fromSpan = binding.arraybuffer.createBufferFromSpan(); + binding.arraybuffer.checkBuffer(fromSpan); + assert.ok(fromSpan instanceof ArrayBuffer); + assert.ok(binding.arraybuffer.checkArrayBufferSpan(fromSpan)); + + const uint8 = new Uint8Array(fromSpan); + assert.ok(binding.arraybuffer.checkTypedArraySpan(uint8)); } ]); } diff --git a/test/binding.gyp b/test/binding.gyp index 9ff334b64..381d5e153 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -118,6 +118,21 @@ 'sources': ['>@(build_sources)'], 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] }, + { + 'target_name': 'binding_cpp20', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'], + 'cflags_cc': ['-std=c++20'], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'AdditionalOptions': ['/std:c++20'] + } + }, + 'xcode_settings': { + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++20' + } + }, { 'target_name': 'binding_except_all', 'dependencies': ['../node_addon_api.gyp:node_addon_api_except_all'], diff --git a/test/buffer.cc b/test/buffer.cc index c10300dc7..cfff78e7e 100644 --- a/test/buffer.cc +++ b/test/buffer.cc @@ -160,6 +160,55 @@ Value GetFinalizeCount(const CallbackInfo& info) { return Number::New(info.Env(), finalizeCount); } +#if defined(__cpp_lib_span) +Value CreateBufferFromSpan(const CallbackInfo& info) { + std::span span(testData, testLength); + Buffer buffer = Buffer::New(info.Env(), span); + if (buffer.Length() != testLength || buffer.Data() != testData) { + Error::New(info.Env(), "Incorrect buffer created from span.") + .ThrowAsJavaScriptException(); + return Value(); + } + InitData(testData, testLength); + return buffer; +} + +Value CreateBufferCopyFromSpan(const CallbackInfo& info) { + InitData(testData, testLength); + std::span span(testData, testLength); + Buffer buffer = Buffer::Copy(info.Env(), span); + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length from span copy.") + .ThrowAsJavaScriptException(); + return Value(); + } + return buffer; +} + +Value CreateBufferNewOrCopyFromSpan(const CallbackInfo& info) { + std::span span(testData, testLength); + Buffer buffer = Buffer::NewOrCopy(info.Env(), span); + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length from NewOrCopy span.") + .ThrowAsJavaScriptException(); + return Value(); + } + InitData(testData, testLength); + return buffer; +} + +Value CheckBufferSpan(const CallbackInfo& info) { + Buffer buffer = info[0].As>(); + std::span span = buffer.Span(); + if (span.size() != testLength || span.data() != buffer.Data()) { + Error::New(info.Env(), "Incorrect buffer span.") + .ThrowAsJavaScriptException(); + return Boolean::New(info.Env(), false); + } + return Boolean::New(info.Env(), true); +} +#endif // defined(__cpp_lib_span) + } // end anonymous namespace Object InitBuffer(Env env) { @@ -181,5 +230,17 @@ Object InitBuffer(Env env) { exports["checkBuffer"] = Function::New(env, CheckBuffer); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); +#if defined(__cpp_lib_span) + exports["hasSpanSupport"] = Boolean::New(env, true); + exports["createBufferFromSpan"] = Function::New(env, CreateBufferFromSpan); + exports["createBufferCopyFromSpan"] = + Function::New(env, CreateBufferCopyFromSpan); + exports["createBufferNewOrCopyFromSpan"] = + Function::New(env, CreateBufferNewOrCopyFromSpan); + exports["checkBufferSpan"] = Function::New(env, CheckBufferSpan); +#else + exports["hasSpanSupport"] = Boolean::New(env, false); +#endif + return exports; } diff --git a/test/buffer.js b/test/buffer.js index 3f49201a7..3165df5a0 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -25,6 +25,26 @@ function test (binding) { assert.ok(test instanceof Buffer); }, + 'std::span Buffer tests', + () => { + if (!binding.buffer.hasSpanSupport) return; + + const fromSpan = binding.buffer.createBufferFromSpan(); + binding.buffer.checkBuffer(fromSpan); + assert.ok(fromSpan instanceof Buffer); + assert.ok(binding.buffer.checkBufferSpan(fromSpan)); + + const copyFromSpan = binding.buffer.createBufferCopyFromSpan(); + binding.buffer.checkBuffer(copyFromSpan); + assert.ok(copyFromSpan instanceof Buffer); + assert.ok(binding.buffer.checkBufferSpan(copyFromSpan)); + + const newOrCopySpan = binding.buffer.createBufferNewOrCopyFromSpan(); + binding.buffer.checkBuffer(newOrCopySpan); + assert.ok(newOrCopySpan instanceof Buffer); + assert.ok(binding.buffer.checkBufferSpan(newOrCopySpan)); + }, + 'External Buffer', () => { const test = binding.buffer.createExternalBuffer(); diff --git a/test/common/index.js b/test/common/index.js index 2469151ef..0fe628272 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -161,12 +161,15 @@ exports.whichBuildType = whichBuildType; exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); + const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) - ].map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) + ].filter(it => fs.existsSync(path.resolve(__dirname, it))) + .map(it => require.resolve(it)); for (const item of bindings) { await Promise.resolve(test(require(item), { bindingPath: item })) @@ -177,12 +180,15 @@ exports.runTest = async function (test, buildType, buildPathRoot = process.env.B exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); + const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) - ].map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) + ].filter(it => fs.existsSync(path.resolve(__dirname, it))) + .map(it => require.resolve(it)); for (const item of bindings) { await test(item); From d6f95c724e429dc47d531dfc46d52f5f40ff4d22 Mon Sep 17 00:00:00 2001 From: vasudevan Date: Sat, 7 Nov 2026 23:10:22 +0530 Subject: [PATCH 2/4] test(concepts): isolate binding_cpp20 target and test runner --- test/binding.gyp | 9 ++++++--- test/buffer.cc | 9 +++++++++ test/buffer.js | 23 +++++++++++++++++++++-- test/common/index.js | 14 ++++---------- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/test/binding.gyp b/test/binding.gyp index 381d5e153..59e2b5301 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -92,6 +92,9 @@ 'build_sources_type_check': [ 'value_type_cast.cc' ], + 'build_sources_cpp20': [ + 'buffer.cc' + ], 'want_coverage': '@(build_sources)'], - 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'], + 'sources': ['>@(build_sources_cpp20)'], + 'defines': ['BUILDING_CPP20_MODULE'], 'cflags_cc': ['-std=c++20'], 'msvs_settings': { 'VCCLCompilerTool': { - 'AdditionalOptions': ['/std:c++20'] + 'AdditionalOptions': ['/std:c++20', '/Zc:__cplusplus'] } }, 'xcode_settings': { diff --git a/test/buffer.cc b/test/buffer.cc index cfff78e7e..030d86e7d 100644 --- a/test/buffer.cc +++ b/test/buffer.cc @@ -244,3 +244,12 @@ Object InitBuffer(Env env) { return exports; } + +#if defined(BUILDING_CPP20_MODULE) +static Object InitBufferModule(Env env, Object exports) { + exports.Set("buffer", InitBuffer(env)); + return exports; +} + +NODE_API_MODULE(addon, InitBufferModule) +#endif diff --git a/test/buffer.js b/test/buffer.js index 3165df5a0..b0a808449 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -1,11 +1,27 @@ 'use strict'; const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const common = require('./common'); const testUtil = require('./testUtil'); -module.exports = require('./common').runTest(test); +module.exports = common.runTestWithBuildType(async (buildType) => { + await common.runTest(test, buildType); -function test (binding) { + const cpp20BindingPath = [ + path.resolve(__dirname, `build/${buildType}/binding_cpp20.node`), + path.resolve(__dirname, `../build/${buildType}/binding_cpp20.node`) + ].find(p => fs.existsSync(p)); + + if (cpp20BindingPath) { + await Promise.resolve( + test(require(cpp20BindingPath), { bindingPath: cpp20BindingPath }) + ).finally(common.mustCall()); + } +}); + +function test (binding, context) { return testUtil.runGCTests([ 'Internal Buffer', () => { @@ -27,6 +43,9 @@ function test (binding) { 'std::span Buffer tests', () => { + if (context && context.bindingPath && context.bindingPath.includes('binding_cpp20')) { + assert.strictEqual(binding.buffer.hasSpanSupport, true); + } if (!binding.buffer.hasSpanSupport) return; const fromSpan = binding.buffer.createBufferFromSpan(); diff --git a/test/common/index.js b/test/common/index.js index 0fe628272..2469151ef 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -161,15 +161,12 @@ exports.whichBuildType = whichBuildType; exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); - const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) - ].filter(it => fs.existsSync(path.resolve(__dirname, it))) - .map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) + ].map(it => require.resolve(it)); for (const item of bindings) { await Promise.resolve(test(require(item), { bindingPath: item })) @@ -180,15 +177,12 @@ exports.runTest = async function (test, buildType, buildPathRoot = process.env.B exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); - const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) - ].filter(it => fs.existsSync(path.resolve(__dirname, it))) - .map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) + ].map(it => require.resolve(it)); for (const item of bindings) { await test(item); From eda8f14dd1a9d53c2667e2b67ba9632ad0d6d505 Mon Sep 17 00:00:00 2001 From: dave-365 Date: Fri, 13 Nov 2026 20:36:28 +0530 Subject: [PATCH 3/4] docs: add documentation for std::span overloads and add SharedArrayBuffer::Span --- doc/array_buffer.md | 94 ++++++++++++++++++ doc/buffer.md | 190 +++++++++++++++++++++++++++++++++++++ doc/shared_array_buffer.md | 11 +++ doc/typed_array_of.md | 11 +++ napi-inl.h | 7 ++ napi.h | 5 + test/binding.gyp | 3 +- test/buffer.cc | 3 + 8 files changed, 323 insertions(+), 1 deletion(-) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index de05e55b3..146fd449e 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -109,6 +109,89 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, Returns a new `Napi::ArrayBuffer` instance. +### New + +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined or when C++20 `std::span` is not available, this method is not available. +> See [External Buffer][] for more information. + +Wraps the provided external data span into a new `Napi::ArrayBuffer` instance. + +The `Napi::ArrayBuffer` instance does not assume ownership for the data and +expects it to be valid for the lifetime of the instance. Since the +`Napi::ArrayBuffer` is subject to garbage collection this overload is only +suitable for data which is static and never needs to be freed. This factory +method will not provide the caller with an opportunity to free the data when the +`Napi::ArrayBuffer` gets garbage-collected. If you need to free the data +retained by the `Napi::ArrayBuffer` object please use other variants of the +`Napi::ArrayBuffer::New` factory method that accept `Napi::Finalizer`. +See [Finalization][] for more details. + +```cpp +template +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, std::span span); +``` + +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. +- `[in] span`: The `std::span` referencing the external data to wrap. + +Returns a new `Napi::ArrayBuffer` instance. + +### New + +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined or when C++20 `std::span` is not available, this method is not available. +> See [External Buffer][] for more information. + +Wraps the provided external data span into a new `Napi::ArrayBuffer` instance. + +The `Napi::ArrayBuffer` instance does not assume ownership for the data and +expects it to be valid for the lifetime of the instance. The data can only be +freed once the `finalizeCallback` is invoked to indicate that the +`Napi::ArrayBuffer` has been released. + +```cpp +template +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback); +``` + +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. +- `[in] span`: The `std::span` referencing the external data to wrap. +- `[in] finalizeCallback`: A function called when the engine destroys the + `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*)`. + See [Finalization][] for more details. + +Returns a new `Napi::ArrayBuffer` instance. + +### New + +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined or when C++20 `std::span` is not available, this method is not available. +> See [External Buffer][] for more information. + +Wraps the provided external data span into a new `Napi::ArrayBuffer` instance. + +The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it +to be valid for the lifetime of the instance. The data can only be freed once +the `finalizeCallback` is invoked to indicate that the `Napi::ArrayBuffer` has been +released. + +```cpp +template +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +``` + +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. +- `[in] span`: The `std::span` referencing the external data to wrap. +- `[in] finalizeCallback`: A function called when the engine destroys the + `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*, + Hint*)`. See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. + +Returns a new `Napi::ArrayBuffer` instance. + ### Constructor Initializes an empty instance of the `Napi::ArrayBuffer` class. @@ -144,6 +227,17 @@ void* Napi::ArrayBuffer::Data() const; Returns a pointer the wrapped data. +### Span + +> When C++20 `std::span` is not available, this method is not available. + +```cpp +template +std::span Napi::ArrayBuffer::Span(); +``` + +Returns a `std::span` view over the wrapped data. + ### Detach ```cpp diff --git a/doc/buffer.md b/doc/buffer.md index 548400481..28050165b 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -105,6 +105,86 @@ static Napi::Buffer Napi::Buffer::New(napi_env env, Returns a new `Napi::Buffer` object. +### New + +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined or when C++20 `std::span` is not available, this method is not available. +> See [External Buffer][] for more information. + +Wraps the provided external data span into a new `Napi::Buffer` object. + +The `Napi::Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. Since the `Napi::Buffer` is subject +to garbage collection this overload is only suitable for data which is static +and never needs to be freed. This factory method will not provide the caller +with an opportunity to free the data when the `Napi::Buffer` gets +garbage-collected. If you need to free the data retained by the `Napi::Buffer` +object please use other variants of the `Napi::Buffer::New` factory method that +accept `Finalizer`. See [Finalization][] for more details. + +```cpp +template +static Napi::Buffer Napi::Buffer::New(napi_env env, std::span span); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to expose. + +Returns a new `Napi::Buffer` object. + +### New + +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined or when C++20 `std::span` is not available, this method is not available. +> See [External Buffer][] for more information. + +Wraps the provided external data span into a new `Napi::Buffer` object. + +The `Napi::Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. + +```cpp +template +static Napi::Buffer Napi::Buffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to expose. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. + +Returns a new `Napi::Buffer` object. + +### New + +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined or when C++20 `std::span` is not available, this method is not available. +> See [External Buffer][] for more information. + +Wraps the provided external data span into a new `Napi::Buffer` object. + +The `Napi::Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. + +```cpp +template +static Napi::Buffer Napi::Buffer::New(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to expose. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. + +Returns a new `Napi::Buffer` object. + ### NewOrCopy Wraps the provided external data into a new `Napi::Buffer` object. When the @@ -191,6 +271,87 @@ static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, Returns a new `Napi::Buffer` object. +### NewOrCopy + +> When C++20 `std::span` is not available, this method is not available. + +Wraps the provided external data span into a new `Napi::Buffer` object. When the +[external buffer][] is not supported, allocates a new `Napi::Buffer` object and +copies the provided external data into it. + +The `Napi::Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage +collection this overload is only suitable for data which is static and never +needs to be freed. + +```cpp +template +static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, std::span span); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to expose. + +Returns a new `Napi::Buffer` object. + +### NewOrCopy + +> When C++20 `std::span` is not available, this method is not available. + +Wraps the provided external data span into a new `Napi::Buffer` object. When the +[external buffer][] is not supported, allocates a new `Napi::Buffer` object and +copies the provided external data into it and the `finalizeCallback` is invoked +immediately. + +The `Napi::Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. + +```cpp +template +static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to expose. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. + +Returns a new `Napi::Buffer` object. + +### NewOrCopy + +> When C++20 `std::span` is not available, this method is not available. + +Wraps the provided external data span into a new `Napi::Buffer` object. When the +[external buffer][] is not supported, allocates a new `Napi::Buffer` object and +copies the provided external data into it and the `finalizeCallback` is invoked +immediately. + +The `Napi::Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. + +```cpp +template +static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, + std::span span, + Finalizer finalizeCallback, + Hint* finalizeHint); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to expose. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. + +Returns a new `Napi::Buffer` object. + ### Copy Allocates a new `Napi::Buffer` object and copies the provided external data into it. @@ -205,6 +366,25 @@ static Napi::Buffer Napi::Buffer::Copy(napi_env env, const T* data, size_t le Returns a new `Napi::Buffer` object containing a copy of the data. +### Copy + +> When C++20 `std::span` is not available, this method is not available. + +Allocates a new `Napi::Buffer` object and copies the provided external data span into it. + +```cpp +template +static Napi::Buffer Napi::Buffer::Copy(napi_env env, std::span span); + +template +static Napi::Buffer Napi::Buffer::Copy(napi_env env, std::span span); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] span`: The `std::span` referencing the external data to copy. + +Returns a new `Napi::Buffer` object containing a copy of the data. + ### Constructor Initializes an empty instance of the `Napi::Buffer` class. @@ -240,6 +420,16 @@ size_t Napi::Buffer::Length() const; Returns the number of `T` elements in the external data. +### Span + +> When C++20 `std::span` is not available, this method is not available. + +```cpp +std::span Napi::Buffer::Span() const; +``` + +Returns a `std::span` view over the buffer data. + [`Napi::Uint8Array`]: ./typed_array_of.md [External Buffer]: ./external_buffer.md [Finalization]: ./finalization.md diff --git a/doc/shared_array_buffer.md b/doc/shared_array_buffer.md index 872dbb408..147425c6a 100644 --- a/doc/shared_array_buffer.md +++ b/doc/shared_array_buffer.md @@ -62,4 +62,15 @@ void* Napi::SharedArrayBuffer::Data() const; Returns a pointer the wrapped data. +### Span + +> When C++20 `std::span` is not available, this method is not available. + +```cpp +template +std::span Napi::SharedArrayBuffer::Span(); +``` + +Returns a `std::span` view over the wrapped data. + [`Napi::Object`]: ./object.md diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index 4ced5841c..d73ccfd9d 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -162,4 +162,15 @@ const T* Napi::TypedArrayOf::Data() const Returns a pointer into the backing `Napi::ArrayBuffer` which is offset to point to the start of the array. +### Span + +> When C++20 `std::span` is not available, this method is not available. + +```cpp +std::span Napi::TypedArrayOf::Span(); +std::span Napi::TypedArrayOf::Span() const; +``` + +Returns a `std::span` view over the typed array data. + [`Napi::TypedArray`]: ./typed_array.md diff --git a/napi-inl.h b/napi-inl.h index 6078b02a6..648fbd9e0 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2203,6 +2203,13 @@ inline size_t SharedArrayBuffer::ByteLength() { NAPI_THROW_IF_FAILED(_env, status, 0); return length; } + +#if defined(__cpp_lib_span) +template +inline std::span SharedArrayBuffer::Span() { + return std::span(reinterpret_cast(Data()), ByteLength() / sizeof(T)); +} +#endif // defined(__cpp_lib_span) #endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 28dd0946d..19c9aa892 100644 --- a/napi.h +++ b/napi.h @@ -1312,6 +1312,11 @@ class SharedArrayBuffer : public Object { void* Data(); size_t ByteLength(); + +#if defined(__cpp_lib_span) + template + std::span Span(); +#endif // defined(__cpp_lib_span) }; #endif diff --git a/test/binding.gyp b/test/binding.gyp index 59e2b5301..c892b90bf 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -93,7 +93,8 @@ 'value_type_cast.cc' ], 'build_sources_cpp20': [ - 'buffer.cc' + 'buffer.cc', + 'buffer_no_external.cc' ], 'want_coverage': ' Date: Fri, 13 Nov 2026 23:41:22 +0530 Subject: [PATCH 4/4] fix(buffer): make ArrayBuffer and SharedArrayBuffer Span() untyped uint8_t const --- doc/array_buffer.md | 5 ++--- doc/shared_array_buffer.md | 5 ++--- napi-inl.h | 18 ++++++++++++------ napi.h | 6 ++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index 146fd449e..db5e2dc62 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -232,11 +232,10 @@ Returns a pointer the wrapped data. > When C++20 `std::span` is not available, this method is not available. ```cpp -template -std::span Napi::ArrayBuffer::Span(); +std::span Napi::ArrayBuffer::Span() const; ``` -Returns a `std::span` view over the wrapped data. +Returns a `std::span` view over the raw byte data. ### Detach diff --git a/doc/shared_array_buffer.md b/doc/shared_array_buffer.md index 147425c6a..ae9b544a6 100644 --- a/doc/shared_array_buffer.md +++ b/doc/shared_array_buffer.md @@ -67,10 +67,9 @@ Returns a pointer the wrapped data. > When C++20 `std::span` is not available, this method is not available. ```cpp -template -std::span Napi::SharedArrayBuffer::Span(); +std::span Napi::SharedArrayBuffer::Span() const; ``` -Returns a `std::span` view over the wrapped data. +Returns a `std::span` view over the raw byte data. [`Napi::Object`]: ./object.md diff --git a/napi-inl.h b/napi-inl.h index 648fbd9e0..0f247099e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2205,9 +2205,12 @@ inline size_t SharedArrayBuffer::ByteLength() { } #if defined(__cpp_lib_span) -template -inline std::span SharedArrayBuffer::Span() { - return std::span(reinterpret_cast(Data()), ByteLength() / sizeof(T)); +inline std::span SharedArrayBuffer::Span() const { + void* data; + size_t length; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, &length); + NAPI_THROW_IF_FAILED(_env, status, std::span()); + return std::span(static_cast(data), length); } #endif // defined(__cpp_lib_span) #endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER @@ -2346,9 +2349,12 @@ inline size_t ArrayBuffer::ByteLength() { } #if defined(__cpp_lib_span) -template -inline std::span ArrayBuffer::Span() { - return std::span(reinterpret_cast(Data()), ByteLength() / sizeof(T)); +inline std::span ArrayBuffer::Span() const { + void* data; + size_t length; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, &length); + NAPI_THROW_IF_FAILED(_env, status, std::span()); + return std::span(static_cast(data), length); } #endif // defined(__cpp_lib_span) diff --git a/napi.h b/napi.h index 19c9aa892..13f2ea55f 100644 --- a/napi.h +++ b/napi.h @@ -1314,8 +1314,7 @@ class SharedArrayBuffer : public Object { size_t ByteLength(); #if defined(__cpp_lib_span) - template - std::span Span(); + std::span Span() const; #endif // defined(__cpp_lib_span) }; #endif @@ -1402,8 +1401,7 @@ class ArrayBuffer : public Object { size_t ByteLength(); ///< Gets the length of the array buffer in bytes. #if defined(__cpp_lib_span) - template - std::span Span(); + std::span Span() const; #endif // defined(__cpp_lib_span) #if NAPI_VERSION >= 7