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.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<String, Map<String, ObjectReference>> {
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
val sb = StringBuilder()
val labeledObjects = HashMap<String, ObjectReference>()
val labeledObjects = HashMap<String, Value>()
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<String, ObjectReference>,
labels: Map<String, Value>,
originalContext: PsiElement?,
project: Project
): 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: ")
if (labelsAsText.isEmpty()) return contextElement
val markupMap = hashMapOf<ObjectReference, ValueMarkup>()
val markupMap = hashMapOf<com.sun.jdi.Value, ValueMarkup>()
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())!!
}
@@ -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")