Step over locations after return in dex debug

This commit is contained in:
Nikolay Krasko
2016-10-30 02:33:07 +03:00
parent f07b5eaea1
commit a2652832e3
4 changed files with 47 additions and 34 deletions
@@ -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 {
@@ -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>()