/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.cfg;
import com.google.common.collect.Lists;
import com.sonar.sslr.api.typed.ActionParser;
import org.junit.Test;
import org.sonar.java.ast.parser.JavaParser;
import org.sonar.java.cfg.CFG.Block;
import org.sonar.java.resolve.SemanticModel;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import org.sonar.plugins.java.api.tree.VariableTree;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.plugins.java.api.tree.Tree.Kind.EQUAL_TO;
import static org.sonar.plugins.java.api.tree.Tree.Kind.IDENTIFIER;
import static org.sonar.plugins.java.api.tree.Tree.Kind.INT_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.METHOD_INVOCATION;
import static org.sonar.plugins.java.api.tree.Tree.Kind.NEW_ARRAY;
import static org.sonar.plugins.java.api.tree.Tree.Kind.NEW_CLASS;
import static org.sonar.plugins.java.api.tree.Tree.Kind.NULL_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.RETURN_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.STRING_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.THROW_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.TRY_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.VARIABLE;
public class CFGTest {
static CFGChecker checker(BlockChecker... checkers) {
return new CFGChecker(checkers);
}
static BlockChecker block(final ElementChecker... checkers) {
return new BlockChecker(checkers);
}
static BlockChecker terminator(final Tree.Kind kind, final int... successorIDs) {
return new BlockChecker(kind, successorIDs);
}
static ElementChecker element(final Tree.Kind kind) {
return new ElementChecker(kind);
}
static ElementChecker element(final Tree.Kind kind, final String name) {
return new ElementChecker(kind, name);
}
static ElementChecker element(final Tree.Kind kind, final int value) {
return new ElementChecker(kind, value);
}
private static class CFGChecker {
private final List<BlockChecker> checkers = new ArrayList<>();
CFGChecker(BlockChecker... checkers) {
Collections.addAll(this.checkers, checkers);
}
public void check(final CFG cfg) {
try {
assertThat(cfg.blocks()).as("Expected number of blocks").hasSize(checkers.size() + 1);
final Iterator<BlockChecker> checkerIterator = checkers.iterator();
final List<Block> blocks = new ArrayList<>(cfg.blocks());
final Block exitBlock = blocks.remove(blocks.size() - 1);
for (final Block block : blocks) {
checkerIterator.next().check(block);
checkLinkedBlocks(block.id(), "Successor", cfg.blocks(), block.successors());
checkLinkedBlocks(block.id(), "Predecessors", cfg.blocks(), block.predecessors());
}
assertThat(exitBlock.elements()).isEmpty();
assertThat(exitBlock.successors()).isEmpty();
assertThat(cfg.blocks()).as("CFG entry block is no longer in the list of blocks!").contains(cfg.entry());
} catch (final Throwable e) {
System.out.println(CFGDebug.toString(cfg));
throw e;
}
}
private void checkLinkedBlocks(int id, String type, List<Block> blocks, Set<Block> linkedBlocks) {
for (Block block : linkedBlocks) {
assertThat(block).as(type + " block " + id + " is missing from he list of blocks").isIn(blocks);
}
}
}
private static class BlockChecker {
private int[] successorIDs = new int[] {};
private int[] exceptionsIDs = new int[] {};
private final List<ElementChecker> checkers = new ArrayList<>();
private TerminatorChecker terminatorChecker;
private int ifTrue = -1;
private int ifFalse = -1;
private int exitId = -1;
private boolean isCatchBlock = false;
private boolean isFinallyBlock = false;
BlockChecker(final int... ids) {
if( ids.length <= 1) {
throw new IllegalArgumentException("creating a block with only one successors should not be possible!");
}
successors(ids);
}
BlockChecker(final Tree.Kind kind, final int... ids) {
successors(ids);
terminator(kind);
}
BlockChecker(final ElementChecker... checkers) {
Collections.addAll(this.checkers, checkers);
if (this.checkers.isEmpty()) {
throw new IllegalArgumentException("Only terminator may have no elements!");
}
}
BlockChecker successors(final int... ids) {
if (ifTrue != -1 || ifFalse != -1) {
throw new IllegalArgumentException("Cannot mix true/false with generic successors!");
}
successorIDs = new int[ids.length];
int n = 0;
for (int i : ids) {
successorIDs[n++] = i;
}
Arrays.sort(successorIDs);
return this;
}
BlockChecker exceptions(final int... ids) {
exceptionsIDs = new int[ids.length];
int n = 0;
for (int i : ids) {
exceptionsIDs[n++] = i;
}
Arrays.sort(exceptionsIDs);
return this;
}
BlockChecker isCatchBlock() {
isCatchBlock = true;
return this;
}
BlockChecker isFinallyBlock() {
isFinallyBlock = true;
return this;
}
BlockChecker ifTrue(final int id) {
if (successorIDs.length > 0) {
throw new IllegalArgumentException("Cannot mix true/false with generic successors!");
}
ifTrue = id;
return this;
}
BlockChecker ifFalse(final int id) {
if (successorIDs.length > 0) {
throw new IllegalArgumentException("Cannot mix true/false with generic successors!");
}
ifFalse = id;
return this;
}
BlockChecker terminator(final Kind kind) {
this.terminatorChecker = new TerminatorChecker(kind);
return this;
}
public void check(final Block block) {
assertThat(block.elements()).as("Expected number of elements in block " + block.id()).hasSize(checkers.size());
final Iterator<ElementChecker> checkerIterator = checkers.iterator();
for (final Tree element : block.elements()) {
checkerIterator.next().check(element);
}
if (successorIDs.length == 0) {
if (ifTrue != -1) {
assertThat(block.trueBlock().id()).as("Expected true successor block " + block.id()).isEqualTo(ifTrue);
}
if (ifFalse != -1) {
assertThat(block.falseBlock().id()).as("Expected true successor block " + block.id()).isEqualTo(ifFalse);
}
if(exitId != -1) {
assertThat(block.exitBlock().id()).as("Expected exit successor block " + block.id()).isEqualTo(exitId);
}
} else {
assertThat(block.successors()).as("Expected number of successors in block " + block.id()).hasSize(successorIDs.length);
final int[] actualSuccessorIDs = new int[successorIDs.length];
int n = 0;
for (final Block successor : block.successors()) {
actualSuccessorIDs[n++] = successor.id();
}
Arrays.sort(actualSuccessorIDs);
assertThat(actualSuccessorIDs).as("Expected successors in block " + block.id()).isEqualTo(successorIDs);
}
assertThat(block.exceptions().stream().mapToInt(Block::id).sorted().toArray()).as("Expected exceptions in block " + block.id()).isEqualTo(exceptionsIDs);
if (terminatorChecker != null) {
terminatorChecker.check(block.terminator());
}
if (isCatchBlock) {
assertThat(block.isCatchBlock()).as("Block B" + block.id() + " expected to be flagged as 'catch' block").isTrue();
}
if (isFinallyBlock) {
assertThat(block.isFinallyBlock()).as("Block B" + block.id() + " expected to be flagged as 'finally' block").isTrue();
}
}
BlockChecker exit(final int id) {
exitId = id;
return this;
}
}
private static class ElementChecker {
private final Tree.Kind kind;
private final String name;
ElementChecker(final Tree.Kind kind, final String name) {
super();
this.kind = kind;
this.name = name;
switch (kind) {
case VARIABLE:
case IDENTIFIER:
case CHAR_LITERAL:
case STRING_LITERAL:
case BOOLEAN_LITERAL:
case INT_LITERAL:
case METHOD_INVOCATION:
break;
default:
throw new IllegalArgumentException("Unsupported element kind! "+kind);
}
}
ElementChecker(final Tree.Kind kind, final int value) {
super();
this.kind = kind;
this.name = Integer.toString(value);
switch (kind) {
case INT_LITERAL:
break;
default:
throw new IllegalArgumentException("Unsupported element kind!");
}
}
public ElementChecker(final Tree.Kind kind) {
super();
this.kind = kind;
name = null;
switch (kind) {
case METHOD_INVOCATION:
case METHOD_REFERENCE:
case MEMBER_SELECT:
case NULL_LITERAL:
case EQUAL_TO:
case NOT_EQUAL_TO:
case LESS_THAN:
case LESS_THAN_OR_EQUAL_TO:
case GREATER_THAN:
case GREATER_THAN_OR_EQUAL_TO:
case POSTFIX_INCREMENT:
case PREFIX_INCREMENT:
case POSTFIX_DECREMENT:
case PREFIX_DECREMENT:
case TRY_STATEMENT:
case NEW_CLASS:
case NEW_ARRAY:
case INSTANCE_OF:
case LAMBDA_EXPRESSION:
case TYPE_CAST:
case PLUS_ASSIGNMENT:
case ASSIGNMENT:
case ARRAY_ACCESS_EXPRESSION:
case LOGICAL_COMPLEMENT:
case PLUS:
break;
default:
throw new IllegalArgumentException("Unsupported element kind: " + kind);
}
}
public void check(final Tree element) {
assertThat(element.kind()).as("Element kind").isEqualTo(kind);
switch (element.kind()) {
case VARIABLE:
assertThat(((VariableTree) element).simpleName().name()).as("Variable name").isEqualTo(name);
break;
case IDENTIFIER:
assertThat(((IdentifierTree) element).identifierToken().text()).as("Identifier").isEqualTo(name);
break;
case INT_LITERAL:
assertThat(((LiteralTree) element).token().text()).as("Integer").isEqualTo(name);
break;
case CHAR_LITERAL:
assertThat(((LiteralTree) element).token().text()).as("String").isEqualTo(name);
break;
case METHOD_INVOCATION:
if (name != null) {
MethodInvocationTree method = (MethodInvocationTree) element;
MemberSelectExpressionTree select = (MemberSelectExpressionTree) method.methodSelect();
assertThat(select.identifier().toString()).as("Method").isEqualTo(name);
}
break;
default:
// No need to test any associated symbol for the other cases
break;
}
}
}
private static class TerminatorChecker {
private final Kind kind;
private TerminatorChecker(final Tree.Kind kind) {
this.kind = kind;
switch (kind) {
case IF_STATEMENT:
case CONDITIONAL_OR:
case CONDITIONAL_AND:
case CONDITIONAL_EXPRESSION:
case BREAK_STATEMENT:
case CONTINUE_STATEMENT:
case SWITCH_STATEMENT:
case RETURN_STATEMENT:
case FOR_STATEMENT:
case FOR_EACH_STATEMENT:
case WHILE_STATEMENT:
case DO_STATEMENT:
case THROW_STATEMENT:
case SYNCHRONIZED_STATEMENT:
break;
default:
throw new IllegalArgumentException("Unexpected terminator kind!");
}
}
public void check(final Tree element) {
assertThat(element).as("Element kind").isNotNull();
assertThat(element.kind()).as("Element kind").isEqualTo(kind);
}
}
public static final ActionParser<Tree> parser = JavaParser.createParser(StandardCharsets.UTF_8);
public static CFG buildCFG(String methodCode) {
return buildCFGFromCUT((CompilationUnitTree) parser.parse("class A { " + methodCode + " }"));
}
public static CFG buildCFG(File file) {
return buildCFGFromCUT((CompilationUnitTree) parser.parse(file));
}
private static CFG buildCFGFromCUT(CompilationUnitTree cut) {
SemanticModel.createFor(cut, Lists.newArrayList());
final MethodTree tree = ((MethodTree) ((ClassTree) cut.types().get(0)).members().get(0));
return CFG.build(tree);
}
@Test
public void empty_cfg() {
final CFG cfg = buildCFG("void fun() {}");
final CFGChecker cfgChecker = checker();
cfgChecker.check(cfg);
assertThat(cfg.entry().isMethodExitBlock()).as("entry is an exit").isTrue();
}
@Test
public void simplest_cfg() {
final CFG cfg = buildCFG("void fun() { bar();}");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
CFG.Block entry = cfg.entry();
assertThat(entry.isMethodExitBlock()).as("1st block is not an exit").isFalse();
assertThat(entry.successors()).as("number of successors").hasSize(1);
CFG.Block exit = entry.successors().iterator().next();
assertThat(exit.isMethodExitBlock()).as("2nd block is an exit").isTrue();
}
@Test
public void straight_method_calls() {
final CFG cfg = buildCFG("void fun() { bar();qix();baz();}");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION),
element(Tree.Kind.IDENTIFIER, "qix"),
element(Tree.Kind.METHOD_INVOCATION),
element(Tree.Kind.IDENTIFIER, "baz"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void single_declaration() {
final CFG cfg = buildCFG("void fun() {Object o;}");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.VARIABLE, "o")).successors(0));
cfgChecker.check(cfg);
}
@Test
public void if_then() {
final CFG cfg = buildCFG("void fun() {if(a) { foo(); } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a")
).terminator(Tree.Kind.IF_STATEMENT).successors(0, 1),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void if_then_else() {
final CFG cfg = buildCFG("void fun() {if(a) { foo(); } else { bar(); } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a"))
.terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0),
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void if_then_elseif() {
final CFG cfg = buildCFG("void fun() {\nif(a) {\n foo(); \n } else if(b) {\n bar();\n } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a")
).terminator(Tree.Kind.IF_STATEMENT).successors(2, 3),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0),
block(
element(Tree.Kind.IDENTIFIER, "b")
).terminator(Tree.Kind.IF_STATEMENT).successors(0, 1),
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void conditionalOR() {
final CFG cfg = buildCFG("void fun() {if(a || b) { foo(); } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a")
).terminator(Tree.Kind.CONDITIONAL_OR).successors(1, 2),
block(
element(Tree.Kind.IDENTIFIER, "b")
).terminator(Tree.Kind.IF_STATEMENT).successors(0, 1),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void conditionalAND() {
final CFG cfg = buildCFG("void fun() {if((a && b)) { foo(); } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a")
).terminator(Tree.Kind.CONDITIONAL_AND).successors(0, 2),
block(
element(Tree.Kind.IDENTIFIER, "b")
).terminator(Tree.Kind.IF_STATEMENT).successors(0, 1),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void assignmentAND() {
final CFG cfg = buildCFG("void fun() {boolean bool = a && b;}");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a")).terminator(Tree.Kind.CONDITIONAL_AND).successors(1, 2),
block(
element(Tree.Kind.IDENTIFIER, "b")).successors(1),
block(
element(Tree.Kind.VARIABLE, "bool")).successors(0));
cfgChecker.check(cfg);
}
@Test
public void three_branch_if() {
final CFG cfg = buildCFG("void fun() { foo ? a : b; a.toString();}");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "foo")).terminator(Tree.Kind.CONDITIONAL_EXPRESSION).successors(2, 3),
block(
element(Tree.Kind.IDENTIFIER, "a")).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "b")).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void switch_statement() {
CFG cfg = buildCFG("void foo(int i, int j, int k) {\n" +
" switch (i==-1 ? j:k) {\n" +
" default:;\n" +
" }\n" +
" }");
assertThat(cfg.blocks().get(0).id()).isEqualTo(5);
cfg = buildCFG(
"void fun(int foo) { int a; switch(foo) { case 1: System.out.println(bar);case 2: System.out.println(qix);break; default: System.out.println(baz);} }");
CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, "1"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(3),
block(
element(INT_LITERAL, "2"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "qix"),
element(Tree.Kind.METHOD_INVOCATION)
).terminator(Tree.Kind.BREAK_STATEMENT).successors(0),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "baz"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0),
block(
element(Tree.Kind.VARIABLE, "a"),
element(Tree.Kind.IDENTIFIER, "foo")
).terminator(Tree.Kind.SWITCH_STATEMENT).successors(2, 3, 4));
cfgChecker.check(cfg);
}
@Test
public void switch_statement_with_piledUpCases_againstDefault() {
final CFG cfg = buildCFG(
"void fun(int foo) { int a; switch(foo) { case 1: System.out.println(bar);case 2: System.out.println(qix);break; case 3: case 4: default: System.out.println(baz);} }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, "1"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)).successors(3),
block(
element(INT_LITERAL, "2"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "qix"),
element(Tree.Kind.METHOD_INVOCATION)).terminator(Tree.Kind.BREAK_STATEMENT).successors(0),
block(
element(INT_LITERAL, "4"),
element(INT_LITERAL, "3"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "baz"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0),
block(
element(Tree.Kind.VARIABLE, "a"),
element(Tree.Kind.IDENTIFIER, "foo")).terminator(Tree.Kind.SWITCH_STATEMENT).successors(2, 3, 4));
cfgChecker.check(cfg);
}
@Test
public void switch_statement_without_default() {
final CFG cfg = buildCFG(
"void fun(int foo) { int a; switch(foo) { case 1: System.out.println(bar);case 2: System.out.println(qix);break;} Integer.toString(foo); }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, "1"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)).successors(3),
block(
element(INT_LITERAL, "2"),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "qix"),
element(Tree.Kind.METHOD_INVOCATION)).terminator(Tree.Kind.BREAK_STATEMENT).successors(1),
block(
element(Tree.Kind.VARIABLE, "a"),
element(Tree.Kind.IDENTIFIER, "foo")).terminator(Tree.Kind.SWITCH_STATEMENT).successors(1, 3, 4),
block(
element(Tree.Kind.IDENTIFIER, "Integer"),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void switch_statement_with_expression_in_case() {
final CFG cfg = buildCFG(
"void fun() { int a; switch(b) { case c : System.out.println(1);break; case d || e: System.out.println(2);break;} }");
final CFGChecker cfgChecker = checker(
block(
element(Kind.IDENTIFIER, "c"),
element(Kind.IDENTIFIER, "System"),
element(Kind.MEMBER_SELECT),
element(INT_LITERAL, "1"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0),
block(
element(Kind.IDENTIFIER, "d")).terminator(Kind.CONDITIONAL_OR).successors(2, 3),
block(
element(Kind.IDENTIFIER, "e")).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(INT_LITERAL, "2"),
element(Tree.Kind.METHOD_INVOCATION)).terminator(Tree.Kind.BREAK_STATEMENT).successors(0),
block(
element(Tree.Kind.VARIABLE, "a"),
element(Tree.Kind.IDENTIFIER, "b")).terminator(Tree.Kind.SWITCH_STATEMENT).successors(0, 4, 5));
cfgChecker.check(cfg);
}
@Test
public void return_statement() {
final CFG cfg = buildCFG("void fun(Object foo) { if(foo == null) return; }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.NULL_LITERAL),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(0, 1),
terminator(Tree.Kind.RETURN_STATEMENT, 0));
cfgChecker.check(cfg);
}
@Test
public void array_loop() {
final CFG cfg = buildCFG("void fun(Object foo) {System.out.println(''); for(int i =0;i<10;i++) { System.out.println(i); } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION),
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.FOR_STATEMENT).successors(0, 2),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT)
).successors(3));
cfgChecker.check(cfg);
}
@Test
public void array_loop_with_break() {
final CFG cfg = buildCFG("void fun(Object foo) { for(int i =0;i<10;i++) { if(i == 5) break; } }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(4),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.FOR_STATEMENT).successors(0, 3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
terminator(Tree.Kind.BREAK_STATEMENT, 0),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT)
).successors(4));
cfgChecker.check(cfg);
}
@Test
public void array_loop_with_continue() {
final CFG cfg = buildCFG("void fun(Object foo) { for(int i =0;i<10;i++) { if(i == 5) continue; } }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(4),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.FOR_STATEMENT).successors(0, 3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
terminator(Tree.Kind.CONTINUE_STATEMENT, 1),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT)
).successors(4));
cfgChecker.check(cfg);
}
@Test
public void foreach_loop_continue() {
final CFG cfg = buildCFG("void fun(){ System.out.println('start'); for(String foo:list) {System.out.println(foo); if(foo.length()> 2) {continue;} System.out.println('');} System.out.println('end'); }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "'start'"),
element(Tree.Kind.METHOD_INVOCATION)).successors(6),
block(
element(Tree.Kind.IDENTIFIER, "list")).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION),
element(INT_LITERAL, 2),
element(Kind.GREATER_THAN)
).terminator(Kind.IF_STATEMENT).successors(3, 4),
terminator(Kind.CONTINUE_STATEMENT).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)).successors(2),
block(
element(Tree.Kind.VARIABLE, "foo")).terminator(Tree.Kind.FOR_EACH_STATEMENT).successors(1, 5),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "'end'"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void foreach_loop() {
CFG cfg = buildCFG("void fun(){ System.out.println(''); for(String foo:list) {System.out.println(foo);} System.out.println(''); }");
CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)).successors(4),
block(
element(Tree.Kind.IDENTIFIER, "list")).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION)).successors(2),
block(
element(Tree.Kind.VARIABLE, "foo")).terminator(Tree.Kind.FOR_EACH_STATEMENT).successors(1, 3),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
cfg = buildCFG("void fun(){ for (String n : dir.list(foo() ? \"**\" : \"\")) {\n" +
" if (s.isEmpty()) {\n" +
" relativePath = n;\n" +
" }\n" +
" }}");
cfgChecker = new CFGChecker(
block(
element(Kind.IDENTIFIER, "dir"),
element(Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)).terminator(Kind.CONDITIONAL_EXPRESSION).ifTrue(6).ifFalse(5),
block(element(Kind.STRING_LITERAL, "**")).successors(4),
block(element(Kind.STRING_LITERAL, "")).successors(4),
block(
element(Kind.METHOD_INVOCATION)).successors(1),
block(
element(Kind.IDENTIFIER, "s"),
element(Kind.METHOD_INVOCATION)).terminator(Kind.IF_STATEMENT).ifTrue(2).ifFalse(1),
block(
element(Kind.IDENTIFIER, "n"),
element(Kind.ASSIGNMENT)).successors(1),
block(element(Kind.VARIABLE, "n")).terminator(Kind.FOR_EACH_STATEMENT).ifFalse(0).ifTrue(3)
);
cfgChecker.check(cfg);
}
@Test
public void while_loop() {
final CFG cfg = buildCFG("void fun() {int i = 0; while(i < 10) {i++; System.out.println(i); } }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.WHILE_STATEMENT).successors(0, 1),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(2));
cfgChecker.check(cfg);
}
@Test
public void while_loop_with_break() {
final CFG cfg = buildCFG("void fun() {int i = 0; while(i < 10) {i++; if(i == 5) break; } }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.WHILE_STATEMENT).successors(0, 2),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 3),
terminator(Tree.Kind.BREAK_STATEMENT, 0));
cfgChecker.check(cfg);
}
@Test
public void while_loop_with_continue() {
final CFG cfg = buildCFG("void fun() {int i = 0; while(i < 10) {i++; if(i == 5) continue; } }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.WHILE_STATEMENT).successors(0, 2),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 3),
terminator(Tree.Kind.CONTINUE_STATEMENT, 3));
cfgChecker.check(cfg);
}
@Test
public void do_while_loop() {
final CFG cfg = buildCFG("void fun() {int i = 0; do {i++; System.out.println(i); }while(i < 10); }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.DO_STATEMENT).successors(0, 2));
cfgChecker.check(cfg);
}
@Test
public void do_while_loop_with_break() {
final CFG cfg = buildCFG("void fun() {int i = 0; do { i++; if(i == 5) break; }while(i < 10); }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
terminator(Tree.Kind.BREAK_STATEMENT, 0),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.DO_STATEMENT).successors(0, 3));
cfgChecker.check(cfg);
}
@Test
public void do_while_loop_with_continue() {
final CFG cfg = buildCFG("void fun() {int i = 0; do{i++; if(i == 5) continue; }while(i < 10); }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
terminator(Tree.Kind.CONTINUE_STATEMENT, 3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.DO_STATEMENT).successors(0, 3));
cfgChecker.check(cfg);
}
@Test
public void break_on_label() {
final CFG cfg = buildCFG("void fun() { foo: for(int i = 0; i<10;i++) { if(i==5) break foo; } }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(4),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.FOR_STATEMENT).successors(0, 3),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
terminator(Tree.Kind.BREAK_STATEMENT, 0),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT)
).successors(4));
cfgChecker.check(cfg);
}
@Test
public void continue_on_label() {
final CFG cfg = buildCFG("void fun() { foo: for(int i = 0; i<10;i++) { plop(); if(i==5) continue foo; plop();} }");
final CFGChecker cfgChecker = checker(
block(
element(INT_LITERAL, 0),
element(Tree.Kind.VARIABLE, "i")
).successors(5),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 10),
element(Tree.Kind.LESS_THAN)
).terminator(Tree.Kind.FOR_STATEMENT).successors(0, 4),
block(
element(Tree.Kind.IDENTIFIER, "plop"),
element(Kind.METHOD_INVOCATION),
element(Tree.Kind.IDENTIFIER, "i"),
element(INT_LITERAL, 5),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(2,3),
terminator(Tree.Kind.CONTINUE_STATEMENT, 1),
block(
element(Tree.Kind.IDENTIFIER, "plop"),
element(Kind.METHOD_INVOCATION)
).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT)
).successors(5));
cfgChecker.check(cfg);
}
@Test
public void assignement_order_of_evaluation() throws Exception {
CFG cfg = buildCFG(" void foo() {\n" +
" int[] a = {4,4};\n" +
" int b = 1;\n" +
" a[b] = b = 0;\n" +
" }");
CFGChecker checker = checker(
block(
element(Tree.Kind.INT_LITERAL, 4),
element(Tree.Kind.INT_LITERAL, 4),
element(Tree.Kind.NEW_ARRAY),
element(Tree.Kind.VARIABLE, "a"),
element(Tree.Kind.INT_LITERAL, 1),
element(Tree.Kind.VARIABLE, "b"),
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.IDENTIFIER, "b"),
element(Tree.Kind.ARRAY_ACCESS_EXPRESSION),
element(Tree.Kind.INT_LITERAL, 0),
element(Tree.Kind.ASSIGNMENT),
element(Tree.Kind.ASSIGNMENT)).successors(0));
checker.check(cfg);
}
@Test
public void prefix_operators() {
final CFG cfg = buildCFG("void fun() { ++i;i++; }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.PREFIX_INCREMENT),
element(Tree.Kind.IDENTIFIER, "i"),
element(Tree.Kind.POSTFIX_INCREMENT)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void exit_block_for_finally_with_if_statement() throws Exception {
CFG cfg = buildCFG(" void test(boolean fooCalled) {\n" +
" Object bar;\n" +
" try {\n" +
" bar = new Bar();\n" +
" } finally {\n" +
" if (fooCalled) {foo();\n" +
" }\n" +
" }\n" +
" bar.toString();\n" +
" }");
CFGChecker cfgChecker = checker(
block(
element(Kind.VARIABLE, "bar"),
element(Kind.TRY_STATEMENT)
).successors(6),
block(
element(Kind.NEW_CLASS)
).successors(5).exceptions(4),
block(
element(Kind.ASSIGNMENT)
).successors(4),
block(
element(Kind.IDENTIFIER, "fooCalled")
).terminator(Kind.IF_STATEMENT).successors(2, 3),
block(
element(Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)
).successors(2),
new BlockChecker(1, 0).exit(0),
block(
element(Kind.IDENTIFIER, "bar"),
element(Kind.METHOD_INVOCATION)
).successors(0)
);
cfgChecker.check(cfg);
}
@Test
public void catch_thrown_in_exception() throws Exception {
CFG cfg = buildCFG(" void foo() throws MyException {\n"+
" try {\n"+
" try {\n"+
" foo(); \n"+
" } catch (MyException e) {\n"+
" foo(); \n"+
" }\n"+
" } catch (MyException e) {\n"+
" System.out.println('outercatch');\n"+
" }\n"+
" }" +
" class MyException{}");
CFGChecker checker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(4),
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(3),
block(
element(Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)
).successors(0).exceptions(0,2),
block(
element(Kind.VARIABLE, "e"),
element(Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)
).successors(0).exceptions(0, 1),
block(
element(Kind.VARIABLE, "e"),
element(Kind.IDENTIFIER, "System"),
element(Kind.MEMBER_SELECT),
element(Kind.CHAR_LITERAL, "'outercatch'"),
element(Kind.METHOD_INVOCATION)
).successors(0).exceptions(0)
);
checker.check(cfg);
}
@Test
public void nested_try_finally() throws Exception {
CFG cfg = buildCFG(" void foo() {\n"+
" try {\n"+
" java.util.zip.ZipFile file = new java.util.zip.ZipFile(fileName);\n"+
" try {\n"+
" file.foo();// do something with the file...\n"+
" } finally {\n"+
" file.close();\n"+
" }\n"+
" } catch (Exception e) {\n"+
" // Handle exception\n"+
" }\n"+
" }");
CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(4),
block(
element(Tree.Kind.IDENTIFIER, "fileName"),
element(Kind.NEW_CLASS)
).successors(3).exceptions(0),
block(
element(Kind.VARIABLE, "file"),
element(Kind.TRY_STATEMENT)
).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "file"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1).exceptions(1),
block(
element(Tree.Kind.IDENTIFIER, "file"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0).exceptions(0)
);
cfgChecker.check(cfg);
}
@Test
public void catch_throwable() throws Exception {
CFG cfg = buildCFG(" public void reschedule() {\n" +
" try {\n" +
" getNextSchedule();\n" +
" } catch (Throwable t) {\n" +
" notifyFailed();\n" +
" }\n" +
" }");
CFGChecker cfgChecker = checker(
block(
element(Kind.TRY_STATEMENT)
).successors(2),
block(
element(Kind.IDENTIFIER, "getNextSchedule"),
element(Kind.METHOD_INVOCATION)
).successors(0).exceptions(0, 1),
block(
element(Kind.VARIABLE, "t"),
element(Kind.IDENTIFIER, "notifyFailed"),
element(Kind.METHOD_INVOCATION)
).successors(0).exceptions(0)
);
cfgChecker.check(cfg);
}
@Test
public void try_statement() {
CFG cfg = buildCFG("void fun() {try {System.out.println('');} finally { System.out.println(''); }}");
CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1).exceptions(1),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0).isFinallyBlock());
cfgChecker.check(cfg);
cfg = buildCFG("void fun() {try {System.out.println('');} catch(IllegalArgumentException e) { foo('i');} catch(Exception e){bar('e');}" +
" finally { System.out.println('finally'); }}");
cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(4),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1).exceptions(1, 2, 3),
block(
element(Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.CHAR_LITERAL, "'i'"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1).exceptions(1).isCatchBlock(),
block(
element(Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.CHAR_LITERAL, "'e'"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(1).exceptions(1).isCatchBlock(),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "'finally'"),
element(Tree.Kind.METHOD_INVOCATION)
).successors(0).isFinallyBlock()
);
cfgChecker.check(cfg);
cfg = buildCFG(
" private void f() {\n" +
" try {\n" +
" } catch (Exception e) {\n" +
" if (e instanceof IOException) { \n" +
" }\n}}");
cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(0),
block(
element(Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "e"),
element(Tree.Kind.INSTANCE_OF)
).terminator(Tree.Kind.IF_STATEMENT).ifTrue(0).ifFalse(0).isCatchBlock()
);
cfgChecker.check(cfg);
cfg = buildCFG(
" private void f() {\n" +
" try {\n" +
" return;" +
"} finally { foo();} bar(); }");
cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)
).successors(3),
terminator(Kind.RETURN_STATEMENT).successors(2).exit(2),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)
).successors(0, 1).exit(0).isFinallyBlock(),
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Kind.METHOD_INVOCATION)
).successors(0)
);
cfgChecker.check(cfg);
}
@Test
public void throw_statement() {
final CFG cfg = buildCFG("void fun(Object a) {if(a==null) { throw new Exception();} System.out.println(''); }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.NULL_LITERAL),
element(EQUAL_TO)
).terminator(Tree.Kind.IF_STATEMENT).successors(1, 2),
block(
element(Tree.Kind.NEW_CLASS)).terminator(Tree.Kind.THROW_STATEMENT).successors(0),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void synchronized_statement() {
final CFG cfg = buildCFG("void fun(Object a) {if(a==null) { synchronized(a) { foo();bar();} } System.out.println(''); }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.NULL_LITERAL),
element(EQUAL_TO)).terminator(Tree.Kind.IF_STATEMENT).successors(1, 3),
block(
element(Tree.Kind.IDENTIFIER, "a")).terminator(Tree.Kind.SYNCHRONIZED_STATEMENT).successors(2),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.METHOD_INVOCATION),
element(Tree.Kind.IDENTIFIER, "bar"),
element(Tree.Kind.METHOD_INVOCATION)).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "System"),
element(Tree.Kind.MEMBER_SELECT),
element(Tree.Kind.CHAR_LITERAL, "''"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void multiple_constructions() {
final CFG cfg = buildCFG("void fun(Object a) {if(a instanceof String) { a::toString;foo(y -> y+1); a += (String) a; } }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.INSTANCE_OF)
).terminator(Tree.Kind.IF_STATEMENT).successors(0, 1),
block(
element(Kind.METHOD_REFERENCE),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Tree.Kind.LAMBDA_EXPRESSION),
element(Tree.Kind.METHOD_INVOCATION),
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.TYPE_CAST),
element(Tree.Kind.PLUS_ASSIGNMENT)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void catching_class_cast_exception() {
CFG cfg = buildCFG("void fun(Object a) {try {return (String) a;} catch(ClassCastException cce) { return null;} }");
CFGChecker cfgChecker = checker(
block(
element(TRY_STATEMENT)
),
block(
element(Tree.Kind.IDENTIFIER, "a"),
element(Tree.Kind.TYPE_CAST)
).successors(1, 2),
terminator(RETURN_STATEMENT, 0),
block(
element(Kind.VARIABLE, "cce"),
element(NULL_LITERAL)
).successors(0)
);
cfgChecker.check(cfg);
}
@Test
public void array_access_expression() {
final CFG cfg = buildCFG("void fun(int[] array) { array[0] = 1; array[3+2] = 4; }");
final CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.IDENTIFIER, "array"),
element(INT_LITERAL, 0),
element(Tree.Kind.ARRAY_ACCESS_EXPRESSION),
element(INT_LITERAL, 1),
element(Tree.Kind.ASSIGNMENT),
element(Tree.Kind.IDENTIFIER, "array"),
element(INT_LITERAL, 3),
element(INT_LITERAL, 2),
element(Tree.Kind.PLUS),
element(Tree.Kind.ARRAY_ACCESS_EXPRESSION),
element(INT_LITERAL, 4),
element(Tree.Kind.ASSIGNMENT)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void try_with_resource() throws Exception {
final CFG cfg = buildCFG("void fun() { String path = ''; try (BufferedReader br = new BufferedReader(new FileReader(path))) {} }");
final CFGChecker cfgChecker = checker(
block(
element(Kind.CHAR_LITERAL, "''"),
element(Kind.VARIABLE, "path"),
element(Kind.TRY_STATEMENT)).successors(3),
block(
element(Kind.IDENTIFIER, "path"),
element(Kind.NEW_CLASS)).successors(2).exceptions(0),
block(
element(Kind.NEW_CLASS)).successors(1).exceptions(0),
block(
element(Kind.VARIABLE, "br")).successors(0));
cfgChecker.check(cfg);
}
@Test
public void returnCascadedAnd() throws Exception {
final CFG cfg = buildCFG(
"void andAll(boolean a, boolean b, boolean c) { return a && b && c;}");
final CFGChecker cfgChecker = checker(
block(element(Kind.IDENTIFIER, "a")).terminator(Kind.CONDITIONAL_AND).ifTrue(4).ifFalse(3),
block(element(Kind.IDENTIFIER, "b")).successors(3),
terminator(Kind.CONDITIONAL_AND).ifTrue(2).ifFalse(1),
block(element(Kind.IDENTIFIER, "c")).successors(1),
terminator(Kind.RETURN_STATEMENT).successors(0));
cfgChecker.check(cfg);
}
@Test
public void returnCascadedOr() throws Exception {
final CFG cfg = buildCFG(
"void orAll(boolean a, boolean b, boolean c) { return a || b || c;}");
final CFGChecker cfgChecker = checker(
block(element(Kind.IDENTIFIER, "a")).terminator(Kind.CONDITIONAL_OR).ifTrue(3).ifFalse(4),
block(element(Kind.IDENTIFIER, "b")).successors(3),
terminator(Kind.CONDITIONAL_OR).ifTrue(1).ifFalse(2),
block(element(Kind.IDENTIFIER, "c")).successors(1),
terminator(Kind.RETURN_STATEMENT).successors(0));
cfgChecker.check(cfg);
}
@Test
public void complex_boolean_expression() throws Exception {
final CFG cfg = buildCFG(" private boolean fun(boolean bool, boolean a, boolean b) {\n" +
" return (!bool && a) || (bool && b);\n" +
" }");
final CFGChecker cfgChecker = checker(
block(
element(Kind.IDENTIFIER, "bool"),
element(Kind.LOGICAL_COMPLEMENT)
).terminator(Kind.CONDITIONAL_AND).ifTrue(5).ifFalse(4),
block(element(Kind.IDENTIFIER, "a")).successors(4),
terminator(Kind.CONDITIONAL_OR).ifTrue(1).ifFalse(3),
block(element(Kind.IDENTIFIER, "bool")).terminator(Kind.CONDITIONAL_AND).ifTrue(2).ifFalse(1),
block(element(Kind.IDENTIFIER, "b")).successors(1),
terminator(Kind.RETURN_STATEMENT).successors(0));
cfgChecker.check(cfg);
}
@Test
public void method_reference() throws Exception {
final CFG cfg = buildCFG("void fun() { foo(Object::toString); }");
final CFGChecker cfgChecker = checker(
block(
element(Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_REFERENCE),
element(Kind.METHOD_INVOCATION)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void try_statement_with_CFG_blocks() {
// method invocation after if
CFG cfg = buildCFG(
" private void f(boolean action) {\n" +
" try {\n" +
" if (action) {" +
" performAction();" +
" }" +
" doSomething();" +
"} catch(Exception e) { foo();} bar(); }");
CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)).successors(5),
block(
element(Tree.Kind.IDENTIFIER, "action")).terminator(Kind.IF_STATEMENT).successors(3, 4),
block(
element(Tree.Kind.IDENTIFIER, "performAction"),
element(Kind.METHOD_INVOCATION)).successors(3).exceptions(0, 2).exit(0),
block(
element(Tree.Kind.IDENTIFIER, "doSomething"),
element(Kind.METHOD_INVOCATION)).successors(1).exceptions(0, 2).exit(0),
block(
element(Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)).successors(1).exceptions(0).exit(0),
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
// method invocation before if
cfg = buildCFG(
" private void f(boolean action) {\n" +
" try {\n" +
" doSomething();" +
" if (action) {" +
" performAction();" +
" }" +
"} catch(Exception e) { foo();} bar(); }");
cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)).successors(5),
block(
element(Tree.Kind.IDENTIFIER, "doSomething"),
element(Kind.METHOD_INVOCATION)).successors(4).exceptions(0, 2).exit(0),
block(
element(Tree.Kind.IDENTIFIER, "action")).terminator(Kind.IF_STATEMENT).successors(1, 3),
block(
element(Tree.Kind.IDENTIFIER, "performAction"),
element(Kind.METHOD_INVOCATION)).successors(1).exceptions(0, 2),
block(
element(Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)).successors(1).exceptions(0),
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
// finally
cfg = buildCFG(
" private void f(boolean action) {\n" +
" try {\n" +
" if (action) {" +
" performAction();" +
" }" +
" doSomething();" +
"} finally { foo();} bar(); }");
cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)).successors(5),
block(
element(Tree.Kind.IDENTIFIER, "action")).terminator(Kind.IF_STATEMENT).successors(3, 4),
block(
element(Tree.Kind.IDENTIFIER, "performAction"),
element(Kind.METHOD_INVOCATION)).successors(3).exceptions(2),
block(
element(Tree.Kind.IDENTIFIER, "doSomething"),
element(Kind.METHOD_INVOCATION)).successors(2).exceptions(2),
block(
element(Tree.Kind.IDENTIFIER, "foo"),
element(Kind.METHOD_INVOCATION)).successors(0, 1),
block(
element(Tree.Kind.IDENTIFIER, "bar"),
element(Kind.METHOD_INVOCATION)).successors(0));
cfgChecker.check(cfg);
}
@Test
public void try_statement_with_checked_exceptions() {
CFG cfg = buildCFG(
" void foo(Object result) {" +
" try { " +
" result = new Plop();" +
" } catch(IllegalAccessException iae) {" +
" try{ " +
" result = new Plop();" +
" } catch(IllegalAccessException iae) {" +
" }" +
" result.toString(); " +
" }" +
"}" +
"" +
"class Plop{" +
" Plop() throws IllegalAccessException{}" +
"}" );
CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)).successors(6),
block(
element(Kind.NEW_CLASS)).successors(1).exceptions(0, 5),
block(
element(Kind.VARIABLE, "iae"),
element(Tree.Kind.TRY_STATEMENT)).successors(4).isCatchBlock(),
block(
element(Kind.NEW_CLASS)).successors(3).exceptions(0, 2),
block(
element(Kind.ASSIGNMENT)
).successors(2),
block(
element(Kind.IDENTIFIER, "result"),
element(Kind.METHOD_INVOCATION)
).successors(0).exceptions(0),
block(
element(Kind.ASSIGNMENT)
).successors(0)
);
cfgChecker.check(cfg);
}
@Test
public void catch_block_correctly_flagged_in_CFG() {
CFG cfg = buildCFG(new File("src/test/files/cfg/CFGCatchBlocks.java"));
CFGChecker cfgChecker = checker(
block(
element(Tree.Kind.TRY_STATEMENT)).successors(8),
block(
element(Tree.Kind.IDENTIFIER, "m1"),
element(Tree.Kind.IDENTIFIER, "o1"),
element(Tree.Kind.IDENTIFIER, "o2"),
element(Tree.Kind.METHOD_INVOCATION)).successors(1).exceptions(7, 5, 3, 1),
block(
element(Tree.Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "m2"),
element(Tree.Kind.METHOD_INVOCATION)).successors(6).exceptions(1).isCatchBlock(),
block(
element(Tree.Kind.IDENTIFIER, "m3"),
element(Tree.Kind.METHOD_INVOCATION)).successors(1).exceptions(1),
block(
element(Tree.Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "o2"),
element(Tree.Kind.NULL_LITERAL),
element(Tree.Kind.EQUAL_TO)).terminator(Tree.Kind.IF_STATEMENT).ifTrue(4).ifFalse(1).isCatchBlock(),
block(
element(Tree.Kind.IDENTIFIER, "m4"),
element(Tree.Kind.METHOD_INVOCATION)).successors(1).exceptions(1),
block(
element(Tree.Kind.VARIABLE, "e"),
element(Tree.Kind.IDENTIFIER, "m5"),
element(Tree.Kind.METHOD_INVOCATION)).successors(2).exceptions(1).isCatchBlock(),
block(
element(Tree.Kind.VARIABLE, "res")).successors(1),
block(
element(Tree.Kind.IDENTIFIER, "m6"),
element(Tree.Kind.METHOD_INVOCATION)).successors(0).isFinallyBlock());
cfgChecker.check(cfg);
}
@Test
public void successor_of_labeled_break_statement() throws Exception {
CFG cfg = buildCFG("private static void test(long toRevision, boolean inverted, Object visitor) {\n" +
"\n" +
" testBlock: {\n" +
" if (inverted) \n" +
" break testBlock;\n" +
" test(0, false ? inverted : !inverted, visitor);\n" +
" }\n" +
" }");
CFGChecker cfgChecker = checker(
block(
element(Kind.IDENTIFIER, "inverted")
).terminator(Kind.IF_STATEMENT)
.ifTrue(5)
.ifFalse(4),
terminator(Kind.BREAK_STATEMENT).successors(0),
block(
element(Kind.IDENTIFIER, "test"),
element(INT_LITERAL, 0),
element(Kind.BOOLEAN_LITERAL, "false")
).terminator(Kind.CONDITIONAL_EXPRESSION)
.ifTrue(3)
.ifFalse(2),
block(
element(Kind.IDENTIFIER, "inverted")
).successors(1),
block(
element(Kind.IDENTIFIER, "inverted"),
element(Kind.LOGICAL_COMPLEMENT)
).successors(1),
block(
element(Kind.IDENTIFIER, "visitor"),
element(Kind.METHOD_INVOCATION)
).successors(0)
);
cfgChecker.check(cfg);
}
@Test
public void test_chained_method_invocation() {
CFG cfg = buildCFG("private void foo(Object p) {\n" +
" if(p == null) {\n" +
" NullArrayAccess\n" +
" .method(p.toString())\n" +
" .method2(p.hashCode());\n" +
" }\n" +
" }");
CFGChecker cfgChecker = checker(
block(
element(IDENTIFIER, "p"),
element(NULL_LITERAL),
element(EQUAL_TO)
).terminator(Kind.IF_STATEMENT)
.ifTrue(1)
.ifFalse(0),
block(
element(IDENTIFIER, "NullArrayAccess"),
element(IDENTIFIER, "p"),
element(METHOD_INVOCATION),
element(METHOD_INVOCATION),
element(IDENTIFIER, "p"),
element(METHOD_INVOCATION),
element(METHOD_INVOCATION)
).successors(0));
cfgChecker.check(cfg);
}
@Test
public void constructor_arguments_order() throws Exception {
CFG cfg = buildCFG("private void foo(Exception e) {\n" +
"throw new IllegalArgumentException(\"iae\", e);\n" +
"} "
);
CFGChecker cfgChecker = checker(
block(
element(STRING_LITERAL, "iae"),
element(IDENTIFIER, "e"),
element(NEW_CLASS)
).terminator(THROW_STATEMENT).successors(0)
);
cfgChecker.check(cfg);
}
@Test
public void array_dim_initializer_order() throws Exception {
CFG cfg = buildCFG("private void fun() {\n" +
"String[] plop = {foo(), bar()};\n" +
"String[][] plop2 = new String[qix()][baz()];\n" +
"} "
);
CFGChecker cfgChecker = checker(
block(
element(IDENTIFIER, "foo"),
element(METHOD_INVOCATION),
element(IDENTIFIER, "bar"),
element(METHOD_INVOCATION),
element(NEW_ARRAY),
element(VARIABLE, "plop"),
element(IDENTIFIER, "qix"),
element(METHOD_INVOCATION),
element(IDENTIFIER, "baz"),
element(METHOD_INVOCATION),
element(NEW_ARRAY),
element(VARIABLE, "plop2")
).successors(0)
);
cfgChecker.check(cfg);
}
}