/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2008, University of Maryland
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.detect;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.WillClose;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.WillNotClose;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector2;
import edu.umd.cs.findbugs.DetectorFactoryCollection;
import edu.umd.cs.findbugs.NonReportingDetector;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.CleanupObligation;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import edu.umd.cs.findbugs.annotations.DischargesObligation;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.interproc.MethodPropertyDatabase;
import edu.umd.cs.findbugs.ba.interproc.PropertyDatabaseFormatException;
import edu.umd.cs.findbugs.ba.obl.MatchMethodEntry;
import edu.umd.cs.findbugs.ba.obl.Obligation;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabase;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabaseActionType;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabaseEntry;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabaseEntryType;
import edu.umd.cs.findbugs.bcel.BCELUtil;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.ml.SplitCamelCaseIdentifier;
import edu.umd.cs.findbugs.util.ExactStringMatcher;
import edu.umd.cs.findbugs.util.RegexStringMatcher;
import edu.umd.cs.findbugs.util.SubtypeTypeMatcher;
/**
* Build the ObligationPolicyDatabase used by ObligationAnalysis. We preload the
* database with some known resources types needing to be released, and augment
* the database with additional entries discovered through scanning referenced
* classes for annotations.
*
* @author David Hovemeyer
*/
public class BuildObligationPolicyDatabase implements Detector2, NonReportingDetector {
static class AuxilaryObligationPropertyDatabase extends MethodPropertyDatabase<String> {
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.findbugs.ba.interproc.PropertyDatabase#decodeProperty(
* java.lang.String)
*/
@Override
protected String decodeProperty(String propStr) throws PropertyDatabaseFormatException {
return propStr;
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.findbugs.ba.interproc.PropertyDatabase#encodeProperty(
* java.lang.Object)
*/
@Override
protected String encodeProperty(String property) {
return property;
}
}
private static final boolean INFER_CLOSE_METHODS = SystemProperties.getBoolean("oa.inferclose", true);
private static final boolean DEBUG_ANNOTATIONS = SystemProperties.getBoolean("oa.debug.annotations");
private static final boolean DUMP_DB = SystemProperties.getBoolean("oa.dumpdb");
private final BugReporter reporter;
private final ObligationPolicyDatabase database;
private final ClassDescriptor willClose;
private final ClassDescriptor willNotClose;
private final ClassDescriptor willCloseWhenClosed;
private final ClassDescriptor cleanupObligation;
private final ClassDescriptor createsObligation;
private final ClassDescriptor dischargesObligation;
/**
* Did we see any WillClose, WillNotClose, or WillCloseWhenClosed
* annotations in application code?
*/
private boolean sawAnnotationsInApplicationCode;
public BuildObligationPolicyDatabase(BugReporter bugReporter) {
this.reporter = bugReporter;
final DescriptorFactory instance = DescriptorFactory.instance();
this.willClose = instance.getClassDescriptor(WillClose.class);
this.willNotClose = instance.getClassDescriptor(WillNotClose.class);
this.willCloseWhenClosed = instance.getClassDescriptor(WillCloseWhenClosed.class);
this.cleanupObligation = instance.getClassDescriptor(CleanupObligation.class);
this.createsObligation = instance.getClassDescriptor(CreatesObligation.class);
this.dischargesObligation = instance.getClassDescriptor(DischargesObligation.class);
database = new ObligationPolicyDatabase();
addBuiltInPolicies();
URL u = DetectorFactoryCollection.getCoreResource("obligationPolicy.db");
try {
if (u != null) {
AuxilaryObligationPropertyDatabase db = new AuxilaryObligationPropertyDatabase();
db.read(u.openStream());
for (Map.Entry<MethodDescriptor, String> e : db.entrySet()) {
String[] v = e.getValue().split(",");
Obligation obligation = database.getFactory().getObligationByName(v[2]);
if (obligation == null)
obligation = database.getFactory().addObligation(v[2]);
database.addEntry(new MatchMethodEntry(e.getKey(), ObligationPolicyDatabaseActionType.valueOf(v[0]),
ObligationPolicyDatabaseEntryType.valueOf(v[1]), obligation));
}
}
} catch (Exception e) {
AnalysisContext.logError("Unable to read " + u, e);
}
scanForResourceTypes();
Global.getAnalysisCache().eagerlyPutDatabase(ObligationPolicyDatabase.class, database);
}
public void visitClass(ClassDescriptor classDescriptor) throws CheckedAnalysisException {
XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDescriptor);
// Is this class an obligation type?
Obligation thisClassObligation = database.getFactory().getObligationByType(xclass.getClassDescriptor());
// Scan methods for uses of obligation-related annotations
for (XMethod xmethod : xclass.getXMethods()) {
// Is this method marked with @CreatesObligation?
if (thisClassObligation != null) {
if (xmethod.getAnnotation(createsObligation) != null) {
database.addEntry(new MatchMethodEntry(xmethod, ObligationPolicyDatabaseActionType.ADD,
ObligationPolicyDatabaseEntryType.STRONG, thisClassObligation));
}
// Is this method marked with @DischargesObligation?
if (xmethod.getAnnotation(dischargesObligation) != null) {
database.addEntry(new MatchMethodEntry(xmethod, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, thisClassObligation));
}
}
// See what obligation parameters there are
Obligation[] paramObligationTypes = database.getFactory().getParameterObligationTypes(xmethod);
//
// Check for @WillCloseWhenClosed, @WillClose, @WillNotClose, or
// other
// indications of how obligation parameters are handled.
//
boolean methodHasCloseInName = false;
if (INFER_CLOSE_METHODS) {
SplitCamelCaseIdentifier splitter = new SplitCamelCaseIdentifier(xmethod.getName());
methodHasCloseInName = splitter.split().contains("close");
}
for (int i = 0; i < xmethod.getNumParams(); i++)
if (paramObligationTypes[i] != null) {
if (xmethod.getParameterAnnotation(i, willCloseWhenClosed) != null) {
//
// Calling this method deletes a parameter obligation
// and
// creates a new obligation for the object returned by
// the method.
//
handleWillCloseWhenClosed(xmethod, paramObligationTypes[i]);
} else if (xmethod.getParameterAnnotation(i, willClose) != null) {
if (paramObligationTypes[i] == null) {
// Hmm...
if (DEBUG_ANNOTATIONS) {
System.out.println("Method " + xmethod.toString() + " has param " + i + " annotated @WillClose, "
+ "but its type is not an obligation type");
}
} else {
addParameterDeletesObligationDatabaseEntry(xmethod, paramObligationTypes[i],
ObligationPolicyDatabaseEntryType.STRONG);
}
sawAnnotationsInApplicationCode = true;
} else if (xmethod.getParameterAnnotation(i, willNotClose) != null) {
// No database entry needs to be added
sawAnnotationsInApplicationCode = true;
} else if (paramObligationTypes[i] != null) {
if (INFER_CLOSE_METHODS && methodHasCloseInName) {
// Method has "close" in its name.
// Assume that it deletes the obligation.
addParameterDeletesObligationDatabaseEntry(xmethod, paramObligationTypes[i],
ObligationPolicyDatabaseEntryType.STRONG);
} else {
// not yet...
/*
* // Interesting case: we have a parameter which is
* // an Obligation type, but no annotation or other
* indication // what is done by the method with the
* obligation. // We'll create a "weak" database
* entry deleting the // obligation. If strict
* checking is performed, // weak entries are
* ignored.
*/
if (xmethod.getName().equals("<init>") || xmethod.isStatic()
|| xmethod.getName().toLowerCase().indexOf("close") >= 0
|| xmethod.getSignature().toLowerCase().indexOf("Closeable") >= 0)
addParameterDeletesObligationDatabaseEntry(xmethod, paramObligationTypes[i],
ObligationPolicyDatabaseEntryType.WEAK);
}
}
}
}
}
public void finishPass() {
//
// If we saw any obligation-related annotations in the application
// code, then we enable strict checking.
// Otherwise, we disable it.
//
database.setStrictChecking(sawAnnotationsInApplicationCode);
if (DUMP_DB || ObligationPolicyDatabase.DEBUG) {
System.out.println("======= Completed ObligationPolicyDatabase ======= ");
System.out.println("Strict checking is " + (database.isStrictChecking() ? "ENABLED" : "disabled"));
for (ObligationPolicyDatabaseEntry entry : database.getEntries()) {
System.out.println(" * " + entry);
}
System.out.println("================================================== ");
}
}
public String getDetectorClassName() {
return this.getClass().getName();
}
private void addBuiltInPolicies() {
// Add the database entries describing methods that add and delete
// file stream/reader obligations.
addFileStreamEntries("InputStream");
addFileStreamEntries("OutputStream");
addFileStreamEntries("Reader");
addFileStreamEntries("Writer");
Obligation javaIoInputStreamObligation = database.getFactory().getObligationByName("java.io.InputStream");
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.lang.Class")),
new ExactStringMatcher("getResourceAsStream"),
new ExactStringMatcher("(Ljava/lang/String;)Ljava/io/InputStream;"), false,
ObligationPolicyDatabaseActionType.ADD, ObligationPolicyDatabaseEntryType.STRONG, javaIoInputStreamObligation));
Obligation javaIoOutputStreamObligation = database.getFactory().getObligationByName("java.io.OutputStream");
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil
.getObjectTypeInstance("java.util.logging.StreamHandler")), new ExactStringMatcher("setOutputStream"),
new ExactStringMatcher("(Ljava/io/OutputStream;)V"), false, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, javaIoOutputStreamObligation));
// Database obligation types
Obligation connection = database.getFactory().addObligation("java.sql.Connection");
Obligation statement = database.getFactory().addObligation("java.sql.Statement");
Obligation resultSet = database.getFactory().addObligation("java.sql.ResultSet");
// Add factory method entries for database obligation types
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.DriverManager")),
new ExactStringMatcher("getConnection"), new RegexStringMatcher("^.*\\)Ljava/sql/Connection;$"), false,
ObligationPolicyDatabaseActionType.ADD, ObligationPolicyDatabaseEntryType.STRONG, connection));
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.Connection")),
new ExactStringMatcher("createStatement"), new RegexStringMatcher("^.*\\)Ljava/sql/Statement;$"), false,
ObligationPolicyDatabaseActionType.ADD, ObligationPolicyDatabaseEntryType.STRONG, statement));
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.Connection")),
new ExactStringMatcher("prepareStatement"), new RegexStringMatcher("^.*\\)Ljava/sql/PreparedStatement;$"), false,
ObligationPolicyDatabaseActionType.ADD, ObligationPolicyDatabaseEntryType.STRONG, statement));
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.Statement")),
new ExactStringMatcher("executeQuery"), new RegexStringMatcher("^.*\\)Ljava/sql/ResultSet;$"), false,
ObligationPolicyDatabaseActionType.ADD, ObligationPolicyDatabaseEntryType.STRONG, resultSet));
// Add close method entries for database obligation types
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.Connection")),
new ExactStringMatcher("close"), new ExactStringMatcher("()V"), false, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, connection));
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.Statement")),
new ExactStringMatcher("close"), new ExactStringMatcher("()V"), false, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, statement, resultSet));
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.sql.ResultSet")),
new ExactStringMatcher("close"), new ExactStringMatcher("()V"), false, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, resultSet));
}
/**
* General method for adding entries for File
* InputStream/OutputStream/Reader/Writer classes.
*/
private void addFileStreamEntries(String kind) {
Obligation obligation = database.getFactory().addObligation("java.io." + kind);
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.io.File" + kind)),
new ExactStringMatcher("<init>"), new RegexStringMatcher(".*"), false, ObligationPolicyDatabaseActionType.ADD,
ObligationPolicyDatabaseEntryType.STRONG, obligation));
database.addEntry(new MatchMethodEntry(new SubtypeTypeMatcher(BCELUtil.getObjectTypeInstance("java.io." + kind)),
new ExactStringMatcher("close"), new ExactStringMatcher("()V"), false, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, obligation));
}
/**
* Add an appropriate policy database entry for parameters marked with the
* WillClose annotation.
*
* @param xmethod
* a method
* @param obligation
* the Obligation deleted by the method
* @param entryType
* type of entry (STRONG or WEAK)
*/
private void addParameterDeletesObligationDatabaseEntry(XMethod xmethod, Obligation obligation,
ObligationPolicyDatabaseEntryType entryType) {
// Add a policy database entry noting that this method
// will delete one instance of the obligation type.
ObligationPolicyDatabaseEntry entry = new MatchMethodEntry(xmethod, ObligationPolicyDatabaseActionType.DEL, entryType,
obligation);
database.addEntry(entry);
if (DEBUG_ANNOTATIONS) {
System.out.println("Added entry: " + entry);
}
}
/**
* Handle a method with a WillCloseWhenClosed parameter annotation.
*/
private void handleWillCloseWhenClosed(XMethod xmethod, Obligation deletedObligation) {
if (deletedObligation == null) {
if (DEBUG_ANNOTATIONS) {
System.out.println("Method " + xmethod.toString() + " is marked @WillCloseWhenClosed, "
+ "but its parameter is not an obligation");
}
return;
}
// See what type of obligation is being created.
Obligation createdObligation = null;
if (xmethod.getName().equals("<init>")) {
// Constructor - obligation type is the type of object being created
// (or some supertype)
createdObligation = database.getFactory().getObligationByType(xmethod.getClassDescriptor());
} else {
// Factory method - obligation type is the return type
Type returnType = Type.getReturnType(xmethod.getSignature());
if (returnType instanceof ObjectType) {
try {
createdObligation = database.getFactory().getObligationByType((ObjectType) returnType);
} catch (ClassNotFoundException e) {
reporter.reportMissingClass(e);
return;
}
}
}
if (createdObligation == null) {
if (DEBUG_ANNOTATIONS) {
System.out.println("Method " + xmethod.toString() + " is marked @WillCloseWhenClosed, "
+ "but its return type is not an obligation");
}
return;
}
// Add database entries:
// - parameter obligation is deleted
// - return value obligation is added
database.addEntry(new MatchMethodEntry(xmethod, ObligationPolicyDatabaseActionType.DEL,
ObligationPolicyDatabaseEntryType.STRONG, deletedObligation));
database.addEntry(new MatchMethodEntry(xmethod, ObligationPolicyDatabaseActionType.ADD,
ObligationPolicyDatabaseEntryType.STRONG, createdObligation));
}
private void scanForResourceTypes() {
Subtypes2 subtypes2 = Global.getAnalysisCache().getDatabase(Subtypes2.class);
Collection<XClass> knownClasses = subtypes2.getXClassCollection();
for (XClass xclass : knownClasses) {
// Is this class a resource type?
if (xclass.getAnnotation(cleanupObligation) != null) {
// Add it as an obligation type
database.getFactory().addObligation(xclass.getClassDescriptor().toDottedClassName());
}
}
if (DEBUG_ANNOTATIONS) {
System.out.println("After scanning for resource types:");
for (Iterator<Obligation> i = database.getFactory().obligationIterator(); i.hasNext();) {
Obligation obligation = i.next();
System.out.println(" " + obligation);
}
}
}
}