KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > detect > FindOpenStream


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003,2004 University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.detect;
21
22
23 import edu.umd.cs.findbugs.*;
24 import edu.umd.cs.findbugs.ba.*;
25 import java.util.*;
26 import org.apache.bcel.Constants;
27 import org.apache.bcel.classfile.*;
28 import org.apache.bcel.generic.*;
29
30 /**
31  * A Detector to look for streams that are opened in a method,
32  * do not escape the method, and are not closed on all paths
33  * out of the method. Note that "stream" is a bit misleading,
34  * since we also use the detector to look for database resources
35  * that aren't closed.
36  *
37  * @author David Hovemeyer
38  */

39 public final class FindOpenStream extends ResourceTrackingDetector<Stream, StreamResourceTracker> implements StatelessDetector {
40     static final boolean DEBUG = SystemProperties.getBoolean("fos.debug");
41     static final boolean IGNORE_WRAPPED_UNINTERESTING_STREAMS = !SystemProperties.getBoolean("fos.allowWUS");
42
43     /* ----------------------------------------------------------------------
44      * Tracked resource types
45      * ---------------------------------------------------------------------- */

46
47     /**
48      * List of base classes of tracked resources.
49      */

50     static final ObjectType[] streamBaseList =
51             {ObjectTypeFactory.getInstance("java.io.InputStream"),
52              ObjectTypeFactory.getInstance("java.io.OutputStream"),
53              ObjectTypeFactory.getInstance("java.io.Reader"),
54              ObjectTypeFactory.getInstance("java.io.Writer"),
55              ObjectTypeFactory.getInstance("java.sql.Connection"),
56              ObjectTypeFactory.getInstance("java.sql.Statement"),
57              ObjectTypeFactory.getInstance("java.sql.ResultSet")};
58
59     /**
60      * StreamFactory objects used to detect resources
61      * created within analyzed methods.
62      */

63     static final StreamFactory[] streamFactoryList;
64
65     static {
66         ArrayList<StreamFactory> streamFactoryCollection = new ArrayList<StreamFactory>();
67
68         // Examine InputStreams, OutputStreams, Readers, and Writers,
69
// ignoring byte array, object stream, char array, and String variants.
70
streamFactoryCollection.add(new IOStreamFactory("java.io.InputStream",
71                 new String JavaDoc[]{"java.io.ByteArrayInputStream", "java.io.StringBufferInputStream", "java.io.PipedInputStream"
72                 ,"java.io.ObjectInputStream"
73                 },
74                 "OS_OPEN_STREAM"));
75         streamFactoryCollection.add(new IOStreamFactory("java.io.OutputStream",
76                 new String JavaDoc[]{"java.io.ByteArrayOutputStream", "java.io.PipedOutputStream"
77                  , "java.io.ObjectOutputStream"
78                 },
79                 "OS_OPEN_STREAM"));
80         streamFactoryCollection.add(new IOStreamFactory("java.io.Reader",
81                 new String JavaDoc[]{"java.io.StringReader", "java.io.CharArrayReader", "java.io.PipedReader"},
82                 "OS_OPEN_STREAM"));
83         streamFactoryCollection.add(new IOStreamFactory("java.io.Writer",
84                 new String JavaDoc[]{"java.io.StringWriter", "java.io.CharArrayWriter", "java.io.PipedWriter"},
85                 "OS_OPEN_STREAM"));
86
87         // Ignore socket input and output streams
88
streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.net.Socket",
89                 "getInputStream", "()Ljava/io/InputStream;"));
90         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.net.Socket",
91                 "getOutputStream", "()Ljava/io/OutputStream;"));
92
93         // Ignore System.{in,out,err}
94
streamFactoryCollection.add(new StaticFieldLoadStreamFactory("java.io.InputStream",
95                 "java.lang.System", "in", "Ljava/io/InputStream;"));
96         streamFactoryCollection.add(new StaticFieldLoadStreamFactory("java.io.OutputStream",
97                 "java.lang.System", "out", "Ljava/io/PrintStream;"));
98         streamFactoryCollection.add(new StaticFieldLoadStreamFactory("java.io.OutputStream",
99                 "java.lang.System", "err", "Ljava/io/PrintStream;"));
100
101         // Ignore input streams loaded from instance fields
102
streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.InputStream"));
103         streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.Reader"));
104
105         // Ignore output streams loaded from instance fields.
106
// FIXME: what we really should do here is ignore the stream
107
// loaded from the field, but report any streams that wrap
108
// it. This is an important and useful distinction that the
109
// detector currently doesn't handle. Should be fairly
110
// easy to add.
111
streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.OutputStream"));
112         streamFactoryCollection.add(new InstanceFieldLoadStreamFactory("java.io.Writer"));
113
114         // JDBC objects
115
streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
116                 "prepareStatement", "(Ljava/lang/String;)Ljava/sql/PreparedStatement;",
117                 "ODR_OPEN_DATABASE_RESOURCE"));
118         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
119                 "prepareStatement", "(Ljava/lang/String;I)Ljava/sql/PreparedStatement;",
120                 "ODR_OPEN_DATABASE_RESOURCE"));
121         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
122                 "prepareStatement", "(Ljava/lang/String;[I)Ljava/sql/PreparedStatement;",
123                 "ODR_OPEN_DATABASE_RESOURCE"));
124         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
125                 "prepareStatement", "(Ljava/lang/String;II)Ljava/sql/PreparedStatement;",
126                 "ODR_OPEN_DATABASE_RESOURCE"));
127         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
128                 "prepareStatement", "(Ljava/lang/String;III)Ljava/sql/PreparedStatement;",
129                 "ODR_OPEN_DATABASE_RESOURCE"));
130         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
131                 "prepareStatement", "(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;",
132                 "ODR_OPEN_DATABASE_RESOURCE"));
133
134         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
135                 "prepareCall", "(Ljava/lang/String;)Ljava/sql/CallableStatement;",
136                 "ODR_OPEN_DATABASE_RESOURCE"));
137         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
138                 "prepareCall", "(Ljava/lang/String;II)Ljava/sql/CallableStatement;",
139                 "ODR_OPEN_DATABASE_RESOURCE"));
140         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
141                 "prepareCall", "(Ljava/lang/String;III)Ljava/sql/CallableStatement;",
142                 "ODR_OPEN_DATABASE_RESOURCE"));
143
144         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.DriverManager",
145                 "getConnection", "(Ljava/lang/String;)Ljava/sql/Connection;",
146                 "ODR_OPEN_DATABASE_RESOURCE"));
147         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.DriverManager",
148                 "getConnection", "(Ljava/lang/String;Ljava/util/Properties;)Ljava/sql/Connection;",
149                 "ODR_OPEN_DATABASE_RESOURCE"));
150         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.DriverManager",
151                 "getConnection",
152                 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;",
153                 "ODR_OPEN_DATABASE_RESOURCE"));
154         streamFactoryCollection.add(new MethodReturnValueStreamFactory("javax.sql.DataSource",
155                 "getConnection",
156                 "()Ljava/sql/Connection;",
157                 "ODR_OPEN_DATABASE_RESOURCE"));
158         streamFactoryCollection.add(new MethodReturnValueStreamFactory("javax.sql.DataSource",
159                 "getConnection",
160                 "(Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;",
161                 "ODR_OPEN_DATABASE_RESOURCE"));
162
163         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
164                 "createStatement", "()Ljava/sql/Statement;",
165                 "ODR_OPEN_DATABASE_RESOURCE"));
166         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
167                 "createStatement", "(II)Ljava/sql/Statement;",
168                 "ODR_OPEN_DATABASE_RESOURCE"));
169         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
170                 "createStatement", "(III)Ljava/sql/Statement;",
171                 "ODR_OPEN_DATABASE_RESOURCE"));
172         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
173                 "createStatement", "(Ljava/lang/String;I)Ljava/sql/PreparedStatement;",
174                 "ODR_OPEN_DATABASE_RESOURCE"));
175         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
176                 "createStatement", "(Ljava/lang/String;II)Ljava/sql/PreparedStatement;",
177                 "ODR_OPEN_DATABASE_RESOURCE"));
178         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
179                 "createStatement", "(Ljava/lang/String;III)Ljava/sql/PreparedStatement;",
180                 "ODR_OPEN_DATABASE_RESOURCE"));
181         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
182                 "createStatement", "(Ljava/lang/String;[I)Ljava/sql/PreparedStatement;",
183                 "ODR_OPEN_DATABASE_RESOURCE"));
184         streamFactoryCollection.add(new MethodReturnValueStreamFactory("java.sql.Connection",
185                 "createStatement", "(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;",
186                 "ODR_OPEN_DATABASE_RESOURCE"));
187
188         streamFactoryList = streamFactoryCollection.toArray(new StreamFactory[streamFactoryCollection.size()]);
189     }
190
191     /* ----------------------------------------------------------------------
192      * Helper classes
193      * ---------------------------------------------------------------------- */

