Socket
Socket
Sign inDemoInstall

quickjs-emscripten

Package Overview
Dependencies
0
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.1.2 to 0.2.0

9

dist/ffi.d.ts

@@ -40,4 +40,8 @@ /// <reference types="emscripten" />

*/
export declare type QTS_C_To_HostCallbackFuncPointer = Pointer<'C_To_HostCallbackFuncPointer'>;
export declare type QTS_C_To_HostCallbackFuncPointer = Pointer<'C_To_HostCallbackFunc'>;
/**
* Used internally for C-to-Javascript interrupt handlers.
*/
export declare type QTS_C_To_HostInterruptFuncPointer = Pointer<'C_To_HostInterruptFunc'>;
/**
* Low-level FFI bindings to QuickJS's Emscripten module.

@@ -57,2 +61,5 @@ * See instead [[QuickJSVm]], the public Javascript interface exposed by this

QTS_NewError: (ctx: JSContextPointer) => JSValuePointer;
QTS_SetInterruptCallback: (cb: QTS_C_To_HostInterruptFuncPointer) => void;
QTS_RuntimeEnableInterruptHandler: (rt: JSRuntimePointer) => void;
QTS_RuntimeDisableInterruptHandler: (rt: JSRuntimePointer) => void;
QTS_GetUndefined: () => JSValueConstPointer;

@@ -59,0 +66,0 @@ QTS_NewRuntime: () => JSRuntimePointer;

@@ -19,2 +19,5 @@ "use strict";

this.QTS_NewError = this.module.cwrap("QTS_NewError", "number", ["number"]);
this.QTS_SetInterruptCallback = this.module.cwrap("QTS_SetInterruptCallback", null, ["number"]);
this.QTS_RuntimeEnableInterruptHandler = this.module.cwrap("QTS_RuntimeEnableInterruptHandler", null, ["number"]);
this.QTS_RuntimeDisableInterruptHandler = this.module.cwrap("QTS_RuntimeDisableInterruptHandler", null, ["number"]);
this.QTS_GetUndefined = this.module.cwrap("QTS_GetUndefined", "number", []);

@@ -21,0 +24,0 @@ this.QTS_NewRuntime = this.module.cwrap("QTS_NewRuntime", "number", []);

@@ -5,3 +5,11 @@ import { QuickJSFFI, JSContextPointer, JSValuePointer, JSRuntimePointer, JSValueConstPointer } from './ffi';

declare type CToHostCallbackFunctionImplementation = (ctx: JSContextPointer, this_ptr: JSValueConstPointer, argc: number, argv: JSValueConstPointer, fn_data_ptr: JSValueConstPointer) => JSValuePointer;
declare type CToHostInterruptImplementation = (rt: JSRuntimePointer) => 0 | 1;
/**
* Determines if a VM's execution should be interrupted.
*
* Return `true` to interrupt JS execution.
* Return `false` or `undefined` to continue JS execution.
*/
export declare type ShouldInterruptHandler = (vm: QuickJSVm) => boolean | undefined;
/**
* A lifetime prevents access to a value after the lifetime has been

@@ -175,6 +183,9 @@ * [[dispose]]ed.

*
* *Note: this does not protect against infinite loops*.
* *Note*: to protect against infinite loops, provide an interrupt handler to
* [[setShouldInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
* create a time-based deadline.
*
* @returns The last statement's value. If the code threw, result `error` be
* a handle to the exception.
* @returns The last statement's value. If the code threw, result `error` will be
* a handle to the exception. If execution was interrupted, the error will
* have name `InternalError` and message `interrupted`.
*/

@@ -192,6 +203,20 @@ evalCode(code: string): VmCallResult<QuickJSHandle>;

unwrapResult(result: VmCallResult<QuickJSHandle>): QuickJSHandle;
private interruptHandler;
/**
* Set a callback which is regularly called by the QuickJS engine when it is
* executing code. This callback can be used to implement an execution
* timeout.
*
* The interrupt handler can be removed with [[removeShouldInterruptHandler]].
*/
setShouldInterruptHandler(cb: ShouldInterruptHandler): void;
/**
* Remove the interrupt handler, if any.
* See [[setShouldInterruptHandler]].
*/
removeShouldInterruptHandler(): void;
/**
* Dispose of this VM's underlying resources.
*
* @throws If Calling this method without disposing of all created handles
* @throws Calling this method without disposing of all created handles
* will result in an error.

@@ -206,2 +231,4 @@ */

cToHostCallbackFunction: CToHostCallbackFunctionImplementation;
/** @hidden */
cToHostInterrupt: CToHostInterruptImplementation;
private assertOwned;

@@ -254,2 +281,12 @@ private freeJSValue;

