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:
Yan Zhulanow
2019-11-22 16:11:51 +09:00
parent b7449c2d6e
commit 8f29f8bc9d
26 changed files with 206 additions and 305 deletions
@@ -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
}
@@ -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(),
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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
@@ -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
@@ -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
@@ -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
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
@@ -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
@@ -14,4 +14,4 @@ inline fun bar(f: () -> Unit) {
fun nop() {}
// STEP_OVER: 2
// STEP_OVER: 3
@@ -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
@@ -15,4 +15,4 @@ inline fun bar(f: () -> Unit) {
fun nop() {}
// STEP_OVER: 2
// STEP_OVER: 3
@@ -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
@@ -14,4 +14,4 @@ inline fun bar(f: () -> Unit) {
fun nop() {}
// STEP_OVER: 2
// STEP_OVER: 3
@@ -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
@@ -18,4 +18,4 @@ inline fun inlineCall(l: () -> Unit) {
l()
}
// STEP_OVER: 4
// STEP_OVER: 5
@@ -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
@@ -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? {