/* * 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.check.Rule; import org.sonar.check.RuleProperty; import org.sonar.java.matcher.MethodMatcherCollection; import org.sonar.java.matcher.MethodMatcherFactory; import org.sonar.java.se.CheckerContext; import org.sonar.java.se.ExplodedGraph; import org.sonar.java.se.ProgramState; 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.JavaFileScannerContext; import org.sonar.plugins.java.api.tree.ExpressionTree; 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.squidbridge.annotations.RuleTemplate; import javax.annotation.Nullable; import java.util.List; @Rule(key = "S3546") @RuleTemplate public class CustomUnclosedResourcesCheck extends SECheck { static class ResourceStatus implements ObjectConstraint.Status { } //see SONARJAVA-1624 fields cannot be static, different instances are needed for every instance of this template rule private final ResourceStatus OPENED = new ResourceStatus(); private final ResourceStatus CLOSED = new ResourceStatus(); @RuleProperty( key = "constructor", description = "the fully-qualified name of a constructor that creates an open resource." + " An optional signature may be specified after the class name." + " E.G. \"org.assoc.res.MyResource\" or \"org.assoc.res.MySpecialResource(java.lang.String, int)\"") public String constructor = ""; @RuleProperty( key = "factoryMethod", description = "the fully-qualified name of a factory method that returns an open resource, with or without a parameter list." + " E.G. \"org.assoc.res.ResourceFactory$Innerclass#create\" or \"org.assoc.res.SpecialResourceFactory#create(java.lang.String, int)\"") public String factoryMethod = ""; @RuleProperty( key = "openingMethod", description = "the fully-qualified name of a method that opens an existing resource, with or without a parameter list." + " E.G. \"org.assoc.res.ResourceFactory#create\" or \"org.assoc.res.SpecialResourceFactory #create(java.lang.String, int)\"") public String openingMethod = ""; @RuleProperty( key = "closingMethod", description = "the fully-qualified name of the method which closes the open resource, with or without a parameter list." + " E.G. \"org.assoc.res.MyResource#closeMe\" or \"org.assoc.res.MySpecialResource#closeMe(java.lang.String, int)\"") public String closingMethod = ""; private MethodMatcherCollection classConstructor; private MethodMatcherCollection factoryList; private MethodMatcherCollection openingList; private MethodMatcherCollection closingList; @Override public ProgramState checkPreStatement(CheckerContext context, Tree syntaxNode) { AbstractStatementVisitor visitor = new PreStatementVisitor(context); syntaxNode.accept(visitor); return visitor.programState; } @Override public ProgramState checkPostStatement(CheckerContext context, Tree syntaxNode) { PostStatementVisitor visitor = new PostStatementVisitor(context); syntaxNode.accept(visitor); return visitor.programState; } @Override public void checkEndOfExecutionPath(CheckerContext context, ConstraintManager constraintManager) { ExplodedGraph.Node node = context.getNode(); context.getState().getValuesWithConstraints(OPENED).keySet() .forEach(sv -> processUnclosedSymbolicValue(node, sv)); } private void processUnclosedSymbolicValue(ExplodedGraph.Node node, SymbolicValue sv) { FlowComputation.flow(node, sv, ObjectConstraint.statusPredicate(OPENED)).stream() .filter(location -> location.syntaxNode.is(Tree.Kind.NEW_CLASS, Tree.Kind.METHOD_INVOCATION)) .forEach(this::reportIssue); } private void reportIssue(JavaFileScannerContext.Location location) { String message = "Close this \"" + name(location.syntaxNode) + "\"."; String flowMessage = name(location.syntaxNode) + " is never closed"; reportIssue(location.syntaxNode, message, FlowComputation.singleton(flowMessage, location.syntaxNode)); } private static String name(Tree tree) { if (tree.is(Tree.Kind.NEW_CLASS)) { return ((NewClassTree) tree).symbolType().name(); } MethodInvocationTree mit = (MethodInvocationTree) tree; if (mit.symbolType().isVoid()) { return mit.symbol().owner().name(); } return mit.symbolType().name(); } private static MethodMatcherCollection createMethodMatchers(String rule) { if (rule.length() > 0) { return MethodMatcherCollection.create(MethodMatcherFactory.methodMatcher(rule)); } else { return MethodMatcherCollection.create(); } } private abstract class AbstractStatementVisitor extends CheckerTreeNodeVisitor { protected AbstractStatementVisitor(ProgramState programState) { super(programState); } protected void closeResource(@Nullable SymbolicValue target) { if (target != null) { ObjectConstraint<ResourceStatus> oConstraint = programState.getConstraintWithStatus(target, OPENED); if (oConstraint != null) { programState = programState.addConstraint(target.wrappedValue(), oConstraint.withStatus(CLOSED)); } } } protected void openResource(SymbolicValue sv) { programState = programState.addConstraint(sv, new ObjectConstraint<>(false, false, OPENED)); } protected boolean isClosingResource(MethodInvocationTree mit) { return closingMethods().anyMatch(mit); } private MethodMatcherCollection closingMethods() { if (closingList == null) { closingList = createMethodMatchers(closingMethod); } return closingList; } } private class PreStatementVisitor extends AbstractStatementVisitor { protected PreStatementVisitor(CheckerContext context) { super(context.getState()); } @Override public void visitNewClass(NewClassTree syntaxNode) { programState.peekValues(syntaxNode.arguments().size()).forEach(this::closeResource); } @Override public void visitMethodInvocation(MethodInvocationTree mit) { if (isOpeningResource(mit)) { openResource(getTargetSV(mit)); } else if (isClosingResource(mit)) { closeResource(getTargetSV(mit)); } else { programState.peekValues(mit.arguments().size()).forEach(this::closeResource); } } private SymbolicValue getTargetSV(MethodInvocationTree mit) { List<SymbolicValue> values = programState.peekValues(mit.arguments().size() + 1); return values.get(values.size() -1); } private boolean isOpeningResource(MethodInvocationTree syntaxNode) { return openingMethods().anyMatch(syntaxNode); } @Override public void visitReturnStatement(ReturnStatementTree syntaxNode) { ExpressionTree expression = syntaxNode.expression(); if (expression != null) { closeResource(programState.peekValue()); } } private MethodMatcherCollection openingMethods() { if (openingList == null) { openingList = createMethodMatchers(openingMethod); } return openingList; } } private class PostStatementVisitor extends AbstractStatementVisitor { protected PostStatementVisitor(CheckerContext context) { super(context.getState()); } @Override public void visitNewClass(NewClassTree newClassTree) { if (isCreatingResource(newClassTree)) { openResource(programState.peekValue()); } } @Override public void visitMethodInvocation(MethodInvocationTree mit) { if (isCreatingResource(mit)) { openResource(programState.peekValue()); } } private boolean isCreatingResource(NewClassTree newClassTree) { return constructorClasses().anyMatch(newClassTree); } private MethodMatcherCollection constructorClasses() { if (classConstructor == null) { classConstructor = MethodMatcherCollection.create(); if (constructor.length() > 0) { classConstructor.add(MethodMatcherFactory.constructorMatcher(constructor)); } } return classConstructor; } private boolean isCreatingResource(MethodInvocationTree mit) { return factoryMethods().anyMatch(mit); } private MethodMatcherCollection factoryMethods() { if (factoryList == null) { factoryList = createMethodMatchers(factoryMethod); } return factoryList; } } }