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

1442 lines
58 KiB
Java
Raw Permalink Normal View History

2023-04-25 11:33:41 +00:00
/*
* 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<DiGraphEdge<Node, Branch>> getAllEdges(
ControlFlowGraph<Node> cfg) {
List<DiGraphEdge<Node, Branch>> edges = Lists.newArrayList();
for (DiGraphNode<Node, Branch> n : cfg.getDirectedGraphNodes()) {
for (DiGraphEdge<Node, Branch> 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<DiGraphEdge<Node, Branch>> getAllEdges(
ControlFlowGraph<Node> cfg, int startToken, int endToken) {
List<DiGraphEdge<Node, Branch>> edges = getAllEdges(cfg);
Iterator<DiGraphEdge<Node, Branch>> it = edges.iterator();
while (it.hasNext()) {
DiGraphEdge<Node, Branch> 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<DiGraphEdge<Node, Branch>> getAllEdges(
ControlFlowGraph<Node> cfg, int startToken, int endToken, Branch type) {
List<DiGraphEdge<Node, Branch>> edges =
getAllEdges(cfg, startToken, endToken);
Iterator<DiGraphEdge<Node, Branch>> 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<DiGraphEdge<Node, Branch>> getAllDownEdges(
ControlFlowGraph<Node> cfg, int startToken, int endToken, Branch type) {
List<DiGraphEdge<Node, Branch>> edges =
getAllEdges(cfg, startToken, endToken, type);
Iterator<DiGraphEdge<Node, Branch>> it = edges.iterator();
while (it.hasNext()) {
DiGraphEdge<Node, Branch> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> cfg,
int startToken) {
List<DiGraphEdge<Node, Branch>> edges = getAllEdges(cfg);
for (DiGraphEdge<Node, Branch> edge : edges) {
Node source = edge.getSource().getValue();
DiGraphNode<Node, Branch> 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<Node> cfg,
int startToken) {
List<DiGraphEdge<Node, Branch>> edges = getAllEdges(cfg);
for (DiGraphEdge<Node, Branch> edge : edges) {
Node source = edge.getSource().getValue();
DiGraphNode<Node, Branch> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> cfg = createCfg(src);
assertUpEdge(cfg, Token.BREAK, Token.BLOCK, Branch.UNCOND);
}
public void testBreakingTryBlock() {
String src = "a: try { break a; } finally {} if(x) {}";
ControlFlowGraph<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> cfg = createCfg(src);
assertReturnEdge(cfg, Token.RETURN);
}
public void testReturnInFinally() {
String src = "function f(x){ try{} finally {return x;} }";
ControlFlowGraph<Node> cfg = createCfg(src);
assertReturnEdge(cfg, Token.RETURN);
}
public void testReturnInFinally2() {
String src = "function f(x){" +
" try{ try{}finally{var dummy; return x;} } finally {} }";
ControlFlowGraph<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> cfg,
List<Integer> nodeTypes) {
List<DiGraphNode<Node, Branch>> 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);
}
}
}