UNPKG

hakojs

Version:

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

496 lines (492 loc) 16.2 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/host/callback.ts import { DisposableResult, Scope } from "../mem/lifetime"; import { CModuleInitializer } from "../vm/cmodule"; import { VMValue } from "../vm/value"; var HAKO_MODULE_SOURCE_STRING = 0; var HAKO_MODULE_SOURCE_PRECOMPILED = 1; var HAKO_MODULE_SOURCE_ERROR = 2; class CallbackManager { exports = null; memory; hostFunctions = new Map; moduleInitHandlers = new Map; classConstructors = new Map; classFinalizers = new Map; nextFunctionId = -32768; moduleLoader = null; moduleNormalizer = null; moduleResolver = null; interruptHandler = null; profilerHandler = null; contextRegistry = new Map; runtimeRegistry = new Map; constructor(memory) { this.memory = memory; } setExports(exports) { this.exports = exports; } getImports() { return { hako: { call_function: (ctxPtr, thisPtr, argc, argv, funcId) => { return this.handleHostFunctionCall(ctxPtr, thisPtr, argc, argv, funcId); }, interrupt_handler: (rtPtr, ctxPtr, opaque) => { return this.handleInterrupt(rtPtr, ctxPtr, opaque) ? 1 : 0; }, load_module: (rtPtr, ctxPtr, moduleNamePtr, _opaque, attributesPtr) => { return this.handleModuleLoad(rtPtr, ctxPtr, moduleNamePtr, attributesPtr); }, normalize_module: (rtPtr, ctxPtr, baseNamePtr, moduleNamePtr, _opaque) => { return this.handleModuleNormalize(rtPtr, ctxPtr, baseNamePtr, moduleNamePtr); }, resolve_module: (rtPtr, ctxPtr, moduleNamePtr, currentModulePtr, _opaque) => { return this.handleModuleResolve(rtPtr, ctxPtr, moduleNamePtr, currentModulePtr, _opaque); }, profile_function_start: (ctxPtr, func_name, opaque) => { this.handleProfileFunctionStart(ctxPtr, func_name, opaque); }, profile_function_end: (ctxPtr, func_name, opaque) => { this.handleProfileFunctionEnd(ctxPtr, func_name, opaque); }, module_init: (ctxPtr, modulePtr) => { return this.handleModuleInit(ctxPtr, modulePtr); }, class_constructor: (ctxPtr, newTargetPtr, argc, argvPtr, classId) => { return this.handleClassConstructor(ctxPtr, newTargetPtr, argc, argvPtr, classId); }, class_finalizer: (rtPtr, opaque, classId) => { this.handleClassFinalizer(rtPtr, opaque, classId); } } }; } registerContext(ctxPtr, ctx) { this.contextRegistry.set(ctxPtr, ctx); } unregisterContext(ctxPtr) { this.contextRegistry.delete(ctxPtr); } unregisterClassConstructor(classId) { this.classConstructors.delete(classId); } unregisterClassFinalizer(classId) { this.classFinalizers.delete(classId); } getContext(ctxPtr) { return this.contextRegistry.get(ctxPtr); } registerRuntime(rtPtr, runtime) { this.runtimeRegistry.set(rtPtr, runtime); } unregisterRuntime(rtPtr) { this.runtimeRegistry.delete(rtPtr); } registerModuleInitHandler(moduleName, handler) { this.moduleInitHandlers.set(moduleName, handler); } unregisterModuleInitHandler(moduleName) { this.moduleInitHandlers.delete(moduleName); } registerClassConstructor(classId, handler) { this.classConstructors.set(classId, handler); } registerClassFinalizer(classId, handler) { this.classFinalizers.set(classId, handler); } getRuntime(rtPtr) { return this.runtimeRegistry.get(rtPtr); } registerHostFunction(callback) { const id = this.nextFunctionId++; this.hostFunctions.set(id, callback); return id; } unregisterHostFunction(id) { this.hostFunctions.delete(id); } newFunction(ctx, callback, name) { if (!this.exports) { throw new Error("Exports not set on CallbackManager"); } const id = this.registerHostFunction(callback); const namePtr = this.memory.allocateString(ctx, name); const funcPtr = this.exports.HAKO_NewFunction(ctx, id, namePtr); this.memory.freeMemory(ctx, namePtr); return funcPtr; } setModuleLoader(loader) { this.moduleLoader = loader; } setModuleNormalizer(normalizer) { this.moduleNormalizer = normalizer; } setModuleResolver(resolver) { this.moduleResolver = resolver; } setInterruptHandler(handler) { this.interruptHandler = handler; } setRuntimeCallbacks(rtPtr, runtime) { this.registerRuntime(rtPtr, runtime); } setProfilerHandler(handler) { this.profilerHandler = handler; } handleHostFunctionCall(ctxPtr, thisPtr, argc, argvPtr, funcId) { const callback = this.hostFunctions.get(funcId); if (!callback) { return this.exports.HAKO_GetUndefined(); } const ctx = this.getContext(ctxPtr); if (!ctx) { return this.exports.HAKO_GetUndefined(); } return Scope.withScope((scope) => { const thisHandle = scope.manage(ctx.borrowValue(thisPtr)); const argHandles = new Array(argc); for (let i = 0;i < argc; i++) { const argPtr = this.exports.HAKO_ArgvGetJSValueConstPointer(argvPtr, i); const arg = ctx.duplicateValue(argPtr); argHandles[i] = scope.manage(arg); } try { const result = callback.apply(thisHandle, argHandles); if (result) { if (result instanceof VMValue) { const duplicatedHandle = this.exports.HAKO_DupValuePointer(ctxPtr, result.getHandle()); result.dispose(); return duplicatedHandle; } if (DisposableResult.is(result)) { if (result.error) { result.dispose(); try { result.unwrap(); } catch (e) { let __stack = []; try { const errorHandle = __using(__stack, ctx.newValue(e), 0); return this.exports.HAKO_Throw(ctxPtr, errorHandle.getHandle()); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } return this.exports.HAKO_GetUndefined(); } const unwrapped = result.unwrap(); result.dispose(); const duplicatedHandle = this.exports.HAKO_DupValuePointer(ctxPtr, unwrapped.getHandle()); unwrapped.dispose(); return duplicatedHandle; } return this.exports.HAKO_GetUndefined(); } return this.exports.HAKO_GetUndefined(); } catch (error) { try { let __stack2 = []; try { const errorHandle = __using(__stack2, ctx.newValue(error), 0); return this.exports.HAKO_Throw(ctxPtr, errorHandle.getHandle()); } catch (_catch2) { var _err2 = _catch2, _hasErr2 = 1; } finally { __callDispose(__stack2, _err2, _hasErr2); } } catch (_conversionError) { return this.exports.HAKO_GetUndefined(); } } }); } createModuleSourceString(ctxPtr, sourceCode) { const structSize = 8; const structPtr = this.exports.HAKO_Malloc(ctxPtr, structSize); if (structPtr === 0) { return 0; } const sourcePtr = this.memory.allocateString(ctxPtr, sourceCode); if (sourcePtr === 0) { this.exports.HAKO_Free(ctxPtr, structPtr); return 0; } const exports = this.exports; const view = new DataView(exports.memory.buffer); view.setUint32(structPtr, HAKO_MODULE_SOURCE_STRING, true); view.setUint32(structPtr + 4, sourcePtr, true); return structPtr; } createModuleSourcePrecompiled(ctxPtr, moduleDefPtr) { const structSize = 8; const structPtr = this.exports.HAKO_Malloc(ctxPtr, structSize); if (structPtr === 0) { return 0; } const exports = this.exports; const view = new DataView(exports.memory.buffer); view.setUint32(structPtr, HAKO_MODULE_SOURCE_PRECOMPILED, true); view.setUint32(structPtr + 4, moduleDefPtr, true); return structPtr; } createModuleSourceError(ctxPtr) { const structSize = 8; const structPtr = this.exports.HAKO_Malloc(ctxPtr, structSize); if (structPtr === 0) { return 0; } const exports = this.exports; const view = new DataView(exports.memory.buffer); view.setUint32(structPtr, HAKO_MODULE_SOURCE_ERROR, true); view.setUint32(structPtr + 4, 0, true); return structPtr; } handleModuleLoad(_rtPtr, ctxPtr, moduleNamePtr, attributesPtr) { if (!this.moduleLoader) { return this.createModuleSourceError(ctxPtr); } const ctx = this.getContext(ctxPtr); if (!ctx) { return this.createModuleSourceError(ctxPtr); } const moduleName = this.memory.readString(moduleNamePtr); let attributes; if (attributesPtr !== 0) { let __stack = []; try { const att = __using(__stack, ctx.borrowValue(attributesPtr), 0); const box = __using(__stack, att.toNativeValue(), 0); attributes = box.value; } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } try { const moduleResult = this.moduleLoader(moduleName, attributes); if (moduleResult === null) { return this.createModuleSourceError(ctxPtr); } switch (moduleResult.type) { case "source": return this.createModuleSourceString(ctxPtr, moduleResult.data); case "precompiled": return this.createModuleSourcePrecompiled(ctxPtr, moduleResult.data); default: return this.createModuleSourceError(ctxPtr); } } catch { return this.createModuleSourceError(ctxPtr); } } handleModuleResolve(_rtPtr, _ctxPtr, moduleNamePtr, currentModulePtr, _opaque) { if (!this.moduleResolver) { return 0; } if (moduleNamePtr === 0) { return 0; } const moduleName = this.memory.readString(moduleNamePtr); let currentModuleName; if (currentModulePtr !== 0) { currentModuleName = this.memory.readString(currentModulePtr); } const resolvedPath = this.moduleResolver(moduleName, currentModuleName); if (!resolvedPath) { return 0; } return this.memory.allocateString(_ctxPtr, resolvedPath); } handleModuleNormalize(_rtPtr, _ctxPtr, baseNamePtr, moduleNamePtr) { if (!this.moduleNormalizer) { return moduleNamePtr; } const baseName = this.memory.readString(baseNamePtr); const moduleName = this.memory.readString(moduleNamePtr); try { const normalizedName = this.moduleNormalizer(baseName, moduleName); return this.memory.allocateString(_ctxPtr, normalizedName); } catch { return moduleNamePtr; } } handleInterrupt(rtPtr, ctxPtr, opaque) { if (!this.interruptHandler) { return false; } try { const runtime = this.getRuntime(rtPtr); if (!runtime) { return true; } const ctx = this.getContext(ctxPtr); if (!ctx) { return true; } const shouldInterrupt = this.interruptHandler(runtime, ctx, opaque); return shouldInterrupt === true; } catch (_error) { return false; } } handleProfileFunctionStart(ctxPtr, eventPtr, opaque) { if (!this.profilerHandler) { return; } const ctx = this.getContext(ctxPtr); if (!ctx) { return; } try { const event = JSON.parse(this.memory.readString(eventPtr)); this.profilerHandler.onFunctionStart(ctx, event, opaque); } catch (_error) {} } handleProfileFunctionEnd(ctxPtr, eventPtr, opaque) { if (!this.profilerHandler) { return; } const ctx = this.getContext(ctxPtr); if (!ctx) { return; } try { const event = JSON.parse(this.memory.readString(eventPtr)); this.profilerHandler.onFunctionEnd(ctx, event, opaque); } catch (_error) {} } handleClassConstructor(ctxPtr, newTargetPtr, argc, argvPtr, classId) { const handler = this.classConstructors.get(classId); if (!handler) { return this.exports.HAKO_GetUndefined(); } const ctx = this.getContext(ctxPtr); if (!ctx) { return this.exports.HAKO_GetUndefined(); } return Scope.withScope((scope) => { const newTarget = scope.manage(ctx.borrowValue(newTargetPtr)); const args = []; for (let i = 0;i < argc; i++) { const argPtr = this.exports.HAKO_ArgvGetJSValueConstPointer(argvPtr, i); const arg = ctx.duplicateValue(argPtr); args.push(scope.manage(arg)); } try { const result = handler(ctx, newTarget, args, classId); if (result && result instanceof VMValue) { const duplicatedHandle = this.exports.HAKO_DupValuePointer(ctxPtr, result.getHandle()); result.dispose(); return duplicatedHandle; } return this.exports.HAKO_GetUndefined(); } catch (error) { try { let __stack = []; try { const errorHandle = __using(__stack, ctx.newValue(error), 0); return this.exports.HAKO_Throw(ctxPtr, errorHandle.getHandle()); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } catch (_conversionError) { return this.exports.HAKO_GetUndefined(); } } }); } handleClassFinalizer(rtPtr, opaque, classId) { const handler = this.classFinalizers.get(classId); if (!handler) { return; } const runtime = this.getRuntime(rtPtr); if (!runtime) { return; } try { const userData = opaque; handler(runtime, userData, classId); } catch (_error) {} } getModuleName(ctx, modulePtr) { const namePtr = ctx.container.exports.HAKO_GetModuleName(ctx.pointer, modulePtr); if (namePtr === 0) { return null; } try { return ctx.container.memory.readString(namePtr); } finally { ctx.container.memory.freeCString(ctx.pointer, namePtr); } } handleModuleInit(ctxPtr, modulePtr) { const ctx = this.getContext(ctxPtr); if (!ctx) { return -1; } const moduleName = this.getModuleName(ctx, modulePtr); if (!moduleName) { return -1; } const handler = this.moduleInitHandlers.get(moduleName); if (!handler) { return -1; } try { const initializer = new CModuleInitializer(ctx, modulePtr); const result = handler(initializer); return typeof result === "number" ? result : 0; } catch (_error) { return -1; } } } export { CallbackManager }; //# debugId=0C6669E21DA9958A64756E2164756E21 //# sourceMappingURL=callback.js.map