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