Debugger: Cleanup stepping code

This commit is contained in:
Yan Zhulanow
2019-08-08 18:31:31 +09:00
parent 00a06ef9b9
commit d7eb0f4bb0
8 changed files with 51 additions and 53 deletions
@@ -45,14 +45,14 @@ import java.util.List;
public class DebuggerSteppingHelper { public class DebuggerSteppingHelper {
private static Logger LOG = Logger.getInstance(DebuggerSteppingHelper.class); private static final Logger LOG = Logger.getInstance(DebuggerSteppingHelper.class);
public static DebugProcessImpl.ResumeCommand createStepOverCommand( public static DebugProcessImpl.ResumeCommand createStepOverCommand(
final SuspendContextImpl suspendContext, SuspendContextImpl suspendContext,
final boolean ignoreBreakpoints, boolean ignoreBreakpoints,
final KotlinSteppingCommandProvider.KotlinSourcePosition kotlinSourcePosition KotlinSteppingCommandProvider.KotlinSourcePosition kotlinSourcePosition
) { ) {
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new ResumeCommand(suspendContext) { return debugProcess.new ResumeCommand(suspendContext) {
@Override @Override
@@ -88,12 +88,12 @@ public class DebuggerSteppingHelper {
} }
public static DebugProcessImpl.ResumeCommand createStepOutCommand( public static DebugProcessImpl.ResumeCommand createStepOutCommand(
final SuspendContextImpl suspendContext, SuspendContextImpl suspendContext,
final boolean ignoreBreakpoints, boolean ignoreBreakpoints,
final List<KtNamedFunction> inlineFunctions, List<KtNamedFunction> inlineFunctions,
final KtFunctionLiteral inlineArgument KtFunctionLiteral inlineArgument
) { ) {
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new ResumeCommand(suspendContext) { return debugProcess.new ResumeCommand(suspendContext) {
@Override @Override
public void contextAction() { public void contextAction() {
@@ -129,7 +129,8 @@ public class DebuggerSteppingHelper {
@NotNull SuspendContextImpl suspendContext, @NotNull SuspendContextImpl suspendContext,
@Nullable ThreadReferenceProxyImpl stepThread, @Nullable ThreadReferenceProxyImpl stepThread,
@NotNull EventRequestManager requestManager, @NotNull EventRequestManager requestManager,
int size, int depth @SuppressWarnings("SameParameterValue") int size,
@SuppressWarnings("SameParameterValue") int depth
) { ) {
if (stepThread == null) { if (stepThread == null) {
return; return;
@@ -170,7 +171,7 @@ public class DebuggerSteppingHelper {
// copied from DebugProcessImpl.getActiveFilters // copied from DebugProcessImpl.getActiveFilters
@NotNull @NotNull
private static List<ClassFilter> getActiveFilters() { private static List<ClassFilter> getActiveFilters() {
List<ClassFilter> activeFilters = new ArrayList<ClassFilter>(); List<ClassFilter> activeFilters = new ArrayList<>();
DebuggerSettings settings = DebuggerSettings.getInstance(); DebuggerSettings settings = DebuggerSettings.getInstance();
if (settings.TRACING_FILTERS_ENABLED) { if (settings.TRACING_FILTERS_ENABLED) {
for (ClassFilter filter : settings.getSteppingFilters()) { for (ClassFilter filter : settings.getSteppingFilters()) {
@@ -81,7 +81,7 @@ class KotlinBasicStepMethodFilter(
} }
val declaration = declarationPtr?.element val declaration = declarationPtr?.element
?: return true // Element is lost. But we know that name is matches, so stop. ?: return true // Element is lost. But we know that name is matches, so stop.
if (currentDeclaration.isEquivalentTo(declaration)) { if (currentDeclaration.isEquivalentTo(declaration)) {
return true return true
@@ -30,8 +30,7 @@ class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java) val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
if (accessor != null && accessor.isGetter) { if (accessor != null && accessor.isGetter) {
val body = accessor.bodyExpression return when (val body = accessor.bodyExpression) {
return when (body) {
is KtBlockExpression -> { is KtBlockExpression -> {
// val a: Int get() { return field } // val a: Int get() { return field }
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
@@ -54,7 +54,7 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
val elementAtOffset = position.elementAt ?: return emptyList() val elementAtOffset = position.elementAt ?: return emptyList()
val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement
?: return emptyList() ?: return emptyList()
val elementTextRange = element.textRange ?: return emptyList() val elementTextRange = element.textRange ?: return emptyList()
@@ -63,7 +63,9 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
private fun doStep( private fun doStep(
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
stepThread: ThreadReferenceProxyImpl, stepThread: ThreadReferenceProxyImpl,
size: Int, depth: Int, hint: RequestHint size: Int,
@Suppress("SameParameterValue") depth: Int,
hint: RequestHint
) { ) {
val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod( val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod(
"doStep", "doStep",
@@ -80,7 +82,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
return getFromField(DebugProcessImpl::class.java.declaredFields.single { it.type == fieldType }) return getFromField(DebugProcessImpl::class.java.declaredFields.single { it.type == fieldType })
} }
private fun <T> getFromField(fieldName: String): T { private fun <T> getFromField(@Suppress("SameParameterValue") fieldName: String): T {
return getFromField(DebugProcessImpl::class.java.getDeclaredField(fieldName)) return getFromField(DebugProcessImpl::class.java.getDeclaredField(fieldName))
} }
@@ -104,7 +106,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
// there could be explicit resume as a result of call to voteSuspend() // there could be explicit resume as a result of call to voteSuspend()
// e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_ // e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
// resuming and all breakpoints in other threads will be ignored. // resuming and all breakpoints in other threads will be ignored.
// As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens // As resume() implicitly clears the filter, the filter must be always applied _before_ any resume() action happens
val breakpointManager = DebuggerManagerEx.getInstanceEx(project).breakpointManager val breakpointManager = DebuggerManagerEx.getInstanceEx(project).breakpointManager
breakpointManager.applyThreadFilter(debuggerProcess, thread.threadReference) breakpointManager.applyThreadFilter(debuggerProcess, thread.threadReference)
} }
@@ -30,8 +30,9 @@ class KotlinStepOverInlinedLinesHint(
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
methodFilter: KotlinMethodFilter methodFilter: KotlinMethodFilter
) : RequestHint(stepThread, suspendContext, methodFilter) { ) : RequestHint(stepThread, suspendContext, methodFilter) {
private companion object {
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java) private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
}
private val filter = methodFilter private val filter = methodFilter
@@ -259,7 +259,7 @@ private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallE
return allFilteredCalls.filter { return allFilteredCalls.filter {
val shouldInclude = it.getLineNumber() in linesRange val shouldInclude = it.getLineNumber() in linesRange
if (shouldInclude) { if (shouldInclude) {
linesRange = min(linesRange.start, it.getLineNumber())..max(linesRange.endInclusive, it.getLineNumber(false)) linesRange = min(linesRange.first, it.getLineNumber())..max(linesRange.last, it.getLineNumber(false))
} }
shouldInclude shouldInclude
} }
@@ -269,17 +269,17 @@ sealed class Action(
val position: XSourcePositionImpl? = null, val position: XSourcePositionImpl? = null,
val stepOverInlineData: StepOverFilterData? = null val stepOverInlineData: StepOverFilterData? = null
) { ) {
class STEP_OVER : Action() { class StepOver : Action() {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) = override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext) debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
} }
class STEP_OUT : Action() { class StepOut : Action() {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) = override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext) debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext)
} }
class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) { class RunToCursor(position: XSourcePositionImpl) : Action(position) {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) { override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
return runReadAction { return runReadAction {
debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints) debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints)
@@ -287,7 +287,7 @@ sealed class Action(
} }
} }
class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) { class StepOverInlined(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) { override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction( return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(
KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!) KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)
@@ -326,13 +326,13 @@ fun getStepOverAction(
frameProxy: StackFrameProxyImpl, frameProxy: StackFrameProxyImpl,
isDexDebug: Boolean isDexDebug: Boolean
): Action { ): Action {
location.declaringType() ?: return Action.STEP_OVER() location.declaringType() ?: return Action.StepOver()
val project = sourceFile.project val project = sourceFile.project
val methodLocations = location.method().safeAllLineLocations() val methodLocations = location.method().safeAllLineLocations()
if (methodLocations.isEmpty()) { if (methodLocations.isEmpty()) {
return Action.STEP_OVER() return Action.StepOver()
} }
val locationsLineAndFile = methodLocations.keysToMap { ktLocationInfo(it, isDexDebug, project, true) } val locationsLineAndFile = methodLocations.keysToMap { ktLocationInfo(it, isDexDebug, project, true) }
@@ -354,15 +354,16 @@ fun getStepOverAction(
return false return false
} }
try { return try {
return nextLocation.ktFileName() == sourceFile.name nextLocation.ktFileName() == sourceFile.name
} catch (e: AbsentInformationException) { } catch (e: AbsentInformationException) {
return true true
} }
} }
fun isBackEdgeLocation(): Boolean { fun isBackEdgeLocation(): Boolean {
val previousSuitableLocation = methodLocations.reversed() val previousSuitableLocation = methodLocations.reversed()
.asSequence()
.dropWhile { it != location } .dropWhile { it != location }
.drop(1) .drop(1)
.filter(::isThisMethodLocation) .filter(::isThisMethodLocation)
@@ -409,8 +410,8 @@ fun getStepOverAction(
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber !isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber
} }
if (!stepOverLocations.isEmpty()) { if (stepOverLocations.isNotEmpty()) {
// Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact. // Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that, being dex-processed, 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 // 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 // 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 // terrible. On each iteration position jumps to the method end or some previous inline call and then returns back. To prevent
@@ -425,7 +426,7 @@ fun getStepOverAction(
} }
} else -1L } else -1L
return Action.STEP_OVER_INLINED( return Action.StepOverInlined(
StepOverFilterData( StepOverFilterData(
patchedLineNumber, patchedLineNumber,
stepOverLocations.map { it.ktLineNumber() }.toSet(), stepOverLocations.map { it.ktLineNumber() }.toSet(),
@@ -436,7 +437,7 @@ fun getStepOverAction(
) )
} }
return Action.STEP_OVER() return Action.StepOver()
} }
fun getStepOutAction( fun getStepOutAction(
@@ -445,7 +446,7 @@ fun getStepOutAction(
inlineFunctions: List<KtNamedFunction>, inlineFunctions: List<KtNamedFunction>,
inlinedArgument: KtFunctionLiteral? inlinedArgument: KtFunctionLiteral?
): Action { ): Action {
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT() val computedReferenceType = location.declaringType() ?: return Action.StepOut()
val locations = computedReferenceType.safeAllLineLocations() val locations = computedReferenceType.safeAllLineLocations()
val nextLineLocations = locations val nextLineLocations = locations
@@ -456,15 +457,15 @@ fun getStepOutAction(
if (inlineFunctions.isNotEmpty()) { if (inlineFunctions.isNotEmpty()) {
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER() return position?.let { Action.RunToCursor(it) } ?: Action.StepOver()
} }
if (inlinedArgument != null) { if (inlinedArgument != null) {
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER() return position?.let { Action.RunToCursor(it) } ?: Action.StepOver()
} }
return Action.STEP_OVER() return Action.StepOver()
} }
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
@@ -578,11 +579,11 @@ private fun findReturnFromDexBytecode(method: Method): Long {
} }
object DexBytecode { object DexBytecode {
val RETURN_VOID = 0x0e const val RETURN_VOID = 0x0e
val RETURN = 0x0f const val RETURN = 0x0f
val RETURN_WIDE = 0x10 const val RETURN_WIDE = 0x10
val RETURN_OBJECT = 0x11 const val RETURN_OBJECT = 0x11
val GOTO = 0x28 const val GOTO = 0x28
val MOVE = 0x01 const val MOVE = 0x01
} }
@@ -31,13 +31,7 @@ import java.lang.reflect.Field
internal class RequestHintWithMethodFilter( internal class RequestHintWithMethodFilter(
stepThread: ThreadReferenceProxyImpl, stepThread: ThreadReferenceProxyImpl,
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
@MagicConstant( @MagicConstant(intValues = [StepRequest.STEP_INTO.toLong(), StepRequest.STEP_OVER.toLong(), StepRequest.STEP_OUT.toLong()]) depth: Int,
intValues = longArrayOf(
StepRequest.STEP_INTO.toLong(),
StepRequest.STEP_OVER.toLong(),
StepRequest.STEP_OUT.toLong()
)
) depth: Int,
methodFilter: MethodFilter methodFilter: MethodFilter
) : RequestHint(stepThread, suspendContext, methodFilter) { ) : RequestHint(stepThread, suspendContext, methodFilter) {
private var targetMethodMatched = false private var targetMethodMatched = false
@@ -49,7 +43,7 @@ internal class RequestHintWithMethodFilter(
} }
} }
private fun findFieldWithValue(value: Int, type: Class<*>): Field? { private fun findFieldWithValue(@Suppress("SameParameterValue") value: Int, @Suppress("SameParameterValue") type: Class<*>): Field? {
return RequestHint::class.java.declaredFields.firstOrNull { field -> return RequestHint::class.java.declaredFields.firstOrNull { field ->
if (field.type == type) { if (field.type == type) {
field.isAccessible = true field.isAccessible = true