/**
* Options for [[QuickJS.evalCode]].
*/
export interface QuickJSEvalOptions {
/**
* Interrupt evaluation if `shouldInterrupt` returns `true`.
* See [[shouldInterruptAfterDeadline]].
*/
shouldInterrupt?: ShouldInterruptHandler;
}
/**
* QuickJS presents a Javascript interface to QuickJS, a Javascript interpreter that

@@ -269,2 +306,3 @@ * supports ES2019.

private vmMap;
private rtMap;
private module;

@@ -281,17 +319,30 @@ constructor();

* One-off evaluate code without needing to create a VM.
* The result is coerced to a native Javascript value using JSON
* serialization, so values unsupported by JSON will be dropped.
*
* 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
* [[QuickJSVm]] instance and use its [[QuickJSVm.evalCode]] method.
*
* *Note: this does not protect against infinite loops.*
* @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 Javascript value and throw.
* 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: string): unknown;
evalCode(code: string, options?: QuickJSEvalOptions): unknown;
private cToHostCallbackFunction;
private cToHostInterrupt;
}
/**
* Returns an interrupt handler that interrupts Javascript execution after a deadline time.
*
* @param deadline - Interrupt execution if it's still running after this time.
* Number values are compared against `Date.now()`
*/
export declare function shouldInterruptAfterDeadline(deadline: Date | number): ShouldInterruptHandler;
/**
* This is the top-level entrypoint for the quickjs-emscripten library.

@@ -298,0 +349,0 @@ * Get the root QuickJS API.

@@ -223,2 +223,13 @@ "use strict";

};
/** @hidden */
this.cToHostInterrupt = function (rt) {
if (rt !== _this.rt.value) {
throw new Error('QuickJSVm instance received C -> JS interrupt with mismatched rt');
}
var fn = _this.interruptHandler;
if (!fn) {
throw new Error('QuickJSVm had no interrupt handler');
}
return fn(_this) ? 1 : 0;
};
this.freeJSValue = function (ptr) {

@@ -436,6 +447,9 @@ _this.ffi.QTS_FreeValuePointer(_this.ctx.value, ptr);

*
* *Note: this does not protect against infinite loops*.
* *Note*: to protect against infinite loops, provide an interrupt handler to
* [[setShouldInterruptHandler]]. You can use [[shouldInterruptAfterDeadline]] to
* create a time-based deadline.
*
* @returns The last statement's value. If the code threw, result `error` be
* a handle to the exception.
* @returns The last statement's value. If the code threw, result `error` will be
* a handle to the exception. If execution was interrupted, the error will
* have name `InternalError` and message `interrupted`.
*/

@@ -496,5 +510,29 @@ QuickJSVm.prototype.evalCode = function (code) {

/**
* Set a callback which is regularly called by the QuickJS engine when it is
* executing code. This callback can be used to implement an execution
* timeout.
*
* The interrupt handler can be removed with [[removeShouldInterruptHandler]].
*/
QuickJSVm.prototype.setShouldInterruptHandler = function (cb) {
var prevInterruptHandler = this.interruptHandler;
this.interruptHandler = cb;
if (!prevInterruptHandler) {
this.ffi.QTS_RuntimeEnableInterruptHandler(this.rt.value);
}
};
/**
* Remove the interrupt handler, if any.
* See [[setShouldInterruptHandler]].
*/
QuickJSVm.prototype.removeShouldInterruptHandler = function () {
if (this.interruptHandler) {
this.ffi.QTS_RuntimeDisableInterruptHandler(this.rt.value);
this.interruptHandler = undefined;
}
};
/**
* Dispose of this VM's underlying resources.
*
* @throws If Calling this method without disposing of all created handles
* @throws Calling this method without disposing of all created handles
* will result in an error.

@@ -572,2 +610,3 @@ */

this.vmMap = new Map();
this.rtMap = new Map();
this.module = QuickJSModule;

@@ -589,2 +628,15 @@ // We need to send this into C-land

};
this.cToHostInterrupt = function (rt) {
try {
var vm = _this.rtMap.get(rt);
if (!vm) {
throw new Error("QuickJSVm(rt = " + rt + ") not found for C interrupt");
}
return vm.cToHostInterrupt(rt);
}
catch (error) {
console.error('[C to host interrupt: returning error]', error);
return 1;
}
};
if (!isReady) {

@@ -601,3 +653,3 @@ throw new Error('QuickJS WASM module not initialized. Either wait for `ready` or use getQuickJS()');

var intType = 'i';
var wasmTypes = [
var functionCallbackWasmTypes = [
pointerType,

@@ -610,4 +662,10 @@ pointerType,

];
var fp = this.module.addFunction(this.cToHostCallbackFunction, wasmTypes.join(''));
this.ffi.QTS_SetHostCallback(fp);
var funcCallbackFp = this.module.addFunction(this.cToHostCallbackFunction, functionCallbackWasmTypes.join(''));
this.ffi.QTS_SetHostCallback(funcCallbackFp);
var interruptCallbackWasmTypes = [
intType,
pointerType,
];
var interruptCallbackFp = this.module.addFunction(this.cToHostInterrupt, interruptCallbackWasmTypes.join(''));
this.ffi.QTS_SetInterruptCallback(interruptCallbackFp);
}

@@ -622,3 +680,6 @@ /**

var _this = this;
var rt = new Lifetime(this.ffi.QTS_NewRuntime(), function (rt_ptr) { return _this.ffi.QTS_FreeRuntime(rt_ptr); });
var rt = new Lifetime(this.ffi.QTS_NewRuntime(), function (rt_ptr) {
_this.rtMap.delete(rt_ptr);
_this.ffi.QTS_FreeRuntime(rt_ptr);
});
var ctx = new Lifetime(this.ffi.QTS_NewContext(rt.value), function (ctx_ptr) {

@@ -635,2 +696,3 @@ _this.vmMap.delete(ctx_ptr);

this.vmMap.set(ctx.value, vm);
this.rtMap.set(rt.value, vm);
return vm;

@@ -640,15 +702,24 @@ };

* One-off evaluate code without needing to create a VM.
* The result is coerced to a native Javascript value using JSON
* serialization, so values unsupported by JSON will be dropped.
*
* 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
* [[QuickJSVm]] instance and use its [[QuickJSVm.evalCode]] method.
*
* *Note: this does not protect against infinite loops.*
* @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 Javascript value and throw.
* converted into a native Javascript value and thrown.
*
* @throws if `options.shouldInterrupt` interrupted execution, will throw a Error
* with name `"InternalError"` and message `"interrupted"`.
*/
QuickJS.prototype.evalCode = function (code) {
QuickJS.prototype.evalCode = function (code, options) {
if (options === void 0) { options = {}; }
var vm = this.createVm();
if (options.shouldInterrupt) {
vm.setShouldInterruptHandler(options.shouldInterrupt);
}
var result = vm.evalCode(code);

@@ -669,2 +740,15 @@ if (result.error) {

exports.QuickJS = QuickJS;
/**
* Returns an interrupt handler that interrupts Javascript execution after a deadline time.
*
* @param deadline - Interrupt execution if it's still running after this time.
* Number values are compared against `Date.now()`
*/
function shouldInterruptAfterDeadline(deadline) {
var deadlineAsNumber = typeof deadline === 'number' ? deadline : deadline.getTime();
return function () {
return Date.now() > deadlineAsNumber;
};
}
exports.shouldInterruptAfterDeadline = shouldInterruptAfterDeadline;
var singleton = undefined;

@@ -671,0 +755,0 @@ /**

@@ -267,2 +267,44 @@ "use strict";

});
mocha_1.describe('interrupt handler', function () {
mocha_1.it('is called with the expected VM', function () {
var calls = 0;
var interruptHandler = function (interruptVm) {
assert_1.default.strictEqual(interruptVm, vm, 'ShouldInterruptHandler callback VM is the vm');
calls++;
return false;
};
vm.setShouldInterruptHandler(interruptHandler);
vm.unwrapResult(vm.evalCode('1 + 1')).dispose();
assert_1.default(calls > 0, 'interruptHandler called at least once');
});
mocha_1.it('interrupts infinite loop execution', function () {
var calls = 0;
var interruptHandler = function (interruptVm) {
if (calls > 10) {
return true;
}
calls++;
return false;
};
vm.setShouldInterruptHandler(interruptHandler);
var result = vm.evalCode('i = 0; while (1) { i++ }');
// Make sure we actually got to interrupt the loop.
var iHandle = vm.getProp(vm.global, 'i');
var i = vm.getNumber(iHandle);
iHandle.dispose();
assert_1.default(i > 10, 'incremented i');
assert_1.default(i > calls, 'incremented i more than called the interrupt handler');
// console.log('Javascript loop iterrations:', i, 'interrupt handler calls:', calls)
if (result.error) {
var errorJson = vm.dump(result.error);
result.error.dispose();
assert_1.default.equal(errorJson.name, 'InternalError');
assert_1.default.equal(errorJson.message, 'interrupted');
}
else {
result.value.dispose();
assert_1.default.fail('Should have returned an interrupt error');
}
});
});
return [2 /*return*/];

@@ -269,0 +311,0 @@ });

2

package.json
{
"name": "quickjs-emscripten",
"version": "0.1.2",
"version": "0.2.0",
"main": "dist/quickjs.js",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -49,11 +49,12 @@ # quickjs-emscripten

```typescript
import { getQuickJS } from 'quickjs-emscripten'
import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten'
getQuickJS.then(QuickJS => {
console.log(QuickJS.evalCode('1 + 1'))
const result = QuickJS.evalCode('1 + 1', {
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000)
})
console.log(result)
})
```
_Note: this will not protect you from infinite loops._
### Interfacing with the interpreter

@@ -111,3 +112,2 @@

- quickjs-emscripten only exposes a small subset of the QuickJS APIs. Add more QuickJS bindings!
- Expose the QuickJS interpreter execution hooks to protect against infinite loops.
- Expose tools for object and array iteration and creation.

@@ -114,0 +114,0 @@ - Stretch goals: class support, an event emitter bridge implementation

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc