diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt index f3935d06d0b..bd89ba77e13 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt @@ -47,7 +47,11 @@ class FrameVisitor(context: EvaluationContextImpl) { } } else -> { - val localVariable = findLocalVariable(name, asmType, checkType) + val localVariable = if (isFunctionType(asmType)) + findLocalVariableForLocalFun(name, asmType, checkType) + else + findLocalVariable(name, asmType, checkType) + if (localVariable != null) { return localVariable } @@ -60,16 +64,17 @@ class FrameVisitor(context: EvaluationContextImpl) { } } - return if (!failIfNotFound) - null - else - throw EvaluateExceptionUtil.createEvaluateException("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}") + return fail("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}", failIfNotFound) } catch(e: InvalidStackFrameException) { throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame") } } + private fun fail(message: String, shouldFail: Boolean): Value? { + return if (shouldFail) throw EvaluateExceptionUtil.createEvaluateException(message) else null + } + private fun findThis(asmType: Type?): Value? { val thisObject = frame!!.thisObject() if (thisObject != null) { @@ -89,6 +94,15 @@ class FrameVisitor(context: EvaluationContextImpl) { return null } + private fun findLocalVariableForLocalFun(name: String, asmType: Type?, checkType: Boolean): Value? { + return findLocalVariable(name + "$", asmType, checkType) + } + + private fun isFunctionType(type: Type?): Boolean { + if (type == null || AsmUtil.isPrimitive(type)) return false + return type.getInternalName().startsWith("kotlin/Function") || type.getInternalName().startsWith("kotlin/ExtensionFunction") + } + private fun findLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? { val localVariable = frame!!.visibleVariableByName(name) if (localVariable == null) return null diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index 7bf240450d3..64327cf5c62 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -73,6 +73,7 @@ import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.types.Flexibility import org.jetbrains.org.objectweb.asm.* @@ -167,9 +168,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, if (extractionResult == null) { throw IllegalStateException("Code fragment cannot be extracted to function") } + val parametersDescriptor = extractionResult.getParametersForDebugger() val extractedFunction = extractionResult.declaration as JetNamedFunction - val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context) + val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor) val outputFiles = classFileFactory.asList() .filter { it.relativePath != "$packageInternalName.class" } @@ -188,7 +190,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, additionalFiles, sourcePosition, funName, - extractionResult.getParametersForDebugger()) + parametersDescriptor) } private fun getClassName(fileName: String): String { @@ -286,11 +288,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, } for (param in config.descriptor.parameters) { - val paramName = if (param.argumentText.contains("@")) { - param.argumentText.substringBefore("@") - } - else { - param.argumentText + val paramName = when { + param.argumentText.contains("@") -> param.argumentText.substringBefore("@") + param.argumentText.startsWith("::") -> param.argumentText.substring(2) + else -> param.argumentText } parameters.add(paramName, param.getParameterType(true)) } @@ -306,7 +307,8 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, private fun createClassFileFactory( codeFragment: JetCodeFragment, extractedFunction: JetNamedFunction, - context: EvaluationContextImpl + context: EvaluationContextImpl, + parameters: ParametersDescriptor ): ClassFileFactory { return runReadAction { val jetFile = createFileForDebugger(codeFragment, extractedFunction) @@ -337,21 +339,28 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, val frameVisitor = FrameVisitor(context) extractedFunction.getReceiverTypeReference()?.let { - state.recordAnonymousType(it, THIS_NAME, frameVisitor) + state.getBindingTrace().recordAnonymousType(it, THIS_NAME, frameVisitor) } - for (param in extractedFunction.getValueParameters()) { - val paramRef = param.getTypeReference() - val paramName = param.getName() - if (paramRef == null || paramName == null) { - logger.error("Each parameter for extracted function should have a name and a type reference", + val valueParameters = extractedFunction.getValueParameters() + var paramIndex = 0 + for (param in parameters) { + val (callText, type) = param + + if (callText.contains(THIS_NAME)) continue + + val valueParameter = valueParameters[paramIndex++] + + val paramRef = valueParameter.getTypeReference() + if (paramRef == null) { + logger.error("Each parameter for extracted function should have a type reference", Attachment("codeFragment.txt", codeFragment.getText()), Attachment("extractedFunction.txt", extractedFunction.getText())) exception("An exception occurs during Evaluate Expression Action") } - state.recordAnonymousType(paramRef, paramName, frameVisitor) + state.getBindingTrace().recordAnonymousType(paramRef, callText, frameVisitor) } KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION) @@ -360,7 +369,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, } } - private fun GenerationState.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, visitor: FrameVisitor) { + private fun BindingTrace.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, visitor: FrameVisitor) { val paramAnonymousType = typeReference.debugTypeInfo if (paramAnonymousType != null) { val declarationDescriptor = paramAnonymousType.getConstructor().getDeclarationDescriptor() @@ -369,7 +378,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, if (localVariable == null) { exception("Couldn't find local variable this in current frame to get classType for anonymous type ${paramAnonymousType}}") } - getBindingTrace().record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType) + record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType) } } } diff --git a/idea/testData/debugger/customLibraryForTinyApp/localFunCustomLib/localFunCustomLib.kt b/idea/testData/debugger/customLibraryForTinyApp/localFunCustomLib/localFunCustomLib.kt new file mode 100644 index 00000000000..472a1b28568 --- /dev/null +++ b/idea/testData/debugger/customLibraryForTinyApp/localFunCustomLib/localFunCustomLib.kt @@ -0,0 +1,6 @@ +package customLib.localFunInLibraryCustomLib + +public fun localFunInLibraryCustomLibMainFun() { + fun localFun() = 1 + val localFunInLibraryCustomLibProperty = 1 +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/outs/localFun.out b/idea/testData/debugger/tinyApp/outs/localFun.out new file mode 100644 index 00000000000..3b8d7b78235 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/localFun.out @@ -0,0 +1,26 @@ +LineBreakpoint created at localFun.kt:9 +LineBreakpoint created at localFun.kt:15 +LineBreakpoint created at localFun.kt:21 +LineBreakpoint created at localFun.kt:34 +LineBreakpoint created at localFun.kt:43 +LineBreakpoint created at localFun.kt:54 +LineBreakpoint created at localFun.kt:61 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! localFun.LocalFunPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +localFun.kt:9 +Compile bytecode for myLocalFun1() +localFun.kt:15 +Compile bytecode for myLocalFun2() +localFun.kt:21 +Compile bytecode for myLocalFun1() + 1 +localFun.kt:34 +Compile bytecode for myLocalFun4() +localFun.kt:43 +Compile bytecode for myLocalFun5(2) +localFun.kt:54 +Compile bytecode for myLocalFun6() +localFun.kt:61 +Compile bytecode for myLocalFun6() + 1 +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/localFunInLibrary.out b/idea/testData/debugger/tinyApp/outs/localFunInLibrary.out new file mode 100644 index 00000000000..585d54afc5c --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/localFunInLibrary.out @@ -0,0 +1,8 @@ +LineBreakpoint created at localFunCustomLib.kt:5 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! localFunInLibrary.LocalFunInLibraryPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +localFunCustomLib.kt:6 +Compile bytecode for localFun() +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/evaluate/multipleBreakpoints/library/localFunInLibrary.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/localFunInLibrary.kt new file mode 100644 index 00000000000..c5e5b3838ed --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/localFunInLibrary.kt @@ -0,0 +1,9 @@ +package localFunInLibrary + +fun main(args: Array) { + customLib.localFunInLibraryCustomLib.localFunInLibraryCustomLibMainFun() +} + +// ADDITIONAL_BREAKPOINT: localFunCustomLib.kt:localFunInLibraryCustomLibProperty +// EXPRESSION: localFun() +// RESULT: 1: I \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt new file mode 100644 index 00000000000..381186c482b --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt @@ -0,0 +1,65 @@ +package localFun + +fun main(args: Array) { + fun myLocalFun1() = 1 + + // EXPRESSION: myLocalFun1() + // RESULT: 1: I + //Breakpoint! + myLocalFun1() + + fun myLocalFun2() = 2 + // EXPRESSION: myLocalFun2() + // RESULT: 2: I + //Breakpoint! + myLocalFun2() + + fun myLocalFun3() { + // EXPRESSION: myLocalFun1() + 1 + // RESULT: Cannot find local variable: name = myLocalFun1 + //Breakpoint! + myLocalFun1() + 1 + } + + myLocalFun3() + + + fun myLocalFun4(): Int { + return 1 + } + + // EXPRESSION: myLocalFun4() + // RESULT: 1: I + //Breakpoint! + myLocalFun4() + + fun myLocalFun5(i: Int): Int { + return i + } + + // EXPRESSION: myLocalFun5(2) + // RESULT: 2: I + //Breakpoint! + myLocalFun5(2) + + var i = 1 + fun myLocalFun6(): Int { + i++ + return i + } + + // EXPRESSION: myLocalFun6() + // RESULT: 2: I + //Breakpoint! + myLocalFun6() + + i = 1 + fun myLocalFun7() { + // EXPRESSION: myLocalFun6() + 1 + // RESULT: 3: I + //Breakpoint! + myLocalFun6() + 1 + } + + myLocalFun7() +} \ No newline at end of file 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 b894fac4944..2bc965afdc2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -607,6 +607,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doMultipleBreakpointsTest(fileName); } + @TestMetadata("localFun.kt") + public void testLocalFun() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt"); + doMultipleBreakpointsTest(fileName); + } + @TestMetadata("whenEntry.kt") public void testWhenEntry() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt"); @@ -644,6 +650,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/customLibClassName.kt"); doMultipleBreakpointsTest(fileName); } + + @TestMetadata("localFunInLibrary.kt") + public void testLocalFunInLibrary() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/localFunInLibrary.kt"); + doMultipleBreakpointsTest(fileName); + } } } }