123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.QuickJSWASMModule = exports.applyModuleEvalRuntimeOptions = exports.applyBaseRuntimeOptions = exports.QuickJSModuleCallbacks = void 0;
- const debug_1 = require("./debug");
- const errors_1 = require("./errors");
- const lifetime_1 = require("./lifetime");
- const runtime_1 = require("./runtime");
- const types_1 = require("./types");
- class QuickJSEmscriptenModuleCallbacks {
- constructor(args) {
- this.callFunction = args.callFunction;
- this.shouldInterrupt = args.shouldInterrupt;
- this.loadModuleSource = args.loadModuleSource;
- this.normalizeModule = args.normalizeModule;
- }
- }
- /**
- * We use static functions per module to dispatch runtime or context calls from
- * C to the host. This class manages the indirection from a specific runtime or
- * context pointer to the appropriate callback handler.
- *
- * @private
- */
- class QuickJSModuleCallbacks {
- constructor(module) {
- this.contextCallbacks = new Map();
- this.runtimeCallbacks = new Map();
- this.suspendedCount = 0;
- this.cToHostCallbacks = new QuickJSEmscriptenModuleCallbacks({
- callFunction: (asyncify, ctx, this_ptr, argc, argv, fn_id) => this.handleAsyncify(asyncify, () => {
- try {
- const vm = this.contextCallbacks.get(ctx);
- if (!vm) {
- throw new Error(`QuickJSContext(ctx = ${ctx}) not found for C function call "${fn_id}"`);
- }
- return vm.callFunction(ctx, this_ptr, argc, argv, fn_id);
- }
- catch (error) {
- console.error("[C to host error: returning null]", error);
- return 0;
- }
- }),
- shouldInterrupt: (asyncify, rt) => this.handleAsyncify(asyncify, () => {
- try {
- const vm = this.runtimeCallbacks.get(rt);
- if (!vm) {
- throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C interrupt`);
- }
- return vm.shouldInterrupt(rt);
- }
- catch (error) {
- console.error("[C to host interrupt: returning error]", error);
- return 1;
- }
- }),
- loadModuleSource: (asyncify, rt, ctx, moduleName) => this.handleAsyncify(asyncify, () => {
- try {
- const runtimeCallbacks = this.runtimeCallbacks.get(rt);
- if (!runtimeCallbacks) {
- throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C module loader`);
- }
- const loadModule = runtimeCallbacks.loadModuleSource;
- if (!loadModule) {
- throw new Error(`QuickJSRuntime(rt = ${rt}) does not support module loading`);
- }
- return loadModule(rt, ctx, moduleName);
- }
- catch (error) {
- console.error("[C to host module loader error: returning null]", error);
- return 0;
- }
- }),
- normalizeModule: (asyncify, rt, ctx, moduleBaseName, moduleName) => this.handleAsyncify(asyncify, () => {
- try {
- const runtimeCallbacks = this.runtimeCallbacks.get(rt);
- if (!runtimeCallbacks) {
- throw new Error(`QuickJSRuntime(rt = ${rt}) not found for C module loader`);
- }
- const normalizeModule = runtimeCallbacks.normalizeModule;
- if (!normalizeModule) {
- throw new Error(`QuickJSRuntime(rt = ${rt}) does not support module loading`);
- }
- return normalizeModule(rt, ctx, moduleBaseName, moduleName);
- }
- catch (error) {
- console.error("[C to host module loader error: returning null]", error);
- return 0;
- }
- }),
- });
- this.module = module;
- this.module.callbacks = this.cToHostCallbacks;
- }
- setRuntimeCallbacks(rt, callbacks) {
- this.runtimeCallbacks.set(rt, callbacks);
- }
- deleteRuntime(rt) {
- this.runtimeCallbacks.delete(rt);
- }
- setContextCallbacks(ctx, callbacks) {
- this.contextCallbacks.set(ctx, callbacks);
- }
- deleteContext(ctx) {
- this.contextCallbacks.delete(ctx);
- }
- handleAsyncify(asyncify, fn) {
- if (asyncify) {
- // We must always call asyncify.handleSync around our function.
- // This allows asyncify to resume suspended execution on the second call.
- // Asyncify internally can detect sync behavior, and avoid suspending.
- return asyncify.handleSleep((done) => {
- try {
- const result = fn();
- if (!(result instanceof Promise)) {
- (0, debug_1.debugLog)("asyncify.handleSleep: not suspending:", result);
- done(result);
- return;
- }
- // Is promise, we intend to suspend.
- if (this.suspended) {
- throw new errors_1.QuickJSAsyncifyError(`Already suspended at: ${this.suspended.stack}\nAttempted to suspend at:`);
- }
- else {
- this.suspended = new errors_1.QuickJSAsyncifySuspended(`(${this.suspendedCount++})`);
- (0, debug_1.debugLog)("asyncify.handleSleep: suspending:", this.suspended);
- }
- result.then((resolvedResult) => {
- this.suspended = undefined;
- (0, debug_1.debugLog)("asyncify.handleSleep: resolved:", resolvedResult);
- done(resolvedResult);
- }, (error) => {
- (0, debug_1.debugLog)("asyncify.handleSleep: rejected:", error);
- console.error("QuickJS: cannot handle error in suspended function", error);
- this.suspended = undefined;
- });
- }
- catch (error) {
- (0, debug_1.debugLog)("asyncify.handleSleep: error:", error);
- this.suspended = undefined;
- throw error;
- }
- });
- }
- // No asyncify - we should never return a promise.
- const value = fn();
- if (value instanceof Promise) {
- throw new Error("Promise return value not supported in non-asyncify context.");
- }
- return value;
- }
- }
- exports.QuickJSModuleCallbacks = QuickJSModuleCallbacks;
- /**
- * Process RuntimeOptions and apply them to a QuickJSRuntime.
- * @private
- */
- function applyBaseRuntimeOptions(runtime, options) {
- if (options.interruptHandler) {
- runtime.setInterruptHandler(options.interruptHandler);
- }
- if (options.maxStackSizeBytes !== undefined) {
- runtime.setMaxStackSize(options.maxStackSizeBytes);
- }
- if (options.memoryLimitBytes !== undefined) {
- runtime.setMemoryLimit(options.memoryLimitBytes);
- }
- }
- exports.applyBaseRuntimeOptions = applyBaseRuntimeOptions;
- /**
- * Process ModuleEvalOptions and apply them to a QuickJSRuntime.
- * @private
- */
- function applyModuleEvalRuntimeOptions(runtime, options) {
- if (options.moduleLoader) {
- runtime.setModuleLoader(options.moduleLoader);
- }
- if (options.shouldInterrupt) {
- runtime.setInterruptHandler(options.shouldInterrupt);
- }
- if (options.memoryLimitBytes !== undefined) {
- runtime.setMemoryLimit(options.memoryLimitBytes);
- }
- if (options.maxStackSizeBytes !== undefined) {
- runtime.setMaxStackSize(options.maxStackSizeBytes);
- }
- }
- exports.applyModuleEvalRuntimeOptions = applyModuleEvalRuntimeOptions;
- /**
- * This class presents a Javascript interface to QuickJS, a Javascript interpreter
- * that supports EcmaScript 2020 (ES2020).
- *
- * It wraps a single WebAssembly module containing the QuickJS library and
- * associated helper C code. WebAssembly modules are completely isolated from
- * each other by the host's WebAssembly runtime. Separate WebAssembly modules
- * have the most isolation guarantees possible with this library.
- *
- * The simplest way to start running code is {@link evalCode}. This shortcut
- * method will evaluate Javascript safely and return the result as a native
- * Javascript value.
- *
- * For more control over the execution environment, or to interact with values
- * inside QuickJS, create a context with {@link newContext} or a runtime with
- * {@link newRuntime}.
- */
- class QuickJSWASMModule {
- /** @private */
- constructor(module, ffi) {
- this.module = module;
- this.ffi = ffi;
- this.callbacks = new QuickJSModuleCallbacks(module);
- }
- /**
- * Create a runtime.
- * Use the runtime to set limits on CPU and memory usage and configure module
- * loading for one or more [[QuickJSContext]]s inside the runtime.
- */
- newRuntime(options = {}) {
- const rt = new lifetime_1.Lifetime(this.ffi.QTS_NewRuntime(), undefined, (rt_ptr) => {
- this.callbacks.deleteRuntime(rt_ptr);
- this.ffi.QTS_FreeRuntime(rt_ptr);
- });
- const runtime = new runtime_1.QuickJSRuntime({
- module: this.module,
- callbacks: this.callbacks,
- ffi: this.ffi,
- rt,
- });
- applyBaseRuntimeOptions(runtime, options);
- if (options.moduleLoader) {
- runtime.setModuleLoader(options.moduleLoader);
- }
- return runtime;
- }
- /**
- * A simplified API to create a new [[QuickJSRuntime]] and a
- * [[QuickJSContext]] inside that runtime at the same time. The runtime will
- * be disposed when the context is disposed.
- */
- newContext(options = {}) {
- const runtime = this.newRuntime();
- const context = runtime.newContext({
- ...options,
- ownedLifetimes: (0, types_1.concat)(runtime, options.ownedLifetimes),
- });
- runtime.context = context;
- return context;
- }
- /**
- * One-off evaluate code without needing to create a [[QuickJSRuntime]] or
- * [[QuickJSContext]] explicitly.
- *
- * To protect against infinite loops, use the `shouldInterrupt` option. The
- * [[shouldInterruptAfterDeadline]] function will create a time-based deadline.
- *
- * If you need more control over how the code executes, create a
- * [[QuickJSRuntime]] (with [[newRuntime]]) or a [[QuickJSContext]] (with
- * [[newContext]] or [[QuickJSRuntime.newContext]]), and use its
- * [[QuickJSContext.evalCode]] method.
- *
- * Asynchronous callbacks may not run during the first call to `evalCode`. If
- * you need to work with async code inside QuickJS, create a runtime and use
- * [[QuickJSRuntime.executePendingJobs]].
- *
- * @returns The result is coerced to a native Javascript value using JSON
- * serialization, so properties and values unsupported by JSON will be dropped.
- *
- * @throws If `code` throws during evaluation, the exception will be
- * converted into a native Javascript value and thrown.
- *
- * @throws if `options.shouldInterrupt` interrupted execution, will throw a Error
- * with name `"InternalError"` and message `"interrupted"`.
- */
- evalCode(code, options = {}) {
- return lifetime_1.Scope.withScope((scope) => {
- const vm = scope.manage(this.newContext());
- applyModuleEvalRuntimeOptions(vm.runtime, options);
- const result = vm.evalCode(code, "eval.js");
- if (options.memoryLimitBytes !== undefined) {
- // Remove memory limit so we can dump the result without exceeding it.
- vm.runtime.setMemoryLimit(-1);
- }
- if (result.error) {
- const error = vm.dump(scope.manage(result.error));
- throw error;
- }
- const value = vm.dump(scope.manage(result.value));
- return value;
- });
- }
- /**
- * Get a low-level interface to the QuickJS functions in this WebAssembly
- * module.
- * @experimental
- * @unstable No warranty is provided with this API. It could change at any time.
- * @private
- */
- getFFI() {
- return this.ffi;
- }
- }
- exports.QuickJSWASMModule = QuickJSWASMModule;
- //# sourceMappingURL=module.js.map
|