JDK-8005122 : Unexpected ICCE when invoking newInvokeSpecial MH for private inner class ctor
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • Submitted: 2012-12-17
  • Updated: 2014-06-06
  • Resolved: 2013-10-01
Related Reports
Cloners :  
Duplicate :  
Relates :  
Relates :  
Description
The following test code creates an instance of Enclosing, which then captures a constructor reference for a nested private class.  The constructor reference wraps a newInvokeSpecial MH on the private ctor, which is accessbile to Enclosing.  But, when we try and invoke it, we get an ICCE with a sub-error about accessibility.  

Test code:

    public void testPrivateInnerCtorRef() {
        Supplier<Object> ctor = new Enclosing().foo();
        System.out.println(ctor);
        System.out.println(ctor.get());
    }

    static class Enclosing {

        public Supplier<Object> foo() {
            return Inner::new;
        }

        private class Inner {
            private Inner() {}
        }
    }

Error:

java.lang.IncompatibleClassChangeError
        at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:383)
        at org.openjdk.tests.java.lang.invoke.GenBadMHTest$Enclosing.foo(GenBadMHTest.java:106)
        at org.openjdk.tests.java.lang.invoke.GenBadMHTest.testPrivateInnerCtorRef(GenBadMHTest.java:98)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:474)
        at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80)
        at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
        at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
        at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
        at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
        at org.testng.TestRunner.privateRun(TestRunner.java:767)
        at org.testng.TestRunner.run(TestRunner.java:617)
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
        at org.testng.SuiteRunner.run(SuiteRunner.java:240)
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1198)
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1123)
        at org.testng.TestNG.run(TestNG.java:1031)
        at org.testng.TestNG.privateMain(TestNG.java:1338)
        at org.testng.TestNG.main(TestNG.java:1307)
Caused by: java.lang.IllegalAccessException: member is private: org.openjdk.tests.java.lang.invoke.GenBadMHTest$Enclosing$Inner.<init>(Enclosing)void/invokeSpecial, from org.openjdk.tests.java.lang.invoke.GenBadMHTest$Enclosing
        at java.lang.invoke.MemberName.makeAccessException(MemberName.java:732)
        at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1135)
        at java.lang.invoke.MethodHandles$Lookup.getDirectConstructor(MethodHandles.java:1243)
        at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(MethodHandles.java:1270)
        at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:381)

Enclosing.foo() bytecode:

public java.util.function.Supplier<java.lang.Object> foo();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokedynamic #2,  0              // InvokeDynamic #0:lambda:(Lorg/openjdk/tests/java/lang/invoke/GenBadMHTest$Enclosing;)Ljava/util/function/Supplier;
         6: areturn       

BootstrapMethods:
    0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      Method arguments:
        #25 invokeinterface java/util/function/Supplier.get:()Ljava/lang/Object;
        #26 newinvokespecial org/openjdk/tests/java/lang/invoke/GenBadMHTest$Enclosing$Inner."<init>":(Lorg/openjdk/tests/java/lang/invoke/GenBadMHTest$Enclosing;)V
        #27 ()Ljava/lang/Object;


Issues:
#1.  Because the Inner ctor is accessible to Enclosing, the MH construction should be allowed, and then aren't we past the access check?

#2.  If the problem is indeed accessibility, why is ICCE thrown?
Comments
This behavior is according to JSR 292 spec. The EG made an explicit decision to make access checks to method handles preserve the Java 1.0 access semantics at the bytecode level. At the bytecode level, inner classes appear to be package siblings, and there is no special rule for nestmate access. This is why (even today) access$NNN wrappers (of package scope) need to be generated. This is wrong, but the EG felt that fixing this problem was out of scope for 292. If we fix this, somehow, the present bug goes away. The fix appears to be quite simple, at least for this part of the problem: Set the private variable ALLOW_NESTMATE_ACCESS to true in MethodHandles.Lookup. But this would violate the 292 specification. On the other hand, in the present state of things, the compiler which compiles the reference to Inner::new needs to do analogous tricks with access$NNN functions to make the private <init> member of Inner be visible to Enclosing. For example, the invokedynamic instruction which needs to materialize the Inner::<new> member can be moved into a package-scope access$NNN function inside Inner. This is a risky workaround, because it does not provide for the corner case (if any) where a single bootstrap specifier needs to access MH constants from two or more nestmates: Then there is no unique nest-member that has full access to all the required constants. Suggested fix: Have the 292 and Lambda EGs agree to loosen access checks performed by Lookup and resolution of CONSTANT_MethodHandle constants, to allow nestmate access, just as the source language allows such access.
18-12-2012