450 lines
14 KiB
Java
450 lines
14 KiB
Java
/*
|
|
* Copyright 2008 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 CoalesceVariableNames}
|
|
*
|
|
*/
|
|
public class CoalesceVariableNamesTest extends CompilerTestCase {
|
|
// The spacing in this file is not exactly standard but it greatly helps
|
|
// picking out which variable names are merged.
|
|
|
|
private boolean usePseudoName = false;
|
|
|
|
@Override
|
|
protected int getNumRepetitions() {
|
|
return 1;
|
|
}
|
|
|
|
@Override
|
|
public void setUp() {
|
|
super.enableLineNumberCheck(true);
|
|
usePseudoName = false;
|
|
}
|
|
|
|
@Override
|
|
public CompilerPass getProcessor(final Compiler compiler) {
|
|
return new CompilerPass() {
|
|
@Override
|
|
public void process(Node externs, Node js) {
|
|
NodeTraversal.traverse(compiler, js,
|
|
new CoalesceVariableNames(compiler, usePseudoName));
|
|
}
|
|
};
|
|
}
|
|
|
|
public void testSimple() {
|
|
inFunction("var x; var y; x=1; x; y=1; y; return y",
|
|
"var x; x=1; x; x=1; x; return x");
|
|
|
|
inFunction("var x,y; x=1; x; y=1; y",
|
|
"var x ; x=1; x; x=1; x");
|
|
|
|
inFunction("var x,y; x=1; y=2; y; x");
|
|
|
|
inFunction("y=0; var x, y; y; x=0; x",
|
|
"y=0; var y ; y; y=0;y");
|
|
|
|
inFunction("var x,y; x=1; y=x; y",
|
|
"var x ; x=1; x=x; x");
|
|
|
|
inFunction("var x,y; x=1; y=x+1; y",
|
|
"var x ; x=1; x=x+1; x");
|
|
|
|
inFunction("x=1; x; y=2; y; var x; var y",
|
|
"x=1; x; x=2; x; var x");
|
|
|
|
inFunction("var x=1; var y=x+1; return y",
|
|
"var x=1; x=x+1; return x");
|
|
|
|
inFunction("var x=1; var y=0; x+=1; y");
|
|
|
|
inFunction("var x=1; x+=1; var y=0; y",
|
|
"var x=1; x+=1; x=0; x");
|
|
|
|
inFunction("var x=1; foo(bar(x+=1)); var y=0; y",
|
|
"var x=1; foo(bar(x+=1)); x=0; x");
|
|
|
|
inFunction("var y, x=1; f(x+=1, y)");
|
|
|
|
inFunction("var x; var y; y += 1, y, x = 1; x");
|
|
}
|
|
|
|
public void testMergeThreeVarNames() {
|
|
inFunction("var x,y,z; x=1; x; y=1; y; z=1; z",
|
|
"var x ; x=1; x; x=1; x; x=1; x");
|
|
}
|
|
|
|
public void testDifferentBlock() {
|
|
inFunction("if(1) { var x = 0; x } else { var y = 0; y }",
|
|
"if(1) { var x = 0; x } else { x = 0; x }");
|
|
}
|
|
|
|
public void testLoops() {
|
|
inFunction("var x; while(1) { x; x = 1; var y = 1; y }");
|
|
inFunction("var y = 1; y; while(1) { var x = 1; x }",
|
|
"var y = 1; y; while(1) { y = 1; y }");
|
|
}
|
|
|
|
public void testEscaped() {
|
|
inFunction("var x = 1; x; function f() { x }; var y = 0; y; f()");
|
|
}
|
|
|
|
public void testFor() {
|
|
inFunction("var x = 1; x; for (;;) var y; y = 1; y",
|
|
"var x = 1; x; for (;;) ; x = 1; x");
|
|
}
|
|
|
|
public void testForIn() {
|
|
// We lose some precision here, unless we have "branched-backward-dataflow".
|
|
inFunction("var x = 1, k; x; ; for (var y in k) { y }",
|
|
"var x = 1, k; x; ; for (var y in k) { y }");
|
|
|
|
inFunction("var x = 1, k; x; y = 1; for (var y in k) { y }",
|
|
"var x = 1, k; x; x = 1; for ( x in k) { x }");
|
|
}
|
|
|
|
public void testLoopInductionVar() {
|
|
inFunction(
|
|
"for(var x = 0; x < 10; x++){}" +
|
|
"for(var y = 0; y < 10; y++){}" +
|
|
"for(var z = 0; z < 10; z++){}",
|
|
|
|
"for(var x = 0; x < 10; x++){}" +
|
|
"for(x = 0; x < 10; x++){}" +
|
|
"for(x = 0; x < 10; x++){}");
|
|
|
|
inFunction(
|
|
"for(var x = 0; x < 10; x++){z}" +
|
|
"for(var y = 0, z = 0; y < 10; y++){z}",
|
|
|
|
"for(var x = 0; x < 10; x++){z}" +
|
|
"for(var x = 0, z = 0; x < 10; x++){z}");
|
|
|
|
inFunction("var x = 1; x; for (var y; y=1; ) {y}",
|
|
"var x = 1; x; for ( ; x=1; ) {x}");
|
|
|
|
inFunction("var x = 1; x; y = 1; while(y) var y; y",
|
|
"var x = 1; x; x = 1; while(x); x");
|
|
|
|
inFunction("var x = 1; x; f:var y; y=1",
|
|
"var x = 1; x; x=1");
|
|
}
|
|
|
|
public void testSwitchCase() {
|
|
inFunction("var x = 1; switch(x) { case 1: var y; case 2: } y = 1; y",
|
|
"var x = 1; switch(x) { case 1: case 2: } x = 1; x");
|
|
}
|
|
|
|
public void testDuplicatedVar() {
|
|
// Is there a shorter version without multiple declarations?
|
|
inFunction("z = 1; var x = 0; x; z; var y = 2, z = 1; y; z;",
|
|
"z = 1; var x = 0; x; z; var x = 2, z = 1; x; z;");
|
|
}
|
|
|
|
public void testTryCatch() {
|
|
inFunction("try {} catch (e) { } var x = 4; x;",
|
|
"try {} catch (e) { } var x = 4; x;");
|
|
inFunction("var x = 4; x; try {} catch (e) { }",
|
|
"var x = 4; x; try {} catch (e) { }");
|
|
}
|
|
|
|
public void testDeadAssignment() {
|
|
inFunction("var x = 6; var y; y = 4 ; x");
|
|
inFunction("var y = 3; var y; y += 4; x");
|
|
inFunction("var y = 3; var y; y ++ ; x");
|
|
inFunction("y = 3; var x; var y = 1 ; x");
|
|
}
|
|
|
|
public void testParameter() {
|
|
test("function FUNC(param) {var x = 0; x}",
|
|
"function FUNC(param) {param = 0; param}");
|
|
}
|
|
|
|
public void testParameter2() {
|
|
// Make sure two formal parameter name never merges.
|
|
test("function FUNC(x,y) {x = 0; x; y = 0; y}");
|
|
test("function FUNC(x,y,z) {x = 0; x; y = 0; z = 0; z}");
|
|
}
|
|
|
|
public void testParameter3() {
|
|
// Make sure the formal parameter declaration is consider a def.
|
|
test("function FUNC(x) {var y; y = 0; x; y}");
|
|
}
|
|
|
|
public void testParameter4() {
|
|
// Make sure that we do not merge two-arg functions because of the
|
|
// IE sort bug (see comments in computeEscaped)
|
|
test("function FUNC(x, y) {var a,b; y; a=0; a; x; b=0; b}",
|
|
"function FUNC(x, y) {var a; y; a=0; a; x; a=0; a}");
|
|
}
|
|
|
|
public void testParameter4b() {
|
|
// Merge parameters
|
|
test("function FUNC(x, y, z) {var a,b; y; a=0; a; x; b=0; b}",
|
|
"function FUNC(x, y, z) { y; y=0; y; x; x=0; x}");
|
|
}
|
|
|
|
public void testLiveRangeChangeWithinCfgNode() {
|
|
inFunction("var x, y; x = 1, y = 2, y, x");
|
|
inFunction("var x, y; x = 1,x; y");
|
|
|
|
// We lose some precisions within the node itself.
|
|
inFunction("var x; var y; y = 1, y, x = 1; x");
|
|
inFunction("var x; var y; y = 1; y, x = 1; x", "var x; x = 1; x, x = 1; x");
|
|
inFunction("var x, y; y = 1, x = 1, x, y += 1, y");
|
|
inFunction("var x, y; y = 1, x = 1, x, y ++, y");
|
|
}
|
|
|
|
public void testLiveRangeChangeWithinCfgNode2() {
|
|
inFunction("var x; var y; var a; var b;" +
|
|
"y = 1, a = 1, y, a, x = 1, b = 1; x; b");
|
|
inFunction("var x; var y; var a; var b;" +
|
|
"y = 1, a = 1, y, a, x = 1; x; b = 1; b",
|
|
"var x; var y; var a; " +
|
|
"y = 1, a = 1, y, a, x = 1; x; x = 1; x");
|
|
inFunction("var x; var y; var a; var b;" +
|
|
"y = 1, a = 1, y, x = 1; a; x; b = 1; b",
|
|
"var x; var y; var a; " +
|
|
"y = 1, a = 1, y, x = 1; a; x; x = 1; x");
|
|
}
|
|
|
|
public void testFunctionNameReuse() {
|
|
// TODO(user): Figure out why this increase code size most of the time.
|
|
// inFunction("function x() {}; x(); var y = 1; y",
|
|
// "function x() {}; x(); x = 1; x");
|
|
// inFunction("x(); var y = 1; y; function x() {}",
|
|
// "x(); x = 1; x; function x() {}");
|
|
// inFunction("x(); var y = 1; function x() {}; y",
|
|
// "x(); x = 1; function x() {}; x");
|
|
// // Can't merge because of possible escape.
|
|
// inFunction("function x() {return x}; x(); var y = 1; y",
|
|
// "function x() {return x}; x(); var y = 1; y");
|
|
//
|
|
// inFunction("var y = 1; y; x; function x() {}",
|
|
// "var y = 1; y; x; function x() {}");
|
|
// inFunction("var y = 1; y; function x() {}; x",
|
|
// "var y = 1; y; function x() {}; x");
|
|
// inFunction("var y = 1; y; function x() {}; x = 1; x",
|
|
// "var y = 1; y; function x() {}; y = 1; y");
|
|
// inFunction("var y = 1; y; x = 1; function x() {}; x",
|
|
// "var y = 1; y; y = 1; function x() {}; y");
|
|
}
|
|
|
|
public void testBug1401831() {
|
|
// Verify that we don't wrongly merge "opt_a2" and "i" without considering
|
|
// arguments[0] aliasing it.
|
|
String src = "function f(opt_a2) {" +
|
|
" var buffer;" +
|
|
" if (opt_a2) {" +
|
|
" for(var i = 0; i < arguments.length; i++) {" +
|
|
" buffer += arguments[i];" +
|
|
" }" +
|
|
" }" +
|
|
" return buffer;" +
|
|
"}";
|
|
test(src, src);
|
|
}
|
|
|
|
public void testDeterministic() {
|
|
// Make the variable interference graph a pentagon.
|
|
// a - b
|
|
// / \
|
|
// e c
|
|
// \ /
|
|
// d
|
|
// The coloring partitioning would be:
|
|
// a = { a, c }
|
|
// b = { b, d }
|
|
// e = { e }
|
|
inFunction("var a,b,c,d,e;" +
|
|
" a=1; b=1; a; b;" +
|
|
" b=1; c=1; b; c;" +
|
|
" c=1; d=1; c; d;" +
|
|
" d=1; e=1; d; e;" +
|
|
" e=1; a=1; e; a;",
|
|
|
|
"var a,b, e;" +
|
|
" a=1; b=1; a; b;" +
|
|
" b=1; a=1; b; a;" +
|
|
" a=1; b=1; a; b;" +
|
|
" b=1; e=1; b; e;" +
|
|
" e=1; a=1; e; a;");
|
|
|
|
// If we favor "d" first by declaring "d" earlier,
|
|
// the coloring partitioning would be:
|
|
// b = { b, e }
|
|
// d = { d, a }
|
|
// c = { c }
|
|
inFunction("var d,a,b,c,e;" +
|
|
" a=1; b=1; a; b;" +
|
|
" b=1; c=1; b; c;" +
|
|
" c=1; d=1; c; d;" +
|
|
" d=1; e=1; d; e;" +
|
|
" e=1; a=1; e; a;",
|
|
|
|
"var d, b,c ;" +
|
|
" d=1; b=1; d; b;" +
|
|
" b=1; c=1; b; c;" +
|
|
" c=1; d=1; c; d;" +
|
|
" d=1; b=1; d; b;" +
|
|
" b=1; d=1; b; d;");
|
|
}
|
|
|
|
// Sometimes live range can be cross even within a VAR declaration.
|
|
public void testVarLiveRangeCross() {
|
|
inFunction("var a={}; var b=a.S(); b",
|
|
"var a={}; a=a.S(); a");
|
|
inFunction("var a={}; var b=a.S(), c=b.SS(); b; c",
|
|
"var a={}; var b=a.S(), a=b.SS(); b; a");
|
|
inFunction("var a={}; var b=a.S(), c=a.SS(), d=a.SSS(); b; c; d",
|
|
"var a={}; var b=a.S(), c=a.SS(), a=a.SSS(); b; c; a");
|
|
inFunction("var a={}; var b=a.S(), c=a.SS(), d=a.SSS(); b; c; d",
|
|
"var a={}; var b=a.S(), c=a.SS(), a=a.SSS(); b; c; a");
|
|
inFunction("var a={}; d=1; d; var b=a.S(), c=a.SS(), d=a.SSS(); b; c; d");
|
|
}
|
|
|
|
public void testBug1445366() {
|
|
// An assignment might not be complete if the RHS throws an exception.
|
|
inFunction(
|
|
" var iframe = getFrame();" +
|
|
" try {" +
|
|
" var win = iframe.contentWindow;" +
|
|
" } catch (e) {" +
|
|
" } finally {" +
|
|
" if (win)" +
|
|
" this.setupWinUtil_();" +
|
|
" else" +
|
|
" this.load();" +
|
|
" }");
|
|
|
|
// Verify that we can still coalesce it if there are no handlers.
|
|
inFunction(
|
|
" var iframe = getFrame();" +
|
|
" var win = iframe.contentWindow;" +
|
|
" if (win)" +
|
|
" this.setupWinUtil_();" +
|
|
" else" +
|
|
" this.load();",
|
|
|
|
" var iframe = getFrame();" +
|
|
" iframe = iframe.contentWindow;" +
|
|
" if (iframe)" +
|
|
" this.setupWinUtil_();" +
|
|
" else" +
|
|
" this.load();");
|
|
}
|
|
|
|
public void testCannotReuseAnyParamsBug() {
|
|
testSame("function handleKeyboardShortcut(e, key, isModifierPressed) {\n" +
|
|
" if (!isModifierPressed) {\n" +
|
|
" return false;\n" +
|
|
" }\n" +
|
|
" var command;\n" +
|
|
" switch (key) {\n" +
|
|
" case 'b': // Ctrl+B\n" +
|
|
" command = COMMAND.BOLD;\n" +
|
|
" break;\n" +
|
|
" case 'i': // Ctrl+I\n" +
|
|
" command = COMMAND.ITALIC;\n" +
|
|
" break;\n" +
|
|
" case 'u': // Ctrl+U\n" +
|
|
" command = COMMAND.UNDERLINE;\n" +
|
|
" break;\n" +
|
|
" case 's': // Ctrl+S\n" +
|
|
" return true;\n" +
|
|
" }\n" +
|
|
"\n" +
|
|
" if (command) {\n" +
|
|
" this.fieldObject.execCommand(command);\n" +
|
|
" return true;\n" +
|
|
" }\n" +
|
|
"\n" +
|
|
" return false;\n" +
|
|
"};");
|
|
}
|
|
|
|
public void testForInWithAssignment() {
|
|
inFunction(
|
|
"var _f = function (commands) {" +
|
|
" var k, v, ref;" +
|
|
" for (k in ref = commands) {" +
|
|
" v = ref[k];" +
|
|
" alert(k + ':' + v);" +
|
|
" }" +
|
|
"}",
|
|
|
|
"var _f = function (commands) {" +
|
|
" var k,ref;" +
|
|
" for (k in ref = commands) {" +
|
|
" commands = ref[k];" +
|
|
" alert(k + ':' + commands);" +
|
|
" }" +
|
|
"}"
|
|
);
|
|
}
|
|
|
|
public void testUsePseduoNames() {
|
|
usePseudoName = true;
|
|
inFunction("var x = 0; print(x ); var y = 1; print( y)",
|
|
"var x_y = 0; print(x_y); x_y = 1; print(x_y)");
|
|
|
|
inFunction("var x_y = 1; var x = 0; print(x ); var y = 1;" +
|
|
"print( y); print(x_y);",
|
|
|
|
"var x_y = 1; var x_y$ = 0; print(x_y$); x_y$ = 1;" + "" +
|
|
"print(x_y$); print(x_y);");
|
|
|
|
inFunction("var x_y = 1; function f() {" +
|
|
"var x = 0; print(x ); var y = 1; print( y);" +
|
|
"print(x_y);}",
|
|
|
|
"var x_y = 1; function f() {" +
|
|
"var x_y$ = 0; print(x_y$); x_y$ = 1; print(x_y$);" +
|
|
"print(x_y);}");
|
|
|
|
inFunction("var x = 0; print(x ); var y = 1; print( y); " +
|
|
"var closure_var; function bar() { print(closure_var); }",
|
|
"var x_y = 0; print(x_y); x_y = 1; print(x_y); " +
|
|
"var closure_var; function bar() { print(closure_var); }");
|
|
}
|
|
|
|
public void testMaxVars() {
|
|
String code = "";
|
|
for (int i = 0;
|
|
i < LiveVariablesAnalysis.MAX_VARIABLES_TO_ANALYZE + 1; i++) {
|
|
code += String.format("var x%d = 0; print(x%d);", i, i);
|
|
}
|
|
inFunction(code);
|
|
}
|
|
|
|
private void inFunction(String src) {
|
|
inFunction(src, src);
|
|
}
|
|
|
|
private void inFunction(String src, String expected) {
|
|
test("function FUNC(){" + src + "}",
|
|
"function FUNC(){" + expected + "}");
|
|
}
|
|
|
|
private void test(String src) {
|
|
test(src, src);
|
|
}
|
|
}
|