IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Diagnosing Java Code: Platform-dependence "gotchas"
Discuss66KBe-mail it!
Contents:
Vendor-dependent bugs
Version-dependent bugs
OS-dependent bugs
Cross-platform's cost
Resources
About the author
Rate this article
Related content:
Improve the performance of your Java code
The Split Cleaner bug pattern
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Turning a spotlight on the platform-dependent bug patterns

Level: Introductory

Eric E. Allen (mailto:eallen@cs.rice.edu?cc=&subject=Platform-dependence "gotchas")
Ph.D. candidate, Java programming languages team, Rice University
1 May 2002

Column iconWrite once, run anywhere. That's the promise, but sometimes the Java language doesn't deliver. Sure, the JVM provides an unprecedented degree of cross-platform interoperability, but minor glitches at both the specification and implementation levels often prevent programs from behaving correctly on multiple platforms. In this article, Eric Allen discusses some platform-dependent aspects of Java programming to watch out for, such as tail-recursive calls, as well as built-in vendor, version, and operating system dependence. Eric also demonstrates some ways around this type of dependence. Share your thoughts on this article with the author and other readers in the discussion forum by clicking Discuss at the top or bottom of the article.

One of the main advantages to programming in the Java language is the tremendous degree of platform-independence it allows you. Rather than having to form separate builds of your product for each target platform, you can simply compile to bytecode and distribute to any platform with a JVM. Or at least that's the way the story is supposed to go.

It's not quite that simple. Although Java programming can save untold hours of developer support for multiple platforms, there are many compatibility snags across different JVM versions. Some of these snags are easy to spot and correct, such as using the platform-specific separator character when constructing path names. But others can be difficult or impossible to intercept.

For that reason, it's important to keep in mind the possibility that some anomalous program behavior that defies explanation may be a bug in a particular JVM.

Vendor-dependent bugs
Of course, if you want to see some of the many subtle platform-dependent bugs that exist in JVMs, you need only make a casual inspection of Sun's Java Bug Parade (see Resources). Many of the bugs listed here are implementation bugs that apply only to JVMs on one specific platform. If you don't happen to be developing on that platform, you may not even know that your program trips over it.

But not all Java platform dependence results from JVM implementation bugs. Significant platform dependence is introduced by the JVM specification itself. When the details of a JVM are left open at the specification level, it can produce vendor-dependent behavior across JVMs.

For example, as we saw back in "Improve the performance of your Java code" (May 2001), the JVM spec does not require optimization of tail-recursive calls. Tail-recursive calls are recursive method invocations that occur as the very last operation in a method. More generally, any method invocation, recursive or not, that occurs at the end of a method is a tail call. For example, consider the following simple code:


public class Math {
  public int factorial(int n) {
    return _factorial(n, 1);
  }  
  
  private int _factorial(int n, int result) {
    if (n <= 0) {
      return result;
    }
    else {
      return _factorial(n - 1, n * result);
    }
  }
}

In this example, both the public factorial method and its private helper method, _factorial, include tail calls; factorial includes a tail call to _factorial, and _factorial includes a recursive tail call to itself.

If that strikes you as a particularly convoluted way to write factorial, you're not alone. Why not write it in the following, much more natural form?


public class Math {
  int factorial(int n) {
    if (n <= 0) {
      return 1;
    }
    else {
      return n * factorial(n-1);
    }
  }
}

The answer is that tail calls allow for very powerful optimization -- they let us replace the stack frame built for the calling method with that for the called method. This can drastically decrease the depth of the stack at run time, preventing stack overflows (especially if the tail calls are recursive, like the one for _factorial in Listing 2).

Some JVMs implement this optimization; some don't. As a result, some programs will cause stack overflows on some platforms and not others. If only this optimization could be performed statically, we could simply compile the bytecode to a tail-call optimized form and then enjoy the optimization with platform independence. Unfortunately, as I explain in the above-referenced article on the subject, this optimization can't be performed statically.

Version-dependent bugs
The platform dependence resulting from tail calls is a result of the JVM spec itself. But the much more common causes of platform dependence are bugs in JVM implementations. In the case of Swing, such bugs are widespread.

For example, the JOptionPane component in JDK 1.4 has an associated bug. If a user adds text in a JOptionPane to a line that comes immediately after a blank line, and then presses the DOWN ARROW key, nothing happens. Try it for yourself:

  • Open a new JOptionPane.
  • In the OptionPane, then press the ENTER key twice.
  • Type "test".
  • Press the UP ARROW key.
  • Press the DOWN ARROW key.

