JDK-8037935 : Javac: final local String var referenced in binary/unary op in lambda produces code that does not verify
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2014-03-19
  • Updated: 2016-05-27
  • Resolved: 2014-04-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u20Fixed 9 teamFixed
Description
FULL PRODUCT VERSION :
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

EXTRA RELEVANT SYSTEM CONFIGURATION :
Windows 7 Professional, Service Pack 1

A DESCRIPTION OF THE PROBLEM :
The class file produced by javac for the attached program makes an illegal access to uninitialized local variable 2.  The verifier understandably objects.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the attached program.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
This is an experimental program to help me understand lambda expressions.  If there is an error in my program, I would expect the error to be reported by javac, rather than by the byte-code verifier.  

I do not believe there is an error in this program.  I believe the program should run and the closure should be 
ACTUAL -
The program does not run because the verifier rejects the class file.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
cygdrive/c/applications/java/jdk1.8.0/bin/java -classpath . MySecondLambda >MySecondLambda.run
java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    MySecondLambda.doInstanceMethod()V @1: aload_2
  Reason:
    Type top (current frame, locals[2]) is not assignable to reference type
  Current Frame:
    bci: @1
    flags: { }
    locals: { 'MySecondLambda' }
    stack: { 'MySecondLambda' }
  Bytecode:
    0000000: 2a2c ba00 1600 004e 2d12 17b9 0018 0200
    0000010: 2d12 19b9 0018 0200 2d12 1ab9 0018 0200
    0000020: b1

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2688)
        at java.lang.Class.getMethod0(Class.java:2937)
        at java.lang.Class.getMethod(Class.java:1771)
        at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Exception in thread "main" Makefile:21: recipe for target `MySecondLambda.run' failed


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;


public class MySecondLambda {
  final int CONSTANT_INSTANCE_INT_VALUE = 5678765;
  final String CONSTANT_INSTANCE_STRING_VALUE = "xyzyx";

  public static void main(String[] args) {
    List<String> names = Arrays.asList("1", "21", "376");
    Stream<Integer> lengths = names.stream().map(name -> name.length());
    System.out.println("This is the result of mapping names to lengths");
    lengths.forEach(a -> System.out.println(a));

    MySecondLambda mfl = new MySecondLambda();
    mfl.doInstanceMethod();
  }

  void doInstanceMethod() {
    final int CONSTANT_INT_VALUE = 232323;
    final String CONSTANT_STRING_VALUE = "mwmwm";

    /*
    Consumer<Integer> consumeInts = y -> {
      System.out.println("instance constant: " + CONSTANT_INSTANCE_INT_VALUE);
      System.out.println("   local constant: " + CONSTANT_INT_VALUE);
      System.out.println("          int arg: " + y);
      System.out.println("              sum: " +
                         (y +
                          CONSTANT_INSTANCE_INT_VALUE + CONSTANT_INT_VALUE));
    };

    consumeInts.accept(37);
    consumeInts.accept(54);
    consumeInts.accept(21);
    */

    Consumer<String> consumeStrings = s -> {
      System.out.println("instance constant: " +
                         CONSTANT_INSTANCE_STRING_VALUE);
      System.out.println("   local constant: " + CONSTANT_STRING_VALUE);
      System.out.println("       string arg: " + s);
      System.out.println("       catenation: " +
                         (s + ":" +
                          CONSTANT_INSTANCE_INT_VALUE + ":" +
                          CONSTANT_INT_VALUE));
    };

    consumeStrings.accept("bob");
    consumeStrings.accept("mary");
    consumeStrings.accept("harry");
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None found.


Comments
Performance Team: the change in space used during a compilation should be measured
03-04-2014

Cause of the problem is this optimization in Attr.visitBinary -- // Remove constant types from arguments to // conserve space. The parser will fold concatenations // of string literals; the code here also // gets rid of intermediate results when some of the // operands are constant identifiers. if (tree.lhs.type.tsym == syms.stringType.tsym) { tree.lhs.type = syms.stringType; } if (tree.rhs.type.tsym == syms.stringType.tsym) { tree.rhs.type = syms.stringType; } It smashes the needed info. The optimization can either be removed or the underlying var accessed.
27-03-2014

The problem is also specific to String. Changing it to "int" works fine. In LambdaToMethod.visitIdent, the test "tree.type.constValue() == null" is false in the failure case, indicating that the wrong type is on the ident tree, or the test is wrong.
27-03-2014

Ah! The lambda method uses the fact that it is a constant, doesn't use/need a passed in value. The bad generated load is not considering this. -g:vars loads it so that it will be in the slot.
27-03-2014

The test case can be reduced to -- interface MyConsumer { void accept(); } public class MySecondLambda { public static void main(String[] args) { final String CONSTANT_STRING_VALUE = "mwmwm"; MyConsumer consumeStrings = () -> { System.out.println(" local constant: " + CONSTANT_STRING_VALUE); }; } } With javap output of -- stack=1, locals=3, args_size=1 0: aload_1 1: invokedynamic #2, 0 // InvokeDynamic #0:accept:(Ljava/lang/String;)LMyConsumer; 6: astore_2 7: return Strangely, compiling -g:vars generates correct and functioning code -- stack=1, locals=3, args_size=1 0: ldc #2 // String mwmwm 2: astore_1 3: aload_1 4: invokedynamic #3, 0 // InvokeDynamic #0:accept:(Ljava/lang/String;)LMyConsumer; 9: astore_2 10: return LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; 3 8 1 CONSTANT_STRING_VALUE Ljava/lang/String; 10 1 2 consumeStrings LMyConsumer;
27-03-2014

this is a lambda code generation issue, reassigning to Robert
24-03-2014