Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Friday, 20 August 2010

Broken Applications

So I've written my own fair share of bugs into software in the past but I hate it when I come up against bugs in software that I have absolutely no control over. Unfortunately these bugs might bite you as well as me.

I've released a number of bits of software through this blog that use Java Web Start to make it easier for you to download and install them. Unfortunately there appears to be a number of bugs which result in the installed shortcuts breaking and even causing the launch link on the web page to not work if you have previously run the application.

Well I upgraded PhotoGrid yesterday and everything broke. I'm assuming it may have broken for everyone else as well. It took me a while to figure out what went wrong and how to fix it so I thought I'd write it up here in case I've broken it for everyone else.

We can fix the problems using the Web Start cache viewer, which you can load by issuing the command javaws -viewer (on Windows you can enter this command into the Run... dialog box accessed from the Start Menu), and which should look like...


Now select the application that won't start and click the red cross button to uninstall it. Even though it will now have been uninstalled Web Start still remembers some details which can be a problem. So in the drop down box at the top left switch to viewing Deleted Applications. A similar list will appear and go through the same steps (select and the red cross) to fully delete the application. You should now be able to re-install without any problems.

The problem only seems to arise when I have to edit the JNLP file that is used to launch the applications. Updating the actual code works flawlessly. On the plus side it is quite rare for me to need to edit the JNLP files and so I'm hopeful that once the applications are working again they should continue to work for the foreseeable future.

Apologies for any inconvenience and I hope it doesn't put you off using the bits of software I write.

Wednesday, 4 November 2009

The Line Endings Are Conspiring Against Me!

It's not paranoia if they're really out to get you!

At work we are gearing up for an important release of GATE. We released a beta last week so that we could get as much feedback as possible on the current build before making the final release. Unfortunately I managed to introduce a show-stopping bug into the build which rendered the default GATE application (ANNIE) unusable under Windows.

I'm pretty careful about testing code before I commit changes so I was mortified when it became obvious that the beta was badly broken. What follows is a verbatim copy of an e-mail I sent to the GATE group explaining how the bug was introduced and why it hadn't been spotted by any of our tests. The specific situation might not happen to many software developers but it's a good example of why we shouldn't rely only on automated tests for application stability.

I was very annoyed at myself for allowing the bug to slip into the beta build but at a complete loss as to exactly how it happened. I've been thinking about it some more and I now know how it happened and I think the reason is important enough to share with everyone.

This isn't an excuse for what happened but it should be a warning to everyone else that sometimes even when we are careful with tests etc we can get caught out. This will be quite a long e-mail as I want to fully explain the situation for those not familiar with the internals of the JAPE parser/compiler. If you want the take home message there is a simple summary at the bottom.

The code that fails was trying to split a block of Java code generated by parsing a JAPE file into separate lines and then return the line at which use of the deprecated annotations parameter had been spotted. The method I checked in looked like this:
public String getSource(int line) {
String[] lines = getSource().split(nl);
return lines[line-japeLine];
}
where nl is a class field that is initialized to System.getProperty("line.separator"). So this method should split a block of text into separate lines based on the platform specific line separator and then return a specific line from the resultant array. This should be safe as the code block is from the generated Java source, which uses the same nl field for separating lines.

I developed the code at home under Windows and it worked -- ANNIE ran with no sign of the exceptions reported against the beta. I checked the code in and a short while later Hudson checked it out, built it and tested it (including running ANNIE) under Linux (see here for the latest build/test results). Everything built and ran successfully.

At this point with it having been tested under Windows and Linux I was fairly happy that the code was stable (I was especially worried after Hamish had agreed to the change but had cautioned about adding new code so near to a release as statistically new code means new bugs - man was he right!).

Ian then built the beta release, publicized it on the mailing list and as we know ANNIE broke spectacularly for anyone trying it under Windows. The only question is why?

I'm willing to bet a beer/coffee to the first person with the time to checkout and build the beta from subversion (SVN) under Windows that it works. So what is different about the builds Ian pushed to the website?

The answer is that the line endings and subversion have conspired against me :(

When a JAPE source file is parsed the Java blocks that make up the RHS are added to the Java source code as is. And we use the platform specific line separator to build the rest of the source. When we check JAPE files into SVN we make sure that we set the svn:eol-style to native. So if you check out GATE from subversion all the ANNIE JAPE files have the native line endings and so the full Java source file that we eventually compile has the same line ending throughout and everything works. This is why ANNIE ran under both Windows and Linux when checked out of SVN.

When Ian built the betas I'm guessing that he did so under Linux (or on his Mac but for the purpose of this discussion that doesn't matter). So again the tests would run as the JAPE files would have Linux line endings and we would use the Linux line separator.

