diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt index 8b9683f80ad..a407b3e4917 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt @@ -16,16 +16,17 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints -import com.intellij.CommonBundle import com.intellij.debugger.DebuggerBundle import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.DebugProcessImpl +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.impl.PositionUtil +import com.intellij.debugger.requests.Requestor import com.intellij.debugger.ui.breakpoints.* -import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages -import com.intellij.openapi.util.Ref import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope @@ -33,16 +34,22 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.XDebuggerUtil import com.intellij.xdebugger.breakpoints.XBreakpoint -import com.intellij.xdebugger.breakpoints.XBreakpointProperties import com.intellij.xdebugger.breakpoints.XLineBreakpoint import com.intellij.xdebugger.breakpoints.XLineBreakpointType import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider +import com.sun.jdi.Method +import com.sun.jdi.ObjectCollectedException +import com.sun.jdi.ReferenceType +import com.sun.jdi.event.* +import com.sun.jdi.request.EventRequest +import com.sun.jdi.request.MethodEntryRequest import org.jetbrains.annotations.TestOnly import org.jetbrains.java.debugger.breakpoints.properties.JavaFieldBreakpointProperties import org.jetbrains.kotlin.asJava.KotlinLightClass import org.jetbrains.kotlin.asJava.KotlinLightClassForExplicitDeclaration import org.jetbrains.kotlin.asJava.KotlinLightClassForPackage +import org.jetbrains.kotlin.codegen.PropertyCodegen import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.idea.JetBundle @@ -51,6 +58,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.load.kotlin.PackageClassUtils +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import javax.swing.Icon @@ -97,7 +105,7 @@ public class KotlinFieldBreakpointType : JavaBreakpointType): FieldBreakpoint(project, breakpoint) { +class KotlinFieldBreakpoint(project: Project, breakpoint: XBreakpoint) : FieldBreakpoint(project, breakpoint) { + companion object { + private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint") + } + + private enum class BreakpointType { + FIELD, + METHOD + } + + private val breakpointType: BreakpointType + + init { + val element = getField()!! + val bindingContext = element.analyzeAndGetResult().bindingContext + var descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element) + if (descriptor is ValueParameterDescriptor) { + descriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor) + } + + breakpointType = if (bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor as PropertyDescriptor)) { + BreakpointType.FIELD + } + else { + BreakpointType.METHOD + } + } + override fun isValid(): Boolean { if (!BreakpointWithHighlighter.isPositionValid(getXBreakpoint().getSourcePosition())) return false @@ -287,6 +322,115 @@ class KotlinFieldBreakpoint(project: Project, breakpoint: XBreakpoint { + val field = refType.fieldByName(getFieldName()) + if (field != null) { + val manager = debugProcess.getRequestsManager() + if (getProperties().WATCH_MODIFICATION && vm.canWatchFieldModification()) { + val request = manager.createModificationWatchpointRequest(this, field) + debugProcess.getRequestsManager().enableRequest(request) + if (LOG.isDebugEnabled()) { + LOG.debug("Modification request added") + } + } + if (getProperties().WATCH_ACCESS && vm.canWatchFieldAccess()) { + val request = manager.createAccessWatchpointRequest(this, field) + debugProcess.getRequestsManager().enableRequest(request) + if (LOG.isDebugEnabled()) { + LOG.debug("Field access request added (field = ${field.name()}; refType = ${refType.name()})") + } + } + } + } + BreakpointType.METHOD -> { + val propertyName = Name.identifier(getFieldName()) + + if (getProperties().WATCH_ACCESS) { + val getter = refType.methodsByName(PropertyCodegen.getterName(propertyName)).firstOrNull() + if (getter != null) { + createMethodBreakpoint(debugProcess, refType, getter) + } + } + + if (getProperties().WATCH_MODIFICATION) { + val setter = refType.methodsByName(PropertyCodegen.setterName(propertyName)).firstOrNull() + if (setter != null) { + createMethodBreakpoint(debugProcess, refType, setter) + } + } + } + } + } + catch (ex: Exception) { + LOG.debug(ex) + } + } + + private fun createMethodBreakpoint(debugProcess: DebugProcessImpl, refType: ReferenceType, accessor: Method) { + val manager = debugProcess.getRequestsManager() + val line = accessor.allLineLocations().firstOrNull() + if (line != null) { + val request = manager.createBreakpointRequest(this, line) + debugProcess.getRequestsManager().enableRequest(request) + if (LOG.isDebugEnabled()) { + LOG.debug("Breakpoint request added") + } + } + else { + var entryRequest: MethodEntryRequest? = findRequest(debugProcess, javaClass(), this) + if (entryRequest == null) { + entryRequest = manager.createMethodEntryRequest(this)!! + if (LOG.isDebugEnabled()) { + LOG.debug("Method entry request added (method = ${accessor.name()}; refType = ${refType.name()})") + } + } + else { + entryRequest.disable() + } + entryRequest.addClassFilter(refType) + manager.enableRequest(entryRequest) + } + } + + inline fun findRequest(debugProcess: DebugProcessImpl, requestClass: Class, requestor: Requestor): T? { + val requests = debugProcess.getRequestsManager().findRequests(requestor) + for (eventRequest in requests) { + if (eventRequest.javaClass == requestClass) { + return eventRequest as T + } + } + return null + } + + override fun evaluateCondition(context: EvaluationContextImpl, event: LocatableEvent): Boolean { + if (breakpointType == BreakpointType.METHOD && !matchesEvent(event)) { + return false + } + return super.evaluateCondition(context, event) + } + + public fun matchesEvent(event: LocatableEvent): Boolean { + val method = event.location().method() + // TODO check property type + return method != null && method.name() in getMethodsName() + } + + private fun getMethodsName(): List { + val propertyName = Name.identifier(getFieldName()) + return arrayListOf(PropertyCodegen.getterName(propertyName), PropertyCodegen.setterName(propertyName)) + + } + + override fun getEventMessage(event: LocatableEvent?): String? { + return super.getEventMessage(event) + } + fun setFieldName(fieldName: String) { getProperties().myFieldName = fieldName } diff --git a/idea/testData/debugger/tinyApp/outs/fwAbstractProperty.out b/idea/testData/debugger/tinyApp/outs/fwAbstractProperty.out new file mode 100644 index 00000000000..a78f3821aa2 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/fwAbstractProperty.out @@ -0,0 +1,26 @@ +KotlinFieldBreakpoint created at fwAbstractProperty.kt:5 +KotlinFieldBreakpoint created at fwAbstractProperty.kt:8 +KotlinFieldBreakpoint created at fwAbstractProperty.kt:42 +KotlinFieldBreakpoint created at fwAbstractProperty.kt:45 +!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! fwAbstractProperty.FwAbstractPropertyPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +fwAbstractProperty.kt:57 +fwAbstractProperty.kt:59 +fwAbstractProperty.kt:61 +fwAbstractProperty.kt:19 +fwAbstractProperty.kt:20 +fwAbstractProperty.kt:20 +fwAbstractProperty.kt:19 +fwAbstractProperty.kt:20 +fwAbstractProperty.kt:20 +fwAbstractProperty.kt:43 +fwAbstractProperty.kt:46 +fwAbstractProperty.kt:43 +fwAbstractProperty.kt:46 +fwAbstractProperty.kt:46 +fwAbstractProperty.kt:43 +fwAbstractProperty.kt:46 +fwAbstractProperty.kt:46 +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/fwPropertyInInterface.out b/idea/testData/debugger/tinyApp/outs/fwPropertyInInterface.out new file mode 100644 index 00000000000..362b7a7702c --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/fwPropertyInInterface.out @@ -0,0 +1,26 @@ +KotlinFieldBreakpoint created at fwPropertyInInterface.kt:5 +KotlinFieldBreakpoint created at fwPropertyInInterface.kt:8 +KotlinFieldBreakpoint created at fwPropertyInInterface.kt:42 +KotlinFieldBreakpoint created at fwPropertyInInterface.kt:45 +!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! fwPropertyInInterface.FwPropertyInInterfacePackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +fwPropertyInInterface.kt:57 +fwPropertyInInterface.kt:59 +fwPropertyInInterface.kt:61 +fwPropertyInInterface.kt:19 +fwPropertyInInterface.kt:20 +fwPropertyInInterface.kt:20 +fwPropertyInInterface.kt:19 +fwPropertyInInterface.kt:20 +fwPropertyInInterface.kt:20 +fwPropertyInInterface.kt:43 +fwPropertyInInterface.kt:46 +fwPropertyInInterface.kt:43 +fwPropertyInInterface.kt:46 +fwPropertyInInterface.kt:46 +fwPropertyInInterface.kt:43 +fwPropertyInInterface.kt:46 +fwPropertyInInterface.kt:46 +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/fwAbstractProperty.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/fwAbstractProperty.kt new file mode 100644 index 00000000000..d524d1b9e58 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/fwAbstractProperty.kt @@ -0,0 +1,83 @@ +package fwAbstractProperty + +// Breakpoint at getter/setter +abstract class MyAbstractClass { + //FieldWatchpoint! (propVal) + abstract val propVal: Int + + //FieldWatchpoint! (propVar) + abstract var propVar: Int + + fun testPropertyInAbstractClass() { + propVal + propVar + propVar = 2 + } +} + +class MyAbstractClassImpl : MyAbstractClass() { + override val propVal = 1 + override var propVar = 1 + + fun testPropertyInAbstractClassImpl() { + propVal + propVar + propVar = 2 + } +} + +abstract class MyAbstractClassWithoutBreakpoints { + abstract val propVal2: Int + abstract var propVar2: Int + + fun testPropertyInAbstractClass() { + propVal2 + propVar2 + propVar2 = 2 + } +} + +// Breakpoint at GETFILED/PUTFIELD +class MyAbstractClassImplWithBreakpoints : MyAbstractClassWithoutBreakpoints() { + //FieldWatchpoint! (propVal2) + override val propVal2 = 1 + + //FieldWatchpoint! (propVar2) + override var propVar2 = 1 + + fun testPropertyInAbstractClassImpl() { + propVal2 + propVar2 + propVar2 = 2 + } +} + +fun main(args: Array) { + val mac = object: MyAbstractClass() { + override val propVal: Int get() = 1 + override var propVar: Int + get() = 1 + set(value) { + } + } + mac.testPropertyInAbstractClass() + + val maci = MyAbstractClassImpl() + maci.testPropertyInAbstractClass() + maci.testPropertyInAbstractClassImpl() + + val macwb = object: MyAbstractClassWithoutBreakpoints() { + override val propVal2: Int get() = 1 + override var propVar2: Int + get() = 1 + set(value) { + } + } + macwb.testPropertyInAbstractClass() + + val macwbi = MyAbstractClassImplWithBreakpoints() + macwbi.testPropertyInAbstractClass() + macwbi.testPropertyInAbstractClassImpl() +} + +// RESUME: 17 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/fwPropertyInInterface.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/fwPropertyInInterface.kt new file mode 100644 index 00000000000..d05a86c0245 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/fwPropertyInInterface.kt @@ -0,0 +1,83 @@ +package fwPropertyInInterface + +// Breakpoint at getter/setter +interface MyInterface { + //FieldWatchpoint! (propVal) + val propVal: Int + + //FieldWatchpoint! (propVar) + var propVar: Int + + fun testPropertyInInterface() { + propVal + propVar + propVar = 2 + } +} + +class MyInterfaceImpl : MyInterface { + override val propVal = 1 + override var propVar = 1 + + fun testPropertyInInterfaceImpl() { + propVal + propVar + propVar = 2 + } +} + +interface MyInterfaceWithoutBreakpoints { + val propVal2: Int + var propVar2: Int + + fun testPropertyInInterface() { + propVal2 + propVar2 + propVar2 = 2 + } +} + +// Breakpoint at GETFILED/PUTFIELD +class MyInterfaceImplWithBreakpoints : MyInterfaceWithoutBreakpoints { + //FieldWatchpoint! (propVal2) + override val propVal2 = 1 + + //FieldWatchpoint! (propVar2) + override var propVar2 = 1 + + fun testPropertyInInterfaceImpl() { + propVal2 + propVar2 + propVar2 = 2 + } +} + +fun main(args: Array) { + val mac = object: MyInterface { + override val propVal: Int get() = 1 + override var propVar: Int + get() = 1 + set(value) { + } + } + mac.testPropertyInInterface() + + val maci = MyInterfaceImpl() + maci.testPropertyInInterface() + maci.testPropertyInInterfaceImpl() + + val macwb = object: MyInterfaceWithoutBreakpoints { + override val propVal2: Int get() = 1 + override var propVar2: Int + get() = 1 + set(value) { + } + } + macwb.testPropertyInInterface() + + val macwbi = MyInterfaceImplWithBreakpoints() + macwbi.testPropertyInInterface() + macwbi.testPropertyInInterfaceImpl() +} + +// RESUME: 17 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOut/inapplicableFieldWatchpoints.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOut/inapplicableFieldWatchpoints.kt index 8111ecab697..cb23c7cad64 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/stepOut/inapplicableFieldWatchpoints.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOut/inapplicableFieldWatchpoints.kt @@ -1,15 +1,5 @@ package inapplicableFieldWatchpoints -class A { - //FieldWatchpoint! (propWithGet) - val propWithGet: Int get() = 1 -} - -interface T { - //FieldWatchpoint! (propInInterface) - val propInInterface: Int -} - fun main(args: Array) { //FieldWatchpoint! (localVal) val localVal = 1 diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index 8cbd776fddb..a8f1f78371c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -382,6 +382,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/stepping/custom"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("fwAbstractProperty.kt") + public void testFwAbstractProperty() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/fwAbstractProperty.kt"); + doCustomTest(fileName); + } + + @TestMetadata("fwPropertyInInterface.kt") + public void testFwPropertyInInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/fwPropertyInInterface.kt"); + doCustomTest(fileName); + } + @TestMetadata("stepIntoStdlibInlineFun2step.kt") public void testStepIntoStdlibInlineFun2step() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");