Debugger: support labels for non object references (primitive, array)

This commit is contained in:
Natalia Ukhorskaya
2015-08-25 13:19:46 +03:00
parent 35cb23ffbe
commit c6d29cc1d5
5 changed files with 183 additions and 14 deletions
@@ -24,18 +24,20 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.psi.JavaCodeFragment import com.intellij.psi.*
import com.intellij.psi.PsiCodeBlock import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.concurrency.Semaphore import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.XDebugSessionImpl import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
import com.sun.jdi.ArrayReference
import com.sun.jdi.ObjectReference import com.sun.jdi.ObjectReference
import com.sun.jdi.PrimitiveValue
import com.sun.jdi.Value import com.sun.jdi.Value
import org.jetbrains.kotlin.asJava.KotlinLightClass import org.jetbrains.kotlin.asJava.KotlinLightClass
import org.jetbrains.kotlin.idea.JetFileType 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.idea.debugger.KotlinEditorTextProvider
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -150,24 +152,53 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
} }
//internal for tests //internal for tests
fun createCodeFragmentForLabeledObjects(markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, ObjectReference>> { fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
val sb = StringBuilder() val sb = StringBuilder()
val labeledObjects = HashMap<String, ObjectReference>() val labeledObjects = HashMap<String, Value>()
for ((value, markup) in markupMap.entrySet()) { for ((value, markup) in markupMap.entrySet()) {
val labelName = markup.text val labelName = markup.text
if (!Name.isValidIdentifier(labelName)) continue 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"
val labelNameWithSuffix = labelName + DEBUG_LABEL_SUFFIX sb.append("${createKotlinProperty(project, labelNameWithSuffix, objectRef.type().name(), TypeKind.getTypeKind(objectRef))}\n")
sb.append("val ").append(labelNameWithSuffix).append(": ").append(typeName).append("? = null\n")
labeledObjects.put(labelNameWithSuffix, objectRef) labeledObjects.put(labelNameWithSuffix, objectRef)
} }
sb.append("val _debug_context_val = 1") sb.append("val _debug_context_val = 1")
return sb.toString() to labeledObjects 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? { private fun wrapContextIfNeeded(project: Project, originalContext: JetElement?): JetElement? {
@@ -177,7 +208,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
val markupMap = session.valueMarkers?.getAllMarkers() val markupMap = session.valueMarkers?.getAllMarkers()
if (markupMap == null || markupMap.isEmpty()) return originalContext if (markupMap == null || markupMap.isEmpty()) return originalContext
val (text, labels) = createCodeFragmentForLabeledObjects(markupMap) val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap)
if (text.isEmpty()) return originalContext if (text.isEmpty()) return originalContext
return createWrappingContext(text, labels, originalContext, project) return createWrappingContext(text, labels, originalContext, project)
@@ -186,7 +217,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
// internal for test // internal for test
fun createWrappingContext( fun createWrappingContext(
newFragmentText: String, newFragmentText: String,
labels: Map<String, ObjectReference>, labels: Map<String, Value>,
originalContext: PsiElement?, originalContext: PsiElement?,
project: Project project: Project
): JetElement? { ): JetElement? {
+27
View File
@@ -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
@@ -0,0 +1,105 @@
package ldifferentTypes
fun main(args: Array<String>) {
// 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
}
@@ -285,7 +285,7 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestB
val labelsAsText = InTextDirectivesUtils.findLinesWithPrefixesRemoved(contextElement.getContainingFile().getText(), "// DEBUG_LABEL: ") val labelsAsText = InTextDirectivesUtils.findLinesWithPrefixesRemoved(contextElement.getContainingFile().getText(), "// DEBUG_LABEL: ")
if (labelsAsText.isEmpty()) return contextElement if (labelsAsText.isEmpty()) return contextElement
val markupMap = hashMapOf<ObjectReference, ValueMarkup>() val markupMap = hashMapOf<com.sun.jdi.Value, ValueMarkup>()
for (labelAsText in labelsAsText) { for (labelAsText in labelsAsText) {
val labelParts = labelAsText.splitBy("=") val labelParts = labelAsText.splitBy("=")
assert(labelParts.size() == 2) { "Wrong format for DEBUG_LABEL directive: // DEBUG_LABEL: {localVariableName} = {labelText}"} 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 labelName = labelParts[1].trim()
val localVariable = context.getFrameProxy()!!.visibleVariableByName(localVariableName) val localVariable = context.getFrameProxy()!!.visibleVariableByName(localVariableName)
assert(localVariable != null) { "Couldn't find localVariable for label: name = $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" } assert(localVariableValue != null) { "Local variable $localVariableName should be an ObjectReference" }
localVariableValue!! localVariableValue!!
markupMap.put(localVariableValue, ValueMarkup(labelName, null, labelName)) 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())!! return KotlinCodeFragmentFactory().createWrappingContext(text, labels, KotlinCodeFragmentFactory.getContextElement(contextElement), getProject())!!
} }
@@ -551,6 +551,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/lSimple.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/lSimple.kt");
doSingleBreakpointTest(fileName); 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") @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas")