Evaluate: Support synthetic 'field' variable evaluation (KT-28342)

This commit is contained in:
Yan Zhulanow
2018-11-26 17:29:32 +09:00
parent 60d2490c45
commit c88d8a5e0d
6 changed files with 99 additions and 14 deletions
@@ -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()
}
@@ -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 {
@@ -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
}
@@ -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
@@ -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
@@ -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");