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
JavaScript
"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