Skip to content

Commit f5c18c5

Browse files
Debugger: fix step over inside inline function (do not step into function literal argument)
1 parent 069eea8 commit f5c18c5

File tree

10 files changed

+201
-90
lines changed

10 files changed

+201
-90
lines changed

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- Fix text with line breaks in popup with line breakpoint variants
2020
- Fix breakpoints inside inline functions in libraries sources
2121
- Allow breakpoints at catch clause declaration
22+
- Do not step into inline function literal argument during step over inside inline function body
2223

2324
## 1.0.2
2425

idea/src/org/jetbrains/kotlin/idea/debugger/stepping/DebuggerSteppingHelper.java

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,30 @@
1919
import com.intellij.debugger.engine.DebugProcessImpl;
2020
import com.intellij.debugger.engine.SuspendContextImpl;
2121
import com.intellij.debugger.engine.evaluation.EvaluateException;
22+
import com.intellij.debugger.impl.DebuggerUtilsEx;
2223
import com.intellij.debugger.jdi.StackFrameProxyImpl;
23-
import com.intellij.openapi.application.ApplicationManager;
24-
import com.intellij.openapi.util.Computable;
24+
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
25+
import com.intellij.debugger.settings.DebuggerSettings;
26+
import com.intellij.openapi.extensions.Extensions;
2527
import com.intellij.psi.PsiElement;
26-
import com.intellij.xdebugger.impl.XSourcePositionImpl;
27-
import kotlin.*;
28-
import kotlin.ranges.*;
28+
import com.intellij.ui.classFilter.ClassFilter;
29+
import com.intellij.ui.classFilter.DebuggerClassFilterProvider;
30+
import com.sun.jdi.Location;
31+
import com.sun.jdi.ObjectCollectedException;
32+
import com.sun.jdi.ReferenceType;
33+
import com.sun.jdi.ThreadReference;
34+
import com.sun.jdi.request.EventRequest;
35+
import com.sun.jdi.request.EventRequestManager;
36+
import com.sun.jdi.request.StepRequest;
37+
import kotlin.ranges.IntRange;
38+
import org.jetbrains.annotations.NotNull;
39+
import org.jetbrains.annotations.Nullable;
2940
import org.jetbrains.kotlin.psi.KtFile;
3041
import org.jetbrains.kotlin.psi.KtFunction;
3142
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
3243
import org.jetbrains.kotlin.psi.KtNamedFunction;
3344

45+
import java.util.ArrayList;
3446
import java.util.List;
3547

