|
|
|||||||||||||||||||||||||||||||||||||||||||||||
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 ![]()
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.