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 08a9e93615e..742a90352da 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -24,18 +24,20 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key -import com.intellij.psi.JavaCodeFragment -import com.intellij.psi.PsiCodeBlock -import com.intellij.psi.PsiElement +import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.concurrency.Semaphore import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.impl.XDebugSessionImpl import com.intellij.xdebugger.impl.ui.tree.ValueMarkup +import com.sun.jdi.ArrayReference import com.sun.jdi.ObjectReference +import com.sun.jdi.PrimitiveValue import com.sun.jdi.Value import org.jetbrains.kotlin.asJava.KotlinLightClass import org.jetbrains.kotlin.idea.JetFileType +import org.jetbrains.kotlin.idea.core.refactoring.j2kText import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* @@ -150,24 +152,53 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { } //internal for tests - fun createCodeFragmentForLabeledObjects(markupMap: Map<*, ValueMarkup>): Pair> { + fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair> { val sb = StringBuilder() - val labeledObjects = HashMap() + val labeledObjects = HashMap() for ((value, markup) in markupMap.entrySet()) { val labelName = markup.text if (!Name.isValidIdentifier(labelName)) continue - val objectRef = value as ObjectReference + val objectRef = value as? Value ?: continue - val typeName = value.type().name() - val labelNameWithSuffix = labelName + DEBUG_LABEL_SUFFIX - sb.append("val ").append(labelNameWithSuffix).append(": ").append(typeName).append("? = null\n") + val labelNameWithSuffix = "$labelName$DEBUG_LABEL_SUFFIX" + sb.append("${createKotlinProperty(project, labelNameWithSuffix, objectRef.type().name(), TypeKind.getTypeKind(objectRef))}\n") labeledObjects.put(labelNameWithSuffix, objectRef) } sb.append("val _debug_context_val = 1") return sb.toString() to labeledObjects } + + private enum class TypeKind { + PRIMITIVE, + ARRAY, + OBJECT; + + companion object { + fun getTypeKind(value: Value): TypeKind { + return when(value) { + is ArrayReference -> ARRAY + is PrimitiveValue -> PRIMITIVE + else -> OBJECT + } + } + } + } + + private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, typeKind: TypeKind): String? { + fun String.addArraySuffix() = if (typeKind == TypeKind.ARRAY) this + "[]" else this + + val className = variableTypeName.replace("$", ".").substringBefore("[]") + val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project)) + val type = (if (typeKind != TypeKind.PRIMITIVE && classType.resolve() == null) + CommonClassNames.JAVA_LANG_OBJECT + else + className).addArraySuffix() + + val field = PsiElementFactory.SERVICE.getInstance(project).createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project))) + return field.j2kText()?.substringAfter("private ") + } } private fun wrapContextIfNeeded(project: Project, originalContext: JetElement?): JetElement? { @@ -177,7 +208,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { val markupMap = session.valueMarkers?.getAllMarkers() if (markupMap == null || markupMap.isEmpty()) return originalContext - val (text, labels) = createCodeFragmentForLabeledObjects(markupMap) + val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap) if (text.isEmpty()) return originalContext return createWrappingContext(text, labels, originalContext, project) @@ -186,7 +217,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { // internal for test fun createWrappingContext( newFragmentText: String, - labels: Map, + labels: Map, originalContext: PsiElement?, project: Project ): JetElement? { diff --git a/idea/testData/debugger/tinyApp/outs/ldifferentTypes.out b/idea/testData/debugger/tinyApp/outs/ldifferentTypes.out new file mode 100644 index 00000000000..2eab5499854 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/ldifferentTypes.out @@ -0,0 +1,27 @@ +LineBreakpoint created at ldifferentTypes.kt:92 +!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! ldifferentTypes.LdifferentTypesPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +ldifferentTypes.kt:92 +Compile bytecode for str_DebugLabel +Compile bytecode for strArray_DebugLabel +Compile bytecode for primitive_DebugLabel +Compile bytecode for primitiveArray_DebugLabel +Compile bytecode for localObj_DebugLabel +Compile bytecode for localObjArray_DebugLabel +Compile bytecode for localClass_DebugLabel +Compile bytecode for localClassArray_DebugLabel +Compile bytecode for aClass_DebugLabel +Compile bytecode for aClass_DebugLabel.test() +Compile bytecode for aClassArray_DebugLabel +Compile bytecode for aClassArray_DebugLabel[0].test() +Compile bytecode for innerClass_DebugLabel +Compile bytecode for innerClass_DebugLabel.test() +Compile bytecode for innerClassArray_DebugLabel +Compile bytecode for innerClassArray_DebugLabel[0].test() +Compile bytecode for nestedClass_DebugLabel +Compile bytecode for nestedClass_DebugLabel.test() +Compile bytecode for nestedClassArray_DebugLabel +Compile bytecode for nestedClassArray_DebugLabel[0].test() +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/labels/ldifferentTypes.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/ldifferentTypes.kt new file mode 100644 index 00000000000..8bf2ac50aab --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/ldifferentTypes.kt @@ -0,0 +1,105 @@ +package ldifferentTypes + +fun main(args: Array) { + // EXPRESSION: str_DebugLabel + // RESULT: "str": Ljava/lang/String; + // DEBUG_LABEL: str = str + val str = "str" + // EXPRESSION: strArray_DebugLabel + // RESULT: instance of java.lang.String[1] (id=ID): [Ljava/lang/String; + // DEBUG_LABEL: strArray = strArray + val strArray = arrayOf("str") + + // EXPRESSION: primitive_DebugLabel + // RESULT: 1: I + // DEBUG_LABEL: primitive = primitive + val primitive = 1 + // EXPRESSION: primitiveArray_DebugLabel + // RESULT: instance of java.lang.Integer[1] (id=ID): [Ljava/lang/Integer; + // DEBUG_LABEL: primitiveArray = primitiveArray + val primitiveArray = arrayOf(1) + + // EXPRESSION: localObj_DebugLabel + // RESULT: instance of ldifferentTypes.LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localObj$1(id=ID): LldifferentTypes/LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localObj$1; + // EXPRESSION: localObj_DebugLabel.test() + // RESULT: Unresolved reference: test + // DEBUG_LABEL: localObj = localObj + val localObj = object { fun test() = 1 } + // EXPRESSION: localObjArray_DebugLabel + // RESULT: instance of ldifferentTypes.LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localObj$1[1] (id=ID): [LldifferentTypes/LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localObj$1; + // EXPRESSION: localObjArray_DebugLabel[0].test() + // RESULT: Unresolved reference: test + // DEBUG_LABEL: localObjArray = localObjArray + val localObjArray = arrayOf(localObj) + + class LocalClass { + fun test() = 1 + } + + // EXPRESSION: localClass_DebugLabel + // RESULT: instance of ldifferentTypes.LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localClass$1(id=ID): LldifferentTypes/LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localClass$1; + // EXPRESSION: localClass_DebugLabel.test() + // RESULT: Unresolved reference: test + // DEBUG_LABEL: localClass = localClass + val localClass = object { fun test() = 1 } + // EXPRESSION: localClassArray_DebugLabel + // RESULT: instance of ldifferentTypes.LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localClass$1[1] (id=ID): [LldifferentTypes/LdifferentTypesPackage$ldifferentTypes$@packagePartHASH$main$localClass$1; + // EXPRESSION: localClassArray_DebugLabel[0].test() + // RESULT: Unresolved reference: test + // DEBUG_LABEL: localClassArray = localClassArray + val localClassArray = arrayOf(localClass) + + // EXPRESSION: aClass_DebugLabel + // RESULT: instance of ldifferentTypes.A(id=ID): LldifferentTypes/A; + // EXPRESSION: aClass_DebugLabel.test() + // RESULT: 1: I + // DEBUG_LABEL: aClass = aClass + val aClass = A() + // EXPRESSION: aClassArray_DebugLabel + // RESULT: instance of ldifferentTypes.A[1] (id=ID): [LldifferentTypes/A; + // EXPRESSION: aClassArray_DebugLabel[0].test() + // RESULT: 1: I + // DEBUG_LABEL: aClassArray = aClassArray + val aClassArray = arrayOf(aClass) + + // EXPRESSION: innerClass_DebugLabel + // RESULT: instance of ldifferentTypes.A$B(id=ID): LldifferentTypes/A$B; + // EXPRESSION: innerClass_DebugLabel.test() + // RESULT: 1: I + // DEBUG_LABEL: innerClass = innerClass + val innerClass = A().B() + // EXPRESSION: innerClassArray_DebugLabel + // RESULT: instance of ldifferentTypes.A$B[1] (id=ID): [LldifferentTypes/A$B; + // EXPRESSION: innerClassArray_DebugLabel[0].test() + // RESULT: 1: I + // DEBUG_LABEL: innerClassArray = innerClassArray + val innerClassArray = arrayOf(innerClass) + + // EXPRESSION: nestedClass_DebugLabel + // RESULT: instance of ldifferentTypes.A$C(id=ID): LldifferentTypes/A$C; + // EXPRESSION: nestedClass_DebugLabel.test() + // RESULT: 1: I + // DEBUG_LABEL: nestedClass = nestedClass + val nestedClass = A.C() + // EXPRESSION: nestedClassArray_DebugLabel + // RESULT: instance of ldifferentTypes.A$C[1] (id=ID): [LldifferentTypes/A$C; + // EXPRESSION: nestedClassArray_DebugLabel[0].test() + // RESULT: 1: I + // DEBUG_LABEL: nestedClassArray = nestedClassArray + val nestedClassArray = arrayOf(nestedClass) + + //Breakpoint! + val b = 1 +} + +class A { + inner class B { + fun test() = 1 + } + + class C { + fun test() = 1 + } + + fun test() = 1 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt index 3b9a0dd6556..4c439c52a65 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt @@ -285,7 +285,7 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestB val labelsAsText = InTextDirectivesUtils.findLinesWithPrefixesRemoved(contextElement.getContainingFile().getText(), "// DEBUG_LABEL: ") if (labelsAsText.isEmpty()) return contextElement - val markupMap = hashMapOf() + val markupMap = hashMapOf() for (labelAsText in labelsAsText) { val labelParts = labelAsText.splitBy("=") assert(labelParts.size() == 2) { "Wrong format for DEBUG_LABEL directive: // DEBUG_LABEL: {localVariableName} = {labelText}"} @@ -293,13 +293,13 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestB val labelName = labelParts[1].trim() val localVariable = context.getFrameProxy()!!.visibleVariableByName(localVariableName) assert(localVariable != null) { "Couldn't find localVariable for label: name = $localVariableName" } - val localVariableValue = context.getFrameProxy()!!.getValue(localVariable) as? ObjectReference + val localVariableValue = context.getFrameProxy()!!.getValue(localVariable) assert(localVariableValue != null) { "Local variable $localVariableName should be an ObjectReference" } localVariableValue!! markupMap.put(localVariableValue, ValueMarkup(labelName, null, labelName)) } - val (text, labels) = KotlinCodeFragmentFactory.createCodeFragmentForLabeledObjects(markupMap) + val (text, labels) = KotlinCodeFragmentFactory.createCodeFragmentForLabeledObjects(contextElement.project, markupMap) return KotlinCodeFragmentFactory().createWrappingContext(text, labels, KotlinCodeFragmentFactory.getContextElement(contextElement), getProject())!! } 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 0aad181bb24..99e9c779431 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -551,6 +551,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/lSimple.kt"); doSingleBreakpointTest(fileName); } + + @TestMetadata("ldifferentTypes.kt") + public void testLdifferentTypes() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/ldifferentTypes.kt"); + doSingleBreakpointTest(fileName); + } } @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas")