Skip to content

Commit

Permalink
add napi_create_buffer_from_arraybuffer (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi authored Oct 12, 2024
1 parent 34e2a22 commit aee10fa
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 16 deletions.
12 changes: 12 additions & 0 deletions packages/emnapi/include/node/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ napi_create_external_buffer(napi_env env,
void* finalize_hint,
napi_value* result);
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED

#ifdef NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_HAS_CREATE_BUFFER_FROM_ARRAYBUFFER

NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_buffer_from_arraybuffer(napi_env env,
napi_value arraybuffer,
size_t byte_offset,
size_t byte_length,
napi_value* result);
#endif // NAPI_EXPERIMENTAL

NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer_copy(napi_env env,
size_t length,
const void* data,
Expand Down
65 changes: 61 additions & 4 deletions packages/emnapi/src/value/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,10 @@ export function napi_create_typedarray (
$CHECK_ARG!(envObject, result)

const handle = emnapiCtx.handleStore.get(arraybuffer)!
const buffer = handle.value
if (!(buffer instanceof ArrayBuffer)) {
if (!handle.isArrayBuffer()) {
return envObject.setLastError(napi_status.napi_invalid_arg)
}
const buffer = handle.value

from64('byte_offset')
from64('length')
Expand Down Expand Up @@ -412,6 +412,63 @@ export function napi_create_external_buffer (
)
}

/**
* @__sig ippppp
*/
export function node_api_create_buffer_from_arraybuffer (
env: napi_env,
arraybuffer: napi_value,
byte_offset: size_t,
byte_length: size_t,
result: Pointer<napi_value>
): napi_status {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let value: number

return $PREAMBLE!(env, (envObject) => {
$CHECK_ARG!(envObject, arraybuffer)
$CHECK_ARG!(envObject, result)
from64('byte_offset')
from64('byte_length')
byte_offset = byte_offset >>> 0
byte_length = byte_length >>> 0
const handle = emnapiCtx.handleStore.get(arraybuffer)!
if (!handle.isArrayBuffer()) {
return envObject.setLastError(napi_status.napi_invalid_arg)
}
const buffer = handle.value

if ((byte_length + byte_offset) > buffer.byteLength) {
const err: RangeError & { code?: string } = new RangeError('The byte offset + length is out of range')
err.code = 'ERR_OUT_OF_RANGE'
throw err
}

const Buffer = emnapiCtx.feature.Buffer!
if (!Buffer) {
throw emnapiCtx.createNotSupportBufferError('node_api_create_buffer_from_arraybuffer', '')
}
const out = Buffer.from(buffer, byte_offset, byte_length)
if (buffer === wasmMemory.buffer) {
if (!emnapiExternalMemory.wasmMemoryViewTable.has(out)) {
emnapiExternalMemory.wasmMemoryViewTable.set(out, {
Ctor: Buffer,
address: byte_offset,
length: byte_length,
ownership: ReferenceOwnership.kUserland,
runtimeAllocated: 0
})
}
}
from64('result')

// eslint-disable-next-line @typescript-eslint/no-unused-vars
value = emnapiCtx.addToCurrentScope(out).id
makeSetValue('result', 0, 'value', '*')
return envObject.getReturnStatus()
})
}

/**
* @__sig ippppp
*/
Expand All @@ -433,10 +490,10 @@ export function napi_create_dataview (
byte_length = byte_length >>> 0
byte_offset = byte_offset >>> 0
const handle = emnapiCtx.handleStore.get(arraybuffer)!
const buffer = handle.value
if (!(buffer instanceof ArrayBuffer)) {
if (!handle.isArrayBuffer()) {
return envObject.setLastError(napi_status.napi_invalid_arg)
}
const buffer = handle.value

if ((byte_length + byte_offset) > buffer.byteLength) {
const err: RangeError & { code?: string } = new RangeError('byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in')
Expand Down
1 change: 1 addition & 0 deletions packages/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ add_test("number" "./number/binding.c;./number/test_null.c" OFF)
add_test("symbol" "./symbol/binding.c" OFF)
add_test("typedarray" "./typedarray/binding.c" OFF)
add_test("buffer" "./buffer/binding.c" OFF)
target_compile_definitions("buffer" PRIVATE "NAPI_EXPERIMENTAL")
add_test("buffer_finalizer" "./buffer_finalizer/binding.c" OFF)
add_test("fatal_exception" "./fatal_exception/binding.c" OFF)
add_test("cleanup_hook" "./cleanup_hook/binding.c" OFF)
Expand Down
63 changes: 51 additions & 12 deletions packages/test/buffer/binding.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ static const char theText[] =
const unsigned int theTextSize = sizeof(theText);

static int deleterCallCount = 0;
static void deleteTheText(napi_env env, void* data, void* finalize_hint) {
NODE_API_ASSERT_RETURN_VOID(
env, data != NULL && strcmp(data, theText) == 0, "invalid data");

static void deleteTheText(node_api_basic_env env,
void* data,
void* finalize_hint) {
NODE_API_BASIC_ASSERT_RETURN_VOID(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) {
NODE_API_ASSERT_RETURN_VOID(
env, data != NULL && strcmp(data, theText) == 0, "invalid data");
static void noopDeleter(node_api_basic_env env,
void* data,
void* finalize_hint) {
NODE_API_BASIC_ASSERT_RETURN_VOID(data != NULL && strcmp(data, theText) == 0,
"invalid data");
(void)finalize_hint;
deleterCallCount++;
}
Expand All @@ -75,9 +81,12 @@ static napi_value newExternalBuffer(napi_env env, napi_callback_info info) {
NODE_API_ASSERT(
env, theCopy, "Failed to copy static text for newExternalBuffer");
NODE_API_CALL(env,
napi_create_external_buffer(
env, theTextSize, theCopy, deleteTheText,
NULL /* finalize_hint */, &theBuffer));
napi_create_external_buffer(env,
sizeof(theText),
theCopy,
deleteTheText,
NULL /* finalize_hint */,
&theBuffer));

return theBuffer;
}
Expand Down Expand Up @@ -134,9 +143,12 @@ static napi_value bufferInfo(napi_env env, napi_callback_info info) {
static napi_value staticBuffer(napi_env env, napi_callback_info info) {
napi_value theBuffer;
NODE_API_CALL(env,
napi_create_external_buffer(
env, sizeof(theText), (void*)theText, noopDeleter,
NULL /* finalize_hint */, &theBuffer));
napi_create_external_buffer(env,
sizeof(theText),
(void*)theText,
noopDeleter,
NULL /* finalize_hint */,
&theBuffer));
return theBuffer;
}

Expand Down Expand Up @@ -190,6 +202,32 @@ static napi_value getMemoryDataAsArray(napi_env env, napi_callback_info info) {
return ret;
}

static napi_value bufferFromArrayBuffer(napi_env env,
napi_callback_info info) {
napi_status status;
napi_value arraybuffer;
napi_value buffer;
size_t byte_length = 1024;
void* data = NULL;
size_t buffer_length = 0;
void* buffer_data = NULL;

status = napi_create_arraybuffer(env, byte_length, &data, &arraybuffer);
NODE_API_ASSERT(env, status == napi_ok, "Failed to create arraybuffer");

status = node_api_create_buffer_from_arraybuffer(
env, arraybuffer, 0, byte_length, &buffer);
NODE_API_ASSERT(
env, status == napi_ok, "Failed to create buffer from arraybuffer");

status = napi_get_buffer_info(env, buffer, &buffer_data, &buffer_length);
NODE_API_ASSERT(env, status == napi_ok, "Failed to get buffer info");

NODE_API_ASSERT(env, buffer_length == byte_length, "Buffer length mismatch");

return buffer;
}

static napi_value Init(napi_env env, napi_value exports) {
napi_value theValue;

Expand All @@ -208,6 +246,7 @@ static napi_value Init(napi_env env, napi_value exports) {
DECLARE_NODE_API_PROPERTY("staticBuffer", staticBuffer),
DECLARE_NODE_API_PROPERTY("invalidObjectAsBuffer", invalidObjectAsBuffer),
DECLARE_NODE_API_PROPERTY("getMemoryDataAsArray", getMemoryDataAsArray),
DECLARE_NODE_API_PROPERTY("bufferFromArrayBuffer", bufferFromArrayBuffer),
};

NODE_API_CALL(env, napi_define_properties(
Expand Down
3 changes: 3 additions & 0 deletions packages/test/buffer/buffer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ module.exports = load('buffer').then(async binding => {

// To test this doesn't crash
binding.invalidObjectAsBuffer({})

const testBuffer = binding.bufferFromArrayBuffer()
assert(testBuffer instanceof Buffer, 'Expected a Buffer')
})().then(common.mustCall())

process.externalBuffer = binding.newExternalBuffer()
Expand Down

0 comments on commit aee10fa

Please sign in to comment.