mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger, kotlin] Smart step into: rewrite with debugger suspend command
GitOrigin-RevId: ba8e3f7a57e081c7c57d569aad3b887cbe7de67c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a8861d2917
commit
c6380e6910
@@ -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()
|
||||
|
||||
+32
-56
@@ -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
|
||||
|
||||
+25
-18
@@ -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, _ ->
|
||||
|
||||
+60
-11
@@ -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>() }
|
||||
}
|
||||
|
||||
+29
-17
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user