Debugger: Rewrite step out action

This commit fixes the following tests:
 - KotlinSteppingTestGenerated.Custom.testStepOutInlineFunctionStdlib
 - KotlinSteppingTestGenerated.StepOut.testStepOutSeveralInlineArgumentDeepest
This commit is contained in:
Yan Zhulanow
2019-12-23 19:56:22 +09:00
parent cc2fe6b0c6
commit f129ab55c5
3 changed files with 28 additions and 123 deletions
@@ -39,8 +39,6 @@ import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter; 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; import java.util.List;
public class DebuggerSteppingHelper { public class DebuggerSteppingHelper {
@@ -101,12 +99,7 @@ public class DebuggerSteppingHelper {
}; };
} }
public static DebugProcessImpl.ResumeCommand createStepOutCommand( public static DebugProcessImpl.ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints) {
SuspendContextImpl suspendContext,
boolean ignoreBreakpoints,
List<KtNamedFunction> inlineFunctions,
KtFunctionLiteral inlineArgument
) {
DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new ResumeCommand(suspendContext) { return debugProcess.new ResumeCommand(suspendContext) {
@Override @Override
@@ -114,12 +107,8 @@ public class DebuggerSteppingHelper {
try { try {
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy(); StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
if (frameProxy != null) { if (frameProxy != null) {
KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOutAction( Location location = frameProxy.location();
frameProxy.location(), KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOutAction(location, frameProxy);
suspendContext,
inlineFunctions,
inlineArgument
);
createStepRequest( createStepRequest(
suspendContext, getContextThread(), suspendContext, getContextThread(),
@@ -132,7 +121,8 @@ public class DebuggerSteppingHelper {
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(); debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
} }
catch (EvaluateException ignored) { catch (EvaluateException e) {
LOG.error(e);
} }
} }
}; };
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.idea.debugger.stepping package org.jetbrains.kotlin.idea.debugger.stepping
import com.intellij.debugger.NoDataException
import com.intellij.debugger.SourcePosition import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.MethodFilter 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.impl.JvmSteppingCommandProvider
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.xdebugger.impl.XSourcePositionImpl import com.sun.jdi.AbsentInformationException
import com.sun.jdi.* import com.sun.jdi.LocalVariable
import com.sun.jdi.Location
import com.sun.jdi.StackFrame
import org.jetbrains.annotations.TestOnly 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.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils 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.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.KotlinSuspendCallStepOverFilter
import org.jetbrains.kotlin.idea.debugger.stepping.filter.LocationToken import org.jetbrains.kotlin.idea.debugger.stepping.filter.LocationToken
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverCallerInfo import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverCallerInfo
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset 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.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -87,17 +83,8 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
} }
private fun getStepOutCommand(suspendContext: SuspendContextImpl, sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? { private fun getStepOutCommand(suspendContext: SuspendContextImpl, sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
val file = sourcePosition.file as? KtFile ?: return null
if (sourcePosition.line < 0) return null if (sourcePosition.line < 0) return null
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true)
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)
} }
} }
@@ -119,16 +106,6 @@ private fun findInlinedFunctionArguments(sourcePosition: SourcePosition): List<K
return args return args
} }
private fun findInlineFunctions(file: KtFile, offset: Int): List<KtNamedFunction> {
val elementAt = file.findElementAt(offset) ?: return emptyList()
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
val descriptor = containingFunction.unsafeResolveToDescriptor()
if (!InlineUtil.isInline(descriptor)) return emptyList()
return DebuggerUtils.analyzeElementWithInline(containingFunction, false).filterIsInstance<KtNamedFunction>()
}
private fun findInlineFunctionCalls(sourcePosition: SourcePosition): List<KtCallExpression> { private fun findInlineFunctionCalls(sourcePosition: SourcePosition): List<KtCallExpression> {
fun isInlineCall(expr: KtCallExpression): Boolean { fun isInlineCall(expr: KtCallExpression): Boolean {
val resolvedCall = expr.resolveToCall() ?: return false val resolvedCall = expr.resolveToCall() ?: return false
@@ -241,89 +218,28 @@ private fun KtElement.getLineRange(): IntRange? {
return startLineNumber..endLineNumber return startLineNumber..endLineNumber
} }
fun getStepOutAction( fun getStepOutAction(location: Location, frameProxy: StackFrameProxyImpl): KotlinStepAction {
location: Location, val stackFrame = frameProxy.safeStackFrame() ?: return KotlinStepAction.StepOut
suspendContext: SuspendContextImpl, val method = location.safeMethod() ?: return KotlinStepAction.StepOut
inlineFunctions: List<KtNamedFunction>, val token = LocationToken.from(stackFrame).takeIf { it.lineNumber > 0 } ?: return KotlinStepAction.StepOut
inlinedArgument: KtFunctionLiteral? if (token.inlineVariables.isEmpty()) {
): KotlinStepAction { return KotlinStepAction.StepOut
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
} }
if (inlinedArgument != null) { val tokensToSkip = mutableSetOf(token)
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
return position?.let { KotlinStepAction.RunToCursor(it) } ?: KotlinStepAction.StepOver
}
return KotlinStepAction.StepOver for (candidate in method.allLineLocations() ?: emptyList()) {
} val candidateStackFrame = StackFrameForLocation(frameProxy.stackFrame, candidate)
val candidateToken = LocationToken.from(candidateStackFrame)
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( val isAcceptable = candidateToken.lineNumber >= 0
locations: List<Location>, && candidateToken.lineNumber != token.lineNumber
inlineFunctionsToSkip: List<KtNamedFunction> && token.inlineVariables.any { it !in candidateToken.inlineVariables }
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) { offset, elementAt ->
if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) {
return@getNextPositionWithFilter true
}
getInlineArgumentIfAny(elementAt) != null if (!isAcceptable) {
} tokensToSkip += candidateToken
}
private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument(
locations: List<Location>,
inlinedArgumentToSkip: KtFunctionLiteral
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) { offset, _ ->
inlinedArgumentToSkip.textRange.contains(offset)
}
}
private fun SuspendContextImpl.getNextPositionWithFilter(
locations: List<Location>,
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
} }
} }
return null return KotlinStepAction.StepOverInlined(tokensToSkip, StepOverCallerInfo.from(location))
}
private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
val functionLiteralExpression = elementAt?.getParentOfType<KtLambdaExpression>(false) ?: return null
val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL)
if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null
return functionLiteralExpression.functionLiteral
} }
@@ -8,8 +8,7 @@ fun main() {
data class Foo(val a: String) data class Foo(val a: String)
// STEP_INTO: 1 // STEP_INTO: 2
// STEP_OUT: 1
// EXPRESSION: Foo("baz") // EXPRESSION: Foo("baz")
// RESULT: instance of dataClassCopy.Foo(id=ID): LdataClassCopy/Foo; // RESULT: instance of dataClassCopy.Foo(id=ID): LdataClassCopy/Foo;