Fabrizio Giudici is a Senior Java Architect with a long Java experience in the industrial field. He runs Tidalwave, his own consultancy company, and has contributed to Java success stories in a number of fields, including Formula One. Fabrizio often appears as a speaker at international Java conferences such as JavaOne and Devoxx and is member of JUG Milano and the NetBeans Dream Team. Fabrizio is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

Generics Compiler Bug?

06.16.2008
| 6593 views |
  • submit to reddit
I've been using generics for a few years, and I'm happy with them. I'm aware of the issues related to their design, and I agree that they could have been made a bit better (but I understand the trade-offs made for backward compability), but on the whole I consider them quite useful for writing better code. I haven't had any problems so far, but now I got stuck with a thing that looks like a compiler error. Consider this class that I'm using as a transactional decorator (*) in my NetBeans RCP stuff:
package it.tidalwave.bluemarine.persistence;

...

public abstract class TxTask
  {
    ...

    public abstract T run()
      throws E;
   
    public static <T> T execute (final TxTask<T, RuntimeException> task)
      {
        return execute(RuntimeException.class, task);
      }
   
    public static <T, E extends Throwable> T execute (final Class exceptionClass,
                                                      final TxTask<T, E> task)
      throws E
      {
            try
              {
                ...
                try
                  {
                    result = task.run();
                    ...
                  }
                catch (RetryException e)
                  {
                    ...
                  }
                catch (PersistenceException e)
                  {
                    ...
                  }
              }
            catch (Throwable t)
              {
                ...

                if (exceptionClass.isAssignableFrom(t.getClass()))
                  {
////////////////////// THE PROBLEM IS HERE
                    throw (E)t;
////////////////////// THE PROBLEM IS HERE
                  } 
                throw (t instanceof RuntimeException) ? (RuntimeException)t : new RuntimeException(t);
              }

            ...
          }
       
        ...
           
        return result;
      }
  }

The peculiarity of this class is that it uses a generic in the "throws" clause. This is ok with the language specifications, see for instance the excellent Angelika Langer's tutorial on generics (even though it's considered pretty useless in most cases, since you can't throw a generified exception because of type erasure - but in my code things are ok since I'm just rethrowing).

Now, the history of this issue is pretty complicated:

  1. The thing worked for several weeks, if not months (I remind you, in a NetBeans RCP project)
  2. At a certain point, I copied the class in the prototype of a web application, and I got a compiler error (see below); since I was focused on other things, I just commented out the offending part. blueMarine was still compiling ok.
  3. Since a few weeks, this is triggering the compiler error even in the NetBeans RCP project: but ONLY IF I BUILD THE WHOLE PROJECT (that is, if multiple *.java files are compiled at the same time). If I select the single TxTask.java file and compile it with the pop-up menu, it gets compiled ok (in fact, with this trick blueMarine builds, runs and passes the tests).

.../TxTask.java:169: unreported exception java.lang.Throwable; must be caught or declared to be thrown
throw (E)t;

This happens with the Java 5 compiler both on Mac OS X and Ubuntu Linux. I've asked for help in a couple of mailing lists so far, without success. So if you have any hints, please help: it is causing major pain, since it's breaking the CI on Hudson. The full source of the class is here: https://bluemarine.dev.java.net/svn/bluemarine/trunk/src/blueMarine-core/Core/Persistence/src/it/tidalwave/bluemarine/persistence/TxTask.java

(*) I'd like to get rid of this class sooner or later and use Spring or EJB3 annotations for transactions - but this won't happen before the next release, and I need to have CI working before that.

From http://weblogs.java.net/blog/fabriziogiudici/

 

Published at DZone with permission of Fabrizio Giudici, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

David Voo replied on Mon, 2008/06/16 - 2:27am

Please revert if any workaround or solutions found, greatly appreciate to know how to resolve this. TQ

Walter Laan replied on Mon, 2008/06/16 - 3:11am

Edit: Oh looks like the blog to javalobby stripped all the generic info form the code, comments on the blog suggest using 1.6 compiler which has the fix for http://bugs.sun.com/view_bug.do?bug_id=6280975 with target 1.5.

The code posted does not compile at all, neither T or E are declared. Do you have the problem using it as follows?

