UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

483 lines (360 loc) 20 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CreatePerIterationEnvironment = CreatePerIterationEnvironment; exports.default = _default; var _realm = require("../realm.js"); var _index = require("../values/index.js"); var _completions = require("../completions.js"); var _traverse = _interopRequireDefault(require("@babel/traverse")); var _index2 = require("../domains/index.js"); var _errors = require("../errors.js"); var _index3 = require("../methods/index.js"); var _ForOfStatement = require("./ForOfStatement.js"); var _singletons = require("../singletons.js"); var _invariant = _interopRequireDefault(require("../invariant.js")); var t = _interopRequireWildcard(require("@babel/types")); var _generator = require("../utils/generator.js"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ // ECMA262 13.7.4.9 function CreatePerIterationEnvironment(realm, perIterationBindings) { // 1. If perIterationBindings has any elements, then if (perIterationBindings.length > 0) { // a. Let lastIterationEnv be the running execution context's LexicalEnvironment. let lastIterationEnv = realm.getRunningContext().lexicalEnvironment; // b. Let lastIterationEnvRec be lastIterationEnv's EnvironmentRecord. let lastIterationEnvRec = lastIterationEnv.environmentRecord; // c. Let outer be lastIterationEnv's outer environment reference. let outer = lastIterationEnv.parent; // d. Assert: outer is not null. (0, _invariant.default)(outer !== null); // e. Let thisIterationEnv be NewDeclarativeEnvironment(outer). let thisIterationEnv = _singletons.Environment.NewDeclarativeEnvironment(realm, outer); // f. Let thisIterationEnvRec be thisIterationEnv's EnvironmentRecord. realm.onDestroyScope(lastIterationEnv); let thisIterationEnvRec = thisIterationEnv.environmentRecord; // g. For each element bn of perIterationBindings do, for (let bn of perIterationBindings) { // i. Perform ! thisIterationEnvRec.CreateMutableBinding(bn, false). thisIterationEnvRec.CreateMutableBinding(bn, false); // ii. Let lastValue be ? lastIterationEnvRec.GetBindingValue(bn, true). let lastValue = lastIterationEnvRec.GetBindingValue(bn, true); // iii.Perform thisIterationEnvRec.InitializeBinding(bn, lastValue). thisIterationEnvRec.InitializeBinding(bn, lastValue); } // h. Set the running execution context's LexicalEnvironment to thisIterationEnv. realm.getRunningContext().lexicalEnvironment = thisIterationEnv; } // 2. Return undefined. return realm.intrinsics.undefined; } // ECMA262 13.7.4.8 function ForBodyEvaluation(realm, test, increment, stmt, perIterationBindings, labelSet, strictCode) { // 1. Let V be undefined. let V = realm.intrinsics.undefined; // 2. Perform ? CreatePerIterationEnvironment(perIterationBindings). CreatePerIterationEnvironment(realm, perIterationBindings); let env = realm.getRunningContext().lexicalEnvironment; let possibleInfiniteLoopIterations = 0; // 3. Repeat while (true) { let result; // a. If test is not [empty], then if (test) { // i. Let testRef be the result of evaluating test. let testRef = env.evaluate(test, strictCode); // ii. Let testValue be ? GetValue(testRef). let testValue = _singletons.Environment.GetValue(realm, testRef); // iii. If ToBoolean(testValue) is false, return NormalCompletion(V). if (!_singletons.To.ToBooleanPartial(realm, testValue)) { result = _singletons.Functions.incorporateSavedCompletion(realm, V); if (result instanceof _completions.JoinedNormalAndAbruptCompletions) { let selector = c => c instanceof _completions.BreakCompletion && !c.target; result = _completions.Completion.normalizeSelectedCompletions(selector, result); result = realm.composeWithSavedCompletion(result); } return V; } } // b. Let result be the result of evaluating stmt. result = env.evaluateCompletion(stmt, strictCode); (0, _invariant.default)(result instanceof _index.Value || result instanceof _completions.AbruptCompletion); // this is a join point for break and continue completions result = _singletons.Functions.incorporateSavedCompletion(realm, result); (0, _invariant.default)(result !== undefined); if (result instanceof _index.Value) result = new _completions.SimpleNormalCompletion(result); // c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)). if (!(0, _ForOfStatement.LoopContinues)(realm, result, labelSet)) { (0, _invariant.default)(result instanceof _completions.AbruptCompletion); // ECMA262 13.1.7 if (result instanceof _completions.BreakCompletion) { if (!result.target) return (0, _index3.UpdateEmpty)(realm, result, V).value; } else if (result instanceof _completions.JoinedAbruptCompletions) { let selector = c => c instanceof _completions.BreakCompletion && !c.target; if (result.containsSelectedCompletion(selector)) { result = _completions.Completion.normalizeSelectedCompletions(selector, result); } } return realm.returnOrThrowCompletion(result); } if (result instanceof _completions.JoinedNormalAndAbruptCompletions) { result = _completions.Completion.normalizeSelectedCompletions(c => c instanceof _completions.ContinueCompletion, result); } (0, _invariant.default)(result instanceof _completions.Completion); result = realm.composeWithSavedCompletion(result); // d. If result.[[Value]] is not empty, let V be result.[[Value]]. let resultValue = (0, _ForOfStatement.InternalGetResultValue)(realm, result); if (!(resultValue instanceof _index.EmptyValue)) V = resultValue; // e. Perform ? CreatePerIterationEnvironment(perIterationBindings). CreatePerIterationEnvironment(realm, perIterationBindings); env = realm.getRunningContext().lexicalEnvironment; // f. If increment is not [empty], then if (increment) { // i. Let incRef be the result of evaluating increment. let incRef = env.evaluate(increment, strictCode); // ii. Perform ? GetValue(incRef). _singletons.Environment.GetValue(realm, incRef); } else if (realm.useAbstractInterpretation) { // If we have no increment and we've hit 6 iterations of trying to evaluate // this loop body, then see if we have a break, return or throw completion in a // guarded condition and fail if it does. We already have logic to guard // against loops that are actually infinite. However, because there may be so // many forked execution paths, and they're non linear, then it might // computationally lead to a something that seems like an infinite loop. possibleInfiniteLoopIterations++; if (possibleInfiniteLoopIterations > 6) { failIfContainsBreakOrReturnOrThrowCompletion(realm.savedCompletion); } } } (0, _invariant.default)(false); function failIfContainsBreakOrReturnOrThrowCompletion(c) { if (c === undefined) return; if (c instanceof _completions.ThrowCompletion || c instanceof _completions.BreakCompletion || c instanceof _completions.ReturnCompletion) { let diagnostic = new _errors.CompilerDiagnostic("break, throw or return cannot be guarded by abstract condition", c.location, "PP0035", "FatalError"); realm.handleError(diagnostic); throw new _errors.FatalError(); } if (c instanceof _completions.JoinedAbruptCompletions || c instanceof _completions.JoinedNormalAndAbruptCompletions) { failIfContainsBreakOrReturnOrThrowCompletion(c.consequent); failIfContainsBreakOrReturnOrThrowCompletion(c.alternate); } } } let BailOutWrapperClosureRefVisitor = { ReferencedIdentifier(path, state) { if (path.node.name === "arguments") { state.usesArguments = true; } }, ThisExpression(path, state) { state.usesThis = true; }, "BreakStatement|ContinueStatement"(path, state) { if (path.node.label !== null) { state.usesGotoToLabel = true; } }, ReturnStatement(path, state) { state.usesReturn = true; }, ThrowStatement(path, state) { state.usesThrow = true; }, VariableDeclaration(path, state) { let node = path.node; // `let` and `const` are lexically scoped. We only need to change `var`s into assignments. Since we hoist the loop // into its own function `var`s (which are function scoped) need to be made available outside the loop. if (node.kind !== "var") return; if (t.isForOfStatement(path.parentPath.node) || t.isForInStatement(path.parentPath.node)) { // For-of and for-in variable declarations behave a bit differently. There is only one declarator and there is // never an initializer. Furthermore we can’t replace with an expression or statement, only a // `LeftHandSideExpression`. However, that `LeftHandSideExpression` will perform a `DestructuringAssignment` // operation which is what we want. (0, _invariant.default)(node.declarations.length === 1); (0, _invariant.default)(node.declarations[0].init == null); const { id } = node.declarations[0]; if (!t.isIdentifier(id)) { // We do not currently support ObjectPattern, SpreadPattern and ArrayPattern // see: https://github.com/babel/babylon/blob/master/ast/spec.md#patterns state.varPatternUnsupported = true; return; } // Replace with the id directly since it is a `LeftHandSideExpression`. path.replaceWith(id); } else { // Change all variable declarations into assignment statements. We assign to capture variables made available // outside of this scope. // If our parent is a `for (var x; x < y; x++)` loop we do not need a wrapper. // i.e. for (var x of y) for (var x in y) for (var x; x < y; x++) let needsExpressionWrapper = !t.isForStatement(path.parentPath.node); const getConvertedDeclarator = index => { let { id, init } = node.declarations[index]; if (t.isIdentifier(id)) { // If init is undefined, then we need to ensure we provide // an actual Babel undefined node for it. if (init === null) { init = t.identifier("undefined"); } return t.assignmentExpression("=", id, init); } else { // We do not currently support ObjectPattern, SpreadPattern and ArrayPattern // see: https://github.com/babel/babylon/blob/master/ast/spec.md#patterns state.varPatternUnsupported = true; } }; if (node.declarations.length === 1) { let convertedNodeOrUndefined = getConvertedDeclarator(0); if (convertedNodeOrUndefined === undefined) { // Do not continue as we don't support this return; } path.replaceWith(needsExpressionWrapper ? t.expressionStatement(convertedNodeOrUndefined) : convertedNodeOrUndefined); } else { // convert to sequence, so: `var x = 1, y = 2;` becomes `x = 1, y = 2;` let expressions = []; for (let i = 0; i < node.declarations.length; i++) { let convertedNodeOrUndefined = getConvertedDeclarator(i); if (convertedNodeOrUndefined === undefined) { // Do not continue as we don't support this return; } expressions.push(convertedNodeOrUndefined); } let sequenceExpression = t.sequenceExpression(expressions); path.replaceWith(needsExpressionWrapper ? t.expressionStatement(sequenceExpression) : sequenceExpression); } } } }; function generateRuntimeForStatement(ast, strictCode, env, realm, labelSet) { let wrapperFunction = new _index.ECMAScriptSourceFunctionValue(realm); let body = t.cloneDeep(t.blockStatement([ast])); wrapperFunction.initialize([], body); wrapperFunction.$Environment = env; // We need to scan to AST looking for "this", "return", "throw", labels and "arguments" let functionInfo = { usesArguments: false, usesThis: false, usesReturn: false, usesGotoToLabel: false, usesThrow: false, varPatternUnsupported: false }; (0, _traverse.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, [], body))])), BailOutWrapperClosureRefVisitor, null, functionInfo); _traverse.default.cache.clear(); let { usesReturn, usesThrow, usesArguments, usesGotoToLabel, varPatternUnsupported, usesThis } = functionInfo; if (usesReturn || usesThrow || usesArguments || usesGotoToLabel || varPatternUnsupported) { // We do not have support for these yet let diagnostic = new _errors.CompilerDiagnostic(`failed to recover from a for/while loop bail-out due to unsupported logic in loop body`, realm.currentLocation, "PP0037", "FatalError"); realm.handleError(diagnostic); throw new _errors.FatalError(); } let args = [wrapperFunction]; if (usesThis) { let thisRef = env.evaluate(t.thisExpression(), strictCode); let thisVal = _singletons.Environment.GetValue(realm, thisRef); _singletons.Leak.value(realm, thisVal); args.push(thisVal); } // We leak the wrapping function value, which in turn invokes the leak // logic which is transitive. The leaking logic should recursively visit // all bindings/objects in the loop and its body and mark the associated // bindings/objects as leaked _singletons.Leak.value(realm, wrapperFunction); let wrapperValue = _index.AbstractValue.createTemporalFromBuildFunction(realm, _index.Value, args, (0, _generator.createOperationDescriptor)("FOR_STATEMENT_FUNC", { usesThis })); (0, _invariant.default)(wrapperValue instanceof _index.AbstractValue); return wrapperValue; } function tryToEvaluateForStatementOrLeaveAsAbstract(ast, strictCode, env, realm, labelSet) { (0, _invariant.default)(!realm.instantRender.enabled); let effects; let savedSuppressDiagnostics = realm.suppressDiagnostics; try { realm.suppressDiagnostics = true; effects = realm.evaluateForEffects(() => evaluateForStatement(ast, strictCode, env, realm, labelSet), undefined, "tryToEvaluateForStatementOrLeaveAsAbstract"); } catch (error) { if (error instanceof _errors.FatalError) { realm.suppressDiagnostics = savedSuppressDiagnostics; return realm.evaluateWithPossibleThrowCompletion(() => generateRuntimeForStatement(ast, strictCode, env, realm, labelSet), _index2.TypesDomain.topVal, _index2.ValuesDomain.topVal); } else { throw error; } } finally { realm.suppressDiagnostics = savedSuppressDiagnostics; } realm.applyEffects(effects); return realm.returnOrThrowCompletion(effects.result); } // ECMA262 13.7.4.7 function _default(ast, strictCode, env, realm, labelSet) { if (realm.isInPureScope() && !realm.instantRender.enabled) { return tryToEvaluateForStatementOrLeaveAsAbstract(ast, strictCode, env, realm, labelSet); } else { return evaluateForStatement(ast, strictCode, env, realm, labelSet); } } function evaluateForStatement(ast, strictCode, env, realm, labelSet) { let { init, test, update, body } = ast; if (init && init.type === "VariableDeclaration") { if (init.kind === "var") { // for (var VariableDeclarationList; Expression; Expression) Statement // 1. Let varDcl be the result of evaluating VariableDeclarationList. let varDcl = env.evaluate(init, strictCode); // 2. ReturnIfAbrupt(varDcl). varDcl; // 3. Return ? ForBodyEvaluation(the first Expression, the second Expression, Statement, « », labelSet). return ForBodyEvaluation(realm, test, update, body, [], labelSet, strictCode); } else { // for (LexicalDeclaration Expression; Expression) Statement // 1. Let oldEnv be the running execution context's LexicalEnvironment. let oldEnv = env; // 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv). let loopEnv = _singletons.Environment.NewDeclarativeEnvironment(realm, oldEnv); // 3. Let loopEnvRec be loopEnv's EnvironmentRecord. let loopEnvRec = loopEnv.environmentRecord; // 4. Let isConst be the result of performing IsConstantDeclaration of LexicalDeclaration. let isConst = init.kind === "const"; // 5. Let boundNames be the BoundNames of LexicalDeclaration. let boundNames = _singletons.Environment.BoundNames(realm, init); // 6. For each element dn of boundNames do for (let dn of boundNames) { // a. If isConst is true, then if (isConst) { // i. Perform ! loopEnvRec.CreateImmutableBinding(dn, true). loopEnvRec.CreateImmutableBinding(dn, true); } else { // b. Else, // i. Perform ! loopEnvRec.CreateMutableBinding(dn, false). loopEnvRec.CreateMutableBinding(dn, false); } } // 7. Set the running execution context's LexicalEnvironment to loopEnv. realm.getRunningContext().lexicalEnvironment = loopEnv; // 8. Let forDcl be the result of evaluating LexicalDeclaration. let forDcl = loopEnv.evaluateCompletion(init, strictCode); // 9. If forDcl is an abrupt completion, then if (forDcl instanceof _completions.AbruptCompletion) { // a. Set the running execution context's LexicalEnvironment to oldEnv. let currentEnv = realm.getRunningContext().lexicalEnvironment; realm.onDestroyScope(currentEnv); if (currentEnv !== loopEnv) (0, _invariant.default)(loopEnv.destroyed); realm.getRunningContext().lexicalEnvironment = oldEnv; // b. Return Completion(forDcl). throw forDcl; } // 10. If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be « ». let perIterationLets = !isConst ? boundNames : []; let bodyResult; try { // 11. Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet). bodyResult = ForBodyEvaluation(realm, test, update, body, perIterationLets, labelSet, strictCode); } finally { // 12. Set the running execution context's LexicalEnvironment to oldEnv. let currentEnv = realm.getRunningContext().lexicalEnvironment; realm.onDestroyScope(currentEnv); if (currentEnv !== loopEnv) (0, _invariant.default)(loopEnv.destroyed); realm.getRunningContext().lexicalEnvironment = oldEnv; } // 13. Return Completion(bodyResult). return bodyResult; } } else { // for (Expression; Expression; Expression) Statement // 1. If the first Expression is present, then if (init) { // a. Let exprRef be the result of evaluating the first Expression. let exprRef = env.evaluate(init, strictCode); // b. Perform ? GetValue(exprRef). _singletons.Environment.GetValue(realm, exprRef); } // 2. Return ? ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet). return ForBodyEvaluation(realm, test, update, body, [], labelSet, strictCode); } } //# sourceMappingURL=ForStatement.js.map