diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt index 4d2d827de85..d4ddead1c24 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt @@ -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 } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index 3cd269ead04..f74982226e5 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -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 { diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/AbstractCoroutineBreakpointFacility.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/AbstractCoroutineBreakpointFacility.kt new file mode 100644 index 00000000000..d92872b17ab --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/AbstractCoroutineBreakpointFacility.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/CoroutineBreakpointFacility.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/CoroutineBreakpointFacility.kt new file mode 100644 index 00000000000..23799daa09a --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/CoroutineBreakpointFacility.kt @@ -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 + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/CoroutineBreakpointFacility.kt.192 b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/CoroutineBreakpointFacility.kt.192 new file mode 100644 index 00000000000..cce60728de6 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/CoroutineBreakpointFacility.kt.192 @@ -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 + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java index da4645436f3..8ca8ac0623a 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java @@ -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(); diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt index 419dd05f9e5..c9594e9caeb 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepAction.kt @@ -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, private val callerInfo: StepOverCallerInfo) : KotlinStepAction() { + class KotlinStepOver(private val tokensToSkip: Set, 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) } } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepActionFactory.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepActionFactory.kt index 35fbad9284c..9b9356f58ac 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepActionFactory.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepActionFactory.kt @@ -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 { diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverRequestHint.kt similarity index 52% rename from idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt rename to idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverRequestHint.kt index 604dee9859e..9c14c82aa07 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverInlinedLinesHint.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepOverRequestHint.kt @@ -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) + } } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 2ae80b56bc3..9c706d6ea11 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -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)) } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinSuspendCallStepOverFilter.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinSuspendCallStepOverFilter.kt deleted file mode 100644 index 8d9ecb344d8..00000000000 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stepping/filter/KotlinSuspendCallStepOverFilter.kt +++ /dev/null @@ -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? = 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) -} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStepping.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStepping.kt index b1999b8e40b..21882d689db 100644 --- a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStepping.kt +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStepping.kt @@ -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) } diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinSteppingTestGenerated.java b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinSteppingTestGenerated.java index b9f15d23d6f..cfa5df31a01 100644 --- a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinSteppingTestGenerated.java +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinSteppingTestGenerated.java @@ -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") diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.kt new file mode 100644 index 00000000000..7fbce130ee1 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.kt @@ -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 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.out new file mode 100644 index 00000000000..f94e16f6ad4 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.kt new file mode 100644 index 00000000000..1599e8296f8 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.kt @@ -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 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.out new file mode 100644 index 00000000000..a0f52df12a1 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceNested2.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.kt new file mode 100644 index 00000000000..c865d3fbce8 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.kt @@ -0,0 +1,14 @@ +package test + +fun main() { + val seq = sequence { + //Breakpoint! + yield(1) + yield(2) + yield(3) + } + + seq.toList() +} + +// STEP_OVER: 4 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.out new file mode 100644 index 00000000000..6c1129c922b --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceSimple.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.kt b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.kt new file mode 100644 index 00000000000..67d7ad23612 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.kt @@ -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 \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.out b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.out new file mode 100644 index 00000000000..6d6a415d718 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/stepping/stepOver/coroutines/sequenceTake2.out @@ -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