/* * Copyright 2010 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.Lists; import com.google.common.collect.Sets; import com.google.javascript.jscomp.CallGraph.Callsite; import com.google.javascript.jscomp.CallGraph.Function; import com.google.javascript.jscomp.graph.FixedPointGraphTraversal; import com.google.javascript.jscomp.graph.FixedPointGraphTraversal.EdgeCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Collection; import java.util.List; import java.util.Set; /** * Tests for CallGraph. * * @author dcc@google.com (Devin Coughlin) */ public class CallGraphTest extends CompilerTestCase { private CallGraph currentProcessor; private boolean createForwardCallGraph; private boolean createBackwardCallGraph; @Override protected CompilerPass getProcessor(Compiler compiler) { // We store the new callgraph so it can be tested later currentProcessor = new CallGraph(compiler, createForwardCallGraph, createBackwardCallGraph); return currentProcessor; } static final String SHARED_EXTERNS = "var ExternalFunction = function(a) {}\n" + "var externalnamespace = {}\n" + "externalnamespace.prop = function(){};\n"; public void testGetFunctionForAstNode() { String source = "function A() {};\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); Node functionANode = functionA.getAstNode(); assertEquals(functionA, callgraph.getFunctionForAstNode(functionANode)); } public void testGetAllFunctions() { String source = "function A() {}\n" + "var B = function() {\n" + "(function C(){A()})()\n" + "};\n"; CallGraph callgraph = compileAndRunForward(source); Collection functions = callgraph.getAllFunctions(); // 3 Functions, plus one for the main function assertEquals(4, functions.size()); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); CallGraph.Function functionB = callgraph.getUniqueFunctionWithName("B"); CallGraph.Function functionC = callgraph.getUniqueFunctionWithName("C"); assertEquals("A", NodeUtil.getFunctionName(functionA.getAstNode())); assertEquals("B", NodeUtil.getFunctionName(functionB.getAstNode())); assertEquals("C", NodeUtil.getFunctionName(functionC.getAstNode())); } public void testGetAllFunctionsContainsNormalFunction() { String source = "function A(){}\n"; CallGraph callgraph = compileAndRunForward(source); Collection allFunctions = callgraph.getAllFunctions(); // 2 functions: one for A() and one for the main function assertEquals(2, allFunctions.size()); assertTrue(allFunctions.contains(callgraph.getUniqueFunctionWithName("A"))); assertTrue(allFunctions.contains(callgraph.getMainFunction())); } public void testGetAllFunctionsContainsVarAssignedLiteralFunction() { String source = "var A = function(){}\n"; CallGraph callgraph = compileAndRunForward(source); Collection allFunctions = callgraph.getAllFunctions(); // 2 functions: one for A() and one for the global function assertEquals(2, allFunctions.size()); Function functionA = callgraph.getUniqueFunctionWithName("A"); assertTrue(allFunctions.contains(functionA)); assertTrue(allFunctions.contains(callgraph.getMainFunction())); } public void testGetAllFunctionsContainsNamespaceAssignedLiteralFunction() { String source = "var namespace = {};\n" + "namespace.A = function(){};\n"; CallGraph callgraph = compileAndRunForward(source); Collection allFunctions = callgraph.getAllFunctions(); // 2 functions: one for namespace.A() and one for the global function assertEquals(2, allFunctions.size()); assertTrue(allFunctions.contains( callgraph.getUniqueFunctionWithName("namespace.A"))); assertTrue(allFunctions.contains(callgraph.getMainFunction())); } public void testGetAllFunctionsContainsLocalFunction() { String source = "var A = function(){var B = function(){}};\n"; CallGraph callgraph = compileAndRunForward(source); Collection allFunctions = callgraph.getAllFunctions(); // 3 functions: one for A, B, and global function assertEquals(3, allFunctions.size()); assertTrue(allFunctions.contains(callgraph.getUniqueFunctionWithName("A"))); assertTrue(allFunctions.contains(callgraph.getUniqueFunctionWithName("B"))); assertTrue(allFunctions.contains(callgraph.getMainFunction())); } public void testGetAllFunctionsContainsAnonymousFunction() { String source = "var A = function(){(function(){})();};\n"; CallGraph callgraph = compileAndRunForward(source); Collection allFunctions = callgraph.getAllFunctions(); // 3 functions: A, anonymous, and global function assertEquals(3, allFunctions.size()); assertTrue(allFunctions.contains(callgraph.getUniqueFunctionWithName("A"))); assertTrue( allFunctions.contains(callgraph.getUniqueFunctionWithName(null))); assertTrue(allFunctions.contains(callgraph.getMainFunction())); } public void testGetCallsiteForAstNode() { String source = "function A() {B()};\n" + "function B(){};\n"; CallGraph callgraph = compileAndRunBackward(source); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); CallGraph.Callsite callToB = functionA.getCallsitesInFunction().iterator().next(); Node callsiteNode = callToB.getAstNode(); assertEquals(callToB, callgraph.getCallsiteForAstNode(callsiteNode)); } public void testFunctionGetCallsites() { String source = "function A() {var x; x()}\n" + "var B = function() {\n" + "(function C(){A()})()\n" + "};\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); Collection callsitesInA = functionA.getCallsitesInFunction(); assertEquals(1, callsitesInA.size()); CallGraph.Callsite firstCallsiteInA = callsitesInA.iterator().next(); Node aTargetExpression = firstCallsiteInA.getAstNode().getFirstChild(); assertEquals(Token.NAME, aTargetExpression.getType()); assertEquals("x", aTargetExpression.getString()); CallGraph.Function functionB = callgraph.getUniqueFunctionWithName("B"); Collection callsitesInB = functionB.getCallsitesInFunction(); assertEquals(1, callsitesInB.size()); CallGraph.Callsite firstCallsiteInB = callsitesInB.iterator().next(); Node bTargetExpression = firstCallsiteInB.getAstNode().getFirstChild(); assertEquals(Token.FUNCTION, bTargetExpression.getType()); assertEquals("C", NodeUtil.getFunctionName(bTargetExpression)); CallGraph.Function functionC = callgraph.getUniqueFunctionWithName("C"); Collection callsitesInC = functionC.getCallsitesInFunction(); assertEquals(1, callsitesInC.size()); CallGraph.Callsite firstCallsiteInC = callsitesInC.iterator().next(); Node cTargetExpression = firstCallsiteInC.getAstNode().getFirstChild(); assertEquals(Token.NAME, aTargetExpression.getType()); assertEquals("A", cTargetExpression.getString()); } public void testFindNewInFunction() { String source = "function A() {var x; new x(1,2)}\n;"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); Collection callsitesInA = functionA.getCallsitesInFunction(); assertEquals(1, callsitesInA.size()); Node callsiteInA = callsitesInA.iterator().next().getAstNode(); assertEquals(Token.NEW, callsiteInA.getType()); Node aTargetExpression = callsiteInA.getFirstChild(); assertEquals(Token.NAME, aTargetExpression.getType()); assertEquals("x", aTargetExpression.getString()); } public void testFindCallsiteTargetGlobalName() { String source = "function A() {}\n" + "function B() {}\n" + "function C() {A()}\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function functionC = callgraph.getUniqueFunctionWithName("C"); assertNotNull(functionC); CallGraph.Callsite callsiteInC = functionC.getCallsitesInFunction().iterator().next(); assertNotNull(callsiteInC); Collection targetsOfCallsiteInC = callsiteInC.getPossibleTargets(); assertNotNull(targetsOfCallsiteInC); assertEquals(1, targetsOfCallsiteInC.size()); } public void testFindCallsiteTargetAliasedGlobalProperty() { String source = "var namespace = {};\n" + "namespace.A = function() {};\n" + "function C() {namespace.A()}\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function functionC = callgraph.getUniqueFunctionWithName("C"); assertNotNull(functionC); CallGraph.Callsite callsiteInC = functionC.getCallsitesInFunction().iterator().next(); assertNotNull(callsiteInC); Collection targetsOfCallsiteInC = callsiteInC.getPossibleTargets(); assertNotNull(targetsOfCallsiteInC); assertEquals(1, targetsOfCallsiteInC.size()); } public void testGetAllCallsitesContainsMultiple() { String source = "function A() {}\n" + "var B = function() {\n" + "(function (){A()})()\n" + "};\n" + "A();\n" + "B();\n"; CallGraph callgraph = compileAndRunBackward(source); Collection allCallsites = callgraph.getAllCallsites(); assertEquals(4, allCallsites.size()); } public void testGetAllCallsitesContainsGlobalSite() { String source = "function A(){}\n" + "A();\n"; CallGraph callgraph = compileAndRunBackward(source); Collection allCallsites = callgraph.getAllCallsites(); assertEquals(1, allCallsites.size()); Node callsiteNode = allCallsites.iterator().next().getAstNode(); assertEquals(Token.CALL, callsiteNode.getType()); assertEquals("A", callsiteNode.getFirstChild().getString()); } public void testGetAllCallsitesContainsLocalSite() { String source = "function A(){}\n" + "function B(){A();}\n"; CallGraph callgraph = compileAndRunBackward(source); Collection allCallsites = callgraph.getAllCallsites(); assertEquals(1, allCallsites.size()); Node callsiteNode = allCallsites.iterator().next().getAstNode(); assertEquals(Token.CALL, callsiteNode.getType()); assertEquals("A", callsiteNode.getFirstChild().getString()); } public void testGetAllCallsitesContainsLiteralSite() { String source = "function A(){(function(a){})();}\n"; CallGraph callgraph = compileAndRunBackward(source); Collection allCallsites = callgraph.getAllCallsites(); assertEquals(1, allCallsites.size()); Node callsiteNode = allCallsites.iterator().next().getAstNode(); assertEquals(Token.CALL, callsiteNode.getType()); assertEquals(Token.FUNCTION, callsiteNode.getFirstChild().getType()); } public void testGetAllCallsitesContainsConstructorSite() { String source = "function A(){}\n" + "function B(){new A();}\n"; CallGraph callgraph = compileAndRunBackward(source); Collection allCallsites = callgraph.getAllCallsites(); assertEquals(1, allCallsites.size()); Node callsiteNode = allCallsites.iterator().next().getAstNode(); assertEquals(Token.NEW, callsiteNode.getType()); assertEquals("A", callsiteNode.getFirstChild().getString()); } /** * Test getting a backward directed graph on a backward call graph * and propagating over it. */ public void testGetDirectedGraph_backwardOnBackward() { // For this test we create a simple callback that, when applied until a // fixedpoint, computes whether a function is "poisoned" by an extern. // A function is poisoned if it calls an extern or if it calls another // poisoned function. String source = "function A(){};\n" + "function B(){ExternalFunction(6); C(); D();}\n" + "function C(){B(); A();};\n" + "function D(){A();};\n" + "function E(){C()};\n" + "A();\n"; CallGraph callgraph = compileAndRunBackward(source); final Set poisonedFunctions = Sets.newHashSet(); // Set up initial poisoned functions for (Callsite callsite : callgraph.getAllCallsites()) { if (callsite.hasExternTarget()) { poisonedFunctions.add(callsite.getContainingFunction()); } } // Propagate poison from callees to callers EdgeCallback edgeCallback = new EdgeCallback() { @Override public boolean traverseEdge(Function callee, Callsite callsite, Function caller) { boolean changed; if (poisonedFunctions.contains(callee)) { changed = poisonedFunctions.add(caller); // Returns true if added } else { changed = false; } return changed; } }; FixedPointGraphTraversal.newTraversal(edgeCallback) .computeFixedPoint(callgraph.getBackwardDirectedGraph()); // We expect B, C, and E to poisoned. assertEquals(3, poisonedFunctions.size()); assertTrue(poisonedFunctions.contains( callgraph.getUniqueFunctionWithName("B"))); assertTrue(poisonedFunctions.contains( callgraph.getUniqueFunctionWithName("C"))); assertTrue(poisonedFunctions.contains( callgraph.getUniqueFunctionWithName("E"))); } /** * Test getting a backward directed graph on a forward call graph * and propagating over it. */ public void testGetDirectedGraph_backwardOnForward() { // For this test we create a simple callback that, when applied until a // fixedpoint, computes whether a function is "poisoned" by an extern. // A function is poisoned if it calls an extern or if it calls another // poisoned function. String source = "function A(){};\n" + "function B(){ExternalFunction(6); C(); D();}\n" + "function C(){B(); A();};\n" + "function D(){A();};\n" + "function E(){C()};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); final Set poisonedFunctions = Sets.newHashSet(); // Set up initial poisoned functions for (Callsite callsite : callgraph.getAllCallsites()) { if (callsite.hasExternTarget()) { poisonedFunctions.add(callsite.getContainingFunction()); } } // Propagate poison from callees to callers EdgeCallback edgeCallback = new EdgeCallback() { @Override public boolean traverseEdge(Function callee, Callsite callsite, Function caller) { boolean changed; if (poisonedFunctions.contains(callee)) { changed = poisonedFunctions.add(caller); // Returns true if added } else { changed = false; } return changed; } }; FixedPointGraphTraversal.newTraversal(edgeCallback) .computeFixedPoint(callgraph.getBackwardDirectedGraph()); // We expect B, C, and E to poisoned. assertEquals(3, poisonedFunctions.size()); assertTrue(poisonedFunctions.contains( callgraph.getUniqueFunctionWithName("B"))); assertTrue(poisonedFunctions.contains( callgraph.getUniqueFunctionWithName("C"))); assertTrue(poisonedFunctions.contains( callgraph.getUniqueFunctionWithName("E"))); } /** * Test getting a forward directed graph on a forward call graph * and propagating over it. */ public void testGetDirectedGraph_forwardOnForward() { // For this test we create a simple callback that, when applied until a // fixedpoint, computes whether a function is reachable from an initial // set of "root" nodes. String source = "function A(){B()};\n" + "function B(){C();D()}\n" + "function C(){B()};\n" + "function D(){};\n" + "function E(){C()};\n" + "function X(){Y()};\n" + "function Y(){Z()};\n" + "function Z(){};" + "B();\n"; CallGraph callgraph = compileAndRunForward(source); final Set reachableFunctions = Sets.newHashSet(); // We assume the main function and X are our roots reachableFunctions.add(callgraph.getMainFunction()); reachableFunctions.add(callgraph.getUniqueFunctionWithName("X")); // Propagate reachability from callers to callees EdgeCallback edgeCallback = new EdgeCallback() { @Override public boolean traverseEdge(Function caller, Callsite callsite, Function callee) { boolean changed; if (reachableFunctions.contains(caller)) { changed = reachableFunctions.add(callee); // Returns true if added } else { changed = false; } return changed; } }; FixedPointGraphTraversal.newTraversal(edgeCallback) .computeFixedPoint(callgraph.getForwardDirectedGraph()); // We expect B, C, D, X, Y, Z and the main function should be reachable. // A and E should not be reachable. assertEquals(7, reachableFunctions.size()); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("B"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("C"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("D"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("X"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("Y"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("Z"))); assertTrue(reachableFunctions.contains( callgraph.getMainFunction())); assertFalse(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("A"))); assertFalse(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("E"))); } /** * Test getting a backward directed graph on a forward call graph * and propagating over it. */ public void testGetDirectedGraph_forwardOnBackward() { // For this test we create a simple callback that, when applied until a // fixedpoint, computes whether a function is reachable from an initial // set of "root" nodes. String source = "function A(){B()};\n" + "function B(){C();D()}\n" + "function C(){B()};\n" + "function D(){};\n" + "function E(){C()};\n" + "function X(){Y()};\n" + "function Y(){Z()};\n" + "function Z(){};" + "B();\n"; CallGraph callgraph = compileAndRunBackward(source); final Set reachableFunctions = Sets.newHashSet(); // We assume the main function and X are our roots reachableFunctions.add(callgraph.getMainFunction()); reachableFunctions.add(callgraph.getUniqueFunctionWithName("X")); // Propagate reachability from callers to callees EdgeCallback edgeCallback = new EdgeCallback() { @Override public boolean traverseEdge(Function caller, Callsite callsite, Function callee) { boolean changed; if (reachableFunctions.contains(caller)) { changed = reachableFunctions.add(callee); // Returns true if added } else { changed = false; } return changed; } }; FixedPointGraphTraversal.newTraversal(edgeCallback) .computeFixedPoint(callgraph.getForwardDirectedGraph()); // We expect B, C, D, X, Y, Z and the main function should be reachable. // A and E should not be reachable. assertEquals(7, reachableFunctions.size()); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("B"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("C"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("D"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("X"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("Y"))); assertTrue(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("Z"))); assertTrue(reachableFunctions.contains( callgraph.getMainFunction())); assertFalse(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("A"))); assertFalse(reachableFunctions.contains( callgraph.getUniqueFunctionWithName("E"))); } public void testFunctionIsMain() { String source = "function A(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function mainFunction = callgraph.getMainFunction(); assertTrue(mainFunction.isMain()); assertNotNull(mainFunction.getBodyNode()); assertTrue(mainFunction.getBodyNode().isBlock()); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); assertFalse(functionA.isMain()); } public void testFunctionGetAstNode() { String source = "function A(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function mainFunction = callgraph.getMainFunction(); // Main function's AST node should be the global block assertTrue(mainFunction.getAstNode().isBlock()); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); // Regular function's AST node should be the function for A assertTrue(functionA.getAstNode().isFunction()); assertEquals("A", NodeUtil.getFunctionName(functionA.getAstNode())); } public void testFunctionGetBodyNode() { String source = "function A(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function mainFunction = callgraph.getMainFunction(); // Main function's body node should its AST node assertEquals(mainFunction.getAstNode(), mainFunction.getBodyNode()); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); // Regular function's body node should be the block for A assertTrue(functionA.getBodyNode().isBlock()); assertEquals(NodeUtil.getFunctionBody(functionA.getAstNode()), functionA.getBodyNode()); } public void testFunctionGetName() { String source = "function A(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); CallGraph.Function mainFunction = callgraph.getMainFunction(); // Main function's name should be CallGraph.MAIN_FUNCTION_NAME assertEquals(CallGraph.MAIN_FUNCTION_NAME, mainFunction.getName()); CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); // Regular function's name should be its name assertEquals(NodeUtil.getFunctionName(functionA.getAstNode()), functionA.getName()); } public void testFunctionGetCallsitesInFunction() { String source = "function A(){};\n" + "function B(){A()};\n" + "A();\n" + "B();\n"; CallGraph callgraph = compileAndRunForward(source); // Main function calls A and B CallGraph.Function mainFunction = callgraph.getMainFunction(); List callsiteNamesInMain = getCallsiteTargetNames(mainFunction.getCallsitesInFunction()); assertEquals(2, callsiteNamesInMain.size()); assertTrue(callsiteNamesInMain.contains("A")); assertTrue(callsiteNamesInMain.contains("B")); // A calls no functions CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); assertEquals(0, functionA.getCallsitesInFunction().size()); // B calls A CallGraph.Function functionB = callgraph.getUniqueFunctionWithName("B"); List callsiteNamesInB = getCallsiteTargetNames(functionB.getCallsitesInFunction()); assertEquals(1, callsiteNamesInB.size()); assertTrue(callsiteNamesInMain.contains("A")); } public void testFunctionGetCallsitesInFunction_ignoreInnerFunction() { String source = "function A(){var B = function(){C();}};\n" + "function C(){};\n"; CallGraph callgraph = compileAndRunForward(source); // A calls no functions (and especially not C) CallGraph.Function functionA = callgraph.getUniqueFunctionWithName("A"); assertEquals(0, functionA.getCallsitesInFunction().size()); } public void testFunctionGetCallsitesPossiblyTargetingFunction() { String source = "function A(){B()};\n" + "function B(){C();C();};\n" + "function C(){C()};\n" + "A();\n"; CallGraph callgraph = compileAndRunBackward(source); Function main = callgraph.getMainFunction(); Function functionA = callgraph.getUniqueFunctionWithName("A"); Function functionB = callgraph.getUniqueFunctionWithName("B"); Function functionC = callgraph.getUniqueFunctionWithName("C"); assertEquals(0, main.getCallsitesPossiblyTargetingFunction().size()); Collection callsitesTargetingA = functionA.getCallsitesPossiblyTargetingFunction(); // A is called only from the main function assertEquals(1, callsitesTargetingA.size()); assertEquals(main, callsitesTargetingA.iterator().next().getContainingFunction()); Collection callsitesTargetingB = functionB.getCallsitesPossiblyTargetingFunction(); // B is called only from A assertEquals(1, callsitesTargetingB.size()); assertEquals(functionA, callsitesTargetingB.iterator().next().getContainingFunction()); Collection callsitesTargetingC = functionC.getCallsitesPossiblyTargetingFunction(); // C is called 3 times: twice from B and once from C assertEquals(3, callsitesTargetingC.size()); Collection expectedFunctionsCallingC = Sets.newHashSet(functionB.getCallsitesInFunction()); expectedFunctionsCallingC.addAll(functionC.getCallsitesInFunction()); assertTrue(callsitesTargetingC.containsAll(expectedFunctionsCallingC)); } public void testFunctionGetCallsitesInFunction_newIsCallsite() { String source = "function A(){};\n" + "function C(){new A()};\n"; CallGraph callgraph = compileAndRunForward(source); // The call to new A() in C() should count as a callsite CallGraph.Function functionC = callgraph.getUniqueFunctionWithName("C"); assertEquals(1, functionC.getCallsitesInFunction().size()); } public void testFunctionGetIsAliased() { // Aliased by VAR assignment String source = "function A(){};\n" + "var ns = {};\n" + "ns.B = function() {};\n" + "var C = function() {}\n" + "var D = function() {}\n" + "var aliasA = A;\n" + "var aliasB = ns.B;\n" + "var aliasC = C;\n" + "D();"; compileAndRunForward(source); assertFunctionAliased(true, "A"); assertFunctionAliased(true, "ns.B"); assertFunctionAliased(true, "C"); assertFunctionAliased(false, "D"); // Aliased by normal assignment source = "function A(){};\n" + "var ns = {};\n" + "ns.B = function() {};\n" + "var C = function() {}\n" + "ns.D = function() {}\n" + "var aliasA;\n" + "aliasA = A;\n" + "var aliasB = {};\n" + "aliasB.foo = ns.B;\n" + "var aliasC;\n" + "aliasC = C;\n" + "ns.D();"; compileAndRunForward(source); assertFunctionAliased(true, "A"); assertFunctionAliased(true, "ns.B"); assertFunctionAliased(true, "C"); assertFunctionAliased(false, "ns.D"); // Aliased by passing as parameter source = "function A(){};\n" + "var ns = {};\n" + "ns.B = function() {};\n" + "var C = function() {}\n" + "function D() {}\n" + "var foo = function(a) {}\n" + "foo(A);\n" + "foo(ns.B)\n" + "foo(C);\n" + "D();"; compileAndRunForward(source); assertFunctionAliased(true, "A"); assertFunctionAliased(true, "ns.B"); assertFunctionAliased(true, "C"); assertFunctionAliased(false, "D"); // Not aliased by being target of call source = "function A(){};\n" + "var ns = {};\n" + "ns.B = function() {};\n" + "var C = function() {}\n" + "A();\n" + "ns.B();\n" + "C();\n"; compileAndRunForward(source); assertFunctionAliased(false, "A"); assertFunctionAliased(false, "ns.B"); assertFunctionAliased(false, "C"); // Not aliased by GET{PROP,ELEM} source = "function A(){};\n" + "var ns = {};\n" + "ns.B = function() {};\n" + "var C = function() {}\n" + "A.foo;\n" + "ns.B.prototype;\n" + "C[0];\n"; compileAndRunForward(source); assertFunctionAliased(false, "A"); assertFunctionAliased(false, "ns.B"); assertFunctionAliased(false, "C"); } public void testFunctionGetIsExposedToCallOrApply() { // Exposed to call String source = "function A(){};\n" + "function B(){};\n" + "function C(){};\n" + "var x;\n" + "A.call(x);\n" + "B.apply(x);\n" + "C();\n"; CallGraph callGraph = compileAndRunForward(source); Function functionA = callGraph.getUniqueFunctionWithName("A"); Function functionB = callGraph.getUniqueFunctionWithName("B"); Function functionC = callGraph.getUniqueFunctionWithName("C"); assertTrue(functionA.isExposedToCallOrApply()); assertTrue(functionB.isExposedToCallOrApply()); assertFalse(functionC.isExposedToCallOrApply()); } public void testCallsiteGetAstNode() { String source = "function A(){B()};\n" + "function B(){};\n"; CallGraph callgraph = compileAndRunForward(source); Function functionA = callgraph.getUniqueFunctionWithName("A"); Callsite callToB = functionA.getCallsitesInFunction().iterator().next(); assertTrue(callToB.getAstNode().isCall()); } public void testCallsiteGetContainingFunction() { String source = "function A(){B()};\n" + "function B(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); Function mainFunction = callgraph.getMainFunction(); Callsite callToA = mainFunction.getCallsitesInFunction().iterator().next(); assertEquals(mainFunction, callToA.getContainingFunction()); Function functionA = callgraph.getUniqueFunctionWithName("A"); Callsite callToB = functionA.getCallsitesInFunction().iterator().next(); assertEquals(functionA, callToB.getContainingFunction()); } public void testCallsiteGetKnownTargets() { String source = "function A(){B()};\n" + "function B(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); Function mainFunction = callgraph.getMainFunction(); Function functionA = callgraph.getUniqueFunctionWithName("A"); Function functionB = callgraph.getUniqueFunctionWithName("B"); Callsite callInMain = mainFunction.getCallsitesInFunction().iterator() .next(); Collection targetsOfCallInMain = callInMain.getPossibleTargets(); assertEquals(1, targetsOfCallInMain.size()); assertTrue(targetsOfCallInMain.contains(functionA)); Callsite callInA = functionA.getCallsitesInFunction().iterator().next(); Collection targetsOfCallInA = callInA.getPossibleTargets(); assertTrue(targetsOfCallInA.contains(functionB)); } public void testCallsiteHasUnknownTarget() { String source = "var A = externalnamespace.prop;\n" + "function B(){A();};\n" + "B();\n"; CallGraph callgraph = compileAndRunForward(source); Function mainFunction = callgraph.getMainFunction(); Function functionB = callgraph.getUniqueFunctionWithName("B"); Callsite callInMain = mainFunction.getCallsitesInFunction().iterator().next(); // B()'s target function is known, and it is functionB assertFalse(callInMain.hasUnknownTarget()); assertEquals("B", callInMain.getAstNode().getFirstChild().getString()); Callsite callInB = functionB.getCallsitesInFunction().iterator().next(); // A() has an unknown target and no known targets assertTrue(callInB.hasUnknownTarget()); assertEquals(0, callInB.getPossibleTargets().size()); } public void testCallsiteHasExternTarget() { String source = "var A = function(){}\n" + "function B(){ExternalFunction(6);};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); Function mainFunction = callgraph.getMainFunction(); Function functionB = callgraph.getUniqueFunctionWithName("B"); Callsite callInMain = mainFunction.getCallsitesInFunction().iterator().next(); // A()'s target function is not an extern assertFalse(callInMain.hasExternTarget()); Callsite callInB = functionB.getCallsitesInFunction().iterator().next(); assertEquals("ExternalFunction", callInB.getAstNode().getFirstChild().getString()); // ExternalFunction(6) is a call to an extern function assertTrue(callInB.hasExternTarget()); assertEquals(0, callInB.getPossibleTargets().size()); } public void testThrowForBackwardOpOnForwardGraph() { String source = "function A(){B()};\n" + "function B(){C();C();};\n" + "function C(){C()};\n" + "A();\n"; CallGraph callgraph = compileAndRunForward(source); Function functionA = callgraph.getUniqueFunctionWithName("A"); UnsupportedOperationException caughtException = null; try { functionA.getCallsitesPossiblyTargetingFunction(); } catch (UnsupportedOperationException e) { caughtException = e; } assertNotNull(caughtException); } public void testThrowForForwardOpOnBackwardGraph() { String source = "function A(){B()};\n" + "function B(){};\n" + "A();\n"; CallGraph callgraph = compileAndRunBackward(source); Function mainFunction = callgraph.getMainFunction(); Function functionA = callgraph.getUniqueFunctionWithName("A"); Callsite callInMain = mainFunction.getCallsitesInFunction().iterator() .next(); UnsupportedOperationException caughtException = null; try { callInMain.getPossibleTargets(); } catch (UnsupportedOperationException e) { return; } fail(); } /** * Helper function that, given a collection of callsites, returns a * collection of the names of the target expression nodes, e.g. * if the callsites are [A(), B.b()], the collection returned is * ["A", "B"]. * * This makes it easier to test methods that return collections of callsites. * * An exception is thrown if the callsite target is not a simple name * (e.g. "a.bar()"). */ private List getCallsiteTargetNames(Collection callsites) { List result = Lists.newArrayList(); for (Callsite callsite : callsites) { Node targetExpressionNode = callsite.getAstNode().getFirstChild(); if (targetExpressionNode.isName()) { result.add(targetExpressionNode.getString()); } else { throw new IllegalStateException("Called getCallsiteTargetNames() on " + "a complex callsite."); } } return result; } private void assertFunctionAliased(boolean aliased, String name) { Function function = currentProcessor.getUniqueFunctionWithName(name); assertEquals(aliased, function.isAliased()); } private CallGraph compileAndRunBackward(String js) { return compileAndRun(SHARED_EXTERNS, js, false, true); } private CallGraph compileAndRunForward(String js) { return compileAndRun(SHARED_EXTERNS, js, true, false); } private CallGraph compileAndRun(String externs, String js, boolean forward, boolean backward) { createBackwardCallGraph = backward; createForwardCallGraph = forward; testSame(externs, js, null); return currentProcessor; } }