Fix irrelevant additional stops on breakpoint on line with inlines (KT-21945)

#KT-21945 Fixed
This commit is contained in:
Nikolay Krasko
2017-12-22 14:20:32 +03:00
parent f38a4f509d
commit f39250c9a2
15 changed files with 193 additions and 36 deletions
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2017 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
import com.intellij.debugger.SourcePosition
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
abstract class DelegateSourcePosition(private var delegate: SourcePosition) : SourcePosition() {
override fun getFile(): PsiFile = delegate.file
override fun getElementAt(): PsiElement = delegate.elementAt
override fun getLine(): Int = delegate.line
override fun getOffset(): Int = delegate.offset
override fun openEditor(requestFocus: Boolean): Editor = delegate.openEditor(requestFocus)
override fun canNavigate() = delegate.canNavigate()
override fun canNavigateToSource() = delegate.canNavigateToSource()
override fun navigate(requestFocus: Boolean) {
delegate.navigate(requestFocus)
}
override fun hashCode(): Int = delegate.hashCode()
override fun equals(other: Any?) = delegate == other
override fun toString() = "DSP($delegate)"
}
@@ -23,6 +23,7 @@ import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.PositionManagerEx
import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.psi.PsiFile
@@ -32,6 +33,7 @@ import com.intellij.util.ThreeState
import com.intellij.xdebugger.frame.XStackFrame
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.Location
import com.sun.jdi.Method
import com.sun.jdi.ReferenceType
import com.sun.jdi.request.ClassPrepareRequest
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
@@ -78,6 +80,11 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
if (location == null) throw NoDataException.INSTANCE
val fileName = location.safeSourceName ?: throw NoDataException.INSTANCE
val lineNumber = location.safeLineNumber
if (lineNumber < 0) {
throw NoDataException.INSTANCE
}
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
val psiFile = getPsiFileByLocation(location)
@@ -102,21 +109,18 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
throw NoDataException.INSTANCE
}
val sourceLineNumber = try {
location.lineNumber() - 1
}
catch (e: InternalError) {
-1
}
if (psiFile !is KtFile) throw NoDataException.INSTANCE
val sourceLineNumber = location.safeSourceLineNumber
if (sourceLineNumber < 0) {
throw NoDataException.INSTANCE
}
val lambdaOrFunIfInside = getLambdaOrFunIfInside(location, psiFile as KtFile, sourceLineNumber)
val lambdaOrFunIfInside = getLambdaOrFunIfInside(location, psiFile, sourceLineNumber)
if (lambdaOrFunIfInside != null) {
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
}
val elementInDeclaration = getElementForDeclarationLine(location, psiFile, sourceLineNumber)
if (elementInDeclaration != null) {
return SourcePosition.createFromElement(elementInDeclaration)
@@ -127,9 +131,23 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
return SourcePosition.createFromLine(ktFile ?: psiFile, line - 1)
}
val sameLineLocations = location.safeMethod?.safeAllLineLocations?.filter { it.safeLineNumber == lineNumber && it.safeSourceName == fileName }
if (sameLineLocations != null) {
// There're several locations for same source line. If same source position would be created for all of them,
// breakpoints at this line will stop on every location.
// Each location is probably some code in arguments between inlined invocations (otherwise same line locations would
// have been merged into one), but it's impossible to correctly map locations to actual source expressions now.
val locationIndex = sameLineLocations.indexOf(location)
if (locationIndex > 0) {
return KotlinReentrantSourcePosition(SourcePosition.createFromLine(psiFile, sourceLineNumber))
}
}
return SourcePosition.createFromLine(psiFile, sourceLineNumber)
}
class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate)
// Returns a property or a constructor if debugger stops at class declaration
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
@@ -199,6 +217,11 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
}
}
private val Location.safeLineNumber: Int get() = DebuggerUtilsEx.getLineNumber(this, false)
private val Location.safeSourceLineNumber: Int get() = DebuggerUtilsEx.getLineNumber(this, true)
private val Location.safeMethod: Method? get() = DebuggerUtilsEx.getMethod(this)
private val Method.safeAllLineLocations: MutableList<Location>? get() = DebuggerUtilsEx.allLineLocations(this)
private fun getPsiFileByLocation(location: Location): PsiFile? {
val sourceName = location.safeSourceName ?: return null
@@ -36,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
import org.jetbrains.kotlin.idea.KotlinIcons;
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
import org.jetbrains.kotlin.psi.KtClassInitializer;
import org.jetbrains.kotlin.psi.KtFunction;
@@ -51,7 +52,11 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType {
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
JavaBreakpointProperties properties = getProperties(breakpoint);
if (properties == null || properties instanceof JavaLineBreakpointProperties) {
if (properties != null && ((JavaLineBreakpointProperties)properties).getLambdaOrdinal() == null) return true;
if (position instanceof KotlinPositionManager.KotlinReentrantSourcePosition) {
return false;
}
if (properties != null && ((JavaLineBreakpointProperties) properties).getLambdaOrdinal() == null) return true;
PsiElement containingMethod = getContainingMethod(breakpoint);
if (containingMethod == null) return false;
@@ -29,9 +29,6 @@ fun main(args: Array<String>) {
//Breakpoint!
a.foo(1) { 1 }.foo(2) { 1 }
// EXPRESSION: it + 7
// RESULT: Unresolved reference: it
// EXPRESSION: it + 8
// RESULT: Unresolved reference: it
//Breakpoint! (lambdaOrdinal = -1)
@@ -53,9 +50,6 @@ fun main(args: Array<String>) {
// EXPRESSION: it + 12
// RESULT: 13: I
// EXPRESSION: it + 13
// RESULT: Unresolved reference: it
// EXPRESSION: it + 14
// RESULT: 16: I
//Breakpoint!
@@ -2,11 +2,11 @@ LineBreakpoint created at multipleBreakpointsAtLine.kt:9 lambdaOrdinal = -1
LineBreakpoint created at multipleBreakpointsAtLine.kt:14 lambdaOrdinal = 1
LineBreakpoint created at multipleBreakpointsAtLine.kt:19 lambdaOrdinal = 2
LineBreakpoint created at multipleBreakpointsAtLine.kt:30
LineBreakpoint created at multipleBreakpointsAtLine.kt:38 lambdaOrdinal = -1
LineBreakpoint created at multipleBreakpointsAtLine.kt:43 lambdaOrdinal = 1
LineBreakpoint created at multipleBreakpointsAtLine.kt:48 lambdaOrdinal = 2
LineBreakpoint created at multipleBreakpointsAtLine.kt:62
LineBreakpoint created at multipleBreakpointsAtLine.kt:67 lambdaOrdinal = 2
LineBreakpoint created at multipleBreakpointsAtLine.kt:35 lambdaOrdinal = -1
LineBreakpoint created at multipleBreakpointsAtLine.kt:40 lambdaOrdinal = 1
LineBreakpoint created at multipleBreakpointsAtLine.kt:45 lambdaOrdinal = 2
LineBreakpoint created at multipleBreakpointsAtLine.kt:56
LineBreakpoint created at multipleBreakpointsAtLine.kt:61 lambdaOrdinal = 2
Run Java
Connected to the target VM
multipleBreakpointsAtLine.kt:9
@@ -19,19 +19,17 @@ multipleBreakpointsAtLine.kt:30
Compile bytecode for it + 5
multipleBreakpointsAtLine.kt:30
Compile bytecode for it + 6
multipleBreakpointsAtLine.kt:38
multipleBreakpointsAtLine.kt:38
multipleBreakpointsAtLine.kt:43
multipleBreakpointsAtLine.kt:35
multipleBreakpointsAtLine.kt:40
Compile bytecode for it + 9
multipleBreakpointsAtLine.kt:48
multipleBreakpointsAtLine.kt:45
Compile bytecode for it + 10
multipleBreakpointsAtLine.kt:62
multipleBreakpointsAtLine.kt:62
multipleBreakpointsAtLine.kt:56
multipleBreakpointsAtLine.kt:56
Compile bytecode for it + 12
multipleBreakpointsAtLine.kt:62
multipleBreakpointsAtLine.kt:62
multipleBreakpointsAtLine.kt:56
Compile bytecode for it + 14
multipleBreakpointsAtLine.kt:67
multipleBreakpointsAtLine.kt:61
Compile bytecode for it + 15
Disconnected from the target VM
@@ -3,7 +3,6 @@ package inlineFunctionBreakpointVariants
import inlineFunctionOtherPackage.*
fun main(args: Array<String>) {
// Breakpoint is outside of function literal, but we stop at it twice: before and after foo1 invocation
//Breakpoint! (lambdaOrdinal = -1)
foo1 { foo2() }
}
@@ -1,8 +1,7 @@
LineBreakpoint created at inlineFunctionBreakpointVariants.kt:8 lambdaOrdinal = -1
LineBreakpoint created at inlineFunctionBreakpointVariants.kt:7 lambdaOrdinal = -1
Run Java
Connected to the target VM
inlineFunctionBreakpointVariants.kt:8
inlineFunctionBreakpointVariants.kt:8
inlineFunctionBreakpointVariants.kt:7
Disconnected from the target VM
Process finished with exit code 0
@@ -4,8 +4,6 @@ Connected to the target VM
parametersOfInlineFunSeveralOnLine.kt:8
parametersOfInlineFunSeveralOnLine.kt:8
parametersOfInlineFunSeveralOnLine.kt:8
parametersOfInlineFunSeveralOnLine.kt:8
Compile bytecode for it
Disconnected from the target VM
Process finished with exit code 0
@@ -24,7 +24,6 @@ Run Java
Connected to the target VM
crossinlineLiteral.kt:75
crossinlineLiteral.kt:13
crossinlineLiteral.kt:75
crossinlineLiteral.kt:18
crossinlineLiteral.kt:81
crossinlineLiteral.kt:23
@@ -0,0 +1,12 @@
package soBreakpointWithInline
fun foo() {}
fun main(args: Array<String>) {
//Breakpoint!
12.inlineExt().normalExt() // Try to step over this line. Debugger will stop on the same line twice
foo()
}
inline fun Any.inlineExt(): Any = this
fun Any.normalExt(): Any = this
@@ -0,0 +1,8 @@
LineBreakpoint created at soBreakpointWithInline.kt:7
Run Java
Connected to the target VM
soBreakpointWithInline.kt:7
soBreakpointWithInline.kt:8
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,57 @@
package soBreakpointWithOrdinalOnInlineCallsInOneLine
fun test(i: Int): Boolean {
return false
}
fun foo() {}
fun main(args: Array<String>) {
val listOf = listOf(1)
//Breakpoint! (lambdaOrdinal = -1)
val testSome = listOf.filterNot({test(it)}).get(0)
foo()
}
// STEP_OVER: 4
@@ -0,0 +1,9 @@
LineBreakpoint created at soBreakpointWithOrdinalOnInlineCallsInOneLine.kt:12 lambdaOrdinal = -1
Run Java
Connected to the target VM
soBreakpointWithOrdinalOnInlineCallsInOneLine.kt:12
soBreakpointWithOrdinalOnInlineCallsInOneLine.kt:13
soBreakpointWithOrdinalOnInlineCallsInOneLine.kt:14
Disconnected from the target VM
Process finished with exit code 0
@@ -524,6 +524,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepOverTest(fileName);
}
@TestMetadata("soBreakpointWithInline.kt")
public void testSoBreakpointWithInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.kt");
doStepOverTest(fileName);
}
@TestMetadata("soBreakpointWithOrdinalOnInlineCallsInOneLine.kt")
public void testSoBreakpointWithOrdinalOnInlineCallsInOneLine() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.kt");
doStepOverTest(fileName);
}
@TestMetadata("soInlineAnonymousFunctionArgument.kt")
public void testSoInlineAnonymousFunctionArgument() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/soInlineAnonymousFunctionArgument.kt");
@@ -16,10 +16,12 @@
package org.jetbrains.kotlin.idea.debugger
import com.sun.jdi.Location
import com.sun.jdi.Method
class MockMethod : Method {
override fun name() = ""
override fun allLineLocations() = emptyList<Location>()
override fun isSynthetic() = throw UnsupportedOperationException()
override fun isFinal() = throw UnsupportedOperationException()
@@ -33,7 +35,6 @@ class MockMethod : Method {
override fun isBridge() = throw UnsupportedOperationException()
override fun isObsolete() = throw UnsupportedOperationException()
override fun isSynchronized() = throw UnsupportedOperationException()
override fun allLineLocations() = throw UnsupportedOperationException()
override fun allLineLocations(stratum: String?, sourceName: String?) = throw UnsupportedOperationException()
override fun isNative() = throw UnsupportedOperationException()
override fun locationOfCodeIndex(codeIndex: Long) = throw UnsupportedOperationException()