3648
public class DebuggerSteppingHelper {
@@ -47,36 +59,31 @@ public static DebugProcessImpl.ResumeCommand createStepOverCommand(
4759
return debugProcess.new ResumeCommand(suspendContext) {
4860
@Override
4961
public void contextAction() {
50-
final StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
51-
if (frameProxy != null) {
52-
DebugProcessImpl.ResumeCommand runToCursorCommand = ApplicationManager.getApplication().runReadAction(new Computable<DebugProcessImpl.ResumeCommand>() {
53-
@Override
54-
public DebugProcessImpl.ResumeCommand compute() {
55-
try {
56-
XSourcePositionImpl position = KotlinSteppingCommandProviderKt.getStepOverPosition(
57-
frameProxy.location(),
58-
file,
59-
linesRange,
60-
inlineArguments,
61-
additionalElementsToSkip
62-
);
63-
if (position != null) {
64-
return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints);
65-
}
66-
}
67-
catch (EvaluateException ignored) {
68-
}
69-
return null;
70-
}
71-
});
62+
try {
63+
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
64+
if (frameProxy != null) {
65+
Action action = KotlinSteppingCommandProviderKt.getStepOverPosition(
66+
debugProcess,
67+
frameProxy.location(),
68+
file,
69+
linesRange,
70+
inlineArguments,
71+
additionalElementsToSkip
72+
);
73+
74+
DebugProcessImpl.ResumeCommand command =
75+
KotlinSteppingCommandProviderKt.createCommand(debugProcess, suspendContext, ignoreBreakpoints, action);
7276

73-
if (runToCursorCommand != null) {
74-
runToCursorCommand.contextAction();
75-
return;
77+
if (command != null) {
78+
command.contextAction();
79+
return;
80+
}
7681
}
77-
}
7882

79-
debugProcess.createStepOutCommand(suspendContext).contextAction();
83+
debugProcess.createStepOutCommand(suspendContext).contextAction();
84+
}
85+
catch (EvaluateException ignored) {
86+
}
8087
}
8188
};
8289
}
@@ -91,35 +98,30 @@ public static DebugProcessImpl.ResumeCommand createStepOutCommand(
9198
return debugProcess.new ResumeCommand(suspendContext) {
9299
@Override
93100
public void contextAction() {
94-
final StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
95-
if (frameProxy != null) {
96-
DebugProcessImpl.ResumeCommand runToCursorCommand = ApplicationManager.getApplication().runReadAction(new Computable<DebugProcessImpl.ResumeCommand>() {
97-
@Override
98-
public DebugProcessImpl.ResumeCommand compute() {
99-
try {
100-
XSourcePositionImpl position = KotlinSteppingCommandProviderKt.getStepOutPosition(
101-
frameProxy.location(),
102-
suspendContext,
103-
inlineFunctions,
104-
inlineArgument
105-
);
106-
if (position != null) {
107-
return debugProcess.createRunToCursorCommand(suspendContext, position, ignoreBreakpoints);
108-
}
109-
}
110-
catch (EvaluateException ignored) {
111-
}
112-
return null;
113-
}
114-
});
101+
try {
102+
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
103+
if (frameProxy != null) {
104+
Action action = KotlinSteppingCommandProviderKt.getStepOutPosition(
105+
frameProxy.location(),
106+
suspendContext,
107+
inlineFunctions,
108+
inlineArgument
109+
);
110+
111+
DebugProcessImpl.ResumeCommand command =
112+
KotlinSteppingCommandProviderKt.createCommand(debugProcess, suspendContext, ignoreBreakpoints, action);
115113

116-
if (runToCursorCommand != null) {
117-
runToCursorCommand.contextAction();
118-
return;
114+
if (command != null) {
115+
command.contextAction();
116+
return;
117+
}
119118
}
119+
120+
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
120121
}
122+
catch (EvaluateException ignored) {
121123

122-
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
124+
}
123125
}
124126
};
125127
}

idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt

Lines changed: 98 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ import com.intellij.debugger.NoDataException
2020
import com.intellij.debugger.SourcePosition
2121
import com.intellij.debugger.engine.DebugProcessImpl
2222
import com.intellij.debugger.engine.SuspendContextImpl
23+
import com.intellij.debugger.engine.evaluation.EvaluateException
2324
import com.intellij.debugger.impl.DebuggerContextImpl
2425
import com.intellij.debugger.impl.JvmSteppingCommandProvider
26+
import com.intellij.debugger.ui.breakpoints.FilteredRequestorImpl
27+
import com.intellij.openapi.application.ApplicationManager
28+
import com.intellij.openapi.util.Computable
2529
import com.intellij.psi.PsiElement
2630
import com.intellij.xdebugger.impl.XSourcePositionImpl
2731
import com.sun.jdi.AbsentInformationException
@@ -36,6 +40,7 @@ import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
3640
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
3741
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
3842
import org.jetbrains.kotlin.idea.util.DebuggerUtils
43+
import org.jetbrains.kotlin.idea.util.application.runReadAction
3944
import org.jetbrains.kotlin.psi.*
4045
import org.jetbrains.kotlin.psi.psiUtil.endOffset
4146
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -78,7 +83,9 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
7883

7984
val linesRange = startLineNumber + 1..endLineNumber + 1
8085

81-
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
86+
val inlineArguments = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null
87+
88+
/*val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
8289
if (inlineFunctionCalls.isEmpty()) return null
8390
8491
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
@@ -89,13 +96,33 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
8996
9097
if (inlineArguments.isEmpty() && inlineFunctionCalls.any { it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
9198
return null
92-
}
99+
}*/
93100

94101
val additionalElementsToSkip = sourcePosition.elementAt.getAdditionalElementsToSkip()
95102

96103
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArguments, additionalElementsToSkip)
97104
}
98105

106+
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
107+
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
108+
if (!inlineFunctionCalls.isEmpty()) {
109+
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
110+
if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
111+
return inlineArguments
112+
}
113+
114+
if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
115+
return emptyList()
116+
}
117+
}
118+
119+
if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) {
120+
return emptyList()
121+
}
122+
123+
return null
124+
}
125+
99126
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
100127
val result = arrayListOf<PsiElement>()
101128
val ifParent = getParentOfType<KtIfExpression>(false)
@@ -287,14 +314,20 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
287314
}
288315
}
289316

