Debugger: do not compute location during creation of step command
This commit is contained in:
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+121
-143
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user