Skip to content

Commit 81d8747

Browse files
committed
Provide exception details
1 parent 61a104e commit 81d8747

File tree

8 files changed

+76
-7
lines changed

8 files changed

+76
-7
lines changed

adapter/src/main/kotlin/org/javacs/ktda/adapter/DAPConverter.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ private typealias DAPThread = org.eclipse.lsp4j.debug.Thread
1717
private typealias DAPExceptionBreakpointsFilter = org.eclipse.lsp4j.debug.ExceptionBreakpointsFilter
1818
private typealias DAPCompletionItem = org.eclipse.lsp4j.debug.CompletionItem
1919
private typealias DAPCompletionItemType = org.eclipse.lsp4j.debug.CompletionItemType
20+
private typealias DAPExceptionDetails = org.eclipse.lsp4j.debug.ExceptionDetails
2021
private typealias InternalSource = org.javacs.ktda.core.Source
2122
private typealias InternalSourceBreakpoint = org.javacs.ktda.core.breakpoint.SourceBreakpoint
2223
private typealias InternalExceptionBreakpoint = org.javacs.ktda.core.breakpoint.ExceptionBreakpoint
2324
private typealias InternalBreakpoint = org.javacs.ktda.core.breakpoint.Breakpoint
2425
private typealias InternalStackFrame = org.javacs.ktda.core.stack.StackFrame
2526
private typealias InternalCompletionItem = org.javacs.ktda.core.completion.CompletionItem
2627
private typealias InternalCompletionItemType = org.javacs.ktda.core.completion.CompletionItemType
28+
private typealias InternalException = org.javacs.ktda.core.exception.DebuggeeException
2729

