397 lines
13 KiB
Java
397 lines
13 KiB
Java
|
/*
|
||
|
* 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;
|
||
|
}
|
||
|
}
|