From f6bb70aad18554a37c6772dd4bac240fb4a4af03 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Fri, 12 Aug 2016 10:33:30 +0300 Subject: [PATCH] Debugger: allow to evaluate 'this' and fields in java files --- .../evaluate/KotlinCodeFragmentFactory.kt | 38 +++++++++++-------- .../debugger/tinyApp/outs/jcProperty.out | 11 ++++++ .../javaContext/jcProperty.kt | 18 +++++++++ .../src/forTests/javaContext/JavaClass.java | 7 ++++ ...KotlinEvaluateExpressionTestGenerated.java | 6 +++ 5 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/jcProperty.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcProperty.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index 91896f84b6d..a06e205bee7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -47,6 +47,7 @@ import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.core.quoteIfNeeded import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor +import org.jetbrains.kotlin.idea.refactoring.j2k import org.jetbrains.kotlin.idea.refactoring.j2kText import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.executeWriteCommand @@ -127,33 +128,36 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { return@putCopyableUserData emptyFile } - // TODO: 'this' is unavailable - val visibleVariables = getVisibleLocalVariables(contextElement, debuggerContext) - if (visibleVariables == null) { - LOG.warn("Couldn't get a list of local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}") + val frameDescriptor = getFrameInfo(contextElement, debuggerContext) + if (frameDescriptor == null) { + LOG.warn("Couldn't get info about 'this' and local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}") return@putCopyableUserData emptyFile } - val fakeFunctionText = "fun _java_locals_debug_fun_() {\n" + - visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) + - "}" + val receiverTypeReference = frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference + val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: "" + + val kotlinVariablesText = frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) + + val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}" val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement) val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull() return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile + }) } return codeFragment } - private fun getVisibleLocalVariables(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): Map? { + private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? { val semaphore = Semaphore() semaphore.down() - var visibleVariables: Map? = null + var frameInfo: FrameInfo? = null val worker = object : DebuggerCommandImpl() { override fun action() { @@ -163,7 +167,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { else debuggerContext.frameProxy?.stackFrame - visibleVariables = frame?.let { it.getValues(it.visibleVariables()) } ?: emptyMap() + frameInfo = FrameInfo(frame?.thisObject(), frame?.let { it.getValues(it.visibleVariables()) } ?: emptyMap()) } catch(ignored: AbsentInformationException) { // Debug info unavailable @@ -180,9 +184,11 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { if (semaphore.waitFor(20)) break } - return visibleVariables + return frameInfo } + private class FrameInfo(val thisObject: Value?, val visibleVariables: Map) + private fun initImports(imports: String?): String? { if (imports != null && !imports.isEmpty()) { return imports.split(KtCodeFragment.IMPORT_SEPARATOR) @@ -334,7 +340,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { val kotlinProperty = createKotlinProperty(project, variableName, variableValue.type().name(), variableValue) ?: continue - sb.append("$kotlinProperty\n") + sb.append("${kotlinProperty.text}\n") } sb.append("val _debug_context_val = 1\n") @@ -342,11 +348,11 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { return sb.toString() } - private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): String? { + private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? { val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project)) if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) { val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable()) - return "val ${variableName.quoteIfNeeded()}: $renderedType = null" + return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false) } fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this @@ -359,7 +365,9 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { className).addArraySuffix() val field = PsiElementFactory.SERVICE.getInstance(project).createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project))) - return field.j2kText()?.substringAfter("private ") + val ktField = field.j2k() as? KtProperty + ktField?.modifierList?.delete() + return ktField } } diff --git a/idea/testData/debugger/tinyApp/outs/jcProperty.out b/idea/testData/debugger/tinyApp/outs/jcProperty.out new file mode 100644 index 00000000000..19732f262e2 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/jcProperty.out @@ -0,0 +1,11 @@ +LineBreakpoint created at jcProperty.kt:6 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! jcProperty.JcPropertyKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +jcProperty.kt:6 +JavaClass.java:47 +Compile bytecode for this.javaProperty +Compile bytecode for javaProperty +Compile bytecode for javaPrivateProperty +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/javaContext/jcProperty.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcProperty.kt new file mode 100644 index 00000000000..973f36cdaaa --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcProperty.kt @@ -0,0 +1,18 @@ +package jcProperty + +fun main(args: Array) { + val javaClass = forTests.javaContext.JavaClass() + //Breakpoint! + javaClass.property() +} + +// STEP_INTO: 1 + +// EXPRESSION: this.javaProperty +// RESULT: 1: I + +// EXPRESSION: javaProperty +// RESULT: 1: I + +// EXPRESSION: javaPrivateProperty +// RESULT: 1: I \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java b/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java index 110a98dcfe9..4d36b3a827d 100644 --- a/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java +++ b/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java @@ -39,4 +39,11 @@ public class JavaClass { Integer i = 1; int breakpoint = 1; } + + public int javaProperty = 1; + private int javaPrivateProperty = 1; + + public void property() { + int breakpoint = 1; + } } \ No newline at end of file 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 5c896f74be8..da5dcfe0ce9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -729,6 +729,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doSingleBreakpointTest(fileName); } + @TestMetadata("jcProperty.kt") + public void testJcProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcProperty.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("jcSimple.kt") public void testJcSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt");