retrolambda
retrolambda copied to clipboard
java.lang.NoClassDefFoundError: java/lang/ReflectiveOperationException
When java 8 code uses reflection the class ReflectiveOperationException doesn't get backported to java 6. Causing a NoClassDefFoundError because all reflection exceptions derive from it since java 7. Would it be possible to add support for it?
I'm asking because I want to backport https://github.com/sdaschner/jaxrs-analyzer to java 6. Because this failed I tried to convert it to java 7, but this gave other NoClassDefFoundErrors. So actually my question is, could you enhance retrolambda to support this project?
Or what would be required to facilitate that enhancment, is there an easy way to say "backport these core java classes that are unavailable in java 6"?
There is no easy way to backport a library which uses Java 7/8 APIs. The ReflectiveOperationException error is probably a case of the code in that library catching it or using it in a throws clause. The library would need to be changed to not catch that exception, but those of its subclasses which exist on Java 6. Usages of other new APIs need to be changed in a case by case manner.
You would probably have to fork that library and manually change all of its code to avoid Java 7/8 APIs. IntelliJ IDEA has a code inspection for warning about usages of too new APIs. And/or run its tests under Java 6 and see where it fails.
I steped into the same problem and realized that I was using a multi-catch block like:
try {
... //stuff
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignore) {
}
After expanding the multi-catch block to:
try {
... //stuff
} catch (NoSuchMethodException ignore) {
} catch (InvocationTargetException ignore) {
} catch (IllegalAccessException ignore) {
}
In the case of the multi-catch statement, looking at the bytecode one can see that there is a frame that refers java/lang/ReflectiveOperationException
, but the bytecode for the second case was containing 3 different frames:
FRAME SAME1 java/lang/ReflectiveOperationException
ASTORE 0
vs
FRAME SAME1 java/lang/NoSuchMethodException
ASTORE 0
... //other instructions
FRAME SAME1 java/lang/reflect/InvocationTargetException
ASTORE 0
... //other instructions
FRAME SAME1 java/lang/IllegalAccessException
ASTORE 0
It is wise to avoid multi-catch statements when dealing with lambda's, to avoid such issues at runtime. I am also using mojohaus/animal-sniffer on the code processed by retrolambda, but apparently it does not catch this case :(.
That multi-catch optimization done by javac is interesting. Maybe it would be possible to duplicate the catch block and replace it with catch blocks for the individual exceptions. Sounds like a lot of work, so I won't try doing it right now. Pull requests are welcome.
I was about to write my idea few minutes ago.
I was thinking, and the best thing is to change the type of the exception with the first super type of that exception from hierarchy available in the version of java we want to translate to.
So, you do not necessarily need to duplicate the block for each exception because they do the same think, so that's why the generated trycatch
blocks share the same labels if you look at the bytecode.
Up to this point everything is fine.
Going forward, the side effect of changing the type of the exception using my suggestion, leads to the issue that one might call a method on the exception that is not available on the super type. I think that call to invokevirtual
on that specific type, needs to be translated to the super type. (i might be wrong it this last phrase).
What I can do is to build a script to build a list of newly added type of exceptions in the middle of the existing hierarchy for java 8 and see if they have any special methods or not and create a list. I think the number of added exceptions is small and translation map can be used.
Let me know if you want to go forward with this idea or any other suggestion and I can help.
As a side note, even if I am using this library for educational purposes, it is a great one. I learned a lot from analyzing what it does and how it goes. Great work!
In case anyone else is looking, here is how the trycatch
table looks like for the multicatch statement:
TRYCATCHBLOCK L0 L1 L2 java/lang/NoSuchMethodException
TRYCATCHBLOCK L0 L1 L2 java/lang/IllegalAccessException
TRYCATCHBLOCK L0 L1 L2 java/lang/reflect/InvocationTargetException
Duplicating that try catch block seem to be more consistent than my idea of translating the exception to the super type, as it inlines better with the substitution principle. I am not sure how easy is to achieve that.
I haven't checked the code, but duplicating the catch block shouldn't be too hard. It should be either just copying the bytecode instructions as-is, or maybe just modifying the trycatch table or adding a few gotos is enough.
It would be risky to change the code to catch the next superclass, because that will change code semantics.