org.sonar.java.closeresource.CloseableVisitor Maven / Gradle / Ivy
/*
* SonarQube Java
* Copyright (C) 2012 SonarSource
* [email protected]
*
* 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 02
*/
package org.sonar.java.closeresource;
import org.sonar.java.checks.methods.MethodMatcher;
import org.sonar.java.checks.methods.MethodInvocationMatcherCollection;
import org.sonar.java.checks.methods.TypeCriteria;
import org.sonar.java.symexecengine.ExecutionState;
import org.sonar.java.symexecengine.State;
import org.sonar.java.symexecengine.SymbolicExecutionCheck;
import org.sonar.java.symexecengine.SymbolicValue;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
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.NewClassTree;
import org.sonar.plugins.java.api.tree.ReturnStatementTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeCastTree;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CloseableVisitor extends SymbolicExecutionCheck {
private static final String CLOSE_METHOD_NAME = "close";
private static final String JAVA_IO_CLOSEABLE = "java.io.Closeable";
private static final String JAVA_LANG_AUTOCLOSEABLE = "java.lang.AutoCloseable";
private static final MethodInvocationMatcherCollection CLOSE_INVOCATIONS = MethodInvocationMatcherCollection.create(
MethodMatcher.create()
.typeDefinition(TypeCriteria.subtypeOf(JAVA_IO_CLOSEABLE))
.name(CLOSE_METHOD_NAME)
.withNoParameterConstraint(),
MethodMatcher.create()
.typeDefinition(TypeCriteria.subtypeOf(JAVA_LANG_AUTOCLOSEABLE))
.name(CLOSE_METHOD_NAME)
.withNoParameterConstraint(),
MethodMatcher.create()
.typeDefinition(TypeCriteria.subtypeOf("org.springframework.context.support.AbstractApplicationContext"))
.name("registerShutdownHook")
.withNoParameterConstraint());
private static final String[] IGNORED_CLOSEABLE_SUBTYPES = {
"java.io.ByteArrayOutputStream",
"java.io.ByteArrayInputStream",
"java.io.StringReader",
"java.io.StringWriter",
"java.io.CharArrayReader",
"java.io.CharArrayWriter"
};
@Override
protected void initialize(ExecutionState executionState, MethodTree tree, List arguments) {
for (SymbolicValue argument : arguments) {
ignoreValue(executionState, argument);
}
}
private static void ignoreValue(ExecutionState executionState, SymbolicValue value) {
executionState.markValueAs(value, new CloseableState.Ignored(value.getTree()));
}
private static boolean isCloseableOrAutoCloseableSubtype(Type type) {
return type.isSubtypeOf(JAVA_IO_CLOSEABLE) || type.isSubtypeOf(JAVA_LANG_AUTOCLOSEABLE);
}
private static boolean isIgnoredCloseableSubtype(Type type) {
for (String fullyQualifiedName : IGNORED_CLOSEABLE_SUBTYPES) {
if (type.isSubtypeOf(fullyQualifiedName)) {
return true;
}
}
return false;
}
private static boolean isSubclassOfInputStreamOrOutputStreamWithoutClose(Type type) {
Symbol.TypeSymbol typeSymbol = type.symbol();
Type superClass = typeSymbol.superClass();
if (superClass != null && (superClass.is("java.io.OutputStream") || superClass.is("java.io.InputStream"))) {
return typeSymbol.lookupSymbols(CLOSE_METHOD_NAME).isEmpty();
}
return false;
}
@Override
protected void onAssignment(ExecutionState executionState, Tree tree, Symbol variable, ExpressionTree expression) {
// check first usage of closeables in order to manage use of same symbol
ignoreClosableSymbols(executionState, expression);
if ((tree.is(Tree.Kind.VARIABLE) || isVariableIdentifierOrMemberSelect((AssignmentExpressionTree) tree)) && isCloseableOrAutoCloseableSubtype(variable.type())) {
executionState.markValueAs(variable, getCloseableStateFromExpression(executionState, variable, expression));
}
}
private static boolean isVariableIdentifierOrMemberSelect(AssignmentExpressionTree tree) {
return tree.variable().is(Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT);
}
private static State getCloseableStateFromExpression(ExecutionState executionState, Symbol symbol, @Nullable ExpressionTree expression) {
if (shouldBeIgnored(executionState, symbol, expression)) {
return new CloseableState.Ignored(expression);
} else if (isNull(expression)) {
return State.UNSET;
} else if (expression.is(Tree.Kind.NEW_CLASS)) {
if (usesIgnoredCloseableAsArgument(executionState, ((NewClassTree) expression).arguments())) {
return new CloseableState.Ignored(expression);
}
return new CloseableState.Open(expression);
}
// TODO SONARJAVA-1029 : Engine currently ignore closeables which are retrieved from method calls. Handle them as OPEN.
return new CloseableState.Ignored(expression);
}
private static boolean isNull(ExpressionTree expression) {
return expression == null || expression.is(Tree.Kind.NULL_LITERAL);
}
private static boolean shouldBeIgnored(ExecutionState executionState, Symbol symbol, @Nullable ExpressionTree expression) {
return shouldBeIgnored(executionState, symbol) || shouldBeIgnored(expression);
}
private static boolean shouldBeIgnored(ExecutionState executionState, Symbol symbol) {
return isSymbolIgnored(executionState, symbol) || symbol.isFinal()
|| isIgnoredCloseableSubtype(symbol.type())
|| isSubclassOfInputStreamOrOutputStreamWithoutClose(symbol.type());
}
private static boolean shouldBeIgnored(@Nullable ExpressionTree expression) {
return expression != null && (isSubclassOfInputStreamOrOutputStreamWithoutClose(expression.symbolType()) || isIgnoredCloseableSubtype(expression.symbolType()));
}
private static boolean usesIgnoredCloseableAsArgument(ExecutionState executionState, List arguments) {
for (ExpressionTree argument : arguments) {
if (isIgnoredCloseableArgument(executionState, argument)) {
return true;
}
}
return false;
}
private static boolean isIgnoredCloseableArgument(ExecutionState executionState, ExpressionTree argument) {
return isNewClassWithIgnoredArguments(executionState, argument)
|| isMethodInvocationWithIgnoredArguments(executionState, argument)
|| useIgnoredCloseable(executionState, argument)
|| isSubclassOfInputStreamOrOutputStreamWithoutClose(argument.symbolType());
}
private static boolean isNewClassWithIgnoredArguments(ExecutionState executionState, ExpressionTree argument) {
return argument.is(Tree.Kind.NEW_CLASS) && usesIgnoredCloseableAsArgument(executionState, ((NewClassTree) argument).arguments());
}
private static boolean isMethodInvocationWithIgnoredArguments(ExecutionState executionState, ExpressionTree argument) {
return argument.is(Tree.Kind.METHOD_INVOCATION) && usesIgnoredCloseableAsArgument(executionState, ((MethodInvocationTree) argument).arguments());
}
private static boolean useIgnoredCloseable(ExecutionState executionState, ExpressionTree expression) {
if (expression.is(Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT)) {
IdentifierTree identifier;
if (expression.is(Tree.Kind.MEMBER_SELECT)) {
identifier = ((MemberSelectExpressionTree) expression).identifier();
} else {
identifier = (IdentifierTree) expression;
}
if (isIgnoredCloseable(executionState, identifier.symbol())) {
return true;
}
}
return false;
}
private static boolean isIgnoredCloseable(ExecutionState executionState, Symbol symbol) {
if (CloseableVisitor.isCloseableOrAutoCloseableSubtype(symbol.type()) && !symbol.owner().isMethodSymbol()) {
return true;
} else {
return isSymbolIgnored(executionState, symbol);
}
}
private static boolean isSymbolIgnored(ExecutionState executionState, Symbol symbol) {
List statesOf = executionState.getStatesOf(symbol);
for (State state : statesOf) {
if ((state instanceof CloseableState) && ((CloseableState) state).isIgnored()) {
return true;
}
}
return false;
}
@Override
protected void onTryResourceClosed(ExecutionState executionState, SymbolicValue resource) {
ignoreValue(executionState, resource);
}
@Override
protected void onValueReturned(ExecutionState executionState, ReturnStatementTree tree, ExpressionTree expression) {
ignoreClosableSymbols(executionState, expression);
}
@Override
protected void onExecutableElementInvocation(ExecutionState executionState, Tree tree, List arguments) {
if (tree.is(Tree.Kind.NEW_CLASS)) {
ignoreClosableSymbols(executionState, ((NewClassTree) tree).arguments());
} else {
MethodInvocationTree methodInvocation = (MethodInvocationTree) tree;
if (CLOSE_INVOCATIONS.anyMatch(methodInvocation)) {
ExpressionTree methodSelect = methodInvocation.methodSelect();
if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) {
ExpressionTree expression = ((MemberSelectExpressionTree) methodSelect).expression();
if (expression.is(Tree.Kind.IDENTIFIER)) {
executionState.markValueAs(((IdentifierTree) expression).symbol(), new CloseableState.Closed(expression));
}
}
} else {
ignoreClosableSymbols(executionState, methodInvocation.arguments());
}
}
}
private static void ignoreClosableSymbols(ExecutionState executionState, List expressions) {
for (ExpressionTree expression : expressions) {
ignoreClosableSymbols(executionState, expression);
}
}
private static void ignoreClosableSymbols(ExecutionState executionState, @Nullable ExpressionTree expression) {
if (expression != null) {
if (expression.is(Tree.Kind.IDENTIFIER) && CloseableVisitor.isCloseableOrAutoCloseableSubtype(expression.symbolType())) {
executionState.markValueAs(((IdentifierTree) expression).symbol(), new CloseableState.Ignored(expression));
} else if (expression.is(Tree.Kind.MEMBER_SELECT)) {
ignoreClosableSymbols(executionState, ((MemberSelectExpressionTree) expression).identifier());
} else if (expression.is(Tree.Kind.TYPE_CAST)) {
ignoreClosableSymbols(executionState, ((TypeCastTree) expression).expression());
} else if (expression.is(Tree.Kind.METHOD_INVOCATION)) {
ignoreClosableSymbols(executionState, ((MethodInvocationTree) expression).arguments());
} else if (expression.is(Tree.Kind.NEW_CLASS)) {
ignoreClosableSymbols(executionState, ((NewClassTree) expression).arguments());
}
}
}
private final Set issueTree = new HashSet<>();
@Override
protected void onValueUnreachable(ExecutionState executionState, State state) {
if (state instanceof CloseableState.Open) {
issueTree.addAll(state.reportingTrees());
}
}
public Set getIssueTrees() {
return issueTree;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy