UNPKG

hakojs

Version:

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

459 lines (454 loc) 15.9 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/vm/cmodule.ts import { HakoError } from "../etc/errors"; import { Scope } from "../mem/lifetime"; import { VMValue } from "./value"; class CModuleBuilder { vmContext; modulePtr; exports = []; createdClasses = []; disposed = false; initializerHandler; constructor(context, name, initHandler) { this.vmContext = context; this.initializerHandler = initHandler; this.modulePtr = Scope.withScope((scope) => { const memory = context.container.memory; const hakoExports = context.container.exports; const moduleName = memory.allocateString(context.pointer, name); scope.add(() => memory.freeMemory(context.pointer, moduleName)); const modulePtr = hakoExports.HAKO_NewCModule(context.pointer, moduleName); if (modulePtr === 0) { throw new HakoError(`Failed to create C module: ${name}`); } return modulePtr; }); this.vmContext.container.callbacks.registerModuleInitHandler(name, (initializer) => { initializer._setParentBuilder(this); this.initializerHandler(initializer); return 0; }); } setPrivateValue(value) { let __stack = []; try { this.checkDisposed(); const vmv = __using(__stack, this.vmContext.newValue(value), 0); this.vmContext.container.exports.HAKO_SetModulePrivateValue(this.vmContext.pointer, this.modulePtr, vmv.getHandle()); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } getPrivateValue() { this.checkDisposed(); return new VMValue(this.vmContext, this.context.container.exports.HAKO_GetModulePrivateValue(this.vmContext.pointer, this.modulePtr), "owned"); } get context() { return this.vmContext; } get pointer() { return this.modulePtr; } get exportNames() { return [...this.exports]; } get name() { return Scope.withScope((scope) => { const exports = this.vmContext.container.exports; const namePtr = exports.HAKO_GetModuleName(this.vmContext.pointer, this.modulePtr); if (namePtr === 0) { throw new HakoError("Module name not found"); } scope.add(() => this.vmContext.container.memory.freeCString(this.vmContext.pointer, namePtr)); return this.vmContext.container.memory.readString(namePtr); }); } addExport(exportName) { this.checkDisposed(); Scope.withScope((scope) => { const memory = this.vmContext.container.memory; const exports = this.vmContext.container.exports; const exportNamePtr = memory.allocateString(this.vmContext.pointer, exportName); scope.add(() => memory.freeMemory(this.vmContext.pointer, exportNamePtr)); const result = exports.HAKO_AddModuleExport(this.vmContext.pointer, this.modulePtr, exportNamePtr); if (result !== 0) { throw new HakoError(`Failed to add export: ${exportName}`); } this.exports.push(exportName); }); return this; } addExports(exportNames) { for (const exportName of exportNames) { this.addExport(exportName); } return this; } _registerClass(classObj) { this.checkDisposed(); this.createdClasses.push(classObj); } _unregisterClass(classObj) { const index = this.createdClasses.indexOf(classObj); if (index !== -1) { this.createdClasses.splice(index, 1); } } checkDisposed() { if (this.disposed) { throw new HakoError("CModuleBuilder has been disposed"); } } [Symbol.dispose]() { if (this.disposed) return; for (const classObj of this.createdClasses) { try { classObj[Symbol.dispose](); } catch (error) { console.error(`Error disposing CModuleClass ${classObj.id}:`, error); } } this.createdClasses.length = 0; this.vmContext.container.callbacks.unregisterModuleInitHandler(this.name); if (this.modulePtr !== 0) { this.modulePtr = 0; } this.disposed = true; } } class CModuleInitializer { vmContext; modulePtr; parentBuilder = null; createdClasses = []; disposed = false; constructor(context, modulePtr) { this.vmContext = context; this.modulePtr = modulePtr; } _setParentBuilder(builder) { this.parentBuilder = builder; } get context() { return this.vmContext; } get pointer() { return this.modulePtr; } setPrivateValue(value) { let __stack = []; try { this.checkDisposed(); const vmv = __using(__stack, this.vmContext.newValue(value), 0); this.vmContext.container.exports.HAKO_SetModulePrivateValue(this.vmContext.pointer, this.modulePtr, vmv.getHandle()); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } getPrivateValue() { this.checkDisposed(); return new VMValue(this.vmContext, this.context.container.exports.HAKO_GetModulePrivateValue(this.vmContext.pointer, this.modulePtr), "owned"); } get name() { return Scope.withScope((scope) => { const exports = this.vmContext.container.exports; const namePtr = exports.HAKO_GetModuleName(this.vmContext.pointer, this.modulePtr); if (namePtr === 0) { throw new HakoError("Module name not found"); } scope.add(() => this.vmContext.container.memory.freeCString(this.vmContext.pointer, namePtr)); return this.vmContext.container.memory.readString(namePtr); }); } setExport(exportName, value) { this.checkDisposed(); if (value instanceof VMValue) { Scope.withScope((scope) => { scope.add(() => value.dispose()); const memory = this.vmContext.container.memory; const exports = this.vmContext.container.exports; const exportNamePtr = memory.allocateString(this.vmContext.pointer, exportName); scope.add(() => memory.freeMemory(this.vmContext.pointer, exportNamePtr)); const result = exports.HAKO_SetModuleExport(this.vmContext.pointer, this.modulePtr, exportNamePtr, value.getHandle()); if (result !== 0) { throw new HakoError(`Failed to set export: ${exportName}`); } }); } else { this.setExport(exportName, this.vmContext.newValue(value)); } } setExports(exports) { for (const [exportName, value] of Object.entries(exports)) { this.setExport(exportName, value); } } setFunction(exportName, fn) { let __stack = []; try { const func = __using(__stack, this.vmContext.newFunction(exportName, fn), 0); this.setExport(exportName, func); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } setClass(name, constructorFn, options = {}) { this.checkDisposed(); const classObj = new CModuleClass(this.vmContext, name, constructorFn, options); this.createdClasses.push(classObj); if (this.parentBuilder) { this.parentBuilder._registerClass(classObj); } this.setExport(name, classObj.ctor); } checkDisposed() { if (this.disposed) { throw new HakoError("CModuleInitializer has been disposed"); } } [Symbol.dispose]() { if (this.disposed) return; this.createdClasses.length = 0; this.disposed = true; } } class CModuleClass { context; classId; proto; ctorFunction; disposed = false; constructor(context, name, constructorFn, options = {}) { this.context = context; this.classId = Scope.withScope((scope) => { const classIdPtr = context.container.memory.allocatePointerArray(context.pointer, 1); scope.add(() => context.container.memory.freeMemory(context.pointer, classIdPtr)); context.container.memory.writePointerToArray(classIdPtr, 0, 0); const classId = context.container.exports.HAKO_NewClassID(classIdPtr); if (classId === 0) { throw new HakoError(`Failed to allocate class ID for: ${name}`); } return classId; }); try { this.setupClass(name, options); const internalConstructor = (_ctx, newTarget, args, _classId) => { let __stack = []; try { const protoProperty = __using(__stack, newTarget.getProperty("prototype"), 0); if (!protoProperty || context.getLastError(protoProperty)) { throw new HakoError("Failed to get prototype from new_target"); } let instance; try { instance = this.createInstanceWithPrototype(protoProperty); constructorFn(instance, args, newTarget); return instance; } catch (error) { if (instance) { instance.dispose(); } throw error; } } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } }; context.container.callbacks.registerClassConstructor(this.classId, internalConstructor); if (options.finalizer) { context.container.callbacks.registerClassFinalizer(this.classId, options.finalizer); } } catch (error) { this.cleanup(); throw error; } } setupClass(name, options) { const protoPtr = this.context.container.exports.HAKO_NewObject(this.context.pointer); const protoError = this.context.getLastError(protoPtr); if (protoError) { throw new HakoError(`Prototype creation exception for ${name}`, protoError); } this.proto = new VMValue(this.context, protoPtr, "owned"); if (options.methods) { for (const [methodName, methodFn] of Object.entries(options.methods)) { let __stack = []; try { const method = __using(__stack, this.context.newFunction(methodName, methodFn), 0); const methodError = this.context.getLastError(method); if (methodError) { throw new HakoError(`Failed to create method ${methodName}`, methodError); } this.proto.setProperty(methodName, method); } catch (_catch) { var _err = _catch, _hasErr = 1; } finally { __callDispose(__stack, _err, _hasErr); } } } this.ctorFunction = Scope.withScope((scope) => { const namePtr = this.context.container.memory.allocateString(this.context.pointer, name); scope.add(() => this.context.container.memory.freeMemory(this.context.pointer, namePtr)); const constructorPtr = this.context.container.exports.HAKO_NewClass(this.context.pointer, this.classId, namePtr, options.finalizer ? 1 : 0); const ctorError = this.context.getLastError(constructorPtr); if (ctorError) { throw new HakoError(`Class constructor exception for ${name}`, ctorError); } return new VMValue(this.context, constructorPtr, "owned"); }); if (options.staticMethods) { for (const [methodName, methodFn] of Object.entries(options.staticMethods)) { let __stack2 = []; try { const method = __using(__stack2, this.context.newFunction(methodName, methodFn), 0); const methodError = this.context.getLastError(method); if (methodError) { throw new HakoError(`Failed to create static method ${methodName}`, methodError); } this.ctorFunction.setProperty(methodName, method); } catch (_catch2) { var _err2 = _catch2, _hasErr2 = 1; } finally { __callDispose(__stack2, _err2, _hasErr2); } } } this.context.container.exports.HAKO_SetConstructor(this.context.pointer, this.ctorFunction.getHandle(), this.proto.getHandle()); this.context.container.exports.HAKO_SetClassProto(this.context.pointer, this.classId, this.proto.getHandle()); } cleanup() { if (this.ctorFunction) { this.ctorFunction.dispose(); this.ctorFunction = undefined; } if (this.proto) { this.proto.dispose(); this.proto = undefined; } } checkDisposed() { if (this.disposed) { throw new HakoError("CModuleClass has been disposed"); } } get ctor() { this.checkDisposed(); if (!this.ctorFunction) { throw new HakoError("Constructor not initialized"); } return this.ctorFunction; } get id() { return this.classId; } get prototype() { this.checkDisposed(); if (!this.proto) { throw new HakoError("Prototype not initialized"); } return this.proto; } createInstance(opaque) { this.checkDisposed(); const instancePtr = this.context.container.exports.HAKO_NewObjectClass(this.context.pointer, this.classId); const error = this.context.getLastError(instancePtr); if (error) { throw new HakoError(`Instance creation exception`, error); } const instance = new VMValue(this.context, instancePtr, "owned"); if (opaque !== undefined) { this.context.container.exports.HAKO_SetOpaque(instance.getHandle(), opaque); } return instance; } createInstanceWithPrototype(customProto, opaque) { this.checkDisposed(); const instancePtr = this.context.container.exports.HAKO_NewObjectProtoClass(this.context.pointer, customProto.getHandle(), this.classId); const error = this.context.getLastError(instancePtr); if (error) { throw new HakoError(`Instance creation with prototype exception`, error); } const instance = new VMValue(this.context, instancePtr, "owned"); if (opaque !== undefined) { this.context.container.exports.HAKO_SetOpaque(instance.getHandle(), opaque); } return instance; } getOpaque(instance) { this.checkDisposed(); return this.context.container.exports.HAKO_GetOpaque(this.context.pointer, instance.getHandle(), this.classId); } isInstance(value) { this.checkDisposed(); const classId = this.context.container.exports.HAKO_GetClassID(value.getHandle()); return classId === this.classId; } [Symbol.dispose]() { if (this.disposed) return; try { this.context.container.callbacks.unregisterClassConstructor?.(this.classId); this.context.container.callbacks.unregisterClassFinalizer?.(this.classId); this.cleanup(); this.disposed = true; } catch (error) { console.error(`Error during CModuleClass ${this.classId} disposal:`, error); this.disposed = true; } } } export { CModuleInitializer, CModuleClass, CModuleBuilder }; //# debugId=3BF6DAD037D2CE7864756E2164756E21 //# sourceMappingURL=cmodule.js.map