[10] RFR (S): 8196022: java.lang.VerifyError is thrown instead of java.lang.IllegalAccessError

David Holmes david.holmes at oracle.com
Tue Jan 30 09:23:47 UTC 2018

Missed one thing at end ...

On 30/01/2018 7:21 AM, Vladimir Ivanov wrote:
> Hi David,
>> Okay. I'm quite concerned that 8188145 has been fixed so late in the 
>> JDK 10 release cycle. Aligning the implementation with the spec is 
>> generally the intention, but you have to evaluate the impact of the 
>> change in behaviour. How long have we been doing the wrong thing? How 
>> much code might depend on us doing the wrong thing? This could have 
>> unexpected consequences - as per the current issue!
> I haven't explored initial implementation (which went into JDK7 GA), but 
> current code is mostly there since 7u40. There were some adjustments in 
> JDK8 (e.g., AbstractMethodError instead of IllegalAccessError).
> But, most of the time, those errors were hidden by BootstrapMethodError, 
> because invokedynamic wrapped all errors. It was changed in 9 (see 
> JDK-8166974 [1]) and then JDK-8188145 was reported: there was a 
> discrepancy in behavior between different JVM implementations noticed.
> So, considering significant behavioral change in 9, the risk some code 
> depends on previous behavior is low. Otherwise, most likely, it is 
> already broken since 9 due to by JDK-8166974.
>>>>> The fix cleans JDK part to behave the same way.
>>>> I'm unclear how this fix changes the JDK side. Is it because it 
>>>> tranforms LinkageErrors, and you no longer allow LinkageErrors to 
>>>> pass through? (Does that make the JDK code "dead"?).
>>> Almost, but there are some important checks on JDK side which can 
>>> fail (e.g., see MemberName.Factory.resolve() [3]). So, I decided to 
>>> leave them as is for now.
>> This is unclear. Have we introduced new failure modes via 8188145, or 
>> are these existing failure modes?
> No new failure modes were introduced. Some existing failure modes left 
> intact for some corner cases (when MethodHandles::resolve_MemberName() 
> returned empty handle w/o throwing any exception).
>>>> Also the CR talks about VerifyError being thrown, but I didn't spot 
>>>> anything that would be dealing with that case ?? Is VerifyError the 
>>>> correct/expected exception now?
>>> Are you asking about JDK part?
>>> The confusion comes from the fact that MemberName resolution can be 
>>> triggered both from bytecode (through an upcall) and reflectively 
>>> (through proper MethodHandles.Lookup call).
>>> Reflective case doesn't want to see Errors, so everything is 
>>> converted to ReflectiveOperationException. But if resolution is 
>>> initiated by the JVM, ReflectiveOperationException is unwrapped back 
>>> to proper Error [4].
>>> To sum up, JDK part doesn't want to see LinkageErrors, so all 
>>> "unknown" errors are wrapped into IllegalAccessExceptions, but 
>>> requests from JVM should behave according to "equivalent bytecode 
>>> behavior".
>> Has this fix altered the behaviour of calls initiated from Java? Or is 
>> it only adjusting the exceptions the VM may see if the sequence is:
> There's one case when behaviour of calls initiated from Java changes: 
> NoClassDefFoundError/ClassNotFoundException were converted to 
> IllegalAccessException, but after this change ClassNotFoundException 
> will be reported.
> If it looks like too much for jdk10, I can change it back and commit 
> separately as a cleanup into the next release.
>> VM -> Java -> VM
>> ?
> For VM->Java->VM it changes IllegalAccessError to proper subclass of 
> LinkageError for the following cases:
>    * NoClassDefFoundError / ClassNotFoundException
>    * LinkageError subclasses reported during method/field resolution in 
> JVM (except NoSuchMethodError, NoSuchFieldError, and AbstractMethodError);
>>> That's why I decided to eliminate ClassNotFoundException for now - it 
>>> complicates with Error <-> Exception mappings.
>> Shouldn't you have just unwrapped the original NCDFE instead of 
>> wrapping the CNFE (which wraps the original NCDFE) with another NCDFE?
> Good point. I decided to mimic the behavior for 
> NoSuchMethodE*/NoSuchFieldE* and not for IllegalAccessException. No 
> strong reasons for that on my side, so if you prefer unwrapping I'll go 
> with it.
>           } catch (NoSuchMethodException ex) {
>               Error err = new NoSuchMethodError(ex.getMessage());
>               throw initCauseFrom(err, ex);
>           } catch (NoSuchFieldException ex) {
>               Error err = new NoSuchFieldError(ex.getMessage());
>               throw initCauseFrom(err, ex);
> +        } catch (ClassNotFoundException ex) {
> +            Error err = new NoClassDefFoundError(ex.getMessage());
> +            throw initCauseFrom(err, ex);

That not what I meant - that's still wrapping the original exception in 
a new one. I was thinking more:

} catch (ClassNotFoundException ex) {
   Throwable cause = ex.getcause();
   if (cause instanceof NoClassDefFoundError)
     throw (NoClassDefFoundError) cause;
   // else ...


> Best regards,
> Vladimir Ivanov
> [1] https://bugs.openjdk.java.net/browse/JDK-8166974

More information about the hotspot-runtime-dev mailing list