diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt index a4faba207cb..ee0d9e87eb5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt @@ -61,9 +61,9 @@ fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Proj return true } -fun readDebugBytecodeInfo(project: Project, - jvmName: JvmClassName, - file: VirtualFile): BytecodeDebugInfo? { +fun readBytecodeInfo(project: Project, + jvmName: JvmClassName, + file: VirtualFile): BytecodeDebugInfo? { return KotlinDebuggerCaches.getOrReadDebugInfoFromBytecode(project, jvmName, file) } @@ -240,7 +240,7 @@ private fun findAndReadClassFile( val virtualFile = file.virtualFile ?: return null if (!fileFilter(virtualFile)) return null - return readDebugBytecodeInfo(project, jvmClassName, virtualFile) + return readBytecodeInfo(project, jvmClassName, virtualFile) } internal fun getLocationsOfInlinedLine(type: ReferenceType, position: SourcePosition, sourceSearchScope: GlobalSearchScope): List { @@ -272,7 +272,7 @@ private fun inlinedLinesNumbers( val virtualFile = file.virtualFile ?: return listOf() - val debugInfo = readDebugBytecodeInfo(project, jvmClassName, virtualFile) ?: return listOf() + val debugInfo = readBytecodeInfo(project, jvmClassName, virtualFile) ?: return listOf() val smapData = debugInfo.smapData ?: return listOf() val smap = smapData.kotlinStrata ?: return listOf() diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlineFilter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlineFilter.kt index e116e6b04d6..6ecc858ef7d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlineFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlineFilter.kt @@ -24,26 +24,34 @@ import com.sun.jdi.LocalVariable import com.sun.jdi.Location import org.jetbrains.kotlin.idea.debugger.noStrataLineNumber -class KotlinStepOverInlineFilter( +class StepOverFilterData( + val lineNumber: Int, + val stepOverLines: Set, + val inlineRangeVariables: List, val isDexDebug: Boolean, - val project: Project, - val stepOverLines: Set, val fromLine: Int, - val inlineFunRangeVariables: List) : KotlinMethodFilter { - private fun Location.ktLineNumber() = noStrataLineNumber(this, isDexDebug, project) + val skipAfterCodeIndex: Long = -1 +) + +class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter { + private fun Location.ktLineNumber() = noStrataLineNumber(this, data.isDexDebug, project) override fun locationMatches(context: SuspendContextImpl, location: Location): Boolean { val frameProxy = context.frameProxy ?: return true + if (data.skipAfterCodeIndex != -1L && location.codeIndex() > data.skipAfterCodeIndex) { + return false + } + val currentLine = location.ktLineNumber() - if (!(stepOverLines.contains(currentLine))) { - return currentLine != fromLine + if (!(data.stepOverLines.contains(currentLine))) { + return currentLine != data.lineNumber } val visibleInlineVariables = getInlineRangeLocalVariables(frameProxy) // 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 inlineFunRangeVariables.any { !visibleInlineVariables.contains(it) } + return data.inlineRangeVariables.any { !visibleInlineVariables.contains(it) } } override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 4d156358162..93855a5a8f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -265,17 +265,12 @@ private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallE } } - sealed class Action(val position: XSourcePositionImpl? = null, - val lineNumber: Int? = null, - val stepOverLines: Set? = null, - val inlineRangeVariables: List? = null, - val isDexDebug: Boolean = false) { + val stepOverInlineData: StepOverFilterData? = null) { class STEP_OVER : Action() class STEP_OUT : Action() class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) - class STEP_OVER_INLINED(lineNumber: Int, stepOverLines: Set, inlineVariables: List, isDexDebug: Boolean) : Action( - lineNumber = lineNumber, stepOverLines = stepOverLines, inlineRangeVariables = inlineVariables, isDexDebug = isDexDebug) + class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, @@ -289,12 +284,7 @@ sealed class Action(val position: XSourcePositionImpl? = null, is Action.STEP_OUT -> debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext) is Action.STEP_OVER -> debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext) is Action.STEP_OVER_INLINED -> KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction( - KotlinStepOverInlineFilter( - isDexDebug, - debugProcess.project, - stepOverLines!!, - lineNumber ?: -1, - inlineRangeVariables!!)).contextAction(suspendContext) + KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)).contextAction(suspendContext) } } } @@ -336,7 +326,7 @@ fun getStepOverAction( return false } - if (nextLocation.ktLineNumber() !in range) { + if (nextLocation.lineNumber() !in range) { return false } @@ -348,8 +338,10 @@ fun getStepOverAction( } } + val methodLocations = location.method().allLineLocations() + fun isBackEdgeLocation(): Boolean { - val previousSuitableLocation = computedReferenceType.allLineLocations().reversed() + val previousSuitableLocation = methodLocations.reversed() .dropWhile { it != location } .drop(1) .filter(::isLocationSuitable) @@ -361,7 +353,7 @@ fun getStepOverAction( val patchedLocation = if (isBackEdgeLocation()) { // Pretend we had already did a backing step - computedReferenceType.allLineLocations() + methodLocations .filter(::isLocationSuitable) .first { it.ktLineNumber() == location.ktLineNumber() } } @@ -388,7 +380,7 @@ fun getStepOverAction( // // It also thinks that too many lines are inlined when there's a call of function argument or other // inline function in last statement of inline function. The list of inlineRangeVariables is used to overcome it. - val probablyInlinedLocations = computedReferenceType.allLineLocations() + val probablyInlinedLocations = methodLocations .dropWhile { it != patchedLocation } .drop(1) .dropWhile { it.ktLineNumber() == patchedLineNumber } @@ -398,12 +390,25 @@ fun getStepOverAction( .dropWhile { it.ktLineNumber() == patchedLineNumber } if (!probablyInlinedLocations.isEmpty()) { - return Action.STEP_OVER_INLINED( + // Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact. + // GOTO instructions are moved to the end of method and as they don't have proper line, line is obtained from the previous + // instruction. It might be method return or previous GOTO from the inlining. Simple stepping over such function is really + // terrible. On each iteration position jumps to the method end or some previous inline call and then returns back. To prevent + // this filter locations with too big code indexes manually + val returnCodeIndex: Long = if (isDexDebug) { + // TODO: won't work for situation when breakpoint is in inlined method + val locationsOfLine = location.method().locationsOfLine(range.last) + locationsOfLine.map { it.codeIndex() }.max() ?: -1L + } + else -1L + + return Action.STEP_OVER_INLINED(StepOverFilterData( patchedLineNumber, probablyInlinedLocations.map { it.ktLineNumber() }.toSet(), inlineRangeVariables, - isDexDebug - ) + isDexDebug, + returnCodeIndex + )) } return Action.STEP_OVER() diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt index 4b4ffa68a3c..09563cbea2a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt @@ -75,7 +75,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter private fun createHyperlinks(jvmName: JvmClassName, file: VirtualFile, line: Int, project: Project): InlineFunctionHyperLinkInfo? { if (!isInlineFunctionLineNumber(file, line, project)) return null - val debugInfo = readDebugBytecodeInfo(project, jvmName, file) ?: return null + val debugInfo = readBytecodeInfo(project, jvmName, file) ?: return null val smapData = debugInfo.smapData ?: return null val inlineInfos = arrayListOf()