UNPKG

@sudoo/marked

Version:

JavaScript & TypeScript code runner in JavaScript, safe with marked territory, asynchronous

403 lines (402 loc) 15.3 kB
"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;