hakojs
Version:
A secure, embeddable JavaScript engine that runs untrusted code inside WebAssembly sandboxes with fine-grained permissions and resource limits
217 lines (214 loc) • 7.45 kB
JavaScript
// src/host/runtime.ts
import {
intrinsicsToFlags,
JS_STRIP_DEBUG,
JS_STRIP_SOURCE
} from "../etc/types";
import { DisposableResult, Scope } from "../mem/lifetime";
import { CModuleBuilder } from "../vm/cmodule";
import { VMContext } from "../vm/context";
import { VMValue } from "../vm/value";
class HakoRuntime {
container;
context;
rtPtr;
isReleased = false;
contextMap = new Map;
currentInterruptHandler = null;
constructor(container, rtPtr) {
this.container = container;
this.rtPtr = rtPtr;
this.container.callbacks.registerRuntime(rtPtr, this);
}
get pointer() {
return this.rtPtr;
}
createCModule(name, handler, ctx = undefined) {
return new CModuleBuilder(ctx ? ctx : this.getSystemContext(), name, handler);
}
createContext(options = {}) {
if (options.contextPointer) {
const existingContext = this.contextMap.get(options.contextPointer);
if (existingContext) {
return existingContext;
}
return new VMContext(this.container, this, options.contextPointer);
}
const intrinsics = options.intrinsics ? intrinsicsToFlags(options.intrinsics) : 0;
const ctxPtr = this.container.exports.HAKO_NewContext(this.rtPtr, intrinsics);
if (ctxPtr === 0) {
throw new Error("Failed to create context");
}
const context = new VMContext(this.container, this, ctxPtr);
if (options.maxStackSizeBytes) {
context.setMaxStackSize(options.maxStackSizeBytes);
}
this.contextMap.set(ctxPtr, context);
return context;
}
setStripInfo(options) {
let flags = 0;
if (options?.stripSource) {
flags |= JS_STRIP_SOURCE;
}
if (options?.stripDebug) {
flags |= JS_STRIP_DEBUG;
}
this.container.exports.HAKO_SetStripInfo(this.rtPtr, flags);
}
getStripInfo() {
const flags = this.container.exports.HAKO_GetStripInfo(this.rtPtr);
return {
stripSource: (flags & JS_STRIP_SOURCE) !== 0 || (flags & JS_STRIP_DEBUG) !== 0,
stripDebug: (flags & JS_STRIP_DEBUG) !== 0
};
}
setMemoryLimit(limit) {
const runtimeLimit = limit === undefined ? -1 : limit;
this.container.exports.HAKO_RuntimeSetMemoryLimit(this.rtPtr, runtimeLimit);
}
computeMemoryUsage(ctx = undefined) {
return Scope.withScope((scope) => {
const ctxPtr = ctx ? ctx.pointer : this.getSystemContext().pointer;
const valuePtr = this.container.exports.HAKO_RuntimeComputeMemoryUsage(this.rtPtr, ctxPtr);
if (valuePtr === 0) {
console.error("Failed to compute memory usage");
return {};
}
scope.add(() => this.container.memory.freeValuePointer(ctxPtr, valuePtr));
const jsonValue = this.container.exports.HAKO_ToJson(ctxPtr, valuePtr, 0);
if (jsonValue === 0) {
throw new Error("Failed to convert memory usage to JSON");
}
scope.add(() => this.container.memory.freeValuePointer(ctxPtr, jsonValue));
const strPtr = this.container.exports.HAKO_ToCString(ctxPtr, jsonValue);
if (strPtr === 0) {
throw new Error("Failed to get string from memory usage");
}
scope.add(() => this.container.memory.freeCString(ctxPtr, strPtr));
const str = this.container.memory.readString(strPtr);
return JSON.parse(str);
});
}
dumpMemoryUsage() {
const strPtr = this.container.exports.HAKO_RuntimeDumpMemoryUsage(this.rtPtr);
const str = this.container.memory.readString(strPtr);
this.container.memory.freeRuntimeMemory(this.pointer, strPtr);
return str;
}
enableModuleLoader(loader, normalizer, resolver) {
this.container.callbacks.setModuleLoader(loader);
if (normalizer) {
this.container.callbacks.setModuleNormalizer(normalizer);
}
if (resolver) {
this.container.callbacks.setModuleResolver(resolver);
}
this.container.exports.HAKO_RuntimeEnableModuleLoader(this.rtPtr, normalizer ? 1 : 0);
}
disableModuleLoader() {
this.container.callbacks.setModuleLoader(null);
this.container.callbacks.setModuleNormalizer(null);
this.container.exports.HAKO_RuntimeDisableModuleLoader(this.rtPtr);
}
enableInterruptHandler(handler, opaque) {
this.currentInterruptHandler = handler;
this.container.callbacks.setInterruptHandler(handler);
this.container.exports.HAKO_RuntimeEnableInterruptHandler(this.rtPtr, opaque || 0);
}
enableProfileCalls(handler, sampling, opaque) {
this.container.callbacks.setProfilerHandler(handler);
this.container.exports.HAKO_EnableProfileCalls(this.rtPtr, sampling ?? 1, opaque ?? 0);
}
disableInterruptHandler() {
this.currentInterruptHandler = null;
this.container.callbacks.setInterruptHandler(null);
this.container.exports.HAKO_RuntimeDisableInterruptHandler(this.rtPtr);
}
getSystemContext() {
if (!this.context) {
this.context = this.createContext();
}
return this.context;
}
createDeadlineInterruptHandler(deadlineMs) {
const deadline = Date.now() + deadlineMs;
return () => {
return Date.now() >= deadline;
};
}
createGasInterruptHandler(maxGas) {
let gas = 0;
return () => {
gas++;
return gas >= maxGas;
};
}
isJobPending() {
return this.container.exports.HAKO_IsJobPending(this.rtPtr) !== 0;
}
executePendingJobs(maxJobsToExecute = -1) {
const ctxPtrOut = this.container.memory.allocateRuntimePointerArray(this.pointer, 1);
const resultPtr = this.container.exports.HAKO_ExecutePendingJob(this.rtPtr, maxJobsToExecute, ctxPtrOut);
const ctxPtr = this.container.memory.readPointerFromArray(ctxPtrOut, 0);
this.container.memory.freeRuntimeMemory(this.pointer, ctxPtrOut);
if (ctxPtr === 0) {
this.container.memory.freeValuePointerRuntime(this.pointer, resultPtr);
return DisposableResult.success(0);
}
const context = this.createContext({
contextPointer: ctxPtr
});
const value = VMValue.fromHandle(context, resultPtr, "owned");
if (value.type === "number") {
const executedJobs = value.asNumber();
value.dispose();
return DisposableResult.success(executedJobs);
}
const error = Object.assign(value, { context });
return DisposableResult.fail(error, (error2) => context.unwrapResult(error2));
}
recoverableLeakCheck() {
return this.container.exports.HAKO_RecoverableLeakCheck();
}
dropContext(context) {
this.contextMap.delete(context.pointer);
}
release() {
if (!this.isReleased) {
if (this.currentInterruptHandler) {
this.disableInterruptHandler();
}
this.container.callbacks.unregisterRuntime(this.rtPtr);
for (const [, context] of this.contextMap.entries()) {
context.release();
}
if (this.context) {
this.context.release();
}
this.contextMap.clear();
this.container.exports.HAKO_FreeRuntime(this.rtPtr);
this.isReleased = true;
}
}
allocateMemory(size) {
if (this.isReleased) {
throw new Error("Cannot allocate memory on a released runtime");
}
return this.container.memory.allocateRuntimeMemory(this.pointer, size);
}
freeMemory(ptr) {
this.container.memory.freeRuntimeMemory(this.pointer, ptr);
}
get build() {
return this.container.utils.getBuildInfo();
}
[Symbol.dispose]() {
this.release();
}
}
export {
HakoRuntime
};
//# debugId=2260AD34E9DC26A664756E2164756E21
//# sourceMappingURL=runtime.js.map