/* * Copyright 2006 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; /** * Tests for {@link AliasExternals}. * */ public class AliasExternalsTest extends CompilerTestCase { private static String EXTERNS = // Globals "/** @const */ var window;" + "/** @const */ var document;" + "var arguments;var _USER_ID;var ActiveXObject;" + "function eval(x) {}" + // Properties "window.setTimeout;" + "window.eval;" + "props.window;props.innerHTML;props.length;props.prototype;props.length;" + // More globals "/** @noalias */ var RangeObject; " + "var /** @noalias */ RuntimeObject, SelectionObject;" + "/** @noalias */ function NoAliasFunction() {};"; // Blacklist and whitelist of globals. Assign to these before running test // if you want to factor them in to the test, otherwise they will be null. private String unaliasableGlobals; private String aliasableGlobals; public AliasExternalsTest() { super(EXTERNS); } @Override protected int getNumRepetitions() { // This pass only runs once. return 1; } @Override public void setUp() { super.enableLineNumberCheck(false); super.enableNormalize(); unaliasableGlobals = null; aliasableGlobals = null; } /** * Test standard global aliasing. */ public void testGlobalAlias() { test("window.setTimeout(function() {}, 0);" + "var doc=window.document;" + "window.alert(\"foo\");" + "window.eval(\"1\");" + "window.location.href=\"http://www.example.com\";" + "function foo() {var window = \"bar\"; return window}foo();", "var GLOBAL_window=window;" + formatPropNameDecl("setTimeout") + "GLOBAL_window[$$PROP_setTimeout](function() {}, 0);" + "var doc=GLOBAL_window.document;" + "GLOBAL_window.alert(\"foo\");" + "GLOBAL_window.eval(\"1\");" + "GLOBAL_window.location.href=\"http://www.example.com\";" + "function foo() {var window = \"bar\"; return window}foo();"); } /** * Some globals should not be aliased because they have special meaning * within the language (like arguments). */ public void testUnaliasable() { test("function foo() {" + "var x=arguments.length;" + "var y=arguments.length;" + "var z=arguments.length;" + "var w=arguments.length;" + "return x + y + z + w" + "};foo();", formatPropNameDecl("length") + "function foo() {" + "var x=arguments[$$PROP_length];" + "var y=arguments[$$PROP_length];" + "var z=arguments[$$PROP_length];" + "var w=arguments[$$PROP_length];" + "return x + y + z + w" + "};foo();"); test("var x=new ActiveXObject();" + "x.foo=\"bar\";" + "var y=new ActiveXObject();" + "y.foo=\"bar\";" + "var z=new ActiveXObject();" + "z.foo=\"bar\";", "var x=new ActiveXObject();" + "x.foo=\"bar\";" + "var y=new ActiveXObject();" + "y.foo=\"bar\";" + "var z=new ActiveXObject();" + "z.foo=\"bar\";"); test("var _a=eval('foo'),_b=eval('foo'),_c=eval('foo'),_d=eval('foo')," + "_e=eval('foo'),_f=eval('foo'),_g=eval('foo');", "var _a=eval('foo'),_b=eval('foo'),_c=eval('foo'),_d=eval('foo')," + "_e=eval('foo'),_f=eval('foo'),_g=eval('foo');"); } /** * Test using a whitelist to explicitly alias only specific * identifiers. */ public void testAliasableGlobals() { aliasableGlobals = "notused,length"; test("function foo() {" + "var x=arguments.length;" + "var y=arguments.length;" + "var z=arguments.length;" + "var w=arguments.length;" + "return x + y + z + w" + "};foo();", formatPropNameDecl("length") + "function foo() {" + "var x=arguments[$$PROP_length];" + "var y=arguments[$$PROP_length];" + "var z=arguments[$$PROP_length];" + "var w=arguments[$$PROP_length];" + "return x + y + z + w" + "};foo();"); aliasableGlobals = "notused,notlength"; test("function foo() {" + "var x=arguments.length;" + "var y=arguments.length;" + "var z=arguments.length;" + "var w=arguments.length;" + "return x + y + z + w" + "};foo();", "function foo() {" + "var x=arguments.length;" + "var y=arguments.length;" + "var z=arguments.length;" + "var w=arguments.length;" + "return x + y + z + w" + "};foo();"); } /** * Test combined usage of aliasable and unaliasable global lists. */ public void testAliasableAndUnaliasableGlobals() { // Only aliasable provided - OK aliasableGlobals = "foo,bar"; unaliasableGlobals = ""; test("var x;", "var x;"); // Only unaliasable provided - OK aliasableGlobals = ""; unaliasableGlobals = "baz,qux"; test("var x;", "var x;"); // Both provided - bad aliasableGlobals = "foo,bar"; unaliasableGlobals = "baz,qux"; try { test("var x;", "var x;"); fail("Expected an IllegalArgumentException"); } catch (IllegalArgumentException ex) { // pass } } /** * Global variables that get re-assigned should not be aliased. */ public void testGlobalAssigment() { test("var x=_USER_ID+window;" + "var y=_USER_ID+window;" + "var z=_USER_ID+window;" + "var w=x+y+z;" + "_USER_ID = \"foo\";" + "window++;", "var x=_USER_ID+window;" + "var y=_USER_ID+window;" + "var z=_USER_ID+window;" + "var w=x+y+z;" + "_USER_ID = \"foo\";" + "window++"); } public void testNewOperator() { test("var x;new x(window);window;window;window;window;window", "var GLOBAL_window=window; var x;" + " new x(GLOBAL_window);GLOBAL_window;GLOBAL_window;" + " GLOBAL_window;GLOBAL_window;GLOBAL_window"); } /** * Test the standard replacement for GETPROP */ public void testGetProp() { test("function foo(a,b){return a.length > b.length;}", formatPropNameDecl("length") + "function foo(a, b){return a[$$PROP_length] > b[$$PROP_length];}"); test("Foo.prototype.bar = function() { return 'foo'; }", formatPropNameDecl("prototype") + "Foo[$$PROP_prototype].bar = function() { return 'foo'; }"); test("Foo.notreplaced = 5", "Foo.notreplaced=5"); } /** * Ops that should be ignored */ public void testIgnoredOps() { testSame("function foo() { this.length-- }"); testSame("function foo() { this.length++ }"); testSame("function foo() { this.length+=5 }"); testSame("function foo() { this.length-=5 }"); } /** * Test property setting */ public void testSetProp() { test("function foo() { this.innerHTML = 'hello!'; }", formatSetPropFn("innerHTML") + "function foo() { SETPROP_innerHTML(this, 'hello!'); }"); } /** * Test for modifying both parent and child, as all replacements * are on a single pass and modifying both involves being careful with * references. */ public void testParentChild() { test("a.length = b.length = c.length;", formatSetPropFn("length") + formatPropNameDecl("length") + "SETPROP_length(a, SETPROP_length(b, c[$$PROP_length]))"); } private static final String MODULE_SRC_ONE = "a=b.length;a=b.length;a=b.length;"; private static final String MODULE_SRC_TWO = "c=d.length;"; /** * Test that the code is placed in the first module when there are no * dependencies. */ public void testModulesWithoutDependencies() { test(createModules(MODULE_SRC_ONE, MODULE_SRC_TWO), new String[] { "var $$PROP_length=\"length\";a=b[$$PROP_length];" + "a=b[$$PROP_length];a=b[$$PROP_length];", "c=d[$$PROP_length];"}); } /** * Test that the code is placed in the first module when the second module * depends on the first. */ public void testModulesWithDependencies() { test(createModuleChain(MODULE_SRC_ONE, MODULE_SRC_TWO), new String[] { "var $$PROP_length=\"length\";a=b[$$PROP_length];" + "a=b[$$PROP_length];a=b[$$PROP_length];", "c=d[$$PROP_length];"}); } public void testPropAccessorPushedDeeper1() { test(createModuleChain("var a = \"foo\";", "var b = a.length;"), new String[] { "var a = \"foo\";", formatPropNameDecl("length") + "var b = a[$$PROP_length]" }); } public void testPropAccessorPushedDeeper2() { test(createModuleChain( "var a = \"foo\";", "var b = a.length;", "var c = a.length;"), new String[] { "var a = \"foo\";", formatPropNameDecl("length") + "var b = a[$$PROP_length]", "var c = a[$$PROP_length]" }); } public void testPropAccessorPushedDeeper3() { test(createModuleStar( "var a = \"foo\";", "var b = a.length;", "var c = a.length;"), new String[] { formatPropNameDecl("length") + "var a = \"foo\";", "var b = a[$$PROP_length]", "var c = a[$$PROP_length]" }); } public void testPropAccessorNotPushedDeeper() { test(createModuleChain("var a = \"foo\"; var b = a.length;", "var c = a.length;"), new String[] { formatPropNameDecl("length") + "var a = \"foo\"; var b = a[$$PROP_length]", "var c = a[$$PROP_length]" }); } public void testPropMutatorPushedDeeper() { test(createModuleChain("var a = [1];", "a.length = 0;"), new String[] { "var a = [1];", formatSetPropFn("length") + "SETPROP_length(a, 0);" }); } public void testPropMutatorNotPushedDeeper() { test(createModuleChain( "var a = [1]; a.length = 1;", "a.length = 0;"), new String[] { formatSetPropFn("length") + "var a = [1]; SETPROP_length(a, 1);", "SETPROP_length(a, 0);" }); } public void testGlobalAliasPushedDeeper() { test(createModuleChain( "var a = 1;", "var b = window, c = window, d = window, e = window;"), new String[] { "var a = 1;", "var GLOBAL_window = window;" + "var b = GLOBAL_window, c = GLOBAL_window, " + " d = GLOBAL_window, e = GLOBAL_window;" }); } public void testGlobalAliasNotPushedDeeper() { test(createModuleChain( "var a = 1, b = window;", "var c = window, d = window, e = window;"), new String[] { "var GLOBAL_window = window;" + "var a = 1, b = GLOBAL_window;", "var c = GLOBAL_window, " + " d = GLOBAL_window, e = GLOBAL_window;" }); } public void testNoAliasAnnotationForSingleVar() { testSame("[RangeObject, RangeObject, RangeObject]"); } public void testNoAliasAnnotationForMultiVarDeclaration() { test("[RuntimeObject, RuntimeObject, RuntimeObject," + " SelectionObject, SelectionObject, SelectionObject]", "var GLOBAL_SelectionObject = SelectionObject;" + "[RuntimeObject, RuntimeObject, RuntimeObject," + " GLOBAL_SelectionObject, GLOBAL_SelectionObject," + " GLOBAL_SelectionObject]"); } public void testNoAliasAnnotationForFunction() { testSame("[NoAliasFunction(), NoAliasFunction(), NoAliasFunction()]"); } private String formatPropNameDecl(String prop) { return "var $$PROP_" + prop + "='" + prop + "';"; } private String formatSetPropFn(String prop) { String mutatorName = "SETPROP_" + prop; String arg1 = mutatorName + "$a"; String arg2 = mutatorName + "$b"; return "function " + mutatorName + "(" + arg1 + "," + arg2 + ") {" + "return " + arg1 + "." + prop + "=" + arg2 + ";}"; } @Override protected CompilerPass getProcessor(Compiler compiler) { AliasExternals ae = new AliasExternals( compiler, compiler.getModuleGraph(), unaliasableGlobals, aliasableGlobals); ae.setRequiredUsage(1); return ae; } }