Step over for suspended calls (KT-18453)
Debugger do the normal step over action and checks if function is going to suspend. In this case the "run-to-cursor" breakpoint is installed on function enter to intercept re-enter into function after suspension. #KT-18453 In Progress
This commit is contained in:
@@ -23,6 +23,8 @@ import com.intellij.debugger.settings.DebuggerSettings
|
|||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.request.StepRequest
|
import com.sun.jdi.request.StepRequest
|
||||||
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager
|
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.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
|
||||||
class KotlinExtraSteppingFilter : ExtraSteppingFilter {
|
class KotlinExtraSteppingFilter : ExtraSteppingFilter {
|
||||||
@@ -54,6 +56,10 @@ class KotlinExtraSteppingFilter : ExtraSteppingFilter {
|
|||||||
return false
|
return false
|
||||||
} ?: return false
|
} ?: return false
|
||||||
|
|
||||||
|
if (isOnSuspendReturnOrReenter(location) && !isOneLineMethod(location)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
val settings = DebuggerSettings.getInstance()
|
val settings = DebuggerSettings.getInstance()
|
||||||
if (settings.TRACING_FILTERS_ENABLED) {
|
if (settings.TRACING_FILTERS_ENABLED) {
|
||||||
val classNames = positionManager.originalClassNameForPosition(sourcePosition).map { it.replace('/', '.') }
|
val classNames = positionManager.originalClassNameForPosition(sourcePosition).map { it.replace('/', '.') }
|
||||||
|
|||||||
@@ -17,12 +17,15 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import com.sun.tools.jdi.LocalVariableImpl
|
import com.sun.tools.jdi.LocalVariableImpl
|
||||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_ASM_TYPE
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
@@ -54,13 +57,21 @@ fun isInsideInlineArgument(inlineArgument: KtFunction, location: Location, debug
|
|||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T: Any> DebugProcessImpl.invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? {
|
fun <T : Any> DebugProcessImpl.invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? {
|
||||||
var result: T? = null
|
var result: T? = null
|
||||||
managerThread.invokeAndWait(object : DebuggerCommandImpl() {
|
val command: DebuggerCommandImpl = object : DebuggerCommandImpl() {
|
||||||
override fun action() {
|
override fun action() {
|
||||||
result = runReadAction { f(debuggerContext) }
|
result = runReadAction { f(debuggerContext) }
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
DebuggerManagerThreadImpl.isManagerThread() ->
|
||||||
|
managerThread.invoke(command)
|
||||||
|
else ->
|
||||||
|
managerThread.invokeAndWait(command)
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,3 +148,48 @@ private class MockStackFrame(private val location: Location, private val vm: Vir
|
|||||||
override fun getArgumentValues(): List<Value> = emptyList()
|
override fun getArgumentValues(): List<Value> = emptyList()
|
||||||
override fun virtualMachine() = vm
|
override fun virtualMachine() = vm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val DO_RESUME_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)Ljava/lang/Object;"
|
||||||
|
|
||||||
|
fun isInSuspendMethod(location: Location): Boolean {
|
||||||
|
val method = location.method()
|
||||||
|
val signature = method.signature()
|
||||||
|
|
||||||
|
return signature.contains(CONTINUATION_ASM_TYPE.toString()) ||
|
||||||
|
(method.name() == DO_RESUME_METHOD_NAME && signature == DO_RESUME_SIGNATURE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun suspendFunctionFirstLineLocation(location: Location): Int? {
|
||||||
|
if (!isInSuspendMethod(location)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val lineNumber = location.method().location().lineNumber()
|
||||||
|
if (lineNumber == -1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isOnSuspendReturnOrReenter(location: Location): Boolean {
|
||||||
|
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return false
|
||||||
|
return suspendStartLineNumber == location.lineNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLastLineLocationInMethod(location: Location): Boolean {
|
||||||
|
val knownLines = location.method().allLineLocations().map { it.lineNumber() }.filter { it != -1 }
|
||||||
|
if (knownLines.isEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return knownLines.max() == location.lineNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isOneLineMethod(location: Location): Boolean {
|
||||||
|
val allLineLocations = location.method().allLineLocations()
|
||||||
|
val firstLine = allLineLocations.firstOrNull()?.lineNumber()
|
||||||
|
val lastLine = allLineLocations.lastOrNull()?.lineNumber()
|
||||||
|
|
||||||
|
return firstLine != null && firstLine == lastLine
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||||
|
import com.intellij.debugger.engine.RequestHint;
|
||||||
|
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||||
|
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
|
||||||
|
import com.sun.jdi.request.StepRequest;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class DebugProcessImplHelper {
|
||||||
|
public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter(
|
||||||
|
SuspendContextImpl suspendContext,
|
||||||
|
boolean ignoreBreakpoints,
|
||||||
|
KotlinSuspendCallStepOverFilter methodFilter) {
|
||||||
|
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||||
|
return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) {
|
||||||
|
@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());
|
||||||
|
|
||||||
|
return hint;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-9
@@ -38,8 +38,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.ktLocationInfo
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.GOTO
|
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.GOTO
|
||||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.MOVE
|
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.MOVE
|
||||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN
|
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN
|
||||||
@@ -89,9 +88,18 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
|||||||
sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
|
sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
|
||||||
val kotlinSourcePosition = KotlinSourcePosition.create(sourcePosition) ?: return null
|
val kotlinSourcePosition = KotlinSourcePosition.create(sourcePosition) ?: return null
|
||||||
|
|
||||||
if (!isSpecialStepOverNeeded(kotlinSourcePosition)) return null
|
if (isSpecialStepOverNeeded(kotlinSourcePosition)) {
|
||||||
|
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, kotlinSourcePosition)
|
||||||
|
}
|
||||||
|
|
||||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, kotlinSourcePosition)
|
val file = sourcePosition.elementAt.containingFile
|
||||||
|
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null
|
||||||
|
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
||||||
|
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
|
||||||
|
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file))
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
data class KotlinSourcePosition(val file: KtFile, val function: KtNamedFunction,
|
data class KotlinSourcePosition(val file: KtFile, val function: KtNamedFunction,
|
||||||
@@ -204,9 +212,8 @@ private fun getInlineArgumentsCallsIfAny(sourcePosition: SourcePosition, declara
|
|||||||
|
|
||||||
fun isCallOfArgument(ktCallExpression: KtCallExpression): Boolean {
|
fun isCallOfArgument(ktCallExpression: KtCallExpression): Boolean {
|
||||||
val context = ktCallExpression.analyze(BodyResolveMode.PARTIAL)
|
val context = ktCallExpression.analyze(BodyResolveMode.PARTIAL)
|
||||||
val resolvedCall = ktCallExpression.getResolvedCall(context) ?: return false
|
val resolvedCall = ktCallExpression.getResolvedCall(context) as? VariableAsFunctionResolvedCallImpl ?: return false
|
||||||
|
|
||||||
if (resolvedCall !is VariableAsFunctionResolvedCallImpl) return false
|
|
||||||
val candidateDescriptor = resolvedCall.variableCall.candidateDescriptor
|
val candidateDescriptor = resolvedCall.variableCall.candidateDescriptor
|
||||||
|
|
||||||
return candidateDescriptor in valueParameters
|
return candidateDescriptor in valueParameters
|
||||||
@@ -394,8 +401,8 @@ fun getStepOverAction(
|
|||||||
.dropWhile { it != patchedLocation }
|
.dropWhile { it != patchedLocation }
|
||||||
.drop(1)
|
.drop(1)
|
||||||
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
||||||
.takeWhile { location ->
|
.takeWhile { loc ->
|
||||||
!isLocationSuitable(location) || lambdaArgumentRanges.any { location.ktLineNumber() in it }
|
!isLocationSuitable(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it }
|
||||||
}
|
}
|
||||||
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
||||||
|
|
||||||
@@ -579,4 +586,4 @@ object DexBytecode {
|
|||||||
|
|
||||||
val GOTO = 0x28
|
val GOTO = 0x28
|
||||||
val MOVE = 0x01
|
val MOVE = 0x01
|
||||||
}
|
}
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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) : 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)
|
||||||
|
return RequestHint.RESUME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile) {
|
||||||
|
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
|
||||||
|
val process = context.debugProcess
|
||||||
|
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
|
||||||
|
process.cancelRunToCursorBreakpoint()
|
||||||
|
|
||||||
|
val runToCursorBreakpoint =
|
||||||
|
runReadAction {
|
||||||
|
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, false)
|
||||||
|
} ?:
|
||||||
|
return
|
||||||
|
|
||||||
|
runToCursorBreakpoint.suspendPolicy = when {
|
||||||
|
context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
|
||||||
|
else -> DebuggerSettings.SUSPEND_ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
runToCursorBreakpoint.createRequest(process)
|
||||||
|
process.setRunToCursorBreakpoint(runToCursorBreakpoint)
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||||
|
import com.intellij.debugger.engine.MethodFilter
|
||||||
|
import com.intellij.debugger.engine.RequestHint
|
||||||
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
|
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.intellij.lang.annotations.MagicConstant
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
internal class RequestHintWithMethodFilter(
|
||||||
|
stepThread: ThreadReferenceProxyImpl,
|
||||||
|
suspendContext: SuspendContextImpl,
|
||||||
|
@MagicConstant(intValues = longArrayOf(
|
||||||
|
StepRequest.STEP_INTO.toLong(),
|
||||||
|
StepRequest.STEP_OVER.toLong(),
|
||||||
|
StepRequest.STEP_OUT.toLong())) depth: Int,
|
||||||
|
methodFilter: MethodFilter
|
||||||
|
) : RequestHint(stepThread, suspendContext, methodFilter) {
|
||||||
|
private var targetMethodMatched = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
// NOTE: Debugger API. Open RequestHint constructor with depth
|
||||||
|
if (depth != StepRequest.STEP_INTO) {
|
||||||
|
findFieldWithValue(StepRequest.STEP_INTO, Integer.TYPE)?.setInt(this, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findFieldWithValue(value: Int, type: Class<*>): Field? {
|
||||||
|
return RequestHint::class.java.declaredFields.firstOrNull { field ->
|
||||||
|
if (field.type == type) {
|
||||||
|
field.isAccessible = true
|
||||||
|
if (field.getInt(this) == value) {
|
||||||
|
return@firstOrNull true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||||
|
try {
|
||||||
|
val frameProxy = context.frameProxy
|
||||||
|
val filter = methodFilter
|
||||||
|
|
||||||
|
if (filter != null && frameProxy != null && filter !is BreakpointStepMethodFilter) {
|
||||||
|
/*NODE: Debugger API. Base implementation works only for smart step into, and calls filter only if !isTheSameFrame(context). */
|
||||||
|
if (filter.locationMatches(context.debugProcess, frameProxy.location())) {
|
||||||
|
targetMethodMatched = true
|
||||||
|
return filter.onReached(context, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ignored: VMDisconnectedException) {
|
||||||
|
return STOP
|
||||||
|
}
|
||||||
|
catch (e: EvaluateException) {
|
||||||
|
LOG.error(e)
|
||||||
|
return STOP
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getNextStepDepth(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun wasStepTargetMethodMatched(): Boolean {
|
||||||
|
return super.wasStepTargetMethodMatched() || targetMethodMatched
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val LOG = Logger.getInstance(RequestHintWithMethodFilter::class.java)
|
||||||
@@ -15,4 +15,8 @@ fun main(args: Array<String>) {
|
|||||||
builder {
|
builder {
|
||||||
first()
|
first()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STEP_INTO: 1
|
||||||
|
|
||||||
|
// TODO: Breakpoint on builder {} is now triggered twice. This is because generated line number on suspend function enter.
|
||||||
@@ -2,6 +2,7 @@ LineBreakpoint created at coroutine.kt:15
|
|||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
coroutine.kt:15
|
coroutine.kt:15
|
||||||
|
coroutine.kt:15
|
||||||
coroutine.kt:16
|
coroutine.kt:16
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package stepIntoSuspendFunctionSimple
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
|
||||||
|
private fun foo() {}
|
||||||
|
|
||||||
|
suspend fun second() {
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun first(): Int {
|
||||||
|
second()
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
//Breakpoint!
|
||||||
|
first()
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at stepIntoSuspendFunctionSimple.kt:18
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
stepIntoSuspendFunctionSimple.kt:18
|
||||||
|
stepIntoSuspendFunctionSimple.kt:10
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package soNonSuspendableSuspendCall
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
|
||||||
|
private fun foo() {}
|
||||||
|
|
||||||
|
suspend fun second() {
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun first(): Int {
|
||||||
|
second()
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
//Breakpoint!
|
||||||
|
first()
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at soNonSuspendableSuspendCall.kt:18
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
soNonSuspendableSuspendCall.kt:18
|
||||||
|
soNonSuspendableSuspendCall.kt:19
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package soSuspendableCallInEndOfFun
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.suspendCoroutine
|
||||||
|
|
||||||
|
private fun foo(a: Any) {}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
inFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
foo("Main end")
|
||||||
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun inFun() {
|
||||||
|
foo("Start")
|
||||||
|
//Breakpoint!
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
suspendCoroutine { cont: Continuation<Unit> ->
|
||||||
|
Thread {
|
||||||
|
cont.resume(Unit)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_OVER: 4
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
LineBreakpoint created at soSuspendableCallInEndOfFun.kt:21
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
soSuspendableCallInEndOfFun.kt:21
|
||||||
|
soSuspendableCallInEndOfFun.kt:18
|
||||||
|
soSuspendableCallInEndOfFun.kt:22
|
||||||
|
soSuspendableCallInEndOfFun.kt:-1
|
||||||
|
soSuspendableCallInEndOfFun.kt:28
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package soSuspendableCallInEndOfLambda
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.suspendCoroutine
|
||||||
|
|
||||||
|
private fun foo(a: Any) {}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
foo("Start")
|
||||||
|
//Breakpoint!
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
foo("Main end")
|
||||||
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
suspendCoroutine { cont: Continuation<Unit> ->
|
||||||
|
Thread {
|
||||||
|
cont.resume(Unit)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_OVER: 2
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
LineBreakpoint created at soSuspendableCallInEndOfLambda.kt:13
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
soSuspendableCallInEndOfLambda.kt:13
|
||||||
|
soSuspendableCallInEndOfLambda.kt:10
|
||||||
|
soSuspendableCallInEndOfLambda.kt:14
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package soSuspendableCallInFun
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.suspendCoroutine
|
||||||
|
|
||||||
|
private fun foo(a: Any) {}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
inFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
foo("Main end")
|
||||||
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun inFun() {
|
||||||
|
//Breakpoint!
|
||||||
|
run()
|
||||||
|
foo("End")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
suspendCoroutine { cont: Continuation<Unit> ->
|
||||||
|
Thread {
|
||||||
|
cont.resume(Unit)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_OVER: 2
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
LineBreakpoint created at soSuspendableCallInFun.kt:20
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
soSuspendableCallInFun.kt:20
|
||||||
|
soSuspendableCallInFun.kt:18
|
||||||
|
soSuspendableCallInFun.kt:21
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
Vendored
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package soSuspendableCallInFunFromOtherStepping
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.suspendCoroutine
|
||||||
|
|
||||||
|
private fun foo(a: Any) {}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
inFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
foo("Main end")
|
||||||
|
Thread.sleep(120)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun inFun() {
|
||||||
|
//Breakpoint!
|
||||||
|
foo("Start")
|
||||||
|
run()
|
||||||
|
foo("End")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
return suspendCoroutine { cont: Continuation<Unit> ->
|
||||||
|
Thread {
|
||||||
|
cont.resume(Unit)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_OVER: 3
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
LineBreakpoint created at soSuspendableCallInFunFromOtherStepping.kt:20
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
soSuspendableCallInFunFromOtherStepping.kt:20
|
||||||
|
soSuspendableCallInFunFromOtherStepping.kt:21
|
||||||
|
soSuspendableCallInFunFromOtherStepping.kt:18
|
||||||
|
soSuspendableCallInFunFromOtherStepping.kt:22
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package soSuspendableCallInLambda
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.suspendCoroutine
|
||||||
|
|
||||||
|
private fun foo(a: Any) {}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
//Breakpoint!
|
||||||
|
run()
|
||||||
|
foo("End")
|
||||||
|
}
|
||||||
|
|
||||||
|
foo("Main end")
|
||||||
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
suspendCoroutine { cont: Continuation<Unit> ->
|
||||||
|
Thread {
|
||||||
|
cont.resume(Unit)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_OVER: 2
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
LineBreakpoint created at soSuspendableCallInLambda.kt:12
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
soSuspendableCallInLambda.kt:12
|
||||||
|
soSuspendableCallInLambda.kt:10
|
||||||
|
soSuspendableCallInLambda.kt:13
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -314,6 +314,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
doStepIntoTest(fileName);
|
doStepIntoTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stepIntoSuspendFunctionSimple.kt")
|
||||||
|
public void testStepIntoSuspendFunctionSimple() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/stepIntoSuspendFunctionSimple.kt");
|
||||||
|
doStepIntoTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("syntheticMethods.kt")
|
@TestMetadata("syntheticMethods.kt")
|
||||||
public void testSyntheticMethods() throws Exception {
|
public void testSyntheticMethods() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/syntheticMethods.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/syntheticMethods.kt");
|
||||||
@@ -608,6 +614,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
doStepOverTest(fileName);
|
doStepOverTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("soNonSuspendableSuspendCall.kt")
|
||||||
|
public void testSoNonSuspendableSuspendCall() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soNonSuspendableSuspendCall.kt");
|
||||||
|
doStepOverTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("soReifiedInlineIfConditionFalse.kt")
|
@TestMetadata("soReifiedInlineIfConditionFalse.kt")
|
||||||
public void testSoReifiedInlineIfConditionFalse() throws Exception {
|
public void testSoReifiedInlineIfConditionFalse() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soReifiedInlineIfConditionFalse.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soReifiedInlineIfConditionFalse.kt");
|
||||||
@@ -620,6 +632,36 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
doStepOverTest(fileName);
|
doStepOverTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("soSuspendableCallInEndOfFun.kt")
|
||||||
|
public void testSoSuspendableCallInEndOfFun() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soSuspendableCallInEndOfFun.kt");
|
||||||
|
doStepOverTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("soSuspendableCallInEndOfLambda.kt")
|
||||||
|
public void testSoSuspendableCallInEndOfLambda() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soSuspendableCallInEndOfLambda.kt");
|
||||||
|
doStepOverTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("soSuspendableCallInFun.kt")
|
||||||
|
public void testSoSuspendableCallInFun() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soSuspendableCallInFun.kt");
|
||||||
|
doStepOverTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("soSuspendableCallInFunFromOtherStepping.kt")
|
||||||
|
public void testSoSuspendableCallInFunFromOtherStepping() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soSuspendableCallInFunFromOtherStepping.kt");
|
||||||
|
doStepOverTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("soSuspendableCallInLambda.kt")
|
||||||
|
public void testSoSuspendableCallInLambda() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soSuspendableCallInLambda.kt");
|
||||||
|
doStepOverTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("stepOverCatchClause.kt")
|
@TestMetadata("stepOverCatchClause.kt")
|
||||||
public void testStepOverCatchClause() throws Exception {
|
public void testStepOverCatchClause() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverCatchClause.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverCatchClause.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user