@sudoo/marked
Version:
JavaScript & TypeScript code runner in JavaScript, safe with marked territory, asynchronous
403 lines (402 loc) • 15.3 kB
JavaScript
"use strict";
/**
* @author WMXPY
* @namespace Marked
* @description Sandbox
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sandbox = void 0;
const controller_1 = require("../debug/break-point/controller");
const node_1 = require("../debug/node");
const error_code_1 = require("../declare/error-code");
const evaluate_1 = require("../declare/evaluate");
const sandbox_1 = require("../declare/sandbox");
const script_location_1 = require("../declare/script-location");
const variable_1 = require("../declare/variable");
const break_point_1 = require("../operation/break-point");
const native_to_sand_1 = require("../parse/native-to-sand");
const parse_script_1 = require("../parse/script/parse-script");
const assert_1 = require("../util/error/assert");
const error_1 = require("../util/error/error");
const options_1 = require("../util/options");
const flag_1 = require("../variable/flag");
const scope_1 = require("../variable/scope");
const trace_1 = require("../variable/trace/trace");
const declare_1 = require("./declare");
const evaluate_2 = require("./evaluate");
const executer_1 = require("./executer");
class Sandbox {
static fromScratch(language = sandbox_1.defaultSandboxLanguage) {
return new Sandbox(language);
}
static fromAllEvaluators(language = sandbox_1.defaultSandboxLanguage) {
const sandbox = new Sandbox(language);
(0, evaluate_2.useEverything)(sandbox);
return sandbox;
}
constructor(language) {
this._language = language;
this._map = new Map();
this._bridgeScope = scope_1.Scope.bridgeScope();
this._executeScope = scope_1.Scope.executeScope(this._bridgeScope);
this._configs = new Map();
this._modules = new Map();
this._resolvers = [];
this._cachedExecuter = new Map();
this._cachedSource = new Map();
this._options = (0, options_1.getDefaultSandboxOption)();
this._logRecorderSet = new Set();
this._debugInterceptor = null;
this._debugNextStep = false;
this._count = 0;
this._broke = false;
this._brokeFlag = null;
this._skipping = false;
this._skippingFlag = null;
this._usingAdditionalArgument = false;
this._additionalArgument = undefined;
}
get broke() {
return this._broke;
}
get count() {
return this._count;
}
get bridgeScope() {
return this._bridgeScope;
}
get executeScope() {
return this._executeScope;
}
get usingAdditionalArgument() {
return this._usingAdditionalArgument;
}
get additionalArgument() {
return this._additionalArgument;
}
use(mixin) {
mixin(this);
return this;
}
break() {
this._broke = true;
return this;
}
skip() {
this._skipping = true;
return this;
}
config(name, value) {
this._configs.set(name, value === undefined ? true : value);
return this;
}
inject(name, value) {
const parsedContent = (0, native_to_sand_1.parseNativeToSand)(value);
this._bridgeScope.register(variable_1.VARIABLE_TYPE.CONSTANT)(name, parsedContent);
return this;
}
module(name) {
return this._modules.get(name) || null;
}
mount(type, evaluator) {
this._map.set(type, evaluator);
return this;
}
provide(name, value) {
if (this._modules.has(name)) {
throw (0, error_1.error)(error_code_1.ERROR_CODE.DUPLICATED_PROVIDED_MODULE_NAME, name);
}
this._modules.set(name, value);
return this;
}
resolver(resolver) {
this._resolvers.push(resolver);
return this;
}
addLogRecorder(recorder) {
this._logRecorderSet.add(recorder);
return this;
}
removeLogRecorder(recorder) {
this._logRecorderSet.delete(recorder);
return this;
}
setDebugInterceptor(interceptor) {
this._debugInterceptor = interceptor;
return this;
}
getOption(name) {
const value = this._options[name];
return (0, assert_1.assert)(value).to.be.exist(error_code_1.ERROR_CODE.UNKNOWN_ERROR).firstValue();
}
setOption(name, value) {
this._options[name] = value;
return this;
}
setAdditionalArgument(value) {
this._usingAdditionalArgument = true;
this._additionalArgument = value;
return this;
}
evaluate(script_1, breakPoints_1) {
return __awaiter(this, arguments, void 0, function* (script, breakPoints, scriptLocation = script_location_1.ScriptLocation.createRoot(), scope) {
const isCodeLengthExceed = (0, options_1.getRawCodeLength)(script) > this._options.maxCodeLength;
if (isCodeLengthExceed) {
this.break();
return {
signal: evaluate_1.END_SIGNAL.ABORTED,
error: (0, error_1.error)(error_code_1.ERROR_CODE.MAXIMUM_CODE_LENGTH_LIMIT_EXCEED),
};
}
if (this._broke) {
return {
signal: evaluate_1.END_SIGNAL.ABORTED,
error: (0, error_1.error)(error_code_1.ERROR_CODE.SANDBOX_IS_BROKE),
};
}
this._cachedSource.set(scriptLocation.hash(), script);
const targetScope = typeof scope === "undefined"
? this._executeScope
: scope;
let parseResult;
const startTime = Date.now();
try {
parseResult = yield (0, parse_script_1.parseScript)(script, this._language);
}
catch (reason) {
return {
signal: evaluate_1.END_SIGNAL.ABORTED,
error: reason,
};
}
try {
const AST = parseResult.estree;
const breakPointController = typeof breakPoints === "undefined"
? undefined
: controller_1.MarkedDebugBreakPointController.fromBreakPoints(breakPoints);
const trace = trace_1.Trace.init(scriptLocation, parseResult.locationFinder, breakPointController);
let result = yield this.execute(AST, targetScope, trace);
if (this._broke) {
if (this._brokeFlag instanceof flag_1.Flag) {
if (this._brokeFlag.isThrow()) {
result = this._brokeFlag;
}
if (this._brokeFlag.isTerminate()) {
result = this._brokeFlag;
}
}
}
const endTime = Date.now();
if (result instanceof flag_1.Flag) {
if (result.isRootReturn()) {
return {
signal: evaluate_1.END_SIGNAL.SUCCEED,
exports: targetScope.exposed,
rootReturn: {
hasRootReturn: true,
returnValue: result.getValue(),
},
comments: parseResult.comments,
startTime,
endTime,
duration: endTime - startTime,
};
}
if (result.isThrow()) {
return {
signal: evaluate_1.END_SIGNAL.EXCEPTION,
trace: result.trace,
exception: result.getValue(),
comments: parseResult.comments,
startTime,
endTime,
duration: endTime - startTime,
};
}
if (result.isFatal()) {
return {
signal: evaluate_1.END_SIGNAL.FAILED,
error: result.getValue(),
comments: parseResult.comments,
startTime,
endTime,
duration: endTime - startTime,
};
}
if (result.isTerminate()) {
return {
signal: evaluate_1.END_SIGNAL.TERMINATED,
trace: result.trace,
comments: parseResult.comments,
startTime,
endTime,
duration: endTime - startTime,
};
}
}
return {
signal: evaluate_1.END_SIGNAL.SUCCEED,
exports: targetScope.exposed,
rootReturn: {
hasRootReturn: false,
},
comments: parseResult.comments,
startTime,
endTime,
duration: endTime - startTime,
};
}
catch (reason) {
const endTime = Date.now();
return {
signal: evaluate_1.END_SIGNAL.FAILED,
error: reason,
comments: parseResult.comments,
startTime,
endTime,
duration: endTime - startTime,
};
}
});
}
putExecuteLog(log) {
for (const recorder of this._logRecorderSet) {
recorder.putExecuteLog(log);
}
return this;
}
getSourceCode(scriptLocation) {
if (!this._cachedSource.has(scriptLocation.hash())) {
return null;
}
return this._cachedSource.get(scriptLocation.hash());
}
hasDebugInterceptor() {
return this._debugInterceptor !== null;
}
ensureGetDebugInterceptor() {
if (this._debugInterceptor === null) {
throw (0, error_1.error)(error_code_1.ERROR_CODE.INTERNAL_ERROR);
}
return this._debugInterceptor;
}
setNextStep(nextStep) {
this._debugNextStep = nextStep;
return this;
}
breakWithFlag(flag) {
this.break();
this._brokeFlag = flag;
}
skipWithFlag(flag) {
this.skip();
this._skippingFlag = flag;
}
getSkippingFlag() {
return this._skippingFlag;
}
recoverFromBreak() {
this._broke = false;
this._brokeFlag = null;
}
recoverFromSkip() {
this._skipping = false;
this._skippingFlag = null;
}
resolveResource(source, trace) {
return __awaiter(this, void 0, void 0, function* () {
for (const resolver of this._resolvers) {
const result = yield Promise.resolve(resolver(source, trace));
if (result) {
return result;
}
}
return null;
});
}
evaluateResource(resolveResult, breakPoints) {
return __awaiter(this, void 0, void 0, function* () {
const hash = resolveResult.scriptLocation.hash();
if (this._cachedExecuter.has(hash)) {
const cachedExecuter = this._cachedExecuter.get(hash);
if (cachedExecuter.isExecuting()) {
return {
signal: declare_1.EVALUATE_RESOURCE_END_SIGNAL.CYCLED_IMPORT,
};
}
return {
signal: declare_1.EVALUATE_RESOURCE_END_SIGNAL.SUCCEED,
executer: cachedExecuter,
};
}
const executer = executer_1.Executer.from(this);
this._cachedExecuter.set(hash, executer);
const result = yield executer.evaluate(resolveResult.script, breakPoints, resolveResult.scriptLocation);
if (result.signal !== evaluate_1.END_SIGNAL.SUCCEED) {
return {
signal: declare_1.EVALUATE_RESOURCE_END_SIGNAL.EVALUATE_FAILED,
result,
};
}
return {
signal: declare_1.EVALUATE_RESOURCE_END_SIGNAL.SUCCEED,
executer,
};
});
}
execute(node, scope, trace) {
return __awaiter(this, void 0, void 0, function* () {
this.putExecuteLog({
node,
scope,
trace,
});
if (this.getOption("duration") > 0) {
yield (0, options_1.awaitableSleep)(this.getOption("duration"));
}
if (this._broke) {
if (this._brokeFlag instanceof flag_1.Flag) {
return this._brokeFlag;
}
throw (0, error_1.error)(error_code_1.ERROR_CODE.SANDBOX_IS_BROKE, this._count.toString(), node, trace);
}
if (this._skipping) {
return;
}
if (this._count >= this._options.maxExpression) {
this.break();
throw (0, error_1.error)(error_code_1.ERROR_CODE.MAXIMUM_EXPRESSION_LIMIT_EXCEED, this._count.toString(), node, trace);
}
if ((0, node_1.shouldDebugNode)(node.type)) {
if (trace.hasBreakPointController()) {
const breakPointController = trace.ensureBreakPointController();
if (breakPointController.shouldBreak(trace.scriptLocation, node)) {
this.setNextStep(true);
}
}
if (this._debugNextStep) {
this.setNextStep(false);
const bindingPauseForBreakPoint = break_point_1.pauseForBreakPoint.bind(this);
yield bindingPauseForBreakPoint(node, scope, trace);
}
}
const executor = this._map.get(node.type);
if (!executor) {
throw (0, error_1.error)(error_code_1.ERROR_CODE.UNMOUNTED_AST_TYPE, node.type, node, trace);
}
this._count++;
const result = yield executor.bind(this)(node, scope, trace);
return result;
});
}
}
exports.Sandbox = Sandbox;