Skip to content

Commit f00010a

Browse files
committed
Allow stepping through disassembly
This change adjusts JavaStackFrameEditorPresenter for ClassFileEditor, so that bytecode instructions are highlighted during debug stepping.
1 parent a4aa8d5 commit f00010a

File tree

5 files changed

+369
-5
lines changed

5 files changed

+369
-5
lines changed

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2228,6 +2228,20 @@ protected IJavaMethodBreakpoint createMethodBreakpoint(IJavaProject project, Str
22282228
protected IJavaMethodBreakpoint createMethodBreakpoint(String packageName, String cuName, String typeName, String methodName, String methodSignature, boolean entry, boolean exit) throws Exception {
22292229
IType type = getType(packageName, cuName, typeName);
22302230
assertNotNull("did not find type to install breakpoint in", type); //$NON-NLS-1$
2231+
return createMethodBreakpoint(type, methodName, methodSignature, entry, exit);
2232+
}
2233+
2234+
/**
2235+
* Creates a method breakpoint in a specified type.
2236+
*
2237+
* @param type the type in which the method breakpoint is set
2238+
* @param methodName method or <code>null</code> for all methods
2239+
* @param methodSignature JLS method signature or <code>null</code> for all methods with the given name
2240+
* @param entry whether to break on entry
2241+
* @param exit whether to break on exit
2242+
* @return method breakpoint
2243+
*/
2244+
protected IJavaMethodBreakpoint createMethodBreakpoint(IType type, String methodName, String methodSignature, boolean entry, boolean exit) throws Exception {
22312245
IMethod method= null;
22322246
if (methodSignature != null && methodName != null) {
22332247
if (type != null ) {
@@ -2240,7 +2254,6 @@ protected IJavaMethodBreakpoint createMethodBreakpoint(String packageName, Strin
22402254
return bp;
22412255
}
22422256

2243-
22442257
/**
22452258
* Creates a MethodBreakPoint on the method specified at the given path.
22462259
* Syntax:

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
import org.eclipse.jdt.debug.tests.refactoring.RenamePublicTypeUnitTests;
136136
import org.eclipse.jdt.debug.tests.sourcelookup.ArchiveSourceLookupTests;
137137
import org.eclipse.jdt.debug.tests.sourcelookup.Bug565462Tests;
138+
import org.eclipse.jdt.debug.tests.sourcelookup.ClassFileEditorHighlightingTest;
138139
import org.eclipse.jdt.debug.tests.sourcelookup.DefaultSourceContainerTests;
139140
import org.eclipse.jdt.debug.tests.sourcelookup.DirectorySourceContainerTests;
140141
import org.eclipse.jdt.debug.tests.sourcelookup.DirectorySourceLookupTests;
@@ -148,6 +149,7 @@
148149
import org.eclipse.jdt.debug.tests.sourcelookup.TypeResolutionTests;
149150
import org.eclipse.jdt.debug.tests.state.RefreshStateTests;
150151
import org.eclipse.jdt.debug.tests.ui.DebugHoverTests;
152+
import org.eclipse.jdt.debug.tests.ui.DebugSelectionTests;
151153
import org.eclipse.jdt.debug.tests.ui.DebugViewTests;
152154
import org.eclipse.jdt.debug.tests.ui.DetailPaneManagerTests;
153155
import org.eclipse.jdt.debug.tests.ui.HotCodeReplaceErrorDialogTest;
@@ -229,6 +231,8 @@ public AutomatedSuite() {
229231
addTest(new TestSuite(TypeResolutionTests.class));
230232
addTest(new TestSuite(JarSourceLookupTests.class));
231233
addTest(new TestSuite(Bug565462Tests.class));
234+
addTest(new TestSuite(DebugSelectionTests.class));
235+
addTest(new TestSuite(ClassFileEditorHighlightingTest.class));
232236

233237
// Variable tests
234238
addTest(new TestSuite(InstanceVariableTests.class));
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026, Daniel Schmid and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Daniel Schmid - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jdt.debug.tests.sourcelookup;
15+
16+
import java.io.IOException;
17+
import java.net.URI;
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.Locale;
23+
24+
import javax.tools.DiagnosticCollector;
25+
import javax.tools.JavaCompiler;
26+
import javax.tools.JavaCompiler.CompilationTask;
27+
import javax.tools.JavaFileObject;
28+
import javax.tools.SimpleJavaFileObject;
29+
import javax.tools.StandardJavaFileManager;
30+
import javax.tools.ToolProvider;
31+
32+
import org.eclipse.core.resources.IFile;
33+
import org.eclipse.core.resources.IProject;
34+
import org.eclipse.core.resources.IResource;
35+
import org.eclipse.core.runtime.IStatus;
36+
import org.eclipse.debug.core.ILaunchConfiguration;
37+
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
38+
import org.eclipse.jdt.core.IJavaProject;
39+
import org.eclipse.jdt.debug.core.IJavaStackFrame;
40+
import org.eclipse.jdt.debug.core.IJavaThread;
41+
import org.eclipse.jdt.debug.testplugin.JavaProjectHelper;
42+
import org.eclipse.jdt.debug.tests.TestUtil;
43+
import org.eclipse.jdt.debug.tests.ui.AbstractDebugUiTests;
44+
import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor;
45+
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
46+
import org.eclipse.jdt.ui.jarpackager.IJarExportRunnable;
47+
import org.eclipse.jdt.ui.jarpackager.JarPackageData;
48+
import org.eclipse.swt.custom.StyleRange;
49+
import org.eclipse.swt.custom.StyledText;
50+
import org.eclipse.swt.custom.StyledTextContent;
51+
import org.eclipse.ui.IWorkbenchWindow;
52+
import org.eclipse.ui.PlatformUI;
53+
import org.junit.Assume;
54+
55+
public class ClassFileEditorHighlightingTest extends AbstractDebugUiTests {
56+
57+
private static final long TIMEOUT = 10_000L;
58+
59+
private static final String CLASS_NAME = "NoSources";
60+
private static final String CLASS_CONTENTS = """
61+
public class NoSources {
62+
private static int i = 0;
63+
public static void main(String[] args) {
64+
i++;
65+
System.out.println(i);
66+
}
67+
}
68+
""";
69+
70+
public ClassFileEditorHighlightingTest(String name) {
71+
super(name);
72+
}
73+
74+
@Override
75+
protected boolean enableUIEventLoopProcessingInWaiter() {
76+
return true;
77+
}
78+
79+
@Override
80+
public void tearDown() throws Exception {
81+
try {
82+
closeAllEditors();
83+
} finally {
84+
super.tearDown();
85+
}
86+
}
87+
88+
public void testDisplaySourceWithClassFileEditorHighlightsLine() throws Exception {
89+
IJavaProject project = createProjectWithNoSources(CLASS_NAME, CLASS_CONTENTS, true);
90+
IJavaThread thread = null;
91+
try {
92+
ILaunchConfiguration config = createLaunchConfiguration(project, CLASS_NAME);
93+
createLineBreakpoint(project.findType(CLASS_NAME), 4);
94+
thread = launchToBreakpoint(config);
95+
96+
stepOver((IJavaStackFrame) thread.getTopStackFrame());
97+
expectHighlightedText(" 8 getstatic java.lang.System.out : java.io.PrintStream [13]", TIMEOUT);
98+
99+
stepOver((IJavaStackFrame) thread.getTopStackFrame());
100+
expectHighlightedText(" 17 return", TIMEOUT);
101+
} finally {
102+
terminateAndRemove(thread);
103+
expectNothingHighlighted(TIMEOUT);
104+
removeAllBreakpoints();
105+
project.getProject().delete(true, null);
106+
}
107+
}
108+
109+
public void testConstructorInPackage() throws Exception {
110+
IJavaProject project = createProjectWithNoSources("ClassOne", """
111+
public class ClassOne {
112+
113+
public static void main(String[] args) {
114+
ClassOne co = new ClassOne();
115+
co.method1();
116+
}
117+
118+
public void method1() {
119+
method2();
120+
}
121+
122+
public void method2() {
123+
method3();
124+
}
125+
126+
public void method3() {
127+
System.out.println("ClassOne, method3");
128+
}
129+
}
130+
""");
131+
createMethodBreakpoint(project.findType("ClassOne"), "<init>", "()V", true, false);
132+
IJavaThread thread = null;
133+
try {
134+
ILaunchConfiguration config = createLaunchConfiguration(project, "ClassOne");
135+
thread = launchToBreakpoint(config);
136+
expectHighlightedText(" 0 aload_0 [this]", TIMEOUT);
137+
} finally {
138+
terminateAndRemove(thread);
139+
removeAllBreakpoints();
140+
project.getProject().delete(true, null);
141+
}
142+
}
143+
144+
public void testDisplaySourceWithClassFileEditorHighlightsLineInConstructor() throws Exception {
145+
IJavaProject project = createProjectWithNoSources("MethodCall", """
146+
public class MethodCall {
147+
148+
private int i;
149+
private int sum = 0;
150+
151+
public static void main(String[] args) {
152+
MethodCall mc = new MethodCall();
153+
mc.go();
154+
}
155+
156+
public void go() {
157+
calculateSum();
158+
}
159+
160+
protected void calculateSum() {
161+
sum += i;
162+
}
163+
}
164+
""");
165+
createMethodBreakpoint(project.findType("MethodCall"), "<init>", "()V", true, false);
166+
IJavaThread thread = null;
167+
try {
168+
ILaunchConfiguration config = createLaunchConfiguration(project, "MethodCall");
169+
thread = launchToBreakpoint(config);
170+
expectHighlightedText(" 0 aload_0 [this]", TIMEOUT);
171+
} finally {
172+
terminateAndRemove(thread);
173+
removeAllBreakpoints();
174+
project.getProject().delete(true, null);
175+
}
176+
}
177+
178+
public void testClassFileWithoutDebuggingInformation() throws Exception {
179+
IJavaProject project = createProjectWithNoSources(CLASS_NAME, CLASS_CONTENTS);
180+
IJavaThread thread = null;
181+
try {
182+
ILaunchConfiguration config = createLaunchConfiguration(project, CLASS_NAME);
183+
ILaunchConfigurationWorkingCopy workingCopy = config.getWorkingCopy();
184+
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
185+
config = workingCopy.doSave();
186+
thread = launchToBreakpoint(config);
187+
188+
String[] expectedHighlights = """
189+
0 getstatic NoSources.i : int [7]
190+
3 iconst_1
191+
4 iadd
192+
5 putstatic NoSources.i : int [7]
193+
8 getstatic java.lang.System.out : java.io.PrintStream [13]
194+
11 getstatic NoSources.i : int [7]
195+
14 invokevirtual java.io.PrintStream.println(int) : void [19]
196+
17 return
197+
""".split(System.lineSeparator());
198+
199+
for (int i = 0; i < expectedHighlights.length; i++) {
200+
expectHighlightedText(expectedHighlights[i], TIMEOUT);
201+
202+
if (i < expectedHighlights.length - 1) {
203+
stepOver((IJavaStackFrame) thread.getTopStackFrame());
204+
}
205+
}
206+
} finally {
207+
terminateAndRemove(thread);
208+
removeAllBreakpoints();
209+
project.getProject().delete(true, null);
210+
}
211+
}
212+
213+
private IJavaProject createProjectWithNoSources(String className, String contents) throws Exception {
214+
return createProjectWithNoSources(className, contents, false);
215+
}
216+
217+
private IJavaProject createProjectWithNoSources(String className, String contents, boolean generateLineInfo) throws Exception {
218+
IJavaProject javaProject = JavaProjectHelper.createJavaProject("ClassFileEditorHighlightingTest", JavaProjectHelper.BIN_DIR);
219+
IProject project = javaProject.getProject();
220+
project.getFolder(LAUNCHCONFIGURATIONS).create(true, true, null);
221+
222+
IFile classFile = project.getFile(className + ".class");
223+
224+
String debug = generateLineInfo ? "lines" : "none";
225+
compileWithJavac(className, contents, List.of("-g:" + debug, "-d", project.getLocation().toString(), "--release", "8"));
226+
classFile.refreshLocal(IResource.DEPTH_ONE, null);
227+
228+
IFile lib = project.getFile("lib.jar");
229+
jarClassFile(lib, classFile);
230+
231+
JavaProjectHelper.addLibrary(javaProject, lib.getFullPath());
232+
waitForBuild();
233+
return javaProject;
234+
}
235+
236+
private static void jarClassFile(IFile jar, IFile classFile) {
237+
JarPackageData data = new JarPackageData();
238+
data.setJarLocation(jar.getLocation());
239+
data.setBuildIfNeeded(true);
240+
data.setOverwrite(true);
241+
data.setElements(new Object[] { classFile });
242+
data.setExportClassFiles(true);
243+
244+
IStatus status = callInUi(() -> {
245+
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
246+
IJarExportRunnable op = data.createJarExportRunnable(window.getShell());
247+
window.run(false, false, op);
248+
return op.getStatus();
249+
});
250+
if (status.getSeverity() == IStatus.ERROR) {
251+
fail("Creating jar failed: " + status.getMessage());
252+
}
253+
}
254+
255+
private static void expectHighlightedText(String expectedHighlightedText, long timeout) throws InterruptedException {
256+
long s = System.currentTimeMillis();
257+
while (System.currentTimeMillis() - s < timeout) {
258+
List<String> highlighted = callInUi(ClassFileEditorHighlightingTest::getClassEditorHighlightedText);
259+
if (List.of(expectedHighlightedText).equals(highlighted)) {
260+
break;
261+
}
262+
TestUtil.runEventLoop();
263+
Thread.sleep(50L);
264+
}
265+
List<String> highlighted = callInUi(ClassFileEditorHighlightingTest::getClassEditorHighlightedText);
266+
assertEquals("Timed out while waiting on highlighting", Arrays.asList(expectedHighlightedText), highlighted);
267+
}
268+
269+
private static void expectNothingHighlighted(long timeout) throws InterruptedException {
270+
long s = System.currentTimeMillis();
271+
while (System.currentTimeMillis() - s < timeout) {
272+
List<String> highlighted = callInUi(ClassFileEditorHighlightingTest::getClassEditorHighlightedText);
273+
if (highlighted.isEmpty()) {
274+
break;
275+
}
276+
TestUtil.runEventLoop();
277+
Thread.sleep(50L);
278+
}
279+
List<String> highlighted = callInUi(ClassFileEditorHighlightingTest::getClassEditorHighlightedText);
280+
assertEquals("Timed out while waiting on highlighting", List.of(), highlighted);
281+
}
282+
283+
private static List<String> getClassEditorHighlightedText() {
284+
List<String> highlighted = new ArrayList<>();
285+
StyledText noSourceTextWidget = null;
286+
ClassFileEditor editor = (ClassFileEditor) getActivePage().getActiveEditor();
287+
if (editor != null) {
288+
noSourceTextWidget = editor.getNoSourceTextWidget();
289+
}
290+
if (noSourceTextWidget != null) {
291+
StyleRange[] styleRanges = noSourceTextWidget.getStyleRanges();
292+
StyledTextContent content = noSourceTextWidget.getContent();
293+
for (int i = 0; i < styleRanges.length; ++i) {
294+
String highlightedText = content.getTextRange(styleRanges[0].start, styleRanges[0].length);
295+
highlighted.add(highlightedText);
296+
}
297+
}
298+
return highlighted;
299+
}
300+
301+
private static void compileWithJavac(String className, String source, List<String> compilerOptions) {
302+
JavaFileObject fileObject = new SourceJavaFileObject(className, source);
303+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
304+
Assume.assumeNotNull(compiler);
305+
306+
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, StandardCharsets.UTF_8);
307+
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
308+
CompilationTask task = compiler.getTask(null, fileManager, collector, compilerOptions, null, List.of(fileObject));
309+
Boolean result = task.call();
310+
assertTrue(String.valueOf(collector.getDiagnostics()), result);
311+
}
312+
313+
private static class SourceJavaFileObject extends SimpleJavaFileObject {
314+
315+
private final String code;
316+
317+
protected SourceJavaFileObject(String name, String code) {
318+
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
319+
this.code = code;
320+
}
321+
322+
@Override
323+
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
324+
return code;
325+
}
326+
}
327+
}

0 commit comments

Comments
 (0)