diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index 665ff03eab9..ee93373dd5f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -17,53 +17,26 @@ package org.jetbrains.kotlin.idea.debugger import com.intellij.debugger.engine.DebugProcessImpl -import com.intellij.psi.PsiElement import com.sun.jdi.* import com.sun.tools.jdi.LocalVariableImpl +import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.KtFunction -import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtTreeVisitorVoid -import org.jetbrains.kotlin.psi.psiUtil.parents -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.util.* fun isInsideInlineArgument(inlineArgument: KtFunction, location: Location, debugProcess: DebugProcessImpl): Boolean { - return isInsideInlineArgument(inlineArgument, location.visibleVariables(debugProcess)) -} - -fun isInsideInlineArgument(inlineArgument: KtFunction, visibleVariables: List): Boolean { + val visibleVariables = location.visibleVariables(debugProcess) val lambdaOrdinalIndex = runReadAction { lambdaOrdinalIndex(inlineArgument) } val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) } - for (variable in markerLocalVariables) { - val lambdaOrdinal = lambdaOrdinal(variable.name()) - if (lambdaOrdinalIndex[inlineArgument] == lambdaOrdinal) { - return true - } - } - return false + return markerLocalVariables.firstOrNull { lambdaOrdinal(it.name()) == lambdaOrdinalIndex } != null } -private fun lambdaOrdinalIndex(elementAt: PsiElement): HashMap { - val parent = elementAt.parents.firstIsInstanceOrNull() +private fun lambdaOrdinalIndex(elementAt: KtFunction): Int { + val typeMapper = JetPositionManager.createTypeMapper(elementAt.getContainingJetFile()) - val actualLambdaOrdinals = hashMapOf() - parent?.accept(object : KtTreeVisitorVoid() { - override fun visitNamedFunction(function: KtNamedFunction) { - if (function != parent) { - actualLambdaOrdinals[function] = actualLambdaOrdinals.size + 1 - } - super.visitNamedFunction(function) - } - - override fun visitFunctionLiteralExpression(expression: KtFunctionLiteralExpression) { - actualLambdaOrdinals[expression.functionLiteral] = actualLambdaOrdinals.size + 1 - super.visitFunctionLiteralExpression(expression) - } - }) - return actualLambdaOrdinals + val type = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, elementAt) + return type.className.substringAfterLast("$").toInt() } private fun Location.visibleVariables(debugProcess: DebugProcessImpl): List { diff --git a/idea/testData/debugger/customLibraryForTinyApp/isInsideInlineLambdaInLibrary.kt b/idea/testData/debugger/customLibraryForTinyApp/isInsideInlineLambdaInLibrary.kt new file mode 100644 index 00000000000..da24655ed05 --- /dev/null +++ b/idea/testData/debugger/customLibraryForTinyApp/isInsideInlineLambdaInLibrary.kt @@ -0,0 +1,41 @@ +package isInsideInlineLambdaInLibrary + +public fun test() { + val a = A() + //Breakpoint1 + a.foo(1) { 1 } + + // inside other lambda + a.foo(100) { + //Breakpoint2 + a.foo(2) { 1 } + 1 + } + + // inside variable declaration + //Breakpoint3 + val x = a.foo(3) { 1 } + + // inside object declaration + val y = object { + fun foo() { + //Breakpoint4 + a.foo(4) { 1 } + } + } + y.foo() + + // inside local function + fun local() { + //Breakpoint5 + a.foo(5) { 1 } + } + local() +} + +class A { + inline fun foo(i: Int, f: (i: Int) -> Int): A { + f(i) + return this + } +} diff --git a/idea/testData/debugger/tinyApp/outs/isInsideInlineLambda.out b/idea/testData/debugger/tinyApp/outs/isInsideInlineLambda.out new file mode 100644 index 00000000000..f51666eac2d --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/isInsideInlineLambda.out @@ -0,0 +1,35 @@ +LineBreakpoint created at isInsideInlineLambdaInLibrary.kt:6 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambdaInLibrary.kt:11 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambdaInLibrary.kt:17 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambdaInLibrary.kt:23 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambdaInLibrary.kt:31 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambda.kt:9 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambda.kt:17 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambda.kt:25 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambda.kt:33 lambdaOrdinal = 0 +LineBreakpoint created at isInsideInlineLambda.kt:43 lambdaOrdinal = 0 +!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! isInsideInlineLambda.IsInsideInlineLambdaKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +isInsideInlineLambda.kt:9 +Compile bytecode for it + 1 +isInsideInlineLambda.kt:17 +Compile bytecode for it + 2 +isInsideInlineLambda.kt:25 +Compile bytecode for it + 3 +isInsideInlineLambda.kt:33 +Compile bytecode for it + 4 +isInsideInlineLambda.kt:43 +Compile bytecode for it + 5 +isInsideInlineLambdaInLibrary.kt:6 +Compile bytecode for it + 11 +isInsideInlineLambdaInLibrary.kt:11 +Compile bytecode for it + 12 +isInsideInlineLambdaInLibrary.kt:17 +Compile bytecode for it + 13 +isInsideInlineLambdaInLibrary.kt:23 +Compile bytecode for it + 14 +isInsideInlineLambdaInLibrary.kt:31 +Compile bytecode for it + 15 +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/stepOverIfWithInline.out b/idea/testData/debugger/tinyApp/outs/stepOverIfWithInline.out index 71d3c2dd9ca..117969fe96f 100644 --- a/idea/testData/debugger/tinyApp/outs/stepOverIfWithInline.out +++ b/idea/testData/debugger/tinyApp/outs/stepOverIfWithInline.out @@ -17,6 +17,7 @@ stepOverIfWithInline.kt:24 stepOverIfWithInline.kt:28 stepOverIfWithInline.kt:41 stepOverIfWithInline.kt:28 +stepOverIfWithInline.kt:28 stepOverIfWithInline.kt:32 stepOverIfWithInline.kt:41 stepOverIfWithInline.kt:32 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/isInsideInlineLambda.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/isInsideInlineLambda.kt new file mode 100644 index 00000000000..f415ad5c0b4 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/isInsideInlineLambda.kt @@ -0,0 +1,75 @@ +package isInsideInlineLambda + +fun main(args: Array) { + val a = A() + + // EXPRESSION: it + 1 + // RESULT: 2: I + //Breakpoint! (0) + a.foo(1) { 1 } + + + // inside other lambda + a.foo(1) { + // EXPRESSION: it + 2 + // RESULT: 3: I + //Breakpoint! (0) + a.foo(1) { 1 } + 1 + } + + // inside variable declaration + // EXPRESSION: it + 3 + // RESULT: 4: I + //Breakpoint! (0) + val x = a.foo(1) { 1 } + + // inside object declaration + val y = object { + fun foo() { + // EXPRESSION: it + 4 + // RESULT: 5: I + //Breakpoint! (0) + a.foo(1) { 1 } + } + } + y.foo() + + // inside local function + fun local() { + // EXPRESSION: it + 5 + // RESULT: 6: I + //Breakpoint! (0) + a.foo(1) { 1 } + } + local() + + isInsideInlineLambdaInLibrary.test() +} + +class A { + inline fun foo(i: Int, f: (i: Int) -> Int): A { + f(i) + return this + } +} + +// ADDITIONAL_BREAKPOINT: isInsideInlineLambdaInLibrary.kt:Breakpoint1:(0) +// EXPRESSION: it + 11 +// RESULT: 12: I + +// ADDITIONAL_BREAKPOINT: isInsideInlineLambdaInLibrary.kt:Breakpoint2:(0) +// EXPRESSION: it + 12 +// RESULT: 14: I + +// ADDITIONAL_BREAKPOINT: isInsideInlineLambdaInLibrary.kt:Breakpoint3:(0) +// EXPRESSION: it + 13 +// RESULT: 16: I + +// ADDITIONAL_BREAKPOINT: isInsideInlineLambdaInLibrary.kt:Breakpoint4:(0) +// EXPRESSION: it + 14 +// RESULT: 18: I + +// ADDITIONAL_BREAKPOINT: isInsideInlineLambdaInLibrary.kt:Breakpoint5:(0) +// EXPRESSION: it + 15 +// RESULT: 20: I diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverIfWithInline.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverIfWithInline.kt index efc2e82869d..c369931606d 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverIfWithInline.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverIfWithInline.kt @@ -44,4 +44,4 @@ inline fun foo(f: () -> Int): Int { fun test(i: Int) = 1 -// STEP_OVER: 21 \ No newline at end of file +// STEP_OVER: 22 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt index ad6e5f4c995..2baec078f59 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.debugger import com.intellij.debugger.DebuggerInvocationUtil -import com.intellij.debugger.DebuggerManagerEx import com.intellij.debugger.SourcePosition import com.intellij.debugger.actions.MethodSmartStepTarget import com.intellij.debugger.actions.SmartStepTarget @@ -269,7 +268,6 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { val document = runReadAction { PsiDocumentManager.getInstance(myProject).getDocument(file) } ?: return val breakpointManager = XDebuggerManager.getInstance(myProject).getBreakpointManager() val kotlinFieldBreakpointType = findBreakpointType(KotlinFieldBreakpointType::class.java) - val kotlinLineBreakpointType = findBreakpointType(KotlinLineBreakpointType::class.java) val virtualFile = file.getVirtualFile() val runnable = { @@ -302,27 +300,8 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { } } else if (comment.startsWith("//Breakpoint!")) { - val javaBreakpoint = createBreakpointOfType( - breakpointManager, - kotlinLineBreakpointType as XLineBreakpointType>, - lineIndex, - virtualFile) - if (javaBreakpoint is LineBreakpoint<*>) { - - if (comment.contains("(")) { - val ordinal = comment.substringAfter("//Breakpoint! (").substringBefore(")").toInt() - - val properties = javaBreakpoint.xBreakpoint.properties as? JavaLineBreakpointProperties ?: continue - properties.lambdaOrdinal = ordinal - - BreakpointManager.addBreakpoint(javaBreakpoint) - println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1} lambdaOrdinal = $ordinal", ProcessOutputTypes.SYSTEM) - } - else { - BreakpointManager.addBreakpoint(javaBreakpoint) - println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM) - } - } + val ordinal = if (comment.contains(" (")) comment.substringAfter("//Breakpoint! (").substringBefore(")").toInt() else null + createLineBreakpoint(breakpointManager, file, lineIndex, ordinal) } else { throw AssertionError("Cannot create breakpoint at line ${lineIndex + 1}") @@ -338,6 +317,33 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { } } + private fun createLineBreakpoint( + breakpointManager: XBreakpointManager, + file: PsiFile, + lineIndex: Int, + lambdaOrdinal: Int? + ) { + val kotlinLineBreakpointType = findBreakpointType(KotlinLineBreakpointType::class.java) + val javaBreakpoint = createBreakpointOfType( + breakpointManager, + kotlinLineBreakpointType as XLineBreakpointType>, + lineIndex, + file.virtualFile) + if (javaBreakpoint is LineBreakpoint<*>) { + if (lambdaOrdinal != null) { + val properties = javaBreakpoint.xBreakpoint.properties as? JavaLineBreakpointProperties ?: return + properties.lambdaOrdinal = lambdaOrdinal + + BreakpointManager.addBreakpoint(javaBreakpoint) + println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1} lambdaOrdinal = $lambdaOrdinal", ProcessOutputTypes.SYSTEM) + } + else { + BreakpointManager.addBreakpoint(javaBreakpoint) + println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM) + } + } + } + private fun createBreakpointOfType( breakpointManager: XBreakpointManager, breakpointType: XLineBreakpointType>, @@ -365,12 +371,19 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { val breakpoints = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// ADDITIONAL_BREAKPOINT: ") for (breakpoint in breakpoints) { val position = breakpoint.split(".kt:") - assert(position.size() == 2) { "Couldn't parse position from test directive: directive = $breakpoint" } - createBreakpoint(position[0], position[1]) + assert(position.size == 2) { "Couldn't parse position from test directive: directive = $breakpoint" } + var lineMarker = position[1] + var ordinal: Int? = null + if (lineMarker.contains(":(") && lineMarker.endsWith(")")) { + val lineMarkerAndOrdinal = lineMarker.split(":(") + lineMarker = lineMarkerAndOrdinal[0] + ordinal = lineMarkerAndOrdinal[1].substringBefore(")").toInt() + } + createBreakpoint(position[0], lineMarker, ordinal) } } - private fun createBreakpoint(fileName: String, lineMarker: String) { + private fun createBreakpoint(fileName: String, lineMarker: String, ordinal: Int?) { val project = getProject()!! val sourceFiles = runReadAction { FilenameIndex.getAllFilesByExt(project, "kt").filter { @@ -384,16 +397,13 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { val runnable = Runnable() { val psiSourceFile = PsiManager.getInstance(project).findFile(sourceFiles.first())!! - val breakpointManager = DebuggerManagerEx.getInstanceEx(project)?.getBreakpointManager()!! + val breakpointManager = XDebuggerManager.getInstance(myProject).getBreakpointManager() val document = PsiDocumentManager.getInstance(project).getDocument(psiSourceFile)!! val index = psiSourceFile.getText()!!.indexOf(lineMarker) - val lineNumber = document.getLineNumber(index) + 1 + val lineNumber = document.getLineNumber(index) + 1 // lineMarker is for previous line - val breakpoint = breakpointManager.addLineBreakpoint(document, lineNumber) - if (breakpoint != null) { - println("LineBreakpoint created at " + psiSourceFile.getName() + ":" + lineNumber, ProcessOutputTypes.SYSTEM); - } + createLineBreakpoint(breakpointManager, psiSourceFile, lineNumber, ordinal) } DebuggerInvocationUtil.invokeAndWait(project, runnable, ModalityState.defaultModalityState()) diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index ff178ca65c5..58dc43a3bcb 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -682,6 +682,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doMultipleBreakpointsTest(fileName); } + @TestMetadata("isInsideInlineLambda.kt") + public void testIsInsideInlineLambda() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/isInsideInlineLambda.kt"); + doMultipleBreakpointsTest(fileName); + } + @TestMetadata("localFun.kt") public void testLocalFun() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt");