Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.lang.NoClassDefFoundError: java/lang/ReflectiveOperationException #104

Open
gerritdaniels opened this issue Jul 16, 2016 · 6 comments

Comments

@gerritdaniels
Copy link

gerritdaniels commented Jul 16, 2016

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

@luontola
Copy link
Owner

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.

@csoroiu
Copy link
Contributor

csoroiu commented Jan 4, 2017

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

@luontola
Copy link
Owner

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.

@csoroiu
Copy link
Contributor

csoroiu commented Jan 17, 2017

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
Copy link
Contributor

csoroiu commented Jan 17, 2017

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.

@luontola
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants