diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index bd0e5782203..c58140d0597 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -41,10 +41,12 @@ import com.sun.jdi.request.ClassPrepareRequest import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.binding.CodegenBinding +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.JetTypeMapper import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider import org.jetbrains.kotlin.fileClasses.getFileClassInternalName @@ -63,9 +65,14 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.utils.addToStdlib.check +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.toReadOnlyList import java.util.* import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils @@ -168,7 +175,7 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).internalName for (literal in literalsOrFunctions) { - if (isInlinedLambda(literal, typeMapper.bindingContext)) { + if (InlineUtil.isInlinedArgument(literal, typeMapper.bindingContext, true)) { if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl)) { return literal } @@ -201,8 +208,9 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M try { if (location.declaringType().containsKotlinStrata()) { //replace is required for windows - referenceInternalName = location.sourcePath().replace('\\','/') - } else { + referenceInternalName = location.sourcePath().replace('\\', '/') + } + else { referenceInternalName = defaultInternalName(location) } } @@ -353,14 +361,24 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M private fun getInternalClassNameForElement(notPositionedElement: PsiElement?, typeMapper: JetTypeMapper, file: KtFile, isInLibrary: Boolean): Collection { val element = getElementToCalculateClassName(notPositionedElement) when { - element is KtClassOrObject -> return getJvmInternalNameForImpl(typeMapper, element).toCollection() + element is KtClassOrObject -> return getJvmInternalNameForImpl(typeMapper, element).toSet() element is KtFunctionLiteral -> { - if (isInlinedLambda(element, typeMapper.bindingContext)) { - return getInternalClassNameForElement(element.parent, typeMapper, file, isInLibrary) + val descriptor = InlineUtil.getInlineArgumentDescriptor(element, typeMapper.bindingContext) + if (descriptor != null) { + val classNamesForParent = getInternalClassNameForElement(element.parent, typeMapper, file, isInLibrary) + if (descriptor.isCrossinline) { + return findCrossInlineArguments(element, descriptor, typeMapper.bindingContext) + classNamesForParent + } + return classNamesForParent } else { + val crossInlineParameterUsages = element.containsCrossInlineParameterUsages(typeMapper.bindingContext) + if (crossInlineParameterUsages.isNotEmpty()) { + return classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext) + } + val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element) - return asmType.internalName.toCollection() + return asmType.internalName.toSet() } } element is KtAnonymousInitializer -> { @@ -375,7 +393,7 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M if (isInPropertyAccessor(notPositionedElement)) { val classOrObject = PsiTreeUtil.getParentOfType(element, KtClassOrObject::class.java) if (classOrObject != null) { - return getJvmInternalNameForImpl(typeMapper, classOrObject).toCollection() + return getJvmInternalNameForImpl(typeMapper, classOrObject).toSet() } } @@ -384,14 +402,18 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M return getInternalClassNameForElement(element.parent, typeMapper, file, isInLibrary) } - return getJvmInternalNameForPropertyOwner(typeMapper, descriptor).toCollection() + return getJvmInternalNameForPropertyOwner(typeMapper, descriptor).toSet() } element is KtNamedFunction -> { if (isInlinedLambda(element, typeMapper.bindingContext)) { return getInternalClassNameForElement(element.parent, typeMapper, file, isInLibrary) } - val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext) + val crossInlineParameterUsages = element.containsCrossInlineParameterUsages(typeMapper.bindingContext) + if (crossInlineParameterUsages.isNotEmpty()) { + return classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext) + } + val parent = getElementToCalculateClassName(element.parent) val parentInternalName = if (parent is KtClassOrObject) { getJvmInternalNameForImpl(typeMapper, parent) @@ -404,11 +426,12 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M NoResolveFileClassesProvider.getFileClassInternalName(file) } - return (inlinedCalls + parentInternalName.toCollection()).toSet() + val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext) + return inlinedCalls + parentInternalName.toSet() } } - return NoResolveFileClassesProvider.getFileClassInternalName(file).toCollection() + return NoResolveFileClassesProvider.getFileClassInternalName(file).toSet() } private val TYPES_TO_CALCULATE_CLASSNAME: Array> = @@ -463,8 +486,7 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M private fun createKeyForTypeMapper(file: KtFile) = NoResolveFileClassesProvider.getFileClassInternalName(file) - - private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): Collection { + private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): Set { return runReadAction { val result = hashSetOf() @@ -485,9 +507,87 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M } } + private fun findCrossInlineArguments(argument: KtFunction, parameterDescriptor: ValueParameterDescriptor, context: BindingContext): Set { + return runReadAction { + val source = parameterDescriptor.source.getPsi() as? KtParameter + val functionName = source?.ownerFunction?.name + if (functionName != null) { + return@runReadAction setOf(getCrossInlineArgumentClassName(argument, functionName, context)) + } + return@runReadAction emptySet() + } + } + + private fun getCrossInlineArgumentClassName(argument: KtFunction, inlineFunctionName: String, context: BindingContext): String { + val anonymousClassNameForArgument = CodegenBinding.asmTypeForAnonymousClass(context, argument).internalName + val newName = anonymousClassNameForArgument.substringIndex() + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName + return "$newName$*" + } + + private fun KtFunction.containsCrossInlineParameterUsages(context: BindingContext): Collection { + fun KtFunction.hasParameterCall(parameter: KtParameter): Boolean { + return ReferencesSearch.search(parameter).any { + this.textRange.contains(it.element.textRange) + } + } + + val inlineFunction = this.parents.firstIsInstanceOrNull() ?: return emptySet() + + val inlineFunctionDescriptor = context[BindingContext.FUNCTION, inlineFunction] + if (inlineFunctionDescriptor == null || !InlineUtil.isInline(inlineFunctionDescriptor)) return emptySet() + + return inlineFunctionDescriptor.valueParameters + .filter { it.isCrossinline } + .mapNotNull { + val psiParameter = it.source.getPsi() as? KtParameter + if (psiParameter != null && this@containsCrossInlineParameterUsages.hasParameterCall(psiParameter)) + it + else + null + } + } + + private fun classNamesForCrossInlineParameters(usedParameters: Collection, context: BindingContext): Set { + // We could calculate className only for one of parameters, because we add '*' to match all crossInlined parameter calls + val parameter = usedParameters.first() + val result = hashSetOf() + val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet() + + ReferencesSearch.search(inlineFunction).forEach { + if (!it.isImportUsage()) { + val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) } + if (call != null) { + val resolvedCall = call.getResolvedCall(context) + val argument = resolvedCall?.valueArguments?.get(parameter) + if (argument != null) { + val argumentExpression = getArgumentExpression(argument.arguments.first()) + if (argumentExpression is KtFunction) { + result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context)) + } + } + } + } + true + } + + return result + } + + private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() + + private fun String.substringIndex(): String { + if (lastIndexOf("$") < 0) return this + + val suffix = substringAfterLast("$") + if (suffix.all { it.isDigit() }) { + return substringBeforeLast("$") + "$" + } + return this + } + private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin") - private fun String?.toCollection() = if (this == null) emptySet() else setOf(this) + private fun String?.toSet() = if (this == null) emptySet() else setOf(this) companion object { public fun createTypeMapper(file: KtFile): JetTypeMapper { diff --git a/idea/testData/debugger/tinyApp/outs/crossinlineLiteral.out b/idea/testData/debugger/tinyApp/outs/crossinlineLiteral.out new file mode 100644 index 00000000000..ed9c88da8a4 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/crossinlineLiteral.out @@ -0,0 +1,39 @@ +LineBreakpoint created at crossinlineLiteral.kt:12 +LineBreakpoint created at crossinlineLiteral.kt:17 +LineBreakpoint created at crossinlineLiteral.kt:22 +LineBreakpoint created at crossinlineLiteral.kt:27 +LineBreakpoint created at crossinlineLiteral.kt:32 +LineBreakpoint created at crossinlineLiteral.kt:35 +LineBreakpoint created at crossinlineLiteral.kt:40 +LineBreakpoint created at crossinlineLiteral.kt:48 +LineBreakpoint created at crossinlineLiteral.kt:56 +LineBreakpoint created at crossinlineLiteral.kt:64 +LineBreakpoint created at crossinlineLiteral.kt:70 +LineBreakpoint created at crossinlineLiteral.kt:79 +LineBreakpoint created at crossinlineLiteral.kt:87 +LineBreakpoint created at crossinlineLiteral.kt:97 +LineBreakpoint created at crossinlineLiteral.kt:104 +LineBreakpoint created at crossinlineLiteral.kt:114 +!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! crossinlineLiteral.CrossinlineLiteralKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +crossinlineLiteral.kt:64 +crossinlineLiteral.kt:12 +crossinlineLiteral.kt:70 +crossinlineLiteral.kt:17 +crossinlineLiteral.kt:79 +crossinlineLiteral.kt:22 +crossinlineLiteral.kt:87 +crossinlineLiteral.kt:27 +crossinlineLiteral.kt:97 +crossinlineLiteral.kt:32 +crossinlineLiteral.kt:104 +crossinlineLiteral.kt:35 +crossinlineLiteral.kt:114 +crossinlineLiteral.kt:40 +crossinlineLiteral.kt:87 +crossinlineLiteral.kt:48 +crossinlineLiteral.kt:87 +crossinlineLiteral.kt:56 +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/custom/crossinlineLiteral.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.kt new file mode 100644 index 00000000000..5b7109b93f0 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.kt @@ -0,0 +1,120 @@ +package crossinlineLiteral + +fun main(args: Array) { + callFromTopLevel() + A().callFromClass() + A.callFromCompanion() +} + +fun callFromTopLevel() { + simpleCall { + //Breakpoint! + val a = 1 + } + + lambdaCall { + //Breakpoint! + val a = 1 + } + + inlinedLambdaCall { + //Breakpoint! + val a = 1 + } + + objectCall { + //Breakpoint! + val a = 1 + } + + multipleCrossInline({ + //Breakpoint! + val a = 1 + }, { + //Breakpoint! + val a = 1 + }) + + lambdaInInlinedLambdaCall { + //Breakpoint! + val a = 1 + } +} + +class A { + fun callFromClass() { + objectCall { + //Breakpoint! + val a = 1 + } + } + + companion object { + fun callFromCompanion() { + objectCall { + //Breakpoint! + val a = 1 + } + } + } +} + +inline fun simpleCall(crossinline f: (Int) -> Unit) { + //Breakpoint! + f(1) +} + +inline fun lambdaCall(crossinline f: (Int) -> Unit) { + val a = { + //Breakpoint! + f(1) + } + + a.invoke() +} + +inline fun inlinedLambdaCall(crossinline f: (Int) -> Unit) { + run { + //Breakpoint! + f(1) + } +} + +inline fun objectCall(crossinline f: (Int) -> Unit) { + val a = object { + fun objFun() { + //Breakpoint! + f(1) + } + } + + a.objFun() +} + +inline fun multipleCrossInline(crossinline f1: (Int) -> Unit, crossinline f2: (Int) -> Unit) { + val a1 = { + //Breakpoint! + f1(1) + } + + a1.invoke() + + val a2 = { + //Breakpoint! + f2(1) + } + + a2.invoke() +} + +inline fun lambdaInInlinedLambdaCall(crossinline f: (Int) -> Unit) { + run { + { + //Breakpoint! + f(1) + }() + + } +} + +// RESUME: 17 \ 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 efd0076f989..706320e6748 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -523,6 +523,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/stepping/custom"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("crossinlineLiteral.kt") + public void testCrossinlineLiteral() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/crossinlineLiteral.kt"); + doCustomTest(fileName); + } + @TestMetadata("funLiteral.kt") public void testFunLiteral() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/funLiteral.kt");