Skip to content

Commit 2fee451

Browse files
committed
Improve breakpoint setting and subscription handling
Introduce a new 'SubscriptionBag' type that makes it easier to disposed of no longer needed subscriptions.
1 parent 98b3e1b commit 2fee451

File tree

4 files changed

+59
-17
lines changed

4 files changed

+59
-17
lines changed

adapter/src/main/kotlin/org/javacs/ktda/jdi/JDIDebuggee.kt

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.javacs.ktda.core.breakpoint.Breakpoint
1111
import org.javacs.ktda.core.breakpoint.ExceptionBreakpoint
1212
import org.javacs.kt.LOG
1313
import org.javacs.ktda.util.ObservableList
14+
import org.javacs.ktda.util.SubscriptionBag
1415
import org.javacs.ktda.classpath.findValidKtFilePath
1516
import org.javacs.ktda.classpath.toJVMClassNames
1617
import org.javacs.ktda.jdi.event.VMEventBus
@@ -38,6 +39,8 @@ class JDIDebuggee(
3839
override val stdin: OutputStream?
3940
override val stdout: InputStream?
4041
override val stderr: InputStream?
42+
43+
private var breakpointSubscriptions = SubscriptionBag()
4144

4245
init {
4346
eventBus = VMEventBus(vm)
@@ -66,6 +69,7 @@ class JDIDebuggee(
6669
}
6770

6871
private fun setAllBreakpoints(breakpoints: List<Breakpoint>) {
72+
breakpointSubscriptions.unsubscribe()
6973
vm.eventRequestManager().deleteAllBreakpoints()
7074
breakpoints.forEach { bp ->
7175
bp.position.let { setBreakpoint(
@@ -94,25 +98,27 @@ class JDIDebuggee(
9498
private fun setBreakpoint(filePath: String, lineNumber: Long) {
9599
val eventRequestManager = vm.eventRequestManager()
96100
toJVMClassNames(filePath)
101+
.flatMap { listOf(it, "$it$*") } // For local types
97102
.forEach { className ->
98103
// Try setting breakpoint using a ClassPrepareRequest
99-
100-
eventRequestManager
101-
.createClassPrepareRequest()
102-
.apply { addClassFilter(className) } // For global types
103-
.enable()
104-
eventRequestManager
104+
105+
val request = eventRequestManager
105106
.createClassPrepareRequest()
106-
.apply { addClassFilter(className + "$*") } // For local types
107-
.enable()
107+
.apply { addClassFilter(className) }
108+
109+
breakpointSubscriptions.add(eventBus.subscribe(ClassPrepareEvent::class) {
110+
if (it.jdiEvent.request() == request) {
111+
LOG.info("Setting breakpoint at prepared type {}", it.jdiEvent.referenceType().name())
112+
setBreakpointAtType(it.jdiEvent.referenceType(), lineNumber)
113+
}
114+
})
108115

109-
eventBus.subscribe(ClassPrepareEvent::class) {
110-
setBreakpointAtType(it.jdiEvent.referenceType(), lineNumber)
111-
}
116+
request.enable()
112117

113118
// Try setting breakpoint using loaded VM classes
114119

115120
vm.classesByName(className).forEach {
121+
LOG.info("Setting breakpoint at known type {}", it.name())
116122
setBreakpointAtType(it, lineNumber)
117123
}
118124
}
@@ -122,13 +128,11 @@ class JDIDebuggee(
122128
private fun setBreakpointAtType(refType: ReferenceType, lineNumber: Long): Boolean {
123129
try {
124130
val location = refType
125-
.locationsOfLine(lineNumber.toInt())
126-
?.firstOrNull() ?: return false
131+
.locationsOfLine(lineNumber.toInt())
132+
?.firstOrNull() ?: return false
127133
val request = vm.eventRequestManager()
128-
.createBreakpointRequest(location)
129-
request?.let {
130-
it.enable()
131-
}
134+
.createBreakpointRequest(location)
135+
request?.enable()
132136
return request != null
133137
} catch (e: AbsentInformationException) {
134138
return false

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.javacs.ktda.jdi.event
22

33
import org.javacs.kt.LOG
4+
import org.javacs.ktda.util.Box
45
import org.javacs.ktda.util.ListenerList
56
import org.javacs.ktda.util.Subscription
67
import org.javacs.ktda.core.event.DebuggeeEventBus
@@ -117,6 +118,7 @@ class VMEventBus(private val vm: VirtualMachine): DebuggeeEventBus {
117118

118119
private fun toThreadID(event: JDILocatableEvent) = event.thread().uniqueID()
119120

121+
/** Subscribes to a JDI event type and lets the caller decide when to stop subscribing. */
120122
@Suppress("UNCHECKED_CAST")
121123
fun <E: JDIEvent> subscribe(eventClass: KClass<E>, listener: (VMEvent<E>) -> Unit): Subscription {
122124
eventListeners.putIfAbsent(eventClass, ListenerList())
@@ -130,6 +132,21 @@ class VMEventBus(private val vm: VirtualMachine): DebuggeeEventBus {
130132
}
131133
}
132134
}
135+
136+
/** Subscribes to a JDI event type and lets the listener decide whether to stop subscribing. */
137+
fun <E: JDIEvent> listen(eventClass: KClass<E>, listener: (VMEvent<E>) -> Boolean) {
138+
var box = Box<((VMEvent<E>) -> Unit)?>(null)
139+
box.value = {
140+
val continueSubscribing = listener(it)
141+
142+
if (!continueSubscribing) {
143+
// See method above for rationale
144+
@Suppress("UNCHECKED_CAST")
145+
eventListeners[eventClass]?.remove(box.value as (VMEvent<JDIEvent>) -> Unit)
146+
}
147+
}
148+
subscribe(eventClass, box.value!!)
149+
}
133150

134151
private fun dispatchEvent(event: JDIEvent, eventSet: JDIEventSet): Boolean {
135152
val VMEvent = VMEvent(event, eventSet)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.javacs.ktda.util
2+
3+
/** A simple boxing wrapper. Useful for captured local variables that have to be mutated. */
4+
class Box<T>(var value: T)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.javacs.ktda.util
2+
3+
class SubscriptionBag: Subscription {
4+
private val subscriptions = mutableListOf<Subscription>()
5+
6+
fun add(subscription: Subscription) {
7+
subscriptions.add(subscription)
8+
}
9+
10+
override fun unsubscribe() {
11+
var iterator = subscriptions.iterator()
12+
while (iterator.hasNext()) {
13+
iterator.next().unsubscribe()
14+
iterator.remove()
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)