From 5d08e82aaff8b28eb4268a8a87a1b5613e37f82d Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Fri, 13 Jan 2017 15:19:05 +0300 Subject: [PATCH] Fix look up for breakpoint in crossinline lambdas in android (KT-15282) #KT-15282 Fixed --- .idea/dictionaries/Nikolay_Krasko.xml | 1 + .../debugger/NoStrataPositionManagerHelper.kt | 38 +++++++++++++++++-- .../stepping/KotlinSteppingCommandProvider.kt | 2 +- ...opInLambdaInInlinedCallWithCrossInline.out | 8 ++++ ...nLambdaInInlinedCallWithCrossInlineDex.out | 8 ++++ ...ousFunctionInInlinedCallWithCrossInline.kt | 20 ++++++++++ ...FunctionInInlinedCallWithCrossInlineDex.kt | 20 ++++++++++ ...topInLambdaInInlinedCallWithCrossInline.kt | 20 ++++++++++ ...InLambdaInInlinedCallWithCrossInlineDex.kt | 20 ++++++++++ .../debugger/KotlinSteppingTestGenerated.java | 24 ++++++++++++ 10 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInline.out create mode 100644 idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInlineDex.out create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInline.kt create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInlineDex.kt create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInline.kt create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInlineDex.kt diff --git a/.idea/dictionaries/Nikolay_Krasko.xml b/.idea/dictionaries/Nikolay_Krasko.xml index abace9e7952..45a4572a3bb 100644 --- a/.idea/dictionaries/Nikolay_Krasko.xml +++ b/.idea/dictionaries/Nikolay_Krasko.xml @@ -2,6 +2,7 @@ accessors + crossinline fqname goto gradle diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt index 0301e4c1689..3893cac8015 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt @@ -30,6 +30,7 @@ import com.intellij.util.containers.ContainerUtil import com.sun.jdi.Location import com.sun.jdi.ReferenceType import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches import org.jetbrains.kotlin.idea.refactoring.getLineCount import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset @@ -42,11 +43,13 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.tail -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.utils.addToStdlib.check +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.getOrPutNullable import org.jetbrains.org.objectweb.asm.* import java.io.File @@ -254,9 +257,17 @@ internal fun getLocationsOfInlinedLine(type: ReferenceType, position: SourcePosi val lineStartOffset = file.getLineStartOffset(line) ?: return listOf() val element = file.findElementAt(lineStartOffset) ?: return listOf() + val ktElement = element.parents.firstIsInstanceOrNull() ?: return listOf() val isInInline = runReadAction { element.parents.any { it is KtFunction && it.hasModifier(KtTokens.INLINE_KEYWORD) } } - if (!isInInline) return listOf() + + if (!isInInline) { + // Lambdas passed to crossinline arguments are inlined when they are used in non-inlined lambdas + val isInCrossinlineArgument = isInCrossinlineArgument(ktElement) + if (!isInCrossinlineArgument) { + return listOf() + } + } val lines = inlinedLinesNumbers(line + 1, position.file.name, FqName(type.name()), type.sourceName(), project, sourceSearchScope) val inlineLocations = lines.flatMap { type.locationsOfLine(it) } @@ -264,6 +275,25 @@ internal fun getLocationsOfInlinedLine(type: ReferenceType, position: SourcePosi return inlineLocations } +fun isInCrossinlineArgument(ktElement: KtElement): Boolean { + val argumentFunctions = runReadAction { + ktElement.parents.filter { + when (it) { + is KtFunctionLiteral -> it.parent is KtLambdaExpression && (it.parent.parent is KtValueArgument || it.parent.parent is KtLambdaArgument) + is KtFunction -> it.parent is KtValueArgument + else -> false + } + }.filterIsInstance() + } + + val bindingContext = ktElement.analyze(BodyResolveMode.PARTIAL) + return argumentFunctions.any { + val argumentDescriptor = InlineUtil.getInlineArgumentDescriptor(it, bindingContext) + argumentDescriptor?.isCrossinline ?: false + } +} + + private fun inlinedLinesNumbers( inlineLineNumber: Int, inlineFileName: String, destinationTypeFqName: FqName, destinationFileName: String, @@ -281,7 +311,7 @@ private fun inlinedLinesNumbers( val smap = smapData.kotlinStrata ?: return listOf() - val mappingsToInlinedFile = smap.fileMappings.filter() { it.name == inlineFileName } + val mappingsToInlinedFile = smap.fileMappings.filter { it.name == inlineFileName } val mappingIntervals = mappingsToInlinedFile.flatMap { it.lineMappings } val mappedLines = mappingIntervals.asSequence(). diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 2f668de648f..2f92aceab3f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -188,7 +188,7 @@ private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List): List { return inlineFunctionCalls.flatMap { it.valueArguments - .map { getArgumentExpression(it) } + .map(::getArgumentExpression) .filterIsInstance() } } diff --git a/idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInline.out b/idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInline.out new file mode 100644 index 00000000000..1c9ab523a30 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInline.out @@ -0,0 +1,8 @@ +LineBreakpoint created at stopInLambdaInInlinedCallWithCrossInline.kt:8 +!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! stopInLambdaInInlinedCallWithCrossInline.StopInLambdaInInlinedCallWithCrossInlineKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +stopInLambdaInInlinedCallWithCrossInline.kt:8 +stopInLambdaInInlinedCallWithCrossInline.kt:18 +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/stopInLambdaInInlinedCallWithCrossInlineDex.out b/idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInlineDex.out new file mode 100644 index 00000000000..e958d1d6a2a --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/stopInLambdaInInlinedCallWithCrossInlineDex.out @@ -0,0 +1,8 @@ +LineBreakpoint created at stopInLambdaInInlinedCallWithCrossInlineDex.kt:8 +!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! stopInLambdaInInlinedCallWithCrossInlineDex.StopInLambdaInInlinedCallWithCrossInlineDexKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +stopInLambdaInInlinedCallWithCrossInlineDex.kt:8 +stopInLambdaInInlinedCallWithCrossInlineDex.kt:19 +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/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInline.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInline.kt new file mode 100644 index 00000000000..e139e1cd0c4 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInline.kt @@ -0,0 +1,20 @@ +package stopInAnonymousFunctionInInlinedCallWithCrossInline + +// KT-15282 + +fun main(args: Array) { + foo(fun () { + //Breakpoint! + 12 + }) +} + +fun bar(f: () -> Unit) { + f() +} + +inline fun foo(crossinline func: () -> Int) { + bar { + func() + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInlineDex.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInlineDex.kt new file mode 100644 index 00000000000..e2f8cb37634 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInlineDex.kt @@ -0,0 +1,20 @@ +package stopInAnonymousFunctionInInlinedCallWithCrossInlineDex + +// KT-15282 + +fun main(args: Array) { + foo(fun () { + //Breakpoint! + 12 + }) +} + +fun bar(f: () -> Unit) { + f() +} + +inline fun foo(crossinline func: () -> Int) { + bar { + func() + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInline.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInline.kt new file mode 100644 index 00000000000..c627028628a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInline.kt @@ -0,0 +1,20 @@ +package stopInLambdaInInlinedCallWithCrossInline + +// KT-15282 + +fun main(args: Array) { + foo { + //Breakpoint! + 12 + } +} + +fun bar(f: () -> Unit) { + f() +} + +inline fun foo(crossinline func: () -> Int) { + bar { + func() + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInlineDex.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInlineDex.kt new file mode 100644 index 00000000000..7737997599c --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInlineDex.kt @@ -0,0 +1,20 @@ +package stopInLambdaInInlinedCallWithCrossInlineDex + +// KT-15282 + +fun main(args: Array) { + foo { + //Breakpoint! + 12 + } +} + +fun bar(f: () -> Unit) { + f() +} + +inline fun foo(crossinline func: () -> Int) { + bar { + func() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index f2e99e07449..9fabd8e74a5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -704,6 +704,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { doStepOverTest(fileName); } + @TestMetadata("stopInAnonymousFunctionInInlinedCallWithCrossInline.kt") + public void testStopInAnonymousFunctionInInlinedCallWithCrossInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInline.kt"); + doStepOverTest(fileName); + } + + @TestMetadata("stopInAnonymousFunctionInInlinedCallWithCrossInlineDex.kt") + public void testStopInAnonymousFunctionInInlinedCallWithCrossInlineDex() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInAnonymousFunctionInInlinedCallWithCrossInlineDex.kt"); + doStepOverTest(fileName); + } + @TestMetadata("stopInInlineCallLocalFunLambda.kt") public void testStopInInlineCallLocalFunLambda() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInInlineCallLocalFunLambda.kt"); @@ -746,6 +758,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { doStepOverTest(fileName); } + @TestMetadata("stopInLambdaInInlinedCallWithCrossInline.kt") + public void testStopInLambdaInInlinedCallWithCrossInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInline.kt"); + doStepOverTest(fileName); + } + + @TestMetadata("stopInLambdaInInlinedCallWithCrossInlineDex.kt") + public void testStopInLambdaInInlinedCallWithCrossInlineDex() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInInlinedCallWithCrossInlineDex.kt"); + doStepOverTest(fileName); + } + @TestMetadata("stopInLambdaInlineCallLambda.kt") public void testStopInLambdaInlineCallLambda() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLambdaInlineCallLambda.kt");