194
195     private static class PotentialOpenStream {
196         public final String JavaDoc bugType;
197         public final int priority;
198         public final Stream stream;
199
200         public PotentialOpenStream(String JavaDoc bugType, int priority, Stream stream) {
201             this.bugType = bugType;
202             this.priority = priority;
203             this.stream = stream;
204         }
205     }
206
207     /* ----------------------------------------------------------------------
208      * Fields
209      * ---------------------------------------------------------------------- */

210
211     private List<PotentialOpenStream> potentialOpenStreamList;
212
213     /* ----------------------------------------------------------------------
214      * Implementation
215      * ---------------------------------------------------------------------- */

216
217     public FindOpenStream(BugReporter bugReporter) {
218         super(bugReporter);
219         this.potentialOpenStreamList = new LinkedList<PotentialOpenStream>();
220     }
221     
222     @Override JavaDoc
223     public Object JavaDoc clone() {
224         try {
225             return super.clone();
226         } catch (CloneNotSupportedException JavaDoc e) {
227             throw new AssertionError JavaDoc(e);
228         }
229     }
230     
231     // List of words that must appear in names of classes which
232
// create possible resources to be tracked. If we don't see a
233
// class containing one of these words, then we don't run the
234
// detector on the class.
235
private static final String JavaDoc[] PRESCREEN_CLASS_LIST =
236         { "Stream", "Reader", "Writer", "DriverManager", "Connection" };
237
238     /* (non-Javadoc)
239      * @see edu.umd.cs.findbugs.Detector#visitClassContext(edu.umd.cs.findbugs.ba.ClassContext)
240      */

