Reformat: debugger.stepping

This commit is contained in:
Nikolay Krasko
2018-06-13 17:20:35 +03:00
parent 89f0b11765
commit 64f14ad89b
13 changed files with 191 additions and 184 deletions
@@ -27,7 +27,8 @@ public class DebugProcessImplHelper {
public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter( public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter(
SuspendContextImpl suspendContext, SuspendContextImpl suspendContext,
boolean ignoreBreakpoints, boolean ignoreBreakpoints,
KotlinSuspendCallStepOverFilter methodFilter) { KotlinSuspendCallStepOverFilter methodFilter
) {
DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) { return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) {
@NotNull @NotNull
@@ -36,8 +36,8 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
class KotlinBasicStepMethodFilter( class KotlinBasicStepMethodFilter(
targetDescriptor: CallableMemberDescriptor, targetDescriptor: CallableMemberDescriptor,
private val myCallingExpressionLines: Range<Int> private val myCallingExpressionLines: Range<Int>
) : NamedMethodFilter { ) : NamedMethodFilter {
private val myTargetMethodName: String = when (targetDescriptor) { private val myTargetMethodName: String = when (targetDescriptor) {
is ClassDescriptor, is ConstructorDescriptor -> "<init>" is ClassDescriptor, is ConstructorDescriptor -> "<init>"
@@ -46,7 +46,7 @@ class KotlinBasicStepMethodFilter(
} }
private val _targetDescriptor = SofterReference( private val _targetDescriptor = SofterReference(
(targetDescriptor as? FunctionDescriptor)?.let { SamCodegenUtil.getOriginalIfSamAdapter(it) } ?: targetDescriptor (targetDescriptor as? FunctionDescriptor)?.let { SamCodegenUtil.getOriginalIfSamAdapter(it) } ?: targetDescriptor
) )
override fun getCallingExpressionLines() = myCallingExpressionLines override fun getCallingExpressionLines() = myCallingExpressionLines
@@ -22,18 +22,18 @@ import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.util.Range import com.intellij.util.Range
import com.sun.jdi.Location import com.sun.jdi.Location
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
import org.jetbrains.kotlin.idea.refactoring.isMultiLine
import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument
import org.jetbrains.kotlin.idea.refactoring.isMultiLine
import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.OperatorNameConventions
class KotlinLambdaMethodFilter( class KotlinLambdaMethodFilter(
private val lambda: KtFunction, private val lambda: KtFunction,
private val myCallingExpressionLines: Range<Int>, private val myCallingExpressionLines: Range<Int>,
private val isInline: Boolean, private val isInline: Boolean,
private val isSuspend: Boolean private val isSuspend: Boolean
): BreakpointStepMethodFilter { ) : BreakpointStepMethodFilter {
private val myFirstStatementPosition: SourcePosition? private val myFirstStatementPosition: SourcePosition?
private val myLastStatementLine: Int private val myLastStatementLine: Int
@@ -52,8 +52,7 @@ class KotlinLambdaMethodFilter(
} }
myFirstStatementPosition = firstStatementPosition myFirstStatementPosition = firstStatementPosition
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1 myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1
} } else {
else {
myFirstStatementPosition = SourcePosition.createFromElement(lambda) myFirstStatementPosition = SourcePosition.createFromElement(lambda)
myLastStatementLine = myFirstStatementPosition!!.line myLastStatementLine = myFirstStatementPosition!!.line
} }
@@ -78,7 +77,7 @@ class KotlinLambdaMethodFilter(
if (isSuspend) { if (isSuspend) {
return name == DO_RESUME_METHOD_NAME return name == DO_RESUME_METHOD_NAME
} }
return name == OperatorNameConventions.INVOKE.asString() return name == OperatorNameConventions.INVOKE.asString()
} }
@@ -26,12 +26,12 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
import javax.swing.Icon import javax.swing.Icon
class KotlinLambdaSmartStepTarget( class KotlinLambdaSmartStepTarget(
label: String, label: String,
highlightElement: KtFunction, highlightElement: KtFunction,
lines: Range<Int>, lines: Range<Int>,
val isInline: Boolean, val isInline: Boolean,
val isSuspend: Boolean val isSuspend: Boolean
): SmartStepTarget(label, highlightElement, true, lines) { ) : SmartStepTarget(label, highlightElement, true, lines) {
override fun getIcon(): Icon = KotlinIcons.LAMBDA override fun getIcon(): Icon = KotlinIcons.LAMBDA
fun getLambda() = highlightElement as KtFunction fun getLambda() = highlightElement as KtFunction
@@ -13,11 +13,11 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import javax.swing.Icon import javax.swing.Icon
class KotlinMethodSmartStepTarget( class KotlinMethodSmartStepTarget(
val descriptor: CallableMemberDescriptor, val descriptor: CallableMemberDescriptor,
label: String, label: String,
highlightElement: PsiElement, highlightElement: PsiElement,
lines: Range<Int> lines: Range<Int>
): SmartStepTarget(label, highlightElement, false, lines) { ) : SmartStepTarget(label, highlightElement, false, lines) {
override fun getIcon(): Icon? { override fun getIcon(): Icon? {
return when { return when {
descriptor.isExtension -> KotlinIcons.EXTENSION_FUNCTION descriptor.isExtension -> KotlinIcons.EXTENSION_FUNCTION
@@ -32,13 +32,12 @@ class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
if (accessor != null && accessor.isGetter) { if (accessor != null && accessor.isGetter) {
val body = accessor.bodyExpression val body = accessor.bodyExpression
return when (body) { return when (body) {
// val a: Int get() { return field }
is KtBlockExpression -> { is KtBlockExpression -> {
// val a: Int get() { return field }
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
returnedExpression.textMatches("field") returnedExpression.textMatches("field")
} }
// val a: Int get() = field is KtExpression -> body.textMatches("field") // val a: Int get() = field
is KtExpression -> body.textMatches("field")
else -> false else -> false
} }
} }
@@ -54,8 +54,8 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
val elementAtOffset = position.elementAt ?: return emptyList() val elementAtOffset = position.elementAt ?: return emptyList()
val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement ?: val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement
return emptyList() ?: return emptyList()
val elementTextRange = element.textRange ?: return emptyList() val elementTextRange = element.textRange ?: return emptyList()
@@ -67,7 +67,7 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
val result = OrderedSet<SmartStepTarget>() val result = OrderedSet<SmartStepTarget>()
// TODO support class initializers, local functions, delegated properties with specified type, setter for properties // TODO support class initializers, local functions, delegated properties with specified type, setter for properties
element.accept(object: KtTreeVisitorVoid() { element.accept(object : KtTreeVisitorVoid() {
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
recordFunctionLiteral(lambdaExpression.functionLiteral) recordFunctionLiteral(lambdaExpression.functionLiteral)
} }
@@ -87,8 +87,11 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
if (argument.arguments.any { getArgumentExpression(it) == function }) { if (argument.arguments.any { getArgumentExpression(it) == function }) {
val resultingDescriptor = resolvedCall.resultingDescriptor val resultingDescriptor = resolvedCall.resultingDescriptor
val label = KotlinLambdaSmartStepTarget.calcLabel(resultingDescriptor, param.name) val label = KotlinLambdaSmartStepTarget.calcLabel(resultingDescriptor, param.name)
result.add(KotlinLambdaSmartStepTarget( result.add(
label, function, lines, InlineUtil.isInline(resultingDescriptor), param.type.isSuspendFunctionType)) KotlinLambdaSmartStepTarget(
label, function, lines, InlineUtil.isInline(resultingDescriptor), param.type.isSuspendFunctionType
)
)
return true return true
} }
} }
@@ -96,7 +99,8 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
return false return false
} }
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() private fun getArgumentExpression(it: ValueArgument) =
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) { override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
// skip calls in object declarations // skip calls in object declarations
@@ -159,10 +163,10 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor) val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor)
result.add(KotlinMethodSmartStepTarget(getterDescriptor, label, expression, lines)) result.add(KotlinMethodSmartStepTarget(getterDescriptor, label, expression, lines))
} }
} } else {
else {
val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor
val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor) val label =
"${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor)
result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, label, expression, lines)) result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, label, expression, lines))
} }
} }
@@ -180,8 +184,7 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
(DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) as? PsiMethod)?.let { (DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) as? PsiMethod)?.let {
result.add(MethodSmartStepTarget(it, null, expression, false, lines)) result.add(MethodSmartStepTarget(it, null, expression, false, lines))
} }
} } else {
else {
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) { if (descriptor is ConstructorDescriptor && descriptor.isPrimary) {
val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
if (psiElement is KtClass && psiElement.getAnonymousInitializers().isEmpty()) { if (psiElement is KtClass && psiElement.getAnonymousInitializers().isEmpty()) {
@@ -216,7 +219,8 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.callingExpressionLines!!) KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.callingExpressionLines!!)
is KotlinLambdaSmartStepTarget -> is KotlinLambdaSmartStepTarget ->
KotlinLambdaMethodFilter( KotlinLambdaMethodFilter(
stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend) stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend
)
else -> super.createMethodFilter(stepTarget) else -> super.createMethodFilter(stepTarget)
} }
} }
@@ -61,13 +61,15 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
// TODO: ask for better API // TODO: ask for better API
// Should be safe to use reflection as method is protected and not obfuscated // Should be safe to use reflection as method is protected and not obfuscated
private fun doStep( private fun doStep(
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
stepThread: ThreadReferenceProxyImpl, stepThread: ThreadReferenceProxyImpl,
size: Int, depth: Int, hint: RequestHint) { size: Int, depth: Int, hint: RequestHint
) {
val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod( val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod(
"doStep", "doStep",
SuspendContextImpl::class.java, ThreadReferenceProxyImpl::class.java, SuspendContextImpl::class.java, ThreadReferenceProxyImpl::class.java,
Integer.TYPE, Integer.TYPE, RequestHint::class.java) Integer.TYPE, Integer.TYPE, RequestHint::class.java
)
doStepMethod.isAccessible = true doStepMethod.isAccessible = true
@@ -89,7 +91,8 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
return field.get(debuggerProcess) as T return field.get(debuggerProcess) as T
} }
private inner class StepOverInlineCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) : KotlinStepAction() { private inner class StepOverInlineCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) :
KotlinStepAction() {
private fun getContextThread(suspendContext: SuspendContextImpl): ThreadReferenceProxyImpl? { private fun getContextThread(suspendContext: SuspendContextImpl): ThreadReferenceProxyImpl? {
val contextThread = debuggerContext.threadProxy val contextThread = debuggerContext.threadProxy
return contextThread ?: suspendContext.thread return contextThread ?: suspendContext.thread
@@ -114,8 +117,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
} }
if (isResumeOnlyCurrentThread && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) { if (isResumeOnlyCurrentThread && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
suspendManager.resumeThread(suspendContext, thread) suspendManager.resumeThread(suspendContext, thread)
} } else {
else {
suspendManager.resume(suspendContext) suspendManager.resume(suspendContext)
} }
} }
@@ -136,8 +138,7 @@ class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
try { try {
session.setIgnoreStepFiltersFlag(stepThread.frameCount()) session.setIgnoreStepFiltersFlag(stepThread.frameCount())
} } catch (e: EvaluateException) {
catch (e: EvaluateException) {
LOG.info(e) LOG.info(e)
} }
@@ -25,11 +25,11 @@ import com.sun.jdi.Location
import org.jetbrains.kotlin.idea.debugger.ktLocationInfo import org.jetbrains.kotlin.idea.debugger.ktLocationInfo
class StepOverFilterData( class StepOverFilterData(
val lineNumber: Int, val lineNumber: Int,
val stepOverLines: Set<Int>, val stepOverLines: Set<Int>,
val inlineRangeVariables: List<LocalVariable>, val inlineRangeVariables: List<LocalVariable>,
val isDexDebug: Boolean, val isDexDebug: Boolean,
val skipAfterCodeIndex: Long = -1 val skipAfterCodeIndex: Long = -1
) )
class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter { class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter {
@@ -16,22 +16,20 @@
package org.jetbrains.kotlin.idea.debugger.stepping package org.jetbrains.kotlin.idea.debugger.stepping
import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.RequestHint
import com.intellij.debugger.engine.* import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.jdi.StackFrameProxy
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.Computable
import com.sun.jdi.VMDisconnectedException import com.sun.jdi.VMDisconnectedException
import com.sun.jdi.request.StepRequest import com.sun.jdi.request.StepRequest
// Originally copied from RequestHint // Originally copied from RequestHint
class KotlinStepOverInlinedLinesHint( class KotlinStepOverInlinedLinesHint(
stepThread: ThreadReferenceProxyImpl, stepThread: ThreadReferenceProxyImpl,
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
methodFilter: KotlinMethodFilter) : RequestHint(stepThread, suspendContext, methodFilter) { methodFilter: KotlinMethodFilter
) : RequestHint(stepThread, suspendContext, methodFilter) {
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java) private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
@@ -46,8 +44,7 @@ class KotlinStepOverInlinedLinesHint(
if (isTheSameFrame(context)) { if (isTheSameFrame(context)) {
return if (filter.locationMatches(context, frameProxy.location())) { return if (filter.locationMatches(context, frameProxy.location())) {
STOP STOP
} } else {
else {
StepRequest.STEP_OVER StepRequest.STEP_OVER
} }
} }
@@ -58,10 +55,8 @@ class KotlinStepOverInlinedLinesHint(
return StepRequest.STEP_OUT return StepRequest.STEP_OUT
} }
} } catch (ignored: VMDisconnectedException) {
catch (ignored: VMDisconnectedException) { } catch (e: EvaluateException) {
}
catch (e: EvaluateException) {
LOG.error(e) LOG.error(e)
} }
@@ -62,9 +62,9 @@ import org.jetbrains.kotlin.utils.keysToMap
class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() { class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
override fun getStepOverCommand( override fun getStepOverCommand(
suspendContext: SuspendContextImpl?, suspendContext: SuspendContextImpl?,
ignoreBreakpoints: Boolean, ignoreBreakpoints: Boolean,
stepSize: Int stepSize: Int
): DebugProcessImpl.ResumeCommand? { ): DebugProcessImpl.ResumeCommand? {
if (suspendContext == null || suspendContext.isResumed) return null if (suspendContext == null || suspendContext.isResumed) return null
@@ -74,17 +74,18 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
@TestOnly @TestOnly
fun getStepOverCommand( fun getStepOverCommand(
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
ignoreBreakpoints: Boolean, ignoreBreakpoints: Boolean,
debuggerContext: DebuggerContextImpl debuggerContext: DebuggerContextImpl
): DebugProcessImpl.ResumeCommand? { ): DebugProcessImpl.ResumeCommand? {
return getStepOverCommand(suspendContext, ignoreBreakpoints, debuggerContext.sourcePosition) return getStepOverCommand(suspendContext, ignoreBreakpoints, debuggerContext.sourcePosition)
} }
private fun getStepOverCommand( private fun getStepOverCommand(
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
ignoreBreakpoints: Boolean, ignoreBreakpoints: Boolean,
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)) { if (isSpecialStepOverNeeded(kotlinSourcePosition)) {
@@ -95,14 +96,17 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) { if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter( return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints)) suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints)
)
} }
return null return null
} }
data class KotlinSourcePosition(val file: KtFile, val function: KtNamedFunction, data class KotlinSourcePosition(
val linesRange: IntRange, val sourcePosition: SourcePosition) { val file: KtFile, val function: KtNamedFunction,
val linesRange: IntRange, val sourcePosition: SourcePosition
) {
companion object { companion object {
fun create(sourcePosition: SourcePosition): KotlinSourcePosition? { fun create(sourcePosition: SourcePosition): KotlinSourcePosition? {
val file = sourcePosition.file as? KtFile ?: return null val file = sourcePosition.file as? KtFile ?: return null
@@ -110,8 +114,8 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
val elementAt = sourcePosition.elementAt ?: return null val elementAt = sourcePosition.elementAt ?: return null
val containingFunction = elementAt.parents val containingFunction = elementAt.parents
.filterIsInstance<KtNamedFunction>() .filterIsInstance<KtNamedFunction>()
.firstOrNull { !it.isLocal } ?: return null .firstOrNull { !it.isLocal } ?: return null
val startLineNumber = containingFunction.getLineNumber(true) + 1 val startLineNumber = containingFunction.getLineNumber(true) + 1
val endLineNumber = containingFunction.getLineNumber(false) + 1 val endLineNumber = containingFunction.getLineNumber(false) + 1
@@ -193,14 +197,18 @@ private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunc
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> { private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
return inlineFunctionCalls.flatMap { return inlineFunctionCalls.flatMap {
it.valueArguments it.valueArguments
.map(::getArgumentExpression) .map(::getArgumentExpression)
.filterIsInstance<KtFunction>() .filterIsInstance<KtFunction>()
} }
} }
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() private fun getArgumentExpression(it: ValueArgument) =
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
private fun getInlineArgumentsCallsIfAny(sourcePosition: SourcePosition, declarationDescriptor: DeclarationDescriptor): List<KtCallExpression>? { private fun getInlineArgumentsCallsIfAny(
sourcePosition: SourcePosition,
declarationDescriptor: DeclarationDescriptor
): List<KtCallExpression>? {
if (declarationDescriptor !is CallableDescriptor) return null if (declarationDescriptor !is CallableDescriptor) return null
val valueParameters = declarationDescriptor.valueParameters.filter { it.type.isFunctionType }.toSet() val valueParameters = declarationDescriptor.valueParameters.filter { it.type.isFunctionType }.toSet()
@@ -249,12 +257,11 @@ private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallE
val start = lineElement.startOffset val start = lineElement.startOffset
val end = lineElement.endOffset val end = lineElement.endOffset
val allFilteredCalls = CodeInsightUtils. val allFilteredCalls = CodeInsightUtils.findElementsOfClassInRange(file, start, end, KtExpression::class.java)
findElementsOfClassInRange(file, start, end, KtExpression::class.java) .map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) } .filterIsInstance<KtCallExpression>()
.filterIsInstance<KtCallExpression>() .filter { filter(it) }
.filter { filter(it) } .toSet()
.toSet()
// It is necessary to check range because of multiline assign // It is necessary to check range because of multiline assign
var linesRange = lineNumber..lineNumber var linesRange = lineNumber..lineNumber
@@ -267,16 +274,20 @@ private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallE
} }
} }
sealed class Action(val position: XSourcePositionImpl? = null, sealed class Action(
val stepOverInlineData: StepOverFilterData? = null) { val position: XSourcePositionImpl? = null,
val stepOverInlineData: StepOverFilterData? = null
) {
class STEP_OVER : Action() { class STEP_OVER : Action() {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) = override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext) debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
} }
class STEP_OUT : Action() { class STEP_OUT : Action() {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) = override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext) debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext)
} }
class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) { class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) { override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
return runReadAction { return runReadAction {
@@ -284,10 +295,12 @@ sealed class Action(val position: XSourcePositionImpl? = null,
}.contextAction(suspendContext) }.contextAction(suspendContext)
} }
} }
class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) { class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) { override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction( return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(
KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)).contextAction(suspendContext) KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)
).contextAction(suspendContext)
} }
} }
@@ -299,26 +312,28 @@ interface KotlinMethodFilter : MethodFilter {
} }
fun getStepOverAction( fun getStepOverAction(
location: Location, location: Location,
kotlinSourcePosition: KotlinSteppingCommandProvider.KotlinSourcePosition, kotlinSourcePosition: KotlinSteppingCommandProvider.KotlinSourcePosition,
frameProxy: StackFrameProxyImpl, frameProxy: StackFrameProxyImpl,
isDexDebug: Boolean isDexDebug: Boolean
): Action { ): Action {
val inlineArgumentsToSkip = runReadAction { val inlineArgumentsToSkip = runReadAction {
getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition) getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition)
} }
return getStepOverAction(location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange, return getStepOverAction(
inlineArgumentsToSkip, frameProxy, isDexDebug) location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange,
inlineArgumentsToSkip, frameProxy, isDexDebug
)
} }
fun getStepOverAction( fun getStepOverAction(
location: Location, location: Location,
sourceFile: KtFile, sourceFile: KtFile,
range: IntRange, range: IntRange,
inlineFunctionArguments: List<KtElement>, inlineFunctionArguments: List<KtElement>,
frameProxy: StackFrameProxyImpl, frameProxy: StackFrameProxyImpl,
isDexDebug: Boolean isDexDebug: Boolean
): Action { ): Action {
location.declaringType() ?: return Action.STEP_OVER() location.declaringType() ?: return Action.STEP_OVER()
@@ -346,19 +361,18 @@ fun getStepOverAction(
try { try {
return nextLocation.ktFileName() == sourceFile.name return nextLocation.ktFileName() == sourceFile.name
} } catch (e: AbsentInformationException) {
catch(e: AbsentInformationException) {
return true return true
} }
} }
fun isBackEdgeLocation(): Boolean { fun isBackEdgeLocation(): Boolean {
val previousSuitableLocation = methodLocations.reversed() val previousSuitableLocation = methodLocations.reversed()
.dropWhile { it != location } .dropWhile { it != location }
.drop(1) .drop(1)
.filter(::isThisMethodLocation) .filter(::isThisMethodLocation)
.dropWhile { it.ktLineNumber() == location.ktLineNumber() } .dropWhile { it.ktLineNumber() == location.ktLineNumber() }
.firstOrNull() .firstOrNull()
return previousSuitableLocation != null && previousSuitableLocation.ktLineNumber() > location.ktLineNumber() return previousSuitableLocation != null && previousSuitableLocation.ktLineNumber() > location.ktLineNumber()
} }
@@ -366,10 +380,9 @@ fun getStepOverAction(
val patchedLocation = if (isBackEdgeLocation()) { val patchedLocation = if (isBackEdgeLocation()) {
// Pretend we had already done a backing step // Pretend we had already done a backing step
methodLocations methodLocations
.filter(::isThisMethodLocation) .filter(::isThisMethodLocation)
.firstOrNull { it.ktLineNumber() == location.ktLineNumber() } ?: location .firstOrNull { it.ktLineNumber() == location.ktLineNumber() } ?: location
} } else {
else {
location location
} }
@@ -394,12 +407,12 @@ fun getStepOverAction(
// We might erroneously extend this range too much when there's a call of function argument or other // We might erroneously extend this range too much when there's a call of function argument or other
// inline function in last statement of inline function. The list of inlineRangeVariables will be used later to overcome it. // inline function in last statement of inline function. The list of inlineRangeVariables will be used later to overcome it.
val stepOverLocations = methodLocations val stepOverLocations = methodLocations
.dropWhile { it != patchedLocation } .dropWhile { it != patchedLocation }
.drop(1) .drop(1)
.dropWhile { it.ktLineNumber() == patchedLineNumber } .dropWhile { it.ktLineNumber() == patchedLineNumber }
.takeWhile { loc -> .takeWhile { loc ->
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber !isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber
} }
if (!stepOverLocations.isEmpty()) { if (!stepOverLocations.isEmpty()) {
// Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact. // Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact.
@@ -412,39 +425,39 @@ fun getStepOverAction(
val locationsOfLine = method.safeLocationsOfLine(range.last) val locationsOfLine = method.safeLocationsOfLine(range.last)
if (locationsOfLine.isNotEmpty()) { if (locationsOfLine.isNotEmpty()) {
locationsOfLine.map { it.codeIndex() }.max() ?: -1L locationsOfLine.map { it.codeIndex() }.max() ?: -1L
} } else {
else {
findReturnFromDexBytecode(location.method()) findReturnFromDexBytecode(location.method())
} }
} } else -1L
else -1L
return Action.STEP_OVER_INLINED(StepOverFilterData( return Action.STEP_OVER_INLINED(
StepOverFilterData(
patchedLineNumber, patchedLineNumber,
stepOverLocations.map { it.ktLineNumber() }.toSet(), stepOverLocations.map { it.ktLineNumber() }.toSet(),
inlineRangeVariables, inlineRangeVariables,
isDexDebug, isDexDebug,
returnCodeIndex returnCodeIndex
)) )
)
} }
return Action.STEP_OVER() return Action.STEP_OVER()
} }
fun getStepOutAction( fun getStepOutAction(
location: Location, location: Location,
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
inlineFunctions: List<KtNamedFunction>, inlineFunctions: List<KtNamedFunction>,
inlinedArgument: KtFunctionLiteral? inlinedArgument: KtFunctionLiteral?
): Action { ): Action {
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT() val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
val locations = computedReferenceType.safeAllLineLocations() val locations = computedReferenceType.safeAllLineLocations()
val nextLineLocations = locations val nextLineLocations = locations
.dropWhile { it != location } .dropWhile { it != location }
.drop(1) .drop(1)
.filter { it.method() == location.method() } .filter { it.method() == location.method() }
.dropWhile { it.lineNumber() == location.lineNumber() } .dropWhile { it.lineNumber() == location.lineNumber() }
if (inlineFunctions.isNotEmpty()) { if (inlineFunctions.isNotEmpty()) {
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
@@ -460,11 +473,10 @@ fun getStepOutAction(
} }
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
locations: List<Location>, locations: List<Location>,
inlineFunctionsToSkip: List<KtNamedFunction> inlineFunctionsToSkip: List<KtNamedFunction>
): XSourcePositionImpl? { ): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) { return getNextPositionWithFilter(locations) { offset, elementAt ->
offset, elementAt ->
if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) { if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) {
return@getNextPositionWithFilter true return@getNextPositionWithFilter true
} }
@@ -474,27 +486,25 @@ private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
} }
private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument( private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument(
locations: List<Location>, locations: List<Location>,
inlinedArgumentToSkip: KtFunctionLiteral inlinedArgumentToSkip: KtFunctionLiteral
): XSourcePositionImpl? { ): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) { return getNextPositionWithFilter(locations) { offset, _ ->
offset, _ ->
inlinedArgumentToSkip.textRange.contains(offset) inlinedArgumentToSkip.textRange.contains(offset)
} }
} }
private fun SuspendContextImpl.getNextPositionWithFilter( private fun SuspendContextImpl.getNextPositionWithFilter(
locations: List<Location>, locations: List<Location>,
skip: (Int, PsiElement) -> Boolean skip: (Int, PsiElement) -> Boolean
): XSourcePositionImpl? { ): XSourcePositionImpl? {
for (location in locations) { for (location in locations) {
val position = runReadAction l@ { val position = runReadAction l@{
val sourcePosition = try { val sourcePosition = try {
this.debugProcess.positionManager.getSourcePosition(location) this.debugProcess.positionManager.getSourcePosition(location)
} } catch (e: NoDataException) {
catch(e: NoDataException) { null
null } ?: return@l null
} ?: return@l null
val file = sourcePosition.file as? KtFile ?: return@l null val file = sourcePosition.file as? KtFile ?: return@l null
val elementAt = sourcePosition.elementAt ?: return@l null val elementAt = sourcePosition.elementAt ?: return@l null
@@ -514,11 +524,11 @@ private fun SuspendContextImpl.getNextPositionWithFilter(
fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> { fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> {
return stackFrame.visibleVariables() return stackFrame.visibleVariables()
.filter { .filter {
val name = it.name() val name = it.name()
name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
} }
.map { it.variable } .map { it.variable }
} }
private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? { private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
@@ -541,8 +551,7 @@ private fun findReturnFromDexBytecode(method: Method): Long {
while (true) { while (true) {
if (method.locationOfCodeIndex(lastMethodCodeIndex + 1) != null) { if (method.locationOfCodeIndex(lastMethodCodeIndex + 1) != null) {
lastMethodCodeIndex++ lastMethodCodeIndex++
} } else {
else {
break break
} }
} }
@@ -562,11 +571,9 @@ private fun findReturnFromDexBytecode(method: Method): Long {
if (instruction == RETURN_VOID || instruction == RETURN || instruction == RETURN_WIDE || instruction == RETURN_OBJECT) { if (instruction == RETURN_VOID || instruction == RETURN || instruction == RETURN_WIDE || instruction == RETURN_OBJECT) {
// Instruction found // Instruction found
return returnIndex return returnIndex
} } else if (instruction == MOVE || instruction == GOTO) {
else if (instruction == MOVE || instruction == GOTO) {
// proceed // proceed
} } else {
else {
// Don't know the instruction and it's length. Abort. // Don't know the instruction and it's length. Abort.
break break
} }
@@ -33,9 +33,10 @@ import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
class KotlinSuspendCallStepOverFilter( class KotlinSuspendCallStepOverFilter(
private val line: Int, private val line: Int,
private val file: PsiFile, private val file: PsiFile,
private val ignoreBreakpoints: Boolean) : MethodFilter { private val ignoreBreakpoints: Boolean
) : MethodFilter {
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line) override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean { override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
@@ -66,10 +67,9 @@ private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int,
} }
val runToCursorBreakpoint = val runToCursorBreakpoint =
runReadAction { runReadAction {
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints) DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
} ?: } ?: return
return
runToCursorBreakpoint.suspendPolicy = when { runToCursorBreakpoint.suspendPolicy = when {
context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
@@ -29,13 +29,16 @@ import org.intellij.lang.annotations.MagicConstant
import java.lang.reflect.Field import java.lang.reflect.Field
internal class RequestHintWithMethodFilter( internal class RequestHintWithMethodFilter(
stepThread: ThreadReferenceProxyImpl, stepThread: ThreadReferenceProxyImpl,
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
@MagicConstant(intValues = longArrayOf( @MagicConstant(
StepRequest.STEP_INTO.toLong(), intValues = longArrayOf(
StepRequest.STEP_OVER.toLong(), StepRequest.STEP_INTO.toLong(),
StepRequest.STEP_OUT.toLong())) depth: Int, StepRequest.STEP_OVER.toLong(),
methodFilter: MethodFilter StepRequest.STEP_OUT.toLong()
)
) depth: Int,
methodFilter: MethodFilter
) : RequestHint(stepThread, suspendContext, methodFilter) { ) : RequestHint(stepThread, suspendContext, methodFilter) {
private var targetMethodMatched = false private var targetMethodMatched = false
@@ -71,11 +74,9 @@ internal class RequestHintWithMethodFilter(
return filter.onReached(context, this) return filter.onReached(context, this)
} }
} }
} } catch (ignored: VMDisconnectedException) {
catch (ignored: VMDisconnectedException) {
return STOP return STOP
} } catch (e: EvaluateException) {
catch (e: EvaluateException) {
LOG.error(e) LOG.error(e)
return STOP return STOP
} }