Debugger: add an option for computing delegated properties values in Variables View

This commit is contained in:
Natalia Ukhorskaya
2015-04-06 19:31:12 +03:00
parent 111f375fa6
commit 649654999f
10 changed files with 203 additions and 36 deletions
@@ -480,3 +480,4 @@ kotlin.compiler.js.option.output.copy.dir=O&utput directory for library &runtime
# Debugger
debugger.filter.ignore.internal.classes=Do not step into specific Kotlin classes
debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution)
@@ -31,6 +31,7 @@ import com.intellij.xdebugger.XDebuggerUtil
State(name = "KotlinDebuggerSettings", storages = array(Storage(file = StoragePathMacros.APP_CONFIG + "/kotlin_debug.xml")))
public class KotlinDebuggerSettings : XDebuggerSettings<KotlinDebuggerSettings>("kotlin_debugger"), Getter<KotlinDebuggerSettings> {
public var DEBUG_RENDER_DELEGATED_PROPERTIES: Boolean = true
public var DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES: Boolean = true
public var DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED: Boolean = false
@@ -44,10 +45,16 @@ public class KotlinDebuggerSettings : XDebuggerSettings<KotlinDebuggerSettings>(
return when (category) {
DebuggerSettingsCategory.STEPPING ->
listOf(SimpleConfigurable.create(
"reference.idesettings.debugger.kotlin",
"reference.idesettings.debugger.kotlin.stepping",
"Kotlin",
javaClass<KotlinSteppingConfigurableUi>(),
this))
DebuggerSettingsCategory.DATA_VIEWS ->
listOf(SimpleConfigurable.create(
"reference.idesettings.debugger.kotlin.data.view",
"Kotlin",
javaClass<KotlinDelegatedPropertyRendererConfigurableUi>(),
this))
else -> listOf()
}
}
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.KotlinDelegatedPropertyRendererConfigurableUi">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="99d36" class="javax.swing.JCheckBox" binding="renderDelegatedProperties">
<constraints>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="false"/>
<text resource-bundle="org/jetbrains/kotlin/idea/JetBundle" key="debugger.data.view.delegated.properties"/>
</properties>
</component>
<vspacer id="c37da">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
</children>
</grid>
</form>
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2015 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.kotlin.idea.debugger;
import com.intellij.openapi.options.ConfigurableUi;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class KotlinDelegatedPropertyRendererConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
private JCheckBox renderDelegatedProperties;
private JPanel myPanel;
@Override
public void reset(@NotNull KotlinDebuggerSettings settings) {
boolean flag = settings.getDEBUG_RENDER_DELEGATED_PROPERTIES();
renderDelegatedProperties.setSelected(flag);
}
@Override
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
return settings.getDEBUG_RENDER_DELEGATED_PROPERTIES() != renderDelegatedProperties.isSelected();
}
@Override
public void apply(@NotNull KotlinDebuggerSettings settings) {
settings.setDEBUG_RENDER_DELEGATED_PROPERTIES(renderDelegatedProperties.isSelected());
}
@NotNull
@Override
public JComponent getComponent() {
return myPanel;
}
}
@@ -16,29 +16,50 @@
package org.jetbrains.kotlin.idea.debugger.render
import com.intellij.openapi.project.Project
import com.sun.jdi.Field
import org.jetbrains.kotlin.load.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.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.settings.NodeRendererSettings
import com.intellij.debugger.ui.tree.FieldDescriptor
import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiExpression
import com.sun.jdi.Field
import com.sun.jdi.Method
import com.sun.jdi.ObjectReference
import com.sun.jdi.Value
import org.jetbrains.kotlin.codegen.PropertyCodegen
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
class DelegatedPropertyFieldDescriptor(
project: Project,
val computedValueFromGetter: Value,
val objectRef: ObjectReference,
val delegate: Field
): ValueDescriptorImpl(project, computedValueFromGetter), FieldDescriptor {
override fun getField() = delegate
override fun getObject() = objectRef
val delegate: Field,
val renderDelegatedProperty: Boolean
) : FieldDescriptorImpl(project, objectRef, delegate) {
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
return computedValueFromGetter
if (evaluationContext == null) return null
if (!renderDelegatedProperty) return super.calcValue(evaluationContext)
val method = findGetterForDelegatedProperty()
val threadReference = evaluationContext.getSuspendContext().getThread()?.getThreadReference()
if (method == null || threadReference == null) {
return super.calcValue(evaluationContext)
}
try {
return evaluationContext.getDebugProcess().invokeInstanceMethod(
evaluationContext,
getObject(),
method,
listOf<Nothing>(),
evaluationContext.getSuspendContext().getSuspendPolicy()
)
}
catch(e: EvaluateException) {
return e.getExceptionFromTargetVM()
}
}
override fun calcValueName(): String? {
@@ -54,12 +75,17 @@ class DelegatedPropertyFieldDescriptor(
}
override fun getName(): String {
return delegate.name().trimTrailing(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)
return delegate.name().removeSuffix(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)
}
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression? {
return null
}
private fun findGetterForDelegatedProperty(): Method? {
val fieldName = getName()
val getterName = PropertyCodegen.getterName(Name.identifier(fieldName))
return getObject().referenceType().methodsByName(getterName)?.firstOrNull()
}
}
@@ -37,6 +37,7 @@ import com.sun.jdi.Method
import org.jetbrains.kotlin.codegen.PropertyCodegen
import org.jetbrains.kotlin.name.Name
import com.intellij.debugger.engine.evaluation.EvaluateException
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
public class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() {
@@ -69,24 +70,22 @@ public class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() {
}
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 {
context.getDebugProcess().invokeInstanceMethod(context, value, method, listOf<Nothing>(), context.getSuspendContext().getSuspendPolicy())
}
catch(e: EvaluateException) {
e.getExceptionFromTargetVM()
}
if (propValue != null) {
val delegatedPropertyDescriptor = DelegatedPropertyFieldDescriptor(
context.getDebugProcess().getProject()!!, propValue, value, field)
children.add(nodeManager.createNode(delegatedPropertyDescriptor, context))
}
val shouldRenderDelegatedProperty = KotlinDebuggerSettings.getInstance().DEBUG_RENDER_DELEGATED_PROPERTIES
if (shouldRenderDelegatedProperty) {
children.add(nodeManager.createNode(fieldDescriptor, context))
}
val delegatedPropertyDescriptor = DelegatedPropertyFieldDescriptor(
context.getDebugProcess().getProject()!!,
value,
field,
shouldRenderDelegatedProperty)
children.add(nodeManager.createNode(delegatedPropertyDescriptor, context))
}
else {
children.add(nodeManager.createNode(fieldDescriptor, context))
}
}
@@ -97,9 +96,4 @@ public class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() {
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()
}
}
@@ -0,0 +1,32 @@
LineBreakpoint created at delegatedPropertyInClassWoRenderer.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! delegatedPropertyInClassWoRenderer.DelegatedPropertyInClassWoRendererPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
delegatedPropertyInClassWoRenderer.kt:8
package delegatedPropertyInClassWoRenderer
import kotlin.properties.Delegates
fun main(args: Array<String>) {
val a = A()
//Breakpoint!
args.size()
}
class A {
val prop by MyDelegate()
}
class MyDelegate {
fun get(t: Any?, p: PropertyMetadata): Int = 1
}
// RENDER_DELEGATED_PROPERTIES: false
// PRINT_FRAME
frame = main():8, DelegatedPropertyInClassWoRendererPackage$@packagePartHASH {delegatedPropertyInClassWoRenderer}
static = static = delegatedPropertyInClassWoRenderer.DelegatedPropertyInClassWoRendererPackage$@packagePartHASH
local = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = delegatedPropertyInClassWoRenderer.kt, 5)
local = a: delegatedPropertyInClassWoRenderer.A = {delegatedPropertyInClassWoRenderer.A@uniqueID} (sp = delegatedPropertyInClassWoRenderer.kt, 6)
field = prop: delegatedPropertyInClassWoRenderer.MyDelegate = {delegatedPropertyInClassWoRenderer.MyDelegate@uniqueID} (sp = delegatedPropertyInClassWoRenderer.kt, 12)
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,20 @@
package delegatedPropertyInClassWoRenderer
import kotlin.properties.Delegates
fun main(args: Array<String>) {
val a = A()
//Breakpoint!
args.size()
}
class A {
val prop by MyDelegate()
}
class MyDelegate {
fun get(t: Any?, p: PropertyMetadata): Int = 1
}
// RENDER_DELEGATED_PROPERTIES: false
// PRINT_FRAME
@@ -38,6 +38,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
private var oldSettings: DebuggerSettings by Delegates.notNull()
private var oldIsFilterForStdlibAlreadyAdded: Boolean by Delegates.notNull()
private var oldDisableKotlinInternalClasses: Boolean by Delegates.notNull()
private var oldRenderDelegatedProperties: Boolean by Delegates.notNull()
override fun initApplication() {
super.initApplication()
@@ -53,6 +54,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
val kotlinSettings = KotlinDebuggerSettings.getInstance()
kotlinSettings.DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED = false
kotlinSettings.DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES = fileText.getValueForSetting("DISABLE_KOTLIN_INTERNAL_CLASSES", oldDisableKotlinInternalClasses)
kotlinSettings.DEBUG_RENDER_DELEGATED_PROPERTIES = fileText.getValueForSetting("RENDER_DELEGATED_PROPERTIES", oldRenderDelegatedProperties)
val debuggerSettings = DebuggerSettings.getInstance()!!
debuggerSettings.SKIP_SYNTHETIC_METHODS = fileText.getValueForSetting("SKIP_SYNTHETIC_METHODS", oldSettings.SKIP_SYNTHETIC_METHODS)
@@ -68,12 +70,14 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
private fun saveDefaultSettings() {
oldIsFilterForStdlibAlreadyAdded = KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED
oldDisableKotlinInternalClasses = KotlinDebuggerSettings.getInstance().DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES
oldRenderDelegatedProperties = KotlinDebuggerSettings.getInstance().DEBUG_RENDER_DELEGATED_PROPERTIES
oldSettings = DebuggerSettings.getInstance()!!.clone()
}
private fun restoreDefaultSettings() {
KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED = oldIsFilterForStdlibAlreadyAdded
KotlinDebuggerSettings.getInstance().DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES = oldDisableKotlinInternalClasses
KotlinDebuggerSettings.getInstance().DEBUG_RENDER_DELEGATED_PROPERTIES = oldRenderDelegatedProperties
val debuggerSettings = DebuggerSettings.getInstance()!!
debuggerSettings.SKIP_SYNTHETIC_METHODS = oldSettings.SKIP_SYNTHETIC_METHODS
@@ -399,6 +399,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doSingleBreakpointTest(fileName);
}
@TestMetadata("delegatedPropertyInClassWoRenderer.kt")
public void testDelegatedPropertyInClassWoRenderer() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClassWoRenderer.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");