Skip to content

Commit f9c02cf

Browse files
Custom request 'refreshFrames' to refresh the thread's frames (#501)
* Custom request 'refreshFrames' to refresh the thread's frames
1 parent 7bdf2ef commit f9c02cf

13 files changed

+233
-10
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.microsoft.java.debug.core.adapter.handler.InlineValuesRequestHandler;
3535
import com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler;
3636
import com.microsoft.java.debug.core.adapter.handler.ProcessIdHandler;
37+
import com.microsoft.java.debug.core.adapter.handler.RefreshFramesHandler;
3738
import com.microsoft.java.debug.core.adapter.handler.RefreshVariablesHandler;
3839
import com.microsoft.java.debug.core.adapter.handler.RestartFrameHandler;
3940
import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler;
@@ -133,6 +134,7 @@ private void initialize() {
133134
registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler());
134135
registerHandlerForDebug(new BreakpointLocationsRequestHander());
135136
registerHandlerForDebug(new StepInTargetsRequestHandler());
137+
registerHandlerForDebug(new RefreshFramesHandler());
136138

137139
// NO_DEBUG mode only
138140
registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler());

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import java.util.ArrayList;
1515
import java.util.Collections;
16+
import java.util.HashMap;
1617
import java.util.HashSet;
1718
import java.util.LinkedHashMap;
1819
import java.util.List;
@@ -32,6 +33,8 @@ protected boolean removeEldestEntry(java.util.Map.Entry<Long, Boolean> eldest) {
3233
}
3334
});
3435
private Map<Long, ThreadReference> eventThreads = new ConcurrentHashMap<>();
36+
private Map<Long, Set<String>> decompiledClassesByThread = new HashMap<>();
37+
private Map<Long, String> threadStoppedReasons = new HashMap<>();
3538

