General musings on programming languages, and Java.

Thursday, May 17, 2007

Using just the JDK and 55 lines, I can find empty 'then' statements in Java code.

A few hours ago I noticed some comments on Joe Darcy's blog post about annotation processors.

I originally lost interest in annotations very quickly when I realised that annotation processors didn't have access to the full AST. I fiddled around with ANTLR a little, with CheckStyle, PMD and Findbugs' APIs to see what I could do. Not a lot, as it turned out. ANTLR looked promising, but as I recall, the language file for Java 5 was a bit flaky at the time. The others didn't provide me with complete types, so I couldn't detect things like code that tried to find a StringBuffer in a List of Strings. I gave up for a while, went off and learned to be productive in Haskell, and Lisp, and stuck my head into closures, which I'm very optimistic about.

I followed some links (about 6 deep) from Joe's blog, and came across the compiler API and compiler tree APIs. I have used it before, when I got some novice students to write their own compiling Java editor (that was fun), but I hadn't realised that you could get access to the actual compiler types - the standard API is rather thin. All you need to do is cast the task returned by the Java compiler, into a com.sun.source.util.JavacTask, and you're away.

I'm already mildly familiar with the compiler, having fiddled with its source a month or two back. I was trying to add a language feature that Stephen Colebourne, Stefan Schulz and I had been discussing, but we dropped the idea before I finished (well, before I really got started). Today I decided to attack a problem that used to catch me out when reading novices' code - an if statement with a semi-colon on the end, which just means 'if this, do nothing' and it executes the next statement regardless of the condition.

Javac uses the visitor pattern incredibly heavily, to a scale I've never seen anywhere else. I like it, but it caught me out temporarily. I don't really write subclasses anymore, I always use an alternative where practical. So I subclassed TreeScanner, which has nice 'return null;' implementations of every method, just ready for me to override. Which I didn't. Though I thought I was. The @Override annotation saved me when I realised..

Because I have my IDE set to Java version 5, and I wanted to run this code against version 6, I was just editing in vim and running from bash in Cygwin. No warnings about unused methods there then..

The short-term solution is to make sure I always remember @Override. The long-term, unlikely solution, is to persuade the javac project lead, sometime after Java 7, to accept my patch that makes these visitors more 'functional', so that instead of overriding, you give them a closure to invoke. The other advantage there is that you would be able to provide real booleans, rather than Booleans, etc.

There was only one more gotcha. Not only do I not subclass, I also don't use null very often, so I'd forgotten how to deal with boolean operators and null (I had to use Boolean, not boolean, thanks to generics). See if you can reduce my bestOf method into a chain of ternary operator calls. I couldn't do that without getting a NPE. I might investigate later.

There were no more gotchas. It was actually really easy, 30 minutes end to end. I included an 'empty' if statement in the source itself for testing. I think my next task should be to find places where @Override should be..

After all that fanfare, the code wouldn't fit in my blog's layout particularly well, so I'm afraid you'll have to click here to see it.


Stefan Schulz said...

It's interesting, how easy it is to use the Java Compiler API. I tried to figure out the javac itself lately, but it is quite complex (or I did not take enough of time).
The eclipse compiler may be of interest to you, as it supports quite a number of additional warning and error handling. For example, raising a warning on empty statements, which includes empty thens.
Regarding your bestof ... well, you can use Boolean's equals:
return Boolean.TRUE.equals(one) || Boolean.TRUE.equals(two);
Autoboxing is evil ;)

Ricky Clarkson said...

I'm interested in javac because I can use it to implement more interesting warnings. This first one was just a proof of concept. Finding empty statements in general takes less code than what I showed here.

I've generally found everything about Eclipse to be awkward to get into, but I haven't actually tried with the compiler.

Your solution to bestOf is probably better than mine, but doesn't actually have the same semantics. Mine makes reduce(null,null) give null, yours gives false.

Stefan Schulz said...

Well, I did not check the internals of eclipse's javac, but obviously they added quite a bunch of additional and switchable checks. If added to javac, switches should be added for checks, too, I think.

Regarding bestOf, I did not know you want it to return null. As you said having problems with NPE, I assume you were having those within bestOf? In this case, rewrites would most probably only look awkward. E.g.:
return a0 ? b : (b0 ? a : a v b);

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.