/*
* 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.se;
import com.google.common.reflect.ClassPath;
import org.junit.Test;
import org.sonar.java.se.checks.ConditionAlwaysTrueOrFalseCheck;
import org.sonar.java.se.checks.CustomUnclosedResourcesCheck;
import org.sonar.java.se.checks.DivisionByZeroCheck;
import org.sonar.java.se.checks.LocksNotUnlockedCheck;
import org.sonar.java.se.checks.NonNullSetToNullCheck;
import org.sonar.java.se.checks.NullDereferenceCheck;
import org.sonar.java.se.checks.SECheck;
import org.sonar.java.se.checks.UnclosedResourcesCheck;
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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
public class ExplodedGraphWalkerTest {
@Test
public void seEngineTest() {
JavaCheckVerifier.verify("src/test/files/se/SeEngineTest.java", seChecks());
}
@Test
public void test_cleanup_state() {
final int[] steps = new int[2];
JavaCheckVerifier.verifyNoIssue("src/test/files/se/SeEngineTestCleanupState.java", new SymbolicExecutionVisitor(Collections.emptyList()) {
@Override
public void visitNode(Tree tree) {
ExplodedGraphWalker explodedGraphWalker = new ExplodedGraphWalker(false);
explodedGraphWalker.visitMethod((MethodTree) tree, new MethodBehavior(((MethodTree) tree).symbol()));
steps[0] += explodedGraphWalker.steps;
}
});
JavaCheckVerifier.verifyNoIssue("src/test/files/se/SeEngineTestCleanupState.java", new SymbolicExecutionVisitor(Collections.emptyList()) {
@Override
public void visitNode(Tree tree) {
ExplodedGraphWalker explodedGraphWalker = new ExplodedGraphWalker();
explodedGraphWalker.visitMethod((MethodTree) tree, new MethodBehavior(((MethodTree) tree).symbol()));
steps[1] += explodedGraphWalker.steps;
}
});
assertThat(steps[0]).isPositive();
assertThat(steps[0]).isGreaterThan(steps[1]);
}
@Test
public void reproducer() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/Reproducer.java", seChecks());
}
@Test
public void use_false_branch_on_loop_when_reaching_max_exec_program_point() {
ExplodedGraph.ProgramPoint[] programPoints = new ExplodedGraph.ProgramPoint[2];
ExplodedGraphWalker explodedGraphWalker = new ExplodedGraphWalker() {
boolean shouldEnqueueFalseBranch = false;
@Override
public void enqueue(ExplodedGraph.ProgramPoint programPoint, ProgramState programState, boolean exitPath) {
int nbOfExecution = programState.numberOfTimeVisited(programPoint);
if (nbOfExecution > MAX_EXEC_PROGRAM_POINT) {
shouldEnqueueFalseBranch = true;
programPoints[0] = programPoint;
} else {
shouldEnqueueFalseBranch = false;
}
int workListSize = workList.size();
super.enqueue(programPoint, programState, exitPath);
assertThat(workList.size()).isEqualTo(workListSize + 1);
if (shouldEnqueueFalseBranch) {
assertThat(programPoints[1]).isNull();
programPoints[1] = workList.peekFirst().programPoint;
}
}
};
JavaCheckVerifier.verifyNoIssue("src/test/files/se/SeEngineTestMaxExecProgramPoint.java", new SymbolicExecutionVisitor(Collections.emptyList()) {
@Override
public void visitNode(Tree tree) {
explodedGraphWalker.visitMethod((MethodTree) tree, new MethodBehavior(((MethodTree) tree).symbol()));
}
});
// we reached the max number of execution of a program point
assertThat(programPoints[0]).isNotNull();
// B2 - for each
assertThat(programPoints[0].block.id()).isEqualTo(2);
// we enqueued a new node in the workList after reaching the max number of execeution point
assertThat(programPoints[1]).isNotNull();
// B1 - using the false branch to exit the loop
assertThat(programPoints[1].block.id()).isEqualTo(1);
}
@Test
public void test_limited_loop_execution() throws Exception {
JavaCheckVerifier.verifyNoIssue("src/test/files/se/SeEngineTestCase.java", new SymbolicExecutionVisitor(Collections.emptyList()) {
@Override
public void visitNode(Tree tree) {
try {
new ExplodedGraphWalker().visitMethod((MethodTree) tree, new MethodBehavior(((MethodTree) tree).symbol()));
} catch (ExplodedGraphWalker.MaximumStepsReachedException exception) {
fail("loop execution should be limited");
}
}
});
}
@Test
public void test_maximum_steps_reached() throws Exception {
JavaCheckVerifier.verifyNoIssue("src/test/files/se/MaxSteps.java", new SymbolicExecutionVisitor(Collections.emptyList()) {
@Override
public void visitNode(Tree tree) {
try {
new ExplodedGraphWalker().visitMethod((MethodTree) tree, new MethodBehavior(((MethodTree) tree).symbol()));
fail("Too many states were processed !");
} catch (ExplodedGraphWalker.MaximumStepsReachedException exception) {
assertThat(exception.getMessage()).startsWith("reached limit of 16000 steps for method");
}
}
});
}
@Test
public void test_maximum_steps_reached_with_issue() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/MaxStepsWithIssue.java", new UnclosedResourcesCheck());
}
@Test
public void test_maximum_number_nested_states() throws Exception {
JavaCheckVerifier.verifyNoIssue("src/test/files/se/MaxNestedStates.java", new SymbolicExecutionVisitor(Collections.emptyList()) {
@Override
public void visitNode(Tree tree) {
try {
new ExplodedGraphWalker().visitMethod((MethodTree) tree, new MethodBehavior(((MethodTree) tree).symbol()));
fail("Too many states were processed !");
} catch (ExplodedGraphWalker.MaximumStepsReachedException exception) {
assertThat(exception.getMessage()).startsWith("reached maximum number of 10000 branched states");
}
}
});
}
@Test
public void system_exit() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/SystemExit.java", seChecks());
}
@Test
public void read_package_annotations() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/PackageAnnotationsNullable.java", seChecks());
JavaCheckVerifier.verify("src/test/files/se/PackageAnnotationsNonNull.java", seChecks());
}
@Test
public void xproc_usage_of_method_behaviors() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/XProcMethodBehavior.java", seChecks());
}
@Test
public void xproc_usage_of_method_behaviors_with_explicit_exceptional_path() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/XProcMethodBehaviorExplicitException.java", seChecks());
}
@Test
public void xproc_usage_of_method_behaviors_with_explicit_exceptional_path_and_branching() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/XProcMethodBehaviorExplicitExceptionBranching.java", seChecks());
}
@Test
public void xproc_usage_of_exceptional_path_and_branching() throws Exception {
JavaCheckVerifier.verify("src/test/files/se/XProcExceptionalBranching.java", seChecks());
}
static class MethodAsInstruction extends SECheck {
int toStringCall = 0;
@Override
public ProgramState checkPreStatement(CheckerContext context, Tree syntaxNode) {
if(syntaxNode.is(Tree.Kind.METHOD_INVOCATION)) {
if(((MethodInvocationTree) syntaxNode).symbol().name().equals("toString")) {
toStringCall++;
}
}
return super.checkPreStatement(context, syntaxNode);
}
}
@Test
public void methods_should_be_evaluated_only_once() throws Exception {
MethodAsInstruction check = new MethodAsInstruction();
JavaCheckVerifier.verifyNoIssue("src/test/files/se/EvaluateMethodOnce.java", check);
assertThat(check.toStringCall).isEqualTo(1);
}
@Test
public void eg_walker_factory_default_checks() throws IOException {
// Compute the list of SEChecks defined in package
List<String> seChecks = ClassPath.from(ExplodedGraphWalkerTest.class.getClassLoader())
.getTopLevelClasses("org.sonar.java.se.checks")
.stream()
.map(ClassPath.ClassInfo::getSimpleName)
.filter(name -> name.endsWith("Check") && !name.equals(SECheck.class.getSimpleName()))
// CustomUnclosedResource is a template rule and should not be activated by default
.filter(name -> !name.equals(CustomUnclosedResourcesCheck.class.getSimpleName()))
.sorted()
.collect(Collectors.toList());
ExplodedGraphWalker.ExplodedGraphWalkerFactory factory = new ExplodedGraphWalker.ExplodedGraphWalkerFactory(new ArrayList<>());
assertThat(factory.seChecks.stream().map(c -> c.getClass().getSimpleName()).sorted().collect(Collectors.toList())).isEqualTo(seChecks);
}
private static SECheck[] seChecks() {
return new SECheck[]{
new NullDereferenceCheck(),
new DivisionByZeroCheck(),
new ConditionAlwaysTrueOrFalseCheck(),
new UnclosedResourcesCheck(),
new CustomUnclosedResourcesCheck(),
new LocksNotUnlockedCheck(),
new NonNullSetToNullCheck(),
};
}
}