[debugger, kotlin] Smart step into: rewrite with debugger suspend command

GitOrigin-RevId: ba8e3f7a57e081c7c57d569aad3b887cbe7de67c
This commit is contained in:
Maksim Zuev
2024-09-26 18:31:31 +00:00
committed by intellij-monorepo-bot
parent a8861d2917
commit c6380e6910
5 changed files with 158 additions and 106 deletions
@@ -296,6 +296,18 @@ class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: Cor
@ApiStatus.Experimental
fun <T> invokeCommandAsCompletableFuture(action: suspend () -> T): CompletableFuture<T> {
DebuggerManagerThreadImpl.assertIsManagerThread()
val managerThread = InvokeThread.currentThread() as DebuggerManagerThreadImpl
val command = DebuggerManagerThreadImpl.getCurrentCommand()
val priority = command?.priority ?: PrioritizedTask.Priority.LOW
val suspendContext = (command as? SuspendContextCommandImpl)?.suspendContext
return invokeCommandAsCompletableFuture(managerThread, priority, suspendContext, action)
}
@ApiStatus.Experimental
fun <T> invokeCommandAsCompletableFuture(managerThread: DebuggerManagerThreadImpl,
priority: PrioritizedTask.Priority = PrioritizedTask.Priority.LOW,
suspendContext: SuspendContextImpl? = null,
action: suspend () -> T): CompletableFuture<T> {
val res = DebuggerCompletableFuture<T>()
suspend fun doRun() {
@@ -307,10 +319,6 @@ fun <T> invokeCommandAsCompletableFuture(action: suspend () -> T): CompletableFu
}
}
val managerThread = InvokeThread.currentThread() as DebuggerManagerThreadImpl
val command = DebuggerManagerThreadImpl.getCurrentCommand()
val priority = command?.priority ?: PrioritizedTask.Priority.LOW
val suspendContext = (command as? SuspendContextCommandImpl)?.suspendContext
if (suspendContext != null) {
managerThread.invoke(object : SuspendContextCommandImpl(suspendContext) {
override suspend fun contextActionSuspend(suspendContext: SuspendContextImpl) = doRun()
@@ -10,23 +10,23 @@ import com.intellij.debugger.actions.SmartStepTarget
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.MethodFilter
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.engine.invokeCommandAsCompletableFuture
import com.intellij.debugger.impl.DebuggerSession
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.debugger.jdi.MethodBytecodeUtil
import com.intellij.debugger.statistics.DebuggerStatistics
import com.intellij.debugger.statistics.Engine
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.readAction
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.util.parents
import com.intellij.util.Range
import com.intellij.util.containers.OrderedSet
import com.sun.jdi.Location
import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.concurrency.Promise
import org.jetbrains.concurrency.compute
import org.jetbrains.concurrency.asPromise
import org.jetbrains.kotlin.idea.base.psi.getTopmostElementAtOffset
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
import org.jetbrains.kotlin.idea.debugger.base.util.*
@@ -48,28 +48,15 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
super.findStepIntoTargets(position, session)
}
override fun findSmartStepTargetsAsync(position: SourcePosition, session: DebuggerSession): Promise<List<SmartStepTarget>> {
val result = AsyncPromise<List<SmartStepTarget>>()
val command =
object : DebuggerCommandImpl(PrioritizedTask.Priority.NORMAL) {
override fun action() =
result.compute { findSmartStepTargetsInReadAction(position, session) }
override fun commandCancelled() {
result.setError("Cancelled")
}
}
val managerThread = session.process.managerThread
if (DebuggerManagerThreadImpl.isManagerThread()) {
managerThread.invoke(command)
} else {
managerThread.schedule(command)
}
return result
}
override fun findSmartStepTargetsAsync(position: SourcePosition, session: DebuggerSession): Promise<List<SmartStepTarget>> =
invokeCommandAsCompletableFuture(session.process.managerThread, PrioritizedTask.Priority.NORMAL) {
findSmartStepTargetsInternal(position, session)
}.asPromise()
override fun findSmartStepTargets(position: SourcePosition): List<SmartStepTarget> =
findSmartStepTargetsInReadAction(position, null)
runBlockingCancellable {
findSmartStepTargetsInternal(position, null)
}
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? =
when (stepTarget) {
@@ -77,26 +64,24 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
else -> super.createMethodFilter(stepTarget)
}
private fun findSmartStepTargetsInReadAction(position: SourcePosition, session: DebuggerSession?) =
ReadAction.nonBlocking<List<SmartStepTarget>> {
try {
findSmartStepTargets(position, session)
} catch (e: Exception) {
DebuggerStatistics.logSmartStepIntoTargetsDetection(session?.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.INTERNAL_ERROR)
throw e
}
}.executeSynchronously()
internal suspend fun findSmartStepTargetsInternal(position: SourcePosition, session: DebuggerSession?): List<SmartStepTarget> =
try {
findSmartStepTargets(position, session)
} catch (e: Exception) {
DebuggerStatistics.logSmartStepIntoTargetsDetection(session?.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.INTERNAL_ERROR)
throw e
}
private fun findSmartStepTargets(position: SourcePosition, session: DebuggerSession?): List<SmartStepTarget> {
val expression = position.getContainingExpression() ?: run {
private suspend fun findSmartStepTargets(position: SourcePosition, session: DebuggerSession?): List<SmartStepTarget> {
val expression = readAction { position.getContainingExpression() } ?: run {
DebuggerStatistics.logSmartStepIntoTargetsDetection(session?.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.INVALID_POSITION)
return emptyList()
}
val lines = expression.getLines()?.coerceAtLeast(position.line) ?: run {
val lines = readAction { expression.getLines()?.coerceAtLeast(position.line) } ?: run {
DebuggerStatistics.logSmartStepIntoTargetsDetection(session?.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.INVALID_POSITION)
return emptyList()
}
var targets = findSmartStepTargets(expression, lines)
var targets = readAction { findSmartStepTargets(expression, lines) }
if (session != null) {
val currentMethodName = session.process.suspendManager.pausedContext?.frameProxy?.safeLocation()?.safeMethod()?.name()
// Cannot analyze method calls in the default method body, as they are located in a different method in bytecode
@@ -109,8 +94,8 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
} else {
DebuggerStatistics.logSmartStepIntoTargetsDetection(position.file.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.BYTECODE_NOT_AVAILABLE)
}
targets = targets.sortedBy { it.highlightElement?.textOffset ?: 0 }
return reorderWithSteppingFilters(targets)
targets = readAction { targets.sortedBy { it.highlightElement?.textOffset ?: 0 } }
return readAction { reorderWithSteppingFilters(targets) }
}
}
@@ -121,16 +106,16 @@ private fun findSmartStepTargets(element: KtElement, lines: Range<Int>): List<Sm
return targets.toList()
}
private fun calculateSmartStepTargetsToShow(targets: List<SmartStepTarget>, context: SmartStepIntoContext): List<SmartStepTarget> {
private suspend fun calculateSmartStepTargetsToShow(targets: List<SmartStepTarget>, context: SmartStepIntoContext): List<SmartStepTarget> {
val methodTargets = targets.filterIsInstance<KotlinMethodSmartStepTarget>()
val notYetExecutedMethodTargets = methodTargets.filterAlreadyExecuted(context).toHashSet()
val targetsToShow = targets.filter { it !is KotlinMethodSmartStepTarget || it in notYetExecutedMethodTargets }
val removed = methodTargets.toHashSet() - notYetExecutedMethodTargets
fixOrdinalsAfterFiltering(targets, removed)
readAction { fixOrdinalsAfterFiltering(targets, removed) }
return targetsToShow
}
private fun List<KotlinMethodSmartStepTarget>.filterAlreadyExecuted(context: SmartStepIntoContext): List<KotlinMethodSmartStepTarget> {
private suspend fun List<KotlinMethodSmartStepTarget>.filterAlreadyExecuted(context: SmartStepIntoContext): List<KotlinMethodSmartStepTarget> {
DebuggerManagerThreadImpl.assertIsManagerThread()
val debugProcess = context.debugProcess
if (isEmpty()) {
@@ -216,7 +201,7 @@ private fun KtElement.getLines(): Range<Int>? {
private fun Range<Int>.coerceAtLeast(value: Int): Range<Int> =
if (from >= value && to >= value) this else Range(max(value, from), max(value, to))
private fun filterSmartStepTargets(
private suspend fun filterSmartStepTargets(
location: Location,
targets: List<KotlinMethodSmartStepTarget>,
context: SmartStepIntoContext,
@@ -226,21 +211,11 @@ private fun filterSmartStepTargets(
DebuggerStatistics.logSmartStepIntoTargetsDetection(debugProcess.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.BYTECODE_NOT_AVAILABLE)
return targets
}
val targetFilterer = KotlinSmartStepTargetFilterer(targets, debugProcess)
val targetFiltererAdapter = KotlinSmartStepTargetFiltererAdapter(
lines, location, debugProcess.positionManager, targetFilterer
)
val targetFiltererAdapter = KotlinSmartStepTargetFiltererAdapter(lines, location)
var unvisitedTargets: List<KotlinMethodSmartStepTarget>? = null
MethodBytecodeUtil.visit(method, Long.MAX_VALUE, object : OpcodeReportingMethodVisitor(targetFiltererAdapter), MethodBytecodeUtil.InstructionOffsetReader {
private var stopCollectingVisitedTargets = false
override fun readBytecodeInstructionOffset(offset: Int) {
targetFiltererAdapter.currentOffset = offset.toLong()
if (!stopCollectingVisitedTargets && offset >= location.codeIndex()) {
unvisitedTargets = targetFilterer.getUnvisitedTargets()
stopCollectingVisitedTargets = true
}
}
override fun reportOpcode(opcode: Int) {
@@ -253,7 +228,8 @@ private fun filterSmartStepTargets(
}
}, true)
val unvisitedAtTheEnd = targetFilterer.getUnvisitedTargets()
val targetFilterer = KotlinSmartStepTargetFilterer(targets, debugProcess)
val (unvisitedTargets, unvisitedAtTheEnd) = targetFiltererAdapter.visitTrace(targetFilterer, debugProcess.positionManager)
if (unvisitedAtTheEnd.isNotEmpty()) {
val targetStrings = unvisitedAtTheEnd.map { "Target(name=${it.methodInfo.name}, ordinal=${it.ordinal})" }
val session = debugProcess.session
@@ -267,7 +243,7 @@ private fun filterSmartStepTargets(
} else {
DebuggerStatistics.logSmartStepIntoTargetsDetection(debugProcess.project, Engine.KOTLIN, SmartStepIntoDetectionStatus.SUCCESS)
}
return unvisitedTargets!!
return unvisitedTargets
}
private fun Range<Int>.toClosedRange() = from..to
@@ -3,6 +3,8 @@ package org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.openapi.application.readAction
import com.intellij.openapi.application.runReadAction
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiPrimitiveType
@@ -36,10 +38,12 @@ class KotlinSmartStepTargetFilterer(
private val functionCounter = mutableMapOf<String, Int>()
private val targetWasVisited = BooleanArray(targets.size) { false }
fun visitInlineFunction(function: KtNamedFunction) {
val label = analyze(function) {
val symbol = function.symbol
KotlinMethodSmartStepTarget.calcLabel(symbol)
suspend fun visitInlineFunction(function: KtNamedFunction) {
val label = readAction {
analyze(function) {
val symbol = function.symbol
KotlinMethodSmartStepTarget.calcLabel(symbol)
}
}
val currentCount = functionCounter.increment(label) - 1
val matchedSteppingTargetIndex = targets.indexOfFirst {
@@ -49,7 +53,7 @@ class KotlinSmartStepTargetFilterer(
targetWasVisited[matchedSteppingTargetIndex] = true
}
fun visitOrdinaryFunction(owner: String, name: String, signature: String) {
suspend fun visitOrdinaryFunction(owner: String, name: String, signature: String) {
val currentCount = functionCounter.increment("$owner.$name$signature") - 1
for ((i, target) in targets.withIndex()) {
if (targetWasVisited[i]) continue
@@ -60,7 +64,7 @@ class KotlinSmartStepTargetFilterer(
}
}
private fun KotlinMethodSmartStepTarget.shouldBeVisited(owner: String, name: String, signature: String, currentCount: Int): Boolean {
private suspend fun KotlinMethodSmartStepTarget.shouldBeVisited(owner: String, name: String, signature: String, currentCount: Int): Boolean {
val (updatedOwner, updatedName, updatedSignature) = BytecodeSignature(owner, name, signature)
.handleMangling(methodInfo)
.handleValueClassMethods(methodInfo)
@@ -71,7 +75,7 @@ class KotlinSmartStepTargetFilterer(
return matches(updatedOwner, updatedName, updatedSignature, currentCount)
}
private fun KotlinMethodSmartStepTarget.matches(owner: String, name: String, signature: String, currentCount: Int): Boolean {
private suspend fun KotlinMethodSmartStepTarget.matches(owner: String, name: String, signature: String, currentCount: Int): Boolean {
if (ordinal != currentCount) return false
val nameMatches = methodNameMatches(methodInfo, name)
if (!nameMatches) return false
@@ -86,16 +90,18 @@ class KotlinSmartStepTargetFilterer(
"equals" -> return signature == "(Ljava/lang/Object;)Z"
}
// it means the method is, in fact, the implicit primary constructor
analyze(declaration) {
return primaryConstructorMatches(declaration, owner, name, signature)
return readAction {
analyze(declaration) {
primaryConstructorMatches(declaration, owner, name, signature)
}
}
}
if (!methodInfo.isInlineClassMember) {
// Cannot create light class for functions with inline classes
val lightMethod = declaration.getLightClassMethod()
val lightMethod = readAction { declaration.getLightClassMethod() }
// Do not match by name, as it was already checked
val lightMethodMatch = lightMethod?.matches(owner, signature, debugProcess)
val lightMethodMatch = runReadAction { lightMethod?.matches(owner, signature, debugProcess) }
// Light method match still can fail in some Kotlin-specific cases (e.g., setter/getter signature)
if (lightMethodMatch == true) {
return true
@@ -112,14 +118,15 @@ class KotlinSmartStepTargetFilterer(
return owner == internalClassName
}
private fun matchesBySignature(declaration: KtDeclaration, owner: String, signature: String): Boolean {
analyze(declaration) {
val symbol = declaration.symbol as? KaCallableSymbol ?: return false
val declarationSignature = symbol.getJvmSignature()
val declarationInternalName = symbol.getJvmInternalClassName()
return signature == declarationSignature && owner.isSubClassOf(declarationInternalName)
private suspend fun matchesBySignature(declaration: KtDeclaration, owner: String, signature: String): Boolean =
readAction {
analyze(declaration) {
val symbol = declaration.symbol as? KaCallableSymbol ?: return@analyze false
val declarationSignature = symbol.getJvmSignature()
val declarationInternalName = symbol.getJvmInternalClassName()
signature == declarationSignature && owner.isSubClassOf(declarationInternalName)
}
}
}
fun getUnvisitedTargets(): List<KotlinMethodSmartStepTarget> =
targets.filterIndexed { i, _ ->
@@ -1,24 +1,36 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto
import com.intellij.debugger.PositionManager
import com.intellij.debugger.engine.PositionManagerAsync
import com.intellij.openapi.application.readAction
import com.intellij.psi.util.parentOfType
import com.sun.jdi.Location
import kotlinx.coroutines.future.await
import org.jetbrains.kotlin.idea.debugger.base.util.safeMethod
import org.jetbrains.kotlin.idea.debugger.core.getInlineFunctionAndArgumentVariablesToBordersMap
import org.jetbrains.kotlin.idea.debugger.core.isInlineFunctionMarkerVariableName
import org.jetbrains.kotlin.psi.KtNamedFunction
class KotlinSmartStepTargetFiltererAdapter(
internal class KotlinSmartStepTargetFiltererAdapter(
lines: ClosedRange<Int>,
location: Location,
private val positionManager: PositionManager,
private val targetFilterer: KotlinSmartStepTargetFilterer
) : LineMatchingMethodVisitor(lines) {
private val inlineCalls = extractInlineCalls(location)
private val targetOffset = location.codeIndex()
private var inInline = false
internal var currentOffset: Long = -1
private val visitedTrace = mutableListOf<BytecodeTraceElement>()
private val unvisitedTrace = mutableListOf<BytecodeTraceElement>()
private fun add(e: BytecodeTraceElement) {
if (currentOffset < targetOffset) {
visitedTrace += e
} else {
unvisitedTrace += e
}
}
public override fun reportOpcode(opcode: Int) {
if (!lineEverMatched) return
val inlineCall = inlineCalls.firstOrNull { currentOffset in it.bciRange }
@@ -33,19 +45,54 @@ class KotlinSmartStepTargetFiltererAdapter(
inInline = true
if (!inlineCall.variableName.isInlineFunctionMarkerVariableName) return
val calledInlineFunction = getCalledInlineFunction(positionManager, inlineCall.startLocation) ?: return
targetFilterer.visitInlineFunction(calledInlineFunction)
add(BytecodeTraceElement.InlineCall(inlineCall))
}
override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) {
if (lineMatches) {
targetFilterer.visitOrdinaryFunction(owner, name, descriptor)
add(BytecodeTraceElement.MethodCall(owner, name, descriptor))
}
}
suspend fun visitTrace(
targetFilterer: KotlinSmartStepTargetFilterer,
positionManager: PositionManagerAsync
): Pair<List<KotlinMethodSmartStepTarget>, List<KotlinMethodSmartStepTarget>> {
for (element in visitedTrace) {
visitTraceElement(element, targetFilterer, positionManager)
}
val unvisitedTargets = targetFilterer.getUnvisitedTargets()
for (element in unvisitedTrace) {
visitTraceElement(element, targetFilterer, positionManager)
}
val unvisitedAtTheEnd = targetFilterer.getUnvisitedTargets()
return unvisitedTargets to unvisitedAtTheEnd
}
private suspend fun visitTraceElement(
element: BytecodeTraceElement,
targetFilterer: KotlinSmartStepTargetFilterer,
positionManager: PositionManagerAsync
) {
when (element) {
is BytecodeTraceElement.InlineCall -> {
val calledInlineFunction = getCalledInlineFunction(positionManager, element.callInfo.startLocation) ?: return
targetFilterer.visitInlineFunction(calledInlineFunction)
}
is BytecodeTraceElement.MethodCall -> {
targetFilterer.visitOrdinaryFunction(element.owner, element.name, element.descriptor)
}
}
}
}
private data class InlineCallInfo(val variableName: String, val bciRange: LongRange, val startLocation: Location)
private sealed class BytecodeTraceElement {
data class InlineCall(val callInfo: InlineCallInfo) : BytecodeTraceElement()
data class MethodCall(val owner: String, val name: String, val descriptor: String) : BytecodeTraceElement()
}
internal data class InlineCallInfo(val variableName: String, val bciRange: LongRange, val startLocation: Location)
private fun extractInlineCalls(location: Location): List<InlineCallInfo> = location.safeMethod()
?.getInlineFunctionAndArgumentVariablesToBordersMap()
@@ -61,5 +108,7 @@ private fun extractInlineCalls(location: Location): List<InlineCallInfo> = locat
// Filter already visible variable to support smart-step-into while inside an inline function
.filterNot { location.codeIndex() in it.bciRange }
private fun getCalledInlineFunction(positionManager: PositionManager, location: Location): KtNamedFunction? =
positionManager.getSourcePosition(location)?.elementAt?.parentOfType<KtNamedFunction>()
private suspend fun getCalledInlineFunction(positionManager: PositionManagerAsync, location: Location): KtNamedFunction? {
val sourcePosition = positionManager.getSourcePositionAsync(location).await()
return readAction { sourcePosition?.elementAt?.parentOfType<KtNamedFunction>() }
}
@@ -21,16 +21,22 @@ import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.jarRepository.JarRepositoryManager
import com.intellij.jarRepository.RemoteRepositoryDescription
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.readAction
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.ComponentManagerEx
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.libraries.ui.OrderRoot
import com.intellij.psi.PsiElement
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.xdebugger.XDebuggerTestUtil
import com.intellij.xdebugger.frame.XStackFrame
import com.intellij.xdebugger.impl.XSourcePositionImpl
import junit.framework.AssertionFailedError
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.concurrency.asDeferred
import org.jetbrains.idea.maven.aether.ArtifactKind
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils.areLogErrorsIgnored
@@ -183,6 +189,9 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
dp.managerThread.schedule(stepOverCommand)
}
private fun launch(block: suspend CoroutineScope.() -> Unit) =
(project as ComponentManagerEx).getCoroutineScope().launch(block = block)
private fun process(instruction: SteppingInstruction) {
fun loop(count: Int, block: SuspendContextImpl.() -> Unit) {
repeat(count) {
@@ -197,18 +206,20 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
SteppingInstructionKind.StepOut -> loop(instruction.arg) { doStepOut() }
SteppingInstructionKind.StepOver -> loop(instruction.arg) { doStepOver() }
SteppingInstructionKind.ForceStepOver -> loop(instruction.arg) { doStepOver(ignoreBreakpoints = true) }
SteppingInstructionKind.SmartStepInto -> loop(instruction.arg) { doSmartStepInto() }
SteppingInstructionKind.SmartStepIntoByIndex -> doOnBreakpoint { doSmartStepInto(instruction.arg) }
SteppingInstructionKind.SmartStepInto -> loop(instruction.arg) { launch { doSmartStepInto() } }
SteppingInstructionKind.SmartStepIntoByIndex -> doOnBreakpoint { launch { doSmartStepInto(instruction.arg) } }
SteppingInstructionKind.Resume -> loop(instruction.arg) { resume(this) }
SteppingInstructionKind.SmartStepTargetsExpectedNumber ->
doOnBreakpoint {
checkNumberOfSmartStepTargets(instruction.arg)
resume(this)
launch {
checkNumberOfSmartStepTargets(instruction.arg)
this@KotlinDescriptorTestCaseWithStepping.resume(this@doOnBreakpoint)
}
}
}
}
private fun checkNumberOfSmartStepTargets(expectedNumber: Int) {
private suspend fun checkNumberOfSmartStepTargets(expectedNumber: Int) {
val smartStepFilters = createSmartStepIntoFilters()
try {
assertEquals(
@@ -221,7 +232,7 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
}
}
private fun SuspendContextImpl.doSmartStepInto(chooseFromList: Int = 0) {
private suspend fun SuspendContextImpl.doSmartStepInto(chooseFromList: Int = 0) {
this.doSmartStepInto(chooseFromList, false)
}
@@ -269,7 +280,7 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
protected open fun extraPrintContext(context: SuspendContextImpl) {}
private fun SuspendContextImpl.doSmartStepInto(chooseFromList: Int, ignoreFilters: Boolean) {
private suspend fun SuspendContextImpl.doSmartStepInto(chooseFromList: Int, ignoreFilters: Boolean) {
val filters = createSmartStepIntoFilters()
if (chooseFromList == 0) {
if (filters.isEmpty()) {
@@ -292,25 +303,26 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
elementAt.getElementTextWithContext()
}
private fun createSmartStepIntoFilters(): List<MethodFilter> {
private suspend fun createSmartStepIntoFilters(): List<MethodFilter> {
val position = debuggerContext.sourcePosition
val stepTargets = KotlinSmartStepIntoHandler()
.findStepIntoTargets(position, debuggerSession)
.blockingGet(XDebuggerTestUtil.TIMEOUT_MS)
?: error("Couldn't calculate smart step targets")
val stepTargets = withContext(Dispatchers.Default) {
KotlinSmartStepIntoHandler()
.findStepIntoTargets(position, debuggerSession)
.asDeferred().await()
}
// the resulting order is different from the order in code when stepping some methods are filtered
// due to de-prioritisation in JvmSmartStepIntoHandler.reorderWithSteppingFilters
if (runReadAction { stepTargets.none { DebugProcessImpl.isClassFiltered(it.className)} }) {
if (readAction { stepTargets.none { DebugProcessImpl.isClassFiltered(it.className)} }) {
try {
assertEquals("Smart step targets are not sorted by position in tree",
stepTargets.sortedByPositionInTree().map { runReadAction { it.presentation } },
stepTargets.map { runReadAction { it.presentation } })
stepTargets.sortedByPositionInTree().map { readAction { it.presentation } },
stepTargets.map { readAction { it.presentation } })
} catch (e: AssertionFailedError) {
thrownExceptions.add(e)
}
}
return runReadAction {
return readAction {
stepTargets.mapNotNull { stepTarget ->
when (stepTarget) {
is KotlinSmartStepTarget -> stepTarget.createMethodFilter()