diff --git a/packages/emnapi/src/function.ts b/packages/emnapi/src/function.ts index a4733327..05f7563d 100644 --- a/packages/emnapi/src/function.ts +++ b/packages/emnapi/src/function.ts @@ -20,11 +20,11 @@ function napi_create_function (env: napi_env, utf8name: Pointer, len }) } -function napi_get_cb_info (env: napi_env, _cbinfo: napi_callback_info, argc: Pointer, argv: Pointer, this_arg: Pointer, data: void_pp): napi_status { +function napi_get_cb_info (env: napi_env, cbinfo: napi_callback_info, argc: Pointer, argv: Pointer, this_arg: Pointer, data: void_pp): napi_status { $CHECK_ENV!(env) const envObject = emnapiCtx.envStore.get(env)! - const cbinfoValue = emnapiCtx.cbinfoStack.current! + const cbinfoValue = emnapiCtx.cbinfoStack.get(cbinfo)! $from64('argc') $from64('argv') @@ -160,7 +160,7 @@ function napi_new_instance ( function napi_get_new_target ( env: napi_env, - _cbinfo: napi_callback_info, + cbinfo: napi_callback_info, result: Pointer ): napi_status { $CHECK_ENV!(env) @@ -170,7 +170,7 @@ function napi_get_new_target ( $from64('result') - const cbinfoValue = emnapiCtx.cbinfoStack.current! + const cbinfoValue = emnapiCtx.cbinfoStack.get(cbinfo)! // eslint-disable-next-line @typescript-eslint/no-unused-vars const value = cbinfoValue.getNewTarget(envObject) $makeSetValue('result', 0, 'value', '*') diff --git a/packages/emnapi/src/internal.ts b/packages/emnapi/src/internal.ts index 47914aac..3b5f4e10 100644 --- a/packages/emnapi/src/internal.ts +++ b/packages/emnapi/src/internal.ts @@ -11,11 +11,11 @@ function emnapiCreateFunction any> (envObject: Env const makeFunction = () => function (this: any): any { 'use strict' - emnapiCtx.cbinfoStack.push(this, data, arguments, f) + const cbinfo = emnapiCtx.cbinfoStack.push(this, data, arguments, f) const scope = emnapiCtx.openScope(envObject) try { return envObject.callIntoModule((envObject) => { - const napiValue = $makeDynCall('ppp', 'cb')(envObject.id, 0) + const napiValue = $makeDynCall('ppp', 'cb')(envObject.id, cbinfo) return (!napiValue) ? undefined : emnapiCtx.handleStore.get(napiValue)!.value }) } finally { diff --git a/packages/runtime/src/CallbackInfo.ts b/packages/runtime/src/CallbackInfo.ts index 00669d40..81fc2bc3 100644 --- a/packages/runtime/src/CallbackInfo.ts +++ b/packages/runtime/src/CallbackInfo.ts @@ -1,8 +1,12 @@ import type { Env } from './env' +const EMPTY_ARGS = [] as const + export class CallbackInfo { public constructor ( - public parent: CallbackInfo | null, + public id: number, + public parent: CallbackInfo, + public child: CallbackInfo | null, public thiz: any, public data: void_p, public args: ArrayLike, @@ -14,15 +18,35 @@ export class CallbackInfo { if (thiz == null || thiz.constructor == null) return 0 return thiz instanceof this.fn ? envObject.ensureHandleId(thiz.constructor) : 0 } + + public dispose (): void { + if (this.thiz !== undefined) this.thiz = undefined + this.args = EMPTY_ARGS + this.fn = null! + } } +const ROOT_CBINFO = new CallbackInfo(0, null!, null, null, 0, null!, null!) + export class CallbackInfoStack { - public current: CallbackInfo | null = null + public current: CallbackInfo = ROOT_CBINFO + + public get (id: number): CallbackInfo | null { + if (id === 1) return ROOT_CBINFO.child! + + let info = ROOT_CBINFO + for (let i = 0; i < id; ++i) { + info = info.child! + if (info === null) return null + } + return info === ROOT_CBINFO ? null : info + } public pop (): void { const current = this.current - if (current === null) return + if (current === ROOT_CBINFO) return this.current = current.parent + current.dispose() } public push ( @@ -30,13 +54,22 @@ export class CallbackInfoStack { data: void_p, args: ArrayLike, fn: Function - ): CallbackInfo { - const info = new CallbackInfo(this.current, thiz, data, args, fn) + ): number { + let info = this.current.child + if (info) { + info.thiz = thiz + info.data = data + info.args = args + info.fn = fn + } else { + info = new CallbackInfo(this.current.id + 1, this.current, null, thiz, data, args, fn) + this.current.child = info + } this.current = info - return info + return info.id } public dispose (): void { - this.current = null + this.current = null! } } diff --git a/packages/test/CMakeLists.txt b/packages/test/CMakeLists.txt index 3ec81c05..39a3447e 100644 --- a/packages/test/CMakeLists.txt +++ b/packages/test/CMakeLists.txt @@ -264,6 +264,7 @@ if((NOT IS_WASM) OR IS_EMSCRIPTEN OR IS_WASI_THREADS) endif() add_test("arg" "./arg/binding.c" ON OFF "") +add_test("cbinfo" "./cbinfo/binding.c" ON OFF "") add_test("callback" "./callback/binding.c" ON OFF "") add_test("objfac" "./objfac/binding.c" ON OFF "") add_test("fnfac" "./fnfac/binding.c" ON OFF "") diff --git a/packages/test/cbinfo/binding.c b/packages/test/cbinfo/binding.c new file mode 100644 index 00000000..8f0265c7 --- /dev/null +++ b/packages/test/cbinfo/binding.c @@ -0,0 +1,69 @@ +#include +#include "../common.h" + +static napi_value Test1(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, + "Test1: Wrong number of arguments. Expects a single argument."); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NAPI_ASSERT(env, valuetype0 == napi_function, + "Test1: Wrong type of arguments. Expects a function as first argument."); + + napi_value argv[1]; + NAPI_CALL(env, napi_create_bigint_int64(env, (int64_t) info, argv)); + + napi_value global; + NAPI_CALL(env, napi_get_global(env, &global)); + + napi_value cb = args[0]; + NAPI_CALL(env, napi_call_function(env, global, cb, 1, argv, NULL)); + + return NULL; +} + +static napi_value Test2(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, + "Test2: Wrong number of arguments. Expects a single argument."); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NAPI_ASSERT(env, valuetype0 == napi_bigint, + "Test2: Wrong type of arguments. Expects a bigint as first argument."); + + int64_t prev_info; + bool lossless; + NAPI_CALL(env, napi_get_value_bigint_int64(env, args[0], &prev_info, &lossless)); + + size_t prev_argc = 1; + napi_value prev_args[1]; + NAPI_CALL(env, napi_get_cb_info(env, (napi_callback_info) prev_info, &prev_argc, prev_args, NULL, NULL)); + + NAPI_ASSERT(env, prev_argc == 1, + "Test2: Wrong number of arguments. Expects a single argument."); + + napi_valuetype t; + NAPI_CALL(env, napi_typeof(env, prev_args[0], &t)); + NAPI_ASSERT(env, t == napi_function, + "Test2: Wrong type of arguments. Expects a function as first argument."); + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc[2] = { + DECLARE_NAPI_PROPERTY("test1", Test1), + DECLARE_NAPI_PROPERTY("test2", Test2), + }; + NAPI_CALL(env, napi_define_properties(env, exports, 2, desc)); + return exports; +} +EXTERN_C_END diff --git a/packages/test/cbinfo/cbinfo.test.js b/packages/test/cbinfo/cbinfo.test.js new file mode 100644 index 00000000..8b00b13c --- /dev/null +++ b/packages/test/cbinfo/cbinfo.test.js @@ -0,0 +1,6 @@ +'use strict' +const { load } = require('../util') + +module.exports = load('cbinfo').then(addon => { + addon.test1(addon.test2) +})