From 3dcfae4a81e2b13fd0dad60d1542d3b445e682a1 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Fri, 12 Sep 2014 14:00:36 +0400 Subject: [PATCH] Debugger: delegated property should be visible in debugger #KT-4699 In Progress #KT-3582 Fixed --- .../debugger/ui/impl/watch/annotations.xml | 6 + .../intellij/debugger/ui/tree/annotations.xml | 10 ++ .../debugger/ui/tree/render/annotations.xml | 10 ++ annotations/com/sun/jdi/annotations.xml | 15 +- idea/src/META-INF/plugin.xml | 1 + .../DelegatedPropertyFieldDescriptor.kt | 49 +++++++ ...otlinClassWithDelegatedPropertyRenderer.kt | 137 ++++++++++++++++++ .../tinyApp/outs/delegatedPropertyInClass.out | 42 ++++++ .../frame/delegatedPropertyInClass.kt | 24 +++ ...KotlinEvaluateExpressionTestGenerated.java | 6 + 10 files changed, 297 insertions(+), 3 deletions(-) create mode 100644 annotations/com/intellij/debugger/ui/impl/watch/annotations.xml create mode 100644 annotations/com/intellij/debugger/ui/tree/annotations.xml create mode 100644 annotations/com/intellij/debugger/ui/tree/render/annotations.xml create mode 100644 idea/src/org/jetbrains/jet/plugin/debugger/render/DelegatedPropertyFieldDescriptor.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/debugger/render/KotlinClassWithDelegatedPropertyRenderer.kt create mode 100644 idea/testData/debugger/tinyApp/outs/delegatedPropertyInClass.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt diff --git a/annotations/com/intellij/debugger/ui/impl/watch/annotations.xml b/annotations/com/intellij/debugger/ui/impl/watch/annotations.xml new file mode 100644 index 00000000000..13cce82ddd9 --- /dev/null +++ b/annotations/com/intellij/debugger/ui/impl/watch/annotations.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/debugger/ui/tree/annotations.xml b/annotations/com/intellij/debugger/ui/tree/annotations.xml new file mode 100644 index 00000000000..8e1742dc406 --- /dev/null +++ b/annotations/com/intellij/debugger/ui/tree/annotations.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/debugger/ui/tree/render/annotations.xml b/annotations/com/intellij/debugger/ui/tree/render/annotations.xml new file mode 100644 index 00000000000..75cf9391a5b --- /dev/null +++ b/annotations/com/intellij/debugger/ui/tree/render/annotations.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/annotations/com/sun/jdi/annotations.xml b/annotations/com/sun/jdi/annotations.xml index fa91e924f4d..4a0d6321246 100644 --- a/annotations/com/sun/jdi/annotations.xml +++ b/annotations/com/sun/jdi/annotations.xml @@ -14,10 +14,16 @@ - + + + + - + + + + @@ -41,7 +47,10 @@ - + + + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index a2a1aac3eef..f5e3a64515b 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -338,6 +338,7 @@ + diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/render/DelegatedPropertyFieldDescriptor.kt b/idea/src/org/jetbrains/jet/plugin/debugger/render/DelegatedPropertyFieldDescriptor.kt new file mode 100644 index 00000000000..b5facfcf360 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/debugger/render/DelegatedPropertyFieldDescriptor.kt @@ -0,0 +1,49 @@ +package org.jetbrains.jet.plugin.debugger.render + +import com.intellij.openapi.project.Project +import com.sun.jdi.Field +import org.jetbrains.jet.lang.resolve.java.JvmAbi +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl +import com.sun.jdi.Value +import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl +import com.intellij.debugger.DebuggerContext +import com.intellij.psi.PsiExpression +import com.intellij.debugger.settings.NodeRendererSettings +import com.intellij.debugger.ui.tree.FieldDescriptor +import com.sun.jdi.ObjectReference + +class DelegatedPropertyFieldDescriptor( + val project: Project, + val computedValueFromGetter: Value, + val objectRef: ObjectReference, + val delegate: Field +): ValueDescriptorImpl(project, computedValueFromGetter), FieldDescriptor { + override fun getField() = delegate + override fun getObject() = objectRef + + override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? { + return computedValueFromGetter + } + + override fun calcValueName(): String? { + return with (StringBuilder()) { + val classRenderer = NodeRendererSettings.getInstance()?.getClassRenderer()!! + append(getName()) + if (classRenderer.SHOW_DECLARED_TYPE) { + append(": ") + append(classRenderer.renderTypeName(getValue()?.`type`()?.name())) + } + toString() + } + } + + override fun getName(): String { + return delegate.name().trimTrailing(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) + } + + override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression? { + return null + } + +} + diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/render/KotlinClassWithDelegatedPropertyRenderer.kt b/idea/src/org/jetbrains/jet/plugin/debugger/render/KotlinClassWithDelegatedPropertyRenderer.kt new file mode 100644 index 00000000000..77354ffd243 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/debugger/render/KotlinClassWithDelegatedPropertyRenderer.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.debugger.render + +import com.intellij.debugger.ui.tree.render.ClassRenderer +import com.sun.jdi.Type as JdiType +import com.sun.jdi.Value +import com.intellij.debugger.ui.tree.render.ChildrenBuilder +import com.intellij.debugger.engine.evaluation.EvaluationContext +import com.intellij.debugger.ui.tree.DebuggerTreeNode +import org.jetbrains.org.objectweb.asm.Type as AsmType +import com.intellij.debugger.engine.DebuggerManagerThreadImpl +import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl +import com.sun.jdi.ObjectReference +import com.intellij.xdebugger.settings.XDebuggerSettingsManager +import com.intellij.debugger.ui.impl.watch.NodeManagerImpl +import com.intellij.debugger.ui.impl.watch.MessageDescriptor +import java.util.ArrayList +import org.jetbrains.jet.lang.resolve.java.JvmAbi +import com.sun.jdi.Field +import com.intellij.debugger.engine.DebuggerUtils +import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl +import com.intellij.debugger.engine.evaluation.EvaluateException +import com.sun.jdi.ReferenceType +import com.sun.jdi.Type +import com.sun.jdi.InvocationException +import com.sun.jdi.Method +import org.jetbrains.jet.codegen.PropertyCodegen +import org.jetbrains.jet.lang.resolve.name.Name + +public class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() { + + override fun isApplicable(jdiType: Type?): Boolean { + if (!super.isApplicable(jdiType)) return false + + if (jdiType !is ReferenceType) return false + + return jdiType.allFields().any { it.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) } + } + + override fun buildChildren(value: Value?, builder: ChildrenBuilder, context: EvaluationContext) { + DebuggerManagerThreadImpl.assertIsManagerThread() + + if (value !is ObjectReference) return + + val nodeManager = builder.getNodeManager()!! + val nodeDescriptorFactory = builder.getDescriptorManager()!! + + val fields = value.referenceType().allFields() + if (fields.isEmpty()) { + builder.setChildren(listOf(nodeManager.createMessageNode(MessageDescriptor.CLASS_HAS_NO_FIELDS.getLabel()))) + return + } + + val children = ArrayList() + for (field in fields) { + if (!shouldDisplay(context, value, field)) { + continue + } + + val fieldDescriptor = nodeDescriptorFactory.getFieldDescriptor(builder.getParentDescriptor(), value, field) + children.add(nodeManager.createNode(fieldDescriptor, context)) + + if (field.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)) { + val method = findGetterForDelegatedProperty(value, field) + val threadReference = context.getSuspendContext().getThread()?.getThreadReference() + if (method != null && threadReference != null) { + val propValue = try { + value.invokeMethod(threadReference, method, listOf(), context.getSuspendContext().getSuspendPolicy()) + } + catch(e: InvocationException) { + e.exception() + } + if (propValue != null) { + val delegatedPropertyDescriptor = DelegatedPropertyFieldDescriptor( + context.getDebugProcess().getProject()!!, propValue, value, field) + children.add(nodeManager.createNode(delegatedPropertyDescriptor, context)) + } + } + } + } + + if (XDebuggerSettingsManager.getInstance()!!.getDataViewSettings().isSortValues()) { + children.sortBy(NodeManagerImpl.getNodeComparator()) + } + + builder.setChildren(children) + } + + private fun findGetterForDelegatedProperty(objRef: ObjectReference, delegate: Field): Method? { + val fieldName = delegate.name().trimTrailing(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) + val getterName = PropertyCodegen.getterName(Name.identifier(fieldName)) + return objRef.referenceType().methodsByName(getterName)?.firstOrNull() + } + + private fun shouldDisplay(context: EvaluationContext, objInstance: ObjectReference, field: Field): Boolean { + val isSynthetic = DebuggerUtils.isSynthetic(field) + when { + !SHOW_SYNTHETICS && isSynthetic, + !SHOW_STATIC && field.isStatic(), + !SHOW_STATIC_FINAL && field.isStatic() && field.isFinal() -> return false + SHOW_VAL_FIELDS_AS_LOCAL_VARIABLES && isSynthetic -> { + try { + val frameProxy = context.getFrameProxy() + if (frameProxy != null) { + val location = frameProxy.location() + if (location != null && + objInstance == context.getThisObject() && + objInstance.referenceType() == location.declaringType() && + field.name().startsWith(FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX) + ) { + return false + } + } + } + catch (ignored: EvaluateException) { + } + return true + } + else -> return true + } + } +} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/outs/delegatedPropertyInClass.out b/idea/testData/debugger/tinyApp/outs/delegatedPropertyInClass.out new file mode 100644 index 00000000000..1cf5e43adf1 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/delegatedPropertyInClass.out @@ -0,0 +1,42 @@ +LineBreakpoint created at delegatedPropertyInClass.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! delegatedPropertyInClass.DelegatedPropertyInClassPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +delegatedPropertyInClass.kt:7 +package delegatedPropertyInClass + +import kotlin.properties.Delegates + +fun main(args: Array) { + val a = A() + //Breakpoint! + args.size +} + +class A { + val prop by MyDelegate() + val propEx by MyDelegateThrowsException() +} + +class MyDelegate { + fun get(t: Any?, p: PropertyMetadata): Int = 1 +} + +class MyDelegateThrowsException { + fun get(t: Any?, p: PropertyMetadata): Int = throw IllegalStateException() +} + +// PRINT_FRAME + frame = main():8, DelegatedPropertyInClassPackage-@packagePartHASH {delegatedPropertyInClass} + static = static = delegatedPropertyInClass.DelegatedPropertyInClassPackage-@packagePartHASH + local = args: java.lang.String[] = {java.lang.String[0]@uniqueID} + local = a: delegatedPropertyInClass.A = {delegatedPropertyInClass.A@uniqueID} + field = prop$delegate: delegatedPropertyInClass.MyDelegate = {delegatedPropertyInClass.MyDelegate@uniqueID} + field = prop: int = 1 + field = propEx$delegate: delegatedPropertyInClass.MyDelegateThrowsException = {delegatedPropertyInClass.MyDelegateThrowsException@uniqueID} + field = propEx: java.lang.IllegalStateException = {java.lang.IllegalStateException@uniqueID}java.lang.IllegalStateException + field = detailMessage: java.lang.String = null + field = cause: java.lang.Throwable = {java.lang.IllegalStateException@uniqueID}java.lang.IllegalStateException + field = stackTrace: java.lang.StackTraceElement[] = null +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/singleBreakpoint/frame/delegatedPropertyInClass.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt new file mode 100644 index 00000000000..6b8c8ab10bc --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt @@ -0,0 +1,24 @@ +package delegatedPropertyInClass + +import kotlin.properties.Delegates + +fun main(args: Array) { + val a = A() + //Breakpoint! + args.size +} + +class A { + val prop by MyDelegate() + val propEx by MyDelegateThrowsException() +} + +class MyDelegate { + fun get(t: Any?, p: PropertyMetadata): Int = 1 +} + +class MyDelegateThrowsException { + fun get(t: Any?, p: PropertyMetadata): Int = throw IllegalStateException() +} + +// PRINT_FRAME \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index 0148807d378..321f2fae64e 100644 --- a/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -188,6 +188,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("delegatedPropertyInClass.kt") + public void testDelegatedPropertyInClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("frameAnonymousObject.kt") public void testFrameAnonymousObject() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameAnonymousObject.kt");