/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003,2004 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.util.ArrayList; import java.util.BitSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.apache.bcel.Constants; import org.apache.bcel.classfile.Constant; import org.apache.bcel.classfile.ConstantInterfaceMethodref; import org.apache.bcel.classfile.ConstantMethodref; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.ResourceCollection; import edu.umd.cs.findbugs.ResourceTrackingDetector; import edu.umd.cs.findbugs.SourceLineAnnotation; import edu.umd.cs.findbugs.StatelessDetector; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.TypeAnnotation; import edu.umd.cs.findbugs.ba.CFG; import edu.umd.cs.findbugs.ba.CFGBuilderException; import edu.umd.cs.findbugs.ba.ClassContext; import edu.umd.cs.findbugs.ba.Dataflow; import edu.umd.cs.findbugs.ba.DataflowAnalysisException; import edu.umd.cs.findbugs.ba.Hierarchy; import edu.umd.cs.findbugs.ba.Location; import edu.umd.cs.findbugs.ba.ObjectTypeFactory; import edu.umd.cs.findbugs.ba.ResourceValueAnalysis; //import edu.umd.cs.findbugs.ba.ResourceValueAnalysisTestDriver; import edu.umd.cs.findbugs.ba.ResourceValueFrame; /** * A Detector to look for streams that are opened in a method, * do not escape the method, and are not closed on all paths * out of the method. Note that "stream" is a bit misleading, * since we also use the detector to look for database resources * that aren't closed. * * @author David Hovemeyer */ public final class FindOpenStream extends ResourceTrackingDetector<Stream, StreamResourceTracker> implements StatelessDetector { static final boolean DEBUG = SystemProperties.getBoolean("fos.debug"); static final boolean IGNORE_WRAPPED_UNINTERESTING_STREAMS = !SystemProperties.getBoolean("fos.allowWUS"); /* ---------------------------------------------------------------------- * Tracked resource types * ---------------------------------------------------------------------- */ /** * List of base classes of tracked resources. */ static final ObjectType[] streamBaseList = {ObjectTypeFactory.getInstance("java.io.InputStream"), ObjectTypeFactory.getInstance("java.io.OutputStream"), ObjectTypeFactory.getInstance("java.io.Reader"), ObjectTypeFactory.getInstance("java.io.Writer"), ObjectTypeFactory.getInstance("java.sql.Connection"), ObjectTypeFactory.getInstance("java.sql.PreparedStatement"), ObjectTypeFactory.getInstance("java.sql.Statement"), ObjectTypeFactory.getInstance("java.sql.ResultSet")}; /** * StreamFactory objects used to detect resources * created within analyzed methods. */ static final StreamFactory[] streamFactoryList; static { ArrayList<StreamFactory> streamFactoryCollection = new ArrayList<StreamFactory>(); // Examine InputStreams, OutputStreams, Readers, and Writers, // ignoring byte array, object stream, char array, and String variants. streamFactoryCollection.add(new IOStreamFactory("java.io.InputStream", new String[]{"java.io.ByteArrayInputStream", "java.io.StringBufferInputStream", "java.io.PipedInputStream" ,"java.io.ObjectInputStream" }, "OS_OPEN_STREAM")); streamFactoryCollection.add(new IOStreamFactory("java.io.OutputStream", new String[]{"java.io.ByteArrayOutputStream", "java.io.PipedOutputStream" , "java.io.ObjectOutputStream" }, "OS_OPEN_STREAM")); streamFactoryCollection.add(new IOStreamFactory("java.io.Reader", new String[]{"java.io.StringReader", "java.io.CharArrayReader", "java.io.PipedReader"}, "OS_OPEN_STREAM")); streamFactoryCollection.add(new IOStreamFactory("java.io.Writer", new String[]{"java.io.StringWriter", "java.io.CharArrayWriter", "java.io.PipedWriter"}, "OS_OPEN_STREAM")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.lang.Class", "getResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;", "OS_OPEN_STREAM")); // Ignore socket input and output streams streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.net.Socket", "getInputStream", "()Ljava/io/InputStream;")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.net.Socket", "getOutputStream", "()Ljava/io/OutputStream;")); // Ignore System.{in,out,err} streamFactoryCollection.add(new StaticFieldLoadStreamFactory("java.io.InputStream", "java.lang.System", "in", "Ljava/io/InputStream;")); streamFactoryCollection.add(new StaticFieldLoadStreamFactory("java.io.OutputStream", "java.lang.System", "out", "Ljava/io/PrintStream;")); streamFactoryCollection.add(new StaticFieldLoadStreamFactory("java.io.OutputStream", "java.lang.System", "err", "Ljava/io/PrintStream;")); // Ignore input streams loaded from instance fields streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.InputStream")); streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.Reader")); // Ignore output streams loaded from instance fields. // FIXME: what we really should do here is ignore the stream // loaded from the field, but report any streams that wrap // it. This is an important and useful distinction that the // detector currently doesn't handle. Should be fairly // easy to add. streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.OutputStream")); streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.Writer")); // JDBC objects streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareStatement", "(Ljava/lang/String;)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareStatement", "(Ljava/lang/String;I)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareStatement", "(Ljava/lang/String;[I)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareStatement", "(Ljava/lang/String;II)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareStatement", "(Ljava/lang/String;III)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareStatement", "(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareCall", "(Ljava/lang/String;)Ljava/sql/CallableStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareCall", "(Ljava/lang/String;II)Ljava/sql/CallableStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "prepareCall", "(Ljava/lang/String;III)Ljava/sql/CallableStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.DriverManager", "getConnection", "(Ljava/lang/String;)Ljava/sql/Connection;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.DriverManager", "getConnection", "(Ljava/lang/String;Ljava/util/Properties;)Ljava/sql/Connection;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.DriverManager", "getConnection", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("javax.sql.DataSource", "getConnection", "()Ljava/sql/Connection;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("javax.sql.DataSource", "getConnection", "(Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "()Ljava/sql/Statement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(II)Ljava/sql/Statement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(III)Ljava/sql/Statement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(Ljava/lang/String;)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(Ljava/lang/String;I)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(Ljava/lang/String;II)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(Ljava/lang/String;III)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(Ljava/lang/String;[I)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection", "createStatement", "(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;", "ODR_OPEN_DATABASE_RESOURCE")); streamFactoryList = streamFactoryCollection.toArray(new StreamFactory[streamFactoryCollection.size()]); } /* ---------------------------------------------------------------------- * Helper classes * ---------------------------------------------------------------------- */ private static class PotentialOpenStream { public final String bugType; public final int priority; public final Stream stream; public PotentialOpenStream(String bugType, int priority, Stream stream) { this.bugType = bugType; this.priority = priority; this.stream = stream; } } /* ---------------------------------------------------------------------- * Fields * ---------------------------------------------------------------------- */ private List<PotentialOpenStream> potentialOpenStreamList; /* ---------------------------------------------------------------------- * Implementation * ---------------------------------------------------------------------- */ public FindOpenStream(BugReporter bugReporter) { super(bugReporter); this.potentialOpenStreamList = new LinkedList<PotentialOpenStream>(); } @Override public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } // List of words that must appear in names of classes which // create possible resources to be tracked. If we don't see a // class containing one of these words, then we don't run the // detector on the class. private static final String[] PRESCREEN_CLASS_LIST = { "Stream", "Reader", "Writer", "DriverManager", "Connection", "Statement" }; /* (non-Javadoc) * @see edu.umd.cs.findbugs.Detector#visitClassContext(edu.umd.cs.findbugs.ba.ClassContext) */ @Override public void visitClassContext(ClassContext classContext) { JavaClass jclass = classContext.getJavaClass(); // Check to see if the class references any other classes // which could be resources we want to track. // If we don't find any such classes, we skip analyzing // the class. (Note: could do this by method.) boolean sawResourceClass = false; for (int i = 0; i < jclass.getConstantPool().getLength(); ++i) { Constant constant = jclass.getConstantPool().getConstant(i); String className = null; if (constant instanceof ConstantMethodref) { ConstantMethodref cmr = (ConstantMethodref) constant; int classIndex = cmr.getClassIndex(); className = jclass.getConstantPool().getConstantString( classIndex, Constants.CONSTANT_Class); } else if (constant instanceof ConstantInterfaceMethodref) { ConstantInterfaceMethodref cmr = (ConstantInterfaceMethodref) constant; int classIndex = cmr.getClassIndex(); className = jclass.getConstantPool().getConstantString( classIndex, Constants.CONSTANT_Class); } if (className != null) { if (DEBUG) System.out.println("FindOpenStream: saw class " + className); for (String aPRESCREEN_CLASS_LIST : PRESCREEN_CLASS_LIST) { if (className.indexOf(aPRESCREEN_CLASS_LIST) >= 0) { sawResourceClass = true; break; } } } } if (sawResourceClass) { super.visitClassContext(classContext); } } @Override public boolean prescreen(ClassContext classContext, Method method, boolean mightClose) { BitSet bytecodeSet = classContext.getBytecodeSet(method); if (bytecodeSet == null) return false; return bytecodeSet.get(Constants.NEW) || bytecodeSet.get(Constants.INVOKEINTERFACE) || bytecodeSet.get(Constants.INVOKESPECIAL) || bytecodeSet.get(Constants.INVOKESTATIC) || bytecodeSet.get(Constants.INVOKEVIRTUAL); } @Override public StreamResourceTracker getResourceTracker(ClassContext classContext, Method method) { return new StreamResourceTracker(streamFactoryList, bugReporter); } public static boolean isMainMethod(Method method) { return method.isStatic() && method.getName().equals("main") && method.getSignature().equals("([Ljava/lang/String;)V"); } @Override public void analyzeMethod(ClassContext classContext, Method method, StreamResourceTracker resourceTracker, ResourceCollection<Stream> resourceCollection) throws CFGBuilderException, DataflowAnalysisException { potentialOpenStreamList.clear(); JavaClass javaClass = classContext.getJavaClass(); MethodGen methodGen = classContext.getMethodGen(method); if (methodGen == null) return; CFG cfg = classContext.getCFG(method); // Add Streams passed into the method as parameters. // These are uninteresting, and should poison // any streams which wrap them. try { Type[] parameterTypeList = Type.getArgumentTypes(methodGen.getSignature()); Location firstLocation = new Location(cfg.getEntry().getFirstInstruction(), cfg.getEntry()); int local = methodGen.isStatic() ? 0 : 1; for (Type type : parameterTypeList) { if (type instanceof ObjectType) { ObjectType objectType = (ObjectType) type; for (ObjectType streamBase : streamBaseList) { if (Hierarchy.isSubtype(objectType, streamBase)) { // OK, found a parameter that is a resource. // Create a Stream object to represent it. // The Stream will be uninteresting, so it will // inhibit reporting for any stream that wraps it. Stream paramStream = new Stream(firstLocation, objectType.getClassName(), streamBase.getClassName()); paramStream.setIsOpenOnCreation(true); paramStream.setOpenLocation(firstLocation); paramStream.setInstanceParam(local); resourceCollection.addPreexistingResource(paramStream); break; } } } switch (type.getType()) { case Constants.T_LONG: case Constants.T_DOUBLE: local += 2; break; default: local += 1; break; } } } catch (ClassNotFoundException e) { bugReporter.reportMissingClass(e); } // Set precomputed map of Locations to Stream creation points. // That way, the StreamResourceTracker won't have to // repeatedly try to figure out where Streams are created. resourceTracker.setResourceCollection(resourceCollection); super.analyzeMethod(classContext, method, resourceTracker, resourceCollection); // Compute streams that escape into other streams: // this takes wrapper streams into account. // This will also compute equivalence classes of streams, // so that if one stream in a class is closed, // they are all considered closed. // (FIXME: this is too simplistic, especially if buffering // is involved. Sometime we should really think harder // about how this should work.) resourceTracker.markTransitiveUninterestingStreamEscapes(); // For each stream closed on all paths, mark its equivalence // class as being closed. for (Iterator<Stream> i = resourceCollection.resourceIterator(); i.hasNext();) { Stream stream = i.next(); StreamEquivalenceClass equivalenceClass = resourceTracker.getStreamEquivalenceClass(stream); if (stream.isClosed()) equivalenceClass.setClosed(); } // Iterate through potential open streams, reporting warnings // for the "interesting" streams that haven't been closed // (and aren't in an equivalence class with another stream // that was closed). for (PotentialOpenStream pos : potentialOpenStreamList) { Stream stream = pos.stream; if (stream.isClosed()) // Stream was in an equivalence class with another // stream that was properly closed. continue; if (stream.isUninteresting()) continue; Location openLocation = stream.getOpenLocation(); if (openLocation == null) continue; if (IGNORE_WRAPPED_UNINTERESTING_STREAMS && resourceTracker.isUninterestingStreamEscape(stream)) continue; String sourceFile = javaClass.getSourceFileName(); String leakClass = stream.getStreamBase(); if (isMainMethod(method) && (leakClass.contains("InputStream") || leakClass.contains("Reader")) ) return; bugAccumulator.accumulateBug(new BugInstance(this, pos.bugType, pos.priority) .addClassAndMethod(methodGen, sourceFile) .addTypeOfNamedClass(leakClass).describe(TypeAnnotation.CLOSEIT_ROLE), SourceLineAnnotation.fromVisitedInstruction(classContext, methodGen, sourceFile, stream.getLocation().getHandle())); } } @Override public void inspectResult(ClassContext classContext, MethodGen methodGen, CFG cfg, Dataflow<ResourceValueFrame, ResourceValueAnalysis<Stream>> dataflow, Stream stream) { ResourceValueFrame exitFrame = dataflow.getResultFact(cfg.getExit()); int exitStatus = exitFrame.getStatus(); if (exitStatus == ResourceValueFrame.OPEN || exitStatus == ResourceValueFrame.OPEN_ON_EXCEPTION_PATH) { // FIXME: Stream object should be queried for the // priority. String bugType = stream.getBugType(); int priority = NORMAL_PRIORITY; if (exitStatus == ResourceValueFrame.OPEN_ON_EXCEPTION_PATH) { bugType += "_EXCEPTION_PATH"; priority = LOW_PRIORITY; } potentialOpenStreamList.add(new PotentialOpenStream(bugType, priority, stream)); } else if (exitStatus == ResourceValueFrame.CLOSED) { // Remember that this stream was closed on all paths. // Later, we will mark all of the streams in its equivalence class // as having been closed. stream.setClosed(); } } // public static void main(String[] argv) throws Exception { // if (argv.length != 3) { // System.err.println("Usage: " + FindOpenStream.class.getName() + // " <class file> <method name> <bytecode offset>"); // System.exit(1); // } // // String classFile = argv[0]; // String methodName = argv[1]; // int offset = Integer.parseInt(argv[2]); // // ResourceValueAnalysisTestDriver<Stream, StreamResourceTracker> driver = // new ResourceValueAnalysisTestDriver<Stream, StreamResourceTracker>() { // @Override // public StreamResourceTracker createResourceTracker(ClassContext classContext, Method method) { // return new StreamResourceTracker(streamFactoryList, classContext.getLookupFailureCallback()); // } // }; // // driver.execute(classFile, methodName, offset); // } } // vim:ts=3