diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/DelegateSourcePosition.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/DelegateSourcePosition.kt new file mode 100644 index 00000000000..11f5bfbecfb --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/DelegateSourcePosition.kt @@ -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)" +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 02eb5b14d19..068d83a5fed 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -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? get() = DebuggerUtilsEx.allLineLocations(this) + private fun getPsiFileByLocation(location: Location): PsiFile? { val sourceName = location.safeSourceName ?: return null diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java index 15ef3ca559a..90866b1f84f 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java @@ -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; diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt index 3211ba915d4..913b60459e7 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt @@ -29,9 +29,6 @@ fun main(args: Array) { //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) { // EXPRESSION: it + 12 // RESULT: 13: I - // EXPRESSION: it + 13 - // RESULT: Unresolved reference: it - // EXPRESSION: it + 14 // RESULT: 16: I //Breakpoint! diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.out index af30c156599..8844df5a545 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.kt index 9db5b461e58..492d8ce60cc 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.kt @@ -3,7 +3,6 @@ package inlineFunctionBreakpointVariants import inlineFunctionOtherPackage.* fun main(args: Array) { - // Breakpoint is outside of function literal, but we stop at it twice: before and after foo1 invocation //Breakpoint! (lambdaOrdinal = -1) foo1 { foo2() } } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.out index ec4311bc478..b48135ecafd 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/inlineFunctionBreakpointVariants.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/parametersOfInlineFunSeveralOnLine.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/parametersOfInlineFunSeveralOnLine.out index c6a028e8785..f77003bb5db 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/parametersOfInlineFunSeveralOnLine.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/parametersOfInlineFunSeveralOnLine.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.out b/idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.out index 74e96ace397..a1a27c54e54 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.out +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.kt new file mode 100644 index 00000000000..17011376cb0 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.kt @@ -0,0 +1,12 @@ +package soBreakpointWithInline + +fun foo() {} + +fun main(args: Array) { + //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 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.out b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.out new file mode 100644 index 00000000000..ff465b8e34e --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithInline.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.kt new file mode 100644 index 00000000000..c4d00e0a339 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.kt @@ -0,0 +1,57 @@ +package soBreakpointWithOrdinalOnInlineCallsInOneLine + +fun test(i: Int): Boolean { + return false +} + +fun foo() {} + +fun main(args: Array) { + val listOf = listOf(1) + //Breakpoint! (lambdaOrdinal = -1) + val testSome = listOf.filterNot({test(it)}).get(0) + foo() +} + + +// STEP_OVER: 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.out b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.out new file mode 100644 index 00000000000..e16aaca3af5 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/soBreakpointWithOrdinalOnInlineCallsInOneLine.out @@ -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 diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index e4cf94af2ce..5650a5e270f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -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"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.kt index 71ed2fcdfda..1240600f26d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/MockMethod.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() 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()