/* * 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); } }