317+
sealed class Action(val position: XSourcePositionImpl?) {
318+
class STEP_OVER: Action(null)
319+
class STEP_OUT: Action(null)
320+
class RUN_TO_CURSOR(position: XSourcePositionImpl): Action(position)
321+
}
322+
290323
fun getStepOverPosition(
324+
debugProcess: DebugProcessImpl,
291325
location: Location,
292326
file: KtFile,
293327
range: IntRange,
294-
inlinedArguments: List<KtElement>,
295-
elementsToSkip: List<PsiElement>
296-
): XSourcePositionImpl? {
297-
val computedReferenceType = location.declaringType() ?: return null
328+
inlinedArguments: MutableList<out KtElement>, elementsToSkip: MutableList<out PsiElement>
329+
): Action {
330+
val computedReferenceType = location.declaringType() ?: return Action.STEP_OVER()
298331

299332
fun isLocationSuitable(nextLocation: Location): Boolean {
300333
if (nextLocation.method() != location.method() || nextLocation.lineNumber() !in range) {
@@ -316,26 +349,54 @@ fun getStepOverPosition(
316349
.dropWhile { it.lineNumber() == location.lineNumber() }
317350

318351
for (locationAtLine in locations) {
319-
val lineNumber = locationAtLine.lineNumber()
320-
val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue
321-
if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue
322-
if (elementsToSkip.any { it.textRange.contains(lineStartOffset) }) continue
352+
val xPosition = runReadAction {
353+
val lineNumber = locationAtLine.lineNumber()
354+
val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: return@runReadAction null
355+
if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) return@runReadAction null
356+
if (elementsToSkip.any { it.textRange.contains(lineStartOffset) }) return@runReadAction null
357+
358+
if (locationAtLine.lineNumber() == location.lineNumber()) return@runReadAction null
323359

324-
val elementAt = file.findElementAt(lineStartOffset) ?: continue
360+
val elementAt = file.findElementAt(lineStartOffset) ?: return@runReadAction null
361+
XSourcePositionImpl.createByElement(elementAt)
362+
}
325363

326-
return XSourcePositionImpl.createByElement(elementAt) ?: return null
364+
if (xPosition != null) {
365+
return Action.RUN_TO_CURSOR(xPosition)
366+
}
327367
}
328368

329-
return null
369+
if (locations.isNotEmpty()) {
370+
return Action.STEP_OUT()
371+
}
372+
373+
return Action.STEP_OVER()
374+
}
375+
376+
fun createCommand(
377+
debugProcess: DebugProcessImpl,
378+
suspendContext: SuspendContextImpl,
379+
ignoreBreakpoints: Boolean,
380+
action: Action
381+
): DebugProcessImpl.ResumeCommand? {
382+
return when (action) {
383+
is Action.RUN_TO_CURSOR -> {
384+
runReadAction {
385+
debugProcess.createRunToCursorCommand(suspendContext, action.position!!, ignoreBreakpoints)
386+
}
387+
}
388+
is Action.STEP_OUT -> debugProcess.createStepOutCommand(suspendContext)
389+
is Action.STEP_OVER -> debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints)
390+
}
330391
}
331392

332393
fun getStepOutPosition(
333394
location: Location,
334395
suspendContext: SuspendContextImpl,
335396
inlineFunctions: List<KtNamedFunction>,
336397
inlinedArgument: KtFunctionLiteral?
337-
): XSourcePositionImpl? {
338-
val computedReferenceType = location.declaringType() ?: return null
398+
): Action {
399+
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
339400

340401
val locations = computedReferenceType.allLineLocations()
341402
val nextLineLocations = locations
@@ -345,14 +406,16 @@ fun getStepOutPosition(
345406
.dropWhile { it.lineNumber() == location.lineNumber() }
346407

347408
if (inlineFunctions.isNotEmpty()) {
348-
return suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions) ?: return null
409+
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
410+
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
349411
}
350412

351413
if (inlinedArgument != null) {
352-
return suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) ?: return null
414+
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
415+
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
353416
}
354417

355-
return null
418+
return Action.STEP_OVER()
356419
}
357420

