Skip to content

Fix breakpoints in classes with inner/anonymous classes #314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 212 additions & 86 deletions src/main/java/org/javacs/debug/JavaDebugServer.java

Large diffs are not rendered by default.

Binary file added src/test/examples/debug/DeepVariables.class
Binary file not shown.
14 changes: 14 additions & 0 deletions src/test/examples/debug/DeepVariables.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
public class DeepVariables {
public static void main(String[] args) {
new DeepVariables().run();
}

public void run() {
Inner object = new Inner();
System.out.println(object.value);
}

private static class Inner {
public final int value = 42;
}
}
Binary file added src/test/examples/debug/InnerClasses$Inner$1.class
Binary file not shown.
Binary file added src/test/examples/debug/InnerClasses$Inner.class
Binary file not shown.
Binary file added src/test/examples/debug/InnerClasses.class
Binary file not shown.
20 changes: 20 additions & 0 deletions src/test/examples/debug/InnerClasses.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
public class InnerClasses {
public static void main(String[] args) {
System.out.println("at main");
new Inner().run();
}

// An inner class:
private static class Inner {
public void run() {
System.out.println("at Inner.run");
// An anonymous class:
new Runnable() {
@Override
public void run() {
System.out.println("at Runnable.run");
}
}.run();
}
}
}
119 changes: 109 additions & 10 deletions src/test/java/org/javacs/JavaDebugServerTest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package org.javacs;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import org.javacs.debug.*;
import org.javacs.debug.proto.*;
import org.junit.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.logging.Logger;
import org.javacs.debug.*;
import org.javacs.debug.proto.*;
import org.junit.Test;

public class JavaDebugServerTest {
Path workingDirectory = Paths.get("src/test/examples/debug");
@@ -42,12 +46,15 @@ public void breakpoint(BreakpointEventBody evt) {
if (evt.breakpoint.verified) {
LOG.info(
String.format(
"Breakpoint at %s:%d is verified", evt.breakpoint.source.path, evt.breakpoint.line));
"Breakpoint at %s:%d is verified",
evt.breakpoint.source.path, evt.breakpoint.line));
} else {
LOG.info(
String.format(
"Breakpoint at %s:%d cannot be verified because %s",
evt.breakpoint.source.path, evt.breakpoint.line, evt.breakpoint.message));
evt.breakpoint.source.path,
evt.breakpoint.line,
evt.breakpoint.message));
}
}

@@ -59,9 +66,18 @@ public RunInTerminalResponseBody runInTerminal(RunInTerminalRequest req) {

public void launchProcess(String mainClass) throws IOException, InterruptedException {
var command =
List.of("java", "-Xdebug", "-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y", mainClass);
List.of(
"java",
"-Xdebug",
"-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y",
mainClass);
LOG.info("Launch " + String.join(", ", command));
process = new ProcessBuilder().command(command).directory(workingDirectory.toFile()).inheritIO().start();
process =
new ProcessBuilder()
.command(command)
.directory(workingDirectory.toFile())
.inheritIO()
.start();
java.lang.Thread.sleep(1000);
}

@@ -109,7 +125,9 @@ public void setBreakpoint() throws IOException, InterruptedException {
var stack = server.stackTrace(requestTrace);
System.out.println("Thread main:");
for (var frame : stack.stackFrames) {
System.out.println(String.format("\t%s:%d (%s)", frame.name, frame.line, frame.source.path));
System.out.println(
String.format(
"\t%s:%d (%s)", frame.name, frame.line, frame.source.path));
}
// Get variables
var requestScopes = new ScopesArguments();
@@ -199,15 +217,17 @@ public void printCollections() throws IOException, InterruptedException {
var stack = server.stackTrace(requestTrace);
System.out.println("Thread main:");
for (var frame : stack.stackFrames) {
System.out.println(String.format("\t%s:%d (%s)", frame.name, frame.line, frame.source.path));
System.out.println(
String.format(
"\t%s:%d (%s)", frame.name, frame.line, frame.source.path));
}
// Get variables
var requestScopes = new ScopesArguments();
requestScopes.frameId = stack.stackFrames[0].id;
var scopes = server.scopes(requestScopes).scopes;
// Get locals
var requestLocals = new VariablesArguments();
requestLocals.variablesReference = scopes[0].variablesReference;
requestLocals.variablesReference = scopes[1].variablesReference;
var locals = server.variables(requestLocals).variables;
System.out.println("Locals:");
for (var v : locals) {
@@ -220,5 +240,84 @@ public void printCollections() throws IOException, InterruptedException {
process.waitFor();
}

@Test
public void deepVariables() throws IOException, InterruptedException {
launchProcess("DeepVariables");
attach(5005);
setBreakpoint("DeepVariables", 8);
server.configurationDone();
stoppedEvents.take();

// Find the main thread
org.javacs.debug.proto.Thread mainThread = null;
for (var t : server.threads().threads) {
if (t.name.equals("main")) {
mainThread = t;
}
}
assertThat(mainThread, notNullValue());

// Get the stack trace
var requestTrace = new StackTraceArguments();
requestTrace.threadId = mainThread.id;
var stack = server.stackTrace(requestTrace);

// Get variables
var requestScopes = new ScopesArguments();
requestScopes.frameId = stack.stackFrames[0].id;
var scopes = server.scopes(requestScopes).scopes;

// Get locals
var requestLocals = new VariablesArguments();
requestLocals.variablesReference = scopes[1].variablesReference;
var locals = server.variables(requestLocals).variables;

// Find an object value
Variable objectVariable = null;
for (var v : locals) {
if (v.name.equals("object")) {
objectVariable = v;
}
}
assertThat(objectVariable, notNullValue());

// Get an object field
var requestObject = new VariablesArguments();
requestObject.variablesReference = objectVariable.variablesReference;
var fields = server.variables(requestObject).variables;

// Inspect an object field
Variable fieldVariable = null;
for (var v : fields) {
if (v.name.equals("value")) {
fieldVariable = v;
}
}
assertThat(fieldVariable, notNullValue());
assertThat(fieldVariable.value, equalTo("42"));

// Wait for process to exit
server.continue_(new ContinueArguments());
process.waitFor();
}

@Test
public void setBreakpointInnerClasses() throws IOException, InterruptedException {
launchProcess("InnerClasses");
// Attach to the process
attach(5005);
// Set breakpoints
setBreakpoint("InnerClasses", 3); // at main
setBreakpoint("InnerClasses", 10); // at Inner.run
setBreakpoint("InnerClasses", 15); // at Runnable.run
server.configurationDone();
// Should stop 3 times
for (int i = 0; i < 3; i++) {
stoppedEvents.take();
server.continue_(new ContinueArguments());
}
process.waitFor();
}

private static final Logger LOG = Logger.getLogger("main");
}