3639
public synchronized void resetThreads(List<ThreadReference> threads) {
3740
allThreads.clear();
@@ -80,6 +83,13 @@ public void addEventThread(ThreadReference thread) {
8083
eventThreads.put(thread.uniqueID(), thread);
8184
}
8285

86+
public void addEventThread(ThreadReference thread, String reason) {
87+
eventThreads.put(thread.uniqueID(), thread);
88+
if (reason != null) {
89+
threadStoppedReasons.put(thread.uniqueID(), reason);
90+
}
91+
}
92+
8393
public void removeEventThread(long threadId) {
8494
eventThreads.remove(threadId);
8595
}
@@ -113,4 +123,40 @@ public List<ThreadReference> visibleThreads(IDebugAdapterContext context) {
113123

114124
return visibleThreads;
115125
}
126+
127+
public Set<String> getDecompiledClassesByThread(long threadId) {
128+
return decompiledClassesByThread.get(threadId);
129+
}
130+
131+
public void setDecompiledClassesByThread(long threadId, Set<String> decompiledClasses) {
132+
if (decompiledClasses == null || decompiledClasses.isEmpty()) {
133+
decompiledClassesByThread.remove(threadId);
134+
return;
135+
}
136+
137+
decompiledClassesByThread.put(threadId, decompiledClasses);
138+
}
139+
140+
public String getThreadStoppedReason(long threadId) {
141+
return threadStoppedReasons.get(threadId);
142+
}
143+
144+
public void setThreadStoppedReason(long threadId, String reason) {
145+
if (reason == null) {
146+
threadStoppedReasons.remove(threadId);
147+
return;
148+
}
149+
150+
threadStoppedReasons.put(threadId, reason);
151+
}
152+
153+
public void clearThreadStoppedState(long threadId) {
154+
threadStoppedReasons.remove(threadId);
155+
decompiledClassesByThread.remove(threadId);
156+
}
157+
158+
public void clearAllThreadStoppedState() {
159+
threadStoppedReasons.clear();
160+
decompiledClassesByThread.clear();
161+
}
116162
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
7676
if (context.isVmStopOnEntry()) {
7777
DebugUtility.stopOnEntry(debugSession, context.getMainClass()).thenAccept(threadId -> {
7878
context.getProtocolServer().sendEvent(new Events.StoppedEvent("entry", threadId));
79+
context.getThreadCache().setThreadStoppedReason(threadId, "entry");
7980
});
8081
}
8182
} else if (event instanceof VMDeathEvent) {
@@ -117,7 +118,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
117118
JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(),
118119
((ExceptionEvent) event).catchLocation() == null);
119120
context.getExceptionManager().setException(thread.uniqueID(), jdiException);
120-
context.getThreadCache().addEventThread(thread);
121+
context.getThreadCache().addEventThread(thread, "exception");
121122
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
122123
debugEvent.shouldResume = false;
123124
} else {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core.adapter.handler;
13+
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.Collection;
17+
import java.util.List;
18+
import java.util.Set;
19+
import java.util.concurrent.CompletableFuture;
20+
21+
import com.microsoft.java.debug.core.AsyncJdwpUtils;
22+
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
23+
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
24+
import com.microsoft.java.debug.core.protocol.Events.StoppedEvent;
25+
import com.microsoft.java.debug.core.protocol.Messages.Response;
26+
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
27+
import com.microsoft.java.debug.core.protocol.Requests.Command;
28+
import com.microsoft.java.debug.core.protocol.Requests.RefreshFramesArguments;
29+
import com.sun.jdi.ObjectCollectedException;
30+
import com.sun.jdi.ThreadReference;
31+
32+
public class RefreshFramesHandler implements IDebugRequestHandler {
33+
34+
@Override
35+
public List<Command> getTargetCommands() {
36+
return Arrays.asList(Command.REFRESHFRAMES);
37+
}
38+
39+
@Override
40+
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
41+
IDebugAdapterContext context) {
42+
RefreshFramesArguments refreshArgs = (RefreshFramesArguments) arguments;
43+
String[] affectedRootPaths = refreshArgs == null ? null : refreshArgs.affectedRootPaths;
44+
List<Long> pausedThreads = getPausedThreads(context);
45+
for (long threadId : pausedThreads) {
46+
if (affectedRootPaths == null || affectedRootPaths.length == 0) {
47+
refreshFrames(threadId, context);
48+
continue;
49+
}
50+
51+
Set<String> decompiledClasses = context.getThreadCache().getDecompiledClassesByThread(threadId);
52+
if (decompiledClasses == null || decompiledClasses.isEmpty()) {
53+
continue;
54+
}
55+
56+
if (anyInAffectedRootPaths(decompiledClasses, affectedRootPaths)) {
57+
refreshFrames(threadId, context);
58+
}
59+
}
60+
61+
return CompletableFuture.completedFuture(response);
62+
}
63+
64+
List<Long> getPausedThreads(IDebugAdapterContext context) {
65+
List<Long> results = new ArrayList<>();
66+
List<CompletableFuture<Long>> futures = new ArrayList<>();
67+
List<ThreadReference> threads = context.getThreadCache().visibleThreads(context);
68+
for (ThreadReference thread : threads) {
69+
if (context.asyncJDWP()) {
70+
futures.add(AsyncJdwpUtils.supplyAsync(() -> {
71+
try {
72+
if (thread.isSuspended()) {
73+
return thread.uniqueID();
74+
}
75+
} catch (ObjectCollectedException ex) {
76+
// Ignore it if the thread is garbage collected.
77+
}
78+
79+
return -1L;
80+
}));
81+
} else {
82+
try {
83+
if (thread.isSuspended()) {
84+
results.add(thread.uniqueID());
85+
}
86+
} catch (ObjectCollectedException ex) {
87+
// Ignore it if the thread is garbage collected.
88+
}
89+
}
90+
}
91+
92+
List<Long> awaitedResutls = AsyncJdwpUtils.await(futures);
93+
for (Long threadId : awaitedResutls) {
94+
if (threadId > 0) {
95+
results.add(threadId);
96+
}
97+
}
98+
99+
return results;
100+
}
101+
102+
/**
103+
* See https://github.com/microsoft/vscode/issues/188606,
104+
* VS Code doesn't provide a simple way to refetch the stack frames.
105+
* We're going to resend a thread stopped event to trick the client
106+
* into refetching the thread stack frames.
107+
*/
108+
void refreshFrames(long threadId, IDebugAdapterContext context) {
109+
StoppedEvent stoppedEvent = new StoppedEvent(context.getThreadCache().getThreadStoppedReason(threadId), threadId);
110+
stoppedEvent.preserveFocusHint = true;
111+
context.getProtocolServer().sendEvent(stoppedEvent);
112+
}
113+
114+
boolean anyInAffectedRootPaths(Collection<String> classes, String[] affectedRootPaths) {
115+
if (affectedRootPaths == null || affectedRootPaths.length == 0) {
116+
return true;
117+
}
118+
119+
for (String classUri : classes) {
120+
// decompiled class uri is like 'jdt://contents/rt.jar/java.io/PrintStream.class?=1.helloworld/%5C/usr%5C/lib%5C/jvm%5C/
121+
// java-8-oracle%5C/jre%5C/lib%5C/rt.jar%3Cjava.io(PrintStream.class'.
122+
if (classUri.startsWith("jdt://contents/")) {
123+
String jarName = classUri.substring("jdt://contents/".length());
124+
int sep = jarName.indexOf("/");
125+
jarName = sep >= 0 ? jarName.substring(0, sep) : jarName;
126+
for (String affectedRootPath : affectedRootPaths) {
127+
if (affectedRootPath.endsWith("/" + jarName) || affectedRootPath.endsWith("\\" + jarName)) {
128+
return true;
129+
}
130+
}
131+
}
132+
}
133+
134+
return false;
135+
}
136+
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ private void stepInto(IDebugAdapterContext context, ThreadReference thread) {
122122
// Have to send two events to keep the UI sync with the step in operations:
123123
context.getProtocolServer().sendEvent(new Events.ContinuedEvent(thread.uniqueID()));
124124
context.getProtocolServer().sendEvent(new Events.StoppedEvent("restartframe", thread.uniqueID()));
125+
context.getThreadCache().setThreadStoppedReason(thread.uniqueID(), "restartframe");
125126
});
126127
request.enable();
127128
thread.resume();

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,14 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
211211
if (resume) {
212212
debugEvent.eventSet.resume();
213213
} else {
214-
context.getThreadCache().addEventThread(bpThread);
214+
context.getThreadCache().addEventThread(bpThread, breakpointName);
215215
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
216216
breakpointName, bpThread.uniqueID()));
217217
}
218218
});
219219
});
220220
} else {
221-
context.getThreadCache().addEventThread(bpThread);
221+
context.getThreadCache().addEventThread(bpThread, breakpointName);
222222
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
223223
breakpointName, bpThread.uniqueID()));
224224
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,13 @@ private void registerWatchpointHandler(IDebugAdapterContext context) {
151151
if (resume) {
152152
debugEvent.eventSet.resume();
153153
} else {
154-
context.getThreadCache().addEventThread(bpThread);
154+
context.getThreadCache().addEventThread(bpThread, "data breakpoint");
155155
context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID()));
156156
}
157157
});
158158
});
159159
} else {
160-
context.getThreadCache().addEventThread(bpThread);
160+
context.getThreadCache().addEventThread(bpThread, "data breakpoint");
161161
context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID()));
162162
}
163163
debugEvent.shouldResume = false;

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,15 @@ private void registerMethodBreakpointHandler(IDebugAdapterContext context) {
165165
if (resume) {
166166
debugEvent.eventSet.resume();
167167
} else {
168-
context.getThreadCache().addEventThread(bpThread);
168+
context.getThreadCache().addEventThread(bpThread, "function breakpoint");
169169
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
170170
"function breakpoint", bpThread.uniqueID()));
171171
}
172172
});
173173
});
174174