358421
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
@@ -384,20 +447,25 @@ private fun SuspendContextImpl.getNextPositionWithFilter(
384447
skip: (Int, PsiElement) -> Boolean
385448
): XSourcePositionImpl? {
386449
for (location in locations) {
387-
val sourcePosition = try {
388-
this.debugProcess.positionManager.getSourcePosition(location)
389-
}
390-
catch(e: NoDataException) {
391-
null
392-
} ?: continue
450+
val position = runReadAction {
451+
val sourcePosition = try {
452+
this.debugProcess.positionManager.getSourcePosition(location)
453+
}
454+
catch(e: NoDataException) {
455+
null
456+
} ?: return@runReadAction null
393457

394-
val file = sourcePosition.file as? KtFile ?: continue
395-
val elementAt = sourcePosition.elementAt ?: continue
396-
val currentLine = location.lineNumber() - 1
397-
val lineStartOffset = file.getLineStartOffset(currentLine) ?: continue
398-
if (skip(lineStartOffset, elementAt)) continue
458+
val file = sourcePosition.file as? KtFile ?: return@runReadAction null
459+
val elementAt = sourcePosition.elementAt ?: return@runReadAction null
460+
val currentLine = location.lineNumber() - 1
461+
val lineStartOffset = file.getLineStartOffset(currentLine) ?: return@runReadAction null
462+
if (skip(lineStartOffset, elementAt)) return@runReadAction null
399463

400-
return XSourcePositionImpl.createByElement(elementAt)
464+
XSourcePositionImpl.createByElement(elementAt)
465+
}
466+
if (position != null) {
467+
return position
468+
}
401469
}
402470

403471
return null

idea/testData/debugger/tinyApp/outs/stepOverIfWithInline.out

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ stepOverIfWithInline.kt:24
1717
stepOverIfWithInline.kt:28
1818
stepOverIfWithInline.kt:46
1919
stepOverIfWithInline.kt:28
20-
stepOverIfWithInline.kt:28
2120
stepOverIfWithInline.kt:32
2221
stepOverIfWithInline.kt:46
2322
stepOverIfWithInline.kt:32
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
LineBreakpoint created at stepOverInsideInlineFun.kt:11
2+
!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
3+
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
4+
stepOverInsideInlineFun.kt:11
5+
stepOverInsideInlineFun.kt:12
6+
stepOverInsideInlineFun.kt:13
7+
stepOverInsideInlineFun.kt:7
8+
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
9+
10+
Process finished with exit code 0

0 commit comments

Comments
 (0)