Implement new stepping for suspend functions
This commit is contained in:
@@ -11,9 +11,7 @@ import com.intellij.debugger.engine.SuspendContext
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager
|
||||
import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
|
||||
import org.jetbrains.kotlin.idea.debugger.isOneLineMethod
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
|
||||
class KotlinExtraSteppingFilter : ExtraSteppingFilter {
|
||||
@@ -43,7 +41,7 @@ class KotlinExtraSteppingFilter : ExtraSteppingFilter {
|
||||
return false
|
||||
} ?: return false
|
||||
|
||||
if (isOnSuspendReturnOrReenter(location) && !isOneLineMethod(location)) {
|
||||
if (isInSuspendMethod(location) && isOnSuspendReturnOrReenter(location) && !isOneLineMethod(location)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+19
-10
@@ -11,8 +11,10 @@ import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||
@@ -181,6 +183,17 @@ private class MockStackFrame(private val location: Location, private val vm: Vir
|
||||
private const val DO_RESUME_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)Ljava/lang/Object;"
|
||||
private const val INVOKE_SUSPEND_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||
|
||||
fun StackFrameProxyImpl.isOnSuspensionPoint(): Boolean {
|
||||
val location = this.safeLocation() ?: return false
|
||||
|
||||
if (isInSuspendMethod(location)) {
|
||||
val firstLocation = getFirstMethodLocation(location) ?: return false
|
||||
return firstLocation.safeLineNumber() == location.safeLineNumber() && firstLocation.codeIndex() != location.codeIndex()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun isInSuspendMethod(location: Location): Boolean {
|
||||
val method = location.method()
|
||||
val signature = method.signature()
|
||||
@@ -194,22 +207,18 @@ fun isInSuspendMethod(location: Location): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun suspendFunctionFirstLineLocation(location: Location): Int? {
|
||||
if (!isInSuspendMethod(location)) {
|
||||
private fun getFirstMethodLocation(location: Location): Location? {
|
||||
val firstLocation = location.safeMethod()?.location() ?: return null
|
||||
if (firstLocation.safeLineNumber() < 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
val lineNumber = location.method().location()?.lineNumber()
|
||||
if (lineNumber == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return lineNumber
|
||||
return firstLocation
|
||||
}
|
||||
|
||||
fun isOnSuspendReturnOrReenter(location: Location): Boolean {
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return false
|
||||
return suspendStartLineNumber == location.lineNumber()
|
||||
val firstLocation = getFirstMethodLocation(location) ?: return false
|
||||
return firstLocation.safeLineNumber() == location.safeLineNumber()
|
||||
}
|
||||
|
||||
fun isLastLineLocationInMethod(location: Location): Boolean {
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.request.EventRequest
|
||||
|
||||
abstract class AbstractCoroutineBreakpointFacility {
|
||||
abstract fun installCoroutineResumedBreakpoint(context: SuspendContextImpl, location: Location, method: Method): Boolean
|
||||
|
||||
protected fun Breakpoint<*>.setSuspendPolicy(context: SuspendContextImpl) {
|
||||
suspendPolicy = when (context.suspendPolicy) {
|
||||
EventRequest.SUSPEND_ALL -> DebuggerSettings.SUSPEND_ALL
|
||||
EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
|
||||
EventRequest.SUSPEND_NONE -> DebuggerSettings.SUSPEND_NONE
|
||||
else -> DebuggerSettings.SUSPEND_ALL
|
||||
}
|
||||
}
|
||||
|
||||
protected fun Breakpoint<*>.stepOverSuspendSwitch(action: SuspendContextCommandImpl, debugProcess: DebugProcessImpl) {
|
||||
val suspendContext = action.suspendContext
|
||||
if (suspendContext != null) {
|
||||
DebuggerSteppingHelper.createStepOverCommandForSuspendSwitch(suspendContext).contextAction(suspendContext)
|
||||
}
|
||||
debugProcess.requestsManager.deleteRequest(this)
|
||||
}
|
||||
|
||||
|
||||
protected fun applyEmptyThreadFilter(debugProcess: DebugProcessImpl) {
|
||||
// TODO this is nasty. Find a way to apply an empty thread filter only to the newly created breakpoint
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
|
||||
breakpointManager.applyThreadFilter(debugProcess, null)
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.StepIntoMethodBreakpoint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.event.LocatableEvent
|
||||
|
||||
object CoroutineBreakpointFacility : AbstractCoroutineBreakpointFacility() {
|
||||
override fun installCoroutineResumedBreakpoint(context: SuspendContextImpl, location: Location, method: Method): Boolean {
|
||||
val debugProcess = context.debugProcess
|
||||
val project = debugProcess.project
|
||||
|
||||
val breakpoint = object : StepIntoMethodBreakpoint(location.declaringType().name(), method.name(), method.signature(), project) {
|
||||
override fun processLocatableEvent(action: SuspendContextCommandImpl, event: LocatableEvent): Boolean {
|
||||
val result = super.processLocatableEvent(action, event)
|
||||
if (result) {
|
||||
stepOverSuspendSwitch(action, debugProcess)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
breakpoint.setSuspendPolicy(context)
|
||||
applyEmptyThreadFilter(debugProcess)
|
||||
breakpoint.createRequest(debugProcess)
|
||||
debugProcess.setSteppingBreakpoint(breakpoint)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.ui.breakpoints.RunToCursorBreakpoint
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.event.LocatableEvent
|
||||
|
||||
object CoroutineBreakpointFacility : AbstractCoroutineBreakpointFacility() {
|
||||
override fun installCoroutineResumedBreakpoint(context: SuspendContextImpl, location: Location, method: Method): Boolean {
|
||||
val debugProcess = context.debugProcess
|
||||
val project = debugProcess.project
|
||||
|
||||
val methodLocation = method.location()
|
||||
val position = debugProcess.positionManager.getSourcePosition(methodLocation) ?: return false
|
||||
|
||||
val breakpoint = object : RunToCursorBreakpoint(project, position, false) {
|
||||
override fun processLocatableEvent(action: SuspendContextCommandImpl, event: LocatableEvent?): Boolean {
|
||||
val result = super.processLocatableEvent(action, event)
|
||||
if (result) {
|
||||
stepOverSuspendSwitch(action, debugProcess)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
breakpoint.setSuspendPolicy(context)
|
||||
applyEmptyThreadFilter(debugProcess)
|
||||
breakpoint.createRequest(debugProcess)
|
||||
debugProcess.setRunToCursorBreakpoint(breakpoint)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+43
-47
@@ -38,7 +38,9 @@ import com.sun.jdi.request.StepRequest;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter;
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtilKt;
|
||||
import org.jetbrains.kotlin.idea.debugger.SafeUtilKt;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DebuggerSteppingHelper {
|
||||
@@ -54,47 +56,44 @@ public class DebuggerSteppingHelper {
|
||||
return debugProcess.new ResumeCommand(suspendContext) {
|
||||
@Override
|
||||
public void contextAction() {
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
Location location = frameProxy == null ? null : SafeUtilKt.safeLocation(frameProxy);
|
||||
|
||||
Location location = frameProxy.location();
|
||||
KotlinStepAction action = KotlinSteppingCommandProviderKt
|
||||
if (location != null) {
|
||||
KotlinStepAction action = KotlinSteppingCommandProviderKt
|
||||
.getStepOverAction(location, sourcePosition, suspendContext, frameProxy);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction();
|
||||
}
|
||||
catch (EvaluateException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter(
|
||||
SuspendContextImpl suspendContext,
|
||||
boolean ignoreBreakpoints,
|
||||
KotlinSuspendCallStepOverFilter methodFilter
|
||||
) {
|
||||
public static DebugProcessImpl.ResumeCommand createStepOverCommandForSuspendSwitch(SuspendContextImpl suspendContext) {
|
||||
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) {
|
||||
@NotNull
|
||||
@Override
|
||||
return debugProcess.new StepOverCommand(suspendContext, false, null, StepRequest.STEP_MIN) {
|
||||
@NotNull @Override
|
||||
protected RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread) {
|
||||
@SuppressWarnings("MagicConstant")
|
||||
RequestHint hint = new RequestHintWithMethodFilter(stepThread, suspendContext, StepRequest.STEP_OVER, methodFilter);
|
||||
hint.setRestoreBreakpoints(ignoreBreakpoints);
|
||||
hint.setIgnoreFilters(ignoreBreakpoints || debugProcess.getSession().shouldIgnoreSteppingFilters());
|
||||
RequestHint hint = new RequestHint(stepThread, suspendContext, StepRequest.STEP_MIN, StepRequest.STEP_OVER, myMethodFilter) {
|
||||
@Override
|
||||
public int getNextStepDepth(SuspendContextImpl context) {
|
||||
StackFrameProxyImpl frameProxy = context.getFrameProxy();
|
||||
if (frameProxy != null && DebuggerUtilKt.isOnSuspensionPoint(frameProxy)) {
|
||||
return StepRequest.STEP_OVER;
|
||||
}
|
||||
|
||||
return super.getNextStepDepth(context);
|
||||
}
|
||||
};
|
||||
hint.setIgnoreFilters(suspendContext.getDebugProcess().getSession().shouldIgnoreSteppingFilters());
|
||||
return hint;
|
||||
}
|
||||
};
|
||||
@@ -105,26 +104,22 @@ public class DebuggerSteppingHelper {
|
||||
return debugProcess.new ResumeCommand(suspendContext) {
|
||||
@Override
|
||||
public void contextAction() {
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
Location location = frameProxy.location();
|
||||
KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOutAction(location, frameProxy);
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
Location location = frameProxy == null ? null : SafeUtilKt.safeLocation(frameProxy);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
if (location != null) {
|
||||
KotlinStepAction action = KotlinSteppingCommandProviderKt.getStepOutAction(location, frameProxy);
|
||||
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
|
||||
}
|
||||
catch (EvaluateException e) {
|
||||
LOG.error(e);
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -191,8 +186,9 @@ public class DebuggerSteppingHelper {
|
||||
try {
|
||||
if (thread != null && thread.frameCount() > 0) {
|
||||
StackFrameProxyImpl stackFrame = thread.frame(0);
|
||||
Location location = stackFrame == null ? null : SafeUtilKt.safeLocation(stackFrame);
|
||||
|
||||
if (stackFrame != null) {
|
||||
Location location = stackFrame.location();
|
||||
ReferenceType referenceType = location == null ? null : location.declaringType();
|
||||
if (referenceType != null) {
|
||||
return referenceType.name();
|
||||
|
||||
+3
-13
@@ -7,14 +7,12 @@ package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinStepOverFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.LocationToken
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverCallerInfo
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
|
||||
sealed class KotlinStepAction {
|
||||
object StepOver : KotlinStepAction() {
|
||||
object JvmStepOver : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
|
||||
}
|
||||
@@ -26,18 +24,10 @@ sealed class KotlinStepAction {
|
||||
}
|
||||
}
|
||||
|
||||
class RunToCursor(private val position: XSourcePositionImpl) : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return runReadAction {
|
||||
debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints)
|
||||
}.contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
class StepOverInlined(private val tokensToSkip: Set<LocationToken>, private val callerInfo: StepOverCallerInfo) : KotlinStepAction() {
|
||||
class KotlinStepOver(private val tokensToSkip: Set<LocationToken>, private val callerInfo: StepOverCallerInfo) : KotlinStepAction() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
val filter = KotlinStepOverFilter(debugProcess.project, tokensToSkip, callerInfo)
|
||||
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(filter).contextAction(suspendContext)
|
||||
return KotlinStepActionFactory(debugProcess).createKotlinStepOverAction(filter).contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-10
@@ -34,12 +34,8 @@ import java.lang.reflect.Field
|
||||
// Mass-copy-paste code for commands behaviour from com.intellij.debugger.engine.DebugProcessImpl
|
||||
@SuppressWarnings("UnnecessaryFinalOnLocalVariableOrParameter")
|
||||
class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
|
||||
abstract class KotlinStepAction {
|
||||
abstract fun contextAction(suspendContext: SuspendContextImpl)
|
||||
}
|
||||
|
||||
fun createKotlinStepOverInlineAction(smartStepFilter: KotlinMethodFilter): KotlinStepAction {
|
||||
return StepOverInlineCommand(smartStepFilter, StepRequest.STEP_LINE)
|
||||
fun createKotlinStepOverAction(smartStepFilter: KotlinMethodFilter): KotlinStepOverCommand {
|
||||
return KotlinStepOverCommand(smartStepFilter, StepRequest.STEP_LINE)
|
||||
}
|
||||
|
||||
private val debuggerContext: DebuggerContextImpl get() = debuggerProcess.debuggerContext
|
||||
@@ -94,8 +90,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
|
||||
return field.get(debuggerProcess) as T
|
||||
}
|
||||
|
||||
private inner class StepOverInlineCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) :
|
||||
KotlinStepAction() {
|
||||
inner class KotlinStepOverCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) {
|
||||
private fun getContextThread(suspendContext: SuspendContextImpl): ThreadReferenceProxyImpl? {
|
||||
val contextThread = debuggerContext.threadProxy
|
||||
return contextThread ?: suspendContext.thread
|
||||
@@ -126,7 +121,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
|
||||
}
|
||||
|
||||
// See: StepIntoCommand.contextAction()
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
showStatusText(KotlinDebuggerCoreBundle.message("stepping.over.inline"))
|
||||
val stepThread = getContextThread(suspendContext)
|
||||
|
||||
@@ -136,7 +131,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
|
||||
return
|
||||
}
|
||||
|
||||
val hint = KotlinStepOverInlinedLinesHint(stepThread, suspendContext, mySmartStepFilter)
|
||||
val hint = KotlinStepOverRequestHint(stepThread, suspendContext, mySmartStepFilter)
|
||||
hint.isResetIgnoreFilters = !session.shouldIgnoreSteppingFilters()
|
||||
|
||||
try {
|
||||
|
||||
+27
-11
@@ -24,33 +24,40 @@ import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.isOnSuspensionPoint
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
|
||||
// Originally copied from RequestHint
|
||||
class KotlinStepOverInlinedLinesHint(
|
||||
class KotlinStepOverRequestHint(
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
suspendContext: SuspendContextImpl,
|
||||
private val filter: KotlinMethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_OVER, filter) {
|
||||
private companion object {
|
||||
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
|
||||
private val LOG = Logger.getInstance(KotlinStepOverRequestHint::class.java)
|
||||
}
|
||||
|
||||
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||
try {
|
||||
val frameProxy = context.frameProxy
|
||||
if (frameProxy != null) {
|
||||
if (isTheSameFrame(context)) {
|
||||
val isAcceptable = (filter.locationMatches(context, frameProxy.location()))
|
||||
return if (isAcceptable) STOP else StepRequest.STEP_OVER
|
||||
} else if (isSteppedOut) {
|
||||
val lineNumber = frameProxy.safeLocation()?.safeLineNumber(JAVA_STRATUM) ?: -1
|
||||
return if (lineNumber >= 0) STOP else StepRequest.STEP_OVER
|
||||
val frameProxy = context.frameProxy ?: return STOP
|
||||
if (isTheSameFrame(context)) {
|
||||
if (frameProxy.isOnSuspensionPoint()) {
|
||||
// Coroutine will sleep now so we can't continue stepping.
|
||||
// Let's put a run-to-cursor breakpoint and resume the debugger.
|
||||
return if (!installCoroutineResumedBreakpoint(context)) STOP else RESUME
|
||||
}
|
||||
|
||||
return StepRequest.STEP_OUT
|
||||
val location = frameProxy.safeLocation()
|
||||
val isAcceptable = location != null && filter.locationMatches(context, location)
|
||||
return if (isAcceptable) STOP else StepRequest.STEP_OVER
|
||||
} else if (isSteppedOut) {
|
||||
val lineNumber = frameProxy.safeLocation()?.safeLineNumber(JAVA_STRATUM) ?: -1
|
||||
return if (lineNumber >= 0) STOP else StepRequest.STEP_OVER
|
||||
}
|
||||
|
||||
return StepRequest.STEP_OUT
|
||||
} catch (ignored: VMDisconnectedException) {
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.error(e)
|
||||
@@ -58,4 +65,13 @@ class KotlinStepOverInlinedLinesHint(
|
||||
|
||||
return STOP
|
||||
}
|
||||
|
||||
private fun installCoroutineResumedBreakpoint(context: SuspendContextImpl): Boolean {
|
||||
val frameProxy = context.frameProxy ?: return false
|
||||
val location = frameProxy.safeLocation() ?: return false
|
||||
val method = location.safeMethod() ?: return false
|
||||
|
||||
context.debugProcess.cancelRunToCursorBreakpoint()
|
||||
return CoroutineBreakpointFacility.installCoroutineResumedBreakpoint(context, location, method)
|
||||
}
|
||||
}
|
||||
+5
-23
@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.KotlinSuspendCallStepOverFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.LocationToken
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.filter.StepOverCallerInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||
@@ -46,33 +45,16 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
||||
stepSize: Int
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
if (suspendContext == null || suspendContext.isResumed) return null
|
||||
|
||||
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
|
||||
return getStepOverCommand(suspendContext, ignoreBreakpoints, sourcePosition)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl,
|
||||
ignoreBreakpoints: Boolean,
|
||||
debuggerContext: DebuggerContextImpl
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
return getStepOverCommand(suspendContext, ignoreBreakpoints, debuggerContext.sourcePosition)
|
||||
}
|
||||
|
||||
private fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl,
|
||||
ignoreBreakpoints: Boolean,
|
||||
sourcePosition: SourcePosition
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
val file = sourcePosition.elementAt.containingFile
|
||||
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.safeLocation() } ?: return null
|
||||
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
||||
return DebuggerSteppingHelper.createStepOverCommandWithCustomFilter(
|
||||
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints)
|
||||
)
|
||||
}
|
||||
|
||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, sourcePosition)
|
||||
}
|
||||
|
||||
@@ -166,9 +148,9 @@ fun getStepOverAction(
|
||||
location: Location, sourcePosition: SourcePosition,
|
||||
suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl
|
||||
): KotlinStepAction {
|
||||
val stackFrame = frameProxy.safeStackFrame() ?: return KotlinStepAction.StepOver
|
||||
val method = location.safeMethod() ?: return KotlinStepAction.StepOver
|
||||
val token = LocationToken.from(stackFrame).takeIf { it.lineNumber > 0 } ?: return KotlinStepAction.StepOver
|
||||
val stackFrame = frameProxy.safeStackFrame() ?: return KotlinStepAction.JvmStepOver
|
||||
val method = location.safeMethod() ?: return KotlinStepAction.JvmStepOver
|
||||
val token = LocationToken.from(stackFrame).takeIf { it.lineNumber > 0 } ?: return KotlinStepAction.JvmStepOver
|
||||
|
||||
val inlinedFunctionArgumentRanges = sourcePosition.collectInlineFunctionArgumentRanges()
|
||||
val positionManager = suspendContext.debugProcess.positionManager
|
||||
@@ -191,7 +173,7 @@ fun getStepOverAction(
|
||||
}
|
||||
}
|
||||
|
||||
return KotlinStepAction.StepOverInlined(tokensToSkip, StepOverCallerInfo.from(location))
|
||||
return KotlinStepAction.KotlinStepOver(tokensToSkip, StepOverCallerInfo.from(location))
|
||||
}
|
||||
|
||||
private fun isInlineFunctionFromLibrary(positionManager: PositionManager, location: Location, token: LocationToken): Boolean {
|
||||
@@ -282,5 +264,5 @@ fun getStepOutAction(location: Location, frameProxy: StackFrameProxyImpl): Kotli
|
||||
}
|
||||
}
|
||||
|
||||
return KotlinStepAction.StepOverInlined(tokensToSkip, StepOverCallerInfo.from(location))
|
||||
return KotlinStepAction.KotlinStepOver(tokensToSkip, StepOverCallerInfo.from(location))
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping.filter
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.util.Range
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
|
||||
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
|
||||
class KotlinSuspendCallStepOverFilter(
|
||||
private val line: Int,
|
||||
private val file: PsiFile,
|
||||
private val ignoreBreakpoints: Boolean
|
||||
) : MethodFilter {
|
||||
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
|
||||
return location != null && isOnSuspendReturnOrReenter(location)
|
||||
}
|
||||
|
||||
override fun onReached(context: SuspendContextImpl, hint: RequestHint): Int {
|
||||
val location = context.frameProxy?.location() ?: return RequestHint.STOP
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return RequestHint.STOP
|
||||
|
||||
val debugProcess = context.debugProcess
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
|
||||
breakpointManager.applyThreadFilter(debugProcess, null)
|
||||
|
||||
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file, ignoreBreakpoints)
|
||||
return RequestHint.RESUME
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile, ignoreBreakpoints: Boolean) {
|
||||
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
|
||||
val process = context.debugProcess
|
||||
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
|
||||
process.cancelRunToCursorBreakpoint()
|
||||
|
||||
if (ignoreBreakpoints) {
|
||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.disableBreakpoints(process)
|
||||
}
|
||||
|
||||
val runToCursorBreakpoint =
|
||||
runReadAction {
|
||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
|
||||
} ?: return
|
||||
|
||||
runToCursorBreakpoint.suspendPolicy = when {
|
||||
context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
|
||||
else -> DebuggerSettings.SUSPEND_ALL
|
||||
}
|
||||
|
||||
runToCursorBreakpoint.createRequest(process)
|
||||
process.setRunToCursorBreakpoint(runToCursorBreakpoint)
|
||||
}
|
||||
+4
-2
@@ -90,8 +90,10 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.doStepOver(ignoreBreakpoints: Boolean = false) {
|
||||
val stepOverCommand = runReadAction { commandProvider.getStepOverCommand(this, ignoreBreakpoints, debuggerContext) }
|
||||
?: dp.createStepOverCommand(this, ignoreBreakpoints)
|
||||
val stepOverCommand = runReadAction {
|
||||
val sourcePosition = debuggerContext.sourcePosition
|
||||
commandProvider.getStepOverCommand(this, ignoreBreakpoints, sourcePosition)
|
||||
} ?: dp.createStepOverCommand(this, ignoreBreakpoints)
|
||||
|
||||
dp.managerThread.schedule(stepOverCommand)
|
||||
}
|
||||
|
||||
+33
@@ -931,6 +931,39 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
public void testWhenWithoutExpression() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/whenWithoutExpression.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Coroutines extends AbstractKotlinSteppingTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doStepOverTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInCoroutines() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("sequenceNested.kt")
|
||||
public void testSequenceNested() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sequenceNested2.kt")
|
||||
public void testSequenceNested2() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sequenceSimple.kt")
|
||||
public void testSequenceSimple() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sequenceTake2.kt")
|
||||
public void testSequenceTake2() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/jvm-debugger/jvm-debugger-test/testData/stepping/filters")
|
||||
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
package test
|
||||
|
||||
fun main() {
|
||||
val seq = sequence {
|
||||
//Breakpoint!
|
||||
yield(
|
||||
sequence {
|
||||
yield(1)
|
||||
yield(2)
|
||||
yield(3)
|
||||
}
|
||||
)
|
||||
|
||||
yield(
|
||||
sequence {
|
||||
yield(4)
|
||||
yield(5)
|
||||
yield(6)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
seq.map { it.toList() }.flatten().toList()
|
||||
}
|
||||
|
||||
// STEP_OVER: 10
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
LineBreakpoint created at sequenceNested.kt:6
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
sequenceNested.kt:6
|
||||
sequenceNested.kt:7
|
||||
sequenceNested.kt:6
|
||||
sequenceNested.kt:14
|
||||
sequenceNested.kt:15
|
||||
sequenceNested.kt:14
|
||||
sequenceNested.kt:21
|
||||
sequenceNested.kt:23
|
||||
sequenceNested.kt:24
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
package test
|
||||
|
||||
fun main() {
|
||||
val seq = sequence {
|
||||
yield(
|
||||
sequence {
|
||||
//Breakpoint!
|
||||
yield(1)
|
||||
yield(2)
|
||||
yield(3)
|
||||
}
|
||||
)
|
||||
|
||||
yield(
|
||||
sequence {
|
||||
yield(4)
|
||||
yield(5)
|
||||
yield(6)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
seq.map { it.toList() }.flatten().toList()
|
||||
}
|
||||
|
||||
// STEP_OVER: 10
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
LineBreakpoint created at sequenceNested2.kt:8
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
sequenceNested2.kt:8
|
||||
sequenceNested2.kt:9
|
||||
sequenceNested2.kt:10
|
||||
sequenceNested2.kt:11
|
||||
sequenceNested2.kt:23
|
||||
sequenceNested2.kt:23
|
||||
sequenceNested2.kt:24
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
package test
|
||||
|
||||
fun main() {
|
||||
val seq = sequence {
|
||||
//Breakpoint!
|
||||
yield(1)
|
||||
yield(2)
|
||||
yield(3)
|
||||
}
|
||||
|
||||
seq.toList()
|
||||
}
|
||||
|
||||
// STEP_OVER: 4
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
LineBreakpoint created at sequenceSimple.kt:6
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
sequenceSimple.kt:6
|
||||
sequenceSimple.kt:7
|
||||
sequenceSimple.kt:8
|
||||
sequenceSimple.kt:9
|
||||
sequenceSimple.kt:11
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package test
|
||||
|
||||
fun main() {
|
||||
val seq = sequence {
|
||||
//Breakpoint!
|
||||
yield(1)
|
||||
yield(2)
|
||||
yield(3)
|
||||
}
|
||||
|
||||
seq.take(2).toList()
|
||||
}
|
||||
|
||||
// STEP_OVER: 4
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
LineBreakpoint created at sequenceTake2.kt:6
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
sequenceTake2.kt:6
|
||||
sequenceTake2.kt:7
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
Reference in New Issue
Block a user