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
JavaScript
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