Step over locations after return in dex debug
This commit is contained in:
@@ -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<Location> {
|
||||
@@ -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()
|
||||
|
||||
@@ -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<Int>,
|
||||
val inlineRangeVariables: List<LocalVariable>,
|
||||
val isDexDebug: Boolean,
|
||||
val project: Project,
|
||||
val stepOverLines: Set<Int>, val fromLine: Int,
|
||||
val inlineFunRangeVariables: List<LocalVariable>) : 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 {
|
||||
|
||||
+25
-20
@@ -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<Int>? = null,
|
||||
val inlineRangeVariables: List<LocalVariable>? = 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<Int>, inlineVariables: List<LocalVariable>, 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()
|
||||
|
||||
@@ -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<InlineFunctionHyperLinkInfo.InlineInfo>()
|
||||
|
||||
Reference in New Issue
Block a user