585 lines
18 KiB
Java
585 lines
18 KiB
Java
/*
|
|
* Copyright 2009 The Closure Compiler Authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.google.javascript.jscomp;
|
|
|
|
import com.google.javascript.rhino.Node;
|
|
|
|
/**
|
|
* Unit tests for {@link FlowSensitiveInlineVariables}.
|
|
*
|
|
*/
|
|
public class FlowSensitiveInlineVariablesTest extends CompilerTestCase {
|
|
|
|
public static final String EXTERN_FUNCTIONS = "" +
|
|
"var print;\n" +
|
|
"/** @nosideeffects */ function noSFX() {} \n" +
|
|
" function hasSFX() {} \n";
|
|
|
|
public FlowSensitiveInlineVariablesTest() {
|
|
enableNormalize(true);
|
|
}
|
|
|
|
@Override
|
|
public int getNumRepetitions() {
|
|
// Test repeatedly inline.
|
|
return 3;
|
|
}
|
|
|
|
@Override
|
|
protected CompilerPass getProcessor(final Compiler compiler) {
|
|
//return new FlowSensitiveInlineVariables(compiler);
|
|
return new CompilerPass() {
|
|
@Override
|
|
public void process(Node externs, Node root) {
|
|
(new MarkNoSideEffectCalls(compiler)).process(externs, root);
|
|
(new FlowSensitiveInlineVariables(compiler)).process(externs, root);
|
|
}
|
|
};
|
|
}
|
|
|
|
public void testSimpleAssign() {
|
|
inline("var x; x = 1; print(x)", "var x; print(1)");
|
|
inline("var x; x = 1; x", "var x; 1");
|
|
inline("var x; x = 1; var a = x", "var x; var a = 1");
|
|
inline("var x; x = 1; x = x + 1", "var x; x = 1 + 1");
|
|
}
|
|
|
|
public void testSimpleVar() {
|
|
inline("var x = 1; print(x)", "var x; print(1)");
|
|
inline("var x = 1; x", "var x; 1");
|
|
inline("var x = 1; var a = x", "var x; var a = 1");
|
|
inline("var x = 1; x = x + 1", "var x; x = 1 + 1");
|
|
}
|
|
|
|
public void testSimpleForIn() {
|
|
inline("var a,b,x = a in b; x",
|
|
"var a,b,x; a in b");
|
|
noInline("var a, b; var x = a in b; print(1); x");
|
|
noInline("var a,b,x = a in b; delete a[b]; x");
|
|
}
|
|
|
|
public void testExported() {
|
|
noInline("var _x = 1; print(_x)");
|
|
}
|
|
|
|
public void testDoNotInlineIncrement() {
|
|
noInline("var x = 1; x++;");
|
|
noInline("var x = 1; x--;");
|
|
}
|
|
|
|
public void testDoNotInlineAssignmentOp() {
|
|
noInline("var x = 1; x += 1;");
|
|
noInline("var x = 1; x -= 1;");
|
|
}
|
|
|
|
public void testDoNotInlineIntoLhsOfAssign() {
|
|
noInline("var x = 1; x += 3;");
|
|
}
|
|
|
|
public void testMultiUse() {
|
|
noInline("var x; x = 1; print(x); print (x);");
|
|
}
|
|
|
|
public void testMultiUseInSameCfgNode() {
|
|
noInline("var x; x = 1; print(x) || print (x);");
|
|
}
|
|
|
|
public void testMultiUseInTwoDifferentPath() {
|
|
noInline("var x = 1; if (print) { print(x) } else { alert(x) }");
|
|
}
|
|
|
|
public void testAssignmentBeforeDefinition() {
|
|
inline("x = 1; var x = 0; print(x)","x = 1; var x; print(0)" );
|
|
}
|
|
|
|
public void testVarInConditionPath() {
|
|
noInline("if (foo) { var x = 0 } print(x)");
|
|
}
|
|
|
|
public void testMultiDefinitionsBeforeUse() {
|
|
inline("var x = 0; x = 1; print(x)", "var x = 0; print(1)");
|
|
}
|
|
|
|
public void testMultiDefinitionsInSameCfgNode() {
|
|
noInline("var x; (x = 1) || (x = 2); print(x)");
|
|
noInline("var x; x = (1 || (x = 2)); print(x)");
|
|
noInline("var x;(x = 1) && (x = 2); print(x)");
|
|
noInline("var x;x = (1 && (x = 2)); print(x)");
|
|
noInline("var x; x = 1 , x = 2; print(x)");
|
|
}
|
|
|
|
public void testNotReachingDefinitions() {
|
|
noInline("var x; if (foo) { x = 0 } print (x)");
|
|
}
|
|
|
|
public void testNoInlineLoopCarriedDefinition() {
|
|
// First print is undefined instead.
|
|
noInline("var x; while(true) { print(x); x = 1; }");
|
|
|
|
// Prints 0 1 1 1 1....
|
|
noInline("var x = 0; while(true) { print(x); x = 1; }");
|
|
}
|
|
|
|
public void testDoNotExitLoop() {
|
|
noInline("while (z) { var x = 3; } var y = x;");
|
|
}
|
|
|
|
public void testDoNotInlineWithinLoop() {
|
|
noInline("var y = noSFX(); do { var z = y.foo(); } while (true);");
|
|
}
|
|
|
|
public void testDoNotInlineCatchExpression1() {
|
|
noInline(
|
|
"var a;\n" +
|
|
"try {\n" +
|
|
" throw Error(\"\");\n" +
|
|
"}catch(err) {" +
|
|
" a = err;\n" +
|
|
"}\n" +
|
|
"return a.stack\n");
|
|
}
|
|
|
|
public void testDoNotInlineCatchExpression1a() {
|
|
noInline(
|
|
"var a;\n" +
|
|
"try {\n" +
|
|
" throw Error(\"\");\n" +
|
|
"}catch(err) {" +
|
|
" a = err + 1;\n" +
|
|
"}\n" +
|
|
"return a.stack\n");
|
|
}
|
|
|
|
public void testDoNotInlineCatchExpression2() {
|
|
noInline(
|
|
"var a;\n" +
|
|
"try {\n" +
|
|
" if (x) {throw Error(\"\");}\n" +
|
|
"}catch(err) {" +
|
|
" a = err;\n" +
|
|
"}\n" +
|
|
"return a.stack\n");
|
|
}
|
|
|
|
public void testDoNotInlineCatchExpression3() {
|
|
noInline(
|
|
"var a;\n" +
|
|
"try {\n" +
|
|
" throw Error(\"\");\n" +
|
|
"} catch(err) {" +
|
|
" err = x;\n" +
|
|
" a = err;\n" +
|
|
"}\n" +
|
|
"return a.stack\n");
|
|
}
|
|
|
|
public void testDoNotInlineCatchExpression4() {
|
|
// Note: it is valid to inline "x" here but we currently don't.
|
|
noInline(
|
|
"try {\n" +
|
|
" stuff();\n" +
|
|
"} catch (e) {\n" +
|
|
" x = e;\n" +
|
|
" print(x);\n" +
|
|
"}");
|
|
}
|
|
|
|
public void testDefinitionAfterUse() {
|
|
inline("var x = 0; print(x); x = 1", "var x; print(0); x = 1");
|
|
}
|
|
|
|
public void testInlineSameVariableInStraightLine() {
|
|
inline("var x; x = 1; print(x); x = 2; print(x)",
|
|
"var x; print(1); print(2)");
|
|
}
|
|
|
|
public void testInlineInDifferentPaths() {
|
|
inline("var x; if (print) {x = 1; print(x)} else {x = 2; print(x)}",
|
|
"var x; if (print) {print(1)} else {print(2)}");
|
|
}
|
|
|
|
public void testNoInlineInMergedPath() {
|
|
noInline(
|
|
"var x,y;x = 1;while(y) { if(y){ print(x) } else { x = 1 } } print(x)");
|
|
}
|
|
|
|
public void testInlineIntoExpressions() {
|
|
inline("var x = 1; print(x + 1);", "var x; print(1 + 1)");
|
|
}
|
|
|
|
public void testInlineExpressions1() {
|
|
inline("var a, b; var x = a+b; print(x)", "var a, b; var x; print(a+b)");
|
|
}
|
|
|
|
public void testInlineExpressions2() {
|
|
// We can't inline because of the redefinition of "a".
|
|
noInline("var a, b; var x = a + b; a = 1; print(x)");
|
|
}
|
|
|
|
public void testInlineExpressions3() {
|
|
inline("var a,b,x; x=a+b; x=a-b ; print(x)",
|
|
"var a,b,x; x=a+b; print(a-b)");
|
|
}
|
|
|
|
public void testInlineExpressions4() {
|
|
// Precision is lost due to comma's.
|
|
noInline("var a,b,x; x=a+b, x=a-b; print(x)");
|
|
}
|
|
|
|
public void testInlineExpressions5() {
|
|
noInline("var a; var x = a = 1; print(x)");
|
|
}
|
|
|
|
public void testInlineExpressions6() {
|
|
noInline("var a, x; a = 1 + (x = 1); print(x)");
|
|
}
|
|
|
|
public void testInlineExpression7() {
|
|
// Possible side effects in foo() that might conflict with bar();
|
|
noInline("var x = foo() + 1; bar(); print(x)");
|
|
|
|
// This is a possible case but we don't have analysis to prove this yet.
|
|
// TODO(user): It is possible to cover this case with the same algorithm
|
|
// as the missing return check.
|
|
noInline("var x = foo() + 1; print(x)");
|
|
}
|
|
|
|
public void testInlineExpression8() {
|
|
// The same variable inlined twice.
|
|
inline(
|
|
"var a,b;" +
|
|
"var x = a + b; print(x); x = a - b; print(x)",
|
|
"var a,b;" +
|
|
"var x; print(a + b); print(a - b)");
|
|
}
|
|
|
|
public void testInlineExpression9() {
|
|
// Check for actual control flow sensitivity.
|
|
inline(
|
|
"var a,b;" +
|
|
"var x; if (g) { x= a + b; print(x) } x = a - b; print(x)",
|
|
"var a,b;" +
|
|
"var x; if (g) { print(a + b)} print(a - b)");
|
|
}
|
|
|
|
public void testInlineExpression10() {
|
|
// The DFA is not fine grain enough for this.
|
|
noInline("var x, y; x = ((y = 1), print(y))");
|
|
}
|
|
|
|
public void testInlineExpressions11() {
|
|
inline("var x; x = x + 1; print(x)", "var x; print(x + 1)");
|
|
noInline("var x; x = x + 1; print(x); print(x)");
|
|
}
|
|
|
|
public void testInlineExpressions12() {
|
|
// ++ is an assignment and considered to modify state so it will not be
|
|
// inlined.
|
|
noInline("var x = 10; x = c++; print(x)");
|
|
}
|
|
|
|
public void testInlineExpressions13() {
|
|
inline("var a = 1, b = 2;" +
|
|
"var x = a;" +
|
|
"var y = b;" +
|
|
"var z = x + y;" +
|
|
"var i = z;" +
|
|
"var j = z + y;" +
|
|
"var k = i;",
|
|
|
|
"var a, b;" +
|
|
"var x;" +
|
|
"var y = 2;" +
|
|
"var z = 1 + y;" +
|
|
"var i;" +
|
|
"var j = z + y;" +
|
|
"var k = z;");
|
|
}
|
|
|
|
public void testNoInlineIfDefinitionMayNotReach() {
|
|
noInline("var x; if (x=1) {} x;");
|
|
}
|
|
|
|
public void testNoInlineEscapedToInnerFunction() {
|
|
noInline("var x = 1; function foo() { x = 2 }; print(x)");
|
|
}
|
|
|
|
public void testNoInlineLValue() {
|
|
noInline("var x; if (x = 1) { print(x) }");
|
|
}
|
|
|
|
public void testSwitchCase() {
|
|
inline("var x = 1; switch(x) { }", "var x; switch(1) { }");
|
|
}
|
|
|
|
public void testShadowedVariableInnerFunction() {
|
|
inline("var x = 1; print(x) || (function() { var x; x = 1; print(x)})()",
|
|
"var x; print(1) || (function() { var x; print(1)})()");
|
|
}
|
|
|
|
public void testCatch() {
|
|
noInline("var x = 0; try { } catch (x) { }");
|
|
noInline("try { } catch (x) { print(x) }");
|
|
}
|
|
|
|
public void testNoInlineGetProp() {
|
|
// We don't know if j alias a.b
|
|
noInline("var x = a.b.c; j.c = 1; print(x);");
|
|
}
|
|
|
|
public void testNoInlineGetProp2() {
|
|
noInline("var x = 1 * a.b.c; j.c = 1; print(x);");
|
|
}
|
|
|
|
public void testNoInlineGetProp3() {
|
|
// Anything inside a function is fine.
|
|
inline("var x = function(){1 * a.b.c}; print(x);",
|
|
"var x; print(function(){1 * a.b.c});");
|
|
}
|
|
|
|
public void testNoInlineGetEle() {
|
|
// Again we don't know if i = j
|
|
noInline("var x = a[i]; a[j] = 2; print(x); ");
|
|
}
|
|
|
|
// TODO(user): These should be inlinable.
|
|
public void testNoInlineConstructors() {
|
|
noInline("var x = new Iterator(); x.next();");
|
|
}
|
|
|
|
// TODO(user): These should be inlinable.
|
|
public void testNoInlineArrayLits() {
|
|
noInline("var x = []; print(x)");
|
|
}
|
|
|
|
// TODO(user): These should be inlinable.
|
|
public void testNoInlineObjectLits() {
|
|
noInline("var x = {}; print(x)");
|
|
}
|
|
|
|
// TODO(user): These should be inlinable after the REGEX checks.
|
|
public void testNoInlineRegExpLits() {
|
|
noInline("var x = /y/; print(x)");
|
|
}
|
|
|
|
public void testInlineConstructorCallsIntoLoop() {
|
|
// Don't inline construction into loops.
|
|
noInline("var x = new Iterator();" +
|
|
"for(i = 0; i < 10; i++) {j = x.next()}");
|
|
}
|
|
|
|
public void testRemoveWithLabels() {
|
|
inline("var x = 1; L: x = 2; print(x)", "var x = 1; L:{} print(2)");
|
|
inline("var x = 1; L: M: x = 2; print(x)", "var x = 1; L:M:{} print(2)");
|
|
inline("var x = 1; L: M: N: x = 2; print(x)",
|
|
"var x = 1; L:M:N:{} print(2)");
|
|
}
|
|
|
|
public void testInlineAcrossSideEffect1() {
|
|
// This can't be inlined because print() has side-effects and might change
|
|
// the definition of noSFX.
|
|
//
|
|
// noSFX must be both const and pure in order to inline it.
|
|
noInline("var y; var x = noSFX(y); print(x)");
|
|
//inline("var y; var x = noSFX(y); print(x)", "var y;var x;print(noSFX(y))");
|
|
}
|
|
|
|
public void testInlineAcrossSideEffect2() {
|
|
// Think noSFX() as a function that reads y.foo and return it
|
|
// and SFX() write some new value of y.foo. If that's the case,
|
|
// inlining across hasSFX() is not valid.
|
|
|
|
// This is a case where hasSFX is right of the source of the inlining.
|
|
noInline("var y; var x = noSFX(y), z = hasSFX(y); print(x)");
|
|
noInline("var y; var x = noSFX(y), z = new hasSFX(y); print(x)");
|
|
noInline("var y; var x = new noSFX(y), z = new hasSFX(y); print(x)");
|
|
}
|
|
|
|
public void testInlineAcrossSideEffect3() {
|
|
// This is a case where hasSFX is left of the destination of the inlining.
|
|
noInline("var y; var x = noSFX(y); hasSFX(y), print(x)");
|
|
noInline("var y; var x = noSFX(y); new hasSFX(y), print(x)");
|
|
noInline("var y; var x = new noSFX(y); new hasSFX(y), print(x)");
|
|
}
|
|
|
|
public void testInlineAcrossSideEffect4() {
|
|
// This is a case where hasSFX is some control flow path between the
|
|
// source and its destination.
|
|
noInline("var y; var x = noSFX(y); hasSFX(y); print(x)");
|
|
noInline("var y; var x = noSFX(y); new hasSFX(y); print(x)");
|
|
noInline("var y; var x = new noSFX(y); new hasSFX(y); print(x)");
|
|
}
|
|
|
|
public void testCanInlineAcrossNoSideEffect() {
|
|
// This can't be inlined because print() has side-effects and might change
|
|
// the definition of noSFX. We should be able to mark noSFX as const
|
|
// in some way.
|
|
noInline(
|
|
"var y; var x = noSFX(y), z = noSFX(); noSFX(); noSFX(), print(x)");
|
|
//inline(
|
|
// "var y; var x = noSFX(y), z = noSFX(); noSFX(); noSFX(), print(x)",
|
|
// "var y; var x, z = noSFX(); noSFX(); noSFX(), print(noSFX(y))");
|
|
}
|
|
|
|
public void testDependOnOuterScopeVariables() {
|
|
noInline("var x; function foo() { var y = x; x = 0; print(y) }");
|
|
noInline("var x; function foo() { var y = x; x++; print(y) }");
|
|
|
|
// Sadly, we don't understand the data flow of outer scoped variables as
|
|
// it can be modified by code outside of this scope. We can't inline
|
|
// at all if the definition has dependence on such variable.
|
|
noInline("var x; function foo() { var y = x; print(y) }");
|
|
}
|
|
|
|
public void testInlineIfNameIsLeftSideOfAssign() {
|
|
inline("var x = 1; x = print(x) + 1", "var x; x = print(1) + 1");
|
|
inline("var x = 1; L: x = x + 2", "var x; L: x = 1 + 2");
|
|
inline("var x = 1; x = (x = x + 1)", "var x; x = (x = 1 + 1)");
|
|
|
|
noInline("var x = 1; x = (x = (x = 10) + x)");
|
|
noInline("var x = 1; x = (f(x) + (x = 10) + x);");
|
|
noInline("var x = 1; x=-1,foo(x)");
|
|
noInline("var x = 1; x-=1,foo(x)");
|
|
}
|
|
|
|
public void testInlineArguments() {
|
|
testSame("function _func(x) { print(x) }");
|
|
testSame("function _func(x,y) { if(y) { x = 1 }; print(x) }");
|
|
|
|
test("function f(x, y) { x = 1; print(x) }",
|
|
"function f(x, y) { print(1) }");
|
|
|
|
test("function f(x, y) { if (y) { x = 1; print(x) }}",
|
|
"function f(x, y) { if (y) { print(1) }}");
|
|
}
|
|
|
|
public void testInvalidInlineArguments1() {
|
|
testSame("function f(x, y) { x = 1; arguments[0] = 2; print(x) }");
|
|
testSame("function f(x, y) { x = 1; var z = arguments;" +
|
|
"z[0] = 2; z[1] = 3; print(x)}");
|
|
testSame("function g(a){a[0]=2} function f(x){x=1;g(arguments);print(x)}");
|
|
}
|
|
|
|
public void testInvalidInlineArguments2() {
|
|
testSame("function f(c) {var f = c; arguments[0] = this;" +
|
|
"f.apply(this, arguments); return this;}");
|
|
}
|
|
|
|
public void testForIn() {
|
|
noInline("var x; var y = {}; for(x in y){}");
|
|
noInline("var x; var y = {}; var z; for(x in z = y){print(z)}");
|
|
noInline("var x; var y = {}; var z; for(x in y){print(z)}");
|
|
|
|
}
|
|
|
|
public void testNotOkToSkipCheckPathBetweenNodes() {
|
|
noInline("var x; for(x = 1; foo(x);) {}");
|
|
noInline("var x; for(; x = 1;foo(x)) {}");
|
|
}
|
|
|
|
public void testIssue698() {
|
|
// Most of the flow algorithms operate on Vars. We want to make
|
|
// sure the algorithm bails out appropriately if it sees
|
|
// a var that it doesn't know about.
|
|
inline(
|
|
"var x = ''; "
|
|
+ "unknown.length < 2 && (unknown='0' + unknown);"
|
|
+ "x = x + unknown; "
|
|
+ "unknown.length < 3 && (unknown='0' + unknown);"
|
|
+ "x = x + unknown; "
|
|
+ "return x;",
|
|
"var x; "
|
|
+ "unknown.length < 2 && (unknown='0' + unknown);"
|
|
+ "x = '' + unknown; "
|
|
+ "unknown.length < 3 && (unknown='0' + unknown);"
|
|
+ "x = x + unknown; "
|
|
+ "return x;");
|
|
}
|
|
|
|
public void testIssue777() {
|
|
test(
|
|
"function f(cmd, ta) {" +
|
|
" var temp = cmd;" +
|
|
" var temp2 = temp >> 2;" +
|
|
" cmd = STACKTOP;" +
|
|
" for (var src = temp2, dest = cmd >> 2, stop = src + 37;" +
|
|
" src < stop;" +
|
|
" src++, dest++) {" +
|
|
" HEAP32[dest] = HEAP32[src];" +
|
|
" }" +
|
|
" temp = ta;" +
|
|
" temp2 = temp >> 2;" +
|
|
" ta = STACKTOP;" +
|
|
" STACKTOP += 8;" +
|
|
" HEAP32[ta >> 2] = HEAP32[temp2];" +
|
|
" HEAP32[ta + 4 >> 2] = HEAP32[temp2 + 1];" +
|
|
"}",
|
|
"function f(cmd, ta){" +
|
|
" var temp;" +
|
|
" var temp2 = cmd >> 2;" +
|
|
" cmd = STACKTOP;" +
|
|
" var src = temp2;" +
|
|
" var dest = cmd >> 2;" +
|
|
" var stop = src + 37;" +
|
|
" for(;src<stop;src++,dest++)HEAP32[dest]=HEAP32[src];" +
|
|
" temp2 = ta >> 2;" +
|
|
" ta = STACKTOP;" +
|
|
" STACKTOP += 8;" +
|
|
" HEAP32[ta>>2] = HEAP32[temp2];" +
|
|
" HEAP32[ta+4>>2] = HEAP32[temp2+1];" +
|
|
"}");
|
|
}
|
|
|
|
public void testTransitiveDependencies1() {
|
|
test(
|
|
"function f(x) { var a = x; var b = a; x = 3; return b; }",
|
|
"function f(x) { var a; var b = x; x = 3; return b; }");
|
|
}
|
|
|
|
public void testTransitiveDependencies2() {
|
|
test(
|
|
"function f(x) { var a = x; var b = a; var c = b; x = 3; return c; }",
|
|
"function f(x) { var a ; var b = x; var c ; x = 3; return b; }");
|
|
}
|
|
|
|
public void testIssue794a() {
|
|
noInline(
|
|
"var x = 1; " +
|
|
"try { x += someFunction(); } catch (e) {}" +
|
|
"x += 1;" +
|
|
"try { x += someFunction(); } catch (e) {}" +
|
|
"return x;");
|
|
}
|
|
|
|
public void testIssue794b() {
|
|
noInline(
|
|
"var x = 1; " +
|
|
"try { x = x + someFunction(); } catch (e) {}" +
|
|
"x = x + 1;" +
|
|
"try { x = x + someFunction(); } catch (e) {}" +
|
|
"return x;");
|
|
}
|
|
|
|
private void noInline(String input) {
|
|
inline(input, input);
|
|
}
|
|
|
|
private void inline(String input, String expected) {
|
|
test(EXTERN_FUNCTIONS, "function _func() {" + input + "}",
|
|
"function _func() {" + expected + "}", null, null);
|
|
}
|
|
}
|