sixlegs.com / blog / java / friday-free-stuff.html

Root Beer Logo Root Beer

Chris Nokleberg's Fizzy Weblog

September 2004
Su M Tu W Th F Sa
      1 2 3 4
5 6 7 8 9 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
Previous  |  Next  |  More...
#  Friday Free Stuff

I just came across the Friday Free Stuff Java puzzle contest run by MaryMaryQuiteContrary. Unfortunately I noticed it after this week's contest had ended (congrats crazybob), but maybe I still have a shot at the bonus points [Emoticon]

The puzzle was to write a static method without a throws clause that would throw any exception you pass to it, including checked exceptions. Since the checked/unchecked exception distinction is not enforced by the JVM (it is a Java language feature), all you really have to do is get the right bytecode loaded. In the following solution, a class implementing the Mirror interface is loaded from a byte array. The single method in the interface does not declare any exceptions, but the sneaky implementation will throw any Throwable you pass to it anyway:

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Thrower {
    public static void sneakyThrow(Throwable t) {
        mirror.reflect(t);
    }

    interface Mirror {
        void reflect(Throwable t);
    }

    private static Mirror mirror = makeMirror();

    private static Mirror makeMirror() {
        try {
            byte[] bytes = new byte[]{
                -54,-2,-70,-66,0,3,0,45,0,16,1,0,11,84,104,114,111,119,101,114,
                73,109,112,108,7,0,1,1,0,16,106,97,118,97,47,108,97,110,103,47,
                79,98,106,101,99,116,7,0,3,1,0,14,84,104,114,111,119,101,114,
                36,77,105,114,114,111,114,7,0,5,1,0,10,83,111,117,114,99,101,
                70,105,108,101,1,0,11,60,103,101,110,101,114,97,116,101,100,62,
                1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,12,0,9,0,10,10,0,4,
                0,11,1,0,7,114,101,102,108,101,99,116,1,0,24,40,76,106,97,118,
                97,47,108,97,110,103,47,84,104,114,111,119,97,98,108,101,59,41,
                86,1,0,4,67,111,100,101,0,1,0,2,0,4,0,1,0,6,0,0,0,2,0,1,0,9,0,
                10,0,1,0,15,0,0,0,17,0,1,0,1,0,0,0,5,42,-73,0,12,-79,0,0,0,0,0,
                1,0,13,0,14,0,1,0,15,0,0,0,15,0,1,0,2,0,0,0,3,43,-65,-79,0,0,0,
                0,0,1,0,7,0,0,0,2,0,8,
            };

            Class[] types = new Class[]{
                String.class,
                byte[].class,
                Integer.TYPE,
                Integer.TYPE,
            };

            Object[] args = new Object[]{
                null,
                bytes,
                new Integer(0),
                new Integer(bytes.length),
            };

            Method m = ClassLoader.class.getDeclaredMethod("defineClass", types);
            m.setAccessible(true);
            ClassLoader cl = Thrower.class.getClassLoader();
            return (Mirror)((Class)m.invoke(cl, args)).newInstance();

        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

The bytecode itself is very straightforward, as you can see from this "javap -c" output:

Compiled from "<generated>"
public class ThrowerImpl extends java.lang.Object implements Thrower$Mirror{
public ThrowerImpl();
  Code:
   0:	aload_0
   1:	invokespecial	#12; //Method java/lang/Object."<init>": ()V
   4:	return

public void reflect(java.lang.Throwable);
  Code:
   0:	aload_1
   1:	athrow
   2:	return
}

The real trick, of course, is crafting the bytecode, since javac won't let you do it. Thankfully, nowadays there are plenty of other tools, such as ASM. Click here to see the code I used; it should print out text suitable for cutting-and-pasting into the array initializer. This code could easily be inlined into the Thrower implementation, but I didn't want to have any non-JDK dependencies.

One advantage of this solution is that any Throwable can be thrown, including IllegalAccessException and InstantiationException.

Postscript: After I had finished writing this I discovered that other people had tried to submit this JavaWorld article as a solution, which describes a technique almost identical to the one here. The difference is that the bytecode is created as a result of modifying an existing classfile via disassembly, instead of being inlined or generated on the fly. The article's solution was turned down because the judges were "looking for code that you can just compile and use, no cheap modify-the-class-file or create-a-new-class-file after-compiling-the-client tricks," but I think that's a bit harsh given it should be clear that was just due to a particular implementation. Actually, they probably just wanted to give free stuff to people who know something about Java, instead of being good at searching for JavaWorld articles. [Emoticon]

[Powered By FreeMarker]  [Valid Atom 1.0]  [Weblog Commenting by HaloScan.com]