2830
/**
2931
* Handles conversions between debug adapter types
@@ -126,4 +128,12 @@ class DAPConverter(
126128
InternalCompletionItemType.REFERENCE -> DAPCompletionItemType.REFERENCE
127129
InternalCompletionItemType.CUSTOMCOLOR -> DAPCompletionItemType.CUSTOMCOLOR
128130
}
131+
132+
fun toDAPExceptionDetails(internalException: InternalException): DAPExceptionDetails = DAPExceptionDetails().apply {
133+
message = internalException.message
134+
typeName = internalException.typeName
135+
fullTypeName = internalException.fullTypeName
136+
stackTrace = internalException.stackTrace
137+
innerException = internalException.innerException?.let(::toDAPExceptionDetails)?.let { arrayOf(it) }
138+
}
129139
}

adapter/src/main/kotlin/org/javacs/ktda/adapter/KotlinDebugAdapter.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import org.javacs.kt.LogMessage
1717
import org.javacs.kt.util.AsyncExecutor
1818
import org.javacs.ktda.util.JSON_LOG
1919
import org.javacs.ktda.util.KotlinDAException
20+
import org.javacs.ktda.util.ObjectPool
2021
import org.javacs.ktda.util.waitFor
2122
import org.javacs.ktda.core.Debuggee
2223
import org.javacs.ktda.core.DebugContext
24+
import org.javacs.ktda.core.exception.DebuggeeException
2325
import org.javacs.ktda.core.event.DebuggeeEventBus
2426
import org.javacs.ktda.core.event.BreakpointStopEvent
2527
import org.javacs.ktda.core.event.StepStopEvent
@@ -44,6 +46,8 @@ class KotlinDebugAdapter(
4446
private var client: IDebugProtocolClient? = null
4547
private var converter = DAPConverter()
4648
private val context = DebugContext()
49+
50+
private val exceptionsPool = ObjectPool<Long, DebuggeeException>() // Contains exceptions thrown by the debuggee owned by thread ids
4751

4852
// TODO: This is a workaround for https://github.com/eclipse/lsp4j/issues/229
4953
// For more information, see launch() method
@@ -57,6 +61,7 @@ class KotlinDebugAdapter(
5761
val capabilities = Capabilities()
5862
capabilities.supportsConfigurationDoneRequest = true
5963
capabilities.supportsCompletionsRequest = true
64+
capabilities.supportsExceptionInfoRequest = true
6065
capabilities.exceptionBreakpointFilters = ExceptionBreakpoint.values()
6166
.map(converter::toDAPExceptionBreakpointsFilter)
6267
.toTypedArray()
@@ -133,6 +138,7 @@ class KotlinDebugAdapter(
133138
sendStopEvent(it.threadID, StoppedEventArgumentsReason.STEP)
134139
}
135140
eventBus.exceptionListeners.add {
141+
exceptionsPool.store(it.threadID, it.exception)
136142
sendStopEvent(it.threadID, StoppedEventArgumentsReason.EXCEPTION)
137143
}
138144
stdoutAsync.execute {
@@ -171,7 +177,7 @@ class KotlinDebugAdapter(
171177
client!!.terminated(TerminatedEventArguments())
172178
LOG.info("Sent exit event")
173179
}
174-
180+
175181
override fun attach(args: Map<String, Any>) = async.execute {
176182
performInitialization()
177183

@@ -258,6 +264,7 @@ class KotlinDebugAdapter(
258264
override fun continue_(args: ContinueArguments) = async.compute {
259265
val success = debuggee!!.threadByID(args.threadId)?.resume()
260266
if (success ?: false) {
267+
exceptionsPool.clear()
261268
converter.variablesPool.clear()
262269
converter.stackFramePool.removeAllOwnedBy(args.threadId)
263270
}
@@ -333,7 +340,7 @@ class KotlinDebugAdapter(
333340
.childs
334341
?.map(converter::toDAPVariable)
335342
?.toTypedArray()
336-
?: emptyArray()
343+
.orEmpty()
337344
}
338345
)
339346

@@ -383,7 +390,14 @@ class KotlinDebugAdapter(
383390
}
384391
}
385392

386-
override fun exceptionInfo(args: ExceptionInfoArguments): CompletableFuture<ExceptionInfoResponse> = notImplementedDAPMethod()
393+
override fun exceptionInfo(args: ExceptionInfoArguments): CompletableFuture<ExceptionInfoResponse> = async.compute {
394+
val id = exceptionsPool.getIDsOwnedBy(args.threadId).firstOrNull()
395+
val exception = id?.let { exceptionsPool.getByID(it) }
396+
ExceptionInfoResponse().apply {
397+
exceptionId = id?.toString() ?: ""
398+
details = exception?.let(converter::toDAPExceptionDetails)
399+
}
400+
}
387401

388402
private fun connectLoggingBackend(client: IDebugProtocolClient) {
389403
val backend: (LogMessage) -> Unit = {
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.javacs.ktda.core.event
22

3+
import org.javacs.ktda.core.exception.DebuggeeException
4+
35
class ExceptionStopEvent(
46
val threadID: Long,
5-
val exceptionName: String
7+
val exception: DebuggeeException
68
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.javacs.ktda.core.exception
2+
3+
interface DebuggeeException {
4+
val message: String?
5+
get() = null
6+
val typeName: String?
7+
get() = null
8+
val fullTypeName: String?
9+
get() = null
10+
val stackTrace: String?
11+
get() = null
12+
val innerException: DebuggeeException?
13+
get() = null
14+
}

adapter/src/main/kotlin/org/javacs/ktda/jdi/event/VMEventBus.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import org.javacs.kt.LOG
44
import org.javacs.ktda.util.ListenerList
55
import org.javacs.ktda.util.Subscription
66
import org.javacs.ktda.core.event.DebuggeeEventBus
7+
import org.javacs.ktda.core.exception.DebuggeeException
78
import org.javacs.ktda.core.event.ExitEvent
89
import org.javacs.ktda.core.event.BreakpointStopEvent
910
import org.javacs.ktda.core.event.ExceptionStopEvent
1011
import org.javacs.ktda.core.event.StepStopEvent
12+
import org.javacs.ktda.jdi.exception.JDIException
1113
import com.sun.jdi.VirtualMachine
1214
import com.sun.jdi.VMDisconnectedException
1315
import com.sun.jdi.event.VMDeathEvent
@@ -81,10 +83,9 @@ class VMEventBus(private val vm: VirtualMachine): DebuggeeEventBus {
8183
it.resumeThreads = false
8284
}
8385
subscribe(JDIExceptionEvent::class) {
84-
val exception = it.jdiEvent.exception()
8586
exceptionListeners.fire(ExceptionStopEvent(
8687
threadID = toThreadID(it.jdiEvent),
87-
exceptionName = exception.referenceType().name()
88+
exception = JDIException(it.jdiEvent.exception(), it.jdiEvent.thread())
8889
))
8990
}
9091
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.javacs.ktda.jdi.exception
2+
3+
import com.sun.jdi.ObjectReference
4+
import com.sun.jdi.ThreadReference
5+
import org.javacs.ktda.core.exception.DebuggeeException
6+
7+
class JDIException(
8+
private val exception: ObjectReference,
9+
private val thread: ThreadReference
10+
) : DebuggeeException {
11+
private val type by lazy { exception.referenceType() }
12+
13+
override val fullTypeName: String by lazy { type.name() }
14+
override val typeName: String? by lazy { fullTypeName.split(".").last() }
15+
override val message: String? by lazy {
16+
type.methodsByName("getMessage")
17+
.firstOrNull()
18+
?.let { exception.invokeMethod(thread, it, emptyList(), 0) }
19+
?.toString()
20+
}
21+
22+
// TODO: Stack frames, inner exception
23+
}

adapter/src/main/kotlin/org/javacs/ktda/jdi/stack/JDIStackFrame.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class JDIStackFrame(
4444
.takeIf { rest.isEmpty() }
4545
?.filter { it.name.startsWith(qual) }
4646
?.map { CompletionItem(it.name, CompletionItemType.VARIABLE) }
47-
} ?: emptyList()
47+
}.orEmpty()
4848

4949
private fun parseQualified(expression: String): List<String> = expression.split(".")
5050

adapter/src/main/kotlin/org/javacs/ktda/util/ObjectPool.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ class ObjectPool<O, V> {
6262
}
6363

6464
fun getByID(id: Long) = mappingsByID[id]?.value
65+
66+
fun getIDsOwnedBy(owner: O): Set<Long> = mappingsByOwner[owner]
67+
?.map { it.key.id }
68+
?.toSet()
69+
.orEmpty()
6570

6671
fun getOwnedBy(owner: O): Set<V> = mappingsByOwner[owner]
6772
?.map { it.value }

0 commit comments

Comments
 (0)