Debugger: allow to evaluate 'this' and fields in java files
This commit is contained in:
@@ -47,6 +47,7 @@ import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
|||||||
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
||||||
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
|
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
|
||||||
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.j2k
|
||||||
import org.jetbrains.kotlin.idea.refactoring.j2kText
|
import org.jetbrains.kotlin.idea.refactoring.j2kText
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
@@ -127,33 +128,36 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
return@putCopyableUserData emptyFile
|
return@putCopyableUserData emptyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 'this' is unavailable
|
val frameDescriptor = getFrameInfo(contextElement, debuggerContext)
|
||||||
val visibleVariables = getVisibleLocalVariables(contextElement, debuggerContext)
|
if (frameDescriptor == null) {
|
||||||
if (visibleVariables == null) {
|
LOG.warn("Couldn't get info about 'this' and local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}")
|
||||||
LOG.warn("Couldn't get a list of local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}")
|
|
||||||
return@putCopyableUserData emptyFile
|
return@putCopyableUserData emptyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
val fakeFunctionText = "fun _java_locals_debug_fun_() {\n" +
|
val receiverTypeReference = frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference
|
||||||
visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) +
|
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
||||||
"}"
|
|
||||||
|
val kotlinVariablesText = frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
||||||
|
|
||||||
|
val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}"
|
||||||
|
|
||||||
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||||
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||||
val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull()
|
val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull()
|
||||||
|
|
||||||
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile
|
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return codeFragment
|
return codeFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVisibleLocalVariables(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): Map<LocalVariable, Value>? {
|
private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? {
|
||||||
val semaphore = Semaphore()
|
val semaphore = Semaphore()
|
||||||
semaphore.down()
|
semaphore.down()
|
||||||
|
|
||||||
var visibleVariables: Map<LocalVariable, Value>? = null
|
var frameInfo: FrameInfo? = null
|
||||||
|
|
||||||
val worker = object : DebuggerCommandImpl() {
|
val worker = object : DebuggerCommandImpl() {
|
||||||
override fun action() {
|
override fun action() {
|
||||||
@@ -163,7 +167,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
else
|
else
|
||||||
debuggerContext.frameProxy?.stackFrame
|
debuggerContext.frameProxy?.stackFrame
|
||||||
|
|
||||||
visibleVariables = frame?.let { it.getValues(it.visibleVariables()) } ?: emptyMap<LocalVariable, Value>()
|
frameInfo = FrameInfo(frame?.thisObject(), frame?.let { it.getValues(it.visibleVariables()) } ?: emptyMap())
|
||||||
}
|
}
|
||||||
catch(ignored: AbsentInformationException) {
|
catch(ignored: AbsentInformationException) {
|
||||||
// Debug info unavailable
|
// Debug info unavailable
|
||||||
@@ -180,9 +184,11 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
if (semaphore.waitFor(20)) break
|
if (semaphore.waitFor(20)) break
|
||||||
}
|
}
|
||||||
|
|
||||||
return visibleVariables
|
return frameInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
|
||||||
|
|
||||||
private fun initImports(imports: String?): String? {
|
private fun initImports(imports: String?): String? {
|
||||||
if (imports != null && !imports.isEmpty()) {
|
if (imports != null && !imports.isEmpty()) {
|
||||||
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
||||||
@@ -334,7 +340,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
|
|
||||||
val kotlinProperty = createKotlinProperty(project, variableName, variableValue.type().name(), variableValue)
|
val kotlinProperty = createKotlinProperty(project, variableName, variableValue.type().name(), variableValue)
|
||||||
?: continue
|
?: continue
|
||||||
sb.append("$kotlinProperty\n")
|
sb.append("${kotlinProperty.text}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append("val _debug_context_val = 1\n")
|
sb.append("val _debug_context_val = 1\n")
|
||||||
@@ -342,11 +348,11 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): String? {
|
private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? {
|
||||||
val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project))
|
val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project))
|
||||||
if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) {
|
if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) {
|
||||||
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable())
|
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable())
|
||||||
return "val ${variableName.quoteIfNeeded()}: $renderedType = null"
|
return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this
|
fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this
|
||||||
@@ -359,7 +365,9 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
className).addArraySuffix()
|
className).addArraySuffix()
|
||||||
|
|
||||||
val field = PsiElementFactory.SERVICE.getInstance(project).createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project)))
|
val field = PsiElementFactory.SERVICE.getInstance(project).createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project)))
|
||||||
return field.j2kText()?.substringAfter("private ")
|
val ktField = field.j2k() as? KtProperty
|
||||||
|
ktField?.modifierList?.delete()
|
||||||
|
return ktField
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
LineBreakpoint created at jcProperty.kt:6
|
||||||
|
!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! jcProperty.JcPropertyKt
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
jcProperty.kt:6
|
||||||
|
JavaClass.java:47
|
||||||
|
Compile bytecode for this.javaProperty
|
||||||
|
Compile bytecode for javaProperty
|
||||||
|
Compile bytecode for javaPrivateProperty
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package jcProperty
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val javaClass = forTests.javaContext.JavaClass()
|
||||||
|
//Breakpoint!
|
||||||
|
javaClass.property()
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_INTO: 1
|
||||||
|
|
||||||
|
// EXPRESSION: this.javaProperty
|
||||||
|
// RESULT: 1: I
|
||||||
|
|
||||||
|
// EXPRESSION: javaProperty
|
||||||
|
// RESULT: 1: I
|
||||||
|
|
||||||
|
// EXPRESSION: javaPrivateProperty
|
||||||
|
// RESULT: 1: I
|
||||||
@@ -39,4 +39,11 @@ public class JavaClass {
|
|||||||
Integer i = 1;
|
Integer i = 1;
|
||||||
int breakpoint = 1;
|
int breakpoint = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int javaProperty = 1;
|
||||||
|
private int javaPrivateProperty = 1;
|
||||||
|
|
||||||
|
public void property() {
|
||||||
|
int breakpoint = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+6
@@ -729,6 +729,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
doSingleBreakpointTest(fileName);
|
doSingleBreakpointTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jcProperty.kt")
|
||||||
|
public void testJcProperty() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcProperty.kt");
|
||||||
|
doSingleBreakpointTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("jcSimple.kt")
|
@TestMetadata("jcSimple.kt")
|
||||||
public void testJcSimple() throws Exception {
|
public void testJcSimple() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user