A user then downloads the beta from the website and tries running ANNIE under Windows -- it fails. The problem here is that the JAPE files in the beta builds have Linux line endings and we then use Windows line endings to assemble the code. I then use the Windows line separator again to split the code to get at single lines of source. Linux uses the single \n to represent the end of line while Windows uses the two character \r\n. So when I try and split code containing Linux line endings using the two character Windows line ending nothing happens and the array offset exception is thrown.

If we had built the beta under Windows then we may never have spotted the problem, as splitting on either platform using either line ending would have worked. The problem would have only arisen if someone created a JAPE file under Linux and then tried to use it under Windows without SVN in the middle, which conceivably might not have been until after the final release.

My fix was to simply change the offending method to
public String getSource(int line) {
String[] lines = getSource().split("\n");
return lines[line-japeLine];
}
which works on all platforms we support.

Looking back it's clear that there is no way we could have easily caught this using the automated tests. Fortunately we have caught the problem now rather than after the final release, yet I don't have any idea how we could stop similar problems occurring again in the future.

In Summary: The bug only appeared because the svn:eol-style was set to native, the beta release was built under Linux, and together this means we end up with a mixture of line endings when running under Windows. Had we built the installers under Windows the issue of running ANNIE would never have arisen but it would have bitten anyone editing JAPE under Linux then running under Windows (without SVN in the middle), which may not have happened until after the final release.

If anyone has any thoughts/suggestions on ways we could improve the testing to try and pick up such weird cases in the future please let me know and I'll try adding them to Hudson.

Sorry for both the bug and the long post but I thought it worth taking the time to explain how the problem arose so we can all (me especially) try and avoiding it happening again in the future,

Thursday, 16 October 2008

Compiled Code Compiles Code

Now and again I try and do something that stretches the limit of a programming language. This usually results in a very bad headache and days of frustration.

At work I have been developing a rule based document annotation system called Saxon. The rules are loaded into the system and converted to Java code which is then compiled, loaded into the running instance of Java and then used to annotate the documents. If you are already getting a headache I'd stop reading here as this post is only going to get more confusing!

For a long time there was no standard way of having a running Java program compile and then use new classes. The only way was to write the source files to disk, call the command line compiler and then create a new class loader to load the compiled classes into memory. Not a simple way of doing things.

With Java 6 Sun introduced a standard API for compiling that was supposed to make life easy. Once you know how the new API works it is in fact very easy to use. On the plus side you can now easily do in-memory compilation which is much faster than reading and writing to disk, but of course you are restricted to using Java 6 which not everyone has upgraded to yet.

Unfortunately there is a much more serious problem. Sun, in their infinite wisdom, decided that compiling code was something only developers would want to do and so only bundle a concrete implementation with the JDK and not the JRE which means that applets and Web Start applications don't have access to a compiler.

Fortunately Eclipse provide the JDT Core Batch Compiler which includes a Java 6 complient compiler. This works great in standalone apps, allowing me to run Saxon from a JRE and still have all the functionallity I need.

However, I was trying to make Saxon available as a Web Start application and ran into a bit of a problem. There is something very strange with the class loading in the Eclipse compiler. It works fine in a standalone application but under Web Start the compiler can only see core Java classes which means I can't reference my own API from the classes I'm trying to compile and everything fails and I end up back at square one.

Don't despair though as their is a solution. Throw away the Java 6 API and use the Eclipse compiler directly. This has the advantage of working under Web Start and under previous Java versions (at least version 5 which is more commonly installed than 6). I can't take all the credit for this solution but I thought it worth blogging about as I'm yet to see anything similar any where else on the web, although I did find lots of posts asking how to do this.

The following code is cobbled together from two main sources; Apache Tomcat and GATE. I actually used the GATE code, which is based on Tomcat code, as my starting point and then simplified it and added some generics support.

You can download the full compiler (a single Java source file and the JDT Batch Compiler) from here. And here is a simple example of how to use it (this is actually the main method of the class for easy testing).
EclipseCompiler compiler = new EclipseCompiler();

String HELLO_WORLD_CLASS_NAME = "HelloWorldTest";
String HELLO_WORLD_SOURCE = "import org.eclipse.jdt.internal.compiler.Compiler;\n\n" +
"public class "+HELLO_WORLD_CLASS_NAME+"{\n" +
" public static void main(String[] args) {\n" +
" System.out.println(\"Hello world\");\n" +
" }\n" +
"}";

Class compiledClass = compiler.compile(HELLO_WORLD_CLASS_NAME, HELLO_WORLD_SOURCE);

if (compiledClass==null)
{
System.out.println("Unable to compile file");
return;
}

Method m = compiledClass.getMethod("main",String[].class);
m.invoke(null, new Object[]{null});
The interesting thing to note is that the small test case imports a class not from the core Java APIs and this works in standalone applications and when run under Web Start.