9.4 KiB
Infer reports resource leaks in C, Objective-C and Java. In general, resources are entities such as files, sockets, connections, etc, that need to be closed after being used.
Resource leak in C
This is an example of a resource leak in C code:
-(void) resource_leak_bug {
FILE *fp;
fp=fopen("c:\\test.txt", "r"); // file opened and not closed.
}
Resource leak in Java
For the remaining of this section, we will consider examples of resource leaks in Java code.
TIP: A common source of bugs is exceptions skipping past close() statements. That is the first thing to look for if INFER reports a potential resource leak.
Basics and Standard Idiom
Some objects in Java, the resources, are supposed to be closed when you stop using them, and failure to close is a resource leak. Resources include input streams, output streams, readers, writers, sockets, http connections, cursors, and json parsers.
The standard idiom is
// Standard idiom
Allocate resource
try {
do some stuff
} finally {
close resource
}
or more for example,
// Standard Idiom
public static void foo () throws IOException{
FileOutputStream fos = new FileOutputStream(new File("whatever.txt"));
try {
fos.write(7);
} finally {
fos.close();
}
}
and you should use the standard idiom for the most part, when you don't want to return the resource to the surrounding context.
Sometimes people just leave out close(), and that is a bug, but more typically exceptional paths are the root of the problem, as in
// leak because of exception
public static void foo () throws IOException {
FileOutputStream fos = new FileOutputStream(new File("whatever.txt"));
fos.write(7); //DOH! What if exception?
fos.close();
}
where an exception in fos.write will cause execution to skip past the close() statement.
Multiple Resources Bugs
We can deal with multiple resources correctly and simply just by nesting the standard idiom.
// Two Resources nested
public static void foo() throws IOException {
FileInputStream fis = new FileInputStream(new File("whatever.txt"));
try {
FileOutputStream fos = new FileOutputStream(new File("everwhat.txt"));
try {
fos.write(fis.read());
} finally {
fos.close();
}
} finally {
fis.close();
}
}
Bugs often occur when using multiple resources in other ways because of exceptions in close() methods. For example,
// Classic Two Resources Bug
public static void foo() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(new File("whatever.txt"));
fos = new FileOutputStream(new File("everwhat.txt"));
fos.write(fis.read());
} finally {
if (fis!=null) fis.close();
if (fos!=null) fos.close();
}
}
Here, if there is an exception in the call to fis.close() execution will skip past fos.close(); a leak.
Another way, besides the standard idiom, to deal with this problem is to swallow exceptions.
// Two Resources Fix 1
public static void foo() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(new File("whatever.txt"));
fos = new FileOutputStream(new File("everwhat.txt"));
fos.write(fis.read());
} finally {
try {
if (fis!=null) fis.close();
} catch (Exception e) {}; // Exception swallowing
if (fos!=null) fos.close();
}
}
You can also swallow the exception on the output stream. Some people prefer not to swallow output stream exceptions, and also flush before closing. http://code.google.com/p/guava-libraries/issues/detail?id=1118
Notice that the nested standard idiom does not need the checks for null, which are in there in this case to protect against the case when one of the allocations throws an exception, in which case one would get a NullPointerException.
Nested_Allocations
When a resource allocation is included as an argument to a constructor, if the constructor fails it can leave an an unreachable resource that no one can close.
For example gzipOutputStream = new GZIPOutputStream(new FileOutputStream(out)); is bad in case the outer constructor, GZIPOutputStream, throws an exception. In that case, no one will have a hold of the FileOutputStream and so no one will be able to close it.
In such a case you need to move the allocation the FileOutputStream out of the nested position and name it, so you are able to close if anything goes wrong during execution of the GZIPOutputStream constructor.
Here are resources that can throw exceptions i their constructor(s).
- ObjectInputStream , ObjectOutputStream, PipedInputStream, PipedOutputStream, PipedReader, PipedWriter, JarInputStream, JarOutputStream, GZIPInputStream, GZIPOutputStream , ZipFile all throw IOException
- PrintStream throws UnsupportedEncodingException
The constructors for FileInputStream, FileOutputStream and RandomAccessFile throw FileNotFoundException, but these cases are not problematic in the sense that their arguments are not resources and so they do not cause the nested resource leak.
Allocation of JSonParser and Cursor resources
Some resources are created inside libraries instead of by "new".
Cursor is an interface, the actual resources are something like SQLiteCursor. So, every time you call a function that returns a Cursor object, there is an allocation.
For instance, in the functions from SQLiteDatabase query(…) and rawQuery(…) allocate a cursor resource. For SQLiteQueryBuilder, ContentProviderClient, ContentResolver. MediaStore and DownloadManager it is only query(…) Cursor objects cursor created by these functions need to be closed (i.e., cursor.close()).
Similarly, JsonParser is an abstract class, and create a resource in functions from the class JsonFactory createParser(byte[] data) createParser(byte[] data, int offset, int len) createParser(String content) createParser(URL url) createParser(File f) JsonParser objects js created by these functions need to be closed (jp.close()). On the other hand . JasonParsers gotten from createParser(InputStream in) and createParser(Reader r) give you JsonParsers that don’t need to be closed. This is because they receive the resource from somewhere that will maintain the responsibility to close it.
Escaping resources and exceptions
Sometimes you want to return a resource to the outside, in which case you should not close it, but you still need to be careful of exceptions in case control skips past the return leaving no one to close. Here is a simple example of a positive use of escaping resources.
// An escaping resource, shouldn't close
public BugReportAttachment createAttachment(File reportDirectory, String fileName)
throws FileNotFoundException {
File file = new File(reportDirectory, fileName);
OutputStream stream = new FileOutputStream(file);
return new BugReportAttachment(Uri.fromFile(file), stream);
}
In this case it is intended that an object that wraps stream
is passed to the
caller of createAttachment
. You should certainly not close stream here,
because it is being passed to the outside.
But for escaping resources like this you still need to be careful of exceptions. For example, in
// An escaping resource, and a leak
public BugReportAttachment createAttachment(File reportDirectory, String fileName)
throws FileNotFoundException {
File file = new File(reportDirectory, fileName);
OutputStream stream = new FileOutputStream(file);
stream.write(7);
return new BugReportAttachment(Uri.fromFile(file), stream);
}
if stream.write(7) throws an exception, then no one will have a hold of stream, and no one will be able to close it; a leak.
Java 7's try-with-resources
(For use with Java 7 only)
Clearly, accounting for the ramifications of all the exceptional cases is complicated, and there is a better way in Java 7.
// Two Resources Fix 2; via try-with-resources
public static void foo() throws IOException {
try (
FileInputStream fis = new FileInputStream(new File("whatever.txt"));
FileOutputStream fos = new FileOutputStream(new File("everwhat.txt"))
) {
fos.write(fis.read());
}
}
All the complicated exceptional cases above are (apparently) covered by this construct, and the result is much simpler.
So, if you are trying to fix a potential leak in code with multiples resources you can go ahead and try to understand whether the potential leak is real. Or, if the code is complex and it is hard to figure out, it would be perfectly legitimate to simply convert the code over to try-with-resources if you have access to Java 7, so as to save yourself some brain-cycles. You will also end up with cleaner code.
If try-with-resources is so great you should always use it. But you shouldn't… Try-with-resources gives resources static scoping, and works via a stack discipline. Sometimes, you want a resource to persist beyond scope, as in the escaping example above. In an escaping example maybe you could refactor lots of code so that try-with-resources applies, and maybe you cannot in a sensible way. This just illustrates that, though you might hear people say that try-with-resources "solves" the resource problem, it does not. It is very useful, but you cannot use it blindly when you see a resource-allocation site.