Skip to content

Commit bbf0491

Browse files
Support exceptionInfo request to show exception info (#255)
* Support exceptionInfo request to show exception info Signed-off-by: Jinbo Wang <[email protected]> * Clear the cached Exception after stepping and continue a thread Signed-off-by: Jinbo Wang <[email protected]> * Use SynchronizedMap to ensure thread safe Signed-off-by: Jinbo Wang <[email protected]>
1 parent ee889d9 commit bbf0491

16 files changed

+292
-2
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2019 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;
13+
14+
import com.sun.jdi.ObjectReference;
15+
16+
public class JdiExceptionReference {
17+
public ObjectReference exception;
18+
public boolean isUncaught;
19+
20+
public JdiExceptionReference(ObjectReference exception, boolean isUncaught) {
21+
this.exception = exception;
22+
this.isUncaught = isUncaught;
23+
}
24+
}

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
@@ -26,6 +26,7 @@
2626
import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestHandler;
2727
import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestWithoutDebuggingHandler;
2828
import com.microsoft.java.debug.core.adapter.handler.EvaluateRequestHandler;
29+
import com.microsoft.java.debug.core.adapter.handler.ExceptionInfoRequestHandler;
2930
import com.microsoft.java.debug.core.adapter.handler.HotCodeReplaceHandler;
3031
import com.microsoft.java.debug.core.adapter.handler.InitializeRequestHandler;
3132
import com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler;
@@ -115,6 +116,7 @@ private void initialize() {
115116
registerHandlerForDebug(new HotCodeReplaceHandler());
116117
registerHandlerForDebug(new RestartFrameHandler());
117118
registerHandlerForDebug(new CompletionsHandler());
119+
registerHandlerForDebug(new ExceptionInfoRequestHandler());
118120

119121
// NO_DEBUG mode only
120122
registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler());

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class DebugAdapterContext implements IDebugAdapterContext {
3232
private boolean debuggerLinesStartAt1 = true;
3333
private boolean debuggerPathsAreUri = true;
3434
private boolean clientLinesStartAt1 = true;
35+
private boolean clientColumnsStartAt1 = true;
3536
private boolean clientPathsAreUri = false;
3637
private boolean supportsRunInTerminalRequest;
3738
private boolean isAttached = false;
@@ -51,6 +52,7 @@ public class DebugAdapterContext implements IDebugAdapterContext {
5152
private IVariableFormatter variableFormatter = VariableFormatterFactory.createVariableFormatter();
5253

5354
private IStackFrameManager stackFrameManager = new StackFrameManager();
55+
private IExceptionManager exceptionManager = new ExceptionManager();
5456

5557
public DebugAdapterContext(IProtocolServer server, IProviderContext providerContext) {
5658
this.providerContext = providerContext;
@@ -107,6 +109,14 @@ public void setClientLinesStartAt1(boolean clientLinesStartAt1) {
107109
this.clientLinesStartAt1 = clientLinesStartAt1;
108110
}
109111

112+
public boolean isClientColumnsStartAt1() {
113+
return clientColumnsStartAt1;
114+
}
115+
116+
public void setClientColumnsStartAt1(boolean clientColumnsStartAt1) {
117+
this.clientColumnsStartAt1 = clientColumnsStartAt1;
118+
}
119+
110120
@Override
111121
public boolean isClientPathsAreUri() {
112122
return clientPathsAreUri;
@@ -276,4 +286,9 @@ public void setArgsfile(Path argsfile) {
276286
public Path getArgsfile() {
277287
return this.argsfile;
278288
}
289+
290+
@Override
291+
public IExceptionManager getExceptionManager() {
292+
return this.exceptionManager;
293+
}
279294
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public enum ErrorCode {
3232
STEP_FAILURE(1015),
3333
RESTARTFRAME_FAILURE(1016),
3434
COMPLETIONS_FAILURE(1017),
35+
EXCEPTION_INFO_FAILURE(1018),
3536
EVALUATION_COMPILE_ERROR(2001),
3637
EVALUATE_NOT_SUSPENDED_THREAD(2002);
3738

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2019 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;
13+
14+
import java.util.Collections;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
import com.microsoft.java.debug.core.JdiExceptionReference;
19+
20+
public class ExceptionManager implements IExceptionManager {
21+
private Map<Long, JdiExceptionReference> exceptions = Collections.synchronizedMap(new HashMap<>());
22+
23+
@Override
24+
public JdiExceptionReference getException(long threadId) {
25+
return exceptions.get(threadId);
26+
}
27+
28+
@Override
29+
public JdiExceptionReference removeException(long threadId) {
30+
return exceptions.remove(threadId);
31+
}
32+
33+
@Override
34+
public JdiExceptionReference setException(long threadId, JdiExceptionReference exception) {
35+
return exceptions.put(threadId, exception);
36+
}
37+
38+
@Override
39+
public void removeAllExceptions() {
40+
exceptions.clear();
41+
}
42+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public interface IDebugAdapterContext {
5151

5252
void setClientLinesStartAt1(boolean clientLinesStartAt1);
5353

54+
boolean isClientColumnsStartAt1();
55+
56+
void setClientColumnsStartAt1(boolean clientColumnsStartAt1);
57+
5458
boolean isClientPathsAreUri();
5559

5660
void setClientPathsAreUri(boolean clientPathsAreUri);
@@ -118,4 +122,6 @@ public interface IDebugAdapterContext {
118122
void setArgsfile(Path argsfile);
119123

120124
Path getArgsfile();
125+
126+
IExceptionManager getExceptionManager();
121127
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2019 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;
13+
14+
import com.microsoft.java.debug.core.JdiExceptionReference;
15+
16+
public interface IExceptionManager {
17+
/**
18+
* Returns the Exception associated with the thread.
19+
*/
20+
JdiExceptionReference getException(long threadId);
21+
22+
/**
23+
* Removes the Exception associated with the thread. Returns the previous Exception mapping to the thread,
24+
* null if no mapping exists.
25+
*/
26+
JdiExceptionReference removeException(long threadId);
27+
28+
/**
29+
* Associates an Exception with the thread. Returns the previous Exception mapping to the thread,
30+
* null if no mapping exists before.
31+
*/
32+
JdiExceptionReference setException(long threadId, JdiExceptionReference exception);
33+
34+
/**
35+
* Clear all Exceptions.
36+
*/
37+
void removeAllExceptions();
38+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
import java.util.Arrays;
1515
import java.util.List;
1616
import java.util.concurrent.CompletableFuture;
17+
import java.util.logging.Logger;
1718

19+
import com.microsoft.java.debug.core.Configuration;
1820
import com.microsoft.java.debug.core.DebugEvent;
1921
import com.microsoft.java.debug.core.DebugUtility;
2022
import com.microsoft.java.debug.core.IDebugSession;
23+
import com.microsoft.java.debug.core.JdiExceptionReference;
2124
import com.microsoft.java.debug.core.UsageDataSession;
2225
import com.microsoft.java.debug.core.adapter.AdapterUtils;
2326
import com.microsoft.java.debug.core.adapter.ErrorCode;
@@ -39,6 +42,7 @@
3942
import com.sun.jdi.event.VMStartEvent;
4043

4144
public class ConfigurationDoneRequestHandler implements IDebugRequestHandler {
45+
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
4246

4347
@Override
4448
public List<Command> getTargetCommands() {
@@ -100,6 +104,10 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
100104
if (engine.isInEvaluation(bpThread)) {
101105
return;
102106
}
107+
108+
JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(),
109+
((ExceptionEvent) event).catchLocation() == null);
110+
context.getExceptionManager().setException(thread.uniqueID(), jdiException);
103111
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
104112
debugEvent.shouldResume = false;
105113
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2019 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.Arrays;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.Objects;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.logging.Level;
20+
import java.util.logging.Logger;
21+
22+
import com.microsoft.java.debug.core.Configuration;
23+
import com.microsoft.java.debug.core.DebugUtility;
24+
import com.microsoft.java.debug.core.JdiExceptionReference;
25+
import com.microsoft.java.debug.core.adapter.AdapterUtils;
26+
import com.microsoft.java.debug.core.adapter.ErrorCode;
27+
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
28+
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
29+
import com.microsoft.java.debug.core.protocol.Messages.Response;
30+
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
31+
import com.microsoft.java.debug.core.protocol.Requests.Command;
32+
import com.microsoft.java.debug.core.protocol.Requests.ExceptionInfoArguments;
33+
import com.microsoft.java.debug.core.protocol.Responses;
34+
import com.microsoft.java.debug.core.protocol.Types.ExceptionBreakMode;
35+
import com.sun.jdi.ClassNotLoadedException;
36+
import com.sun.jdi.IncompatibleThreadStateException;
37+
import com.sun.jdi.InvalidTypeException;
38+
import com.sun.jdi.InvocationException;
39+
import com.sun.jdi.Method;
40+
import com.sun.jdi.ObjectReference;
41+
import com.sun.jdi.ThreadReference;
42+
import com.sun.jdi.Value;
43+
44+
public class ExceptionInfoRequestHandler implements IDebugRequestHandler {
45+
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
46+
47+
@Override
48+
public List<Command> getTargetCommands() {
49+
return Arrays.asList(Command.EXCEPTIONINFO);
50+
}
51+
52+
@Override
53+
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
54+
IDebugAdapterContext context) {
55+
ExceptionInfoArguments exceptionInfoArgs = (ExceptionInfoArguments) arguments;
56+
ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), exceptionInfoArgs.threadId);
57+
if (thread == null) {
58+
throw AdapterUtils.createCompletionException("Thread " + exceptionInfoArgs.threadId + " doesn't exist.", ErrorCode.EXCEPTION_INFO_FAILURE);
59+
}
60+
61+
JdiExceptionReference jdiException = context.getExceptionManager().getException(exceptionInfoArgs.threadId);
62+
if (jdiException == null) {
63+
throw AdapterUtils.createCompletionException("No exception exists in thread " + exceptionInfoArgs.threadId, ErrorCode.EXCEPTION_INFO_FAILURE);
64+
}
65+
66+
Method toStringMethod = null;
67+
for (Method method : jdiException.exception.referenceType().allMethods()) {
68+
if (Objects.equals("toString", method.name()) && Objects.equals("()Ljava/lang/String;", method.signature())) {
69+
toStringMethod = method;
70+
break;
71+
}
72+
}
73+
74+
String typeName = jdiException.exception.type().name();
75+
String exceptionToString = typeName;
76+
if (toStringMethod != null) {
77+
try {
78+
Value returnValue = jdiException.exception.invokeMethod(thread, toStringMethod, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
79+
exceptionToString = returnValue.toString();
80+
} catch (InvalidTypeException | ClassNotLoadedException | IncompatibleThreadStateException
81+
| InvocationException e) {
82+
logger.log(Level.SEVERE, String.format("Failed to get the return value of the method Exception.toString(): %s", e.toString(), e));
83+
}
84+
}
85+
86+
response.body = new Responses.ExceptionInfoResponse(typeName, exceptionToString,
87+
jdiException.isUncaught ? ExceptionBreakMode.USERUNHANDLED : ExceptionBreakMode.ALWAYS);
88+
return CompletableFuture.completedFuture(response);
89+
}
90+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public CompletableFuture<Messages.Response> handle(Requests.Command command, Req
3232
IDebugAdapterContext context) {
3333
Requests.InitializeArguments initializeArguments = (Requests.InitializeArguments) argument;
3434
context.setClientLinesStartAt1(initializeArguments.linesStartAt1);
35+
context.setClientColumnsStartAt1(initializeArguments.columnsStartAt1);
3536
String pathFormat = initializeArguments.pathFormat;
3637
if (pathFormat != null) {
3738
switch (pathFormat) {
@@ -59,6 +60,7 @@ public CompletableFuture<Messages.Response> handle(Requests.Command command, Req
5960
Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER,
6061
};
6162
caps.exceptionBreakpointFilters = exceptionFilters;
63+
caps.supportsExceptionInfoRequest = true;
6264
response.body = caps;
6365
return CompletableFuture.completedFuture(response);
6466
}

0 commit comments

Comments
 (0)