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 3ea547f9ca6..f550fdf4372 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 @@ -39,8 +39,6 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter; -import org.jetbrains.kotlin.psi.KtFunctionLiteral; -import org.jetbrains.kotlin.psi.KtNamedFunction; import java.util.List; public class DebuggerSteppingHelper { @@ -101,12 +99,7 @@ public class DebuggerSteppingHelper { }; } - public static DebugProcessImpl.ResumeCommand createStepOutCommand( - SuspendContextImpl suspendContext, - boolean ignoreBreakpoints, - List inlineFunctions, - KtFunctionLiteral inlineArgument - ) { + public static DebugProcessImpl.ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints) { DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); return debugProcess.new ResumeCommand(suspendContext) { @Override @@ -114,12 +107,8 @@ public class DebuggerSteppingHelper { try { StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy(); if (frameProxy != null) { - KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOutAction( - frameProxy.location(), - suspendContext, - inlineFunctions, - inlineArgument - ); + Location location = frameProxy.location(); + KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOutAction(location, frameProxy); createStepRequest( suspendContext, getContextThread(), @@ -132,7 +121,8 @@ public class DebuggerSteppingHelper { debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(); } - catch (EvaluateException ignored) { + catch (EvaluateException e) { + LOG.error(e); } } }; 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 386e27a2799..56a08113866 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 @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.idea.debugger.stepping -import com.intellij.debugger.NoDataException import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.MethodFilter @@ -14,26 +13,23 @@ import com.intellij.debugger.impl.DebuggerContextImpl 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.* +import com.sun.jdi.AbsentInformationException +import com.sun.jdi.LocalVariable +import com.sun.jdi.Location +import com.sun.jdi.StackFrame import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall -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.* import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter 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.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.inline.InlineUtil -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import kotlin.math.max import kotlin.math.min @@ -87,17 +83,8 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() { } private fun getStepOutCommand(suspendContext: SuspendContextImpl, sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? { - val file = sourcePosition.file as? KtFile ?: return null if (sourcePosition.line < 0) return null - - val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null - - val inlineFunctions = findInlineFunctions(file, lineStartOffset) - val inlinedArgument = getInlineArgumentIfAny(sourcePosition.elementAt) - - if (inlineFunctions.isEmpty() && inlinedArgument == null) return null - - return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument) + return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true) } } @@ -119,16 +106,6 @@ private fun findInlinedFunctionArguments(sourcePosition: SourcePosition): List { - val elementAt = file.findElementAt(offset) ?: return emptyList() - val containingFunction = elementAt.getParentOfType(false) ?: return emptyList() - - val descriptor = containingFunction.unsafeResolveToDescriptor() - if (!InlineUtil.isInline(descriptor)) return emptyList() - - return DebuggerUtils.analyzeElementWithInline(containingFunction, false).filterIsInstance() -} - private fun findInlineFunctionCalls(sourcePosition: SourcePosition): List { fun isInlineCall(expr: KtCallExpression): Boolean { val resolvedCall = expr.resolveToCall() ?: return false @@ -241,89 +218,28 @@ private fun KtElement.getLineRange(): IntRange? { return startLineNumber..endLineNumber } -fun getStepOutAction( - location: Location, - suspendContext: SuspendContextImpl, - inlineFunctions: List, - inlinedArgument: KtFunctionLiteral? -): KotlinStepAction { - val computedReferenceType = location.declaringType() ?: return KotlinStepAction.StepOut - - val locations = computedReferenceType.safeAllLineLocations() - val nextLineLocations = locations - .dropWhile { it != location } - .drop(1) - .filter { it.method() == location.method() } - .dropWhile { it.lineNumber() == location.lineNumber() } - - if (inlineFunctions.isNotEmpty()) { - val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) - return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver +fun getStepOutAction(location: Location, frameProxy: StackFrameProxyImpl): KotlinStepAction { + val stackFrame = frameProxy.safeStackFrame() ?: return KotlinStepAction.StepOut + val method = location.safeMethod() ?: return KotlinStepAction.StepOut + val token = LocationToken.from(stackFrame).takeIf { it.lineNumber > 0 } ?: return KotlinStepAction.StepOut + if (token.inlineVariables.isEmpty()) { + return KotlinStepAction.StepOut } - if (inlinedArgument != null) { - val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) - return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver - } + val tokensToSkip = mutableSetOf(token) - return KotlinStepAction.StepOver -} + for (candidate in method.allLineLocations() ?: emptyList()) { + val candidateStackFrame = StackFrameForLocation(frameProxy.stackFrame, candidate) + val candidateToken = LocationToken.from(candidateStackFrame) -private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( - locations: List, - inlineFunctionsToSkip: List -): XSourcePositionImpl? { - return getNextPositionWithFilter(locations) { offset, elementAt -> - if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) { - return@getNextPositionWithFilter true - } + val isAcceptable = candidateToken.lineNumber >= 0 + && candidateToken.lineNumber != token.lineNumber + && token.inlineVariables.any { it !in candidateToken.inlineVariables } - getInlineArgumentIfAny(elementAt) != null - } -} - -private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument( - locations: List, - inlinedArgumentToSkip: KtFunctionLiteral -): XSourcePositionImpl? { - return getNextPositionWithFilter(locations) { offset, _ -> - inlinedArgumentToSkip.textRange.contains(offset) - } -} - -private fun SuspendContextImpl.getNextPositionWithFilter( - locations: List, - skip: (Int, PsiElement) -> Boolean -): XSourcePositionImpl? { - for (location in locations) { - val position = runReadAction l@{ - val sourcePosition = try { - this.debugProcess.positionManager.getSourcePosition(location) - } catch (e: NoDataException) { - null - } ?: return@l null - - val file = sourcePosition.file as? KtFile ?: return@l null - val elementAt = sourcePosition.elementAt ?: return@l null - val currentLine = location.lineNumber() - 1 - val lineStartOffset = file.getLineStartOffset(currentLine) ?: return@l null - if (skip(lineStartOffset, elementAt)) return@l null - - XSourcePositionImpl.createByElement(elementAt) - } - if (position != null) { - return position + if (!isAcceptable) { + tokensToSkip += candidateToken } } - return null -} - -private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? { - val functionLiteralExpression = elementAt?.getParentOfType(false) ?: return null - - val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL) - if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null - - return functionLiteralExpression.functionLiteral + return KotlinStepAction.StepOverInlined(tokensToSkip, StepOverCallerInfo.from(location)) } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/dataClassCopy.kt b/idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/dataClassCopy.kt index 3fb798725ff..2421afd6e65 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/dataClassCopy.kt +++ b/idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/dataClassCopy.kt @@ -8,8 +8,7 @@ fun main() { data class Foo(val a: String) -// STEP_INTO: 1 -// STEP_OUT: 1 +// STEP_INTO: 2 // EXPRESSION: Foo("baz") // RESULT: instance of dataClassCopy.Foo(id=ID): LdataClassCopy/Foo;