@@ -29,6 +29,7 @@ import com.intellij.xdebugger.impl.XSourcePositionImpl
29
29
import com.sun.jdi.AbsentInformationException
30
30
import com.sun.jdi.LocalVariable
31
31
import com.sun.jdi.Location
32
+ import com.sun.jdi.Method
32
33
import org.jetbrains.annotations.TestOnly
33
34
import org.jetbrains.kotlin.builtins.isFunctionType
34
35
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
@@ -41,6 +42,12 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
41
42
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
42
43
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
43
44
import org.jetbrains.kotlin.idea.debugger.noStrataLineNumber
45
+ import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.GOTO
46
+ import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.MOVE
47
+ import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN
48
+ import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_VOID
49
+ import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_OBJECT
50
+ import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_WIDE
44
51
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
45
52
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
46
53
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
@@ -326,7 +333,7 @@ fun getStepOverAction(
326
333
return false
327
334
}
328
335
329
- if (nextLocation.lineNumber () !in range) {
336
+ if (nextLocation.ktLineNumber () !in range) {
330
337
return false
331
338
}
332
339
@@ -396,9 +403,14 @@ fun getStepOverAction(
396
403
// terrible. On each iteration position jumps to the method end or some previous inline call and then returns back. To prevent
397
404
// this filter locations with too big code indexes manually
398
405
val returnCodeIndex: Long = if (isDexDebug) {
399
- // TODO: won't work for situation when breakpoint is in inlined method
400
- val locationsOfLine = location.method().locationsOfLine(range.last)
401
- locationsOfLine.map { it.codeIndex() }.max() ? : - 1L
406
+ val method = location.method()
407
+ val locationsOfLine = method.locationsOfLine(range.last)
408
+ if (locationsOfLine.isNotEmpty()) {
409
+ locationsOfLine.map { it.codeIndex() }.max() ? : - 1L
410
+ }
411
+ else {
412
+ findReturnFromDexBytecode(location.method())
413
+ }
402
414
}
403
415
else - 1L
404
416
@@ -512,3 +524,56 @@ private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
512
524
513
525
return functionLiteralExpression.functionLiteral
514
526
}
527
+
528
+ private fun findReturnFromDexBytecode (method : Method ): Long {
529
+ val methodLocations = method.allLineLocations()
530
+ if (methodLocations.isEmpty()) return - 1L
531
+
532
+ var lastMethodCodeIndex = methodLocations.last().codeIndex()
533
+ // Continue while it's possible to get location
534
+ while (true ) {
535
+ if (method.locationOfCodeIndex(lastMethodCodeIndex + 1 ) != null ) {
536
+ lastMethodCodeIndex++
537
+ }
538
+ else {
539
+ break
540
+ }
541
+ }
542
+
543
+ var returnIndex = lastMethodCodeIndex + 1
544
+
545
+ val bytecode = method.bytecodes()
546
+ var i = bytecode.size
547
+
548
+ while (i >= 2 ) {
549
+ // Can step only through two-byte instructions and abort on any unknown one
550
+ i - = 2
551
+ returnIndex - = 1
552
+
553
+ val instruction = bytecode[i].toInt()
554
+
555
+ if (instruction == RETURN_VOID || instruction == RETURN || instruction == RETURN_WIDE || instruction == RETURN_OBJECT ) {
556
+ // Instruction found
557
+ return returnIndex
558
+ }
559
+ else if (instruction == MOVE || instruction == GOTO ) {
560
+ // proceed
561
+ }
562
+ else {
563
+ // Don't know the instruction and it's length. Abort.
564
+ break
565
+ }
566
+ }
567
+
568
+ return - 1L
569
+ }
570
+
571
+ object DexBytecode {
572
+ val RETURN_VOID = 0x0e
573
+ val RETURN = 0x0f
574
+ val RETURN_WIDE = 0x10
575
+ val RETURN_OBJECT = 0x11
576
+
577
+ val GOTO = 0x28
578
+ val MOVE = 0x01
579
+ }
0 commit comments