Could it really be so simple? After I found out that programs seem to work if I exclude the SynchronizedMethodToBlock
instrumentation strategy, I spent days looking over its source code. There wasn’t a whole lot, but I knew the code had to be directly or indirectly responsible. It had to be a somewhat subtle issue, since it didn’t occur all the time.
One of the things that happens most rarely, probably, is that an exception has to unwind the stack yet execute the finally
handler that I add to unlock the monitor. And it seems like this is where the problem actually was. I had adjusted the maximum stack size field to be at least one, since I need one stack slot for the aload\_0; monitorenter/monitorexit
sequence. However, in the finally handler, one slot is already occupied by the Throwable
instance that was thrown. The object whose monitor to be unlocked is a second item on the stack.
I enlarged the stack to at least two, and now it seems to work! I closed item 9 in my to-do list. I also took care of item 5, removing the events for (static) synchronized methods, which was trivial. Since synchronized methods get turned into regular methods with a synchronized blocks, only events for synchronized blocks are generated at this point.