Debugger: do not compute location during creation of step command

This commit is contained in:
Natalia Ukhorskaya
2015-10-09 15:16:41 +03:00
parent bf4a71ee02
commit d441f2cf8a
2 changed files with 246 additions and 143 deletions
@@ -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<JetFunction> inlineArguments,
final List<PsiElement> 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<DebugProcessImpl.ResumeCommand>() {
@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<JetNamedFunction> 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<DebugProcessImpl.ResumeCommand>() {
@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();
}
};
}
}
@@ -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<JetNamedFunction>(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<JetNamedFunction>(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<PsiElement> {
@@ -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<Location>,
inlineFunctionsToSkip: List<JetNamedFunction>
): 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<Location>,
inlinedArgumentToSkip: JetFunctionLiteral
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) {
file, offset ->
inlinedArgumentToSkip.textRange.contains(offset)
}
}
private fun SuspendContextImpl.getNextPositionWithFilter(
locations: List<Location>,
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<JetNamedFunction> {
@@ -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<JetFunctionLiteralExpression>(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 <T: Any> 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<JetCallExpression>): List<JetFunction> {
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<JetCallExpression>): List<JetFunction> {
return inlineFunctionCalls.flatMap {
it.valueArguments
.map { it.getArgumentExpression() }
@@ -375,3 +250,106 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
}
}
}
fun getStepOutPosition(
location: Location,
file: JetFile,
range: Range<Int>,
inlinedArguments: List<JetElement>,
elementsToSkip: List<PsiElement>
): 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<JetNamedFunction>,
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<Location>,
inlineFunctionsToSkip: List<JetNamedFunction>
): 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<Location>,
inlinedArgumentToSkip: JetFunctionLiteral
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) {
file, offset ->
inlinedArgumentToSkip.textRange.contains(offset)
}
}
private fun SuspendContextImpl.getNextPositionWithFilter(
locations: List<Location>,
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<JetFunctionLiteralExpression>(false) ?: return null
val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL)
if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null
return functionLiteralExpression.functionLiteral
}