mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-08-17 10:30:32 +03:00
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:
parent
1412ffa1ed
commit
8b75241959
@ -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
|
||||
|
31
test/serializer/optimized-functions/LoopBailout18.js
Normal file
31
test/serializer/optimized-functions/LoopBailout18.js
Normal 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]));
|
||||
};
|
15
test/serializer/optimized-functions/LoopBailout19.js
Normal file
15
test/serializer/optimized-functions/LoopBailout19.js
Normal 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]));
|
||||
};
|
Loading…
Reference in New Issue
Block a user