Debugger: support labels for non object references (primitive, array)
This commit is contained in:
@@ -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? {
|
||||
|
||||
@@ -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
|
||||
+105
@@ -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
|
||||
}
|
||||
+3
-3
@@ -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())!!
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user