English | 简体中文
🏖️ 使用JavaScript编写的更快更安全的JavaScript解释器,其可以用来:
- 执行沙盒(类似于Figma的插件沙盒,但是更易于使用);
- 小游戏/小程序的动态执行;
- 通过将JavaScript编译为opcode进行代码保护;
sablejs已经覆盖了约95%的 test262 es5-tests cases,其可以很安全可靠的用于你的生产之中。
sablejs分离出了编译器和解释器,因此我们移除了规范之中一些需要动态执行的相关API(详情见 限制)。简而言之, 你需要先使用sablejs cli编译你的JavaScript代码,然后才能使用解释器进行执行。
假设我们编写了 fib.js 中的代码:
function fib(n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
var start = Date.now();
console.log("[INFO] fib: " + fib(30));
console.log("[INFO] time consuming: " + (Date.now() - start) + "ms");> npm i sablejs -g
> sablejs -i fib.js -o output # 你将获得一个base64字符串的生成文件sablejs cli包含了如下的命令:
Usage: sablejs [options]
Options:
-v, --vers 输出当前版本号
-i, --input <path> 指定编译的文件路径
-o, --output <path> 指定输出的文件路径
-s, --slient 静默模式,不输出日志
-h, --help> npm install sablejs --save你也可以直接通过script标签进行运行时的引入:
<script src="https://cdn.jsdelivr.net/npm/sablejs@1.0.3/runtime.js"></script>const VM = require("sablejs/runtime")();
// import console.log function to vm call
const vm = new VM();
const vGlobal = vm.getGlobal();
const vConsole = vm.createObject();
const vLog = vm.createFunction("log", function () {
const temp = [];
for (let i = 0; i < arguments.length; i++) {
temp.push(vm.asString(arguments[i]));
}
console.log(...temp);
return vm.createUndefined();
});
vm.setProperty(vConsole, "log", vLog);
vm.setProperty(vGlobal, "console", vConsole);
(async () => {
const resp = await fetch("<output url>");
const data = await resp.text();
vm.run(data);
vm.destroy();
})();const VM = require("sablejs/runtime")();
const fs = require("fs");
// import console.log function to vm call
const vm = new VM();
const vGlobal = vm.getGlobal();
const vConsole = vm.createObject();
const vLog = vm.createFunction("log", function () {
const temp = [];
for (let i = 0; i < arguments.length; i++) {
temp.push(vm.asString(arguments[i]));
}
console.log(...temp);
return vm.createUndefined();
});
vm.setProperty(vConsole, "log", vLog);
vm.setProperty(vGlobal, "console", vConsole);
// please run: sablejs -i fib.js -o output
vm.run(fs.readFileSync("./output").toString());
vm.destroy();- VM.prototype.run(source)
- source: String
return:undefined
初始化VM并执行编译的代码。
const VM = require('sablejs/runtime')();
const vm = new VM();
// source should be base64 string via sablejs compiling
vm.run(`<compile source string>`);- VM.prototype.getGlobal()
return:Value
返回VM的 global 对象,其类似于浏览器环境中的 window 对象呢及Node.js环境的 global 对象。
const global = vm.getGlobal();- VM.prototype.createUndefined()
returnValue
创建 undefined 的包装类型。
const vUndefined = vm.createUndefined();- VM.prototype.createNull()
return:Value
创建 null 的包装类型。
const vNull = vm.createNull();- VM.prototype.createBoolean(bool)
- bool: Boolean
returnValue
创建 bool 的包装类型。
const vBoolean = vm.createBoolean(true);- VM.prototype.createNumber(num)
- num: Number
returnValue
创建 number 的包装类型。
const vNumber = vm.createNumber(1024);- VM.prototype.createString(str)
- str: String
returnValue
创建 string 的包装类型。
const vString = vm.createString('Hello World!');- VM.prototype.createObject()
returnValue
创建 object 的包装类型。
const vObject = vm.createObject();- VM.prototype.createArray(length)
- length: Number | undefined
returnValue
创建 array 的包装类型。
const vArray1 = vm.createArray();
// or
const vArray2 = vm.createArray(128);- VM.prototype.createFunction(name, func)
- name: String
- func: Function
returnValue
创建 function 的包装类型。其接收函数名 name 及具体函数实现 func 两个参数。但需要注意的是,其函数入参及 this 均为包装类型。
const vFuncntion = vm.createFunction("trim", function(str) {
// this is the undefined or new's instannce boxed type
// str maybe the string boxed type, we need to check it
});- VM.prototype.createError(message)
- message: String | undefined
returnValue
创建 error 的包装类型。
const vError1 = vm.createError();
// or
const vError2 = vm.createError("unknown error");- VM.prototype.createRegExp(pattern, flags)
- pattern: String
- flags: String | undefined
returnValue
创建 regexp 的包装类型。
const vRegExp = vm.createRegExp("\\w+", "ig");- VM.prototype.createDate()
returnValue
创建 date 的包装类型。
const vDate = vm.createDate();- VM.prototype.isUndefined(value)
- value: Value
returnBoolean
判断包装类型是否为 undefined。
const vUndefined = vm.createUndefined();
if(vm.isUndefined(vUndefined)) {
// ...
}- VM.prototype.isNull(value)
- value: Value
returnBoolean
判断包装类型是否为 null。
const vNull = vm.createNull();
if(vm.isNull(vNull)) {
// ...
}- VM.prototype.isBoolean(value)
- value: Value
returnBoolean
判断包装类型是否为 bool。
const vBoolean = vm.createBoolean(true);
if(vm.isBoolean(vBoolean)) {
// ...
}- VM.prototype.isNumber(value)
- value: Value
returnBoolean
判断包装类型是否为 number。
const vNumber = vm.createNumber(1024);
if(vm.isNumber(vNumber)) {
// ...
}- VM.prototype.isString(value)
- value: Value
returnBoolean
判断包装类型是否为 string。
const vString = vm.createString("Hello World!");
if(vm.isString(vString)) {
// ...
}- VM.prototype.isObject(value)
- value: Value
returnBoolean
判断包装类型是否为 object。
const vObject = vm.createObject();
const vArray = vm.createArray();
if(vm.isObject(vObject) && vm.isObject(vArray)) {
// ...
}- VM.prototype.isArray(value)
- value: Value
returnBoolean
判断包装类型是否为 array。
const vArray = vm.createArray();
if(vm.isArray(vArray)) {
// ...
}- VM.prototype.isFunction(value)
- value: Value
returnBoolean
判断包装类型是否为 function。
const vFunction = vm.createFunction("log", function(){});
if(vm.isFunction(vFunction)){
// ...
}- VM.prototype.isError(value)
- value: Value
returnBoolean
判断包装类型是否为 error。
const vError = vm.createError('unknown error');
if(vm.isError(vError)){
// ...
}- VM.prototype.isRegExp(value)
- value: Value
returnBoolean
判断包装类型是否为 regexp。
const vRegExp = vm.createRegExp("\\w+", "ig");
if(vm.isRegExp(vRegExp)){
// ...
}- VM.prototype.isDate(value)
- value: Value
returnBoolean
判断包装类型是否为 date。
const vDate = vm.createDate();
if(vm.isDate(vDate)){
// ...
}- VM.prototype.asUndefined(value)
- value: Value
returnundefined
将 undefined 包装类型转换为普通的 undefined 值。
const vUndefined = vm.createUndefined();
vm.asUndefined(vUndefined) === undefined;- VM.prototype.asNull(value)
- value: Value
returnnull
将 null 包装类型转换为普通的 null 值。
const vNull = vm.createNull();
vm.asNull(vNull) === null;- VM.prototype.asBoolean(value)
- value: Value
returnBoolean
将 bool 包装类型转换为普通的 bool 值。
const vBoolean = vm.createBoolean(true);
const boolean = vm.asBoolean(vBoolean);
if(boolean === true) {
// ...
}- VM.prototype.asNumber(value)
- value: Value
returnNumber
将 number 包装类型转换为普通的 number 值。
const vNumber = vm.createNumber(1024);
const number = vm.asNumber(vNumber);
if(number === 1024) {
// ...
}- VM.prototype.asString(value)
- value: Value
returnString
将 string 包装类型转换为普通的 string 值。
const vString = vm.createString('Hello World!');
const string = vm.asString(vString);
if(string === 'Hello World!') {
// ...
}- VM.prototype.asObject(value)
- value: Value
returnObject
将 object 包装类型转换为内部的 object 类型。
const vObject = vm.createFunction("asObject", function(){});
const object = vm.asObject(vObject);
if(object.type === 12) {
// ...
}- VM.prototype.instanceof(lval, rval)
- lval: Value
- rval: Value
returnBoolean
等价于 instanceof 关键字用法。
const global = vm.getGlobal();
const vDateFunc = vm.getProperty(global, "Date");
const vDate = vm.createDate();
if(vm.instanceof(vDate, vDateFunc)) {
// ...
}- VM.prototype.typeof(value)
- value: Value
returnString
等价于 typeof 关键字用法。
const vString = vm.createString('Hello World!');
if(vm.typeof(vString) === "string") {
// ...
}- VM.prototype.getProperty(value, name)
- value: Value
- name: String
returnValue
获取一个对象的对应属性,其返回值是一个包装类型。
const global = vm.getGlobal();
const vPrint = vm.getProperty(global, "print");
if(vm.isFunction(vPrint)) {
// ...
}- VM.prototype.setProperty(value, name, property)
- value: Value
- name: String
- property: Value
returnValue
设置一个对象的对应属性,其返回值是一个包装那类型。
const global = vm.getGlobal();
const console = vm.createObject();
const log = vm.createFunction("log", function() {
// console.log impl
});
vm.setProperty(console, "log", log);
vm.setProperty(global, "console", console);- VM.prototype.deleteProperty(value, name)
- value: Value
- name: String
returnBoolean
删除对象的对应属性。
const global = vm.getGlobal();
vm.deleteProperty(global, "print");
const vPrint = vm.getProperty(global, "print");
if(vm.isUndefined(vPrint)) {
// ...
} - VM.prototype.defineProperty(value, name, desc)
- value: Value
- name: String
- desc: Object
returnValue
等价于 Object.defineProperty 函数的用法。
const vObject = vm.createObject();
vm.defineProperty(vObject, "name", {
value: vm.createString("sablejs"),
});
const getter = vm.createFunction("getter", functionn() {
return vm.createNumber("101");
});
const setter = vm.createFunction("setter", function(age) {
vm.setProperty(this, "__age__", age);
});
vm.defineProperty(vObject, "age", {
enumerable: false,
get: getter,
set: setter,
});- VM.prototype.getPrototype(value)
- value: Value
returnValue
获取一个对象的原型。
const global = vm.getGlobal();
const vStringFunc = vm.getProperty(global, "String");
if(!vm.isUndefined(vStringFunc)) {
const vTrimStart = vm.createFunction("trimStart", function() {
const str = vm.asString(this);
return vm.createString(str);
});
const vStringFuncProto = vm.getPrototype(vStringFunc);
vm.setProperty(vStringFuncProto, "trimStart", vTrimStart);
}- VM.prototype.setPrototype(value, prototype)
- value: Value
- prototype: Value
returnValue
设置一个对象的原型。
const vA = vm.createFunction("A", function() {});
const vObject = vm.createObject();
vm.setProperty(vObject, 'name', vm.createString('Hello World!'));
vm.setPrototype(vA, vObject);- VM.prototype.throw(value)
- value: Value
returnundefined
等价于 throw 关键字。
const vError = vm.createError('unknown error');
vm.throw(vError);- VM.prototype.new(func[, arg1, arg2, arg3...])
- func: Value
- arg: Value
returnValue
等价于 new 关键字。
const vA = vm.createFunction('A', function(name) {
vm.setProperty(this, 'name', name);
});
vm.new(vA, vm.createString("A"));- VM.prototype.call(func, thisPtr[, arg1, arg2, arg3...])
- func: Value
- thisPtr: Value | undefined
- arg: Value
returnValue
等价于 Function.prototype.call 函数。
const vLog = vm.createFunction('log', function() {
const temp = [];
for(let i = 0; i < arguments.length; i++){
temp.push(vm.asString(arguments[i]));
}
console.log(...temp); // '1', 1, false
});
vm.call(
vLog,
vm.createUndefined(),
vm.createString('1'),
vm.createNumber(1),
vm.createBoolean(false)
);- VM.prototype.destroy
returnundefined
销毁VM实例,释放内存。
vm.destroy();sablejs也许是使用JavaScript编写的JavaScript引擎中最快的实现 (测试用例使用 v8 benchmark suites):
性能测试环境:
- Node.js v12.19.0
- Golang 1.15.6
- GCC 5.4.0 -O3
- 2.4 GHz Intel Core i9
- MacOS Mojave 10.14.6 (18G6032)
| sablejs | sval | eval5 | quickjs-wasm | goja | |
|---|---|---|---|---|---|
| Language | JavaScript | JavaScript | JavaScript | C + WebAssembly | Golang |
| Richards | 110 | 24.9 | 24.7 | 376 | 208 |
| Crypto | 114 | 24.6 | 20.2 | 400 | 104 |
| RayTrace | 258 | 92.2 | 98.5 | 471 | 294 |
| NavierStokes | 183 | 35.9 | 49.8 | 665 | 191 |
| DeltaBlue | 120 | 35.3 | 29.5 | 402 | 276 |
| Total score | 148 | 37.3 | 37.3 | 452 | 202 |
| Baseline | 1 | ▼ 2.96 | ▼ 2.96 | ▲ 2.05 | ▲ 0.36 |
| File Size(KB) | 216 | 152 | 134 | 434 | - |
| Gzip Size(KB) | 29 | 40 | 34 | 245 | - |
- eval及Function的动态执行部分在sablejs是被禁止的,但是如果你参数仅传递string/number/null及undefined的话,其是可以被正常执行的(因为解释器中不包含编译器,因此仅支持字面量的分析和传入)。
eval("print('Hello World!')"); // it's ok
eval("var " + "a=1"); // it's ok
var str = "Hello World!";
eval("print('" + str + "')"); // throw SyntaxError
Function("a", "b", "return a+b"); // it's ok
new Function("a", "b", "return a+b"); // it's ok
var str = "return a+b";
Function("a", "b", str); // throw SyntaxError
new Function("a", "b", str); // throw SyntaxError- 浏览器环境中需要支持
btoa、unescape、decodeURIComponent等原生函数,若果你想在IE9及以下使用,请自行添加相关的shims。
sablejs JavaScript Engine
Copyright (c) 2020-2021 ErosZhao
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Non-profit projects of individuals or organizations and commercial projects with commercial authorization of the author.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
