From d441f2cf8ae2f6b8140d4ae0bcd7e243f8d589ed Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Fri, 9 Oct 2015 15:16:41 +0300 Subject: [PATCH] Debugger: do not compute location during creation of step command --- .../stepping/DebuggerSteppingHelper.java | 125 +++++++++ .../stepping/KotlinSteppingCommandProvider.kt | 264 ++++++++---------- 2 files changed, 246 insertions(+), 143 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java new file mode 100644 index 00000000000..67cd873b3d6 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java @@ -0,0 +1,125 @@ +/* + * Copyright 2010-2015 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.SuspendContextImpl; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.jdi.StackFrameProxyImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Computable; +import com.intellij.psi.PsiElement; +import com.intellij.xdebugger.impl.XSourcePositionImpl; +import kotlin.IntRange; +import org.jetbrains.kotlin.psi.JetFile; +import org.jetbrains.kotlin.psi.JetFunction; +import org.jetbrains.kotlin.psi.JetFunctionLiteral; +import org.jetbrains.kotlin.psi.JetNamedFunction; + +import java.util.List; + +public class DebuggerSteppingHelper { + + public static DebugProcessImpl.ResumeCommand createStepOverCommand( + final SuspendContextImpl suspendContext, + final boolean ignoreBreakpoints, + final JetFile file, + final IntRange linesRange, + final List inlineArguments, + final List additionalElementsToSkip + ) { + final DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); + return debugProcess.new ResumeCommand(suspendContext) { + @Override + public void contextAction() { + final StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy(); + if (frameProxy != null) { + DebugProcessImpl.ResumeCommand runToCursorCommand = ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public DebugProcessImpl.ResumeCommand compute() { + try { + XSourcePositionImpl position = KotlinSteppingCommandProviderKt.getStepOutPosition( + frameProxy.location(), + file, + linesRange, + inlineArguments, + additionalElementsToSkip + ); + if (position != null) { + return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints); + } + } + catch (EvaluateException ignored) { + } + return null; + } + }); + + if (runToCursorCommand != null) { + runToCursorCommand.contextAction(); + return; + } + } + + debugProcess.createStepOutCommand(suspendContext).contextAction(); + } + }; + } + + public static DebugProcessImpl.ResumeCommand createStepOutCommand( + final SuspendContextImpl suspendContext, + final boolean ignoreBreakpoints, + final List inlineFunctions, + final JetFunctionLiteral inlineArgument + ) { + final DebugProcessImpl debugProcess = suspendContext.getDebugProcess(); + return debugProcess.new ResumeCommand(suspendContext) { + @Override + public void contextAction() { + final StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy(); + if (frameProxy != null) { + DebugProcessImpl.ResumeCommand runToCursorCommand = ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public DebugProcessImpl.ResumeCommand compute() { + try { + XSourcePositionImpl position = KotlinSteppingCommandProviderKt.getStepOverPosition( + frameProxy.location(), + suspendContext, + inlineFunctions, + inlineArgument + ); + if (position != null) { + return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints); + } + } + catch (EvaluateException ignored) { + } + return null; + } + }); + + if (runToCursorCommand != null) { + runToCursorCommand.contextAction(); + return; + } + } + + debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(); + } + }; + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 268ce130c27..2223c346598 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -19,11 +19,8 @@ package org.jetbrains.kotlin.idea.debugger.stepping import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.SuspendContextImpl -import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.impl.JvmSteppingCommandProvider -import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.psi.PsiElement -import com.intellij.util.concurrency.Semaphore import com.intellij.xdebugger.impl.XSourcePositionImpl import com.sun.jdi.Location import org.jetbrains.kotlin.idea.caches.resolve.analyze @@ -51,49 +48,31 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { ): DebugProcessImpl.ResumeCommand? { if (suspendContext == null || suspendContext.isResumed) return null - val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null + val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition + val file = sourcePosition.file as? JetFile ?: return null + if (sourcePosition.line < 0) return null + + val containingFunction = sourcePosition.elementAt.getParentOfType(false) ?: return null + + val startLineNumber = containingFunction.getLineNumber(true) + val endLineNumber = containingFunction.getLineNumber(false) + if (startLineNumber > endLineNumber) return null + + val linesRange = startLineNumber + 1..endLineNumber + 1 val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition) if (inlineFunctionCalls.isEmpty()) return null - val inlinedArguments = getInlinedArgumentsIfAny(inlineFunctionCalls) + val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls) - if (inlinedArguments.any { it.shouldNotUseStepOver(sourcePosition.elementAt) }) { + if (inlineArguments.any { it.shouldNotUseStepOver(sourcePosition.elementAt) }) { return null } - val location = computeInManagerThread(suspendContext) { - it.safeFrameProxy?.location() - } ?: return null - - val computedReferenceType = location.declaringType() ?: return null - val additionalElementsToSkip = sourcePosition.elementAt.getAdditionalElementsToSkip() - val containingFunction = sourcePosition.elementAt.getParentOfType(false) - val startLineNumber = containingFunction?.getLineNumber(true) ?: return null - val endLineNumber = containingFunction?.getLineNumber(false) ?: return null - val linesRange = startLineNumber + 1..endLineNumber + 1 - val locations = computedReferenceType.allLineLocations() - .dropWhile { it != location } - .drop(1) - .filter { it.method() == location.method() && it.lineNumber() in linesRange } - .dropWhile { it.lineNumber() == location.lineNumber() } - - for (locationAtLine in locations) { - val lineNumber = locationAtLine.lineNumber() - val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue - if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue - if (additionalElementsToSkip.any { it.textRange.contains(lineStartOffset) }) continue - - val elementAt = file.findElementAt(lineStartOffset) ?: continue - - val xPosition = XSourcePositionImpl.createByElement(elementAt) ?: return null - return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, ignoreBreakpoints) - } - - return suspendContext.debugProcess.createStepOutCommand(suspendContext)!! + return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArguments, additionalElementsToSkip) } private fun PsiElement.getAdditionalElementsToSkip(): List { @@ -185,7 +164,9 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { if (suspendContext == null || suspendContext.isResumed) return null val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null + val file = sourcePosition.file as? JetFile ?: return null + if (sourcePosition.line < 0) return null val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null @@ -194,68 +175,7 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { if (inlineFunctions.isEmpty() && inlinedArgument == null) return null - val location = computeInManagerThread(suspendContext) { - it.safeFrameProxy?.location() - } ?: return null - - val computedReferenceType = location.declaringType() ?: return null - - val locations = computedReferenceType.allLineLocations() - val nextLineLocations = locations.dropWhile { it.lineNumber() != location.lineNumber() }.filter { it.method() == location.method() } - - if (inlineFunctions.isNotEmpty()) { - val xPosition = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) ?: return null - return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, true) - } - - if (inlinedArgument != null) { - val xPosition = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) ?: return null - return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, true) - } - - return null - } - - private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( - locations: List, - inlineFunctionsToSkip: List - ): XSourcePositionImpl? { - return getNextPositionWithFilter(locations) { - file, offset -> - if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) { - return@getNextPositionWithFilter true - } - - val inlinedArgument = getInlineArgumentIfAny(file, offset) - inlinedArgument != null && inlinedArgument.textRange.contains(offset) - } - } - - private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument( - locations: List, - inlinedArgumentToSkip: JetFunctionLiteral - ): XSourcePositionImpl? { - return getNextPositionWithFilter(locations) { - file, offset -> - inlinedArgumentToSkip.textRange.contains(offset) - } - } - - private fun SuspendContextImpl.getNextPositionWithFilter( - locations: List, - skip: (JetFile, Int) -> Boolean - ): XSourcePositionImpl? { - for (location in locations) { - val file = this.debugProcess.positionManager.getSourcePosition(location)?.file as? JetFile ?: continue - val currentLine = location.lineNumber() - 1 - val lineStartOffset = file.getLineStartOffset(currentLine) ?: continue - if (skip(file, lineStartOffset)) continue - - val elementAt = file.findElementAt(lineStartOffset) ?: continue - return XSourcePositionImpl.createByElement(elementAt) - } - - return null + return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument) } private fun getInlineFunctionsIfAny(file: JetFile, offset: Int): List { @@ -275,52 +195,7 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { return inlineFunctionsCalls } - private fun getInlineArgumentIfAny(file: JetFile, offset: Int): JetFunctionLiteral? { - val elementAt = file.findElementAt(offset) ?: return null - val functionLiteralExpression = elementAt.getParentOfType(false) ?: return null - - val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL) - if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null - - return functionLiteralExpression.functionLiteral - } - - private val SuspendContextImpl.safeFrameProxy: StackFrameProxyImpl? - get() = if (isResumed) null else frameProxy - - private fun isKotlinStrataAvailable(suspendContext: SuspendContextImpl): Boolean { - val availableStrata = suspendContext.safeFrameProxy?.location()?.declaringType()?.availableStrata() ?: return false - return availableStrata.contains("Kotlin") - } - - private fun computeInManagerThread(suspendContext: SuspendContextImpl, action: (SuspendContextImpl) -> T?): T? { - val semaphore = Semaphore() - semaphore.down() - - var result : T? = null - val worker = object : DebuggerCommandImpl() { - override fun action() { - try { - if (isKotlinStrataAvailable(suspendContext)) { - result = action(suspendContext) - } - } - finally { - semaphore.up() - } - } - } - - suspendContext.debugProcess.managerThread?.invoke(worker) - - for (i in 0..25) { - if (semaphore.waitFor(20)) break - } - - return result - } - - private fun getInlinedArgumentsIfAny(inlineFunctionCalls: List): List { + private fun getInlineArgumentsIfAny(inlineFunctionCalls: List): List { return inlineFunctionCalls.flatMap { it.valueArguments .map { it.getArgumentExpression() } @@ -375,3 +250,106 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { } } } + +fun getStepOutPosition( + location: Location, + file: JetFile, + range: Range, + inlinedArguments: List, + elementsToSkip: List +): XSourcePositionImpl? { + val computedReferenceType = location.declaringType() ?: return null + + val locations = computedReferenceType.allLineLocations() + .dropWhile { it != location } + .drop(1) + .filter { it.method() == location.method() && it.lineNumber() in range } + .dropWhile { it.lineNumber() == location.lineNumber() } + + for (locationAtLine in locations) { + val lineNumber = locationAtLine.lineNumber() + val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue + if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue + if (elementsToSkip.any { it.textRange.contains(lineStartOffset) }) continue + + val elementAt = file.findElementAt(lineStartOffset) ?: continue + + return XSourcePositionImpl.createByElement(elementAt) ?: return null + } + + return null +} + +fun getStepOverPosition( + location: Location, + suspendContext: SuspendContextImpl, + inlineFunctions: List, + inlinedArgument: JetFunctionLiteral? +): XSourcePositionImpl? { + val computedReferenceType = location.declaringType() ?: return null + + val locations = computedReferenceType.allLineLocations() + val nextLineLocations = locations.dropWhile { it.lineNumber() != location.lineNumber() }.filter { it.method() == location.method() } + + if (inlineFunctions.isNotEmpty()) { + return suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) ?: return null + } + + if (inlinedArgument != null) { + return suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) ?: return null + } + + return null +} + +private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( + locations: List, + inlineFunctionsToSkip: List +): XSourcePositionImpl? { + return getNextPositionWithFilter(locations) { + file, offset -> + if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) { + return@getNextPositionWithFilter true + } + + val inlinedArgument = getInlineArgumentIfAny(file, offset) + inlinedArgument != null && inlinedArgument.textRange.contains(offset) + } +} + +private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument( + locations: List, + inlinedArgumentToSkip: JetFunctionLiteral +): XSourcePositionImpl? { + return getNextPositionWithFilter(locations) { + file, offset -> + inlinedArgumentToSkip.textRange.contains(offset) + } +} + +private fun SuspendContextImpl.getNextPositionWithFilter( + locations: List, + skip: (JetFile, Int) -> Boolean +): XSourcePositionImpl? { + for (location in locations) { + val file = this.debugProcess.positionManager.getSourcePosition(location)?.file as? JetFile ?: continue + val currentLine = location.lineNumber() - 1 + val lineStartOffset = file.getLineStartOffset(currentLine) ?: continue + if (skip(file, lineStartOffset)) continue + + val elementAt = file.findElementAt(lineStartOffset) ?: continue + return XSourcePositionImpl.createByElement(elementAt) + } + + return null +} + +private fun getInlineArgumentIfAny(file: JetFile, offset: Int): JetFunctionLiteral? { + val elementAt = file.findElementAt(offset) ?: return null + val functionLiteralExpression = elementAt.getParentOfType(false) ?: return null + + val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL) + if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null + + return functionLiteralExpression.functionLiteral +}