JDK-8054220 : Debugger doesn't show variables *outside* lambda
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8u11
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: linux_ubuntu
  • CPU: x86_64
  • Submitted: 2014-08-02
  • Updated: 2015-09-29
  • Resolved: 2015-03-26
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
8u60Fixed 9 b59Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04 LTS
Release:	14.04
Codename:	trusty


A DESCRIPTION OF THE PROBLEM :
Basically if you're debugging inside a lambda, you can't inspect any variables outside of it. This is happening even if said variables are marked final, and regardless of whether they're being accessed inside the lambda.
The error message is : Cannot find local variable '...' if I try to add such variable to a Watch.

An example:
 final int outside = 0;
final Map<Integer, Integer> map = new HashMap<>();
map.put(1, 2);
map.entrySet().stream().forEach(entry ->  {
  System.out.println(outside); // break here, can't inspect the value of "outside"
  return entry.getValue();
}).sum();

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
 final int outside = 0;
final Map<Integer, Integer> map = new HashMap<>();
map.put(1, 2);
map.entrySet().stream().forEach(entry ->  {
  System.out.println(outside); // break here, can't inspect the value of "outside"
  return entry.getValue();
}).sum();

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I should see a variable outside in a debugger on a line starting with System.out
ACTUAL -
Variable is not available.

REPRODUCIBILITY :
This bug can be reproduced always.


Comments
http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009248.html
20-01-2015

The example given above does not compile, but here is some slightly different code that has some more variations on this: public void run(int input) { final int outside = 0; final Map<Integer, Integer> map = new HashMap<>(); final Set<Integer> set = new HashSet<>(); map.put(1, 2); map.values().stream().forEach(value -> { set.add(value + input + outside); }); } The lambda in the above code will be compiled into the following byte code: private static void lambda$run$0(java.util.Set, int, java.lang.Integer); descriptor: (Ljava/util/Set;ILjava/lang/Integer;)V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=3, locals=3, args_size=3 0: aload_0 1: aload_2 2: invokevirtual #15 // Method java/lang/Integer.intValue:()I 5: iload_1 6: iadd 7: iconst_0 8: iadd 9: invokestatic #9 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 12: invokeinterface #16, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z 17: pop 18: return LineNumberTable: line 34: 0 line 35: 18 LocalVariableTable: Start Length Slot Name Signature 0 19 2 value Ljava/lang/Integer; In a debugger, the only variable that shows up when breaking in the lambda is 'value'. This is the third method parameter to the generated lambda method (the Integer). The reason it shows up in the debugger is that it has a LocalVariableTable slot which gives it a name. Let's look at the variable 'outside', then. This is actually a compile-time constant set to 0 and will be treated as such by the lambda. There is no reference from the lambda to this variable, only a reference to the constant 0 (see byte code index 7). This will not be possible to see in a debugger (it will also be uninteresting). Let's look at 'input' instead. This is not a compile-time constant and more interesting to the discussion. This value will be sent to the lambda as the second method parameter (the int). Why does this not show up in the debugger, then? Because it has no LocalVariableTable slot in the lambda method. It looks like this should be simple to add. The same reasoning as for 'input' works for the 'set' variable. It also has no LocalVariableTable slot, but it should be possible to add one. The most challenging of the variables is 'map'. Since it is not used in the lambda body, there is no reference to it in the generated lambda method. I see no simple way to make this show up in a debugger. One possibility could be for the debugger to recognize the lambda$run$0() method as a lambda and go looking on the stack for the enclosing frame. That frame's local variables could then be displayed together with the lambda$run$0() frame's local variables. That of course only works if the lambda is being used on the same stack - it could be used in a completely different context.
19-01-2015