Debugger: Rewrite step over action (KT-14296)
The main goal is to make behavior similar to what happens in Java. For instance, now we always skip lambdas. Also, we can reliably use '$i$f' and '$i$a' synthetic local variables. There is no need in complicated hacks any more.
This commit is contained in:
+2
-1
@@ -123,7 +123,8 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
|
||||
if (psiFile !is KtFile) throw NoDataException.INSTANCE
|
||||
|
||||
val sourceLineNumber = location.safeSourceLineNumber()
|
||||
// Zero-based line-number for Document.getLineStartOffset()
|
||||
val sourceLineNumber = location.safeLineNumber() - 1
|
||||
if (sourceLineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
+5
-3
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping;
|
||||
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.RequestHint;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
@@ -48,7 +49,7 @@ public class DebuggerSteppingHelper {
|
||||
public static DebugProcessImpl.ResumeCommand createStepOverCommand(
|
||||
SuspendContextImpl suspendContext,
|
||||
boolean ignoreBreakpoints,
|
||||
KotlinSourcePosition kotlinSourcePosition
|
||||
SourcePosition sourcePosition
|
||||
) {
|
||||
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
|
||||
@@ -58,8 +59,9 @@ public class DebuggerSteppingHelper {
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
KotlinStepAction action = KotlinSteppingCommandProviderKt
|
||||
.getStepOverAction(frameProxy.location(), kotlinSourcePosition, frameProxy);
|
||||
|
||||
Location location = frameProxy.location();
|
||||
KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOverAction(location, sourcePosition, frameProxy);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
|
||||
data class KotlinSourcePosition(
|
||||
val file: KtFile, val declaration: KtDeclaration,
|
||||
val linesRange: IntRange, val sourcePosition: SourcePosition
|
||||
) {
|
||||
companion object {
|
||||
fun create(sourcePosition: SourcePosition): KotlinSourcePosition? {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val elementAt = sourcePosition.elementAt ?: return null
|
||||
|
||||
val containingDeclaration = elementAt.parents
|
||||
.filterIsInstance<KtDeclaration>()
|
||||
.filter { it is KtFunction || it is KtProperty || it is KtClassInitializer }
|
||||
.firstOrNull { !KtPsiUtil.isLocal(it) }
|
||||
?: return null
|
||||
|
||||
val startLineNumber = containingDeclaration.getLineNumber(true) + 1
|
||||
val endLineNumber = containingDeclaration.getLineNumber(false) + 1
|
||||
if (startLineNumber > endLineNumber) return null
|
||||
|
||||
val linesRange = startLineNumber..endLineNumber
|
||||
|
||||
return KotlinSourcePosition(file, containingDeclaration, linesRange, sourcePosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-15
@@ -9,36 +9,35 @@ import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinStepOverInlineFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverFilterData
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.LocationToken
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverCallerInfo
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
|
||||
sealed class KotlinStepAction(
|
||||
val position: XSourcePositionImpl? = null,
|
||||
val stepOverInlineData: StepOverFilterData? = null
|
||||
) {
|
||||
class StepOver : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
|
||||
sealed class KotlinStepAction {
|
||||
object StepOver : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
class StepOut : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
|
||||
object StepOut : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
class RunToCursor(position: XSourcePositionImpl) : KotlinStepAction(position) {
|
||||
class RunToCursor(private val position: XSourcePositionImpl) : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return runReadAction {
|
||||
debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints)
|
||||
debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints)
|
||||
}.contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
class StepOverInlined(stepOverInlineData: StepOverFilterData) : KotlinStepAction(stepOverInlineData = stepOverInlineData) {
|
||||
class StepOverInlined(private val tokensToSkip: Set<LocationToken>, private val callerInfo: StepOverCallerInfo) : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(
|
||||
KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)
|
||||
).contextAction(suspendContext)
|
||||
val filter = KotlinStepOverInlineFilter(debugProcess.project, tokensToSkip, callerInfo)
|
||||
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(filter).contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-15
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess.JAVA_STRATUM
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
@@ -23,35 +24,29 @@ import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
|
||||
// Originally copied from RequestHint
|
||||
class KotlinStepOverInlinedLinesHint(
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
suspendContext: SuspendContextImpl,
|
||||
methodFilter: KotlinMethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, methodFilter) {
|
||||
private val filter: KotlinMethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_OVER, filter) {
|
||||
private companion object {
|
||||
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
|
||||
}
|
||||
|
||||
private val filter = methodFilter
|
||||
|
||||
override fun getDepth(): Int = StepRequest.STEP_OVER
|
||||
|
||||
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||
try {
|
||||
val frameProxy = context.frameProxy
|
||||
if (frameProxy != null) {
|
||||
if (isTheSameFrame(context)) {
|
||||
return if (filter.locationMatches(context, frameProxy.location())) {
|
||||
STOP
|
||||
} else {
|
||||
StepRequest.STEP_OVER
|
||||
}
|
||||
}
|
||||
|
||||
if (isSteppedOut) {
|
||||
return STOP
|
||||
val isAcceptable = (filter.locationMatches(context, frameProxy.location()))
|
||||
return if (isAcceptable) STOP else StepRequest.STEP_OVER
|
||||
} else if (isSteppedOut) {
|
||||
val lineNumber = frameProxy.safeLocation()?.safeLineNumber(JAVA_STRATUM) ?: -1
|
||||
return if (lineNumber >= 0) STOP else StepRequest.STEP_OVER
|
||||
}
|
||||
|
||||
return StepRequest.STEP_OUT
|
||||
|
||||
+71
-182
@@ -15,32 +15,23 @@ import com.intellij.debugger.impl.JvmSteppingCommandProvider
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinStepOverInlineFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverFilterData
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.LocationToken
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverCallerInfo
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import kotlin.math.max
|
||||
@@ -72,12 +63,6 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
||||
ignoreBreakpoints: Boolean,
|
||||
sourcePosition: SourcePosition
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
val kotlinSourcePosition = KotlinSourcePosition.create(sourcePosition) ?: return null
|
||||
|
||||
if (isSpecialStepOverNeeded(kotlinSourcePosition)) {
|
||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, kotlinSourcePosition)
|
||||
}
|
||||
|
||||
val file = sourcePosition.elementAt.containingFile
|
||||
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.safeLocation() } ?: return null
|
||||
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
||||
@@ -86,27 +71,7 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isSpecialStepOverNeeded(kotlinSourcePosition: KotlinSourcePosition): Boolean {
|
||||
val sourcePosition = kotlinSourcePosition.sourcePosition
|
||||
|
||||
val hasInlineCallsOnLine = getInlineFunctionCallsIfAny(sourcePosition).isNotEmpty()
|
||||
if (hasInlineCallsOnLine) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Step over calls to lambda arguments in inline function while execution is already in that function
|
||||
val containingDescriptor = kotlinSourcePosition.declaration.resolveToDescriptorIfAny()
|
||||
if (containingDescriptor != null && InlineUtil.isInline(containingDescriptor)) {
|
||||
val inlineArgumentsCallsIfAny = getInlineArgumentsCallsIfAny(sourcePosition, containingDescriptor)
|
||||
if (inlineArgumentsCallsIfAny != null && inlineArgumentsCallsIfAny.isNotEmpty()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, sourcePosition)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
@@ -127,7 +92,7 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
||||
|
||||
val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null
|
||||
|
||||
val inlineFunctions = getInlineFunctionsIfAny(file, lineStartOffset)
|
||||
val inlineFunctions = findInlineFunctions(file, lineStartOffset)
|
||||
val inlinedArgument = getInlineArgumentIfAny(sourcePosition.elementAt)
|
||||
|
||||
if (inlineFunctions.isEmpty() && inlinedArgument == null) return null
|
||||
@@ -140,12 +105,21 @@ private operator fun PsiElement?.contains(element: PsiElement): Boolean {
|
||||
return this?.textRange?.contains(element.textRange) ?: false
|
||||
}
|
||||
|
||||
private fun getInlineCallFunctionArgumentsIfAny(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
||||
return getInlineArgumentsIfAny(inlineFunctionCalls)
|
||||
private fun findInlinedFunctionArguments(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val args = mutableListOf<KtFunction>()
|
||||
|
||||
for (call in findInlineFunctionCalls(sourcePosition)) {
|
||||
for (arg in call.valueArguments) {
|
||||
val expression = arg.getArgumentExpression()
|
||||
val functionExpression = (expression as? KtLambdaExpression)?.functionLiteral ?: expression
|
||||
args += functionExpression as? KtFunction ?: continue
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||
private fun findInlineFunctions(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
||||
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
||||
|
||||
@@ -155,41 +129,7 @@ private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunc
|
||||
return DebuggerUtils.analyzeElementWithInline(containingFunction, false).filterIsInstance<KtNamedFunction>()
|
||||
}
|
||||
|
||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||
return inlineFunctionCalls.flatMap {
|
||||
it.valueArguments
|
||||
.map(::getArgumentExpression)
|
||||
.filterIsInstance<KtFunction>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) =
|
||||
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun getInlineArgumentsCallsIfAny(
|
||||
sourcePosition: SourcePosition,
|
||||
declarationDescriptor: DeclarationDescriptor
|
||||
): List<KtCallExpression>? {
|
||||
if (declarationDescriptor !is CallableDescriptor) return null
|
||||
|
||||
val valueParameters = declarationDescriptor.valueParameters.filter { it.type.isFunctionType }.toSet()
|
||||
|
||||
if (valueParameters.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun isCallOfArgument(ktCallExpression: KtCallExpression): Boolean {
|
||||
val resolvedCall = ktCallExpression.resolveToCall() as? VariableAsFunctionResolvedCall ?: return false
|
||||
|
||||
val candidateDescriptor = resolvedCall.variableCall.candidateDescriptor
|
||||
|
||||
return candidateDescriptor in valueParameters
|
||||
}
|
||||
|
||||
return findCallsOnPosition(sourcePosition, ::isCallOfArgument)
|
||||
}
|
||||
|
||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
private fun findInlineFunctionCalls(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
fun isInlineCall(expr: KtCallExpression): Boolean {
|
||||
val resolvedCall = expr.resolveToCall() ?: return false
|
||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||
@@ -239,108 +179,66 @@ interface KotlinMethodFilter : MethodFilter {
|
||||
fun locationMatches(context: SuspendContextImpl, location: Location): Boolean
|
||||
}
|
||||
|
||||
fun getStepOverAction(location: Location, kotlinSourcePosition: KotlinSourcePosition, frameProxy: StackFrameProxyImpl): KotlinStepAction {
|
||||
val inlineArgumentsToSkip = runReadAction {
|
||||
getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition)
|
||||
fun getStepOverAction(location: Location, sourcePosition: SourcePosition, frameProxy: StackFrameProxyImpl): KotlinStepAction {
|
||||
val stackFrame = frameProxy.safeStackFrame() ?: return KotlinStepAction.StepOver
|
||||
val method = location.safeMethod() ?: return KotlinStepAction.StepOver
|
||||
val token = LocationToken.from(stackFrame).takeIf { it.lineNumber > 0 } ?: return KotlinStepAction.StepOver
|
||||
|
||||
val inlinedFunctionArgumentRanges = sourcePosition.collectInlineFunctionArgumentRanges()
|
||||
|
||||
val tokensToSkip = mutableSetOf(token)
|
||||
|
||||
for (candidate in method.allLineLocations() ?: emptyList()) {
|
||||
val candidateKotlinLineNumber = candidate.safeKotlinPreferredLineNumber()
|
||||
val candidateStackFrame = StackFrameForLocation(frameProxy.stackFrame, candidate)
|
||||
val candidateToken = LocationToken.from(candidateStackFrame)
|
||||
|
||||
val isAcceptable = candidateToken.lineNumber >= 0
|
||||
&& candidateToken.lineNumber != token.lineNumber
|
||||
&& inlinedFunctionArgumentRanges.none { range -> range.contains(candidateKotlinLineNumber) }
|
||||
&& candidateToken.inlineVariables.none { it !in token.inlineVariables }
|
||||
|
||||
if (!isAcceptable) {
|
||||
tokensToSkip += candidateToken
|
||||
}
|
||||
}
|
||||
|
||||
return getStepOverAction(
|
||||
location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange,
|
||||
inlineArgumentsToSkip, frameProxy
|
||||
)
|
||||
return KotlinStepAction.StepOverInlined(tokensToSkip, StepOverCallerInfo.from(location))
|
||||
}
|
||||
|
||||
fun getStepOverAction(
|
||||
location: Location,
|
||||
sourceFile: KtFile,
|
||||
range: IntRange,
|
||||
inlineFunctionArguments: List<KtElement>,
|
||||
frameProxy: StackFrameProxyImpl
|
||||
): KotlinStepAction {
|
||||
location.declaringType() ?: return KotlinStepAction.StepOver()
|
||||
|
||||
val methodLocations = location.method().safeAllLineLocations()
|
||||
if (methodLocations.isEmpty()) {
|
||||
return KotlinStepAction.StepOver()
|
||||
}
|
||||
|
||||
fun isThisMethodLocation(nextLocation: Location): Boolean {
|
||||
if (nextLocation.method() != location.method()) {
|
||||
return false
|
||||
private fun SourcePosition.collectInlineFunctionArgumentRanges(): List<IntRange> {
|
||||
return runReadAction {
|
||||
val ranges = mutableListOf<IntRange>()
|
||||
for (arg in findInlinedFunctionArguments(this)) {
|
||||
val range = arg.getLineRange() ?: continue
|
||||
if (range.count() > 1) {
|
||||
ranges += range
|
||||
}
|
||||
}
|
||||
return@runReadAction ranges
|
||||
}
|
||||
}
|
||||
|
||||
val ktLineNumber = nextLocation.lineNumber()
|
||||
if (ktLineNumber !in range) {
|
||||
return false
|
||||
}
|
||||
private class StackFrameForLocation(private val original: StackFrame, private val location: Location) : StackFrame by original {
|
||||
override fun location() = location
|
||||
|
||||
return try {
|
||||
nextLocation.sourceName(KOTLIN_STRATA_NAME) == sourceFile.name
|
||||
} catch (e: AbsentInformationException) {
|
||||
true
|
||||
}
|
||||
override fun visibleVariables(): List<LocalVariable> {
|
||||
return location.method()?.variables()?.filter { it.isVisible(this) } ?: throw AbsentInformationException()
|
||||
}
|
||||
|
||||
fun isBackEdgeLocation(): Boolean {
|
||||
val previousSuitableLocation = methodLocations.reversed()
|
||||
.asSequence()
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
.filter(::isThisMethodLocation)
|
||||
.dropWhile { it.lineNumber() == location.lineNumber() }
|
||||
.firstOrNull()
|
||||
override fun visibleVariableByName(name: String?): LocalVariable {
|
||||
return location.method()?.variablesByName(name)?.firstOrNull { it.isVisible(this) } ?: throw AbsentInformationException()
|
||||
}
|
||||
}
|
||||
|
||||
return previousSuitableLocation != null && previousSuitableLocation.lineNumber() > location.lineNumber()
|
||||
private fun KtElement.getLineRange(): IntRange? {
|
||||
val startLineNumber = getLineNumber(true)
|
||||
val endLineNumber = getLineNumber(false)
|
||||
if (startLineNumber > endLineNumber) {
|
||||
return null
|
||||
}
|
||||
|
||||
val patchedLocation = if (isBackEdgeLocation()) {
|
||||
// Pretend we had already done a backing step
|
||||
methodLocations
|
||||
.filter(::isThisMethodLocation)
|
||||
.firstOrNull { it.lineNumber() == location.lineNumber() } ?: location
|
||||
} else {
|
||||
location
|
||||
}
|
||||
|
||||
val patchedLineNumber = patchedLocation.lineNumber()
|
||||
|
||||
val lambdaArgumentRanges = runReadAction {
|
||||
inlineFunctionArguments.map {
|
||||
val startLineNumber = it.getLineNumber(true) + 1
|
||||
val endLineNumber = it.getLineNumber(false) + 1
|
||||
|
||||
startLineNumber..endLineNumber
|
||||
}
|
||||
}
|
||||
|
||||
val inlineRangeVariables = getInlineRangeLocalVariables(frameProxy)
|
||||
|
||||
// Try to find the range for step over:
|
||||
// - Lines from other files and from functions that are not in range of current one are definitely inlined and should be stepped over.
|
||||
// - Lines in function arguments of inlined functions are inlined too as we found them starting from the position of inlined call.
|
||||
// - Current line locations should also be stepped over.
|
||||
//
|
||||
// We might erroneously extend this range too much when there's a call of function argument or other
|
||||
// inline function in last statement of inline function. The list of inlineRangeVariables will be used later to overcome it.
|
||||
val stepOverLocations = methodLocations
|
||||
.dropWhile { it != patchedLocation }
|
||||
.drop(1)
|
||||
.dropWhile { it.lineNumber() == patchedLineNumber }
|
||||
.takeWhile { loc ->
|
||||
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.lineNumber() in it } || loc.lineNumber() == patchedLineNumber
|
||||
}
|
||||
|
||||
if (stepOverLocations.isNotEmpty()) {
|
||||
return KotlinStepAction.StepOverInlined(
|
||||
StepOverFilterData(
|
||||
patchedLineNumber,
|
||||
stepOverLocations.map { it.lineNumber() }.toSet(),
|
||||
inlineRangeVariables
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return KotlinStepAction.StepOver()
|
||||
return startLineNumber..endLineNumber
|
||||
}
|
||||
|
||||
fun getStepOutAction(
|
||||
@@ -349,7 +247,7 @@ fun getStepOutAction(
|
||||
inlineFunctions: List<KtNamedFunction>,
|
||||
inlinedArgument: KtFunctionLiteral?
|
||||
): KotlinStepAction {
|
||||
val computedReferenceType = location.declaringType() ?: return KotlinStepAction.StepOut()
|
||||
val computedReferenceType = location.declaringType() ?: return KotlinStepAction.StepOut
|
||||
|
||||
val locations = computedReferenceType.safeAllLineLocations()
|
||||
val nextLineLocations = locations
|
||||
@@ -360,15 +258,15 @@ fun getStepOutAction(
|
||||
|
||||
if (inlineFunctions.isNotEmpty()) {
|
||||
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
|
||||
return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver()
|
||||
return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver
|
||||
}
|
||||
|
||||
if (inlinedArgument != null) {
|
||||
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
|
||||
return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver()
|
||||
return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver
|
||||
}
|
||||
|
||||
return KotlinStepAction.StepOver()
|
||||
return KotlinStepAction.StepOver
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
|
||||
@@ -421,15 +319,6 @@ private fun SuspendContextImpl.getNextPositionWithFilter(
|
||||
return null
|
||||
}
|
||||
|
||||
fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> {
|
||||
return stackFrame.safeVisibleVariables()
|
||||
.filter {
|
||||
val name = it.name()
|
||||
name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
|
||||
}
|
||||
.map { it.variable }
|
||||
}
|
||||
|
||||
private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
|
||||
val functionLiteralExpression = elementAt?.getParentOfType<KtLambdaExpression>(false) ?: return null
|
||||
|
||||
|
||||
+51
-20
@@ -16,39 +16,70 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping.filter
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess.JAVA_STRATUM
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.StackFrame
|
||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
import org.jetbrains.kotlin.idea.debugger.safeVariables
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinMethodFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.getInlineRangeLocalVariables
|
||||
|
||||
class StepOverFilterData(
|
||||
val lineNumber: Int,
|
||||
val stepOverLines: Set<Int>,
|
||||
val inlineRangeVariables: List<LocalVariable>
|
||||
)
|
||||
|
||||
class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter {
|
||||
override fun locationMatches(context: SuspendContextImpl, location: Location): Boolean {
|
||||
val frameProxy = context.frameProxy ?: return true
|
||||
|
||||
val currentLine = location.lineNumber()
|
||||
if (!(data.stepOverLines.contains(currentLine))) {
|
||||
return currentLine != data.lineNumber
|
||||
@Suppress("EqualsOrHashCode")
|
||||
data class StepOverCallerInfo(val declaringType: String, val methodName: String?, val methodSignature: String?) {
|
||||
companion object {
|
||||
fun from(location: Location): StepOverCallerInfo {
|
||||
val method = location.safeMethod()
|
||||
val declaringType = location.declaringType().name()
|
||||
val methodName = method?.name()
|
||||
val methodSignature = method?.signature()
|
||||
return StepOverCallerInfo(declaringType, methodName, methodSignature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val visibleInlineVariables = getInlineRangeLocalVariables(frameProxy)
|
||||
data class LocationToken(val lineNumber: Int, val inlineVariables: List<String>) {
|
||||
companion object {
|
||||
fun from(stackFrame: StackFrame): LocationToken {
|
||||
val location = stackFrame.location()
|
||||
val lineNumber = location.safeLineNumber(JAVA_STRATUM)
|
||||
val methodVariables = ArrayList<String>(0)
|
||||
|
||||
// Our ranges check missed exit from inline function. This is when breakpoint was in last statement of inline functions.
|
||||
// This can be observed by inline local range-variables. Absence of any means step out was done.
|
||||
return data.inlineRangeVariables.any { !visibleInlineVariables.contains(it) }
|
||||
for (variable in location.safeMethod()?.safeVariables() ?: emptyList()) {
|
||||
val name = variable.name()
|
||||
if (variable.isVisible(stackFrame) && isFakeLocalVariableForInline(name)) {
|
||||
methodVariables += name
|
||||
}
|
||||
}
|
||||
|
||||
return LocationToken(lineNumber, methodVariables)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinStepOverInlineFilter(
|
||||
val project: Project,
|
||||
private val tokensToSkip: Set<LocationToken>,
|
||||
private val callerInfo: StepOverCallerInfo
|
||||
) : KotlinMethodFilter {
|
||||
override fun locationMatches(context: SuspendContextImpl, location: Location): Boolean {
|
||||
val stackFrame = context.frameProxy?.stackFrame ?: return true
|
||||
val token = LocationToken.from(stackFrame)
|
||||
val callerInfo = StepOverCallerInfo.from(location)
|
||||
|
||||
if (callerInfo.methodName != null && callerInfo.methodSignature != null && this.callerInfo == callerInfo) {
|
||||
return token.lineNumber >= 0 && token !in tokensToSkip
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
throw IllegalStateException() // Should not be called from Kotlin hint
|
||||
throw IllegalStateException("Should not be called from Kotlin hint")
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines(): Range<Int>? = null
|
||||
|
||||
+1
-2
@@ -172,5 +172,4 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) {
|
||||
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
|
||||
frame.safeLocation()?.safeMethod()?.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" &&
|
||||
frame.safeLocation()?.safeLineNumber() ?: 0 < 0
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ functionCallStoredToVariable.kt:43
|
||||
functionCallStoredToVariable.kt:11
|
||||
functionCallStoredToVariable.kt:21
|
||||
functionCallStoredToVariable.kt:22
|
||||
functionCallStoredToVariable.kt:18
|
||||
functionCallStoredToVariable.kt:25
|
||||
functionCallStoredToVariable.kt:28
|
||||
functionCallStoredToVariable.kt:29
|
||||
functionCallStoredToVariable.kt:47
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ continueLabel.kt:5
|
||||
continueLabel.kt:6
|
||||
continueLabel.kt:7
|
||||
continueLabel.kt:8
|
||||
continueLabel.kt:10
|
||||
continueLabel.kt:5
|
||||
continueLabel.kt:6
|
||||
continueLabel.kt:7
|
||||
|
||||
Vendored
-1
@@ -6,7 +6,6 @@ inlineCallInForRangeExpression.kt:8
|
||||
inlineCallInForRangeExpression.kt:9
|
||||
inlineCallInForRangeExpression.kt:8
|
||||
inlineCallInForRangeExpression.kt:9
|
||||
inlineCallInForRangeExpression.kt:8
|
||||
inlineCallInForRangeExpression.kt:11
|
||||
Disconnected from the target VM
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ LineBreakpoint created at noParameterExtensionLambdaArgumentCallInInline3.kt:12
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
noParameterExtensionLambdaArgumentCallInInline3.kt:12
|
||||
noParameterExtensionLambdaArgumentCallInInline3.kt:5
|
||||
noParameterExtensionLambdaArgumentCallInInline3.kt:14
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ LineBreakpoint created at noParameterLambdaArgumentCallInLambda.kt:5 lambdaOrdin
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
noParameterLambdaArgumentCallInLambda.kt:5
|
||||
noParameterLambdaArgumentCallInLambda.kt:6
|
||||
noParameterLambdaArgumentCallInLambda.kt:11
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
+1
@@ -3,6 +3,7 @@ Run Java
|
||||
Connected to the target VM
|
||||
soInlineCallInLastStatementInInlineFunctionArgument.kt:7
|
||||
soInlineCallInLastStatementInInlineFunctionArgument.kt:8
|
||||
soInlineCallInLastStatementInInlineFunctionArgument.kt:14
|
||||
soInlineCallInLastStatementInInlineFunctionArgument.kt:9
|
||||
Disconnected from the target VM
|
||||
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ LineBreakpoint created at soInlineFunOnOneLineFor.kt:17
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
soInlineFunOnOneLineFor.kt:17
|
||||
soInlineFunOnOneLineFor.kt:18
|
||||
soInlineFunOnOneLineFor.kt:8
|
||||
soInlineFunOnOneLineFor.kt:9
|
||||
Disconnected from the target VM
|
||||
|
||||
-2
@@ -1,7 +1,5 @@
|
||||
package soInlineOperatorIterator
|
||||
|
||||
// TODO: Test with current bad behaviour of KT-14296
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
//Breakpoint!
|
||||
val list = listOf("a", "b", "c")
|
||||
|
||||
Vendored
+8
-15
@@ -1,22 +1,15 @@
|
||||
LineBreakpoint created at soInlineOperatorIterator.kt:7
|
||||
LineBreakpoint created at soInlineOperatorIterator.kt:5
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
soInlineOperatorIterator.kt:5
|
||||
soInlineOperatorIterator.kt:6
|
||||
soInlineOperatorIterator.kt:7
|
||||
soInlineOperatorIterator.kt:8
|
||||
soInlineOperatorIterator.kt:23
|
||||
soInlineOperatorIterator.kt:27
|
||||
soInlineOperatorIterator.kt:6
|
||||
soInlineOperatorIterator.kt:7
|
||||
soInlineOperatorIterator.kt:6
|
||||
soInlineOperatorIterator.kt:7
|
||||
soInlineOperatorIterator.kt:6
|
||||
soInlineOperatorIterator.kt:9
|
||||
soInlineOperatorIterator.kt:8
|
||||
soInlineOperatorIterator.kt:23
|
||||
soInlineOperatorIterator.kt:27
|
||||
soInlineOperatorIterator.kt:9
|
||||
soInlineOperatorIterator.kt:8
|
||||
soInlineOperatorIterator.kt:23
|
||||
soInlineOperatorIterator.kt:27
|
||||
soInlineOperatorIterator.kt:9
|
||||
soInlineOperatorIterator.kt:8
|
||||
soInlineOperatorIterator.kt:23
|
||||
soInlineOperatorIterator.kt:11
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@ inline fun bar(f: () -> Unit) {
|
||||
|
||||
fun nop() {}
|
||||
|
||||
// STEP_OVER: 2
|
||||
// STEP_OVER: 3
|
||||
+1
@@ -3,6 +3,7 @@ Run Java
|
||||
Connected to the target VM
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymous.kt:6
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymous.kt:7
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymous.kt:13
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymous.kt:8
|
||||
Disconnected from the target VM
|
||||
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ inline fun bar(f: () -> Unit) {
|
||||
|
||||
fun nop() {}
|
||||
|
||||
// STEP_OVER: 2
|
||||
// STEP_OVER: 3
|
||||
+1
@@ -3,6 +3,7 @@ Run Java
|
||||
Connected to the target VM
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt:6
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt:7
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt:14
|
||||
soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt:9
|
||||
Disconnected from the target VM
|
||||
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@ inline fun bar(f: () -> Unit) {
|
||||
|
||||
fun nop() {}
|
||||
|
||||
// STEP_OVER: 2
|
||||
// STEP_OVER: 3
|
||||
+1
@@ -3,6 +3,7 @@ Run Java
|
||||
Connected to the target VM
|
||||
soLastStatementInInlineFunctionArgumentInPars.kt:6
|
||||
soLastStatementInInlineFunctionArgumentInPars.kt:7
|
||||
soLastStatementInInlineFunctionArgumentInPars.kt:13
|
||||
soLastStatementInInlineFunctionArgumentInPars.kt:8
|
||||
Disconnected from the target VM
|
||||
|
||||
|
||||
idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.kt
Vendored
+1
-1
@@ -18,4 +18,4 @@ inline fun inlineCall(l: () -> Unit) {
|
||||
l()
|
||||
}
|
||||
|
||||
// STEP_OVER: 4
|
||||
// STEP_OVER: 5
|
||||
+1
@@ -3,6 +3,7 @@ Run Java
|
||||
Connected to the target VM
|
||||
stepOverInlineFunWithRecursionCall.kt:6
|
||||
stepOverInlineFunWithRecursionCall.kt:6
|
||||
stepOverInlineFunWithRecursionCall.kt:19
|
||||
stepOverInlineFunWithRecursionCall.kt:9
|
||||
stepOverInlineFunWithRecursionCall.kt:14
|
||||
stepOverInlineFunWithRecursionCall.kt:15
|
||||
|
||||
+31
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess.JAVA_STRATUM
|
||||
import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.jdi.StackFrameProxy
|
||||
@@ -12,6 +13,7 @@ import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
|
||||
fun StackFrameProxyImpl.safeVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||
return wrapAbsentInformationException { visibleVariables() } ?: emptyList()
|
||||
@@ -21,6 +23,10 @@ fun StackFrameProxyImpl.safeVisibleVariableByName(name: String): LocalVariablePr
|
||||
return wrapAbsentInformationException { visibleVariableByName(name) }
|
||||
}
|
||||
|
||||
fun StackFrame.safeVisibleVariables(): List<LocalVariable> {
|
||||
return wrapAbsentInformationException { visibleVariables() } ?: emptyList()
|
||||
}
|
||||
|
||||
fun Method.safeAllLineLocations(): List<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
@@ -61,6 +67,14 @@ fun StackFrameProxy.safeLocation(): Location? {
|
||||
}
|
||||
}
|
||||
|
||||
fun StackFrameProxy.safeStackFrame(): StackFrame? {
|
||||
return try {
|
||||
this.stackFrame
|
||||
} catch (e: EvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Location.safeSourceName(): String? {
|
||||
return wrapAbsentInformationException { this.sourceName() }
|
||||
}
|
||||
@@ -73,8 +87,23 @@ fun Location.safeLineNumber(): Int {
|
||||
return DebuggerUtilsEx.getLineNumber(this, false)
|
||||
}
|
||||
|
||||
fun Location.safeSourceLineNumber(): Int {
|
||||
return DebuggerUtilsEx.getLineNumber(this, true)
|
||||
fun Location.safeLineNumber(stratum: String): Int {
|
||||
return try {
|
||||
lineNumber(stratum)
|
||||
} catch (e: InternalError) {
|
||||
-1
|
||||
} catch (e: IllegalArgumentException) {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
fun Location.safeKotlinPreferredLineNumber(): Int {
|
||||
val kotlinLineNumber = safeLineNumber(KOTLIN_STRATA_NAME)
|
||||
if (kotlinLineNumber > 0) {
|
||||
return kotlinLineNumber
|
||||
}
|
||||
|
||||
return safeLineNumber(JAVA_STRATUM)
|
||||
}
|
||||
|
||||
fun Location.safeMethod(): Method? {
|
||||
|
||||
Reference in New Issue
Block a user