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.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? {
|
||||||
|
|||||||
@@ -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: ")
|
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())!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user