/*
* 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.checks.helpers;
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.resolve.SemanticModel;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.IfStatementTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.ReturnStatementTree;
import org.sonar.plugins.java.api.tree.StatementTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import java.io.File;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static org.assertj.core.api.Assertions.assertThat;
public class ReassignmentFinderTest {
private final ActionParser<Tree> p = JavaParser.createParser(StandardCharsets.UTF_8);
@Test
public void private_constructor() throws Exception {
assertThat(isFinal(ReassignmentFinder.class.getModifiers())).isTrue();
Constructor<ReassignmentFinder> constructor = ReassignmentFinder.class.getDeclaredConstructor();
assertThat(isPrivate(constructor.getModifiers())).isTrue();
assertThat(constructor.isAccessible()).isFalse();
constructor.setAccessible(true);
constructor.newInstance();
}
@Test
public void parameter() throws Exception {
String code = newCode(
"int foo(int a) {",
" return a;",
"}");
MethodTree method = methodTree(code);
List<StatementTree> statements = method.block().body();
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, null);
}
@Test
public void parameter_with_usage() throws Exception {
String code = newCode(
"int foo(boolean test) {",
" if (test) {}",
" return test;",
"}");
MethodTree method = methodTree(code);
List<StatementTree> statements = method.block().body();
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, null);
}
@Test
public void declaration() throws Exception {
String code = newCode(
"int foo() {",
" int a = 0;",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree aDeclarationInitializer = initializerFromVariableDeclarationStatement(statements.get(0));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, aDeclarationInitializer);
}
@Test
public void unknown_variable() throws Exception {
String code = newCode(
"int foo() {",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, null);
}
@Test
public void array_declaration() throws Exception {
String code = newCode(
"int foo() {",
" int a[] = new int[42];",
" a[0] = 42;",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree arrayAssignmentExpression = initializerFromVariableDeclarationStatement(statements.get(0));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, arrayAssignmentExpression);
}
@Test
public void assignement() throws Exception {
String code = newCode(
"int foo() {",
" int a;",
" a = 0;",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree aAssignmentExpression = assignementExpressionFromStatement(statements.get(1));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, aAssignmentExpression);
}
@Test
public void assignement_with_other_variable() throws Exception {
String code = newCode(
"int foo() {",
" int a;",
" int b;",
" a = 0;",
" b = 0;",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree aAssignmentExpression = assignementExpressionFromStatement(statements.get(2));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, aAssignmentExpression);
}
@Test
public void assignement_with_parenthesis() throws Exception {
String code = newCode(
"int foo() {",
" int a;",
" a = 0;",
" int b = ((a));",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree aAssignmentExpression = assignementExpressionFromStatement(statements.get(1));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, aAssignmentExpression);
}
@Test
public void last_assignement() throws Exception {
String code = newCode(
"int foo() {",
" int a;",
" a = 0;",
" a = 1;",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree secondAssignment = assignementExpressionFromStatement(statements.get(2));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, secondAssignment);
}
@Test
public void last_assignement_on_same_line() throws Exception {
String code = newCode(
"int foo() {",
" int a;",
" a = 0;",
" a = 1; return a;",
"}");
List<StatementTree> statements = methodBody(code);
ExpressionTree secondAssignment = assignementExpressionFromStatement(statements.get(2));
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, secondAssignment);
}
@Test
public void outside_method() throws Exception {
String code = newCode(
"int b;",
"int foo() {",
" return b;",
"}");
ClassTree classTree = classTree(code);
List<StatementTree> statements = ((MethodTree) classTree.members().get(1)).block().body();
ExpressionTree variableDeclaration = ((VariableTree) (classTree.members().get(0))).initializer();
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, variableDeclaration);
}
@Test
public void ignore_assignation_after_starting_point() throws Exception {
String code = newCode(
"int foo() {",
" int b = 0;",
" doSomething(b);",
" b = 1;",
"}");
List<StatementTree> statements = methodBody(code);
Tree expectedVariableDeclaration = initializerFromVariableDeclarationStatement(statements.get(0));
MethodInvocationTree startingPoint = (MethodInvocationTree) ((ExpressionStatementTree) statements.get(1)).expression();
Symbol searchedVariable = ((IdentifierTree) startingPoint.arguments().get(0)).symbol();
assertThatLastReassignmentsOfVariableIsEqualTo(searchedVariable, startingPoint, expectedVariableDeclaration);
}
@Test
public void ignore_assignation_after_starting_point_same_line() throws Exception {
String code = newCode(
"int foo() {",
" int b = 0;",
" doSomething(b); b = 1;",
"}");
List<StatementTree> statements = methodBody(code);
Tree expectedVariableDeclaration = initializerFromVariableDeclarationStatement(statements.get(0));
MethodInvocationTree startingPoint = (MethodInvocationTree) ((ExpressionStatementTree) statements.get(1)).expression();
Symbol searchedVariable = ((IdentifierTree) startingPoint.arguments().get(0)).symbol();
assertThatLastReassignmentsOfVariableIsEqualTo(searchedVariable, startingPoint, expectedVariableDeclaration);
}
private static void assertThatLastReassignmentsOfVariableIsEqualTo(Symbol searchedVariable, Tree startingPoint, Tree expectedVariableDeclaration) {
assertThat(ReassignmentFinder.getClosestReassignmentOrDeclarationExpression(startingPoint, searchedVariable)).isEqualTo(expectedVariableDeclaration);
}
@Test
public void known_limitation() throws Exception {
String code = newCode(
"int foo(boolean test) {",
" int a;",
" if (test) {",
" a = 0;",
" } else {",
" a = 1;", // Should have returned both thenAssignment and elseAssignment. CFG?
" }",
" return a;",
"}");
List<StatementTree> statements = methodBody(code);
StatementTree elseAssignment = ((BlockTree) ((IfStatementTree) statements.get(1)).elseStatement()).body().get(0);
ExpressionTree expression = assignementExpressionFromStatement(elseAssignment);
assertThatLastReassignmentsOfReturnedVariableIsEqualTo(statements, expression);
}
private static void assertThatLastReassignmentsOfReturnedVariableIsEqualTo(List<StatementTree> statements, ExpressionTree target) {
assertThat(getLastReassignment(statements)).isEqualTo(target);
}
private static Tree getLastReassignment(List<StatementTree> statements) {
return ReassignmentFinder.getClosestReassignmentOrDeclarationExpression(statements.get(statements.size() - 1), variableFromLastReturnStatement(statements).symbol());
}
private static IdentifierTree variableFromLastReturnStatement(List<StatementTree> statements) {
return (IdentifierTree) ((ReturnStatementTree) statements.get(statements.size() - 1)).expression();
}
private static ExpressionTree assignementExpressionFromStatement(StatementTree statement) {
return ((AssignmentExpressionTree) ((ExpressionStatementTree) statement).expression()).expression();
}
private static ExpressionTree initializerFromVariableDeclarationStatement(Tree statement) {
return ((VariableTree) statement).initializer();
}
private ClassTree classTree(String classBody) {
CompilationUnitTree compilationUnitTree = (CompilationUnitTree) p.parse(classBody);
SemanticModel.createFor(compilationUnitTree, Lists.<File>newArrayList());
return (ClassTree) compilationUnitTree.types().get(0);
}
private MethodTree methodTree(String classBody) {
ClassTree firstType = classTree(classBody);
return (MethodTree) firstType.members().get(0);
}
private List<StatementTree> methodBody(String code) {
return methodTree(code).block().body();
}
private static String newCode(String... lines) {
String lineSeparator = System.lineSeparator();
StringBuilder sb = new StringBuilder("class A {").append(lineSeparator);
for (String string : lines) {
sb.append(string).append(lineSeparator);
}
return sb.append("}").append(lineSeparator).toString();
}
}