retrolambda icon indicating copy to clipboard operation
retrolambda copied to clipboard

java.lang.NoClassDefFoundError: java/lang/ReflectiveOperationException

Open gerritdaniels opened this issue 8 years ago • 6 comments

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"?

gerritdaniels avatar Jul 16 '16 03:07 gerritdaniels

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.

luontola avatar Jul 16 '16 12:07 luontola

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 :(.

csoroiu avatar Jan 04 '17 11:01 csoroiu

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.

luontola avatar Jan 17 '17 19:01 luontola

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

csoroiu avatar Jan 17 '17 20:01 csoroiu

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.

csoroiu avatar Jan 17 '17 20:01 csoroiu

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.

luontola avatar Jan 22 '17 15:01 luontola