UNPKG

hakojs

Version:

A secure, embeddable JavaScript engine that runs untrusted code inside WebAssembly sandboxes with fine-grained permissions and resource limits

696 lines (693 loc) 22.9 kB
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose"); var __asyncDispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose"); var __using = (stack, value, async) => { if (value != null) { if (typeof value !== "object" && typeof value !== "function") throw TypeError('Object expected to be assigned to "using" declaration'); var dispose; if (async) dispose = value[__asyncDispose]; if (dispose === undefined) dispose = value[__dispose]; if (typeof dispose !== "function") throw TypeError("Object not disposable"); stack.push([async, dispose, value]); } else if (async) { stack.push([async]); } return value; }; var __callDispose = (stack, error, hasError) => { var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) { return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _; }, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => { while (it = stack.pop()) { try { var result = it[1] && it[1].call(it[2]); if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next())); } catch (e) { fail(e); } } if (hasError) throw error; }; return next(); }; // src/vm/value.ts import { HakoError, PrimJSUseAfterFree } from "../etc/errors"; import { LEPUS_BOOLToBoolean, PROPERTY_ENUM_ENUMERABLE, PROPERTY_ENUM_STRING } from "../etc/types"; import { Scope } from "../mem/lifetime"; class VMValue { context; handle; lifecycle; constructor(context, handle, lifecycle = "owned") { this.context = context; this.handle = handle; this.lifecycle = lifecycle; } static fromHandle(ctx, handle, lifecycle) { return new VMValue(ctx, handle, lifecycle); } getHandle() { this.assertAlive(); return this.handle; } get alive() { return this.handle !== 0; } getContextPointer() { this.assertAlive(); return this.context.pointer; } consume(consumer) { this.assertAlive(); try { return consumer(this); } finally { this.dispose(); } } dup() { this.assertAlive(); const newPtr = this.context.container.memory.dupValuePointer(this.context.pointer, this.handle); return new VMValue(this.context, newPtr, "owned"); } borrow() { this.assertAlive(); return new VMValue(this.context, this.handle, "borrowed"); } getValueType() { const type = this.context.container.exports.HAKO_TypeOf(this.context.pointer, this.handle); switch (type) { case 0: return "undefined"; case 1: return "object"; case 2: return "string"; case 3: return "symbol"; case 4: return "boolean"; case 5: return "number"; case 6: return "bigint"; case 7: return "function"; default: return "undefined"; } } get type() { this.assertAlive(); if (this.isNull()) return "object"; return this.getValueType(); } isUndefined() { this.assertAlive(); return this.context.container.utils.isEqual(this.context.pointer, this.handle, this.context.container.exports.HAKO_GetUndefined(), 0); } isError() { this.assertAlive(); return this.context.container.exports.HAKO_IsError(this.context.pointer, this.handle) !== 0; } isException() { this.assertAlive(); return this.context.container.exports.HAKO_IsException(this.handle) !== 0; } isNull() { this.assertAlive(); return LEPUS_BOOLToBoolean(this.context.container.exports.HAKO_IsNull(this.handle)); } isBoolean() { this.assertAlive(); return this.type === "boolean"; } isNumber() { this.assertAlive(); return this.type === "number"; } isString() { this.assertAlive(); return this.type === "string"; } isSymbol() { this.assertAlive(); return this.type === "symbol"; } isObject() { this.assertAlive(); return this.type === "object"; } isArray() { this.assertAlive(); return this.context.container.exports.HAKO_IsArray(this.context.pointer, this.handle) !== 0; } getTypedArrayType() { this.assertAlive(); if (!this.isTypedArray()) { throw new TypeError("Value is not a typed array"); } const typeId = this.context.container.exports.HAKO_GetTypedArrayType(this.context.pointer, this.handle); switch (typeId) { case 1: return "Uint8Array"; case 2: return "Uint8ClampedArray"; case 3: return "Int8Array"; case 4: return "Uint16Array"; case 5: return "Int16Array"; case 6: return "Uint32Array"; case 7: return "Int32Array"; case 8: return "BigInt64Array"; case 9: return "BigUint64Array"; case 10: return "Float16Array"; case 11: return "Float32Array"; case 12: return "Float64Array"; default: return "Unknown"; } } isTypedArray() { this.assertAlive(); return this.context.container.exports.HAKO_IsTypedArray(this.context.pointer, this.handle) !== 0; } isArrayBuffer() { this.assertAlive(); return this.context.container.exports.HAKO_IsArrayBuffer(this.handle) !== 0; } isFunction() { this.assertAlive(); return this.type === "function"; } isPromise() { this.assertAlive(); return this.context.container.exports.HAKO_IsPromise(this.context.pointer, this.handle) !== 0; } asNumber() { this.assertAlive(); return this.context.container.exports.HAKO_GetFloat64(this.context.pointer, this.handle); } asBoolean() { this.assertAlive(); if (this.isBoolean()) { return this.context.container.utils.isEqual(this.context.pointer, this.handle, this.context.container.exports.HAKO_GetTrue(), 0); } if (this.isNull() || this.isUndefined()) return false; if (this.isNumber()) return this.asNumber() !== 0 && !Number.isNaN(this.asNumber()); if (this.isString()) return this.asString() !== ""; return true; } asString() { this.assertAlive(); const strPtr = this.context.container.exports.HAKO_ToCString(this.context.pointer, this.handle); const str = this.context.container.memory.readString(strPtr); this.context.container.memory.freeCString(this.context.pointer, strPtr); return str; } getProperty(key) { this.assertAlive(); return Scope.withScope((scope) => { if (typeof key === "number") { const propPtr2 = this.context.container.exports.HAKO_GetPropNumber(this.context.pointer, this.handle, key); if (propPtr2 === 0) { const error = this.context.getLastError(); if (error) { throw error; } } return new VMValue(this.context, propPtr2, "owned"); } let keyPtr; if (typeof key === "string") { const keyStrPtr = this.context.container.memory.allocateString(this.context.pointer, key); keyPtr = this.context.container.exports.HAKO_NewString(this.context.pointer, keyStrPtr); scope.add(() => { this.context.container.memory.freeMemory(this.context.pointer, keyStrPtr); this.context.container.memory.freeValuePointer(this.context.pointer, keyPtr); }); } else { keyPtr = key.getHandle(); } const propPtr = this.context.container.exports.HAKO_GetProp(this.context.pointer, this.handle, keyPtr); if (propPtr === 0) { const error = this.context.getLastError(); if (error) { throw error; } } return new VMValue(this.context, propPtr, "owned"); }); } setProperty(key, value) { this.assertAlive(); return Scope.withScope((scope) => { let keyPtr; let valuePtr; if (typeof key === "number") { const keyValue = scope.manage(this.context.newValue(key)); keyPtr = keyValue.getHandle(); } else if (typeof key === "string") { const keyValue = scope.manage(this.context.newValue(key)); keyPtr = keyValue.getHandle(); } else { keyPtr = key.getHandle(); } if (value instanceof VMValue) { valuePtr = value.getHandle(); } else { const valueJSValue = scope.manage(this.context.newValue(value)); valuePtr = valueJSValue.getHandle(); } const result = this.context.container.exports.HAKO_SetProp(this.context.pointer, this.handle, keyPtr, valuePtr); if (result === -1) { const error = this.context.getLastError(); if (error) { throw error; } } return LEPUS_BOOLToBoolean(result); }); } defineProperty(key, descriptor) { this.assertAlive(); return Scope.withScope((scope) => { let keyPtr; if (typeof key === "string") { const keyStr = scope.manage(this.context.newValue(key)); keyPtr = keyStr.getHandle(); } else { keyPtr = key.getHandle(); } let valuePtr = this.context.container.exports.HAKO_GetUndefined(); let getPtr = this.context.container.exports.HAKO_GetUndefined(); let setPtr = this.context.container.exports.HAKO_GetUndefined(); const configurable = descriptor.configurable || false; const enumerable = descriptor.enumerable || false; let hasValue = false; if (descriptor.value !== undefined) { if (descriptor.value instanceof VMValue) { valuePtr = descriptor.value.getHandle(); } else { const jsValue = scope.manage(this.context.newValue(descriptor.value)); valuePtr = jsValue.getHandle(); } hasValue = true; } if (descriptor.get !== undefined) { if (descriptor.get instanceof VMValue) { getPtr = descriptor.get.getHandle(); } else { throw new Error("Getter must be a JSValue"); } } if (descriptor.set !== undefined) { if (descriptor.set instanceof VMValue) { setPtr = descriptor.set.getHandle(); } else { throw new Error("Setter must be a JSValue"); } } const result = this.context.container.exports.HAKO_DefineProp(this.context.pointer, this.handle, keyPtr, valuePtr, getPtr, setPtr, configurable ? 1 : 0, enumerable ? 1 : 0, hasValue ? 1 : 0); if (result === -1) { const error = this.context.getLastError(); if (error) { throw error; } } return LEPUS_BOOLToBoolean(result); }); } isGlobalSymbol() { this.assertAlive(); return this.context.container.exports.HAKO_IsGlobalSymbol(this.context.pointer, this.handle) === 1; } *getOwnPropertyNames(flags = PROPERTY_ENUM_STRING | PROPERTY_ENUM_ENUMERABLE) { this.assertAlive(); const scope = new Scope; let outPtrsBase = null; let outLen = 0; const outPtrPtr = this.context.container.memory.allocatePointerArray(this.context.pointer, 2); const outLenPtr = this.context.container.memory.allocateMemory(this.context.pointer, 4); scope.add(() => { this.context.container.memory.freeMemory(this.context.pointer, outPtrPtr); this.context.container.memory.freeMemory(this.context.pointer, outLenPtr); }); this.context.container.memory.writeUint32(outLenPtr, 1000); const errorPtr = this.context.container.exports.HAKO_GetOwnPropertyNames(this.context.pointer, outPtrPtr, outLenPtr, this.handle, flags); const error = this.context.getLastError(errorPtr); if (error) { this.context.container.memory.freeValuePointer(this.context.pointer, errorPtr); scope.release(); throw error; } outLen = this.context.container.memory.readUint32(outLenPtr); outPtrsBase = this.context.container.memory.readPointer(outPtrPtr); for (let currentIndex = 0;currentIndex < outLen; currentIndex++) { const valuePtr = this.context.container.memory.readPointerFromArray(outPtrsBase, currentIndex); try { yield new VMValue(this.context, valuePtr, "owned"); } catch (_e) { for (let i = currentIndex;i < outLen; i++) { const unyieldedValuePtr = this.context.container.memory.readPointerFromArray(outPtrsBase, i); this.context.container.memory.freeValuePointer(this.context.pointer, unyieldedValuePtr); } break; } } if (outPtrsBase !== null) { this.context.container.memory.freeMemory(this.context.pointer, outPtrsBase); } scope.release(); } getPromiseState() { this.assertAlive(); if (!this.isPromise()) { throw new Error("Value is not a promise"); } switch (this.context.container.exports.HAKO_PromiseState(this.context.pointer, this.handle)) { case 0: return "pending"; case 1: return "fulfilled"; case 2: return "rejected"; default: return; } } getPromiseResult() { this.assertAlive(); if (!this.isPromise()) { throw new TypeError("Value is not a promise"); } const state = this.getPromiseState(); if (state !== "fulfilled" && state !== "rejected") { return; } const resultPtr = this.context.container.exports.HAKO_PromiseResult(this.context.pointer, this.handle); return new VMValue(this.context, resultPtr, "owned"); } stringify(indent = 0) { this.assertAlive(); const jsonPtr = this.context.container.exports.HAKO_ToJson(this.context.pointer, this.handle, indent); const error = this.context.getLastError(jsonPtr); if (error) { this.context.container.memory.freeValuePointer(this.context.pointer, jsonPtr); throw error; } return new VMValue(this.context, jsonPtr, "owned").consume((json) => { const str = json.asString(); this.context.container.memory.freeCString(this.context.pointer, jsonPtr); return str; }); } getBigInt() { if (!this.context.container.utils.getBuildInfo().hasBignum) { throw new HakoError("This build of Hako does not have BigInt enabled."); } this.assertAlive(); if (this.type !== "bigint") { throw new TypeError("Value is not a BigInt"); } return BigInt(this.asString()); } getLength() { this.assertAlive(); if (!this.isArray()) { throw new Error("Value is not an array"); } return this.context.container.utils.getLength(this.context.pointer, this.handle); } toNativeValue() { this.assertAlive(); const type = this.type; const disposables = []; disposables.push(this); const createResult = (value) => { let alive = true; return { value, alive, dispose() { if (!alive) return; alive = false; for (const d of disposables) d[Symbol.dispose](); }, [Symbol.dispose]() { this.dispose(); } }; }; try { switch (type) { case "undefined": return createResult(undefined); case "boolean": return createResult(this.asBoolean()); case "number": return createResult(this.asNumber()); case "string": return createResult(this.asString()); case "symbol": return createResult(Symbol(this.asString())); case "bigint": return createResult(BigInt(this.asString())); case "object": { if (this.isNull()) { return createResult(null); } if (this.isArray()) { const length = this.getLength(); const result2 = []; for (let i = 0;i < length; i++) { const item = this.getProperty(i).toNativeValue(); disposables.push(item); result2.push(item.value); } return createResult(result2); } const result = {}; const ownProps = this.getOwnPropertyNames(); let iterationResult = ownProps.next(); try { while (!iterationResult.done) { const prop = iterationResult.value; const propName = prop.consume((v) => v.asString()); const value = this.getProperty(propName).toNativeValue(); disposables.push(value); result[propName] = value.value; iterationResult = ownProps.next(); } return createResult(result); } catch (error) { ownProps.throw(error); throw error; } } case "function": { let __stack = []; try { const jsFunction = (...args) => { return Scope.withScope((scope) => { let __stack2 = []; try { const jsArgs = args.map((arg) => scope.manage(this.context.newValue(arg))); const result = __using(__stack2, this.context.callFunction(this, null, ...jsArgs).unwrap(), 0); const resultJs = scope.manage(result.toNativeValue()); return resultJs.value; } catch (_catch2) { var _err2 = _catch2, _hasErr2 = 1; } finally { __callDispose(__stack2, _err2, _hasErr2); } }); }; const nameProperty = __using(__stack, this.getProperty("name"), 0); if (nameProperty?.isString()) { Object.defineProperty(jsFunction, "name", { value: nameProperty.asString(), configurable: true }); } jsFunction.toString = () => `[PrimJS Function: ${this.asString()}]`; return createResult(jsFunction); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } default: throw new Error("Unknown type"); } } catch (error) { for (const d of disposables) d[Symbol.dispose](); throw error; } } copyTypedArray() { this.assertAlive(); if (this.getTypedArrayType() !== "Uint8Array") { throw new TypeError("Value is not a Uint8Array"); } return Scope.withScope((scope) => { const pointer = this.context.container.memory.allocatePointerArray(this.context.pointer, 1); scope.add(() => { this.context.container.memory.freeMemory(this.context.pointer, pointer); }); const bufPtr = this.context.container.exports.HAKO_CopyTypedArrayBuffer(this.context.pointer, this.handle, pointer); if (bufPtr === 0) { const error = this.context.getLastError(); if (error) { throw error; } } const length = this.context.container.memory.readPointerFromArray(pointer, 0); scope.add(() => { this.context.container.memory.freeMemory(this.context.pointer, bufPtr); }); return new Uint8Array(this.context.container.exports.memory.buffer).slice(bufPtr, bufPtr + length); }); } copyArrayBuffer() { this.assertAlive(); if (!this.isArrayBuffer()) { throw new TypeError("Value is not an ArrayBuffer"); } return Scope.withScope((scope) => { const pointer = this.context.container.memory.allocatePointerArray(this.context.pointer, 1); scope.add(() => { this.context.container.memory.freeMemory(this.context.pointer, pointer); }); const bufPtr = this.context.container.exports.HAKO_CopyArrayBuffer(this.context.pointer, this.handle, pointer); if (bufPtr === 0) { const error = this.context.getLastError(); if (error) { throw error; } } const length = this.context.container.memory.readPointer(pointer); if (length === 0) { return new ArrayBuffer(0); } scope.add(() => { this.context.container.memory.freeMemory(this.context.pointer, bufPtr); }); const mem = this.context.container.memory.slice(bufPtr, length); const result = new ArrayBuffer(length); const resultView = new Uint8Array(result); resultView.set(mem); return result; }); } dispose() { if (!this.alive) return; if (this.lifecycle === "borrowed") { this.handle = 0; return; } this.context.container.memory.freeValuePointer(this.context.pointer, this.handle); this.handle = 0; } [Symbol.dispose]() { this.dispose(); } static isEqual(a, b, ctx, compar, equalityType = "strict") { if (a === b) { return true; } if (a.getContextPointer() !== ctx || b.getContextPointer() !== 0) { return false; } if (a.getContextPointer() !== b.getContextPointer()) { return false; } const mapped = equalityType === "strict" ? 0 : equalityType === "same" ? 1 : 2; const result = compar(ctx, a.getHandle(), b.getHandle(), mapped); if (result === -1) { throw new Error("NOT IMPLEMENTED"); } return Boolean(result); } classId() { this.assertAlive(); return this.context.container.exports.HAKO_GetClassID(this.handle); } getOpaque() { this.assertAlive(); const result = this.context.container.exports.HAKO_GetOpaque(this.context.pointer, this.handle, this.classId()); if (this.context.getLastError()) { throw this.context.getLastError(); } return result; } setOpaque(opaque) { this.assertAlive(); this.context.container.exports.HAKO_SetOpaque(this.handle, opaque); } freeOpaque() { this.assertAlive(); const opaque = this.getOpaque(); if (opaque !== 0) { this.context.container.exports.HAKO_Free(this.context.pointer, opaque); this.setOpaque(0); } } instanceof(other) { this.assertAlive(); const result = this.context.container.exports.HAKO_IsInstanceOf(this.context.pointer, this.handle, other.getHandle()); if (result === -1) { const error = this.context.getLastError(); if (error) { throw error; } } return result === 1; } eq(other) { return VMValue.isEqual(this, other, this.getContextPointer(), this.context.container.exports.HAKO_IsEqual, "strict"); } sameValue(other) { return VMValue.isEqual(this, other, this.getContextPointer(), this.context.container.exports.HAKO_IsEqual, "same"); } sameValueZero(other) { return VMValue.isEqual(this, other, this.getContextPointer(), this.context.container.exports.HAKO_IsEqual, "same-zero"); } assertAlive() { if (!this.alive) { throw new PrimJSUseAfterFree("VMValue is disposed"); } } } export { VMValue }; //# debugId=1A6769D02615231064756E2164756E21 //# sourceMappingURL=value.js.map