public abstract class TxTask<T, E extends Throwable> {

public abstract T run() throws E;

public static <T, E extends Throwable> T execute(final TxTask<T, E> task) throws E {
return execute(null, task);
}

public static <T, E extends Throwable> T execute(
final Class<E> exceptionClass, final TxTask<T, E> task) throws E {
T result = null;
try {
try {
result = task.run();
}
catch (Exception e) {
}
}
catch (Throwable t) {
if (exceptionClass != null && exceptionClass.isInstance(t)) {
throw exceptionClass.cast(t);
}
throw (t instanceof RuntimeException) ? (RuntimeException) t : new RuntimeException(t);
}

return result;
}
}

 

Honey Monster replied on Mon, 2008/06/16 - 8:51am

It appears that you have been bitten by type erasure. You can not cast to a type defined by a type parameter (nor can you ever catch an exception using a type parameter as the type of the exception). The reason for this is that the type is not known at runtime, since it has been erased.

I would also question your assertion that you understand the trade-offs made for backward compability. The fact that you refer to the issue as backwards compatibility sort of gives you away. It was never backwards compatability which mandated type erasure, but a much more esoteric term reffered to (by Gafter) as migration compatibility. It was entirely possible to have designed reified generics (i.e. no type erasure) and maintained backwards compatability.

Type erasure was only mandated in extreme cases where a customer would be using multiple libraries 1) to which he didn't have source access to 2) were supplied from separate vendors 3) where the vendors had not isolated their usage of collections by using interfaces 4) where some of the vendors made compelling versions only on Java 1.5+ while other vendors made compelling new versions only on 1.4, 5) the vendors relied directly upon eachother - i.e. not integrated through source code under control of the customer and 6) the customer could not possibly wait for the vendors to get their libraries in synch. Only when all of the above applied would usual "backward" compatibility not apply.

That type erasure was needed because of backwards compatibility is a myth. Pure and simple. The constraint which could not be solved with reified generics were a niche constraint which IMO is unlikely to have ever benefitted anyone but lazy VM vendors. .NET had a similar situation and created reified generics and implemented new generic collection classes which simply implemented the old-style (non-typesafe) interfaces with runtime casts, thus not only allowing old bytecode to run unmodified on the new VM but also allowed new generic collections to be passed to old code.

Geertjan Wielenga replied on Mon, 2008/06/16 - 10:08am in response to: Walter Laan

[quote=walter laan]

Edit: Oh looks like the blog to javalobby stripped all the generic info form the code

[/quote]

Thanks, fixed that. 

Fabrizio Giudici replied on Mon, 2008/06/16 - 12:39pm

@David, Rémi Forax on my blog (where I originally posted the question) pointed me where the problem is, that is the same bug compiler notified by Walter. More in details, the bug affects the Java 5 compiler and it triggers more or less unpredictably when you are compiling multiple files together. That's why when I compile it alone, the thing works. The bug has been solved in Java 6, but I can't use it on my Mac because it's only 32 bit (I'm repeating for the n-th time, thanks Apple). I think I'll try to work around it by tweaking the build.xml and have an explicit compilation of TxTask alone.  @Walter, no, using exceptionTask.cast() doesn't work.  @Uffe, indeed I can cast to a type parameter, in fact this would be compiled to a type cast in bytecode, which is not affected by type erasure; you're right that I can't catch a generified exception, because type erasure plays here, but I'm not catching a generified exception. Actually the listed code is perfectly legal, as pointed out it's "just" a compiler bug.  

Fabrizio Giudici replied on Mon, 2008/06/16 - 5:37pm

Ok, this added to the build.xml of my project (a NetBeans RCP project) makes sure that the offending file is compiled alone before the others in the module, and the bug goes away.

    <!-- Workaround javac bug 6280975 - drop fix when definitely moving to Java 6 -->
<target name="-pre-compile">
<property name="javac.includes" value="it/tidalwave/bluemarine/persistence/TxTask.java"/>
<echo message="Compiling ${javac.includes} separately because of http://bugs.sun.com/view_bug.do?bug_id=6280975"/>
<ant target="compile-single" />
</target>
<target name="compile" depends="-pre-compile,projectized-common.compile"/>

Daniel Spiewak replied on Tue, 2008/06/17 - 11:11am

Worth noting that I've never run into generics bugs in the Eclipse compiler, even pre-6.0.  Other bugs, yes, but not generics.  There are actually still bugs in Sun's 6.0 implementation of javac relating to generics, cases that are valid according to the spec and even handled correctly by ejc.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.