From 8f29f8bc9da71a81382ba5299c93093ab758690b Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 22 Nov 2019 16:11:51 +0900 Subject: [PATCH] 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. --- .../idea/debugger/KotlinPositionManager.kt | 3 +- .../stepping/DebuggerSteppingHelper.java | 8 +- .../debugger/stepping/KotlinSourcePosition.kt | 39 --- .../debugger/stepping/KotlinStepAction.kt | 29 +- .../KotlinStepOverInlinedLinesHint.kt | 25 +- .../stepping/KotlinSteppingCommandProvider.kt | 253 +++++------------- .../filter/KotlinStepOverInlineFilter.kt | 71 +++-- .../coroutine/command/CoroutineBuilder.kt | 3 +- .../custom/functionCallStoredToVariable.out | 2 +- .../stepping/stepInto/continueLabel.out | 1 + .../inlineCallInForRangeExpression.out | 1 - ...erExtensionLambdaArgumentCallInInline3.out | 2 +- .../noParameterLambdaArgumentCallInLambda.out | 2 +- ...nLastStatementInInlineFunctionArgument.out | 1 + .../stepOver/soInlineFunOnOneLineFor.out | 1 - .../stepOver/soInlineOperatorIterator.kt | 2 - .../stepOver/soInlineOperatorIterator.out | 23 +- ...mentInInlineFunctionArgumentAsAnonymous.kt | 2 +- ...entInInlineFunctionArgumentAsAnonymous.out | 1 + ...eFunctionArgumentAsAnonymousParNextLine.kt | 2 +- ...FunctionArgumentAsAnonymousParNextLine.out | 1 + ...StatementInInlineFunctionArgumentInPars.kt | 2 +- ...tatementInInlineFunctionArgumentInPars.out | 1 + .../stepOverInlineFunWithRecursionCall.kt | 2 +- .../stepOverInlineFunWithRecursionCall.out | 1 + .../kotlin/idea/debugger/safeUtil.kt | 33 ++- 26 files changed, 206 insertions(+), 305 deletions(-) delete mode 100644 idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSourcePosition.kt diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 72d83eff98e..7ec4781cd9a 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -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 } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java index ab08587c2f4..3ea547f9ca6 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java @@ -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(), diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSourcePosition.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSourcePosition.kt deleted file mode 100644 index 608a85ba431..00000000000 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSourcePosition.kt +++ /dev/null @@ -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() - .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) - } - } -} diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt index efafb8d148c..88253189a87 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt @@ -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, 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) } } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt index fcbcf0a6434..604dee9859e 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index da1f5c9991b..386e27a2799 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -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 { - val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition) - return getInlineArgumentsIfAny(inlineFunctionCalls) +private fun findInlinedFunctionArguments(sourcePosition: SourcePosition): List { + val args = mutableListOf() + + 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 { +private fun findInlineFunctions(file: KtFile, offset: Int): List { val elementAt = file.findElementAt(offset) ?: return emptyList() val containingFunction = elementAt.getParentOfType(false) ?: return emptyList() @@ -155,41 +129,7 @@ private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List() } -private fun getInlineArgumentsIfAny(inlineFunctionCalls: List): List { - return inlineFunctionCalls.flatMap { - it.valueArguments - .map(::getArgumentExpression) - .filterIsInstance() - } -} - -private fun getArgumentExpression(it: ValueArgument) = - (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() - -private fun getInlineArgumentsCallsIfAny( - sourcePosition: SourcePosition, - declarationDescriptor: DeclarationDescriptor -): List? { - 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 { +private fun findInlineFunctionCalls(sourcePosition: SourcePosition): List { 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, - 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 { + return runReadAction { + val ranges = mutableListOf() + 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 { + 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, 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 { - 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(false) ?: return null diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt index 96f4fedb2fe..ba5e61c3b84 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinStepOverInlineFilter.kt @@ -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, - val inlineRangeVariables: List -) - -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) { + companion object { + fun from(stackFrame: StackFrame): LocationToken { + val location = stackFrame.location() + val lineNumber = location.safeLineNumber(JAVA_STRATUM) + val methodVariables = ArrayList(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, + 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? = null diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt index 555a08c53c5..7703a27fc28 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt @@ -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 -} - +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/custom/functionCallStoredToVariable.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/custom/functionCallStoredToVariable.out index f0afcb86766..c41543d1ea9 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/custom/functionCallStoredToVariable.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/custom/functionCallStoredToVariable.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepInto/continueLabel.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepInto/continueLabel.out index 5f61c05af23..9f2bac02ba4 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepInto/continueLabel.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepInto/continueLabel.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/inlineCallInForRangeExpression.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/inlineCallInForRangeExpression.out index 49a2c93441a..09f66c8c145 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/inlineCallInForRangeExpression.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/inlineCallInForRangeExpression.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterExtensionLambdaArgumentCallInInline3.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterExtensionLambdaArgumentCallInInline3.out index f612c7f22b6..68823274b24 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterExtensionLambdaArgumentCallInInline3.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterExtensionLambdaArgumentCallInInline3.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterLambdaArgumentCallInLambda.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterLambdaArgumentCallInLambda.out index 83a0085e887..addbd8580c2 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterLambdaArgumentCallInLambda.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/noParameterLambdaArgumentCallInLambda.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineCallInLastStatementInInlineFunctionArgument.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineCallInLastStatementInInlineFunctionArgument.out index b41b510fe09..22445529a83 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineCallInLastStatementInInlineFunctionArgument.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineCallInLastStatementInInlineFunctionArgument.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineFunOnOneLineFor.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineFunOnOneLineFor.out index 13ade0aa699..370afa6c8ea 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineFunOnOneLineFor.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineFunOnOneLineFor.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.kt index 4521756877c..5d7524f59ae 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.kt +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.kt @@ -1,7 +1,5 @@ package soInlineOperatorIterator -// TODO: Test with current bad behaviour of KT-14296 - fun main(args: Array) { //Breakpoint! val list = listOf("a", "b", "c") diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.out index a913a3d55d9..56c392ddf21 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soInlineOperatorIterator.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.kt index e466cafcf98..b8374f753a6 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.kt +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.kt @@ -14,4 +14,4 @@ inline fun bar(f: () -> Unit) { fun nop() {} -// STEP_OVER: 2 \ No newline at end of file +// STEP_OVER: 3 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.out index 302b4e6f864..e1e2d623842 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymous.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt index 8ecd52af253..a03b4be5bc2 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.kt @@ -15,4 +15,4 @@ inline fun bar(f: () -> Unit) { fun nop() {} -// STEP_OVER: 2 \ No newline at end of file +// STEP_OVER: 3 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.out index 2d20bef6e92..f6b9dcfd1f0 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentAsAnonymousParNextLine.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.kt index 87f75250ebc..1b3bb2f4e28 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.kt +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.kt @@ -14,4 +14,4 @@ inline fun bar(f: () -> Unit) { fun nop() {} -// STEP_OVER: 2 \ No newline at end of file +// STEP_OVER: 3 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.out index e93173ec172..227e946aab2 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/soLastStatementInInlineFunctionArgumentInPars.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.kt index a04bebf526a..b8e3232ad40 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.kt +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.kt @@ -18,4 +18,4 @@ inline fun inlineCall(l: () -> Unit) { l() } -// STEP_OVER: 4 \ No newline at end of file +// STEP_OVER: 5 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.out index 17eddeec2ec..8ddec4c45c9 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/stepOverInlineFunWithRecursionCall.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt index d7c406a2392..9780671bb66 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt @@ -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 { return wrapAbsentInformationException { visibleVariables() } ?: emptyList() @@ -21,6 +23,10 @@ fun StackFrameProxyImpl.safeVisibleVariableByName(name: String): LocalVariablePr return wrapAbsentInformationException { visibleVariableByName(name) } } +fun StackFrame.safeVisibleVariables(): List { + return wrapAbsentInformationException { visibleVariables() } ?: emptyList() +} + fun Method.safeAllLineLocations(): List { 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? {