/* * Copyright 2008 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.javascript.jscomp.ControlFlowGraph.Branch; import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge; import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import junit.framework.TestCase; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * Tests {@link ControlFlowAnalysis}. * */ public class ControlFlowAnalysisTest extends TestCase { /** * Given an input in JavaScript, test if the control flow analysis * creates the proper control flow graph by comparing the expected * Dot file output. * * @param input Input JavaScript. * @param expected Expected Graphviz Dot file. */ private void testCfg(String input, String expected) { testCfg(input, expected, true); } /** * Gets all the edges of the graph. */ private static List> getAllEdges( ControlFlowGraph cfg) { List> edges = Lists.newArrayList(); for (DiGraphNode n : cfg.getDirectedGraphNodes()) { for (DiGraphEdge e : cfg.getOutEdges(n.getValue())) { edges.add(e); } } return edges; } /** * Gets all the control flow edges from some node with the first token to * some node with the second token. */ private static List> getAllEdges( ControlFlowGraph cfg, int startToken, int endToken) { List> edges = getAllEdges(cfg); Iterator> it = edges.iterator(); while (it.hasNext()) { DiGraphEdge edge = it.next(); Node startNode = edge.getSource().getValue(); Node endNode = edge.getDestination().getValue(); if (startNode == null || endNode == null || startNode.getType() != startToken || endNode.getType() != endToken) { it.remove(); } } return edges; } /** * Gets all the control flow edges of the given type from some node with the * first token to some node with the second token. */ private static List> getAllEdges( ControlFlowGraph cfg, int startToken, int endToken, Branch type) { List> edges = getAllEdges(cfg, startToken, endToken); Iterator> it = edges.iterator(); while (it.hasNext()) { if (type != it.next().getValue()) { it.remove(); } } return edges; } private static boolean isAncestor(Node n, Node maybeDescendent) { for (Node current = n.getFirstChild(); current != null; current = current.getNext()) { if (current == maybeDescendent || isAncestor(current, maybeDescendent)) { return true; } } return false; } /** * Gets all the control flow edges of the given type from some node with * the first token to some node with the second token. * This edge must flow from a parent to one of its descendants. */ private static List> getAllDownEdges( ControlFlowGraph cfg, int startToken, int endToken, Branch type) { List> edges = getAllEdges(cfg, startToken, endToken, type); Iterator> it = edges.iterator(); while (it.hasNext()) { DiGraphEdge edge = it.next(); Node source = edge.getSource().getValue(); Node dest = edge.getDestination().getValue(); if (!isAncestor(source, dest)) { it.remove(); } } return edges; } /** * Assert that there exists a control flow edge of the given type * from some node with the first token to some node with the second token. */ private static void assertNoEdge(ControlFlowGraph cfg, int startToken, int endToken) { assertEquals(0, getAllEdges(cfg, startToken, endToken).size()); } /** * Assert that there exists a control flow edge of the given type * from some node with the first token to some node with the second token. * This edge must flow from a parent to one of its descendants. */ private static void assertDownEdge(ControlFlowGraph cfg, int startToken, int endToken, Branch type) { assertTrue("No down edge found", 0 != getAllDownEdges(cfg, startToken, endToken, type).size()); } /** * Assert that there exists a control flow edge of the given type * from some node with the first token to some node with the second token. * This edge must flow from a node to one of its ancestors. */ private static void assertUpEdge(ControlFlowGraph cfg, int startToken, int endToken, Branch type) { assertTrue("No up edge found", 0 != getAllDownEdges(cfg, endToken, startToken, type).size()); } /** * Assert that there exists a control flow edge of the given type * from some node with the first token to some node with the second token. * This edge must flow between two nodes that are not in the same subtree. */ private static void assertCrossEdge(ControlFlowGraph cfg, int startToken, int endToken, Branch type) { int numDownEdges = getAllDownEdges(cfg, startToken, endToken, type).size(); int numUpEdges = getAllDownEdges(cfg, endToken, startToken, type).size(); int numEdges = getAllEdges(cfg, startToken, endToken, type).size(); assertTrue("No cross edges found", numDownEdges + numUpEdges < numEdges); } /** * Assert that there exists a control flow edge of the given type * from some node with the first token to the return node. */ private static void assertReturnEdge(ControlFlowGraph cfg, int startToken) { List> edges = getAllEdges(cfg); for (DiGraphEdge edge : edges) { Node source = edge.getSource().getValue(); DiGraphNode dest = edge.getDestination(); if (source.getType() == startToken && cfg.isImplicitReturn(dest)) { return; } } fail("No return edge found"); } /** * Assert that there exists no control flow edge of the given type * from some node with the first token to the return node. */ private static void assertNoReturnEdge(ControlFlowGraph cfg, int startToken) { List> edges = getAllEdges(cfg); for (DiGraphEdge edge : edges) { Node source = edge.getSource().getValue(); DiGraphNode dest = edge.getDestination(); if (source.getType() == startToken) { assertTrue("Token " + startToken + " should not have an out going" + " edge to the implicit return", !cfg.isImplicitReturn(dest)); return; } } } /** * Given an input in JavaScript, get a control flow graph for it. * * @param input Input JavaScript. */ private ControlFlowGraph createCfg(String input, boolean runSynBlockPass) { Compiler compiler = new Compiler(); ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, true, true); Node root = compiler.parseSyntheticCode("cfgtest", input); if (runSynBlockPass) { CreateSyntheticBlocks pass = new CreateSyntheticBlocks( compiler, "START", "END"); pass.process(null, root); } cfa.process(null, root); return cfa.getCfg(); } private ControlFlowGraph createCfg(String input) { return createCfg(input, false); } /** * Given an input in JavaScript, test if the control flow analysis * creates the proper control flow graph by comparing the expected * Dot file output. * * @param input Input JavaScript. * @param expected Expected Graphviz Dot file. * @param shouldTraverseFunctions Whether to traverse functions when * constructing the CFG (true by default). Passed in to the * constructor of {@link ControlFlowAnalysis}. */ private void testCfg(String input, String expected, boolean shouldTraverseFunctions) { Compiler compiler = new Compiler(); ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, shouldTraverseFunctions, true); Node root = compiler.parseSyntheticCode("cfgtest", input); cfa.process(null, root); ControlFlowGraph cfg = cfa.getCfg(); try { assertEquals(expected, DotFormatter.toDot(root, cfg)); } catch (java.io.IOException e) { fail("Tests failed with IOExceptions"); } } public void testSimpleStatements() { String src = "var a; a = a; a = a"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.SCRIPT, Token.VAR, Branch.UNCOND); assertCrossEdge(cfg, Token.VAR, Token.EXPR_RESULT, Branch.UNCOND); assertCrossEdge(cfg, Token.EXPR_RESULT, Token.EXPR_RESULT, Branch.UNCOND); } // Test a simple IF control flow. public void testSimpleIf() { String src = "var x; if (x) { x() } else { x() };"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.SCRIPT, Token.VAR, Branch.UNCOND); assertCrossEdge(cfg, Token.VAR, Token.IF, Branch.UNCOND); assertDownEdge(cfg, Token.IF, Token.BLOCK, Branch.ON_TRUE); assertDownEdge(cfg, Token.BLOCK, Token.EXPR_RESULT, Branch.UNCOND); assertNoEdge(cfg, Token.EXPR_RESULT, Token.CALL); assertDownEdge(cfg, Token.IF, Token.BLOCK, Branch.ON_FALSE); assertReturnEdge(cfg, Token.EMPTY); } public void testBreakingBlock() { // BUG #1382217 String src = "X: { while(1) { break } }"; ControlFlowGraph cfg = createCfg(src); assertUpEdge(cfg, Token.BREAK, Token.BLOCK, Branch.UNCOND); } public void testBreakingTryBlock() { String src = "a: try { break a; } finally {} if(x) {}"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.BREAK, Token.IF, Branch.UNCOND); src = "a: try {} finally {break a;} if(x) {}"; cfg = createCfg(src); assertCrossEdge(cfg, Token.BREAK, Token.IF, Branch.UNCOND); src = "a: try {} catch(e) {break a;} if(x) {}"; cfg = createCfg(src); assertCrossEdge(cfg, Token.BREAK, Token.IF, Branch.UNCOND); } public void testWithStatement() { String src = "var x, y; with(x) { y() }"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.WITH, Token.BLOCK, Branch.UNCOND); assertNoEdge(cfg, Token.WITH, Token.NAME); assertNoEdge(cfg, Token.NAME, Token.BLOCK); assertDownEdge(cfg, Token.BLOCK, Token.EXPR_RESULT, Branch.UNCOND); assertReturnEdge(cfg, Token.EXPR_RESULT); } // Test a simple WHILE control flow with BREAKs. public void testSimpleWhile() { String src = "var x; while (x) { x(); if (x) { break; } x() }"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.WHILE, Token.BLOCK, Branch.ON_TRUE); assertDownEdge(cfg, Token.BLOCK, Token.EXPR_RESULT, Branch.UNCOND); assertDownEdge(cfg, Token.IF, Token.BLOCK, Branch.ON_TRUE); assertReturnEdge(cfg, Token.BREAK); } public void testSimpleSwitch() { String src = "var x; switch(x){ case(1): x(); case('x'): x(); break" + "; default: x();}"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.VAR, Token.SWITCH, Branch.UNCOND); assertNoEdge(cfg, Token.SWITCH, Token.NAME); // Transfer between cases and default. assertDownEdge(cfg, Token.SWITCH, Token.CASE, Branch.UNCOND); assertCrossEdge(cfg, Token.CASE, Token.CASE, Branch.ON_FALSE); assertCrossEdge(cfg, Token.CASE, Token.DEFAULT_CASE, Branch.ON_FALSE); // Within each case. assertDownEdge(cfg, Token.CASE, Token.BLOCK, Branch.ON_TRUE); assertDownEdge(cfg, Token.BLOCK, Token.EXPR_RESULT, Branch.UNCOND); assertNoEdge(cfg, Token.EXPR_RESULT, Token.CALL); assertNoEdge(cfg, Token.CALL, Token.NAME); } public void testSimpleNoDefault() { String src = "var x; switch(x){ case(1): break; } x();"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.CASE, Token.EXPR_RESULT, Branch.ON_FALSE); } public void testSwitchDefaultFirst() { // DEFAULT appears first. But it is should evaluated last. String src = "var x; switch(x){ default: break; case 1: break; }"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.SWITCH, Token.CASE, Branch.UNCOND); assertCrossEdge(cfg, Token.CASE, Token.DEFAULT_CASE, Branch.ON_FALSE); } public void testSwitchDefaultInMiddle() { // DEFAULT appears in the middle. But it is should evaluated last. String src = "var x; switch(x){ case 1: break; default: break; " + "case 2: break; }"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.SWITCH, Token.CASE, Branch.UNCOND); assertCrossEdge(cfg, Token.CASE, Token.CASE, Branch.ON_FALSE); assertCrossEdge(cfg, Token.CASE, Token.DEFAULT_CASE, Branch.ON_FALSE); } public void testSwitchEmpty() { // DEFAULT appears first. But it is should evaluated last. String src = "var x; switch(x){}; x()"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.SWITCH, Token.EMPTY, Branch.UNCOND); assertCrossEdge(cfg, Token.EMPTY, Token.EXPR_RESULT, Branch.UNCOND); } public void testReturnThrowingException() { String src = "function f() {try { return a(); } catch (e) {e()}}"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.RETURN, Token.BLOCK, Branch.ON_EX); assertDownEdge(cfg, Token.BLOCK, Token.CATCH, Branch.UNCOND); } // Test a simple FOR loop. public void testSimpleFor() { String src = "var a; for (var x = 0; x < 100; x++) { a(); }"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"VAR\"];\n" + " node1 -> node3 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 [label=\"FOR\"];\n" + " node0 -> node4 [weight=1];\n" + " node4 -> node3 [weight=1];\n" + " node5 [label=\"NAME\"];\n" + " node3 -> node5 [weight=1];\n" + " node6 [label=\"NUMBER\"];\n" + " node5 -> node6 [weight=1];\n" + " node3 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node7 [label=\"LT\"];\n" + " node4 -> node7 [weight=1];\n" + " node8 [label=\"NAME\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"NUMBER\"];\n" + " node7 -> node9 [weight=1];\n" + " node10 [label=\"INC\"];\n" + " node4 -> node10 [weight=1];\n" + " node11 [label=\"NAME\"];\n" + " node10 -> node11 [weight=1];\n" + " node10 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node12 [label=\"BLOCK\"];\n" + " node4 -> node12 [weight=1];\n" + " node13 [label=\"EXPR_RESULT\"];\n" + " node12 -> node13 [weight=1];\n" + " node14 [label=\"CALL\"];\n" + " node13 -> node14 [weight=1];\n" + " node15 [label=\"NAME\"];\n" + " node14 -> node15 [weight=1];\n" + " node13 -> node10 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node12 -> node13 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> RETURN " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node12 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testSimpleForWithContinue() { String src = "var a; for (var x = 0; x < 100; x++) {a();continue;a()}"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"VAR\"];\n" + " node1 -> node3 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 [label=\"FOR\"];\n" + " node0 -> node4 [weight=1];\n" + " node4 -> node3 [weight=1];\n" + " node5 [label=\"NAME\"];\n" + " node3 -> node5 [weight=1];\n" + " node6 [label=\"NUMBER\"];\n" + " node5 -> node6 [weight=1];\n" + " node3 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node7 [label=\"LT\"];\n" + " node4 -> node7 [weight=1];\n" + " node8 [label=\"NAME\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"NUMBER\"];\n" + " node7 -> node9 [weight=1];\n" + " node10 [label=\"INC\"];\n" + " node4 -> node10 [weight=1];\n" + " node11 [label=\"NAME\"];\n" + " node10 -> node11 [weight=1];\n" + " node10 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node12 [label=\"BLOCK\"];\n" + " node4 -> node12 [weight=1];\n" + " node13 [label=\"EXPR_RESULT\"];\n" + " node12 -> node13 [weight=1];\n" + " node14 [label=\"CALL\"];\n" + " node13 -> node14 [weight=1];\n" + " node15 [label=\"NAME\"];\n" + " node14 -> node15 [weight=1];\n" + " node16 [label=\"CONTINUE\"];\n" + " node13 -> node16 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node12 -> node16 [weight=1];\n" + " node16 -> node10 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node17 [label=\"EXPR_RESULT\"];\n" + " node12 -> node17 [weight=1];\n" + " node18 [label=\"CALL\"];\n" + " node17 -> node18 [weight=1];\n" + " node19 [label=\"NAME\"];\n" + " node18 -> node19 [weight=1];\n" + " node17 -> node10 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node12 -> node13 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> RETURN " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node12 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testNestedFor() { // This is tricky as the inner FOR branches to "x++" ON_FALSE. String src = "var a,b;a();for(var x=0;x<100;x++){for(var y=0;y<100;y++){" + "continue;b();}}"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"NAME\"];\n" + " node1 -> node3 [weight=1];\n" + " node4 [label=\"EXPR_RESULT\"];\n" + " node1 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node4 [weight=1];\n" + " node5 [label=\"CALL\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"NAME\"];\n" + " node5 -> node6 [weight=1];\n" + " node7 [label=\"VAR\"];\n" + " node4 -> node7 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 [label=\"FOR\"];\n" + " node0 -> node8 [weight=1];\n" + " node8 -> node7 [weight=1];\n" + " node9 [label=\"NAME\"];\n" + " node7 -> node9 [weight=1];\n" + " node10 [label=\"NUMBER\"];\n" + " node9 -> node10 [weight=1];\n" + " node7 -> node8 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node11 [label=\"LT\"];\n" + " node8 -> node11 [weight=1];\n" + " node12 [label=\"NAME\"];\n" + " node11 -> node12 [weight=1];\n" + " node13 [label=\"NUMBER\"];\n" + " node11 -> node13 [weight=1];\n" + " node14 [label=\"INC\"];\n" + " node8 -> node14 [weight=1];\n" + " node15 [label=\"NAME\"];\n" + " node14 -> node15 [weight=1];\n" + " node14 -> node8 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node16 [label=\"BLOCK\"];\n" + " node8 -> node16 [weight=1];\n" + " node17 [label=\"FOR\"];\n" + " node16 -> node17 [weight=1];\n" + " node18 [label=\"VAR\"];\n" + " node17 -> node18 [weight=1];\n" + " node19 [label=\"NAME\"];\n" + " node18 -> node19 [weight=1];\n" + " node20 [label=\"NUMBER\"];\n" + " node19 -> node20 [weight=1];\n" + " node18 -> node17 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node21 [label=\"LT\"];\n" + " node17 -> node21 [weight=1];\n" + " node22 [label=\"NAME\"];\n" + " node21 -> node22 [weight=1];\n" + " node23 [label=\"NUMBER\"];\n" + " node21 -> node23 [weight=1];\n" + " node24 [label=\"INC\"];\n" + " node17 -> node24 [weight=1];\n" + " node25 [label=\"NAME\"];\n" + " node24 -> node25 [weight=1];\n" + " node24 -> node17 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node26 [label=\"BLOCK\"];\n" + " node17 -> node26 [weight=1];\n" + " node27 [label=\"CONTINUE\"];\n" + " node26 -> node27 [weight=1];\n" + " node27 -> node24 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node28 [label=\"EXPR_RESULT\"];\n" + " node26 -> node28 [weight=1];\n" + " node29 [label=\"CALL\"];\n" + " node28 -> node29 [weight=1];\n" + " node30 [label=\"NAME\"];\n" + " node29 -> node30 [weight=1];\n" + " node28 -> node24 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node26 -> node27 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node17 -> node14 " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node17 -> node26 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node16 -> node18 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 -> RETURN " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 -> node16 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testNestedDoWithBreak() { // The BREAK branches to a() with UNCOND. String src = "var a;do{do{break}while(a);do{a()}while(a)}while(a);"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"BLOCK\"];\n" + " node1 -> node3 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 [label=\"DO\"];\n" + " node0 -> node4 [weight=1];\n" + " node4 -> node3 [weight=1];\n" + " node5 [label=\"DO\"];\n" + " node3 -> node5 [weight=1];\n" + " node6 [label=\"BLOCK\"];\n" + " node5 -> node6 [weight=1];\n" + " node7 [label=\"BREAK\"];\n" + " node6 -> node7 [weight=1];\n" + " node8 [label=\"BLOCK\"];\n" + " node7 -> node8 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node6 -> node7 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node9 [label=\"NAME\"];\n" + " node5 -> node9 [weight=1];\n" + " node5 -> node6 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node5 -> node8 " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node10 [label=\"DO\"];\n" + " node3 -> node10 [weight=1];\n" + " node10 -> node8 [weight=1];\n" + " node11 [label=\"EXPR_RESULT\"];\n" + " node8 -> node11 [weight=1];\n" + " node12 [label=\"CALL\"];\n" + " node11 -> node12 [weight=1];\n" + " node13 [label=\"NAME\"];\n" + " node12 -> node13 [weight=1];\n" + " node11 -> node10 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 -> node11 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node14 [label=\"NAME\"];\n" + " node10 -> node14 [weight=1];\n" + " node10 -> node4 " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node10 -> node8 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node3 -> node6 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node15 [label=\"NAME\"];\n" + " node4 -> node15 [weight=1];\n" + " node4 -> RETURN " + "[label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node3 " + "[label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testForIn() { String src = "var a,b;for(a in b){a()};"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"NAME\"];\n" + " node1 -> node3 [weight=1];\n" + " node4 [label=\"NAME\"];\n" + " node1 -> node4 [label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node5 [label=\"FOR\"];\n" + " node0 -> node5 [weight=1];\n" + " node6 [label=\"NAME\"];\n" + " node5 -> node6 [weight=1];\n" + " node5 -> node4 [weight=1];\n" + " node4 -> node5 [label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node7 [label=\"BLOCK\"];\n" + " node5 -> node7 [weight=1];\n" + " node8 [label=\"EXPR_RESULT\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"CALL\"];\n" + " node8 -> node9 [weight=1];\n" + " node10 [label=\"NAME\"];\n" + " node9 -> node10 [weight=1];\n" + " node8 -> node5 [label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node7 -> node8 [label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node11 [label=\"EMPTY\"];\n" + " node5 -> node11 [label=\"ON_FALSE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node5 -> node7 [label=\"ON_TRUE\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node11 [weight=1];\n" + " node11 -> RETURN [label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testThrow() { String src = "function f() { throw 1; f() }"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"FUNCTION\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"PARAM_LIST\"];\n" + " node1 -> node3 [weight=1];\n" + " node4 [label=\"BLOCK\"];\n" + " node1 -> node4 [weight=1];\n" + " node5 [label=\"THROW\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"NUMBER\"];\n" + " node5 -> node6 [weight=1];\n" + " node7 [label=\"EXPR_RESULT\"];\n" + " node4 -> node7 [weight=1];\n" + " node8 [label=\"CALL\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"NAME\"];\n" + " node8 -> node9 [weight=1];\n" + " node7 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node5 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } // Test a simple FUNCTION. public void testSimpleFunction() { String src = "function f() { f() } f()"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"FUNCTION\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"PARAM_LIST\"];\n" + " node1 -> node3 [weight=1];\n" + " node4 [label=\"BLOCK\"];\n" + " node1 -> node4 [weight=1];\n" + " node5 [label=\"EXPR_RESULT\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"CALL\"];\n" + " node5 -> node6 [weight=1];\n" + " node7 [label=\"NAME\"];\n" + " node6 -> node7 [weight=1];\n" + " node5 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node5 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node4 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 [label=\"EXPR_RESULT\"];\n" + " node0 -> node8 [weight=1];\n" + " node9 [label=\"CALL\"];\n" + " node8 -> node9 [weight=1];\n" + " node10 [label=\"NAME\"];\n" + " node9 -> node10 [weight=1];\n" + " node8 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node8 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testSimpleCatch() { String src = "try{ throw x; x(); x['stuff']; x.x; x} catch (e) { e() }"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"TRY\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"BLOCK\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"THROW\"];\n" + " node2 -> node3 [weight=1];\n" + " node4 [label=\"NAME\"];\n" + " node3 -> node4 [weight=1];\n" + " node5 [label=\"BLOCK\"];\n" + " node3 -> node5 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node6 [label=\"EXPR_RESULT\"];\n" + " node2 -> node6 [weight=1];\n" + " node7 [label=\"CALL\"];\n" + " node6 -> node7 [weight=1];\n" + " node8 [label=\"NAME\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"EXPR_RESULT\"];\n" + " node6 -> node5 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node6 -> node9 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> node9 [weight=1];\n" + " node10 [label=\"GETELEM\"];\n" + " node9 -> node10 [weight=1];\n" + " node11 [label=\"NAME\"];\n" + " node10 -> node11 [weight=1];\n" + " node12 [label=\"STRING\"];\n" + " node10 -> node12 [weight=1];\n" + " node13 [label=\"EXPR_RESULT\"];\n" + " node9 -> node13 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node9 -> node5 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> node13 [weight=1];\n" + " node14 [label=\"GETPROP\"];\n" + " node13 -> node14 [weight=1];\n" + " node15 [label=\"NAME\"];\n" + " node14 -> node15 [weight=1];\n" + " node16 [label=\"STRING\"];\n" + " node14 -> node16 [weight=1];\n" + " node17 [label=\"EXPR_RESULT\"];\n" + " node13 -> node17 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node13 -> node5 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> node17 [weight=1];\n" + " node18 [label=\"NAME\"];\n" + " node17 -> node18 [weight=1];\n" + " node17 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> node3 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node5 [weight=1];\n" + " node19 [label=\"CATCH\"];\n" + " node5 -> node19 [weight=1];\n" + " node20 [label=\"NAME\"];\n" + " node19 -> node20 [weight=1];\n" + " node21 [label=\"BLOCK\"];\n" + " node19 -> node21 [weight=1];\n" + " node22 [label=\"EXPR_RESULT\"];\n" + " node21 -> node22 [weight=1];\n" + " node23 [label=\"CALL\"];\n" + " node22 -> node23 [weight=1];\n" + " node24 [label=\"NAME\"];\n" + " node23 -> node24 [weight=1];\n" + " node22 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node21 -> node22 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node19 -> node21 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node5 -> node19 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node2 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testFunctionWithinTry() { // Make sure we don't search for the handler outside of the function. String src = "try { function f() {throw 1;} } catch (e) { }"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"TRY\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"BLOCK\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"FUNCTION\"];\n" + " node2 -> node3 [weight=1];\n" + " node4 [label=\"NAME\"];\n" + " node3 -> node4 [weight=1];\n" + " node5 [label=\"PARAM_LIST\"];\n" + " node3 -> node5 [weight=1];\n" + " node6 [label=\"BLOCK\"];\n" + " node3 -> node6 [weight=1];\n" + " node7 [label=\"THROW\"];\n" + " node6 -> node7 [weight=1];\n" + " node8 [label=\"NUMBER\"];\n" + " node7 -> node8 [weight=1];\n" + " node6 -> node7 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node3 -> node6 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node9 [label=\"BLOCK\"];\n" + " node1 -> node9 [weight=1];\n" + " node10 [label=\"CATCH\"];\n" + " node9 -> node10 [weight=1];\n" + " node11 [label=\"NAME\"];\n" + " node10 -> node11 [weight=1];\n" + " node12 [label=\"BLOCK\"];\n" + " node10 -> node12 [weight=1];\n" + " node12 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node10 -> node12 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node9 -> node10 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node2 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testNestedCatch() { // Make sure we are going to the right handler. String src = "try{try{throw 1;}catch(e){throw 2}}catch(f){}"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"TRY\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"BLOCK\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"TRY\"];\n" + " node2 -> node3 [weight=1];\n" + " node4 [label=\"BLOCK\"];\n" + " node3 -> node4 [weight=1];\n" + " node5 [label=\"THROW\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"NUMBER\"];\n" + " node5 -> node6 [weight=1];\n" + " node7 [label=\"BLOCK\"];\n" + " node5 -> node7 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node5 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node3 -> node7 [weight=1];\n" + " node8 [label=\"CATCH\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"NAME\"];\n" + " node8 -> node9 [weight=1];\n" + " node10 [label=\"BLOCK\"];\n" + " node8 -> node10 [weight=1];\n" + " node11 [label=\"THROW\"];\n" + " node10 -> node11 [weight=1];\n" + " node12 [label=\"NUMBER\"];\n" + " node11 -> node12 [weight=1];\n" + " node13 [label=\"BLOCK\"];\n" + " node11 -> node13 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node10 -> node11 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 -> node10 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node7 -> node8 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node3 -> node4 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> node3 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node13 [weight=1];\n" + " node14 [label=\"CATCH\"];\n" + " node13 -> node14 [weight=1];\n" + " node15 [label=\"NAME\"];\n" + " node14 -> node15 [weight=1];\n" + " node16 [label=\"BLOCK\"];\n" + " node14 -> node16 [weight=1];\n" + " node16 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node14 -> node16 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node13 -> node14 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node2 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testSimpleFinally() { String src = "try{var x; foo()}finally{}"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.TRY, Token.BLOCK, Branch.UNCOND); assertDownEdge(cfg, Token.BLOCK, Token.VAR, Branch.UNCOND); // VAR to FINALLY. assertCrossEdge(cfg, Token.EXPR_RESULT, Token.BLOCK, Branch.UNCOND); // No CATCH to FINALLY. assertNoEdge(cfg, Token.BLOCK, Token.BLOCK); } public void testSimpleCatchFinally() { // Make sure we are going to the right handler. String src = "try{ if(a){throw 1}else{a} } catch(e){a}finally{a}"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"TRY\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"BLOCK\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"IF\"];\n" + " node2 -> node3 [weight=1];\n" + " node4 [label=\"NAME\"];\n" + " node3 -> node4 [weight=1];\n" + " node5 [label=\"BLOCK\"];\n" + " node3 -> node5 [weight=1];\n" + " node6 [label=\"THROW\"];\n" + " node5 -> node6 [weight=1];\n" + " node7 [label=\"NUMBER\"];\n" + " node6 -> node7 [weight=1];\n" + " node8 [label=\"BLOCK\"];\n" + " node6 -> node8 [label=\"ON_EX\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node5 -> node6 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node9 [label=\"BLOCK\"];\n" + " node3 -> node9 [weight=1];\n" + " node10 [label=\"EXPR_RESULT\"];\n" + " node9 -> node10 [weight=1];\n" + " node11 [label=\"NAME\"];\n" + " node10 -> node11 [weight=1];\n" + " node12 [label=\"BLOCK\"];\n" + " node10 -> node12 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node9 -> node10 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node3 -> node5 [label=\"ON_TRUE\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node3 -> node9 [label=\"ON_FALSE\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node2 -> node3 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node8 [weight=1];\n" + " node13 [label=\"CATCH\"];\n" + " node8 -> node13 [weight=1];\n" + " node14 [label=\"NAME\"];\n" + " node13 -> node14 [weight=1];\n" + " node15 [label=\"BLOCK\"];\n" + " node13 -> node15 [weight=1];\n" + " node16 [label=\"EXPR_RESULT\"];\n" + " node15 -> node16 [weight=1];\n" + " node17 [label=\"NAME\"];\n" + " node16 -> node17 [weight=1];\n" + " node16 -> node12 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node15 -> node16 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node13 -> node15 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node8 -> node13 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node12 [weight=1];\n" + " node18 [label=\"EXPR_RESULT\"];\n" + " node12 -> node18 [weight=1];\n" + " node19 [label=\"NAME\"];\n" + " node18 -> node19 [weight=1];\n" + " node18 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node12 -> node18 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node1 -> node2 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testComplicatedFinally2() { // Now the most nasty case..... String src = "while(1){try{" + "if(a){a;continue;}else if(b){b;break;} else if(c) throw 1; else a}" + "catch(e){}finally{c()}bar}foo"; ControlFlowGraph cfg = createCfg(src); // Focus only on the ON_EX edges. assertCrossEdge(cfg, Token.CONTINUE, Token.BLOCK, Branch.UNCOND); assertCrossEdge(cfg, Token.BREAK, Token.BLOCK, Branch.UNCOND); assertCrossEdge(cfg, Token.THROW, Token.BLOCK, Branch.ON_EX); } public void testDeepNestedBreakwithFinally() { String src = "X:while(1){try{while(2){try{var a;break X;}" + "finally{}}}finally{}}"; ControlFlowGraph cfg = createCfg(src); assertDownEdge(cfg, Token.WHILE, Token.BLOCK, Branch.ON_TRUE); assertDownEdge(cfg, Token.BLOCK, Token.TRY, Branch.UNCOND); assertDownEdge(cfg, Token.BLOCK, Token.VAR, Branch.UNCOND); // BREAK to FINALLY. assertCrossEdge(cfg, Token.BREAK, Token.BLOCK, Branch.UNCOND); // FINALLY to FINALLY. assertCrossEdge(cfg, Token.BLOCK, Token.BLOCK, Branch.ON_EX); assertCrossEdge(cfg, Token.WHILE, Token.BLOCK, Branch.ON_FALSE); assertReturnEdge(cfg, Token.BLOCK); } public void testDeepNestedFinally() { String src = "try{try{try{throw 1}" + "finally{1;var a}}finally{2;if(a);}}finally{3;a()}"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.THROW, Token.BLOCK, Branch.ON_EX); assertCrossEdge(cfg, Token.VAR, Token.BLOCK, Branch.UNCOND); assertCrossEdge(cfg, Token.IF, Token.BLOCK, Branch.ON_EX); } public void testReturn() { String src = "function f() { return; }"; ControlFlowGraph cfg = createCfg(src); assertReturnEdge(cfg, Token.RETURN); } public void testReturnInFinally() { String src = "function f(x){ try{} finally {return x;} }"; ControlFlowGraph cfg = createCfg(src); assertReturnEdge(cfg, Token.RETURN); } public void testReturnInFinally2() { String src = "function f(x){" + " try{ try{}finally{var dummy; return x;} } finally {} }"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.VAR, Token.RETURN, Branch.UNCOND); assertCrossEdge(cfg, Token.RETURN, Token.BLOCK, Branch.UNCOND); assertReturnEdge(cfg, Token.BLOCK); assertNoReturnEdge(cfg, Token.RETURN); } public void testReturnInTry() { String src = "function f(x){ try{x; return x()} finally {} var y;}"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.EXPR_RESULT, Token.RETURN, Branch.UNCOND); assertCrossEdge(cfg, Token.RETURN, Token.BLOCK, Branch.UNCOND); assertCrossEdge(cfg, Token.BLOCK, Token.VAR, Branch.UNCOND); assertReturnEdge(cfg, Token.VAR); assertReturnEdge(cfg, Token.BLOCK); assertNoReturnEdge(cfg, Token.RETURN); } public void testOptionNotToTraverseFunctions() { String src = "var x = 1; function f() { x = null; }"; String expectedWhenNotTraversingFunctions = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"NUMBER\"];\n" + " node2 -> node3 [weight=1];\n" + " node1 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 [label=\"FUNCTION\"];\n" + " node0 -> node4 [weight=1];\n" + " node5 [label=\"NAME\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"PARAM_LIST\"];\n" + " node4 -> node6 [weight=1];\n" + " node7 [label=\"BLOCK\"];\n" + " node4 -> node7 [weight=1];\n" + " node8 [label=\"EXPR_RESULT\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"ASSIGN\"];\n" + " node8 -> node9 [weight=1];\n" + " node10 [label=\"NAME\"];\n" + " node9 -> node10 [weight=1];\n" + " node11 [label=\"NULL\"];\n" + " node9 -> node11 [weight=1];\n" + " node0 -> node1 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"VAR\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"NAME\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"NUMBER\"];\n" + " node2 -> node3 [weight=1];\n" + " node1 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 [label=\"FUNCTION\"];\n" + " node0 -> node4 [weight=1];\n" + " node5 [label=\"NAME\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"PARAM_LIST\"];\n" + " node4 -> node6 [weight=1];\n" + " node7 [label=\"BLOCK\"];\n" + " node4 -> node7 [weight=1];\n" + " node8 [label=\"EXPR_RESULT\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"ASSIGN\"];\n" + " node8 -> node9 [weight=1];\n" + " node10 [label=\"NAME\"];\n" + " node9 -> node10 [weight=1];\n" + " node11 [label=\"NULL\"];\n" + " node9 -> node11 [weight=1];\n" + " node8 -> RETURN " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node7 -> node8 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node4 -> node7 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 " + "[label=\"UNCOND\", fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); testCfg(src, expectedWhenNotTraversingFunctions, false); } public void testInstanceOf() { String src = "try { x instanceof 'x' } catch (e) { }"; ControlFlowGraph cfg = createCfg(src, true); assertCrossEdge(cfg, Token.EXPR_RESULT, Token.BLOCK, Branch.ON_EX); } public void testSynBlock() { String src = "START(); var x; END(); var y;"; ControlFlowGraph cfg = createCfg(src, true); assertCrossEdge(cfg, Token.BLOCK, Token.EXPR_RESULT, Branch.SYN_BLOCK); } public void testPartialTraversalOfScope() { Compiler compiler = new Compiler(); ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, true, true); Node script1 = compiler.parseSyntheticCode("cfgtest", "var foo;"); Node script2 = compiler.parseSyntheticCode("cfgtest2", "var bar;"); Node root = new Node(Token.BLOCK, script1, script2); cfa.process(null, script1); ControlFlowGraph cfg = cfa.getCfg(); assertNotNull(cfg.getNode(script1)); assertNull(cfg.getNode(script2)); } public void testForLoopOrder() { assertNodeOrder( createCfg("for (var i = 0; i < 5; i++) { var x = 3; } if (true) {}"), Lists.newArrayList( Token.SCRIPT, Token.VAR, Token.FOR, Token.BLOCK, Token.VAR, Token.INC /* i++ */, Token.IF, Token.BLOCK)); } public void testLabelledForInLoopOrder() { assertNodeOrder( createCfg("var i = 0; var y = {}; " + "label: for (var x in y) { " + " if (x) { break label; } else { i++ } x(); }"), Lists.newArrayList( Token.SCRIPT, Token.VAR, Token.VAR, Token.NAME, Token.FOR, Token.BLOCK, Token.IF, Token.BLOCK, Token.BREAK, Token.BLOCK, Token.EXPR_RESULT, Token.EXPR_RESULT)); } public void testLocalFunctionOrder() { ControlFlowGraph cfg = createCfg("function f() { while (x) { x++; } } var x = 3;"); assertNodeOrder( cfg, Lists.newArrayList( Token.SCRIPT, Token.VAR, Token.FUNCTION, Token.BLOCK, Token.WHILE, Token.BLOCK, Token.EXPR_RESULT)); } public void testDoWhileOrder() { assertNodeOrder( createCfg("do { var x = 3; } while (true); void x;"), Lists.newArrayList( Token.SCRIPT, Token.BLOCK, Token.VAR, Token.DO, Token.EXPR_RESULT)); } public void testBreakInFinally1() { String src = "f = function() {\n" + " var action;\n" + " a: {\n" + " var proto = null;\n" + " try {\n" + " proto = new Proto\n" + " } finally {\n" + " action = proto;\n" + " break a\n" + // Remove this... " }\n" + " }\n" + " alert(action)\n" + // but not this. "};"; String expected = "digraph AST {\n" + " node [color=lightblue2, style=filled];\n" + " node0 [label=\"SCRIPT\"];\n" + " node1 [label=\"EXPR_RESULT\"];\n" + " node0 -> node1 [weight=1];\n" + " node2 [label=\"ASSIGN\"];\n" + " node1 -> node2 [weight=1];\n" + " node3 [label=\"NAME\"];\n" + " node2 -> node3 [weight=1];\n" + " node4 [label=\"FUNCTION\"];\n" + " node2 -> node4 [weight=1];\n" + " node5 [label=\"NAME\"];\n" + " node4 -> node5 [weight=1];\n" + " node6 [label=\"PARAM_LIST\"];\n" + " node4 -> node6 [weight=1];\n" + " node7 [label=\"BLOCK\"];\n" + " node4 -> node7 [weight=1];\n" + " node8 [label=\"VAR\"];\n" + " node7 -> node8 [weight=1];\n" + " node9 [label=\"NAME\"];\n" + " node8 -> node9 [weight=1];\n" + " node10 [label=\"LABEL\"];\n" + " node7 -> node10 [weight=1];\n" + " node11 [label=\"LABEL_NAME\"];\n" + " node10 -> node11 [weight=1];\n" + " node12 [label=\"BLOCK\"];\n" + " node10 -> node12 [weight=1];\n" + " node13 [label=\"VAR\"];\n" + " node12 -> node13 [weight=1];\n" + " node14 [label=\"NAME\"];\n" + " node13 -> node14 [weight=1];\n" + " node15 [label=\"NULL\"];\n" + " node14 -> node15 [weight=1];\n" + " node16 [label=\"TRY\"];\n" + " node12 -> node16 [weight=1];\n" + " node17 [label=\"BLOCK\"];\n" + " node16 -> node17 [weight=1];\n" + " node18 [label=\"EXPR_RESULT\"];\n" + " node17 -> node18 [weight=1];\n" + " node19 [label=\"ASSIGN\"];\n" + " node18 -> node19 [weight=1];\n" + " node20 [label=\"NAME\"];\n" + " node19 -> node20 [weight=1];\n" + " node21 [label=\"NEW\"];\n" + " node19 -> node21 [weight=1];\n" + " node22 [label=\"NAME\"];\n" + " node21 -> node22 [weight=1];\n" + " node23 [label=\"BLOCK\"];\n" + " node16 -> node23 [weight=1];\n" + " node24 [label=\"BLOCK\"];\n" + " node16 -> node24 [weight=1];\n" + " node25 [label=\"EXPR_RESULT\"];\n" + " node24 -> node25 [weight=1];\n" + " node26 [label=\"ASSIGN\"];\n" + " node25 -> node26 [weight=1];\n" + " node27 [label=\"NAME\"];\n" + " node26 -> node27 [weight=1];\n" + " node28 [label=\"NAME\"];\n" + " node26 -> node28 [weight=1];\n" + " node29 [label=\"BREAK\"];\n" + " node24 -> node29 [weight=1];\n" + " node30 [label=\"LABEL_NAME\"];\n" + " node29 -> node30 [weight=1];\n" + " node31 [label=\"EXPR_RESULT\"];\n" + " node7 -> node31 [weight=1];\n" + " node32 [label=\"CALL\"];\n" + " node31 -> node32 [weight=1];\n" + " node33 [label=\"NAME\"];\n" + " node32 -> node33 [weight=1];\n" + " node34 [label=\"NAME\"];\n" + " node32 -> node34 [weight=1];\n" + " node1 -> RETURN [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + " node0 -> node1 [label=\"UNCOND\", " + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n" + "}\n"; testCfg(src, expected); } public void testBreakInFinally2() { String src = "var action;\n" + "a: {\n" + " var proto = null;\n" + " try {\n" + " proto = new Proto\n" + " } finally {\n" + " action = proto;\n" + " break a\n" + " }\n" + "}\n" + "alert(action)\n"; ControlFlowGraph cfg = createCfg(src); assertCrossEdge(cfg, Token.BREAK, Token.EXPR_RESULT, Branch.UNCOND); assertNoEdge(cfg, Token.BREAK, Token.BLOCK); } /** * Asserts the priority order of CFG nodes. * * Checks that the node type of the highest-priority node matches the * first element of the list, the type of the second node matches the * second element of the list, and so on. * * @param cfg The control flow graph. * @param nodeTypes The expected node types, in order. */ private void assertNodeOrder(ControlFlowGraph cfg, List nodeTypes) { List> cfgNodes = Lists.newArrayList(cfg.getDirectedGraphNodes()); Collections.sort(cfgNodes, cfg.getOptionalNodeComparator(true)); // IMPLICIT RETURN must always be last. Node implicitReturn = cfgNodes.remove(cfgNodes.size() - 1).getValue(); assertNull(implicitReturn == null ? "null" : implicitReturn.toStringTree(), implicitReturn); assertEquals("Wrong number of CFG nodes", nodeTypes.size(), cfgNodes.size()); for (int i = 0; i < cfgNodes.size(); i++) { int expectedType = nodeTypes.get(i); int actualType = cfgNodes.get(i).getValue().getType(); assertEquals( "node type mismatch at " + i + ".\n" + "found : " + Token.name(actualType) + "\n" + "required: " + Token.name(expectedType) + "\n", expectedType, actualType); } } }