[PATCH] 8129740: Runtime exception when passing lambda in inner class constructor

Srikanth srikanth.adayapalam at oracle.com
Thu Oct 8 09:29:51 UTC 2015


I seemed to have missed this thread as I was on vacation, but have been
working on https://bugs.openjdk.java.net/browse/JDK-8129740. ATM, my belief
is that this is valid code and should be accepted and run without any 
errors.

I have captured some notes in 
https://bugs.openjdk.java.net/browse/JDK-8129740
that show the reasoning.

Thanks!
Srikanth

On Thursday 08 October 2015 02:45 PM, bsrbnd wrote:
> Any comment about this issue and the patch below?
> Thanks,
>
> bsrbnd
>
> 2015-09-17 13:46 GMT+02:00 bsrbnd <bsrbnd at gmail.com>:
>> Hi,
>>
>> Just two precisions:
>>
>> 1) If you run the compiled example, a "java.lang.VerifyError
>> (uninitializedThis)" is thrown, not a runtime exception (sorry for
>> that mistake):
>>
>> Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
>> Exception Details:
>>    Location:
>>      Bug$Inner.<init>(LBug;)V @3: invokedynamic
>>    Reason:
>>      Type uninitializedThis (current frame, stack[2]) is not assignable
>> to 'Bug$Inner'
>>
>>
>> 2) The output of javap on "Inner" class gives us a good explaination.
>> The lambda expression is translated into an instance method (to access
>> instance members of the class and outer-class) and thus "this" is used
>> by "invokedynamic" before calling the constructor which leads to the
>> VerifiyError:
>>
>> public class Bug$Inner {
>>    java.lang.Object vi;
>>    final Bug this$0;
>>    public Bug$Inner(Bug, java.lang.Runnable);
>>    public Bug$Inner(Bug, java.lang.Object);
>>    public Bug$Inner(Bug);
>>    private void lambda$new$0();
>> }
>>
>>    public Bug$Inner(Bug);
>>      Code:
>>         0: aload_0
>>         1: aload_1
>>         2: aload_0
>>         3: invokedynamic #8,  0              // InvokeDynamic
>> #0:run:(LBug$Inner;)Ljava/lang/Runnable;
>>         8: invokespecial #9                  // Method
>> "<init>":(LBug;Ljava/lang/Runnable;)V
>>        11: return
>>
>> If the lambda expression doesn't access instance members of the class
>> (but static members or instance members of other classes), the lamda
>> expression is translated into a static method an the "invokedynamic"
>> doesn't need "this". For example:
>>   this(()->System.out.println(new Other().o));
>>
>> gives:
>>
>> public class Bug$Inner {
>>    java.lang.Object vi;
>>    final Bug this$0;
>>    public Bug$Inner(Bug, java.lang.Runnable);
>>    public Bug$Inner(Bug, java.lang.Object);
>>    public Bug$Inner(Bug);
>>    private static void lambda$new$0();
>> }
>>
>>    public Bug$Inner(Bug);
>>      Code:
>>         0: aload_0
>>         1: aload_1
>>         2: invokedynamic #8,  0              // InvokeDynamic
>> #0:run:()Ljava/lang/Runnable;
>>         7: invokespecial #9                  // Method
>> "<init>":(LBug;Ljava/lang/Runnable;)V
>>        10: return
>>
>> Thus, jls 8.8.7.1 seems to be respected (the error appears to be
>> "right" in issue 8129740) and then javac should (I think) give an
>> error message as shown in the patch below.
>>
>> Regards,
>> bsrbnd
>>
>> 2015-09-14 15:11 GMT+02:00 bsrbnd <bsrbnd at gmail.com>:
>>> Hi,
>>>
>>> As explained in issue 8129740, the following code produces a runtime
>>> exception ("uninitialized this"):
>>>
>>> public class Bug {
>>>      Object vb=new Object();
>>>
>>>      public class Inner {
>>>          Object vi=new Object();
>>>
>>>          public Inner(Runnable r) {r.run();}
>>>          public Inner(Object o) {System.out.println(o);}
>>>          public Inner() {
>>>              this(()->System.out.println(vb)); // KO (bug 8129740
>>> runtime exception)
>>>          }
>>>      }
>>>      public static void main(String[] args) {
>>>          new Bug().new Inner();
>>>      }
>>> }
>>>
>>> class Other {
>>>      public Object o=new Object();
>>> }
>>>
>>> With respect to JLS 8.8.7.1 (p. 264, l. 1-4), I think (if I've not
>>> missed anything) that this example should probably run without
>>> exception because "vb" is not within "Inner" class but in the
>>> outer-class.
>>>
>>> For example, all the following statements compile and run according to
>>> jls 8.8.7.1:
>>>   this(vb);
>>>   this(()->System.out.println(new Other().o));
>>>   this(new Runnable() {public void run() {System.out.println(vb);}});
>>>
>>> And the following ones doesn't compile, according to jls:
>>>   this(vi);
>>>   this(()->System.out.println(vi));
>>>   this(new Runnable() {public void run() {System.out.println(vi);}});
>>>
>>> But, if the runtime exception is "right" (as said in issue 8129740),
>>> we can detect in the attribute phase the special case of an outer
>>> member access within a lambda expression in a this/super call as shown
>>> in the following patch.
>>> Method Attr.visitLambda() keeps a track in the AttrContext if we are
>>> in a this/super call and method Attr.visitIdent() produces an error
>>> message for that case (notice that "noOuterThisPath" on line 3209
>>> never changes).
>>>
>>> Regards,
>>> bsrbnd
>>>
>>> diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
>>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
>>> --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
>>> +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
>>> @@ -2327,6 +2327,7 @@
>>>           }
>>>           //create an environment for attribution of the lambda expression
>>>           final Env<AttrContext> localEnv = lambdaEnv(that, env);
>>> +        localEnv.info.isLambdaInSelfCall = localEnv.info.isSelfCall;
>>>           boolean needsRecovery =
>>>                   resultInfo.checkContext.deferredAttrContext().mode ==
>>> DeferredAttr.AttrMode.CHECK;
>>>           try {
>>> @@ -3207,6 +3208,7 @@
>>>           //     (`noOuterThisPath').
>>>           Env<AttrContext> symEnv = env;
>>>           boolean noOuterThisPath = false;
>>> +        boolean isLambdaInSelfCall = symEnv.info.isLambdaInSelfCall;
>>>           if (env.enclClass.sym.owner.kind != PCK && // we are in an inner class
>>>               sym.kind.matches(KindSelector.VAL_MTH) &&
>>>               sym.owner.kind == TYP &&
>>> @@ -3238,7 +3240,7 @@
>>>           // In a constructor body,
>>>           // if symbol is a field or instance method, check that it is
>>>           // not accessed before the supertype constructor is called.
>>> -        if ((symEnv.info.isSelfCall || noOuterThisPath) &&
>>> +        if ((symEnv.info.isSelfCall || isLambdaInSelfCall ||
>>> noOuterThisPath) &&
>>>               sym.kind.matches(KindSelector.VAL_MTH) &&
>>>               sym.owner.kind == TYP &&
>>>               (sym.flags() & STATIC) == 0) {
>>> diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java
>>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java
>>> --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java
>>> +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java
>>> @@ -72,6 +72,8 @@
>>>        *  Is this an attribution environment for an instance creation expression?
>>>        */
>>>       boolean isNewClass = false;
>>> +
>>> +    boolean isLambdaInSelfCall = false;
>>>
>>>       /** Are arguments to current function applications boxed into an
>>> array for varargs?
>>>        */
>>> @@ -112,6 +114,7 @@
>>>           info.isSpeculative = isSpeculative;
>>>           info.isAnonymousDiamond = isAnonymousDiamond;
>>>           info.isNewClass = isNewClass;
>>> +        info.isLambdaInSelfCall = isLambdaInSelfCall;
>>>           return info;
>>>       }



More information about the compiler-dev mailing list