This repository has been archived on 2023-06-18. You can view files and clone it, but cannot push or open issues or pull requests.
ima02/resources/defects4j-checkout-closure-1f/test/com/google/javascript/jscomp/PureFunctionIdentifierTest.java

1277 lines
43 KiB
Java
Raw Normal View History

2023-04-25 11:33:41 +00:00
/*
* 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.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import static com.google.javascript.jscomp.PureFunctionIdentifier.INVALID_NO_SIDE_EFFECT_ANNOTATION;
import com.google.javascript.rhino.Node;
import java.util.List;
/**
* Tests for {@link PureFunctionIdentifier}
*
*/
public class PureFunctionIdentifierTest extends CompilerTestCase {
List<String> noSideEffectCalls = Lists.newArrayList();
List<String> localResultCalls = Lists.newArrayList();
boolean regExpHaveSideEffects = true;
private static String kExterns =
CompilerTypeTestCase.DEFAULT_EXTERNS +
"var window; window.setTimeout;" +
"/**@nosideeffects*/ function externSENone(){}\n" +
"/**@modifies{this}*/ function externSEThis(){}\n" +
"/**@constructor\n" +
" * @modifies{this}*/\n" +
"function externObjSEThis(){}\n" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @modifies{this}\n" +
" */\n" +
"externObjSEThis.prototype.externObjSEThisMethod = function(s) {};" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @modifies{arguments}\n" +
" */\n" +
"externObjSEThis.prototype.externObjSEThisMethod2 = function(s) {};" +
"/**@nosideeffects*/function Error(){}" +
"function externSef1(){}" +
"/**@nosideeffects*/function externNsef1(){}" +
"var externSef2 = function(){};" +
"/**@nosideeffects*/var externNsef2 = function(){};" +
"var externNsef3 = /**@nosideeffects*/function(){};" +
"var externObj;" +
"externObj.sef1 = function(){};" +
"/**@nosideeffects*/externObj.nsef1 = function(){};" +
"externObj.nsef2 = /**@nosideeffects*/function(){};" +
"externObj.partialFn;" +
"externObj.partialSharedFn;" +
"var externObj2;" +
"externObj2.partialSharedFn = /**@nosideeffects*/function(){};" +
"/**@constructor*/function externSefConstructor(){}" +
"externSefConstructor.prototype.sefFnOfSefObj = function(){};" +
"externSefConstructor.prototype.nsefFnOfSefObj = " +
" /**@nosideeffects*/function(){};" +
"externSefConstructor.prototype.externShared = function(){};" +
"/**@constructor\n@nosideeffects*/function externNsefConstructor(){}" +
"externNsefConstructor.prototype.sefFnOfNsefObj = function(){};" +
"externNsefConstructor.prototype.nsefFnOfNsefObj = " +
" /**@nosideeffects*/function(){};" +
"externNsefConstructor.prototype.externShared = " +
" /**@nosideeffects*/function(){};" +
"/**@constructor\n@nosideeffects*/function externNsefConstructor2(){}" +
"externNsefConstructor2.prototype.externShared = " +
" /**@nosideeffects*/function(){};" +
"externNsefConstructor.prototype.sharedPartialSef;" +
"/**@nosideeffects*/externNsefConstructor.prototype.sharedPartialNsef;" +
// An externs definition with a stub before.
"/**@constructor*/function externObj3(){}" +
"externObj3.prototype.propWithStubBefore;" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @nosideeffects\n" +
" */\n" +
"externObj3.prototype.propWithStubBefore = function(s) {};" +
// useless JsDoc
"/**\n" +
" * @see {foo}\n" +
" */\n" +
"externObj3.prototype.propWithStubBeforeWithJSDoc;" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @nosideeffects\n" +
" */\n" +
"externObj3.prototype.propWithStubBeforeWithJSDoc = function(s) {};" +
// An externs definition with a stub after.
"/**@constructor*/function externObj4(){}" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @nosideeffects\n" +
" */\n" +
"externObj4.prototype.propWithStubAfter = function(s) {};" +
"externObj4.prototype.propWithStubAfter;" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @nosideeffects\n" +
" */\n" +
"externObj4.prototype.propWithStubAfterWithJSDoc = function(s) {};" +
// useless JsDoc
"/**\n" +
" * @see {foo}\n" +
" */\n" +
"externObj4.prototype.propWithStubAfterWithJSDoc;";
public PureFunctionIdentifierTest() {
super(kExterns);
enableTypeCheck(CheckLevel.ERROR);
}
@Override
protected int getNumRepetitions() {
// run pass once.
return 1;
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
noSideEffectCalls.clear();
localResultCalls.clear();
boolean regExpHaveSideEffects = true;
}
public void testIssue303() throws Exception {
checkMarkedCalls(
"/** @constructor */ function F() {" +
" var self = this;" +
" window.setTimeout(function() {" +
" window.location = self.location;" +
" }, 0);" +
"}" +
"F.prototype.setLocation = function(x) {" +
" this.location = x;" +
"};" +
"(new F()).setLocation('http://www.google.com/');",
ImmutableList.<String>of());
}
public void testIssue303b() throws Exception {
checkMarkedCalls(
"/** @constructor */ function F() {" +
" var self = this;" +
" window.setTimeout(function() {" +
" window.location = self.location;" +
" }, 0);" +
"}" +
"F.prototype.setLocation = function(x) {" +
" this.location = x;" +
"};" +
"function x() {" +
" (new F()).setLocation('http://www.google.com/');" +
"} window['x'] = x;",
ImmutableList.<String>of());
}
public void testAnnotationInExterns_new1() throws Exception {
checkMarkedCalls("externSENone()",
ImmutableList.<String>of("externSENone"));
}
public void testAnnotationInExterns_new2() throws Exception {
checkMarkedCalls("externSEThis()",
ImmutableList.<String>of());
}
public void testAnnotationInExterns_new3() throws Exception {
checkMarkedCalls("new externObjSEThis()",
ImmutableList.<String>of("externObjSEThis"));
}
public void testAnnotationInExterns_new4() throws Exception {
// The entire expression containing "externObjSEThisMethod" is considered
// side-effect free in this context.
checkMarkedCalls("new externObjSEThis().externObjSEThisMethod('')",
ImmutableList.<String>of(
"externObjSEThis", "NEW STRING externObjSEThisMethod"));
}
public void testAnnotationInExterns_new5() throws Exception {
checkMarkedCalls(
"function f() { new externObjSEThis() };" +
"f();",
ImmutableList.<String>of("externObjSEThis", "f"));
}
public void testAnnotationInExterns_new6() throws Exception {
// While "externObjSEThisMethod" has modifies "this"
// it does not have global side-effects with "this" is
// a known local value.
// TODO(johnlenz): "f" is side-effect free but we need
// to propagate that "externObjSEThisMethod" is modifying
// a local object.
checkMarkedCalls(
"function f() {" +
" new externObjSEThis().externObjSEThisMethod('') " +
"};" +
"f();",
ImmutableList.<String>of(
"externObjSEThis", "NEW STRING externObjSEThisMethod"));
}
public void testAnnotationInExterns_new7() throws Exception {
// While "externObjSEThisMethod" has modifies "this"
// it does not have global side-effects with "this" is
// a known local value.
checkMarkedCalls(
"function f() {" +
" var x = new externObjSEThis(); " +
" x.externObjSEThisMethod('') " +
"};" +
"f();",
ImmutableList.<String>of("externObjSEThis"));
}
public void testAnnotationInExterns_new8() throws Exception {
// "externObjSEThisMethod" modifies "this", the 'this'
// is not a known local value, so it must be assumed it is to
// have global side-effects.
checkMarkedCalls(
"function f(x) {" +
" x.externObjSEThisMethod('') " +
"};" +
"f(new externObjSEThis());",
ImmutableList.<String>of("externObjSEThis"));
}
public void testAnnotationInExterns_new9() throws Exception {
// "externObjSEThisMethod" modifies "this", the 'this'
// is not a known local value, so it must be assumed it is to
// have global side-effects. All possible values of "x" are considered
// as no intraprocedural data flow is done.
checkMarkedCalls(
"function f(x) {" +
" x = new externObjSEThis(); " +
" x.externObjSEThisMethod('') " +
"};" +
"f(g);",
ImmutableList.<String>of("externObjSEThis"));
}
public void testAnnotationInExterns_new10() throws Exception {
// While "externObjSEThisMethod2" only modifies it arguments
// and the arguments are known local values, we don't
// yet connect the dots, and "f" is consider to have
// global side-effects.
checkMarkedCalls(
"function f() {" +
" new externObjSEThis().externObjSEThisMethod2('') " +
"};" +
"f();",
ImmutableList.<String>of("externObjSEThis"));
}
public void testAnnotationInExterns1() throws Exception {
checkMarkedCalls("externSef1()", ImmutableList.<String>of());
}
public void testAnnotationInExterns2() throws Exception {
checkMarkedCalls("externSef2()", ImmutableList.<String>of());
}
public void testAnnotationInExterns3() throws Exception {
checkMarkedCalls("externNsef1()", ImmutableList.of("externNsef1"));
}
public void testAnnotationInExterns4() throws Exception {
checkMarkedCalls("externNsef2()", ImmutableList.of("externNsef2"));
}
public void testAnnotationInExterns5() throws Exception {
checkMarkedCalls("externNsef3()", ImmutableList.of("externNsef3"));
}
public void testNamespaceAnnotationInExterns1() throws Exception {
checkMarkedCalls("externObj.sef1()", ImmutableList.<String>of());
}
public void testNamespaceAnnotationInExterns2() throws Exception {
checkMarkedCalls("externObj.nsef1()", ImmutableList.of("externObj.nsef1"));
}
public void testNamespaceAnnotationInExterns3() throws Exception {
checkMarkedCalls("externObj.nsef2()", ImmutableList.of("externObj.nsef2"));
}
public void testNamespaceAnnotationInExterns4() throws Exception {
checkMarkedCalls("externObj.partialFn()",
ImmutableList.<String>of());
}
public void testNamespaceAnnotationInExterns5() throws Exception {
// Test that adding a second definition for a partially defined
// function doesn't make us think that the function has no side
// effects.
String templateSrc = "var o = {}; o.<fnName> = function(){}; o.<fnName>()";
// Ensure that functions with name != "partialFn" get marked.
checkMarkedCalls(templateSrc.replaceAll("<fnName>", "notPartialFn"),
ImmutableList.of("o.notPartialFn"));
checkMarkedCalls(templateSrc.replaceAll("<fnName>", "partialFn"),
ImmutableList.<String>of());
}
public void testNamespaceAnnotationInExterns6() throws Exception {
checkMarkedCalls("externObj.partialSharedFn()",
ImmutableList.<String>of());
}
public void testConstructorAnnotationInExterns1() throws Exception {
checkMarkedCalls("new externSefConstructor()",
ImmutableList.<String>of());
}
public void testConstructorAnnotationInExterns2() throws Exception {
checkMarkedCalls("var a = new externSefConstructor();" +
"a.sefFnOfSefObj()",
ImmutableList.<String>of());
}
public void testConstructorAnnotationInExterns3() throws Exception {
checkMarkedCalls("var a = new externSefConstructor();" +
"a.nsefFnOfSefObj()",
ImmutableList.of("a.nsefFnOfSefObj"));
}
public void testConstructorAnnotationInExterns4() throws Exception {
checkMarkedCalls("var a = new externSefConstructor();" +
"a.externShared()",
ImmutableList.<String>of());
}
public void testConstructorAnnotationInExterns5() throws Exception {
checkMarkedCalls("new externNsefConstructor()",
ImmutableList.of("externNsefConstructor"));
}
public void testConstructorAnnotationInExterns6() throws Exception {
checkMarkedCalls("var a = new externNsefConstructor();" +
"a.sefFnOfNsefObj()",
ImmutableList.of("externNsefConstructor"));
}
public void testConstructorAnnotationInExterns7() throws Exception {
checkMarkedCalls("var a = new externNsefConstructor();" +
"a.nsefFnOfNsefObj()",
ImmutableList.of("externNsefConstructor",
"a.nsefFnOfNsefObj"));
}
public void testConstructorAnnotationInExterns8() throws Exception {
checkMarkedCalls("var a = new externNsefConstructor();" +
"a.externShared()",
ImmutableList.of("externNsefConstructor"));
}
public void testSharedFunctionName1() throws Exception {
checkMarkedCalls("var a; " +
"if (true) {" +
" a = new externNsefConstructor()" +
"} else {" +
" a = new externSefConstructor()" +
"}" +
"a.externShared()",
ImmutableList.of("externNsefConstructor"));
}
public void testSharedFunctionName2() throws Exception {
// Implementation for both externNsefConstructor and externNsefConstructor2
// have no side effects.
boolean broken = true;
if (broken) {
checkMarkedCalls("var a; " +
"if (true) {" +
" a = new externNsefConstructor()" +
"} else {" +
" a = new externNsefConstructor2()" +
"}" +
"a.externShared()",
ImmutableList.of("externNsefConstructor",
"externNsefConstructor2"));
} else {
checkMarkedCalls("var a; " +
"if (true) {" +
" a = new externNsefConstructor()" +
"} else {" +
" a = new externNsefConstructor2()" +
"}" +
"a.externShared()",
ImmutableList.of("externNsefConstructor",
"externNsefConstructor2",
"a.externShared"));
}
}
public void testAnnotationInExternStubs1() throws Exception {
checkMarkedCalls("o.propWithStubBefore('a');",
ImmutableList.<String>of("o.propWithStubBefore"));
}
public void testAnnotationInExternStubs1b() throws Exception {
checkMarkedCalls("o.propWithStubBeforeWithJSDoc('a');",
ImmutableList.<String>of("o.propWithStubBeforeWithJSDoc"));
}
public void testAnnotationInExternStubs2() throws Exception {
checkMarkedCalls("o.propWithStubAfter('a');",
ImmutableList.<String>of("o.propWithStubAfter"));
}
public void testAnnotationInExternStubs2b() throws Exception {
checkMarkedCalls("o.propWithStubAfter('a');",
ImmutableList.<String>of("o.propWithStubAfter"));
}
public void testAnnotationInExternStubs3() throws Exception {
checkMarkedCalls("propWithAnnotatedStubAfter('a');",
ImmutableList.<String>of());
}
public void testAnnotationInExternStubs4() throws Exception {
// An externs definition with a stub that differs from the declaration.
// Verify our assumption is valid about this.
String externs =
"/**@constructor*/function externObj5(){}\n" +
"externObj5.prototype.propWithAnnotatedStubAfter = function(s) {};\n" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @nosideeffects\n" +
" */\n" +
"externObj5.prototype.propWithAnnotatedStubAfter;\n";
List<String> expected = ImmutableList.<String>of();
testSame(externs,
"o.prototype.propWithAnnotatedStubAfter",
TypeValidator.DUP_VAR_DECLARATION, false);
assertEquals(expected, noSideEffectCalls);
noSideEffectCalls.clear();
}
public void testAnnotationInExternStubs5() throws Exception {
// An externs definition with a stub that differs from the declaration.
// Verify our assumption is valid about this.
String externs =
"/**@constructor*/function externObj5(){}\n" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" * @nosideeffects\n" +
" */\n" +
"externObj5.prototype.propWithAnnotatedStubAfter = function(s) {};\n" +
"/**\n" +
" * @param {string} s id.\n" +
" * @return {string}\n" +
" */\n" +
"externObj5.prototype.propWithAnnotatedStubAfter;\n";
List<String> expected = ImmutableList.<String>of();
testSame(externs,
"o.prototype.propWithAnnotatedStubAfter",
TypeValidator.DUP_VAR_DECLARATION, false);
assertEquals(expected, noSideEffectCalls);
noSideEffectCalls.clear();
}
public void testNoSideEffectsSimple() throws Exception {
String prefix = "function f(){";
String suffix = "} f()";
List<String> expected = ImmutableList.of("f");
checkMarkedCalls(
prefix + "" + suffix, expected);
checkMarkedCalls(
prefix + "return 1" + suffix, expected);
checkMarkedCalls(
prefix + "return 1 + 2" + suffix, expected);
// local var
checkMarkedCalls(
prefix + "var a = 1; return a" + suffix, expected);
// mutate local var
checkMarkedCalls(
prefix + "var a = 1; a = 2; return a" + suffix, expected);
checkMarkedCalls(
prefix + "var a = 1; a = 2; return a + 1" + suffix, expected);
// read from obj literal
checkMarkedCalls(
prefix + "var a = {foo : 1}; return a.foo" + suffix, expected);
checkMarkedCalls(
prefix + "var a = {foo : 1}; return a.foo + 1" + suffix, expected);
// read from extern
checkMarkedCalls(
prefix + "return externObj" + suffix, expected);
checkMarkedCalls(
"function g(x) { x.foo = 3; }" /* to suppress missing property */ +
prefix + "return externObj.foo" + suffix, expected);
}
public void testResultLocalitySimple() throws Exception {
String prefix = "var g; function f(){";
String suffix = "} f()";
List<String> expected = ImmutableList.of("f");
List<String> notExpected = ImmutableList.of();
// no return
checkLocalityOfMarkedCalls(
prefix + "" + suffix, expected);
// simple return expressions
checkLocalityOfMarkedCalls(
prefix + "return 1" + suffix, expected);
checkLocalityOfMarkedCalls(
prefix + "return 1 + 2" + suffix, expected);
// global result
checkLocalityOfMarkedCalls(
prefix + "return g" + suffix, notExpected);
// multiple returns
checkLocalityOfMarkedCalls(
prefix + "return 1; return 2" + suffix, expected);
checkLocalityOfMarkedCalls(
prefix + "return 1; return g" + suffix, notExpected);
// local var, not yet.
checkLocalityOfMarkedCalls(
prefix + "var a = 1; return a" + suffix, notExpected);
// mutate local var, not yet.
checkLocalityOfMarkedCalls(
prefix + "var a = 1; a = 2; return a" + suffix, notExpected);
checkLocalityOfMarkedCalls(
prefix + "var a = 1; a = 2; return a + 1" + suffix, expected);
// read from obj literal
checkLocalityOfMarkedCalls(
prefix + "return {foo : 1}.foo" + suffix,
notExpected);
checkLocalityOfMarkedCalls(
prefix + "var a = {foo : 1}; return a.foo" + suffix,
notExpected);
// read from extern
checkLocalityOfMarkedCalls(
prefix + "return externObj" + suffix, notExpected);
checkLocalityOfMarkedCalls(
"function inner(x) { x.foo = 3; }" /* to suppress missing property */ +
prefix + "return externObj.foo" + suffix, notExpected);
}
public void testExternCalls() throws Exception {
String prefix = "function f(){";
String suffix = "} f()";
checkMarkedCalls(prefix + "externNsef1()" + suffix,
ImmutableList.of("externNsef1", "f"));
checkMarkedCalls(prefix + "externObj.nsef1()" + suffix,
ImmutableList.of("externObj.nsef1", "f"));
checkMarkedCalls(prefix + "externSef1()" + suffix,
ImmutableList.<String>of());
checkMarkedCalls(prefix + "externObj.sef1()" + suffix,
ImmutableList.<String>of());
}
public void testApply() throws Exception {
checkMarkedCalls("function f() {return 42}" +
"f.apply()",
ImmutableList.of("f.apply"));
}
public void testCall() throws Exception {
checkMarkedCalls("function f() {return 42}" +
"f.call()",
ImmutableList.<String>of("f.call"));
}
public void testInference1() throws Exception {
checkMarkedCalls("function f() {return g()}" +
"function g() {return 42}" +
"f()",
ImmutableList.of("g", "f"));
}
public void testInference2() throws Exception {
checkMarkedCalls("var a = 1;" +
"function f() {g()}" +
"function g() {a=2}" +
"f()",
ImmutableList.<String>of());
}
public void testInference3() throws Exception {
checkMarkedCalls("var f = function() {return g()};" +
"var g = function() {return 42};" +
"f()",
ImmutableList.of("g", "f"));
}
public void testInference4() throws Exception {
checkMarkedCalls("var a = 1;" +
"var f = function() {g()};" +
"var g = function() {a=2};" +
"f()",
ImmutableList.<String>of());
}
public void testInference5() throws Exception {
checkMarkedCalls("var goog = {};" +
"goog.f = function() {return goog.g()};" +
"goog.g = function() {return 42};" +
"goog.f()",
ImmutableList.of("goog.g", "goog.f"));
}
public void testInference6() throws Exception {
checkMarkedCalls("var a = 1;" +
"var goog = {};" +
"goog.f = function() {goog.g()};" +
"goog.g = function() {a=2};" +
"goog.f()",
ImmutableList.<String>of());
}
public void testLocalizedSideEffects1() throws Exception {
// Returning a function that contains a modification of a local
// is not a global side-effect.
checkMarkedCalls("function f() {" +
" var x = {foo : 0}; return function() {x.foo++};" +
"}" +
"f()",
ImmutableList.<String>of("f"));
}
public void testLocalizedSideEffects2() throws Exception {
// Calling a function that contains a modification of a local
// is a global side-effect (the value has escaped).
checkMarkedCalls("function f() {" +
" var x = {foo : 0}; (function() {x.foo++})();" +
"}" +
"f()",
ImmutableList.<String>of());
}
public void testLocalizedSideEffects3() throws Exception {
// A local that might be assigned a global value and whose properties
// are modified must be considered a global side-effect.
checkMarkedCalls("var g = {foo:1}; function f() {var x = g; x.foo++}" +
"f()",
ImmutableList.<String>of());
}
public void testLocalizedSideEffects4() throws Exception {
// An array is an local object, assigning a local array is not a global
// side-effect.
checkMarkedCalls("function f() {var x = []; x[0] = 1;}" +
"f()",
ImmutableList.<String>of("f"));
}
public void testLocalizedSideEffects5() throws Exception {
// Assigning a local alias of a global is a global
// side-effect.
checkMarkedCalls("var g = [];function f() {var x = g; x[0] = 1;}" +
"f()",
ImmutableList.<String>of());
}
public void testLocalizedSideEffects6() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
checkMarkedCalls("function f() {" +
" var x = {}; x.foo = 1; return x;" +
"}" +
"f()",
ImmutableList.<String>of("f"));
}
public void testLocalizedSideEffects7() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
checkMarkedCalls("/** @constructor A */ function A() {};" +
"function f() {" +
" var a = []; a[1] = 1; return a;" +
"}" +
"f()",
ImmutableList.<String>of("f"));
}
public void testLocalizedSideEffects8() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
// TODO(johnlenz): Not yet. Propagate local object information.
checkMarkedCalls("/** @constructor A */ function A() {};" +
"function f() {" +
" var a = new A; a.foo = 1; return a;" +
"}" +
"f()",
ImmutableList.<String>of("A"));
}
public void testLocalizedSideEffects9() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
// TODO(johnlenz): Not yet. Propagate local object information.
checkMarkedCalls("/** @constructor A */ function A() {this.x = 1};" +
"function f() {" +
" var a = new A; a.foo = 1; return a;" +
"}" +
"f()",
ImmutableList.<String>of("A"));
}
public void testLocalizedSideEffects10() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
checkMarkedCalls("/** @constructor A */ function A() {};" +
"A.prototype.g = function() {this.x = 1};" +
"function f() {" +
" var a = new A; a.g(); return a;" +
"}" +
"f()",
ImmutableList.<String>of("A"));
}
public void testLocalizedSideEffects11() throws Exception {
// Calling a function of a local object that taints this.
checkMarkedCalls(
"/** @constructor */ function A() {}" +
"A.prototype.update = function() { this.x = 1; };" +
"/** @constructor */ function B() { " +
" this.a_ = new A();" +
"}" +
"B.prototype.updateA = function() {" +
" var b = this.a_;" +
" b.update();" +
"};" +
"var x = new B();" +
"x.updateA();",
ImmutableList.of("A", "B"));
}
public void testUnaryOperators1() throws Exception {
checkMarkedCalls("function f() {var x = 1; x++}" +
"f()",
ImmutableList.of("f"));
}
public void testUnaryOperators2() throws Exception {
checkMarkedCalls("var x = 1;" +
"function f() {x++}" +
"f()",
ImmutableList.<String>of());
}
public void testUnaryOperators3() throws Exception {
checkMarkedCalls("function f() {var x = {foo : 0}; x.foo++}" +
"f()",
ImmutableList.<String>of("f"));
}
public void testUnaryOperators4() throws Exception {
checkMarkedCalls("var x = {foo : 0};" +
"function f() {x.foo++}" +
"f()",
ImmutableList.<String>of());
}
public void testUnaryOperators5() throws Exception {
checkMarkedCalls("function f(x) {x.foo++}" +
"f({foo : 0})",
ImmutableList.<String>of());
}
public void testDeleteOperator1() throws Exception {
checkMarkedCalls("var x = {};" +
"function f() {delete x}" +
"f()",
ImmutableList.<String>of());
}
public void testDeleteOperator2() throws Exception {
checkMarkedCalls("function f() {var x = {}; delete x}" +
"f()",
ImmutableList.of("f"));
}
public void testOrOperator1() throws Exception {
checkMarkedCalls("var f = externNsef1 || externNsef2;\n" +
"f()",
ImmutableList.<String>of());
}
public void testOrOperator2() throws Exception {
checkMarkedCalls("var f = function(){} || externNsef2;\n" +
"f()",
ImmutableList.<String>of());
}
public void testOrOperator3() throws Exception {
checkMarkedCalls("var f = externNsef2 || function(){};\n" +
"f()",
ImmutableList.<String>of());
}
public void testOrOperators4() throws Exception {
checkMarkedCalls("var f = function(){} || function(){};\n" +
"f()",
ImmutableList.<String>of());
}
public void testAndOperator1() throws Exception {
checkMarkedCalls("var f = externNsef1 && externNsef2;\n" +
"f()",
ImmutableList.<String>of());
}
public void testAndOperator2() throws Exception {
checkMarkedCalls("var f = function(){} && externNsef2;\n" +
"f()",
ImmutableList.<String>of());
}
public void testAndOperator3() throws Exception {
checkMarkedCalls("var f = externNsef2 && function(){};\n" +
"f()",
ImmutableList.<String>of());
}
public void testAndOperators4() throws Exception {
checkMarkedCalls("var f = function(){} && function(){};\n" +
"f()",
ImmutableList.<String>of());
}
public void testHookOperator1() throws Exception {
checkMarkedCalls("var f = true ? externNsef1 : externNsef2;\n" +
"f()",
ImmutableList.<String>of());
}
public void testHookOperator2() throws Exception {
checkMarkedCalls("var f = true ? function(){} : externNsef2;\n" +
"f()",
ImmutableList.<String>of());
}
public void testHookOperator3() throws Exception {
checkMarkedCalls("var f = true ? externNsef2 : function(){};\n" +
"f()",
ImmutableList.<String>of());
}
public void testHookOperators4() throws Exception {
checkMarkedCalls("var f = true ? function(){} : function(){};\n" +
"f()",
ImmutableList.<String>of());
}
public void testThrow1() throws Exception {
checkMarkedCalls("function f(){throw Error()};\n" +
"f()",
ImmutableList.<String>of("Error"));
}
public void testThrow2() throws Exception {
checkMarkedCalls("/**@constructor*/function A(){throw Error()};\n" +
"function f(){return new A()}\n" +
"f()",
ImmutableList.<String>of("Error"));
}
public void testAssignmentOverride() throws Exception {
checkMarkedCalls("/**@constructor*/function A(){}\n" +
"A.prototype.foo = function(){};\n" +
"var a = new A;\n" +
"a.foo();\n",
ImmutableList.<String>of("A", "a.foo"));
checkMarkedCalls("/**@constructor*/function A(){}\n" +
"A.prototype.foo = function(){};\n" +
"var x = 1\n" +
"function f(){x = 10}\n" +
"var a = new A;\n" +
"a.foo = f;\n" +
"a.foo();\n",
ImmutableList.<String>of("A"));
}
public void testInheritance1() throws Exception {
String source =
CompilerTypeTestCase.CLOSURE_DEFS +
"/**@constructor*/function I(){}\n" +
"I.prototype.foo = function(){};\n" +
"I.prototype.bar = function(){this.foo()};\n" +
"/**@constructor\n@extends {I}*/function A(){};\n" +
"goog.inherits(A, I)\n;" +
"/** @override */A.prototype.foo = function(){var data=24};\n" +
"var i = new I();i.foo();i.bar();\n" +
"var a = new A();a.foo();a.bar();";
checkMarkedCalls(source,
ImmutableList.of("this.foo", "goog.inherits",
"I", "i.foo", "i.bar",
"A", "a.foo", "a.bar"));
}
public void testInheritance2() throws Exception {
String source =
CompilerTypeTestCase.CLOSURE_DEFS +
"/**@constructor*/function I(){}\n" +
"I.prototype.foo = function(){};\n" +
"I.prototype.bar = function(){this.foo()};\n" +
"/**@constructor\n@extends {I}*/function A(){};\n" +
"goog.inherits(A, I)\n;" +
"/** @override */A.prototype.foo = function(){this.data=24};\n" +
"var i = new I();i.foo();i.bar();\n" +
"var a = new A();a.foo();a.bar();";
checkMarkedCalls(source, ImmutableList.of("goog.inherits", "I", "A"));
}
public void testCallBeforeDefinition() throws Exception {
checkMarkedCalls("f(); function f(){}",
ImmutableList.of("f"));
checkMarkedCalls("var a = {}; a.f(); a.f = function (){}",
ImmutableList.of("a.f"));
}
public void testConstructorThatModifiesThis1() throws Exception {
String source = "/**@constructor*/function A(){this.foo = 1}\n" +
"function f() {return new A}" +
"f()";
checkMarkedCalls(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesThis2() throws Exception {
String source = "/**@constructor*/function A(){this.foo()}\n" +
"A.prototype.foo = function(){this.data=24};\n" +
"function f() {return new A}" +
"f()";
checkMarkedCalls(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesThis3() throws Exception {
// test chained
String source = "/**@constructor*/function A(){this.foo()}\n" +
"A.prototype.foo = function(){this.bar()};\n" +
"A.prototype.bar = function(){this.data=24};\n" +
"function f() {return new A}" +
"f()";
checkMarkedCalls(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesThis4() throws Exception {
// test ".call" notation.
String source = "/**@constructor*/function A(){foo.call(this)}\n" +
"function foo(){this.data=24};\n" +
"function f() {return new A}" +
"f()";
checkMarkedCalls(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesGlobal1() throws Exception {
String source = "var b = 0;" +
"/**@constructor*/function A(){b=1};\n" +
"function f() {return new A}" +
"f()";
checkMarkedCalls(source, ImmutableList.<String>of());
}
public void testConstructorThatModifiesGlobal2() throws Exception {
String source = "var b = 0;" +
"/**@constructor*/function A(){this.foo()}\n" +
"A.prototype.foo = function(){b=1};\n" +
"function f() {return new A}" +
"f()";
checkMarkedCalls(source, ImmutableList.<String>of());
}
public void testCallFunctionThatModifiesThis() throws Exception {
String source = "/**@constructor*/function A(){}\n" +
"A.prototype.foo = function(){this.data=24};\n" +
"function f(){var a = new A; return a}\n" +
"function g(){var a = new A; a.foo(); return a}\n" +
"f(); g()";
checkMarkedCalls(source, ImmutableList.<String>of("A", "A", "f"));
}
public void testCallFunctionFOrG() throws Exception {
String source = "function f(){}\n" +
"function g(){}\n" +
"function h(){ (f || g)() }\n" +
"h()";
checkMarkedCalls(source, ImmutableList.<String>of("(f || g)", "h"));
}
public void testCallFunctionFOrGViaHook() throws Exception {
String source = "function f(){}\n" +
"function g(){}\n" +
"function h(){ (false ? f : g)() }\n" +
"h()";
checkMarkedCalls(source, ImmutableList.<String>of("(f : g)", "h"));
}
public void testCallFunctionForGorH() throws Exception {
String source = "function f(){}\n" +
"function g(){}\n" +
"function h(){}\n" +
"function i(){ (false ? f : (g || h))() }\n" +
"i()";
checkMarkedCalls(source, ImmutableList.<String>of("(f : (g || h))", "i"));
}
public void testCallFunctionFOrGWithSideEffects() throws Exception {
String source = "var x = 0;\n" +
"function f(){x = 10}\n" +
"function g(){}\n" +
"function h(){ (f || g)() }\n" +
"function i(){ (g || f)() }\n" +
"function j(){ (f || f)() }\n" +
"function k(){ (g || g)() }\n" +
"h(); i(); j(); k()";
checkMarkedCalls(source, ImmutableList.<String>of("(g || g)", "k"));
}
public void testCallFunctionFOrGViaHookWithSideEffects() throws Exception {
String source = "var x = 0;\n" +
"function f(){x = 10}\n" +
"function g(){}\n" +
"function h(){ (false ? f : g)() }\n" +
"function i(){ (false ? g : f)() }\n" +
"function j(){ (false ? f : f)() }\n" +
"function k(){ (false ? g : g)() }\n" +
"h(); i(); j(); k()";
checkMarkedCalls(source, ImmutableList.<String>of("(g : g)", "k"));
}
public void testCallRegExpWithSideEffects() throws Exception {
String source = "var x = 0;\n" +
"function k(){(/a/).exec('')}\n" +
"k()";
regExpHaveSideEffects = true;
checkMarkedCalls(source, ImmutableList.<String>of());
regExpHaveSideEffects = false;
checkMarkedCalls(source, ImmutableList.<String>of(
"REGEXP STRING exec", "k"));
}
public void testAnonymousFunction1() throws Exception {
String source = "(function (){})();";
checkMarkedCalls(source, ImmutableList.<String>of(
"FUNCTION"));
}
public void testAnonymousFunction2() throws Exception {
String source = "(Error || function (){})();";
checkMarkedCalls(source, ImmutableList.<String>of(
"(Error || FUNCTION)"));
}
public void testAnonymousFunction3() throws Exception {
String source = "var a = (Error || function (){})();";
checkMarkedCalls(source, ImmutableList.<String>of(
"(Error || FUNCTION)"));
}
// Indirect complex function definitions aren't yet supported.
public void testAnonymousFunction4() throws Exception {
String source = "var a = (Error || function (){});" +
"a();";
// This should be "(Error || FUNCTION)" but isn't.
checkMarkedCalls(source, ImmutableList.<String>of());
}
public void testInvalidAnnotation1() throws Exception {
test("/** @nosideeffects */ function foo() {}",
null, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
public void testInvalidAnnotation2() throws Exception {
test("var f = /** @nosideeffects */ function() {}",
null, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
public void testInvalidAnnotation3() throws Exception {
test("/** @nosideeffects */ var f = function() {}",
null, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
public void testInvalidAnnotation4() throws Exception {
test("var f = function() {};" +
"/** @nosideeffects */ f.x = function() {}",
null, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
public void testInvalidAnnotation5() throws Exception {
test("var f = function() {};" +
"f.x = /** @nosideeffects */ function() {}",
null, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
void checkMarkedCalls(String source, List<String> expected) {
testSame(source);
assertEquals(expected, noSideEffectCalls);
noSideEffectCalls.clear();
}
void checkLocalityOfMarkedCalls(String source, List<String> expected) {
testSame(source);
assertEquals(expected, localResultCalls);
localResultCalls.clear();
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new NoSideEffectCallEnumerator(compiler);
}
/**
* Run PureFunctionIdentifier, then gather a list of calls that are
* marked as having no side effects.
*/
private class NoSideEffectCallEnumerator
extends AbstractPostOrderCallback implements CompilerPass {
private final Compiler compiler;
NoSideEffectCallEnumerator(Compiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
compiler.setHasRegExpGlobalReferences(regExpHaveSideEffects);
SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler);
defFinder.process(externs, root);
PureFunctionIdentifier passUnderTest =
new PureFunctionIdentifier(compiler, defFinder);
passUnderTest.process(externs, root);
// Ensure that debug report computation works.
String debugReport = passUnderTest.getDebugReport();
NodeTraversal.traverse(compiler, externs, this);
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isNew()) {
if (!NodeUtil.constructorCallHasSideEffects(n)) {
noSideEffectCalls.add(generateNameString(n.getFirstChild()));
}
} else if (n.isCall()) {
if (!NodeUtil.functionCallHasSideEffects(n)) {
noSideEffectCalls.add(generateNameString(n.getFirstChild()));
}
if (NodeUtil.callHasLocalResult(n)) {
localResultCalls.add(generateNameString(n.getFirstChild()));
}
}
}
private String generateNameString(Node node) {
if (node.isOr()) {
return "(" + generateNameString(node.getFirstChild()) +
" || " + generateNameString(node.getLastChild()) + ")";
} else if (node.isHook()) {
return "(" + generateNameString(node.getFirstChild().getNext()) +
" : " + generateNameString(node.getLastChild()) + ")";
} else {
String result = node.getQualifiedName();
if (result == null) {
if (node.isFunction()) {
result = node.toString(false, false, false).trim();
} else {
result = node.getFirstChild().toString(false, false, false);
result += " " + node.getLastChild().toString(false, false, false);
}
}
return result;
}
}
}
}