Java Debug Interface, Lambdas and Line Numbers
Asked Answered
A

1

11

I am having some problems updating a debugger to work with Java 8. Consider the following program for example:

public class Lam {
    public static void main(String[] args) {
        java.util.function.Function<Integer, Integer> square =
            x -> {
            int result = 0;
            for (int i=0;
                 i<x;
                 i++)
                result++;
            return result;
        };
        System.out.println(square.apply(5));
    }
}

As expected, Java 8 compiles the lambda to something like this:

> javap -c -p -v -s -constants Lam
Classfile Lam.class
...
  private static java.lang.Integer lambda$main$0(java.lang.Integer);
...
Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_1
...
LineNumberTable:
    line 5: 0
    line 6: 2
    line 7: 4
    line 9: 12
    line 8: 15
    line 10: 21

This looks pretty much like normal code. However, I am trying to use the Java Debugger Interface (JDI) to intercept every step of the program. The fist thing that goes wrong is when I handle the ClassPrepareEvent event corresponding to the lambda class. Asking the event.referenceType() gives me something like Lam$$Lambda$1.1464642111 which is cool. But then calling .allLineLocations() on the .referenceType() gives a AbsentInformationException, which seems at odds with the LineNumberTable in the compiled file.

It looks like stepping through lambda bodies in Java 8 is possible. But does anyone know how it can be done in JDI?

Updates:

  • when .allLineLocations is called on the Lam class, it does reflect all of these line numbers.
  • when a JDI Event happens within the lambda class (e.g. from stepping), the .sourceName() of the location throws an AbsentInformationException
  • it looks like jdk.internal.org.objectweb.asm.* is doing a bunch of stuff related to copying the lambda
  • I'm not sure if the map from source lines to bytecodes is kept in Java, or in the JDI

So my working hypothesis is that when the lambda's class is created at runtime, the JDI needs to do something to recognize that the new class's bytecode is coming from the old class's bytecode (which is in turn coming from from Lam.java). I don't know enough about the internal representation of java.lang.Class or com.sun.jdi.ClassType to know where to begin.

Why am I trying to do this:

Aimo answered 29/3, 2014 at 19:34 Comment(0)
H
7

You seem to be confusing the compiled class with the runtime generated lambda class. The latter contains only the glue that connects the functional interface with the implementation in the compiler class lambda method -- there isn't anything here you want to step through, with the possible exception of just the method name with no source. There is no sourceName for the lambda class because there is no source. The ASM code is building the generated lambda class. Map from bytecode location to source lines is in the class file.

Haggar answered 31/3, 2014 at 21:3 Comment(1)
This was helpful and caused me to rethink what was going on. I was throwing out the baby with the bathwater by not stepping through any method with "$" in its title, which I previously used to hide "access$" methods. Now that I fixed this it works great! goo.gl/VwFzINAimo

© 2022 - 2024 — McMap. All rights reserved.