Sunday, August 10, 2008

Watch your return values

Preface - This post was going to be a one-sentence comment to a post by Jeremy Zawodny, but then I remembered my professor, and it went from there.

I had a professor for two semesters that required that all our assignments were written in C. This was his assignment submission policy:
1) Run lint on all your code.
2) All code must have zero warnings from lint, with no exceptions.
3) Any use of strcpy resulted in a zero for the assignment.

#3 was my first lesson about buffer overflows. (We all used strncpy.)

One of the lint warnings pertained to unused return values. For instance, the method signature for printf is
int printf(char *format,...)
How often is printf's returned value given attention? It represents the number of characters written to the stream, or a negative number on failure.

Our linter complained about unused return values for typical uses of printf like:
printf("--done.");
We were required to either accept and process the return value, or explicitly disregard it:
(void) printf("--done.");
This was one of my earliest impressions of studying defensive programming.

In Java, the method java.io.InputStream.skip has a contract that requires you to pay attention to the return value, and the reason may surprise you:
public long skip(long) throws IOException
The skip method may, for a variety of reasons, end up skipping over some smaller number of bytes, possibly 0. This may result from any of a number of conditions; reaching end of file before n bytes have been skipped is only one possibility. The actual number of bytes skipped is returned. If n is negative, no bytes are skipped.
So, you supply a distance to skip, and you likely expect the same value back, at least, most of the time. What amazes me is that the documentation even addresses the common expectation: sure EOF is one way skip(n) != n, and then says, twice, that there may be a "variety of reasons" and a "number of conditions." The author is trying to make up for a troublesome API with special javadoc.

Forget that oftentimes there's no documentation and hence, no contract, you still can't expect that documenting unexpected behavior is going to result in proper use of the API.

Present day, Java has Findbugs, The Lint Of Java (zero results, you saw it here first!) Java's classfile format and FindBugs' semantic analysis makes it much more powerful than lint, it can identify this issue with java.io.InputStream.skip(). From the description as Findbugs reports it:
This method ignores the return value of java.io.InputStream.skip() which can skip multiple bytes. If the return value is not checked, the caller will not be able to correctly handle the case where fewer bytes were skipped than the caller requested. This is a particularly insidious kind of bug, because in many programs, skips from input streams usually do skip the full amount of data requested, causing the program to fail only sporadically. With Buffered streams, however, skip() will only skip data in the buffer, and will routinely fail to skip the requested number of bytes.
That's great, but there's a problem, and that comes with expanding the API. If I build my own API with its own nuances, someone needs to write a FindBugs detector. (Hint: be careful writing a clever API!)

It seems draconian to enforce a policy of "always address return values" with Java outside academia since there's no easy way to mimic the explicit cast to void. Otherwise you wind up with unused local variables, which becomes yet another code smell.

In the end all tools like FindBugs and lint only augment the human analysis that accompanies development.

I hope students today are being told that they cannot turn in any Java assignments without running FindBugs.

5 comments:

Ben Darnell said...

GCC supports a special tag "__attribute__((warn_unused_result))" that can be placed after a function definition to cause the compiler (not just lint) to emit a warning if the return value is ignored. Java/Findbugs could easily do the same thing with a special annotation for cases where you must write an API with similar subtleties (although personally, I think most cases where this comes up in C would be better served by an exception in Java)

konberg said...

Ben, that sounds like an excellent feature in GCC. I just let my mind wander about the possibilities. How much do people use it?

Exceptions in lieu of return values is certainly useful, but I don't think it applies in this particular case (not that you said it did.)

I also like the idea of a special annotation; I'd go from like to love if Findbugs was packaged along with Java, so such annotations could be applied to the VM.

At the same time, it can't be difficult to write a general purpose Findbugs detector for ignoring return values, and merely duplicating it, stamping your own class and method as needed.

Anonymous said...

Robert and Ben, I also wish Java or FindBug had such an annotation for expressing that a return value must not be ignored.

I want to give another example: In his books and interviews, J. Bloch makes an excellent point on using immutable objects. When calling a side-effect-free method of an immutable object, ignoring the return value is almost always a programming error. We know this from BigInteger and other immutable value types, but implementing immutable objects where programmers grep up with mutable interfaces is especially dangerous:

ImmutableStack stack = ...
...
stack.pop()

The user might expect that pop() modifies the current stack, while in fact the reduced stack is returned by pop().

konberg said...

Oliver. Good point. Not unlike novice attempts at calling String.concat.

Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!