Evaluate: Support synthetic 'field' variable evaluation (KT-28342)
This commit is contained in:
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
|
||||
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import java.util.*
|
||||
|
||||
@@ -315,3 +317,12 @@ fun findCallByEndToken(element: PsiElement): KtCallExpression? {
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun PropertyDescriptor.getBackingFieldName(): String? {
|
||||
if (backingField == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val jvmNameAnnotation = DescriptorUtils.findJvmNameAnnotation(this) ?: return name.asString()
|
||||
return jvmNameAnnotation.allValueArguments.values.singleOrNull()?.toString()
|
||||
}
|
||||
+4
-3
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -226,14 +227,14 @@ class KotlinDebuggerCaches(project: Project) {
|
||||
class ParametersDescriptor : Iterable<Parameter> {
|
||||
private val list = ArrayList<Parameter>()
|
||||
|
||||
fun add(name: String, jetType: KotlinType, value: Value? = null) {
|
||||
list.add(Parameter(name, jetType, value))
|
||||
fun add(name: String, jetType: KotlinType, value: Value? = null, error: EvaluateException? = null) {
|
||||
list.add(Parameter(name, jetType, value, error))
|
||||
}
|
||||
|
||||
override fun iterator() = list.iterator()
|
||||
}
|
||||
|
||||
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null)
|
||||
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null)
|
||||
|
||||
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
|
||||
companion object {
|
||||
|
||||
+41
-11
@@ -55,6 +55,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
@@ -67,6 +68,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Compiled
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ParametersDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||
import org.jetbrains.kotlin.idea.debugger.getBackingFieldName
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
|
||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
@@ -80,6 +82,7 @@ import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
|
||||
@@ -223,7 +226,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line)
|
||||
?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}")
|
||||
val (parametersDescriptor, extractedFunction) = try {
|
||||
extractionResult.getParametersForDebugger(codeFragment) to extractionResult.declaration as KtNamedFunction
|
||||
extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction
|
||||
} finally {
|
||||
Disposer.dispose(extractionResult)
|
||||
}
|
||||
@@ -407,7 +410,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
return sharedVar?.asJdiValue(vm, sharedVar.asmType) ?: jdiValue.asJdiValue(vm, jdiValue.asmType)
|
||||
}
|
||||
|
||||
private fun ExtractionResult.getParametersForDebugger(fragment: KtCodeFragment): ParametersDescriptor {
|
||||
private fun ExtractionResult.getParametersForDebugger(
|
||||
fragment: KtCodeFragment,
|
||||
context: EvaluationContextImpl
|
||||
): ParametersDescriptor {
|
||||
return runReadAction {
|
||||
val valuesForLabels = HashMap<String, Value>()
|
||||
|
||||
@@ -435,23 +441,47 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
param.argumentText.startsWith("::") -> param.argumentText.substring(2)
|
||||
else -> param.argumentText
|
||||
}
|
||||
|
||||
val paramDescriptor = param.originalDescriptor
|
||||
if (paramDescriptor is SyntheticFieldDescriptor) {
|
||||
val backingFieldName = paramDescriptor.propertyDescriptor.getBackingFieldName()
|
||||
if (backingFieldName != null) {
|
||||
val thisObject = context.suspendContext.frameProxy?.thisObject()
|
||||
val field = thisObject?.referenceType()?.fieldByName(backingFieldName)
|
||||
|
||||
if (thisObject != null && field != null) {
|
||||
parameters.add(backingFieldName, param.getParameterType(true), thisObject.getValue(field).asValue())
|
||||
} else {
|
||||
parameters.add(
|
||||
backingFieldName, paramDescriptor.builtIns.unitType, null,
|
||||
EvaluateException("Can't find a backing field for property ${paramDescriptor.name}")
|
||||
)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
parameters.add(paramName, param.getParameterType(true), valuesForLabels[paramName])
|
||||
}
|
||||
|
||||
parameters
|
||||
}
|
||||
}
|
||||
|
||||
private fun EvaluationContextImpl.getArgumentsForEval4j(parameters: ParametersDescriptor, parameterTypes: Array<Type>): List<Value> {
|
||||
private fun EvaluationContextImpl.getArgumentsForEval4j(
|
||||
parameters: ParametersDescriptor,
|
||||
parameterTypes: Array<Type>
|
||||
): List<Value> {
|
||||
val frameVisitor = FrameVisitor(this)
|
||||
return parameters.zip(parameterTypes).map {
|
||||
val result = if (it.first.value != null) {
|
||||
it.first.value!!
|
||||
}
|
||||
else {
|
||||
frameVisitor.findValue(it.first.callText, it.second, checkType = false, failIfNotFound = true)!!
|
||||
}
|
||||
return parameters.zip(parameterTypes).map { (parameter, type) ->
|
||||
parameter.error?.let { throw it }
|
||||
|
||||
val result = parameter.value
|
||||
?: frameVisitor.findValue(parameter.callText, type, checkType = false, failIfNotFound = true)!!
|
||||
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Parameter for eval4j: name = ${it.first.callText}, type = ${it.second}, value = $result")
|
||||
LOG.debug("Parameter for eval4j: name = ${parameter.callText}, type = $type, value = $result")
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package fieldVariable
|
||||
|
||||
class Foo {
|
||||
val a: String? = "field"
|
||||
get() {
|
||||
//Breakpoint!
|
||||
val a = 5
|
||||
return "not a " + field
|
||||
}
|
||||
|
||||
val b: String?
|
||||
get() {
|
||||
//Breakpoint!
|
||||
return "b"
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
Foo().a
|
||||
Foo().b
|
||||
}
|
||||
|
||||
// EXPRESSION: field
|
||||
// RESULT: "field": Ljava/lang/String;
|
||||
|
||||
// EXPRESSION: field
|
||||
// RESULT: Can't find a backing field for property field
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
LineBreakpoint created at fieldVariable.kt:7
|
||||
LineBreakpoint created at fieldVariable.kt:14
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
fieldVariable.kt:7
|
||||
Compile bytecode for field
|
||||
fieldVariable.kt:14
|
||||
Compile bytecode for field
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
Generated
+5
@@ -876,6 +876,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fieldVariable.kt")
|
||||
public void testFieldVariable() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/fieldVariable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("funFromOuterClassInLamdba.kt")
|
||||
public void testFunFromOuterClassInLamdba() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt");
|
||||
|
||||
Reference in New Issue
Block a user