Skip to main content

Don't Try This at Home

Posted by crazybob on September 11, 2004 at 7:44 AM PDT

Dr. Josh Bloch and Dr. Neal Gafter (Click and Hack) guest starred on Mary's Friday Free Stuff Puzzler last week. Every week, Mary, a Sun marketing geek, boxes up random items she finds around her office and ships them off to the weekly winner. Her blog is a fun read. Check it out.

Click and Hack provided an example of subverting compiler exception checking (i.e. throwing a checked exception like an unchecked exception) using the deprecated Thread.stop(Throwable) method, and then challenged the reader to do the same with a non deprecated method and without any sort of bytecode trickery. They knew of two possible alternate solutions, one that would work with any JDK, and one that was JDK 1.5 specific. Their provided example method performs the same function as the throw clause, except the caller is not forced to handle checked exceptions:

  public class Thrower {  
    public static void sneakyThrow(Throwable t) {
      Thread.currentThread().stop(t); // deprecated
    }
  }

See for yourself by running this example program:

  public class Test {
    public static void main(String[] args) {
      Thrower.sneakyThrow(new Exception("Ouch"));
    }
  }

The first solution takes advantage of a bad design decision in the Class.newInstance() method. Class.newInstance() directly propagates any exception thrown by the default constructor. The API designers have since avoided problems in other reflective methods by wrapping target exceptions. For example, Constructor.newInstance(Object[]) throws InvocationTargetException which wraps any exception thrown by the target constructor. This helps the client differentiate between target exceptions and exceptions thrown by newInstance() itself.

  public class Thrower {

    private static Throwable throwable;

    private Thrower() throws Throwable {
      throw throwable;
    }

    public static synchronized void sneakyThrow(Throwable throwable) {
      // can't handle these types.
      if (throwable instanceof IllegalAccessException
          || throwable instanceof InstantiationException)
        throw new IllegalArgumentException();
      
      Thrower.throwable = throwable;
      try {
        Thrower.class.newInstance();
      }
      catch (InstantiationException e) {
        // can't happen.
        e.printStackTrace();
      }
      catch (IllegalAccessException e) {
        // can't happen.
        e.printStackTrace();
      }
      finally {
        Thrower.throwable = null;
      }
    }
  }

Note that the code sets the static throwable field back to null in a finally block. Neal added this to prevent a memory leak (this is why we trust these guys with our core code). "Who cares about how much memory one exception takes up?" some might ask. Though this is a trivial example, the real world implications are much larger. For example, without the finally block, if Thrower is in the system class path, we call sneakyThrow() with an exception whose class loaded in a web application, and then we try to hot deploy that web application, the VM can not garbage collect the old deployment. The system class loader holds a strong reference to the exception instance, which holds a strong reference to the class, which holds a strong reference to the web application classloader, which holds a strong reference to all of the classes in the web application (substantially more than one exception). Whether we're talking about a framework that handles AOP, dependency injection, or logging, the implementor has to keep this in mind.

The final JDK 1.5 specific solution takes advantage of the fact that the compiler does not type check generics at runtime. In defense of JDK 1.5, the compiler does print a warning.

  public class TigerThrower {

    private void sneakyThrow2(Throwable t) throws T {
      throw (T) t; // Unchecked cast!!!
    }

    public static void sneakyThrow(Throwable t) {
      new TigerThrower().sneakyThrow2(t);
    }
  }