UNPKG

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