175175
} else {
176-
context.getThreadCache().addEventThread(bpThread);
176+
context.getThreadCache().addEventThread(bpThread, "function breakpoint");
177177
context.getProtocolServer()
178178
.sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID()));
179179
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import java.net.URISyntaxException;
1616
import java.util.ArrayList;
1717
import java.util.Arrays;
18+
import java.util.LinkedHashSet;
1819
import java.util.List;
1920
import java.util.Objects;
21+
import java.util.Set;
2022
import java.util.concurrent.CancellationException;
2123
import java.util.concurrent.CompletableFuture;
2224
import java.util.concurrent.CompletionException;
@@ -73,16 +75,23 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
7375
response.body = new Responses.StackTraceResponseBody(result, 0);
7476
return CompletableFuture.completedFuture(response);
7577
}
76-
ThreadReference thread = context.getThreadCache().getThread(stacktraceArgs.threadId);
78+
long threadId = stacktraceArgs.threadId;
79+
ThreadReference thread = context.getThreadCache().getThread(threadId);
7780
if (thread == null) {
78-
thread = DebugUtility.getThread(context.getDebugSession(), stacktraceArgs.threadId);
81+
thread = DebugUtility.getThread(context.getDebugSession(), threadId);
7982
}
8083
int totalFrames = 0;
8184
if (thread != null) {
85+
Set<String> decompiledClasses = new LinkedHashSet<>();
8286
try {
8387
// Thread state has changed and then invalidate the stack frame cache.
8488
if (stacktraceArgs.startFrame == 0) {
8589
context.getStackFrameManager().clearStackFrames(thread);
90+
} else {
91+
Set<String> existing = context.getThreadCache().getDecompiledClassesByThread(threadId);
92+
if (existing != null) {
93+
decompiledClasses.addAll(existing);
94+
}
8695
}
8796

8897
totalFrames = thread.frameCount();
@@ -102,6 +111,10 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
102111
Types.StackFrame lspFrame = convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context);
103112
result.add(lspFrame);
104113
frameReference.setSource(lspFrame.source);
114+
int jdiLineNumber = AdapterUtils.convertLineNumber(jdiFrame.lineNumber, context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
115+
if (jdiLineNumber != lspFrame.line) {
116+
decompiledClasses.add(lspFrame.source.path);
117+
}
105118
}
106119
} catch (IncompatibleThreadStateException | IndexOutOfBoundsException | URISyntaxException
107120
| AbsentInformationException | ObjectCollectedException
@@ -110,6 +123,8 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
110123
// 1. the vscode has wrong parameter/wrong uri
111124
// 2. the thread actually terminates
112125
// TODO: should record a error log here.
126+
} finally {
127+
context.getThreadCache().setDecompiledClassesByThread(threadId, decompiledClasses);
113128
}
114129
}
115130
response.body = new Responses.StackTraceResponseBody(result, totalFrames);

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
308308
if (threadState.eventSubscription != null) {
309309
threadState.eventSubscription.dispose();
310310
}
311-
context.getThreadCache().addEventThread(thread);
311+
context.getThreadCache().addEventThread(thread, "step");
312312
context.getProtocolServer().sendEvent(new Events.StoppedEvent("step", thread.uniqueID()));
313313
debugEvent.shouldResume = false;
314314
} else if (event instanceof MethodExitEvent) {

0 commit comments

Comments
 (0)