Prevent loops from getting stuck in infinite loops (#2247)

Summary:
Release notes: stop abstract loops from getting stuck in infinite loops when body contains a return, throw or break completion

Fixes https://github.com/facebook/prepack/issues/2053. This PR adds logic to "for loops" where they might get stuck in an infinite loop when there is no incrementor but there is a return, throw or break completion in the loop body. We check this via a new function `andfailIfContainsBreakOrReturnOrThrowCompletion` and only do this after `100` iterations of evaluating the loop body. I was thinking of making the iteration count configurable but wanted feedback on the approach first. I tried `1000` iterations, but it actually ended up taking about 2 minutes to evaluate the test case :/
Pull Request resolved: https://github.com/facebook/prepack/pull/2247

Differential Revision: D8827425

Pulled By: trueadm

fbshipit-source-id: d05f539e2c8a6dce15b7f23c1b76e89087437738
This commit is contained in:
Dominic Gannaway 2018-07-12 23:30:58 -07:00 committed by Facebook Github Bot
parent 1412ffa1ed
commit 8b75241959
3 changed files with 81 additions and 0 deletions

View File

@ -99,6 +99,7 @@ function ForBodyEvaluation(
// 2. Perform ? CreatePerIterationEnvironment(perIterationBindings).
CreatePerIterationEnvironment(realm, perIterationBindings);
let env = realm.getRunningContext().lexicalEnvironment;
let possibleInfiniteLoopIterations = 0;
// 3. Repeat
while (true) {
@ -159,10 +160,39 @@ function ForBodyEvaluation(
// ii. Perform ? GetValue(incRef).
Environment.GetValue(realm, incRef);
} else if (realm.useAbstractInterpretation) {
// If we have no increment and we've hit 100 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 > 100) {
failIfContainsBreakOrReturnOrThrowCompletion(realm.savedCompletion);
}
}
}
invariant(false);
function failIfContainsBreakOrReturnOrThrowCompletion(c: void | Completion | Value) {
if (c === undefined) return;
if (c instanceof ThrowCompletion || c instanceof BreakCompletion || c instanceof ReturnCompletion) {
let diagnostic = new CompilerDiagnostic(
"break, throw or return cannot be guarded by abstract condition",
c.location,
"PP0035",
"FatalError"
);
realm.handleError(diagnostic);
throw new FatalError();
}
if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) {
failIfContainsBreakOrReturnOrThrowCompletion(c.consequent);
failIfContainsBreakOrReturnOrThrowCompletion(c.alternate);
}
}
function failIfContainsBreakOrContinueCompletionWithNonLocalTarget(c: void | Completion | Value) {
if (c === undefined) return;
if (c instanceof ContinueCompletion || c instanceof BreakCompletion) {
@ -322,6 +352,11 @@ let BailOutWrapperClosureRefVisitor = {
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

View File

@ -0,0 +1,31 @@
function fn(x) {
for (
var _iterator = x.items,
_isArray = Array.isArray(_iterator),
_i = 0,
_iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();
;
) {
// ...
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var item = _ref;
}
}
global.__optimize && __optimize(fn);
inspect = function() {
return JSON.stringify(fn([1, 2, 3, 4, 5]));
};

View File

@ -0,0 +1,15 @@
function fn(x, oldItems) {
var items = [];
for (var i; i < x; ) {
i++;
var oldItem = oldItems[i];
items.push(oldItem + 2);
}
return items;
}
global.__optimize && __optimize(fn);
inspect = function() {
return JSON.stringify(fn(5, [1, 2, 3, 4, 5]));
};