241     @Override JavaDoc
242          public void visitClassContext(ClassContext classContext) {
243         JavaClass jclass = classContext.getJavaClass();
244
245         // Check to see if the class references any other classes
246
// which could be resources we want to track.
247
// If we don't find any such classes, we skip analyzing
248
// the class. (Note: could do this by method.)
249
boolean sawResourceClass = false;
250         for (int i = 0; i < jclass.getConstantPool().getLength(); ++i) {
251             Constant constant = jclass.getConstantPool().getConstant(i);
252             
253             if (constant instanceof ConstantMethodref) {
254                 ConstantMethodref cmr = (ConstantMethodref) constant;
255                 
256                 int classIndex = cmr.getClassIndex();
257                 String JavaDoc className = jclass.getConstantPool().getConstantString(
258                         classIndex, Constants.CONSTANT_Class);
259                 
260                 if (DEBUG) System.out.println("FindOpenStream: saw class " + className);
261                 
262                 if (className != null) {
263                     for (String JavaDoc aPRESCREEN_CLASS_LIST : PRESCREEN_CLASS_LIST) {
264                         if (className.indexOf(aPRESCREEN_CLASS_LIST) >= 0) {
265                             sawResourceClass = true;
266                             break;
267                         }
268                     }
269                 }
270             }
271         }
272         
273         if (sawResourceClass) {
274             super.visitClassContext(classContext);
275         }
276     }
277
278     @Override JavaDoc
279          public boolean prescreen(ClassContext classContext, Method method) {
280         BitSet bytecodeSet = classContext.getBytecodeSet(method);
281         if (bytecodeSet == null) return false;
282         return bytecodeSet.get(Constants.NEW)
283                 || bytecodeSet.get(Constants.INVOKEINTERFACE)
284                 || bytecodeSet.get(Constants.INVOKESPECIAL)
285                 || bytecodeSet.get(Constants.INVOKESTATIC)
286                 || bytecodeSet.get(Constants.INVOKEVIRTUAL);
287     }
288
289     @Override JavaDoc
290          public StreamResourceTracker getResourceTracker(ClassContext classContext, Method method) {
291         return new StreamResourceTracker(streamFactoryList, bugReporter);
292     }
293
294     public static boolean isMainMethod(Method method) {
295         return method.isStatic()
296                 && method.getName().equals("main")
297                 && method.getSignature().equals("([Ljava/lang/String;)V");
298     }
299
300     @Override JavaDoc
301          public void analyzeMethod(ClassContext classContext, Method method,
302                               StreamResourceTracker resourceTracker,
303                               ResourceCollection<Stream> resourceCollection)
304             throws CFGBuilderException, DataflowAnalysisException {
305
306         if (isMainMethod(method)) return;
307
308         potentialOpenStreamList.clear();
309
310         JavaClass javaClass = classContext.getJavaClass();
311         MethodGen methodGen = classContext.getMethodGen(method);
312         if (methodGen == null) return;
313         CFG cfg = classContext.getCFG(method);
314
315         // Add Streams passed into the method as parameters.
316
// These are uninteresting, and should poison
317
// any streams which wrap them.
318
try {
319             Type[] parameterTypeList = Type.getArgumentTypes(methodGen.getSignature());
320             Location firstLocation = new Location(cfg.getEntry().getFirstInstruction(), cfg.getEntry());
321
322             int local = methodGen.isStatic() ? 0 : 1;
323
324             for (Type type : parameterTypeList) {
325                 if (type instanceof ObjectType) {
326                     ObjectType objectType = (ObjectType) type;
327                     for (ObjectType streamBase : streamBaseList) {
328                         if (Hierarchy.isSubtype(objectType, streamBase)) {
329                             // OK, found a parameter that is a resource.
330
// Create a Stream object to represent it.
331
// The Stream will be uninteresting, so it will
332
// inhibit reporting for any stream that wraps it.
333

334                             Stream paramStream =
335                                     new Stream(firstLocation, objectType.getClassName(), streamBase.getClassName());
336                             paramStream.setIsOpenOnCreation(true);
337                             paramStream.setOpenLocation(firstLocation);
338                             paramStream.setInstanceParam(local);
339                             resourceCollection.addPreexistingResource(paramStream);
340
341                             break;
342                         }
343                     }
344                 }
345
346                 switch (type.getType()) {
347                 case Constants.T_LONG:
348                 case Constants.T_DOUBLE:
349                     local += 2;
350                     break;
351                 default:
352                     local += 1;
353                     break;
354                 }
355             }
356         } catch (ClassNotFoundException JavaDoc e) {
357             bugReporter.reportMissingClass(e);
358         }
359
360         // Set precomputed map of Locations to Stream creation points.
361
// That way, the StreamResourceTracker won't have to
362
// repeatedly try to figure out where Streams are created.
363
resourceTracker.setResourceCollection(resourceCollection);
364
365         super.analyzeMethod(classContext, method, resourceTracker, resourceCollection);
366
367         // Compute streams that escape into other streams:
368
// this takes wrapper streams into account.
369
// This will also compute equivalence classes of streams,
370
// so that if one stream in a class is closed,
371
// they are all considered closed.
372
// (FIXME: this is too simplistic, especially if buffering
373
// is involved. Sometime we should really think harder
374
// about how this should work.)
375
resourceTracker.markTransitiveUninterestingStreamEscapes();
376
377         // For each stream closed on all paths, mark its equivalence
378
// class as being closed.
379
for (Iterator<Stream> i = resourceCollection.resourceIterator(); i.hasNext();) {
380             Stream stream = i.next();
381             StreamEquivalenceClass equivalenceClass = resourceTracker.getStreamEquivalenceClass(stream);
382             if (stream.isClosed())
383                 equivalenceClass.setClosed();
384         }
385
386         // Iterate through potential open streams, reporting warnings
387
// for the "interesting" streams that haven't been closed
388
// (and aren't in an equivalence class with another stream
389
// that was closed).
390
for (PotentialOpenStream pos : potentialOpenStreamList) {
391             Stream stream = pos.stream;
392             if (stream.isClosed())
393                 // Stream was in an equivalence class with another
394
// stream that was properly closed.
395
continue;
396
397             if (stream.isUninteresting())
398                 continue;
399
400             Location openLocation = stream.getOpenLocation();
401             if (openLocation == null)
402                 continue;
403
404             if (IGNORE_WRAPPED_UNINTERESTING_STREAMS
405                     && resourceTracker.isUninterestingStreamEscape(stream))
406                 continue;
407
408             String JavaDoc sourceFile = javaClass.getSourceFileName();
409             bugReporter.reportBug(new BugInstance(this, pos.bugType, pos.priority)
410                     .addClassAndMethod(methodGen, sourceFile)
411                     .addSourceLine(classContext, methodGen, sourceFile, stream.getLocation().getHandle()));
412         }
413     }
414
415     @Override JavaDoc
416          public void inspectResult(ClassContext classContext, MethodGen methodGen, CFG cfg,
417                               Dataflow<ResourceValueFrame, ResourceValueAnalysis<Stream>> dataflow, Stream stream) {
418         
419         ResourceValueFrame exitFrame = dataflow.getResultFact(cfg.getExit());
420
421         int exitStatus = exitFrame.getStatus();
422         if (exitStatus == ResourceValueFrame.OPEN
423                 || exitStatus == ResourceValueFrame.OPEN_ON_EXCEPTION_PATH) {
424
425             // FIXME: Stream object should be queried for the
426
// priority.
427

428             String JavaDoc bugType = stream.getBugType();
429             int priority = NORMAL_PRIORITY;
430             if (exitStatus == ResourceValueFrame.OPEN_ON_EXCEPTION_PATH) {
431                 bugType += "_EXCEPTION_PATH";
432                 priority = LOW_PRIORITY;
433             }
434
435             potentialOpenStreamList.add(new PotentialOpenStream(bugType, priority, stream));
436         } else if (exitStatus == ResourceValueFrame.CLOSED) {
437             // Remember that this stream was closed on all paths.
438
// Later, we will mark all of the streams in its equivalence class
439
// as having been closed.
440
stream.setClosed();
441         }
442     }
443
444     public static void main(String JavaDoc[] argv) throws Exception JavaDoc {
445         if (argv.length != 3) {
446             System.err.println("Usage: " + FindOpenStream.class.getName() +
447                     " <class file> <method name> <bytecode offset>");
448             System.exit(1);
449         }
450
451         String JavaDoc classFile = argv[0];
452         String JavaDoc methodName = argv[1];
453         int offset = Integer.parseInt(argv[2]);
454
455         ResourceValueAnalysisTestDriver<Stream, StreamResourceTracker> driver =
456                 new ResourceValueAnalysisTestDriver<Stream, StreamResourceTracker>() {
457                     @Override JavaDoc
458                                  public StreamResourceTracker createResourceTracker(ClassContext classContext, Method method) {
459                         return new StreamResourceTracker(streamFactoryList, classContext.getLookupFailureCallback());
460                     }
461                 };
462
463         driver.execute(classFile, methodName, offset);
464     }
465
466 }
467
468 // vim:ts=3
469
Popular Tags