Debugger: support field watchpoints for kotlin

This commit is contained in:
Natalia Ukhorskaya
2015-05-20 15:13:28 +03:00
parent 7406627e26
commit 2b015ae09a
10 changed files with 493 additions and 0 deletions
@@ -338,3 +338,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)
debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints
+2
View File
@@ -406,7 +406,9 @@
<debugger.frameExtraVarsProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinFrameExtraVariablesProvider"/>
<debugger.extraSteppingFilter implementation="org.jetbrains.kotlin.idea.ExtraSteppingFilter"/>
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinBreakpointHandlerFactory"/>
<codeInsight.implementMethod language="jet" implementationClass="org.jetbrains.kotlin.idea.core.codeInsight.ImplementMethodsHandler"/>
<codeInsight.overrideMethod language="jet" implementationClass="org.jetbrains.kotlin.idea.codeInsight.OverrideMethodsHandler"/>
@@ -0,0 +1,30 @@
/*
* 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.breakpoints
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaBreakpointHandler
import com.intellij.debugger.engine.JavaBreakpointHandlerFactory
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
public class KotlinBreakpointHandlerFactory: JavaBreakpointHandlerFactory {
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
return KotlinFieldBreakpointHandler(process)
}
}
public class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(javaClass<KotlinFieldBreakpointType>(), process)
@@ -0,0 +1,224 @@
/*
* 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.breakpoints
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.impl.PositionUtil
import com.intellij.debugger.ui.breakpoints.*
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.breakpoints.XBreakpoint
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 org.jetbrains.annotations.TestOnly
import org.jetbrains.java.debugger.breakpoints.properties.JavaFieldBreakpointProperties
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import javax.swing.Icon
import javax.swing.JComponent
public class KotlinFieldBreakpointType : JavaBreakpointType<JavaFieldBreakpointProperties>, XLineBreakpointType<JavaFieldBreakpointProperties>(
"kotlin-field", JetBundle.message("debugger.field.watchpoints.tab.title")
) {
private val delegate = JavaFieldBreakpointType()
override fun createJavaBreakpoint(project: Project, breakpoint: XBreakpoint<JavaFieldBreakpointProperties>): Breakpoint<*> {
return KotlinFieldBreakpoint(project, breakpoint)
}
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
val psiFile = PsiManager.getInstance(project).findFile(file)
if (psiFile == null || psiFile.getVirtualFile().getFileType() != JetFileType.INSTANCE) {
return false
}
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
var canPutAt = false
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean {
// avoid comments
if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, javaClass<PsiComment>(), false) != null) {
return true
}
var element = el
var parent = element.getParent()
while (parent != null) {
val offset = parent.getTextOffset()
if (offset >= 0 && document.getLineNumber(offset) != line) break
element = parent
parent = element.getParent()
}
if (element is JetProperty || element is JetParameter) {
val bindingContext = (element as JetElement).analyzeAndGetResult().bindingContext
var descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
if (descriptor is ValueParameterDescriptor) {
descriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor)
}
if (descriptor is PropertyDescriptor && bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor)) {
canPutAt = true
}
return false
}
return true
})
return canPutAt
}
override fun getPriority() = 120
override fun createBreakpointProperties(file: VirtualFile, line: Int): JavaFieldBreakpointProperties? {
return delegate.createBreakpointProperties(file, line)
}
override fun addBreakpoint(project: Project?, parentComponent: JComponent?): XLineBreakpoint<JavaFieldBreakpointProperties>? {
return delegate.addBreakpoint(project, parentComponent)
}
override fun isAddBreakpointButtonVisible(): Boolean {
return delegate.isAddBreakpointButtonVisible()
}
override fun getMutedEnabledIcon(): Icon {
return delegate.getMutedEnabledIcon()
}
override fun getDisabledIcon(): Icon {
return delegate.getDisabledIcon()
}
override fun getEnabledIcon(): Icon {
return delegate.getEnabledIcon()
}
override fun getMutedDisabledIcon(): Icon {
return delegate.getMutedDisabledIcon()
}
override fun canBeHitInOtherPlaces(): Boolean {
return delegate.canBeHitInOtherPlaces()
}
override fun getShortText(breakpoint: XLineBreakpoint<JavaFieldBreakpointProperties>?): String? {
return delegate.getShortText(breakpoint)
}
override fun createProperties(): JavaFieldBreakpointProperties? {
return delegate.createProperties()
}
override fun createCustomPropertiesPanel(): XBreakpointCustomPropertiesPanel<XLineBreakpoint<JavaFieldBreakpointProperties>>? {
return delegate.createCustomPropertiesPanel()
}
override fun getDisplayText(breakpoint: XLineBreakpoint<JavaFieldBreakpointProperties>?): String? {
return delegate.getDisplayText(breakpoint)
}
override fun getEditorsProvider(): XDebuggerEditorsProvider? {
return delegate.getEditorsProvider()
}
override fun createCustomRightPropertiesPanel(project: Project): XBreakpointCustomPropertiesPanel<XLineBreakpoint<JavaFieldBreakpointProperties>>? {
return delegate.createCustomRightPropertiesPanel(project)
}
override fun isSuspendThreadSupported(): Boolean {
return delegate.isSuspendThreadSupported()
}
}
class KotlinFieldBreakpoint(project: Project, breakpoint: XBreakpoint<JavaFieldBreakpointProperties>): FieldBreakpoint(project, breakpoint) {
override fun isValid(): Boolean {
if (!BreakpointWithHighlighter.isPositionValid(getXBreakpoint().getSourcePosition())) return false
return runReadAction {
val field = getField()
field != null && field.isValid()
}
}
public fun getField(): JetCallableDeclaration? {
val sourcePosition = getSourcePosition()
return getProperty(sourcePosition)
}
private fun getProperty(sourcePosition: SourcePosition?): JetCallableDeclaration? {
val property: JetProperty? = PositionUtil.getPsiElementAt(getProject(), javaClass(), sourcePosition)
if (property != null) {
return property
}
val parameter: JetParameter? = PositionUtil.getPsiElementAt(getProject(), javaClass(), sourcePosition)
if (parameter != null) {
return parameter
}
return null
}
override fun reload(psiFile: PsiFile?) {
val property = getProperty(getSourcePosition())
if (property != null) {
setFieldName(property.getName())
if (property is JetProperty && property.isTopLevel()) {
getProperties().myClassName = PackageClassUtils.getPackageClassFqName(property.getContainingJetFile().getPackageFqName()).asString()
}
else {
val jetClass: JetClassOrObject? = PsiTreeUtil.getParentOfType(property, javaClass())
if (jetClass is JetClassOrObject) {
val fqName = jetClass.getFqName()
if (fqName != null) {
getProperties().myClassName = fqName.asString()
}
}
}
setInstanceFiltersEnabled(false)
}
}
fun setFieldName(fieldName: String) {
getProperties().myFieldName = fieldName
}
@TestOnly
fun setWatchAccess(value: Boolean) {
getProperties().WATCH_ACCESS = value
}
@TestOnly
fun setWatchModification(value: Boolean) {
getProperties().WATCH_MODIFICATION = value
}
}
@@ -0,0 +1,44 @@
KotlinFieldBreakpoint created at fieldWatchpoints.kt:4
KotlinFieldBreakpoint created at fieldWatchpoints.kt:7
KotlinFieldBreakpoint created at fieldWatchpoints.kt:17
KotlinFieldBreakpoint created at fieldWatchpoints.kt:20
KotlinFieldBreakpoint created at fieldWatchpoints.kt:30
KotlinFieldBreakpoint created at fieldWatchpoints.kt:32
KotlinFieldBreakpoint created at fieldWatchpoints.kt:44
KotlinFieldBreakpoint created at fieldWatchpoints.kt:47
!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! fieldWatchpoints.FieldWatchpointsPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
fieldWatchpoints.kt:18
fieldWatchpoints.kt:21
fieldWatchpoints.kt:59
fieldWatchpoints.kt:5
fieldWatchpoints.kt:8
fieldWatchpoints.kt:59
fieldWatchpoints.kt:11
fieldWatchpoints.kt:12
fieldWatchpoints.kt:13
fieldWatchpoints.kt:60
fieldWatchpoints.kt:24
fieldWatchpoints.kt:25
fieldWatchpoints.kt:26
fieldWatchpoints.kt:61
fieldWatchpoints.kt:0
fieldWatchpoints.kt:0
fieldWatchpoints.kt:61
fieldWatchpoints.kt:36
fieldWatchpoints.kt:37
fieldWatchpoints.kt:38
fieldWatchpoints.kt:62
fieldWatchpoints.kt:45
fieldWatchpoints.kt:48
fieldWatchpoints.kt:62
fieldWatchpoints.kt:44
fieldWatchpoints.kt:51
fieldWatchpoints.kt:47
fieldWatchpoints.kt:52
fieldWatchpoints.kt:47
fieldWatchpoints.kt:54
fieldWatchpoints.kt:63
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,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! inapplicableFieldWatchpoints.InapplicableFieldWatchpointsPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,65 @@
package fieldWatchpoints
class A {
//FieldWatchpoint! (propVal)
val propVal = 1
//FieldWatchpoint! (propVar)
var propVar = 1
fun testPublicPropertyInClass() {
propVal
propVar
propVar = 2
}
}
//FieldWatchpoint! (topPropVal)
val topPropVal = 1
//FieldWatchpoint! (topPropVar)
var topPropVar = 1
fun testPublicTopLevelProperty() {
topPropVal
topPropVar
topPropVar = 2
}
class B(
//FieldWatchpoint! (bPropVal)
val bPropVal: Int,
//FieldWatchpoint! (bPropVar)
var bPropVar: Int
) {
fun testConstructorProperty() {
bPropVal
bPropVar
bPropVar = 2
}
}
class AWithCompanion {
companion object {
//FieldWatchpoint! (compPropVal)
val compPropVal = 1
//FieldWatchpoint! (compPropVar)
var compPropVar = 1
fun testCompanionProperty() {
compPropVal
compPropVar
compPropVar = 2
}
}
}
fun main(args: Array<String>) {
A().testPublicPropertyInClass()
testPublicTopLevelProperty()
B(1, 1).testConstructorProperty()
AWithCompanion.testCompanionProperty()
}
// STEP_OUT: 36
@@ -0,0 +1,23 @@
package inapplicableFieldWatchpoints
class A {
//FieldWatchpoint! (propWithGet)
val propWithGet: Int get() = 1
}
interface T {
//FieldWatchpoint! (propInInterface)
val propInInterface: Int
}
fun main(args: Array<String>) {
//FieldWatchpoint! (localVal)
val localVal = 1
}
fun foo(
//FieldWatchpoint! (funParam)
funParam: Int
) {
}
@@ -16,6 +16,8 @@
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.MethodFilter
@@ -33,6 +35,28 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
import kotlin.properties.Delegates
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.ui.breakpoints.BreakpointManager
import com.intellij.debugger.ui.breakpoints.FieldBreakpoint
import com.intellij.debugger.ui.breakpoints.JavaFieldBreakpointType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.editor.Document
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.Computable
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.search.FilenameIndex
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.breakpoints.*
import org.jetbrains.java.debugger.breakpoints.properties.JavaFieldBreakpointProperties
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import javax.swing.SwingUtilities
abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
private var oldSettings: DebuggerSettings by Delegates.notNull()
@@ -154,4 +178,58 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
resume(this)
}
}
override fun createBreakpoints(file: PsiFile?) {
super.createBreakpoints(file)
if (file == null) return
val document = PsiDocumentManager.getInstance(myProject).getDocument(file) ?: return
val breakpointManager = XDebuggerManager.getInstance(myProject).getBreakpointManager()
val breakpointType = javaClass<KotlinFieldBreakpointType>() as Class<out XBreakpointType<XBreakpoint<XBreakpointProperties<*>>, XBreakpointProperties<*>>>
val type = XDebuggerUtil.getInstance().findBreakpointType<XBreakpoint<XBreakpointProperties<*>>>(breakpointType) as KotlinFieldBreakpointType
val virtualFile = file.getVirtualFile()
val runnable = {
var offset = -1;
while (true) {
offset = document.getText().indexOf("FieldWatchpoint!", offset + 1)
if (offset == -1) break
val commentLine = document.getLineNumber(offset)
val comment = document.getText().substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine))
val lineIndex = commentLine + 1
val fieldName = comment.substringAfter("//FieldWatchpoint! (").substringBefore(")")
if (!type.canPutAt(virtualFile, lineIndex, myProject)) continue
val xBreakpoint = runWriteAction {
breakpointManager.addLineBreakpoint(
type as XLineBreakpointType<XBreakpointProperties<*>>,
virtualFile.getUrl(),
lineIndex,
type.createBreakpointProperties(virtualFile, lineIndex)
)
}
val javaBreakpoint = BreakpointManager.getJavaBreakpoint(xBreakpoint)
if (javaBreakpoint is KotlinFieldBreakpoint) {
javaBreakpoint.setFieldName(fieldName)
javaBreakpoint.setWatchAccess(true)
javaBreakpoint.setWatchModification(true)
BreakpointManager.addBreakpoint(javaBreakpoint)
println("KotlinFieldBreakpoint created at ${file.getVirtualFile().getName()}:$lineIndex", ProcessOutputTypes.SYSTEM)
}
}
}
if (!SwingUtilities.isEventDispatchThread()) {
DebuggerInvocationUtil.invokeAndWait(myProject, runnable, ModalityState.defaultModalityState())
}
else {
runnable.invoke()
}
}
}
@@ -272,6 +272,27 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
}
}
@TestMetadata("idea/testData/debugger/tinyApp/src/stepOut")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StepOut extends AbstractKotlinSteppingTest {
public void testAllFilesPresentInStepOut() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/stepOut"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("fieldWatchpoints.kt")
public void testFieldWatchpoints() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepOut/fieldWatchpoints.kt");
doStepOutTest(fileName);
}
@TestMetadata("inapplicableFieldWatchpoints.kt")
public void testInapplicableFieldWatchpoints() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepOut/inapplicableFieldWatchpoints.kt");
doStepOutTest(fileName);
}
}
@TestMetadata("idea/testData/debugger/tinyApp/src/filters")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)