Skip to content

Commit 8f038cb

Browse files
danthe1sttrancexpress
authored andcommitted
Allow stepping through disassembly
This change adjusts JavaStackFrameEditorPresenter for ClassFileEditor, so that bytecode instructions are highlighted during debug stepping.
1 parent a4aa8d5 commit 8f038cb

File tree

5 files changed

+432
-5
lines changed

5 files changed

+432
-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: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
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+
removeAllBreakpoints();
104+
project.getProject().delete(true, null);
105+
}
106+
}
107+
108+
public void testConstructorInPackage() throws Exception {
109+
IJavaProject project = createProjectWithNoSources("ClassOne", """
110+
public class ClassOne {
111+
112+
public static void main(String[] args) {
113+
ClassOne co = new ClassOne();
114+
co.method1();
115+
}
116+
117+
public void method1() {
118+
method2();
119+
}
120+
121+
public void method2() {
122+
method3();
123+
}
124+
125+
public void method3() {
126+
System.out.println("ClassOne, method3");
127+
}
128+
}
129+
""");
130+
createMethodBreakpoint(project.findType("ClassOne"), "<init>", "()V", true, false);
131+
IJavaThread thread = null;
132+
try {
133+
ILaunchConfiguration config = createLaunchConfiguration(project, "ClassOne");
134+
thread = launchToBreakpoint(config);
135+
expectHighlightedText(" 0 aload_0 [this]", TIMEOUT);
136+
} finally {
137+
terminateAndRemove(thread);
138+
removeAllBreakpoints();
139+
project.getProject().delete(true, null);
140+
}
141+
}
142+
143+
public void testDisplaySourceWithClassFileEditorHighlightsLineInConstructor() throws Exception {
144+
IJavaProject project = createProjectWithNoSources("MethodCall", """
145+
public class MethodCall {
146+
147+
private int i;
148+
private int sum = 0;
149+
150+
public static void main(String[] args) {
151+
MethodCall mc = new MethodCall();
152+
mc.go();
153+
}
154+
155+
public void go() {
156+
calculateSum();
157+
}
158+
159+
protected void calculateSum() {
160+
sum += i;
161+
}
162+
}
163+
""");
164+
createMethodBreakpoint(project.findType("MethodCall"), "<init>", "()V", true, false);
165+
IJavaThread thread = null;
166+
try {
167+
ILaunchConfiguration config = createLaunchConfiguration(project, "MethodCall");
168+
thread = launchToBreakpoint(config);
169+
expectHighlightedText(" 0 aload_0 [this]", TIMEOUT);
170+
} finally {
171+
terminateAndRemove(thread);
172+
removeAllBreakpoints();
173+
project.getProject().delete(true, null);
174+
}
175+
}
176+
177+
public void testClassFileWithoutDebuggingInformation() throws Exception {
178+
IJavaProject project = createProjectWithNoSources(CLASS_NAME, CLASS_CONTENTS);
179+
IJavaThread thread = null;
180+
try {
181+
ILaunchConfiguration config = createLaunchConfiguration(project, CLASS_NAME);
182+
ILaunchConfigurationWorkingCopy workingCopy = config.getWorkingCopy();
183+
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
184+
config = workingCopy.doSave();
185+
thread = launchToBreakpoint(config);
186+
187+
String[] expectedHighlights = """
188+
0 getstatic NoSources.i : int [7]
189+
3 iconst_1
190+
4 iadd
191+
5 putstatic NoSources.i : int [7]
192+
8 getstatic java.lang.System.out : java.io.PrintStream [13]
193+
11 getstatic NoSources.i : int [7]
194+
14 invokevirtual java.io.PrintStream.println(int) : void [19]
195+
17 return
196+
""".split(System.lineSeparator());
197+
198+
for (int i = 0; i < expectedHighlights.length; i++) {
199+
expectHighlightedText(expectedHighlights[i], TIMEOUT);
200+
201+
if (i < expectedHighlights.length - 1) {
202+
stepOver((IJavaStackFrame) thread.getTopStackFrame());
203+
}
204+
}
205+
} finally {
206+
terminateAndRemove(thread);
207+
removeAllBreakpoints();
208+
project.getProject().delete(true, null);
209+
}
210+
}
211+
212+
private IJavaProject createProjectWithNoSources(String className, String contents) throws Exception {
213+
return createProjectWithNoSources(className, contents, false);
214+
}
215+
216+
private IJavaProject createProjectWithNoSources(String className, String contents, boolean generateLineInfo) throws Exception {
217+
IJavaProject javaProject = JavaProjectHelper.createJavaProject("ClassFileEditorHighlightingTest", JavaProjectHelper.BIN_DIR);
218+
IProject project = javaProject.getProject();
219+
project.getFolder(LAUNCHCONFIGURATIONS).create(true, true, null);
220+
221+
IFile classFile = project.getFile(className + ".class");
222+
223+
String debug = generateLineInfo ? "lines" : "none";
224+
compileWithJavac(className, contents, List.of("-g:" + debug, "-d", project.getLocation().toString(), "--release", "8"));
225+
classFile.refreshLocal(IResource.DEPTH_ONE, null);
226+
227+
IFile lib = project.getFile("lib.jar");
228+
jarClassFile(lib, classFile);
229+
230+
JavaProjectHelper.addLibrary(javaProject, lib.getFullPath());
231+
waitForBuild();
232+
return javaProject;
233+
}
234+
235+
private static void jarClassFile(IFile jar, IFile classFile) {
236+
JarPackageData data = new JarPackageData();
237+
data.setJarLocation(jar.getLocation());
238+
data.setBuildIfNeeded(true);
239+
data.setOverwrite(true);
240+
data.setElements(new Object[] { classFile });
241+
data.setExportClassFiles(true);
242+
243+
IStatus status = callInUi(() -> {
244+
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
245+
IJarExportRunnable op = data.createJarExportRunnable(window.getShell());
246+
window.run(false, false, op);
247+
return op.getStatus();
248+
});
249+
if (status.getSeverity() == IStatus.ERROR) {
250+
fail("Creating jar failed: " + status.getMessage());
251+
}
252+
}
253+
254+
private static void expectHighlightedText(String expectedHighlightedText, long timeout) throws InterruptedException {
255+
long s = System.currentTimeMillis();
256+
while (System.currentTimeMillis() - s < timeout) {
257+
List<String> highlighted = callInUi(ClassFileEditorHighlightingTest::getClassEditorHighlightedText);
258+
if (Arrays.asList(expectedHighlightedText).equals(highlighted)) {
259+
break;
260+
}
261+
TestUtil.runEventLoop();
262+
Thread.sleep(50L);
263+
}
264+
List<String> highlighted = callInUi(ClassFileEditorHighlightingTest::getClassEditorHighlightedText);
265+
assertEquals("Timed out while waiting on highlighting", Arrays.asList(expectedHighlightedText), highlighted);
266+
}
267+
268+
private static List<String> getClassEditorHighlightedText() {
269+
List<String> highlighted = new ArrayList<>();
270+
StyledText noSourceTextWidget = null;
271+
ClassFileEditor editor = (ClassFileEditor) getActivePage().getActiveEditor();
272+
if (editor != null) {
273+
noSourceTextWidget = editor.getNoSourceTextWidget();
274+
}
275+
if (noSourceTextWidget != null) {
276+
StyleRange[] styleRanges = noSourceTextWidget.getStyleRanges();
277+
StyledTextContent content = noSourceTextWidget.getContent();
278+
for (int i = 0; i < styleRanges.length; ++i) {
279+
String highlightedText = content.getTextRange(styleRanges[0].start, styleRanges[0].length);
280+
highlighted.add(highlightedText);
281+
}
282+
}
283+
return highlighted;
284+
}
285+
286+
private static void compileWithJavac(String className, String source, List<String> compilerOptions) {
287+
JavaFileObject fileObject = new SourceJavaFileObject(className, source);
288+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
289+
Assume.assumeNotNull(compiler);
290+
291+
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, StandardCharsets.UTF_8);
292+
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
293+
CompilationTask task = compiler.getTask(null, fileManager, collector, compilerOptions, null, List.of(fileObject));
294+
Boolean result = task.call();
295+
assertTrue(String.valueOf(collector.getDiagnostics()), result);
296+
}
297+
298+
private static class SourceJavaFileObject extends SimpleJavaFileObject {
299+
300+
private final String code;
301+
302+
protected SourceJavaFileObject(String name, String code) {
303+
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
304+
this.code = code;
305+
}
306+
307+
@Override
308+
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
309+
return code;
310+
}
311+
}
312+
}

0 commit comments

Comments
 (0)