Skip to content

Instantly share code, notes, and snippets.

@devsnek
Created June 12, 2019 00:41
Show Gist options
  • Save devsnek/db5499bf774f078e9ebb679680bd2cd1 to your computer and use it in GitHub Desktop.
Save devsnek/db5499bf774f078e9ebb679680bd2cd1 to your computer and use it in GitHub Desktop.
'use strict';
const util = require('util');
const NAPI_OK = 0;
const NAPI_INVALID_ARG = 1;
const NAPI_OBJECT_EXPECTED = 2;
const NAPI_STRING_EXPECTED = 3;
const NAPI_NAME_EXPECTED = 4;
const NAPI_FUNCTION_EXPECTED = 5;
const NAPI_NUMBER_EXPECTED = 6;
const NAPI_BOOLEAN_EXPECTED = 7;
const NAPI_ARRAY_EXPECTED = 8;
const NAPI_GENERIC_FAILURE = 9;
const NAPI_PENDING_EXCEPTION = 10;
const NAPI_CANCELLED = 11;
const NAPI_ESCAPE_CALLED_TWICE = 12;
const NAPI_HANDLE_SCOPE_MISMATCH = 13;
const NAPI_CALLBACK_SCOPE_MISMATCH = 14;
const NAPI_QUEUE_FULL = 15;
const NAPI_CLOSING = 16;
const NAPI_BIGINT_EXPECTED = 17;
const NAPI_DATE_EXPECTED = 18;
const NAPI_UNDEFINED = 0;
const NAPI_NULL = 1;
const NAPI_BOOLEAN = 2;
const NAPI_NUMBER = 3;
const NAPI_STRING = 4;
const NAPI_SYMBOL = 5;
const NAPI_OBJECT = 6;
const NAPI_FUNCTION = 7;
const NAPI_EXTERNAL = 8;
const NAPI_BIGINT = 9;
const NAPI_DEFAULT = 0;
const NAPI_WRITABLE = 1 << 0;
const NAPI_ENUMERATE = 1 << 1;
const NAPI_CONFIGURABLE = 1 << 2;
const NAPI_STATIC = 1 << 10;
const kFuncNormal = Symbol('kFuncNormal');
const kFuncConstructor = Symbol('kFuncConstructor');
const kFuncMethod = Symbol('kFuncMethod');
class NAPI {
indirectFunctionTable = undefined;
memory = undefined;
view = undefined;
handleScope = [];
references = [];
externalData = new WeakMap();
wrapData = new WeakMap();
finalizationGroup = new FinalizationGroup((items) => {
for (const [cbIdx, envPtr, data, hint] of items) {
try {
this.indirectFunctionTable.get(cbIdx)(envPtr, data, hint);
} catch (e) {
internalBinding('task_queue').triggerFatalException(e);
}
}
});
exception = null;
exceptionPending = false;
wrap(f, bypass = false) {
return (...args) => {
if (!bypass && this.exceptionPending) {
return NAPI_PENDING_EXCEPTION;
}
try {
this.refreshMemory();
return f(...args);
} catch (e) {
this.exception = e;
this.exceptionPending = true;
return NAPI_PENDING_EXCEPTION;
}
};
}
createFunction(envPtr, cb, data, name, mode = kFuncNormal) {
const self = this;
const func = (0, function (...args) { // eslint-disable-line func-names
if (mode === kFuncConstructor && new.target === undefined) {
throw new TypeError(`Class constructor ${name ? `${name} ` : ''}cannot be invoked without 'new'`);
}
if (mode === kFuncMethod && new.target !== undefined) {
throw new TypeError(`${name ? `${name} is n` : 'N'}ot a constructor`);
}
self.openHandleScope();
try {
const callbackInfo = {
this: this,
newTarget: new.target,
args,
data,
};
const idx = self.indirectFunctionTable.get(cb)(envPtr, self.store(callbackInfo));
return self.load(idx);
} finally {
self.closeHandleScope();
if (self.exceptionPending) {
self.exceptionPending = false;
throw self.exception; // eslint-disable-line no-unsafe-finally
}
}
});
if (name) {
Object.defineProperty(func, 'name', {
value: name,
configurable: true,
});
}
return func;
}
readPropertyDescriptors(count, ptr) {
const descriptors = [];
for (let i = 0; i < count; i += 1) {
const utf8namePtr = this.readUint32(ptr);
ptr += 4;
const nameIdx = this.readUint32(ptr);
ptr += 4;
const method = this.readUint32(ptr);
ptr += 4;
const getter = this.readUint32(ptr);
ptr += 4;
const setter = this.readUint32(ptr);
ptr += 4;
const valueIdx = this.readUint32(ptr);
ptr += 4;
const attributes = this.readUint32(ptr);
ptr += 4;
const dataPtr = this.readUint32(ptr);
ptr += 4;
const name = utf8namePtr
? this.readCStringFrom(utf8namePtr)
: this.load(nameIdx);
if (typeof name !== 'string' || typeof name !== 'symbol') {
return NAPI_NAME_EXPECTED;
}
const descriptor = {};
if ((attributes & NAPI_WRITABLE) === 0) {
descriptor.writable = false;
}
if ((attributes & NAPI_ENUMERATE) === 0) {
descriptor.enumerable = false;
}
if ((attributes & NAPI_CONFIGURABLE) === 0) {
descriptor.configurable = false;
}
if ((attributes & NAPI_STATIC) === NAPI_STATIC) {
descriptor.static = true;
}
descriptors.push({
method,
getter,
setter,
valueIdx,
dataPtr,
descriptor,
name,
});
}
return descriptors;
}
exports = {
napi_get_last_error_info: () => {}, // PENDING
napi_throw: this.wrap((envPtr, valueIdx) => {
this.exception = this.load(valueIdx);
this.exceptionPending = true;
return NAPI_OK;
}),
napi_throw_error: this.wrap((envPtr, codePtr, msgPtr) => {}),
napi_throw_type_error: this.wrap((envPtr, codePtr, msgPtr) => {}),
napi_throw_range_error: this.wrap((envPtr, codePtr, msgPtr) => {}),
napi_create_error: this.wrap((envPtr, codeIdx, msgIdx, resultPtr) => {}),
napi_create_type_error: this.wrap((envPtr, codeIdx, msgIdx, resultPtr) => {}),
napi_create_range_error: this.wrap((envPtr, codeIdx, msgIdx, resultPtr) => {}),
napi_get_and_clear_last_exception: (envPtr, resultPtr) => {
const e = this.exception;
this.exception = null;
this.exceptionPending = false;
this.refreshMemory();
this.writeValue(resultPtr, this.store(e));
return NAPI_OK;
},
napi_is_exception_pending: (envPtr, resultPtr) => {
this.refreshMemory();
this.view.setUint8(resultPtr, this.exceptionPending);
return NAPI_OK;
},
napi_fatal_exception: this.wrap((envPtr, valueIdx) => {
const value = this.load(valueIdx);
internalBinding('task_queue').triggerFatalException(value);
return NAPI_OK;
}),
napi_fatal_error: (locationPtr, locationLen, messagePtr, messageLen) => {
this.refreshMemory();
const location = Buffer.from(this.memory.buffer, locationPtr, locationLen).toString('utf8');
const message = Buffer.from(this.memory.buffer, messagePtr, messageLen).toString('utf8');
if (location) {
process._rawDebug(`FATAL ERROR: ${location} ${message}`);
} else {
process._rawDebug(`FATAL ERROR: ${message}`);
}
process.abort();
// unreachable
return NAPI_GENERIC_FAILURE;
},
napi_open_handle_scope: this.wrap((envPtr, resultPtr) => {
this.openHandleScope();
this.view.setUint32(resultPtr, this.handleScope.length - 1, true);
return NAPI_OK;
}),
napi_close_handle_scope: (envPtr, scopePtr) => {
if (scopePtr !== this.handleScope.length - 1) {
return NAPI_HANDLE_SCOPE_MISMATCH;
}
this.closeHandleScope();
return NAPI_OK;
},
napi_open_escapable_handle_scope: this.wrap((envPtr, resultPtr) => {
this.openHandleScope();
this.view.setUint32(resultPtr, this.scope.length - 1, true);
return NAPI_OK;
}),
napi_close_escapable_handle_scope: (envPtr, scopePtr) => {
if (scopePtr !== this.handleScope.length - 1) {
return NAPI_HANDLE_SCOPE_MISMATCH;
}
this.closeHandleScope();
return NAPI_OK;
},
napi_escape_handle: (envPtr, scopePtr, escapeeIdx, resultPtr) => {}, // PENDING
napi_create_reference: this.wrap((envPtr, valueIdx, initialRefcount, resultPtr) => {
const value = this.load(valueIdx);
const ref = { value, ref: initialRefcount };
this.references.push(ref);
this.writeValue(resultPtr, this.references.length - 1);
return NAPI_OK;
}),
napi_delete_reference: (envPtr, refIdx) => {
this.references[refIdx] = undefined;
return NAPI_OK;
},
napi_reference_ref: this.wrap((envPtr, refIdx, resultPtr) => {
const ref = this.references[refIdx];
if (ref.ref === 0) {
return NAPI_GENERIC_FAILURE;
}
ref.ref += 1;
if (resultPtr) {
this.view.setUint32(resultPtr, ref.ref, true);
}
return NAPI_OK;
}),
napi_reference_unref: this.wrap((envPtr, refIdx, resultPtr) => {
const ref = this.references[refIdx];
if (ref.ref === 0) {
return NAPI_GENERIC_FAILURE;
}
ref.ref -= 1;
if (resultPtr) {
this.view.setUint32(resultPtr, ref.ref, true);
}
return NAPI_OK;
}),
napi_get_reference_value: this.wrap((envPtr, refIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(this.references[refIdx].value));
return NAPI_OK;
}),
// napi_add_env_cleanup_hook: () => {},
// napi_remove_env_cleanup_hook: () => {},
napi_create_array: this.wrap((envPtr, resultPtr) => {
this.writeValue(resultPtr, this.store([]));
return NAPI_OK;
}),
napi_create_array_with_length: this.wrap((envPtr, length, resultPtr) => {
this.writeValue(resultPtr, this.store(new Array(length)));
return NAPI_OK;
}),
napi_create_arraybuffer: this.wrap((envPtr, length, dataPtr, resultPtr) => {
this.writeValue(resultPtr, this.store(this.memory.buffer.slice(dataPtr, dataPtr + length)));
return NAPI_OK;
}),
napi_create_date: this.wrap((envPtr, time, resultPtr) => {
this.writeValue(resultPtr, this.store(new Date(time)));
return NAPI_OK;
}),
napi_create_external: this.wrap((envPtr, data, resultPtr) => {
const obj = {};
this.externalData.set(obj, data);
this.writeValue(resultPtr, this.store(obj));
return NAPI_OK;
}),
napi_create_object: this.wrap((envPtr, resultPtr) => {
this.writeValue(resultPtr, this.store({}));
return NAPI_OK;
}),
napi_create_symbol: this.wrap((envPtr, descriptionIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(Symbol(this.load(descriptionIdx))));
return NAPI_OK;
}),
napi_create_typedarray: this.wrap((envPtr, type, length, arraybufferIdx,
byteOffset, resultPtr) => {
const ab = new [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
][type](this.load(arraybufferIdx), byteOffset, length);
this.writeValue(resultPtr, this.store(ab));
return NAPI_OK;
}),
napi_create_dataview: this.wrap((envPtr, byteLength, arraybufferIdx, byteOffset, resultPtr) => {
const dv = new DataView(this.load(arraybufferIdx), byteOffset, byteLength);
this.writeValue(resultPtr, this.store(dv));
return NAPI_OK;
}),
napi_create_int32: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_uint32: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_int64: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_double: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_bigint_int64: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_bigint_uint64: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_bigint_words: this.wrap((envPtr, signBit, wordCount, wordsPtr, resultPtr) => {
let b = 0n;
let shift = 0n;
for (let i = 0; i < wordCount; i += 1) {
const word = this.view.getBigUint64((i * 64) + wordsPtr, true);
b += word << shift;
shift += 64n;
}
const value = ((-1) ** signBit) * b;
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_create_string_latin1: this.wrap((envPtr, strPtr, length, resultPtr) => {
const str = Buffer.from(this.memory.buffer, strPtr, length).toString('latin1');
this.writeValue(resultPtr, this.store(str));
return NAPI_OK;
}),
napi_create_string_utf16: this.wrap((envPtr, strPtr, length, resultPtr) => {
const str = Buffer.from(this.memory.buffer, strPtr, length).toString('utf16');
this.writeValue(resultPtr, this.store(str));
return NAPI_OK;
}),
napi_create_string_utf8: this.wrap((envPtr, strPtr, length, resultPtr) => {
const str = Buffer.from(this.memory.buffer, strPtr, length).toString('utf8');
this.writeValue(resultPtr, this.store(str));
return NAPI_OK;
}),
napi_get_array_length: this.wrap((envPtr, valueIdx, resultPtr) => {
const a = this.load(valueIdx);
if (!util.types.isArray(a)) {
return NAPI_ARRAY_EXPECTED;
}
this.view.setUint32(resultPtr, a.length, true);
return NAPI_OK;
}),
napi_get_prototype: this.wrap((envPtr, valueIdx, resultPtr) => {
const proto = Object.getPrototypeOf(this.load(valueIdx));
this.writeValue(resultPtr, this.store(proto));
return NAPI_OK;
}),
get_date_value: this.wrap((envPtr, valueIdx, resultPtr) => {
const d = this.load(valueIdx);
if (!util.types.isDate(d)) {
return NAPI_DATE_EXPECTED;
}
this.view.setFloat64(resultPtr, d.getTime(), true);
return NAPI_OK;
}),
get_value_bool: this.wrap((envPtr, valueIdx, resultPtr) => {
const b = this.load(valueIdx);
if (typeof b !== 'boolean') {
return NAPI_BOOLEAN_EXPECTED;
}
this.view.setUint8(resultPtr, b);
return NAPI_OK;
}),
napi_get_value_double: this.wrap((envPtr, valueIdx, resultPtr) => {
const v = this.load(valueIdx);
if (typeof v !== 'number') {
return NAPI_NUMBER_EXPECTED;
}
this.view.setFloat64(resultPtr, v, true);
return NAPI_OK;
}),
napi_get_value_bigint_int64: this.wrap((envPtr, valueIdx, resultPtr, losslessPtr) => {
const b = this.load(valueIdx);
if (typeof b !== 'bigint') { // eslint-disable-line valid-typeof
return NAPI_BIGINT_EXPECTED;
}
if (losslessPtr !== 0) {
this.view.setUint8(losslessPtr, BigInt.asIntN(64, b) !== b);
}
this.view.setBigInt64(resultPtr, b, true);
return NAPI_OK;
}),
napi_get_value_bigint_uint64: this.wrap((envPtr, valueIdx, resultPtr, losslessPtr) => {
const b = this.load(valueIdx);
if (typeof b !== 'bigint') { // eslint-disable-line valid-typeof
return NAPI_BIGINT_EXPECTED;
}
if (losslessPtr !== 0) {
this.view.setUint8(losslessPtr, BigInt.asUintN(64, b) !== b);
}
this.view.setBigUint64(resultPtr, b, true);
return NAPI_OK;
}),
napi_get_value_bigint_words: this.wrap((envPtr, valueIdx, signBitPtr,
wordCountPtr, wordsPtr) => {
const b = this.load(valueIdx);
if (typeof b !== 'bigint') { // eslint-disable-line valid-typeof
return NAPI_BIGINT_EXPECTED;
}
if (signBitPtr !== 0) {
this.view.setUint8(signBitPtr, b < 0n);
}
const wordCount = this.view.getUint32(signBitPtr, true);
let i = 0;
let ull = b < 0n ? -b : b;
for (; i < wordCount; i += 1) {
const bv = ull & ((1n << 64n) - 1);
this.view.setBigUint64(wordsPtr + (i * 64), bv, true);
ull >>= 64n;
}
while (ull > 0) {
i += 1;
ull >>= 64n;
}
this.view.setUint32(wordCountPtr, i, true);
return NAPI_OK;
}),
napi_get_value_external: this.wrap((envPtr, valueIdx, resultPtr) => {
const e = this.load(valueIdx);
if (!this.externalData.has(e)) {
return NAPI_INVALID_ARG;
}
this.view.setUint32(resultPtr, this.externalData.get(e), true);
return NAPI_OK;
}),
napi_get_value_int32: this.wrap((envPtr, valueIdx, resultPtr) => {
const n = this.load(valueIdx);
if (typeof n !== 'number') {
return NAPI_NUMBER_EXPECTED;
}
this.view.setInt32(resultPtr, n, true);
return NAPI_OK;
}),
napi_get_value_int64: this.wrap(() => {}),
napi_get_value_string_latin1: this.wrap((envPtr, valueIdx, bufPtr, bufSize, resultPtr) => {
const s = this.load(valueIdx);
if (typeof s !== 'string') {
return NAPI_STRING_EXPECTED;
}
const written = Buffer.from(this.memory.buffer).write(s, bufPtr, bufSize, 'latin1');
this.view.setUint32(resultPtr, written, true);
return NAPI_OK;
}),
napi_get_value_string_utf8: this.wrap((envPtr, valueIdx, bufPtr, bufSize, resultPtr) => {
const s = this.load(valueIdx);
if (typeof s !== 'string') {
return NAPI_STRING_EXPECTED;
}
const written = Buffer.from(this.memory.buffer).write(s, bufPtr, bufSize, 'utf8');
this.view.setUint32(resultPtr, written, true);
return NAPI_OK;
}),
napi_get_value_string_utf16: this.wrap((envPtr, valueIdx, bufPtr, bufSize, resultPtr) => {
const s = this.load(valueIdx);
if (typeof s !== 'string') {
return NAPI_STRING_EXPECTED;
}
const written = Buffer.from(this.memory.buffer).write(s, bufPtr, bufSize, 'utf16');
this.view.setUint32(resultPtr, written, true);
return NAPI_OK;
}),
napi_get_value_uint32: this.wrap((envPtr, valueIdx, resultPtr) => {
const n = this.load(valueIdx);
if (typeof n !== 'number') {
return NAPI_NUMBER_EXPECTED;
}
this.view.setInt32(resultPtr, n, true);
return NAPI_OK;
}),
napi_get_boolean: this.wrap((envPtr, value, resultPtr) => {
this.writeValue(resultPtr, this.store(value));
return NAPI_OK;
}),
napi_get_global: this.wrap((envPtr, resultPtr) => {
this.writeValue(resultPtr, this.store(global));
return NAPI_OK;
}),
napi_get_null: this.wrap((envPtr, resultPtr) => {
this.writeValue(resultPtr, this.store(null));
return NAPI_OK;
}),
napi_get_undefined: this.wrap((envPtr, resultPtr) => {
this.writeValue(resultPtr, this.store(undefined));
return NAPI_OK;
}),
napi_coerce_to_bool: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(!!this.load(valueIdx)));
return NAPI_OK;
}),
napi_coerce_to_number: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(Number(this.load(valueIdx))));
return NAPI_OK;
}),
napi_coerce_to_object: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(Object(this.load(valueIdx))));
return NAPI_OK;
}),
napi_coerce_to_string: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(String(this.load(valueIdx))));
return NAPI_OK;
}),
napi_typeof: this.wrap((envPtr, valueIdx, resultPtr) => {
const v = this.load(valueIdx);
let vt;
switch (typeof v) {
case 'undefined':
vt = NAPI_UNDEFINED;
break;
// null handled below
case 'boolean':
vt = NAPI_BOOLEAN;
break;
case 'number':
vt = NAPI_NUMBER;
break;
case 'string':
vt = NAPI_STRING;
break;
case 'symbol':
vt = NAPI_SYMBOL;
break;
case 'object':
if (v === null) {
vt = NAPI_NULL;
} else if (this.externalData.has(v)) {
vt = NAPI_EXTERNAL;
} else {
vt = NAPI_OBJECT;
}
break;
case 'function':
vt = NAPI_FUNCTION;
break;
// external handled above
case 'bigint':
vt = NAPI_BIGINT;
break;
default:
throw new RangeError();
}
this.view.setUint32(resultPtr, vt, true);
return NAPI_OK;
}),
napi_instanceof: this.wrap((envPtr, objectIdx, constructorIdx, resultPtr) => {
const object = this.load(objectIdx);
const cons = this.load(constructorIdx);
this.view.setUint8(resultPtr, object instanceof cons);
return NAPI_OK;
}),
napi_is_array: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(util.types.isArray(this.load(valueIdx))));
return NAPI_OK;
}),
napi_is_arraybuffer: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(util.types.isArrayBuffer(this.load(valueIdx))));
return NAPI_OK;
}),
napi_is_date: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(util.types.isDate(this.load(valueIdx))));
return NAPI_OK;
}),
napi_is_error: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(util.types.isNativeError(this.load(valueIdx))));
return NAPI_OK;
}),
napi_is_typedarray: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(util.types.isTypedArray(this.load(valueIdx))));
return NAPI_OK;
}),
napi_is_dataview: this.wrap((envPtr, valueIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(util.types.isDataView(this.load(valueIdx))));
return NAPI_OK;
}),
napi_strict_equals: this.wrap((envPtr, lhsIdx, rhsIdx, resultPtr) => {
this.writeValue(resultPtr, this.store(this.load(lhsIdx) === this.load(rhsIdx)));
return NAPI_OK;
}),
napi_get_property_names: this.wrap((envPtr, valueIdx, resultPtr) => {
const object = this.load(valueIdx);
this.writeValue(resultPtr, this.store(Object.keys(object)));
return NAPI_OK;
}),
napi_set_property: this.wrap((envPtr, objectIdx, keyIdx, valueIdx) => {
const object = this.load(objectIdx);
const key = this.load(keyIdx);
const value = this.load(valueIdx);
object[key] = value;
return NAPI_OK;
}),
napi_get_property: this.wrap((envPtr, objectIdx, keyIdx, resultPtr) => {
const object = this.load(objectIdx);
const key = this.load(keyIdx);
this.writeValue(resultPtr, this.store(object[key]));
return NAPI_OK;
}),
napi_has_property: this.wrap((envPtr, objectIdx, keyIdx, resultPtr) => {
const object = this.load(objectIdx);
const key = this.load(keyIdx);
this.writeValue(resultPtr, this.store(key in object));
return NAPI_OK;
}),
napi_delete_property: this.wrap((envPtr, objectIdx, keyIdx, resultPtr) => {
const object = this.load(objectIdx);
const key = this.load(keyIdx);
this.writeValue(resultPtr, this.store(delete object[key]));
return NAPI_OK;
}),
napi_has_own_property: this.wrap((envPtr, objectIdx, keyIdx, resultPtr) => {
const object = this.load(objectIdx);
const key = this.load(keyIdx);
if (typeof key !== 'string' || typeof key !== 'symbol') {
return NAPI_NAME_EXPECTED;
}
this.writeValue(resultPtr, this.store(Object.prototype.hasOwnProperty.call(object, key)));
return NAPI_OK;
}),
napi_set_named_property: this.wrap((envPtr, objectIdx, utf8NamePtr, valueIdx) => {
const object = this.load(objectIdx);
const name = this.readCStringFrom(utf8NamePtr);
const value = this.load(valueIdx);
object[name] = value;
return NAPI_OK;
}),
napi_get_named_property: this.wrap((envPtr, objectIdx, utf8NamePtr, resultPtr) => {
const object = this.load(objectIdx);
const name = this.readCStringFrom(utf8NamePtr);
this.writeValue(resultPtr, this.store(object[name]));
return NAPI_OK;
}),
napi_has_named_property: this.wrap((envPtr, objectIdx, utf8NamePtr, resultPtr) => {
const object = this.load(objectIdx);
const key = this.readCStringFrom(utf8NamePtr);
this.writeValue(resultPtr, this.store(Object.prototype.hasOwnProperty.call(object, key)));
return NAPI_OK;
}),
napi_set_element: this.wrap((envPtr, objectIdx, index, valueIdx) => {
this.load(objectIdx)[index] = this.load(valueIdx);
return NAPI_OK;
}),
napi_get_element: this.wrap((envPtr, objectIdx, index, resultPtr) => {
this.writeValue(resultPtr, this.store(this.load(objectIdx)[index]));
return NAPI_OK;
}),
napi_has_element: this.wrap((envPtr, objectIdx, index, resultPtr) => {
const object = this.load(objectIdx);
this.writeValue(resultPtr, this.store(Object.prototype.hasOwnProperty.call(object, index)));
return NAPI_OK;
}),
napi_delete_element: this.wrap((envPtr, objectIdx, index, resultPtr) => {
const object = this.load(objectIdx);
this.writeValue(resultPtr, this.store(delete object[index]));
return NAPI_OK;
}),
napi_define_properties: this.wrap((envPtr, objectIdx, propertyCount, propertiesPtr) => {
const object = this.load(objectIdx);
if (typeof object !== 'object' || object === null) {
return NAPI_OBJECT_EXPECTED;
}
this.readPropertyDescriptors(propertyCount, propertiesPtr)
.forEach(({
method,
getter,
setter,
valueIdx,
dataPtr,
descriptor,
name,
}) => {
if (getter || setter) {
const get = getter ? this.createFunction(envPtr, getter, dataPtr) : undefined;
const set = setter ? this.createFunction(envPtr, setter, dataPtr) : undefined;
Object.defineProperty(object, name, { ...descriptor, get, set });
} else if (method) {
const value = this.createFunction(envPtr, method, dataPtr, name);
Object.defineProperty(object, name, { ...descriptor, value });
} else {
const value = this.load(valueIdx);
Object.defineProperty(object, name, { ...descriptor, value });
}
});
return NAPI_OK;
}),
napi_call_function: this.wrap((envPtr, recvIdx, funcIdx, argc, argvPtr, resultPtr) => {
const recv = this.load(recvIdx);
const func = this.load(funcIdx);
if (typeof func !== 'function') {
return NAPI_FUNCTION_EXPECTED;
}
const args = [];
for (let i = 0; i < argc; i += 1) {
args.push(this.load(argvPtr + (i * 4)));
}
const result = func.apply(recv, args);
this.writeValue(resultPtr, this.store(result));
return NAPI_OK;
}),
napi_create_function: this.wrap((envPtr, utf8NamePtr, length, cb, data, resultPtr) => {
let name;
if (length > 0) {
name = Buffer.from(this.memory.buffer, utf8NamePtr, length).toString('utf8');
}
const func = this.createFunction(envPtr, cb, data, name);
this.writeValue(resultPtr, this.store(func));
return NAPI_OK;
}),
napi_get_cb_info: this.wrap((envPtr, cbinfoIdx, argcPtr, argvPtr, thisArgPtr, dataPtr) => {
const info = this.load(cbinfoIdx);
const argc = this.view.getUint32(argcPtr, true);
this.view.setUint32(argcPtr, info.args.length, true);
for (let i = 0; i < argc; i += 1) {
this.writeValue(argvPtr + (i * 4), this.store(info.args[i]));
}
this.writeValue(thisArgPtr, this.store(info.this));
this.view.setUint32(dataPtr, info.data, true);
return NAPI_OK;
}),
napi_get_new_target: this.wrap((envPtr, cbinfoIdx, resultPtr) => {
const info = this.load(cbinfoIdx);
this.writeValue(resultPtr, this.store(info.newTarget));
return NAPI_OK;
}),
napi_new_instance: this.wrap((envPtr, consIdx, argc, argvPtr, resultPtr) => {
const cons = this.load(consIdx);
if (typeof func !== 'function') {
return NAPI_FUNCTION_EXPECTED;
}
const args = [];
for (let i = 0; i < argc; i += 1) {
args.push(this.load(argvPtr + (i * 4)));
}
const result = Reflect.construct(cons, args);
this.writeValue(resultPtr, this.store(result));
return NAPI_OK;
}),
napi_define_class: this.wrap((envPtr, utf8NamePtr, utf8NameLength, constructorIdx,
data, propertyCount, propertiesPtr, resultPtr) => {
const func = this.createFunction(envPtr, constructorIdx, data, utf8NameLength > 0
? Buffer.from(this.memory.buffer, utf8NamePtr, utf8NameLength).toString('utf8')
: undefined, kFuncConstructor);
this.readPropertyDescriptors(propertyCount, propertiesPtr)
.forEach(({
method,
getter,
setter,
valueIdx,
dataPtr,
descriptor,
name,
}) => {
const target = descriptor.static ? func : func.prototype;
if (getter || setter) {
const get = getter ? this.createFunction(envPtr, getter, dataPtr) : undefined;
const set = setter ? this.createFunction(envPtr, setter, dataPtr) : undefined;
Object.defineProperty(target, name, { ...descriptor, get, set });
} else if (method) {
const value = this.createFunction(envPtr, method, dataPtr, name, kFuncMethod);
Object.defineProperty(target, name, { ...descriptor, value });
} else {
const value = this.load(valueIdx);
Object.defineProperty(target, name, { ...descriptor, value });
}
});
this.writeValue(resultPtr, this.store(func));
return NAPI_OK;
}),
napi_wrap: this.wrap((envPtr, jsObjectIdx, nativeObjectPtr,
finalizeCb, finalizeHint, resultPtr) => {
const jsObject = this.load(jsObjectIdx);
this.finalizationGroup
.register(jsObject, [finalizeCb, envPtr, nativeObjectPtr, finalizeHint], nativeObjectPtr);
this.wrapData.set(jsObject, nativeObjectPtr);
if (resultPtr !== 0) {
const ref = { value: jsObject, ref: 1 };
this.references.push(ref);
this.writeValue(resultPtr, this.references.length - 1);
}
return NAPI_OK;
}),
napi_unwrap: this.wrap((envPtr, jsObjectIdx, resultPtr) => {
const jsObject = this.load(jsObjectIdx);
if (!this.wrapData.has(jsObject)) {
return NAPI_INVALID_ARG;
}
this.view.setUint32(resultPtr, this.wrapData.get(jsObject));
return NAPI_OK;
}),
napi_remove_wrap: this.wrap((envPtr, jsObjectIdx, resultPtr) => {
const jsObject = this.load(jsObjectIdx);
if (!this.wrapData.has(jsObject)) {
return NAPI_INVALID_ARG;
}
const nativeObjectPtr = this.wrapData.get(jsObject);
this.wrapData.delete(jsObject);
this.finalizationGroup.unregister(nativeObjectPtr);
this.view.setUint32(resultPtr, nativeObjectPtr);
return NAPI_OK;
}),
napi_add_finalizer: this.wrap((envPtr, jsObjectIdx, nativeObjectPtr,
finalizeCb, finalizeHint, resultPtr) => {
const jsObject = this.load(jsObjectIdx);
this.finalizationGroup
.register(jsObject, [finalizeCb, envPtr, nativeObjectPtr, finalizeHint], nativeObjectPtr);
if (resultPtr !== 0) {
const ref = { value: jsObject, ref: 1 };
this.references.push(ref);
this.writeValue(resultPtr, this.references.length - 1);
}
return NAPI_OK;
}),
};
refreshMemory() {
if (this.view === undefined || this.view.byteLength === 0) {
this.view = new DataView(this.memory.buffer);
}
}
openHandleScope() {
this.handleScope.unshift([null, undefined, true, false, global]);
}
closeHandleScope() {
this.handleScope.shift();
}
store(obj) {
if (obj === null) {
return 0;
}
if (obj === undefined) {
return 1;
}
if (obj === true) {
return 2;
}
if (obj === false) {
return 3;
}
if (obj === global) {
return 4;
}
const idx = this.handleScope[0].length;
this.handleScope[0].push(obj);
return idx;
}
load(idx) {
return this.handleScope[0][idx];
}
writeValue(ptr, idx) {
this.view.setUint32(ptr, idx, true);
}
readCStringFrom(ptr) {
let length = 0;
while (this.view.getUint8(ptr + length) !== 0) {
length += 1;
}
return Buffer.from(this.memory.buffer, ptr, length).toString('utf8');
}
init(instance) {
this.memory = instance.exports.memory;
this.indirectFunctionTable = instance.exports.__indirect_function_table;
this.openHandleScope();
try {
const idx = instance.exports._napi_register(0, this.store({}));
return this.load(idx);
} finally {
this.closeHandleScope();
if (this.exceptionPending) {
this.exceptionPending = false;
throw this.exception; // eslint-disable-line no-unsafe-finally
}
}
}
}
module.exports = NAPI;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment