Debugger: Cleanup stepping code
This commit is contained in:
+13
-12
@@ -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()) {
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
+1
-2
@@ -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
|
||||||
|
|||||||
+1
-1
@@ -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()
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -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
|
||||||
|
|
||||||
|
|||||||
+25
-24
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user