diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index 2334dc3f152..fe0fc89df74 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.inline.InlineUtil import java.util.* @@ -315,3 +317,12 @@ fun findCallByEndToken(element: PsiElement): KtCallExpression? { else -> null } } + +fun PropertyDescriptor.getBackingFieldName(): String? { + if (backingField == null) { + return null + } + + val jvmNameAnnotation = DescriptorUtils.findJvmNameAnnotation(this) ?: return name.asString() + return jvmNameAnnotation.allValueArguments.values.singleOrNull()?.toString() +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt index 139d28e2707..ee54b6645d1 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.debugger.evaluate import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.project.Project @@ -226,14 +227,14 @@ class KotlinDebuggerCaches(project: Project) { class ParametersDescriptor : Iterable { private val list = ArrayList() - fun add(name: String, jetType: KotlinType, value: Value? = null) { - list.add(Parameter(name, jetType, value)) + fun add(name: String, jetType: KotlinType, value: Value? = null, error: EvaluateException? = null) { + list.add(Parameter(name, jetType, value, error)) } override fun iterator() = list.iterator() } - data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null) + data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null) class ComputedClassNames(val classNames: List, val shouldBeCached: Boolean) { companion object { diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index 5660a717709..e08c4d6dbfd 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -55,6 +55,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Severity @@ -67,6 +68,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Compiled import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ParametersDescriptor import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely +import org.jetbrains.kotlin.idea.debugger.getBackingFieldName import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE import org.jetbrains.kotlin.idea.util.application.runReadAction @@ -80,6 +82,7 @@ import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.Opcodes.ASM5 @@ -223,7 +226,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line) ?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}") val (parametersDescriptor, extractedFunction) = try { - extractionResult.getParametersForDebugger(codeFragment) to extractionResult.declaration as KtNamedFunction + extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction } finally { Disposer.dispose(extractionResult) } @@ -407,7 +410,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour return sharedVar?.asJdiValue(vm, sharedVar.asmType) ?: jdiValue.asJdiValue(vm, jdiValue.asmType) } - private fun ExtractionResult.getParametersForDebugger(fragment: KtCodeFragment): ParametersDescriptor { + private fun ExtractionResult.getParametersForDebugger( + fragment: KtCodeFragment, + context: EvaluationContextImpl + ): ParametersDescriptor { return runReadAction { val valuesForLabels = HashMap() @@ -435,23 +441,47 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour param.argumentText.startsWith("::") -> param.argumentText.substring(2) else -> param.argumentText } + + val paramDescriptor = param.originalDescriptor + if (paramDescriptor is SyntheticFieldDescriptor) { + val backingFieldName = paramDescriptor.propertyDescriptor.getBackingFieldName() + if (backingFieldName != null) { + val thisObject = context.suspendContext.frameProxy?.thisObject() + val field = thisObject?.referenceType()?.fieldByName(backingFieldName) + + if (thisObject != null && field != null) { + parameters.add(backingFieldName, param.getParameterType(true), thisObject.getValue(field).asValue()) + } else { + parameters.add( + backingFieldName, paramDescriptor.builtIns.unitType, null, + EvaluateException("Can't find a backing field for property ${paramDescriptor.name}") + ) + } + + continue + } + } + parameters.add(paramName, param.getParameterType(true), valuesForLabels[paramName]) } + parameters } } - private fun EvaluationContextImpl.getArgumentsForEval4j(parameters: ParametersDescriptor, parameterTypes: Array): List { + private fun EvaluationContextImpl.getArgumentsForEval4j( + parameters: ParametersDescriptor, + parameterTypes: Array + ): List { val frameVisitor = FrameVisitor(this) - return parameters.zip(parameterTypes).map { - val result = if (it.first.value != null) { - it.first.value!! - } - else { - frameVisitor.findValue(it.first.callText, it.second, checkType = false, failIfNotFound = true)!! - } + return parameters.zip(parameterTypes).map { (parameter, type) -> + parameter.error?.let { throw it } + + val result = parameter.value + ?: frameVisitor.findValue(parameter.callText, type, checkType = false, failIfNotFound = true)!! + if (LOG.isDebugEnabled) { - LOG.debug("Parameter for eval4j: name = ${it.first.callText}, type = ${it.second}, value = $result") + LOG.debug("Parameter for eval4j: name = ${parameter.callText}, type = $type, value = $result") } result } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.kt new file mode 100644 index 00000000000..a39d5fb1f97 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.kt @@ -0,0 +1,27 @@ +package fieldVariable + +class Foo { + val a: String? = "field" + get() { + //Breakpoint! + val a = 5 + return "not a " + field + } + + val b: String? + get() { + //Breakpoint! + return "b" + } +} + +fun main() { + Foo().a + Foo().b +} + +// EXPRESSION: field +// RESULT: "field": Ljava/lang/String; + +// EXPRESSION: field +// RESULT: Can't find a backing field for property field \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.out new file mode 100644 index 00000000000..709baab034e --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.out @@ -0,0 +1,11 @@ +LineBreakpoint created at fieldVariable.kt:7 +LineBreakpoint created at fieldVariable.kt:14 +Run Java +Connected to the target VM +fieldVariable.kt:7 +Compile bytecode for field +fieldVariable.kt:14 +Compile bytecode for field +Disconnected from the target VM + +Process finished with exit code 0 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 6e127915b9a..e4861e6cd15 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -876,6 +876,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.kt"); } + @TestMetadata("fieldVariable.kt") + public void testFieldVariable() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.kt"); + } + @TestMetadata("funFromOuterClassInLamdba.kt") public void testFunFromOuterClassInLamdba() throws Exception { runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt");