All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.se.checks.UnclosedResourcesCheck Maven / Gradle / Ivy

The newest version!
/*
 * 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.checks;

import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.java.se.CheckerContext;
import org.sonar.java.se.ProgramState;
import org.sonar.java.se.SymbolicValueFactory;
import org.sonar.java.se.constraint.ConstraintManager;
import org.sonar.java.se.constraint.ObjectConstraint;
import org.sonar.java.se.symbolicvalues.SymbolicValue;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.Arguments;
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.NewClassTree;
import org.sonar.plugins.java.api.tree.ReturnStatementTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TryStatementTree;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonar.squidbridge.annotations.ActivatedByDefault;
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
import org.sonar.squidbridge.annotations.SqaleSubCharacteristic;

import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@Rule(
  key = "S2095",
  name = "Resources should be closed",
  priority = Priority.BLOCKER,
  tags = {"bug", "cert", "cwe", "denial-of-service", "leak", "security"})
@ActivatedByDefault
@SqaleSubCharacteristic(RulesDefinition.SubCharacteristics.LOGIC_RELIABILITY)
@SqaleConstantRemediation("5min")
public class UnclosedResourcesCheck extends SECheck {

  private enum Status {
    OPENED, CLOSED
  }

  private static final String JAVA_IO_AUTO_CLOSEABLE = "java.lang.AutoCloseable";
  private static final String JAVA_IO_CLOSEABLE = "java.io.Closeable";
  private static final String JAVA_SQL_STATEMENT = "java.sql.Statement";
  private static final String[] JDBC_RESOURCE_CREATIONS = {"java.sql.Connection", JAVA_SQL_STATEMENT};
  private static final String[] IGNORED_CLOSEABLE_SUBTYPES = {
    "java.io.ByteArrayOutputStream",
    "java.io.ByteArrayInputStream",
    "java.io.CharArrayReader",
    "java.io.CharArrayWriter",
    "java.io.StringReader",
    "java.io.StringWriter",
    "com.sun.org.apache.xml.internal.security.utils.UnsyncByteArrayOutputStream"
  };

  @Override
  public ProgramState checkPreStatement(CheckerContext context, Tree syntaxNode) {
    final PreStatementVisitor visitor = new PreStatementVisitor(context);
    syntaxNode.accept(visitor);
    return visitor.programState;
  }

  @Override
  public ProgramState checkPostStatement(CheckerContext context, Tree syntaxNode) {
    final PostStatementVisitor visitor = new PostStatementVisitor(context);
    syntaxNode.accept(visitor);
    return visitor.programState;
  }

  @Override
  public void checkEndOfExecutionPath(CheckerContext context, ConstraintManager constraintManager) {
    final List constraints = context.getState().getFieldConstraints(Status.OPENED);
    for (ObjectConstraint constraint : constraints) {
      Tree syntaxNode = constraint.syntaxNode();
      String name = null;
      if (syntaxNode.is(Tree.Kind.NEW_CLASS)) {
        name = ((NewClassTree) syntaxNode).identifier().symbolType().name();
      } else if (syntaxNode.is(Tree.Kind.METHOD_INVOCATION)) {
        name = ((MethodInvocationTree) syntaxNode).symbolType().name();
      }
      if(name != null) {
        context.reportIssue(syntaxNode, this, "Close this \"" + name + "\".");
      }
    }
  }

  private static boolean needsClosing(Type type) {
    for (String ignoredTypes : IGNORED_CLOSEABLE_SUBTYPES) {
      if (type.is(ignoredTypes)) {
        return false;
      }
    }
    return isCloseable(type);
  }

  private static boolean isCloseable(Type type) {
    return type.isSubtypeOf(JAVA_IO_AUTO_CLOSEABLE) || type.isSubtypeOf(JAVA_IO_CLOSEABLE);
  }

  private static boolean isOpeningResource(NewClassTree syntaxNode) {
    if (isWithinTryHeader(syntaxNode)) {
      return false;
    }
    return needsClosing(syntaxNode.symbolType());
  }

  private static boolean isWithinTryHeader(Tree syntaxNode) {
    final Tree parent = syntaxNode.parent();
    if (parent.is(Tree.Kind.VARIABLE)) {
      return isTryStatementResource((VariableTree) parent);
    }
    return false;
  }

  private static boolean isTryStatementResource(VariableTree variable) {
    final TryStatementTree tryStatement = getEnclosingTryStatement(variable);
    return tryStatement != null && tryStatement.resources().contains(variable);
  }

  private static TryStatementTree getEnclosingTryStatement(Tree syntaxNode) {
    Tree parent = syntaxNode.parent();
    while (parent != null) {
      if (parent.is(Tree.Kind.TRY_STATEMENT)) {
        return (TryStatementTree) parent;
      }
      parent = parent.parent();
    }
    return null;
  }

  private static class ResourceWrapperSymbolicValue extends SymbolicValue {

    private final SymbolicValue dependent;

    ResourceWrapperSymbolicValue(int id, SymbolicValue dependent) {
      super(id);
      this.dependent = dependent;
    }

    @Override
    public SymbolicValue wrappedValue() {
      return dependent.wrappedValue();
    }

  }

  private static class WrappedValueFactory implements SymbolicValueFactory {

    private final SymbolicValue value;

    WrappedValueFactory(SymbolicValue value) {
      this.value = value;
    }

    @Override
    public SymbolicValue createSymbolicValue(int counter, Tree syntaxNode) {
      return new ResourceWrapperSymbolicValue(counter, value);
    }

  }

  private static class PreStatementVisitor extends CheckerTreeNodeVisitor {
    private static final String CLOSE_METHOD_NAME = "close";
    private static final String GET_MORE_RESULTS_METHOD_NAME = "getMoreResults";

    private static final String GET_RESULT_SET = "getResultSet";

    private final ConstraintManager constraintManager;

    PreStatementVisitor(CheckerContext context) {
      super(context.getState());
      constraintManager = context.getConstraintManager();
    }

    @Override
    public void visitNewClass(NewClassTree syntaxNode) {
      final List arguments = programState.peekValues(syntaxNode.arguments().size());
      if (isOpeningResource(syntaxNode)) {
        Iterator iterator = arguments.iterator();
        for (ExpressionTree argument : syntaxNode.arguments()) {
          if (!iterator.hasNext()) {
            throw new IllegalStateException("Mismatch between declared constructor arguments and argument values!");
          }
          final Type type = argument.symbolType();
          final SymbolicValue value = iterator.next();
          if (isCloseable(type)) {
            constraintManager.setValueFactory(new WrappedValueFactory(value));
            break;
          }
        }
      } else {
        closeArguments(syntaxNode.arguments(), 0);
      }
    }

    @Override
    public void visitReturnStatement(ReturnStatementTree syntaxNode) {
      SymbolicValue currentVal = programState.peekValue();
      if (currentVal != null) {
        final ExpressionTree expression = syntaxNode.expression();
        if (expression != null) {
          if (expression.is(Tree.Kind.IDENTIFIER)) {
            final IdentifierTree identifier = (IdentifierTree) expression;
            currentVal = programState.getValue(identifier.symbol());
          } else {
            currentVal = programState.peekValue();
          }
          programState = closeResource(programState, currentVal);
        }
      }
    }

    @Override
    public void visitAssignmentExpression(AssignmentExpressionTree syntaxNode) {
      final ExpressionTree variable = syntaxNode.variable();
      if (variable.is(Tree.Kind.ARRAY_ACCESS_EXPRESSION)) {
        List stackedValues = programState.peekValues(2);
        SymbolicValue value = stackedValues.get(1);
        programState = closeResource(programState, value);
      }
    }

    @Override
    public void visitMethodInvocation(MethodInvocationTree syntaxNode) {
      final ExpressionTree methodSelect = syntaxNode.methodSelect();
      if (isClosingResource(syntaxNode.symbol())) {
        if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) {
          final ExpressionTree targetExpression = ((MemberSelectExpressionTree) methodSelect).expression();
          if (targetExpression.is(Tree.Kind.IDENTIFIER)) {
            final IdentifierTree identifier = (IdentifierTree) targetExpression;
            programState = closeResource(programState, programState.getValue(identifier.symbol()));
          } else {
            programState = closeResource(programState, programState.peekValue());
          }
        }
      } else if (syntaxNode.methodSelect().is(Tree.Kind.MEMBER_SELECT) && isOpeningResultSet(syntaxNode.symbol())) {
        final SymbolicValue value = getTargetValue(syntaxNode);
        constraintManager.setValueFactory(new WrappedValueFactory(value));
      } else if (isClosingResultSets(syntaxNode.symbol())) {
        if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) {
          final SymbolicValue value = getTargetValue(syntaxNode);
          closeResultSetsRelatedTo(value);
        }
      } else {
        closeArguments(syntaxNode.arguments(), 1);
      }
    }

    private SymbolicValue getTargetValue(MethodInvocationTree syntaxNode) {
      final ExpressionTree targetExpression = ((MemberSelectExpressionTree) syntaxNode.methodSelect()).expression();
      final SymbolicValue value;
      if (targetExpression.is(Tree.Kind.IDENTIFIER)) {
        final IdentifierTree identifier = (IdentifierTree) targetExpression;
        value = programState.getValue(identifier.symbol());
      } else {
        value = programState.peekValue();
      }
      return value;
    }

    private void closeResultSetsRelatedTo(SymbolicValue value) {
      for (Map.Entry constrainedValue : programState.getValuesWithConstraints(Status.OPENED).entrySet()) {
        if (constrainedValue.getKey() instanceof ResourceWrapperSymbolicValue) {
          ResourceWrapperSymbolicValue rValue = (ResourceWrapperSymbolicValue) constrainedValue.getKey();
          if (value.equals(rValue.dependent)) {
            programState = programState.addConstraint(rValue, constrainedValue.getValue().withStatus(Status.CLOSED));
          }
        }
      }
    }

    private void closeArguments(final Arguments arguments, int stackOffset) {
      final List values = programState.peekValues(arguments.size() + stackOffset);
      final List argumentValues = values.subList(stackOffset, values.size());
      for (SymbolicValue target : argumentValues) {
        programState = closeResource(programState, target);
      }
    }

    private static ProgramState closeResource(ProgramState programState, @Nullable final SymbolicValue target) {
      if (target != null) {
        ObjectConstraint oConstraint = openedConstraint(programState, target);
        if (oConstraint != null) {
          return programState.addConstraint(target.wrappedValue(), oConstraint.withStatus(Status.CLOSED));
        }
      }
      return programState;
    }

    private static ObjectConstraint openedConstraint(ProgramState programState, SymbolicValue value) {
      final Object constraint = programState.getConstraint(value.wrappedValue());
      if (constraint instanceof ObjectConstraint) {
        ObjectConstraint oConstraint = (ObjectConstraint) constraint;
        if (oConstraint.hasStatus(Status.OPENED)) {
          return oConstraint;
        }
      }
      return null;
    }

    private static boolean isClosingResource(Symbol symbol) {
      return isMethodMatchingName(symbol, CLOSE_METHOD_NAME);
    }

    private static boolean isClosingResultSets(Symbol symbol) {
      return isMethodMatchingName(symbol, GET_MORE_RESULTS_METHOD_NAME);
    }

    private static boolean isOpeningResultSet(Symbol symbol) {
      return isMethodMatchingName(symbol, GET_RESULT_SET);
    }

    private static boolean isMethodMatchingName(Symbol symbol, String matchName) {
      return symbol.isMethodSymbol() && matchName.equals(symbol.name());
    }

  }

  private static class PostStatementVisitor extends CheckerTreeNodeVisitor {

    PostStatementVisitor(CheckerContext context) {
      super(context.getState());
    }

    @Override
    public void visitNewClass(NewClassTree syntaxNode) {
      if (isOpeningResource(syntaxNode)) {
        final SymbolicValue instanceValue = programState.peekValue();
        if (!(instanceValue instanceof ResourceWrapperSymbolicValue)) {
          programState = programState.addConstraint(instanceValue, new ObjectConstraint(false, false, syntaxNode, Status.OPENED));
        }
      }
    }

    @Override
    public void visitMethodInvocation(MethodInvocationTree syntaxNode) {
      if (syntaxNode.methodSelect().is(Tree.Kind.MEMBER_SELECT) && needsClosing(syntaxNode.symbolType())) {
        final ExpressionTree targetExpression = ((MemberSelectExpressionTree) syntaxNode.methodSelect()).expression();
        if (targetExpression.is(Tree.Kind.IDENTIFIER) && !isWithinTryHeader(syntaxNode)
          && (syntaxNode.symbol().isStatic() || isJdbcResourceCreation(targetExpression))) {
          programState = programState.addConstraint(programState.peekValue(), new ObjectConstraint(false, false, syntaxNode, Status.OPENED));
        }
      }
    }

    private static boolean isJdbcResourceCreation(ExpressionTree targetExpression) {
      for (String creator : JDBC_RESOURCE_CREATIONS) {
        if (targetExpression.symbolType().is(creator)) {
          return true;
        }
      }
      return false;
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy