Implement new stepping for suspend functions

This commit is contained in:
Yan Zhulanow
2020-01-16 19:55:40 +09:00
parent 2044ece335
commit 69965eaa5c
21 changed files with 390 additions and 201 deletions
@@ -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
}
@@ -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 {
@@ -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)
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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();
@@ -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)
}
}
@@ -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 {
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
@@ -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)
}
@@ -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")
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,14 @@
package test
fun main() {
val seq = sequence {
//Breakpoint!
yield(1)
yield(2)
yield(3)
}
seq.toList()
}
// STEP_OVER: 4
@@ -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
@@ -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
@@ -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