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