Debugger: fix step over inside inline function (do not step into function literal argument)

This commit is contained in:
Natalia Ukhorskaya
2016-04-06 17:30:35 +03:00
parent 8ad339836d
commit f0cfd450e5
9 changed files with 171 additions and 100 deletions
+4
View File
@@ -11,6 +11,10 @@
### IDE ### IDE
#### Debugger
- Do not step into inline lambda argument during step over inside inline function body
## 1.0.2 ## 1.0.2
### Compiler ### Compiler
@@ -20,12 +20,8 @@ import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.SuspendContextImpl; import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.jdi.StackFrameProxyImpl; 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.psi.PsiElement;
import com.intellij.xdebugger.impl.XSourcePositionImpl; import kotlin.ranges.IntRange;
import kotlin.*;
import kotlin.ranges.*;
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtFunction; import org.jetbrains.kotlin.psi.KtFunction;
import org.jetbrains.kotlin.psi.KtFunctionLiteral; import org.jetbrains.kotlin.psi.KtFunctionLiteral;
@@ -47,36 +43,29 @@ public class DebuggerSteppingHelper {
return debugProcess.new ResumeCommand(suspendContext) { return debugProcess.new ResumeCommand(suspendContext) {
@Override @Override
public void contextAction() { public void contextAction() {
final StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy(); try {
if (frameProxy != null) { StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
DebugProcessImpl.ResumeCommand runToCursorCommand = ApplicationManager.getApplication().runReadAction(new Computable<DebugProcessImpl.ResumeCommand>() { if (frameProxy != null) {
@Override Action action = KotlinSteppingCommandProviderKt.getStepOverPosition(
public DebugProcessImpl.ResumeCommand compute() { frameProxy.location(),
try { file,
XSourcePositionImpl position = KotlinSteppingCommandProviderKt.getStepOverPosition( linesRange,
frameProxy.location(), inlineArguments,
file, additionalElementsToSkip
linesRange, );
inlineArguments,
additionalElementsToSkip DebugProcessImpl.ResumeCommand command = action.createCommand(debugProcess, suspendContext, ignoreBreakpoints);
);
if (position != null) { if (command != null) {
return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints); command.contextAction();
} return;
}
catch (EvaluateException ignored) {
}
return null;
} }
});
if (runToCursorCommand != null) {
runToCursorCommand.contextAction();
return;
} }
}
debugProcess.createStepOutCommand(suspendContext).contextAction(); debugProcess.createStepOutCommand(suspendContext).contextAction();
}
catch (EvaluateException ignored) {
}
} }
}; };
} }
@@ -91,35 +80,29 @@ public class DebuggerSteppingHelper {
return debugProcess.new ResumeCommand(suspendContext) { return debugProcess.new ResumeCommand(suspendContext) {
@Override @Override
public void contextAction() { public void contextAction() {
final StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy(); try {
if (frameProxy != null) { StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
DebugProcessImpl.ResumeCommand runToCursorCommand = ApplicationManager.getApplication().runReadAction(new Computable<DebugProcessImpl.ResumeCommand>() { if (frameProxy != null) {
@Override Action action = KotlinSteppingCommandProviderKt.getStepOutPosition(
public DebugProcessImpl.ResumeCommand compute() { frameProxy.location(),
try { suspendContext,
XSourcePositionImpl position = KotlinSteppingCommandProviderKt.getStepOutPosition( inlineFunctions,
frameProxy.location(), inlineArgument
suspendContext, );
inlineFunctions,
inlineArgument DebugProcessImpl.ResumeCommand command = action.createCommand(debugProcess, suspendContext, ignoreBreakpoints);
);
if (position != null) { if (command != null) {
return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints); command.contextAction();
} return;
}
catch (EvaluateException ignored) {
}
return null;
} }
});
if (runToCursorCommand != null) {
runToCursorCommand.contextAction();
return;
} }
}
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(); debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
}
catch (EvaluateException ignored) {
}
} }
}; };
} }
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
import org.jetbrains.kotlin.idea.refactoring.getLineNumber import org.jetbrains.kotlin.idea.refactoring.getLineNumber
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.util.DebuggerUtils import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -78,22 +79,31 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
val linesRange = startLineNumber + 1..endLineNumber + 1 val linesRange = startLineNumber + 1..endLineNumber + 1
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition) val inlineArgumentsToSkip = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null
if (inlineFunctionCalls.isEmpty()) return null
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
if (inlineArguments.any { it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
return null
}
if (inlineArguments.isEmpty() && inlineFunctionCalls.any { it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
return null
}
val additionalElementsToSkip = sourcePosition.elementAt.getAdditionalElementsToSkip() val additionalElementsToSkip = sourcePosition.elementAt.getAdditionalElementsToSkip()
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArguments, additionalElementsToSkip) return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArgumentsToSkip, additionalElementsToSkip)
}
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
if (!inlineFunctionCalls.isEmpty()) {
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
return inlineArguments
}
if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
return emptyList()
}
}
if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) {
return emptyList()
}
return null
} }
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> { private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
@@ -287,14 +297,36 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
} }
} }
sealed class Action(val position: XSourcePositionImpl?) {
class STEP_OVER: Action(null)
class STEP_OUT: Action(null)
class RUN_TO_CURSOR(position: XSourcePositionImpl): Action(position)
fun createCommand(
debugProcess: DebugProcessImpl,
suspendContext: SuspendContextImpl,
ignoreBreakpoints: Boolean
): DebugProcessImpl.ResumeCommand? {
return when (this) {
is Action.RUN_TO_CURSOR -> {
runReadAction {
debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints)
}
}
is Action.STEP_OUT -> debugProcess.createStepOutCommand(suspendContext)
is Action.STEP_OVER -> debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints)
}
}
}
fun getStepOverPosition( fun getStepOverPosition(
location: Location, location: Location,
file: KtFile, file: KtFile,
range: IntRange, range: IntRange,
inlinedArguments: List<KtElement>, inlinedArguments: List<KtElement>,
elementsToSkip: List<PsiElement> elementsToSkip: List<PsiElement>
): XSourcePositionImpl? { ): Action {
val computedReferenceType = location.declaringType() ?: return null val computedReferenceType = location.declaringType() ?: return Action.STEP_OVER()
fun isLocationSuitable(nextLocation: Location): Boolean { fun isLocationSuitable(nextLocation: Location): Boolean {
if (nextLocation.method() != location.method() || nextLocation.lineNumber() !in range) { if (nextLocation.method() != location.method() || nextLocation.lineNumber() !in range) {
@@ -316,17 +348,28 @@ fun getStepOverPosition(
.dropWhile { it.lineNumber() == location.lineNumber() } .dropWhile { it.lineNumber() == location.lineNumber() }
for (locationAtLine in locations) { for (locationAtLine in locations) {
val lineNumber = locationAtLine.lineNumber() val xPosition = runReadAction l@ {
val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue val lineNumber = locationAtLine.lineNumber()
if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: return@l null
if (elementsToSkip.any { it.textRange.contains(lineStartOffset) }) continue if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) return@l null
if (elementsToSkip.any { it.textRange.contains(lineStartOffset) }) return@l null
val elementAt = file.findElementAt(lineStartOffset) ?: continue if (locationAtLine.lineNumber() == location.lineNumber()) return@l null
return XSourcePositionImpl.createByElement(elementAt) ?: return null val elementAt = file.findElementAt(lineStartOffset) ?: return@l null
XSourcePositionImpl.createByElement(elementAt)
}
if (xPosition != null) {
return Action.RUN_TO_CURSOR(xPosition)
}
} }
return null if (locations.isNotEmpty()) {
return Action.STEP_OUT()
}
return Action.STEP_OVER()
} }
fun getStepOutPosition( fun getStepOutPosition(
@@ -334,8 +377,8 @@ fun getStepOutPosition(
suspendContext: SuspendContextImpl, suspendContext: SuspendContextImpl,
inlineFunctions: List<KtNamedFunction>, inlineFunctions: List<KtNamedFunction>,
inlinedArgument: KtFunctionLiteral? inlinedArgument: KtFunctionLiteral?
): XSourcePositionImpl? { ): Action {
val computedReferenceType = location.declaringType() ?: return null val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
val locations = computedReferenceType.allLineLocations() val locations = computedReferenceType.allLineLocations()
val nextLineLocations = locations val nextLineLocations = locations
@@ -345,14 +388,16 @@ fun getStepOutPosition(
.dropWhile { it.lineNumber() == location.lineNumber() } .dropWhile { it.lineNumber() == location.lineNumber() }
if (inlineFunctions.isNotEmpty()) { if (inlineFunctions.isNotEmpty()) {
return suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) ?: return null val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
} }
if (inlinedArgument != null) { if (inlinedArgument != null) {
return suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) ?: return null val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
} }
return null return Action.STEP_OVER()
} }
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction( private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
@@ -384,20 +429,25 @@ private fun SuspendContextImpl.getNextPositionWithFilter(
skip: (Int, PsiElement) -> Boolean skip: (Int, PsiElement) -> Boolean
): XSourcePositionImpl? { ): XSourcePositionImpl? {
for (location in locations) { for (location in locations) {
val sourcePosition = try { val position = runReadAction l@ {
this.debugProcess.positionManager.getSourcePosition(location) val sourcePosition = try {
this.debugProcess.positionManager.getSourcePosition(location)
}
catch(e: NoDataException) {
null
} ?: return@l null
val file = sourcePosition.file as? KtFile ?: return@l null
val elementAt = sourcePosition.elementAt ?: return@l null
val currentLine = location.lineNumber() - 1
val lineStartOffset = file.getLineStartOffset(currentLine) ?: return@l null
if (skip(lineStartOffset, elementAt)) return@l null
XSourcePositionImpl.createByElement(elementAt)
}
if (position != null) {
return position
} }
catch(e: NoDataException) {
null
} ?: continue
val file = sourcePosition.file as? KtFile ?: continue
val elementAt = sourcePosition.elementAt ?: continue
val currentLine = location.lineNumber() - 1
val lineStartOffset = file.getLineStartOffset(currentLine) ?: continue
if (skip(lineStartOffset, elementAt)) continue
return XSourcePositionImpl.createByElement(elementAt)
} }
return null return null
@@ -17,7 +17,6 @@ stepOverIfWithInline.kt:24
stepOverIfWithInline.kt:28 stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:46 stepOverIfWithInline.kt:46
stepOverIfWithInline.kt:28 stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:32 stepOverIfWithInline.kt:32
stepOverIfWithInline.kt:46 stepOverIfWithInline.kt:46
stepOverIfWithInline.kt:32 stepOverIfWithInline.kt:32
@@ -0,0 +1,10 @@
LineBreakpoint created at stepOverInsideInlineFun.kt:11
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stepOverInsideInlineFun.StepOverInsideInlineFunKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOverInsideInlineFun.kt:11
stepOverInsideInlineFun.kt:12
stepOverInsideInlineFun.kt:13
stepOverInsideInlineFun.kt:7
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -17,6 +17,9 @@ stepOverTryCatchWithInline.kt:36
stepOverTryCatchWithInline.kt:38 stepOverTryCatchWithInline.kt:38
stepOverTryCatchWithInline.kt:39 stepOverTryCatchWithInline.kt:39
stepOverTryCatchWithInline.kt:43 stepOverTryCatchWithInline.kt:43
stepOverTryCatchWithInline.kt:48
stepOverTryCatchWithInline.kt:43
stepOverTryCatchWithInline.kt:7
stepOverTryCatchWithInline.kt:8 stepOverTryCatchWithInline.kt:8
stepOverTryCatchWithInline.kt:10 stepOverTryCatchWithInline.kt:10
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
@@ -0,0 +1,16 @@
package stepOverInsideInlineFun
fun main(args: Array<String>) {
bar {
val b = 1
}
}
inline fun bar(f: (Int) -> Unit) {
//Breakpoint!
val a = 1
val f = f(1)
val c = 1
}
// STEP_OVER: 3
@@ -51,4 +51,4 @@ inline fun foo(f: () -> Int): Int {
fun test(i: Int) = 1 fun test(i: Int) = 1
// STEP_OVER: 19 // STEP_OVER: 20
@@ -415,6 +415,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepOverTest(fileName); doStepOverTest(fileName);
} }
@TestMetadata("stepOverInsideInlineFun.kt")
public void testStepOverInsideInlineFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInsideInlineFun.kt");
doStepOverTest(fileName);
}
@TestMetadata("stepOverReifiedParam.kt") @TestMetadata("stepOverReifiedParam.kt")
public void testStepOverReifiedParam() throws Exception { public void testStepOverReifiedParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverReifiedParam.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverReifiedParam.kt");