Imagine the following (failing) JUnit test:
@Test
public void yieldInsideException() {
Iterator<String> it = new Yielder<String>() {
@Override
protected void yieldNextCore() {
try {
throw new Exception("Aviad");
} catch (Exception e) {
yieldReturn(e.getMessage());
}
}
}.iterator();
Assert.assertEquals("Aviad", it.next());
Assert.assertFalse(it.hasNext());
}
I will examine this as soon as I can.
TRYCATCH L0 (try start) L1 (try end, not inclusive) L1 (catch start)
L0:
NEW java/lang/Exception
DUP
LDC "Aviad"
INVOKESPECIAL java/lang/Exception.<init> (Ljava/lang/String;)V
ATHROW
L1:
// Here, the stack is already filled with the thrown exception!!
ASTORE 1 // storing so we can use it later on
ALOAD 0
ALOAD 1
INVOKEVIRTUAL java/lang/Exception.getMessage ()Ljava/lang/String;
INVOKEVIRTUAL com/infomancers/tests/YielderTests$19.yieldReturn
(Ljava/lang/Object;)V
But, since the ASTORE is changed to PUTFIELD and ALOAD is placed in
the beginning of the code (expecting the stack to be empty in the
beginning of each code block, since all local variables are kept on
field members):
L1:
// Here, the stack is already filled with the thrown exception!!
ALOAD 0
PUTFIELD slot$1 // but, nothing was set on the stack to put into
the slot$1 member, so it crashes.
So, it seems like a special treatment is needed for exception
handlers. Anyone with a better idea, please step up!
The naive approach means, before any INVOKEVIRTUAL call, add a
CHECKCAST with the owner of the method of INVOKEVIRTUAL.
While this might have worked for parameter-less methods, this doesn't
work otherwise. The reason is that during method invocation, the stack
is ordered as: "invoker, param1, param2, param3", so the CHECKCAST
command will actually occur on "param3" and not on "invoker", as
intended.
The solution required here is turning out to be complex. Any ideas for
a simple one?
After fixing (at least I hope I have) the problem with the exceptions,
I've encountered a new problem: Any method invocation other than on
the Object type fails due to casting problems. For example, the
following (enhanced) code fails:
//inside yielder, slot$1 holds a String object.
ALOAD 0
ALOAD 0
GETFIELD slot$1 Ljava/lang/Object;
INVOKEVIRTUAL java/lang/String.toString ()V
INVOKEVIRTUAL yielder.yieldReturn (Ljava/lang/Object;)V
This is roughly equivilent to:
yieldReturn(slot$1.toString());
As you can see from the above Java code, this will not pass a
compiler; a cast is required. The JVM is protecting itself from such
mischiefs by checking that no calls are made to unchecked classes, so
it fails the execution as well. Therefore, a CHECKCAST call is
required for slot$1 before the invocation.
Sigh..