/*
* 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 static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.List;
/**
* Tests for {@link PureFunctionIdentifier}
*
* @author johnlenz@google.com (John Lenz)
*/
public final class PureFunctionIdentifierTest extends TypeICompilerTestCase {
List<String> noSideEffectCalls;
List<String> localResultCalls;
boolean regExpHaveSideEffects = true;
private static final String TEST_EXTERNS =
CompilerTypeTestCase.DEFAULT_EXTERNS + LINE_JOINER.join(
"var window; window.setTimeout;",
"/**@nosideeffects*/ function externSENone(){}",
"/**@modifies{this}*/ function externSEThis(){}",
"/**@constructor",
" * @modifies{this}*/",
"function externObjSEThis(){}",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @modifies{this}",
" */",
"externObjSEThis.prototype.externObjSEThisMethod = function(s) {};",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @modifies{arguments}",
" */",
"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@nosideeffects*/function externNsefConstructor(){}",
"externNsefConstructor.prototype.sefFnOfNsefObj = function(){};",
"externNsefConstructor.prototype.nsefFnOfNsefObj = ",
" /**@nosideeffects*/function(){};",
"externNsefConstructor.prototype.externShared = ",
" /**@nosideeffects*/function(){};",
"/**@constructor @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;",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @nosideeffects",
" */",
"externObj3.prototype.propWithStubBefore = function(s) {};",
// useless JsDoc
"/**",
" * @see {foo}",
" */",
"externObj3.prototype.propWithStubBeforeWithJSDoc;",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @nosideeffects",
" */",
"externObj3.prototype.propWithStubBeforeWithJSDoc = function(s) {};",
// An externs definition with a stub after.
"/**@constructor*/function externObj4(){}",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @nosideeffects",
" */",
"externObj4.prototype.propWithStubAfter = function(s) {};",
"externObj4.prototype.propWithStubAfter;",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @nosideeffects",
" */",
"externObj4.prototype.propWithStubAfterWithJSDoc = function(s) {};",
// useless JsDoc
"/**",
" * @see {foo}",
" */",
"externObj4.prototype.propWithStubAfterWithJSDoc;",
"var goog = {};",
"goog.reflect = {};",
"goog.reflect.cache = function(a, b, c, opt_d) {};",
"/** @nosideeffects */",
"externObj.prototype.duplicateExternFunc = function() {};",
"externObj2.prototype.duplicateExternFunc = function() {};",
"externObj.prototype['weirdDefinition'] = function() {};"
);
public PureFunctionIdentifierTest() {
super(TEST_EXTERNS);
}
@Override
protected int getNumRepetitions() {
// run pass once.
return 1;
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
regExpHaveSideEffects = true;
}
/**
* 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) {
noSideEffectCalls = new ArrayList<>();
localResultCalls = new ArrayList<>();
compiler.setHasRegExpGlobalReferences(regExpHaveSideEffects);
compiler.getOptions().setUseTypesForLocalOptimization(true);
NameBasedDefinitionProvider defFinder = new NameBasedDefinitionProvider(compiler, true);
defFinder.process(externs, root);
PureFunctionIdentifier pureFunctionIdentifier =
new PureFunctionIdentifier(compiler, defFinder);
pureFunctionIdentifier.process(externs, root);
// Ensure that debug report computation doesn't crash.
pureFunctionIdentifier.getDebugReport();
NodeTraversal.traverseEs6(compiler, externs, this);
NodeTraversal.traverseEs6(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, compiler)) {
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.getSecondChild())
+ " : " + 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;
}
}
}
public void testIssue303() throws Exception {
String source = LINE_JOINER.join(
"/** @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/');"
);
assertNoPureCalls(source);
}
public void testIssue303b() throws Exception {
String source = LINE_JOINER.join(
"/** @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;"
);
assertNoPureCalls(source);
}
public void testAnnotationInExterns_new1() throws Exception {
assertPureCallsMarked("externSENone()", ImmutableList.of("externSENone"));
}
public void testAnnotationInExterns_new2() throws Exception {
assertNoPureCalls("externSEThis()");
}
public void testAnnotationInExterns_new3() throws Exception {
assertPureCallsMarked("new externObjSEThis()", ImmutableList.of("externObjSEThis"));
}
public void testAnnotationInExterns_new4() throws Exception {
// The entire expression containing "externObjSEThisMethod" is considered
// side-effect free in this context.
assertPureCallsMarked("new externObjSEThis().externObjSEThisMethod('')",
ImmutableList.of("externObjSEThis", "NEW STRING externObjSEThisMethod"));
}
public void testAnnotationInExterns_new5() throws Exception {
assertPureCallsMarked(
"function f() { new externObjSEThis() };" +
"f();",
ImmutableList.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.
String source = LINE_JOINER.join(
"function f() {",
" new externObjSEThis().externObjSEThisMethod('') ",
"};",
"f();"
);
assertPureCallsMarked(
source,
ImmutableList.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.
String source = LINE_JOINER.join(
"function f() {",
" var x = new externObjSEThis(); ",
" x.externObjSEThisMethod('') ",
"};",
"f();"
);
assertPureCallsMarked(source, ImmutableList.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.
String source = LINE_JOINER.join(
"function f(x) {",
" x.externObjSEThisMethod('') ",
"};",
"f(new externObjSEThis());"
);
assertPureCallsMarked(source, ImmutableList.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.
String source = LINE_JOINER.join(
"function f(x) {",
" x = new externObjSEThis(); ",
" x.externObjSEThisMethod('') ",
"};",
"f(g);"
);
assertPureCallsMarked(source, ImmutableList.of("externObjSEThis"));
}
public void testAnnotationInExterns_new10() throws Exception {
String source = LINE_JOINER.join(
"function f() {",
" new externObjSEThis().externObjSEThisMethod2('') ",
"};",
"f();"
);
assertPureCallsMarked(source,
ImmutableList.of("externObjSEThis", "NEW STRING externObjSEThisMethod2", "f"));
}
public void testAnnotationInExterns1() throws Exception {
assertNoPureCalls("externSef1()");
}
public void testAnnotationInExterns2() throws Exception {
assertNoPureCalls("externSef2()");
}
public void testAnnotationInExterns3() throws Exception {
assertPureCallsMarked("externNsef1()", ImmutableList.of("externNsef1"));
}
public void testAnnotationInExterns4() throws Exception {
assertPureCallsMarked("externNsef2()", ImmutableList.of("externNsef2"));
}
public void testAnnotationInExterns5() throws Exception {
assertPureCallsMarked("externNsef3()", ImmutableList.of("externNsef3"));
}
public void testNamespaceAnnotationInExterns1() throws Exception {
assertNoPureCalls("externObj.sef1()");
}
public void testNamespaceAnnotationInExterns2() throws Exception {
assertPureCallsMarked("externObj.nsef1()", ImmutableList.of("externObj.nsef1"));
}
public void testNamespaceAnnotationInExterns3() throws Exception {
assertPureCallsMarked("externObj.nsef2()", ImmutableList.of("externObj.nsef2"));
}
public void testNamespaceAnnotationInExterns4() throws Exception {
assertNoPureCalls("externObj.partialFn()");
}
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.
assertPureCallsMarked(
templateSrc.replace("<fnName>", "notPartialFn"), ImmutableList.of("o.notPartialFn"));
assertNoPureCalls(templateSrc.replace("<fnName>", "partialFn"));
}
public void testNamespaceAnnotationInExterns6() throws Exception {
assertNoPureCalls("externObj.partialSharedFn()");
}
public void testConstructorAnnotationInExterns1() throws Exception {
assertNoPureCalls("new externSefConstructor()");
}
public void testConstructorAnnotationInExterns2() throws Exception {
String source = LINE_JOINER.join(
"var a = new externSefConstructor();",
"a.sefFnOfSefObj()");
assertNoPureCalls(source);
}
public void testConstructorAnnotationInExterns3() throws Exception {
String source = LINE_JOINER.join(
"var a = new externSefConstructor();",
"a.nsefFnOfSefObj()");
assertPureCallsMarked(source, ImmutableList.of("a.nsefFnOfSefObj"));
}
public void testConstructorAnnotationInExterns4() throws Exception {
String source = LINE_JOINER.join(
"var a = new externSefConstructor();",
"a.externShared()");
assertNoPureCalls(source);
}
public void testConstructorAnnotationInExterns5() throws Exception {
assertPureCallsMarked("new externNsefConstructor()", ImmutableList.of("externNsefConstructor"));
}
public void testConstructorAnnotationInExterns6() throws Exception {
String source = LINE_JOINER.join(
"var a = new externNsefConstructor();",
"a.sefFnOfNsefObj()");
assertPureCallsMarked(source, ImmutableList.of("externNsefConstructor"));
}
public void testConstructorAnnotationInExterns7() throws Exception {
String source = LINE_JOINER.join(
"var a = new externNsefConstructor();",
"a.nsefFnOfNsefObj()");
assertPureCallsMarked(source, ImmutableList.of("externNsefConstructor", "a.nsefFnOfNsefObj"));
}
public void testConstructorAnnotationInExterns8() throws Exception {
String source = LINE_JOINER.join(
"var a = new externNsefConstructor();",
"a.externShared()");
assertPureCallsMarked(source, ImmutableList.of("externNsefConstructor"));
}
public void testSharedFunctionName1() throws Exception {
String source = LINE_JOINER.join(
"if (true) {",
" a = new externNsefConstructor()",
"} else {",
" a = new externSefConstructor()",
"}",
"a.externShared()");
assertPureCallsMarked(source, ImmutableList.of("externNsefConstructor"));
}
public void testSharedFunctionName2() throws Exception {
// Implementation for both externNsefConstructor and externNsefConstructor2
// have no side effects.
boolean broken = true;
if (broken) {
assertPureCallsMarked("var a; " +
"if (true) {" +
" a = new externNsefConstructor()" +
"} else {" +
" a = new externNsefConstructor2()" +
"}" +
"a.externShared()",
ImmutableList.of("externNsefConstructor",
"externNsefConstructor2"));
} else {
assertPureCallsMarked("var a; " +
"if (true) {" +
" a = new externNsefConstructor()" +
"} else {" +
" a = new externNsefConstructor2()" +
"}" +
"a.externShared()",
ImmutableList.of("externNsefConstructor",
"externNsefConstructor2",
"a.externShared"));
}
}
public void testAnnotationInExternStubs1() throws Exception {
assertPureCallsMarked("o.propWithStubBefore('a');",
ImmutableList.of("o.propWithStubBefore"));
}
public void testAnnotationInExternStubs1b() throws Exception {
assertPureCallsMarked("o.propWithStubBeforeWithJSDoc('a');",
ImmutableList.of("o.propWithStubBeforeWithJSDoc"));
}
public void testAnnotationInExternStubs2() throws Exception {
assertPureCallsMarked("o.propWithStubAfter('a');",
ImmutableList.of("o.propWithStubAfter"));
}
public void testAnnotationInExternStubs3() throws Exception {
assertNoPureCalls("propWithAnnotatedStubAfter('a');");
}
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 = LINE_JOINER.join(
"/**@constructor*/function externObj5(){}",
"externObj5.prototype.propWithAnnotatedStubAfter = function(s) {};",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @nosideeffects",
" */",
"externObj5.prototype.propWithAnnotatedStubAfter;");
this.mode = TypeInferenceMode.OTI_ONLY;
testSame(
externs,
"o.prototype.propWithAnnotatedStubAfter",
TypeValidator.DUP_VAR_DECLARATION_TYPE_MISMATCH, false);
assertThat(noSideEffectCalls).isEmpty();
this.mode = TypeInferenceMode.NTI_ONLY;
testSame(
TEST_EXTERNS + externs,
"o.prototype.propWithAnnotatedStubAfter",
GlobalTypeInfo.REDECLARED_PROPERTY, false);
assertThat(noSideEffectCalls).isEmpty();
}
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 = LINE_JOINER.join(
"/**@constructor*/function externObj5(){}",
"/**",
" * @param {string} s id.",
" * @return {string}",
" * @nosideeffects",
" */",
"externObj5.prototype.propWithAnnotatedStubAfter = function(s) {};",
"/**",
" * @param {string} s id.",
" * @return {string}",
" */",
"externObj5.prototype.propWithAnnotatedStubAfter;");
this.mode = TypeInferenceMode.OTI_ONLY;
testSame(externs,
"o.prototype.propWithAnnotatedStubAfter",
TypeValidator.DUP_VAR_DECLARATION, false);
assertThat(noSideEffectCalls).isEmpty();
this.mode = TypeInferenceMode.NTI_ONLY;
testSame(TEST_EXTERNS + externs,
"o.prototype.propWithAnnotatedStubAfter",
GlobalTypeInfo.REDECLARED_PROPERTY, false);
assertThat(noSideEffectCalls).isEmpty();
}
public void testNoSideEffectsSimple() throws Exception {
String prefix = "function f(){";
String suffix = "} f()";
List<String> expected = ImmutableList.of("f");
assertPureCallsMarked(
prefix + "" + suffix, expected);
assertPureCallsMarked(
prefix + "return 1" + suffix, expected);
assertPureCallsMarked(
prefix + "return 1 + 2" + suffix, expected);
// local var
assertPureCallsMarked(
prefix + "var a = 1; return a" + suffix, expected);
// mutate local var
assertPureCallsMarked(
prefix + "var a = 1; a = 2; return a" + suffix, expected);
assertPureCallsMarked(
prefix + "var a = 1; a = 2; return a + 1" + suffix, expected);
// read from obj literal
assertPureCallsMarked(
prefix + "var a = {foo : 1}; return a.foo" + suffix, expected);
assertPureCallsMarked(
prefix + "var a = {foo : 1}; return a.foo + 1" + suffix, expected);
// read from extern
assertPureCallsMarked(
prefix + "return externObj" + suffix, expected);
assertPureCallsMarked(
"function g(x) { x.foo = 3; }" /* to suppress missing property */ +
prefix + "return externObj.foo" + suffix, expected);
}
public void testNoSideEffectsSimple2() throws Exception {
regExpHaveSideEffects = false;
String source = LINE_JOINER.join(
"function f() {",
" return ''.replace(/xyz/g, '');",
"}",
"f()");
assertPureCallsMarked(source, ImmutableList.of("STRING STRING replace", "f"));
}
public void testNoSideEffectsSimple3() throws Exception {
regExpHaveSideEffects = false;
String source = LINE_JOINER.join(
"function f(/** string */ str) {",
" return str.replace(/xyz/g, '');",
"}",
"f('')");
assertPureCallsMarked(source, ImmutableList.of("str.replace", "f"));
}
public void testResultLocalitySimple() throws Exception {
String prefix = "var g; function f(){";
String suffix = "} f()";
final List<String> fReturnsLocal = ImmutableList.of("f");
final List<String> fReturnsNonLocal = ImmutableList.<String>of();
// no return
checkLocalityOfMarkedCalls(prefix + "" + suffix, fReturnsLocal);
// simple return expressions
checkLocalityOfMarkedCalls(prefix + "return 1" + suffix, fReturnsLocal);
checkLocalityOfMarkedCalls(prefix + "return 1 + 2" + suffix, fReturnsLocal);
// global result
checkLocalityOfMarkedCalls(prefix + "return g" + suffix, fReturnsNonLocal);
// multiple returns
checkLocalityOfMarkedCalls(prefix + "return 1; return 2" + suffix, fReturnsLocal);
checkLocalityOfMarkedCalls(prefix + "return 1; return g" + suffix, fReturnsNonLocal);
// local var, not yet. Note we do not handle locals properly here.
checkLocalityOfMarkedCalls(prefix + "var a = 1; return a" + suffix, fReturnsNonLocal);
// mutate local var, not yet. Note we do not handle locals properly here.
checkLocalityOfMarkedCalls(prefix + "var a = 1; a = 2; return a" + suffix, fReturnsNonLocal);
checkLocalityOfMarkedCalls(prefix + "var a = 1; a = 2; return a + 1" + suffix, fReturnsLocal);
// read from obj literal
checkLocalityOfMarkedCalls(prefix + "return {foo : 1}.foo" + suffix, fReturnsNonLocal);
checkLocalityOfMarkedCalls(
prefix + "var a = {foo : 1}; return a.foo" + suffix, fReturnsNonLocal);
// read from extern
checkLocalityOfMarkedCalls(prefix + "return externObj" + suffix, ImmutableList.<String>of());
checkLocalityOfMarkedCalls(
"function inner(x) { x.foo = 3; }" /* to suppress missing property */ +
prefix + "return externObj.foo" + suffix, ImmutableList.<String>of());
}
public void testReturnLocalityTaintObjectLiteralWithGlobal() {
// return empty object literal. This is completely local
String source = LINE_JOINER.join(
"function f() { return {} }",
"f();"
);
checkLocalityOfMarkedCalls(source, ImmutableList.of("f"));
// return obj literal with global taint.
source = LINE_JOINER.join(
"var global = new Object();",
"function f() { return {'asdf': global} }",
"f();");
checkLocalityOfMarkedCalls(source, ImmutableList.<String>of());
}
public void testReturnLocalityTaintArrayLiteralWithGlobal() {
String source =
LINE_JOINER.join(
"function f() { return []; }",
"f();",
"function g() { return [1, {}]; }",
"g();");
checkLocalityOfMarkedCalls(source, ImmutableList.of("f", "g"));
// return obj literal with global taint.
source =
LINE_JOINER.join(
"var global = new Object();",
"function f() { return [2 ,global]; }",
"f();");
checkLocalityOfMarkedCalls(source, ImmutableList.<String>of());
}
public void testReturnLocalityMultipleDefinitionsSameName() {
String source = LINE_JOINER.join(
"var global = new Object();",
"A.func = function() {return global}", // return global (taintsReturn)
"B.func = function() {return 1; }", // returns local
"C.func();");
checkLocalityOfMarkedCalls(source, ImmutableList.<String>of());
}
public void testExternCalls() throws Exception {
String prefix = "function f(){";
String suffix = "} f()";
assertPureCallsMarked(prefix + "externNsef1()" + suffix,
ImmutableList.of("externNsef1", "f"));
assertPureCallsMarked(prefix + "externObj.nsef1()" + suffix,
ImmutableList.of("externObj.nsef1", "f"));
assertNoPureCalls(prefix + "externSef1()" + suffix);
assertNoPureCalls(prefix + "externObj.sef1()" + suffix);
}
public void testApply() throws Exception {
String source = LINE_JOINER.join(
"function f() {return 42}",
"f.apply(null)");
assertPureCallsMarked(source, ImmutableList.of("f.apply"));
}
public void testCall() throws Exception {
String source = LINE_JOINER.join(
"function f() {return 42}",
"f.call(null)");
assertPureCallsMarked(source, ImmutableList.of("f.call"));
}
public void testApplyToUnknownDefinition() throws Exception {
String source = LINE_JOINER.join(
"var dict = {'func': function() {}};",
"function f() { var s = dict['func'];}",
"f.apply(null)"
);
assertPureCallsMarked(source, ImmutableList.of("f.apply"));
// Not marked because the definition cannot be found so unknown side effects.
source = LINE_JOINER.join(
"var dict = {'func': function() {}};",
"function f() { var s = dict['func'].apply();}",
"f.apply(null)"
);
assertNoPureCalls(source);
// Not marked becuase the definition cannot be found so unknown side effects.
source = LINE_JOINER.join(
"var pure = function() {};",
"var dict = {'func': function() {}};",
"function f() { var s = (dict['func'] || pure)();}",
"f()"
);
assertNoPureCalls(source);
// Not marked becuase the definition cannot be found so unknown side effects.
source = LINE_JOINER.join(
"var pure = function() {};"
, "var dict = {'func': function() {}};"
, "function f() { var s = (condition ? dict['func'] : pure)();}"
, "f()"
);
assertNoPureCalls(source);
}
public void testInference1() throws Exception {
String source = LINE_JOINER.join(
"function f() {return g()}",
"function g() {return 42}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("g", "f"));
}
public void testInference2() throws Exception {
String source = LINE_JOINER.join(
"var a = 1;",
"function f() {g()}",
"function g() {a=2}",
"f()"
);
assertNoPureCalls(source);
}
public void testInference3() throws Exception {
String source = LINE_JOINER.join(
"var f = function() {return g()};",
"var g = function() {return 42};",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("g", "f"));
}
public void testInference4() throws Exception {
String source = LINE_JOINER.join(
"var a = 1;" +
"var f = function() {g()};",
"var g = function() {a=2};",
"f()"
);
assertNoPureCalls(source);
}
public void testInference5() throws Exception {
String source = LINE_JOINER.join(
"goog.f = function() {return goog.g()};",
"goog.g = function() {return 42};",
"goog.f()"
);
assertPureCallsMarked(source, ImmutableList.of("goog.g", "goog.f"));
}
public void testInference6() throws Exception {
String source = LINE_JOINER.join(
"var a = 1;",
"goog.f = function() {goog.g()};",
"goog.g = function() {a=2};",
"goog.f()"
);
assertNoPureCalls(source);
}
public void testLocalizedSideEffects1() throws Exception {
// Returning a function that contains a modification of a local
// is not a global side-effect.
String source = LINE_JOINER.join(
"function f() {",
" var x = {foo : 0}; return function() {x.foo++};",
"}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.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).
String source = LINE_JOINER.join(
"function f() {",
" var x = {foo : 0}; (function() {x.foo++})();",
"}",
"f()"
);
assertNoPureCalls(source);
}
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.
String source = LINE_JOINER.join(
"var g = {foo:1};",
"function f() {var x = g; x.foo++};",
"f();"
);
assertNoPureCalls(source);
}
public void testLocalizedSideEffects4() throws Exception {
// An array is an local object, assigning a local array is not a global
// side-effect.
String source = LINE_JOINER.join(
"function f() {var x = []; x[0] = 1;}",
"f()");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testLocalizedSideEffects5() throws Exception {
// Assigning a local alias of a global is a global
// side-effect.
String source = LINE_JOINER.join(
"var g = [];",
"function f() {var x = g; x[0] = 1;};",
"f()"
);
assertNoPureCalls(source);
}
public void testLocalizedSideEffects6() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
String source = LINE_JOINER.join(
"function f() {",
" var x = {}; x.foo = 1; return x;",
"}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testLocalizedSideEffects7() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
String source = LINE_JOINER.join(
"/** @constructor A */ function A() {};",
"function f() {",
" var a = []; a[1] = 1; return a;",
"}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testLocalizedSideEffects8() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
// TODO(tdeegan): Not yet. Propagate local object information.
String source =
LINE_JOINER.join(
"/** @constructor A */ function A() {};",
"function f() {",
" var a = new A; a.foo = 1; return a;",
"}",
"f()");
assertPureCallsMarked(source, ImmutableList.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.
String source = LINE_JOINER.join(
"/** @constructor A */ function A() {this.x = 1};",
"function f() {",
" var a = new A; a.foo = 1; return a;",
"}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("A"));
}
public void testLocalizedSideEffects10() throws Exception {
// Returning a local object that has been modified
// is not a global side-effect.
String source = LINE_JOINER.join(
"/** @constructor A */ function A() {};",
"A.prototype.g = function() {this.x = 1};",
"function f() {",
" var a = new A; a.g(); return a;",
"}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("A"));
}
public void testLocalizedSideEffects11() throws Exception {
// TODO(tdeegan): updateA is side effect free.
// Calling a function of a local object that taints this.
String source =
LINE_JOINER.join(
"/** @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();");
assertPureCallsMarked(source, ImmutableList.of("A", "B"));
}
public void testLocalizedSideEffects12() throws Exception {
// An array is an local object, assigning a local array is not a global
// side-effect. This tests the behavior if the access is in a block scope.
String source = LINE_JOINER.join(
"function f() {var x = []; { x[0] = 1; } }",
"f()");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testUnaryOperators1() throws Exception {
String source = LINE_JOINER.join(
"function f() {var x = 1; x++}",
"f()");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testUnaryOperators2() throws Exception {
String source = LINE_JOINER.join(
"var x = 1;",
"function f() {x++}",
"f()");
assertNoPureCalls(source);
}
public void testUnaryOperators3() throws Exception {
String source = LINE_JOINER.join(
"function f() {var x = {foo : 0}; x.foo++}",
"f()");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testUnaryOperators4() throws Exception {
String source = LINE_JOINER.join(
"var x = {foo : 0};",
"function f() {x.foo++}",
"f()");
assertNoPureCalls(source);
}
public void testUnaryOperators5() throws Exception {
String source = LINE_JOINER.join(
"function f(x) {x.foo++}",
"f({foo : 0})");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testDeleteOperator1() throws Exception {
String source = LINE_JOINER.join(
"var x = {};",
"function f() {delete x}",
"f()");
assertNoPureCalls(source);
}
public void testDeleteOperator2() throws Exception {
String source = LINE_JOINER.join(
"function f() {var x = {}; delete x}",
"f()");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testOrOperator1() throws Exception {
String source = LINE_JOINER.join(
"var f = externNsef1 || externNsef2;",
"f()");
assertNoPureCalls(source);
}
public void testOrOperator2() throws Exception {
String source = LINE_JOINER.join(
"var f = function(){} || externNsef2;",
"f()");
assertNoPureCalls(source);
}
public void testOrOperator3() throws Exception {
String source = LINE_JOINER.join(
"var f = externNsef2 || function(){};",
"f()");
assertNoPureCalls(source);
}
public void testOrOperators4() throws Exception {
String source = LINE_JOINER.join(
"var f = function(){} || function(){};",
"f()");
assertNoPureCalls(source);
}
public void testAndOperator1() throws Exception {
String source = LINE_JOINER.join(
"var f = externNsef1 && externNsef2;",
"f()");
assertNoPureCalls(source);
}
public void testAndOperator2() throws Exception {
String source = LINE_JOINER.join(
"var f = function(){} && externNsef2;",
"f()");
assertNoPureCalls(source);
}
public void testAndOperator3() throws Exception {
String source = LINE_JOINER.join(
"var f = externNsef2 && function(){};",
"f()");
assertNoPureCalls(source);
}
public void testAndOperators4() throws Exception {
String source = LINE_JOINER.join(
"var f = function(){} && function(){};",
"f()");
assertNoPureCalls(source);
}
public void testHookOperator1() throws Exception {
String source = LINE_JOINER.join(
"var f = true ? externNsef1 : externNsef2;",
"f()");
assertNoPureCalls(source);
}
public void testHookOperator2() throws Exception {
String source = LINE_JOINER.join(
"var f = true ? function(){} : externNsef2;",
"f()");
assertNoPureCalls(source);
}
public void testHookOperator3() throws Exception {
String source = LINE_JOINER.join(
"var f = true ? externNsef2 : function(){};",
"f()");
assertNoPureCalls(source);
}
public void testHookOperators4() throws Exception {
String source = LINE_JOINER.join(
"var f = true ? function(){} : function(){};",
"f()");
assertPureCallsMarked(source, ImmutableList.<String>of("f"));
}
public void testHookOperators5() throws Exception {
String source = LINE_JOINER.join(
"var f = String.prototype.trim ? function(str){return str} : function(){};",
"f()");
assertPureCallsMarked(source, ImmutableList.<String>of("f"));
}
public void testHookOperators6() throws Exception {
String source = LINE_JOINER.join(
"var f = yyy ? function(str){return str} : xxx ? function() {} : function(){};",
"f()");
assertPureCallsMarked(source, ImmutableList.<String>of("f"));
}
public void testThrow1() throws Exception {
String source = LINE_JOINER.join(
"function f(){throw Error()};",
"f()");
assertPureCallsMarked(source, ImmutableList.<String>of("Error"));
}
public void testThrow2() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){throw Error()};",
"function f(){return new A()}",
"f()");
assertPureCallsMarked(source, ImmutableList.<String>of("Error"));
}
public void testAssignmentOverride() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){}",
"A.prototype.foo = function(){};",
"var a = new A;",
"a.foo();");
assertPureCallsMarked(source, ImmutableList.of("A", "a.foo"));
// Ideally inline aliases takes care of this.
String sourceOverride = LINE_JOINER.join(
"/**@constructor*/ function A(){}",
"A.prototype.foo = function(){};",
"var x = 1",
"function f(){x = 10}",
"var a = new A;",
"a.foo = f;",
"a.foo();");
assertPureCallsMarked(sourceOverride, ImmutableList.of("A"));
}
public void testInheritance1() throws Exception {
String source = CompilerTypeTestCase.CLOSURE_DEFS + LINE_JOINER.join(
"/**@constructor*/function I(){}",
"I.prototype.foo = function(){};",
"I.prototype.bar = function(){this.foo()};",
"/**@constructor@extends {I}*/function A(){};",
"goog.inherits(A, I);",
"/** @override */A.prototype.foo = function(){var data=24};",
"var i = new I();i.foo();i.bar();",
"var a = new A();a.foo();a.bar();"
);
assertPureCallsMarked(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 + LINE_JOINER.join(
"/**@constructor*/function I(){}",
"I.prototype.foo = function(){};",
"I.prototype.bar = function(){this.foo()};",
"/**@constructor@extends {I}*/function A(){};",
"goog.inherits(A, I);",
"/** @override */A.prototype.foo = function(){this.data=24};",
"var i = new I();i.foo();i.bar();",
"var a = new A();a.foo();a.bar();"
);
assertPureCallsMarked(source, ImmutableList.of("goog.inherits", "I", "A"));
}
public void testAmbiguousDefinitions() throws Exception {
String source = CompilerTypeTestCase.CLOSURE_DEFS + LINE_JOINER.join(
"var globalVar = 1;",
"A.f = function() {globalVar = 2;};",
"A.f = function() {};",
"function sideEffectCaller() { A.f() };",
"sideEffectCaller();"
);
// Can't tell which f is being called so it assumes both.
assertNoPureCalls(source);
}
public void testAmbiguousDefinitionsCall() throws Exception {
String source = CompilerTypeTestCase.CLOSURE_DEFS + LINE_JOINER.join(
"var globalVar = 1;",
"A.f = function() {globalVar = 2;};",
"A.f = function() {};",
"function sideEffectCaller() { A.f.call(null); };",
"sideEffectCaller();"
);
// Can't tell which f is being called so it assumes both.
assertNoPureCalls(source);
}
public void testAmbiguousDefinitionsAllPropagationTypes() throws Exception {
String source = CompilerTypeTestCase.CLOSURE_DEFS + LINE_JOINER.join(
"var globalVar = 1;",
"/**@constructor*/A.f = function() { this.x = 5; };",
"/**@constructor*/B.f = function() {};",
"function sideEffectCaller() { new C.f() };",
"sideEffectCaller();"
);
// Can't tell which f is being called so it assumes both.
assertPureCallsMarked(source, ImmutableList.<String>of("C.f", "sideEffectCaller"));
}
public void testAmbiguousDefinitionsCallWithThis() throws Exception {
String source = CompilerTypeTestCase.CLOSURE_DEFS + LINE_JOINER.join(
"var globalVar = 1;",
"A.modifiesThis = function() { this.x = 5; };",
"/**@constructor*/function Constructor() { Constructor.modifiesThis.call(this); };",
"Constructor.modifiesThis = function() {};",
"new Constructor();",
"A.modifiesThis();"
);
// Can't tell which modifiesThis is being called so it assumes both.
this.mode = TypeInferenceMode.OTI_ONLY;
assertPureCallsMarked(source, ImmutableList.<String>of("Constructor"));
this.mode = TypeInferenceMode.NTI_ONLY;
assertPureCallsMarked(
source,
ImmutableList.<String>of("Constructor"),
// modifiesThis not defined on Constructor
NewTypeInference.GLOBAL_THIS);
}
public void testAmbiguousDefinitionsBothCallThis() throws Exception {
String source =
LINE_JOINER.join(
"B.f = function() {",
" this.x = 1;",
"}",
"/** @constructor */ function C() {",
" this.f.apply(this);",
"}",
"C.prototype.f = function() {",
" this.x = 2;",
"}",
"new C();");
this.mode = TypeInferenceMode.OTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("C"));
this.mode = TypeInferenceMode.NTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("C"), NewTypeInference.GLOBAL_THIS);
}
public void testAmbiguousDefinitionsAllCallThis() throws Exception {
String source =
LINE_JOINER.join(
"A.f = function() { this.y = 1 };",
"C.f = function() { };",
"var g = function() {D.f()};",
"/** @constructor */ var h = function() {E.f.apply(this)};",
"var i = function() {F.f.apply({})};", // it can't tell {} is local.
"g();",
"new h();",
"i();" // With better locals tracking i could be identified as pure
);
this.mode = TypeInferenceMode.OTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("F.f.apply", "h"));
this.mode = TypeInferenceMode.NTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("F.f.apply", "h"), NewTypeInference.GLOBAL_THIS);
}
public void testAmbiguousDefinitionsMutatesGlobalArgument() throws Exception {
String source =
LINE_JOINER.join(
"// Mutates argument",
"A.a = function(argument) {",
" argument.x = 2;",
"};",
"// No side effects",
"B.a = function() {};",
"var b = function(x) {C.a(x)};",
"b({});");
assertNoPureCalls(source);
}
public void testAmbiguousDefinitionsMutatesLocalArgument() throws Exception {
String source =
LINE_JOINER.join(
"// Mutates argument",
"A.a = function(argument) {",
" argument.x = 2;",
"};",
"// No side effects",
"B.a = function() {};",
"var b = function() {",
" C.a({});",
"};",
"b();");
assertPureCallsMarked(source, ImmutableList.of("C.a", "b"));
}
public void testAmbiguousExternDefinitions() {
assertNoPureCalls("x.duplicateExternFunc()");
// nsef1 is defined as no side effect in the externs.
String source = LINE_JOINER.join(
"var global = 1;",
// Overwrite the @nosideeffects with this side effect
"A.nsef1 = function () {global = 2;};",
"externObj.nsef1();"
);
assertNoPureCalls(source);
}
/**
* Test bug where the FunctionInformation for "A.x" and "a" were separate causing
* .x() calls to appear pure because the global side effect was only registed for the function
* linked to "a".
*/
public void testAmbiguousDefinitionsDoubleDefinition() {
String source = LINE_JOINER.join(
"var global = 1;",
"A.x = function a() { global++; }",
"B.x = function() {}",
"B.x();"
);
assertNoPureCalls(source);
}
public void testAmbiguousDefinitionsDoubleDefinition2() {
String source = LINE_JOINER.join(
"var global = 1;",
"A.x = function a() { global++; }",
"a = function() {}",
"B.x(); a();"
);
assertNoPureCalls(source);
}
public void testAmbiguousDefinitionsDoubleDefinition3() {
String source = LINE_JOINER.join(
"var global = 1;",
"A.x = function a() {}",
"a = function() { global++; }",
"B.x(); a();"
);
assertPureCallsMarked(source, ImmutableList.of("B.x"));
}
public void testAmbiguousDefinitionsDoubleDefinition4() {
String source = LINE_JOINER.join(
"var global = 1;",
"A.x = function a() {}",
"B.x = function() { global++; }",
"B.x(); a();"
);
assertPureCallsMarked(source, ImmutableList.of("a"));
}
public void testAmbiguousDefinitionsDoubleDefinition5() {
String source = LINE_JOINER.join(
"var global = 1;",
"A.x = cond ? function a() { global++ } : function b() {}",
"B.x = function() { global++; }",
"B.x(); a(); b();"
);
assertPureCallsMarked(source, ImmutableList.of("b"));
}
public void testAmbiguousDefinitionsDoubleDefinition6() {
String source = LINE_JOINER.join(
"var SetCustomData1 = function SetCustomData2(element, dataName, dataValue) {",
" var x = element['_customData'];",
" x[dataName] = dataValue;",
"}",
"SetCustomData1(window, \"foo\", \"bar\");");
assertNoPureCalls(source);
}
public void testCallBeforeDefinition() throws Exception {
assertPureCallsMarked("f(); function f(){}", ImmutableList.of("f"));
this.mode = TypeInferenceMode.OTI_ONLY;
assertPureCallsMarked("var a = {}; a.f(); a.f = function (){}", ImmutableList.of("a.f"));
this.mode = TypeInferenceMode.NTI_ONLY;
assertPureCallsMarked(
"var a = {}; a.f(); a.f = function (){}",
ImmutableList.of("a.f"),
NewTypeInference.INEXISTENT_PROPERTY);
}
public void testConstructorThatModifiesThis1() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){this.foo = 1}",
"function f() {return new A}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesThis2() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){this.foo()}",
"A.prototype.foo = function(){this.data=24};",
"function f() {return new A}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesThis3() throws Exception {
// test chained
String source = LINE_JOINER.join(
"/**@constructor*/function A(){this.foo()}",
"A.prototype.foo = function(){this.bar()};",
"A.prototype.bar = function(){this.data=24};",
"function f() {return new A}",
"f()"
);
assertPureCallsMarked(source, ImmutableList.of("A", "f"));
}
public void testConstructorThatModifiesThis4() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){foo.call(this)}",
"function foo(){this.data=24};",
"function f() {return new A}",
"f()"
);
this.mode = TypeInferenceMode.OTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("A", "f"));
this.mode = TypeInferenceMode.NTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("A", "f"), NewTypeInference.GLOBAL_THIS);
}
public void testConstructorThatModifiesGlobal1() throws Exception {
String source = LINE_JOINER.join(
"var b = 0;",
"/**@constructor*/function A(){b=1};",
"function f() {return new A}",
"f()"
);
assertNoPureCalls(source);
}
public void testConstructorThatModifiesGlobal2() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){this.foo()}",
"A.prototype.foo = function(){b=1};",
"function f() {return new A}",
"f()"
);
assertNoPureCalls(source);
}
public void testCallFunctionThatModifiesThis() throws Exception {
String source = LINE_JOINER.join(
"/**@constructor*/function A(){}" ,
"A.prototype.foo = function(){this.data=24};" ,
"function f(){var a = new A; return a}" ,
"function g(){var a = new A; a.foo(); return a}" ,
"f(); g()"
);
assertPureCallsMarked(source, ImmutableList.of("A", "A", "f"));
}
public void testMutatesArguments1() throws Exception {
String source = LINE_JOINER.join(
"function f(x) { x.y = 1; }",
"f({});");
assertPureCallsMarked(source, ImmutableList.of("f"));
}
public void testMutatesArguments2() throws Exception {
String source = LINE_JOINER.join(
"function f(x) { x.y = 1; }",
"f(window);");
assertNoPureCalls(source);
}
public void testMutatesArguments3() throws Exception {
// We could do better here with better side-effect propagation.
String source = LINE_JOINER.join(
"function f(x) { x.y = 1; }",
"function g(x) { f(x); }",
"g({});");
assertNoPureCalls(source);
}
public void testMutatesArguments4() throws Exception {
String source = LINE_JOINER.join(
"function f(x) { x.y = 1; }",
"function g(x) { f({}); x.y = 1; }",
"g({});");
assertPureCallsMarked(source, ImmutableList.of("f", "g"));
}
public void testMutatesArguments5() throws Exception {
String source = LINE_JOINER.join(
"function f(x) {",
" function g() {",
" x.prop = 5;",
" }",
" g();",
"}",
"f(window);");
assertNoPureCalls(source);
}
public void testMutatesArgumentsArray1() throws Exception {
String source = LINE_JOINER.join(
"function f(x) { arguments[0] = 1; }",
"f({});");
assertPureCallsMarked(source, ImmutableList.<String>of("f"));
}
public void testMutatesArgumentsArray2() throws Exception {
// We could be smarter here.
String source = LINE_JOINER.join(
"function f(x) { arguments[0].y = 1; }",
"f({});");
assertNoPureCalls(source);
}
public void testMutatesArgumentsArray3() throws Exception {
String source = LINE_JOINER.join(
"function f(x) { arguments[0].y = 1; }",
"f(x);");
assertNoPureCalls(source);
}
public void testCallFunctionFOrG() throws Exception {
String source = LINE_JOINER.join(
"function f(){}",
"function g(){}",
"function h(){ (f || g)() }",
"h()"
);
assertPureCallsMarked(source, ImmutableList.of("(f || g)", "h"));
}
public void testCallFunctionFOrGViaHook() throws Exception {
String source = LINE_JOINER.join(
"function f(){}",
"function g(){}",
"function h(){ (false ? f : g)() }",
"h()"
);
assertPureCallsMarked(source, ImmutableList.of("(f : g)", "h"));
}
public void testCallFunctionForGorH() throws Exception {
String source = LINE_JOINER.join(
"function f(){}",
"function g(){}",
"function h(){}",
"function i(){ (false ? f : (g || h))() }",
"i()"
);
assertPureCallsMarked(source, ImmutableList.of("(f : (g || h))", "i"));
}
public void testCallFunctionForGWithSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var x = 0;",
"function f(){x = 10}",
"function g(){}",
"function h(){ (f || g)() }",
"function i(){ (g || f)() }",
"function j(){ (f || f)() }",
"function k(){ (g || g)() }",
"h(); i(); j(); k()"
);
assertPureCallsMarked(source, ImmutableList.of("(g || g)", "k"));
}
public void testCallFunctionFOrGViaHookWithSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var x = 0;",
"function f(){x = 10}",
"function g(){}",
"function h(){ (false ? f : g)() }",
"function i(){ (false ? g : f)() }",
"function j(){ (false ? f : f)() }",
"function k(){ (false ? g : g)() }",
"h(); i(); j(); k()"
);
assertPureCallsMarked(source, ImmutableList.of("(g : g)", "k"));
}
public void testCallRegExpWithSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var x = 0;",
"function k(){(/a/).exec('')}",
"k()"
);
regExpHaveSideEffects = true;
assertNoPureCalls(source);
regExpHaveSideEffects = false;
assertPureCallsMarked(source, ImmutableList.of(
"REGEXP STRING exec", "k"));
}
public void testAnonymousFunction1() throws Exception {
assertPureCallsMarked("(function (){})();", ImmutableList.of("FUNCTION"));
}
public void testAnonymousFunction2() throws Exception {
String source = "(Error || function (){})();";
assertPureCallsMarked(source, ImmutableList.of("(Error || FUNCTION)"));
}
public void testAnonymousFunction3() throws Exception {
String source = "var a = (Error || function (){})();";
assertPureCallsMarked(source, ImmutableList.of("(Error || FUNCTION)"));
}
// Indirect complex function definitions aren't yet supported.
public void testAnonymousFunction4() throws Exception {
String source = LINE_JOINER.join(
"var a = (Error || function (){});",
"a();"
);
// This should be "(Error || FUNCTION)" but isn't.
assertNoPureCalls(source);
}
public void testFunctionProperties1() throws Exception {
String source = LINE_JOINER.join(
"/** @constructor */",
"function F() { this.bar; }",
"function g() {",
" this.bar = function() { alert(3); };",
"}",
"var x = new F();",
"g.call(x);",
"x.bar();"
);
this.mode = TypeInferenceMode.OTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("F"));
Node lastRoot = getLastCompiler().getRoot();
Node call = findQualifiedNameNode("g.call", lastRoot).getParent();
assertEquals(
new Node.SideEffectFlags()
.clearAllFlags().setMutatesArguments().valueOf(),
call.getSideEffectFlags());
this.mode = TypeInferenceMode.NTI_ONLY;
assertPureCallsMarked(source, ImmutableList.of("F"), NewTypeInference.GLOBAL_THIS);
lastRoot = getLastCompiler().getRoot();
call = findQualifiedNameNode("g.call", lastRoot).getParent();
assertEquals(
new Node.SideEffectFlags()
.clearAllFlags().setMutatesArguments().valueOf(),
call.getSideEffectFlags());
}
public void testCallCache() throws Exception {
String source = LINE_JOINER.join(
"var valueFn = function() {};",
"goog.reflect.cache(externObj, \"foo\", valueFn)"
);
assertPureCallsMarked(source, ImmutableList.of("goog.reflect.cache"));
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node call = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(call.isNoSideEffectsCall()).isTrue();
assertThat(call.mayMutateGlobalStateOrThrow()).isFalse();
}
public void testCallCache_withKeyFn() throws Exception {
String source = LINE_JOINER.join(
"var valueFn = function(v) { return v };",
"var keyFn = function(v) { return v };",
"goog.reflect.cache(externObj, \"foo\", valueFn, keyFn)"
);
assertPureCallsMarked(source, ImmutableList.of("goog.reflect.cache"));
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node call = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(call.isNoSideEffectsCall()).isTrue();
assertThat(call.mayMutateGlobalStateOrThrow()).isFalse();
}
public void testCallCache_anonymousFn() throws Exception {
String source = "goog.reflect.cache(externObj, \"foo\", function(v) { return v })";
assertPureCallsMarked(source, ImmutableList.of("goog.reflect.cache"));
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node call = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(call.isNoSideEffectsCall()).isTrue();
assertThat(call.mayMutateGlobalStateOrThrow()).isFalse();
}
public void testCallCache_anonymousFn_hasSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var x = 0;",
"goog.reflect.cache(externObj, \"foo\", function(v) { return (x+=1) })"
);
assertNoPureCalls(source);
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node call = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(call.isNoSideEffectsCall()).isFalse();
assertThat(call.mayMutateGlobalStateOrThrow()).isTrue();
}
public void testCallCache_hasSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var x = 0;",
"var valueFn = function() { return (x+=1); };",
"goog.reflect.cache(externObj, \"foo\", valueFn)"
);
assertNoPureCalls(source);
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node call = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(call.isNoSideEffectsCall()).isFalse();
assertThat(call.mayMutateGlobalStateOrThrow()).isTrue();
}
public void testCallCache_withKeyFn_hasSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var x = 0;",
"var keyFn = function(v) { return (x+=1) };",
"var valueFn = function(v) { return v };",
"goog.reflect.cache(externObj, \"foo\", valueFn, keyFn)"
);
assertNoPureCalls(source);
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node call = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(call.isNoSideEffectsCall()).isFalse();
assertThat(call.mayMutateGlobalStateOrThrow()).isTrue();
}
public void testCallCache_propagatesSideEffects() throws Exception {
String source = LINE_JOINER.join(
"var valueFn = function(x) { return x * 2; };",
"var helper = function(x) { return goog.reflect.cache(externObj, x, valueFn); };",
"helper(10);"
);
assertPureCallsMarked(source, ImmutableList.of("goog.reflect.cache", "helper"));
Node lastRoot = getLastCompiler().getRoot().getLastChild();
Node cacheCall = findQualifiedNameNode("goog.reflect.cache", lastRoot).getParent();
assertThat(cacheCall.isNoSideEffectsCall()).isTrue();
assertThat(cacheCall.mayMutateGlobalStateOrThrow()).isFalse();
Node helperCall = Iterables.getLast(findQualifiedNameNodes("helper", lastRoot)).getParent();
assertThat(helperCall.isNoSideEffectsCall()).isTrue();
assertThat(helperCall.mayMutateGlobalStateOrThrow()).isFalse();
}
void assertNoPureCalls(String source) {
assertPureCallsMarked(source, ImmutableList.<String>of(), null);
}
void assertPureCallsMarked(String source, List<String> expected) {
assertPureCallsMarked(source, expected, null);
}
void assertPureCallsMarked(String source, List<String> expected, DiagnosticType warning) {
assertPureCallsMarked(source, expected, warning, LanguageMode.ECMASCRIPT_2015);
assertPureCallsMarked(source, expected, warning, LanguageMode.ECMASCRIPT5);
}
void assertPureCallsMarked(
String source, List<String> expected, DiagnosticType warning, LanguageMode mode) {
setAcceptedLanguage(mode);
testSame(source, warning);
assertEquals(expected, noSideEffectCalls);
}
void checkLocalityOfMarkedCalls(String source, List<String> expected) {
checkLocalityOfMarkedCalls(source, expected, LanguageMode.ECMASCRIPT_2015);
checkLocalityOfMarkedCalls(source, expected, LanguageMode.ECMASCRIPT5);
}
void checkLocalityOfMarkedCalls(String source, List<String> expected, LanguageMode mode) {
setAcceptedLanguage(mode);
testSame(source);
assertEquals(expected, localResultCalls);
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new NoSideEffectCallEnumerator(compiler);
}
}