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
#### Debugger
- Do not step into inline lambda argument during step over inside inline function body
## 1.0.2
### Compiler
@@ -20,12 +20,8 @@ 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.*;
import kotlin.ranges.*;
import kotlin.ranges.IntRange;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtFunction;
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
@@ -47,36 +43,29 @@ public class DebuggerSteppingHelper {
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(),
file,
linesRange,
inlineArguments,
additionalElementsToSkip
);
if (position != null) {
return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints);
}
}
catch (EvaluateException ignored) {
}
return null;
try {
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
if (frameProxy != null) {
Action action = KotlinSteppingCommandProviderKt.getStepOverPosition(
frameProxy.location(),
file,
linesRange,
inlineArguments,
additionalElementsToSkip
);
DebugProcessImpl.ResumeCommand command = action.createCommand(debugProcess, suspendContext, ignoreBreakpoints);
if (command != null) {
command.contextAction();
return;
}
});
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) {
@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(),
suspendContext,
inlineFunctions,
inlineArgument
);
if (position != null) {
return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints);
}
}
catch (EvaluateException ignored) {
}
return null;
try {
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
if (frameProxy != null) {
Action action = KotlinSteppingCommandProviderKt.getStepOutPosition(
frameProxy.location(),
suspendContext,
inlineFunctions,
inlineArgument
);
DebugProcessImpl.ResumeCommand command = action.createCommand(debugProcess, suspendContext, ignoreBreakpoints);
if (command != null) {
command.contextAction();
return;
}
});
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.getLineStartOffset
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.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -78,22 +79,31 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
val linesRange = startLineNumber + 1..endLineNumber + 1
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
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 inlineArgumentsToSkip = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null
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> {
@@ -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(
location: Location,
file: KtFile,
range: IntRange,
inlinedArguments: List<KtElement>,
elementsToSkip: List<PsiElement>
): XSourcePositionImpl? {
val computedReferenceType = location.declaringType() ?: return null
): Action {
val computedReferenceType = location.declaringType() ?: return Action.STEP_OVER()
fun isLocationSuitable(nextLocation: Location): Boolean {
if (nextLocation.method() != location.method() || nextLocation.lineNumber() !in range) {
@@ -316,17 +348,28 @@ fun getStepOverPosition(
.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 xPosition = runReadAction l@ {
val lineNumber = locationAtLine.lineNumber()
val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: return@l null
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(
@@ -334,8 +377,8 @@ fun getStepOutPosition(
suspendContext: SuspendContextImpl,
inlineFunctions: List<KtNamedFunction>,
inlinedArgument: KtFunctionLiteral?
): XSourcePositionImpl? {
val computedReferenceType = location.declaringType() ?: return null
): Action {
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
val locations = computedReferenceType.allLineLocations()
val nextLineLocations = locations
@@ -345,14 +388,16 @@ fun getStepOutPosition(
.dropWhile { it.lineNumber() == location.lineNumber() }
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) {
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(
@@ -384,20 +429,25 @@ private fun SuspendContextImpl.getNextPositionWithFilter(
skip: (Int, PsiElement) -> Boolean
): XSourcePositionImpl? {
for (location in locations) {
val sourcePosition = try {
this.debugProcess.positionManager.getSourcePosition(location)
val position = runReadAction l@ {
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
@@ -17,7 +17,6 @@ stepOverIfWithInline.kt:24
stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:46
stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:28
stepOverIfWithInline.kt:32
stepOverIfWithInline.kt:46
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:39
stepOverTryCatchWithInline.kt:43
stepOverTryCatchWithInline.kt:48
stepOverTryCatchWithInline.kt:43
stepOverTryCatchWithInline.kt:7
stepOverTryCatchWithInline.kt:8
stepOverTryCatchWithInline.kt:10
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
// STEP_OVER: 19
// STEP_OVER: 20
@@ -415,6 +415,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
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")
public void testStepOverReifiedParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverReifiedParam.kt");