diff --git a/packages/emnapi/src/core/string.ts b/packages/emnapi/src/core/string.ts index b8e6f5d2..f8455ad6 100644 --- a/packages/emnapi/src/core/string.ts +++ b/packages/emnapi/src/core/string.ts @@ -195,6 +195,7 @@ function emnapiUtf8ToString (ptr: number, length: number): string { return UTF8ToString(ptr) } length = length >>> 0 + if (!length) return '' const HEAPU8 = new Uint8Array(wasmMemory.buffer) const shared = (typeof SharedArrayBuffer === 'function') && (wasmMemory.buffer instanceof SharedArrayBuffer) return emnapiUtf8Decoder.decode(shared ? HEAPU8.slice(ptr, ptr + length) : HEAPU8.subarray(ptr, ptr + length)) @@ -225,6 +226,7 @@ function emnapiUtf16ToString (ptr: number, length: number): string { return UTF16ToString(ptr) } length = length >>> 0 + if (!length) return '' const HEAPU8 = new Uint8Array(wasmMemory.buffer) const shared = (typeof SharedArrayBuffer === 'function') && (wasmMemory.buffer instanceof SharedArrayBuffer) return emnapiUtf16leDecoder.decode(shared ? HEAPU8.slice(ptr, ptr + length * 2) : HEAPU8.subarray(ptr, ptr + length * 2)) diff --git a/packages/emnapi/src/emscripten/string.ts b/packages/emnapi/src/emscripten/string.ts index 238f351a..000ac215 100644 --- a/packages/emnapi/src/emscripten/string.ts +++ b/packages/emnapi/src/emscripten/string.ts @@ -90,6 +90,7 @@ mergeInto(LibraryManager.library, { return UTF8ToString(ptr) } length = length >>> 0 + if (!length) return '' return emnapiUtf8Decoder.decode($getUnsharedTextDecoderView('HEAPU8', 'ptr', 'ptr + length')) }, @@ -135,6 +136,7 @@ mergeInto(LibraryManager.library, { return UTF16ToString(ptr) } length = length >>> 0 + if (!length) return '' return emnapiUtf16leDecoder.decode($getUnsharedTextDecoderView('HEAPU8', 'ptr', 'ptr + length * 2')) } }) diff --git a/packages/emnapi/src/life.ts b/packages/emnapi/src/life.ts index f3edee9c..3e688aed 100644 --- a/packages/emnapi/src/life.ts +++ b/packages/emnapi/src/life.ts @@ -76,8 +76,8 @@ function napi_create_reference ( $CHECK_ARG!(envObject, result) const handle = emnapiCtx.handleStore.get(value)! - if (!(handle.isObject() || handle.isFunction())) { - return envObject.setLastError(napi_status.napi_object_expected) + if (!(handle.isObject() || handle.isFunction() || handle.isSymbol())) { + return envObject.setLastError(napi_status.napi_invalid_arg) } // eslint-disable-next-line @typescript-eslint/no-unused-vars const ref = emnapiCtx.createReference(envObject, handle.id, initial_refcount >>> 0, Ownership.kUserland as any) diff --git a/packages/emnapi/src/value/convert2napi.ts b/packages/emnapi/src/value/convert2napi.ts index 13ae02f4..05347fe9 100644 --- a/packages/emnapi/src/value/convert2napi.ts +++ b/packages/emnapi/src/value/convert2napi.ts @@ -60,17 +60,21 @@ function napi_create_double (env: napi_env, value: double, result: Pointer): napi_status { $CHECK_ENV!(env) const envObject = emnapiCtx.envStore.get(env)! - $CHECK_ARG!(envObject, result) - $from64('str') $from64('length') + const autoLength = length === -1 length = length >>> 0 - if (!((length === 0xffffffff) || (length <= 2147483647)) || (!str)) { + if (length !== 0) { + $CHECK_ARG!(envObject, str) + } + $CHECK_ARG!(envObject, result) + $from64('str') + if (!(autoLength || (length <= 2147483647))) { return envObject.setLastError(napi_status.napi_invalid_arg) } let latin1String = '' let len = 0 - if (length === -1) { + if (autoLength) { while (true) { const ch = $makeGetValue('str', 0, 'u8') as number if (!ch) break @@ -96,11 +100,16 @@ function napi_create_string_latin1 (env: napi_env, str: const_char_p, length: si function napi_create_string_utf16 (env: napi_env, str: const_char16_t_p, length: size_t, result: Pointer): napi_status { $CHECK_ENV!(env) const envObject = emnapiCtx.envStore.get(env)! + $from64('length') + const autoLength = length === -1 + const sizelength = length >>> 0 + if (length !== 0) { + $CHECK_ARG!(envObject, str) + } $CHECK_ARG!(envObject, result) $from64('str') - $from64('length') - if (((length < -1) || (length > 2147483647)) || (!str)) { + if (!(autoLength || (sizelength <= 2147483647))) { return envObject.setLastError(napi_status.napi_invalid_arg) } @@ -115,11 +124,16 @@ function napi_create_string_utf16 (env: napi_env, str: const_char16_t_p, length: function napi_create_string_utf8 (env: napi_env, str: const_char_p, length: size_t, result: Pointer): napi_status { $CHECK_ENV!(env) const envObject = emnapiCtx.envStore.get(env)! + $from64('length') + const autoLength = length === -1 + const sizelength = length >>> 0 + if (length !== 0) { + $CHECK_ARG!(envObject, str) + } $CHECK_ARG!(envObject, result) $from64('str') - $from64('length') - if (((length < -1) || (length > 2147483647)) || (!str)) { + if (!(autoLength || (sizelength <= 2147483647))) { return envObject.setLastError(napi_status.napi_invalid_arg) } const utf8String = emnapiUtf8ToString(str, length) diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index 0977b03f..dda5ef77 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -428,7 +428,13 @@ function node_api_symbol_for (env: napi_env, utf8description: const_char_p, leng $from64('utf8description') $from64('result') - if (((length < -1) || (length > 2147483647)) || (!utf8description)) { + const autoLength = length === -1 + const sizelength = length >>> 0 + if (length !== 0) { + $CHECK_ARG!(envObject, utf8description) + } + + if (!(autoLength || (sizelength <= 2147483647))) { return envObject.setLastError(napi_status.napi_invalid_arg) } diff --git a/packages/runtime/src/Context.ts b/packages/runtime/src/Context.ts index 4f765416..13869a07 100644 --- a/packages/runtime/src/Context.ts +++ b/packages/runtime/src/Context.ts @@ -10,6 +10,7 @@ import { _global, supportReflect, supportFinalizer, + supportWeakSymbol, supportBigInt, supportNewFunction, canSetFunctionName, @@ -115,6 +116,7 @@ export class Context { public feature = { supportReflect, supportFinalizer, + supportWeakSymbol, supportBigInt, supportNewFunction, canSetFunctionName, diff --git a/packages/runtime/src/Persistent.ts b/packages/runtime/src/Persistent.ts index 17a80096..f5228085 100644 --- a/packages/runtime/src/Persistent.ts +++ b/packages/runtime/src/Persistent.ts @@ -1,13 +1,29 @@ import { supportFinalizer } from './util' -export class Persistent { - private _ref: T | WeakRef | null +class StrongRef { + private _value: T + + constructor (value: T) { + this._value = value + } + + deref (): T { + return this._value + } + + dispose (): void { + this._value = undefined! + } +} + +export class Persistent { + private _ref: StrongRef | WeakRef | undefined private _param: any private _callback: ((param: any) => void) | undefined private static readonly _registry = supportFinalizer ? new FinalizationRegistry((value: Persistent) => { - value._ref = null + value._ref = undefined const callback = value._callback const param = value._param value._callback = undefined @@ -19,33 +35,45 @@ export class Persistent { : undefined! constructor (value: T) { - this._ref = value + this._ref = new StrongRef(value) } setWeak

(param: P, callback: (param: P) => void): void { - if (this._ref === null) return - if (!supportFinalizer) return - if (this._ref instanceof WeakRef) return - this._param = param - this._callback = callback - Persistent._registry.register(this._ref, this, this) - this._ref = new WeakRef(this._ref) + if (!supportFinalizer || this._ref === undefined || this._ref instanceof WeakRef) return + const value = this._ref.deref() + try { + // try { + Persistent._registry.register(value as any, this, this) + const weakRef = new WeakRef(value) + this._ref.dispose() + this._ref = weakRef + // } catch (_) { + // Persistent._registry.register(this._ref, this, this) + // this._ref = new WeakRef(this._ref) + // } + this._param = param + this._callback = callback + } catch (_) {} } clearWeak (): void { - if (this._ref === null) return - if (!supportFinalizer) return + if (!supportFinalizer || this._ref === undefined) return if (this._ref instanceof WeakRef) { try { Persistent._registry.unregister(this) } catch (_) {} this._param = undefined this._callback = undefined - this._ref = this._ref.deref() as T + const value = this._ref.deref() + if (value === undefined) { + this._ref = value + } else { + this._ref = new StrongRef(value as T) + } } } - reset (other?: T | WeakRef): void { + reset (): void { if (supportFinalizer) { try { Persistent._registry.unregister(this) @@ -53,28 +81,18 @@ export class Persistent { } this._param = undefined this._callback = undefined - if (other) { - this._ref = other - } else { - this._ref = null + if (this._ref instanceof StrongRef) { + this._ref.dispose() } + this._ref = undefined } isEmpty (): boolean { - return this._ref === null + return this._ref === undefined } deref (): T | undefined { - if (!supportFinalizer) { - return (this._ref as T | null) ?? undefined - } - - if (this._ref === null) return undefined - - if (this._ref instanceof WeakRef) { - return this._ref.deref() - } - - return this._ref + if (this._ref === undefined) return undefined + return this._ref.deref() } } diff --git a/packages/runtime/src/Reference.ts b/packages/runtime/src/Reference.ts index 51083e47..a742975d 100644 --- a/packages/runtime/src/Reference.ts +++ b/packages/runtime/src/Reference.ts @@ -1,5 +1,4 @@ import type { IStoreValue } from './Store' -import { isReferenceType } from './util' import type { Env } from './env' import { RefBase } from './RefBase' import { Persistent } from './Persistent' @@ -22,12 +21,9 @@ export class Reference extends RefBase implements IStoreValue { finalize_hint: void_p = 0 ): Reference { const handle = envObject.ctx.handleStore.get(handle_id)! - if (!isReferenceType(handle.value)) { - throw new TypeError('Invalid reference value') - } const ref = new Reference(envObject, initialRefcount, ownership, finalize_callback, finalize_data, finalize_hint) envObject.ctx.refStore.add(ref) - ref.persistent = new Persistent(handle.value) + ref.persistent = new Persistent(handle.value) if (initialRefcount === 0) { ref._setWeak() diff --git a/packages/runtime/src/util.ts b/packages/runtime/src/util.ts index c4f2c9a8..4b08b028 100644 --- a/packages/runtime/src/util.ts +++ b/packages/runtime/src/util.ts @@ -70,6 +70,18 @@ export const canSetFunctionName = /*#__PURE__*/ (function () { export const supportReflect = typeof Reflect === 'object' export const supportFinalizer = (typeof FinalizationRegistry !== 'undefined') && (typeof WeakRef !== 'undefined') +export const supportWeakSymbol = /*#__PURE__*/ (function () { + try { + // eslint-disable-next-line symbol-description + const sym = Symbol() + // eslint-disable-next-line no-new + new WeakRef(sym as any) + new WeakMap().set(sym as any, undefined) + } catch (_) { + return false + } + return true +})() export const supportBigInt = typeof BigInt !== 'undefined' export function isReferenceType (v: any): v is object { diff --git a/packages/test/async/async.test.js b/packages/test/async/async.test.js index 51c9a569..b15705f7 100644 --- a/packages/test/async/async.test.js +++ b/packages/test/async/async.test.js @@ -90,6 +90,8 @@ async function main () { throw new Error('uncaught') })) }) + + process.exitCode = 0 } module.exports = main() diff --git a/packages/test/ref/ref.test.js b/packages/test/ref/ref.test.js index bf2cbc56..0ff2cd60 100644 --- a/packages/test/ref/ref.test.js +++ b/packages/test/ref/ref.test.js @@ -16,31 +16,29 @@ module.exports = p.then(test_reference => { // Run each test function in sequence, // with an async delay and GC call between each. async function runTests () { - // emnapi can not create reference on a symbol // https://github.com/tc39/proposal-symbols-as-weakmap-keys + (() => { + const symbol = test_reference.createSymbol('testSym') + test_reference.createReference(symbol, 0) + assert.strictEqual(test_reference.referenceValue, symbol) + })() + test_reference.deleteReference(); - // (() => { - // const symbol = test_reference.createSymbol('testSym') - // test_reference.createReference(symbol, 0) - // assert.strictEqual(test_reference.referenceValue, symbol) - // })() - // test_reference.deleteReference(); - - // (() => { - // const symbol = test_reference.createSymbolFor('testSymFor') - // test_reference.createReference(symbol, 0) - // assert.strictEqual(test_reference.referenceValue, symbol) - // assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor')) - // })() - // test_reference.deleteReference(); + (() => { + const symbol = test_reference.createSymbolFor('testSymFor') + test_reference.createReference(symbol, 0) + assert.strictEqual(test_reference.referenceValue, symbol) + assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor')) + })() + test_reference.deleteReference(); - // (() => { - // const symbol = test_reference.createSymbolForEmptyString() - // test_reference.createReference(symbol, 0) - // assert.strictEqual(test_reference.referenceValue, symbol) - // assert.strictEqual(test_reference.referenceValue, Symbol.for('')) - // })() - // test_reference.deleteReference() + (() => { + const symbol = test_reference.createSymbolForEmptyString() + test_reference.createReference(symbol, 0) + assert.strictEqual(test_reference.referenceValue, symbol) + assert.strictEqual(test_reference.referenceValue, Symbol.for('')) + })() + test_reference.deleteReference() assert.throws(() => test_reference.createSymbolForIncorrectLength(), /Invalid argument/);