Apparently, that sequence of operations (and similar sequences of operations) put JOptionPanes into a strange state. If a user of your program discovered this bug, he might very well recover from it by frantically banging at his keyboard. (It's not hard to recover from such a state; pressing the RIGHT ARROW key will do the trick.) Once he recovered, he may no longer care much that things froze up, and may never even report the bug. Users' standards of acceptability have been lowered substantially by decades of buggy software.

Here's the kicker, though. This bug exists on all versions of Sun JDK 1.4 for every platform I tested -- Windows, Solaris, and Linux. So it's likely an operating-system-independent bug in Sun's JDK.

This example illustrates that platform dependence is not just about OS dependence, and it's not just about vendor dependence -- it's about JVM version dependence, both backward and forward.

Teams are usually concerned about providing backward compatibility, but they often expect their code to maintain its behavior on later versions. Ideally, this expectation would be correct, but in reality it's not. In fact, it's not so surprising that Sun introduced a bug in Swing on version 1.4 given the tremendous effort they made in improving performance on that version.

Incidentally, Sun was not the only one dissatisfied with Swing's performance. The Eclipse project, an open source project designed to deliver a robust, open-source, full-featured, commercial-quality platform for the development of highly integrated tools, implements an entirely new widget toolkit, called the Standard Widget Toolkit (SWT). SWT is extremely lightweight, because, unlike Swing, it leverages the underlying platform-specific windowing system (see Resources). The API is identical across the platforms on which it is implemented, but the look-and-feel is entirely platform dependent. So we can expect a whole new set of platform-dependent issues with that toolkit.

OS-dependent bugs
As our final example of some of the insidious forms of platform dependence you can experience on the Java platform, suppose we are writing code for an editor that will open files and read them into an editor window. As a first cut, we might write the code as follows:


 FileReader reader = new FileReader(file);
 _editorKit.read(reader, tempDoc, 0);

The call to _editorKit.read reads the contents of the file into a temporary document which is later added to the collection of open documents. But after these two lines, we never refer to reader again.

This code is taken from an early version of the DrJava IDE, Rice University's free, open source Java IDE (see Resources). Now, if you are familiar with the Split Cleaner bug pattern, you may have noticed that this code provides a great example of that pattern.

A FileReader is constructed to read the contents of the file, but that FileReader is never closed. Of course, like other instances of the Split Cleaner, this bug will not produce any symptoms until some other attempt is made to access the file. But, depending on the platform, it may not produce any symptoms even then!

Suppose the user later tries to delete this file. On UNIX, open files may be deleted, so the vestigal, unclosed FileReader won't cause any problems. But if the user is on Windows, open files cannot be deleted, so an exception will be thrown. The bug in the previous code listing was discovered when one of our unit tests managed to pass on UNIX but not on Windows. Once the problem was diagnosed, it wasn't hard to fix:


      FileReader reader = new FileReader(file);
      _editorKit.read(reader, tempDoc, 0);
      reader.close(); // win32 needs readers closed explicitly!

The cost of cross platform is not zero
As the examples in this column demonstrate, the Java language is not immune to insidious platform-dependent bugs. The symptoms of these bugs are quite varied, but you can expect some of them to bite you at one time or another.

Although the cost of writing cross-platform is much less with the Java language than in many other languages, it's not zero. The best advice I can offer is to run your unit tests on as many platforms as possible, using as many JVM versions as possible. And, as always, avoid writing bug-prone code. Bug-prone code and platform dependence are a deadly combination. Here's a look at what we covered this month:

  • Pattern: Vendor-dependent bugs.

  • Symptoms: Errors may occur on some JVMs, but not on others.

  • Cause: Some unspecified areas in the JVM specification (such as no required optimization of tail-recursive calls, for example). This type of cause is less common than with version-dependent bugs.

  • Cures and preventions: Varies for the problems encountered.


  • Pattern: Version-dependent bugs.

  • Symptoms: Errors may occur on some versions of a JVM, but not on others.

  • Cause: Bugs in certain JVM implementations, such as Swing. This is a more common cause than the vendor-dependent bugs.

  • Cures and preventions: Varies for the problems encountered.


  • Pattern: OS-dependent bugs.

  • Symptoms: Errors may occur on some operating systems, but not on others.

  • Cause: Rules of system behavior are different on different operating systems (for instance, on Unix, open files may be deleted; on Windows, they may not).

  • Cures and preventions: Varies for the problems encountered.

I would like to thank DrJava developers Brian Stoler and John Garvin for their assistance in identifying the latter two bugs discussed in this article.

Resources

  • Participate in the discussion forum on this article. (You can also click Discuss at the top or bottom of the article to access the forum.)

  • Check out the Java Bug Parade for many examples of platform-dependent bugs. (You must be registered with the Java Developer Connection to access this list -- it's free!)

  • DrJava is Rice University's free, open source Java IDE, with a read-eval-print loop.

  • Check out the Eclipse Web site, for more information about the Standard Widget Toolkit.

  • Visit the Extreme Programming Web site for a summary of the ideas behind XP.

  • If you're interested in learning more about XP, "XP distilled" by Roy Miller and Chris Collins (developerWorks, March 2001) offers a succint overview into this agile software development methodology.

  • Download Jython, an implementation of Python that is seamlessly interoperable with Java code and libraries.

  • The JUnit Web site provides links to many interesting articles from a multitude of sources that discuss program testing methods.

  • Read all of Eric Allen's Diagnosing Java Code columns, including a full complement of articles on bug patterns.

  • Find other Java related content on the developerWorks Java technology zone.

About the author
Eric Allen has a bachelor's degree in computer science and mathematics from Cornell University and is a PhD candidate in the Java programming languages team at Rice University. Before returning to Rice to finish his degree, Eric was the lead Java software developer at Cycorp, Inc. He has also moderated the Java Beginner discussion forum at JavaWorld. His research concerns the development of semantic models and static analysis tools for the Java language, both at the source and bytecode levels. Eric is the lead developer of Rice's experimental compiler for the NextGen programming language, an extension of the Java language with added language features, and is a project manager of DrJava, an open-source Java IDE designed for beginners. Contact Eric at eallen@cs.rice.edu.


Discuss66KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Send us your comments or click Discuss to share your comments with others.



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact