Evaluator: Support delegated local variables

This commit is contained in:
Yan Zhulanow
2019-02-19 22:04:21 +03:00
parent 77bec27c99
commit 81f4e2fb3b
5 changed files with 58 additions and 8 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
@@ -38,7 +39,7 @@ interface CodeFragmentParameter {
val debugString: String
enum class Kind {
ORDINARY, EXTENSION_RECEIVER, DISPATCH_RECEIVER, COROUTINE_CONTEXT, LOCAL_FUNCTION,
ORDINARY, DELEGATED, EXTENSION_RECEIVER, DISPATCH_RECEIVER, COROUTINE_CONTEXT, LOCAL_FUNCTION,
FAKE_JAVA_OUTER_CLASS, FIELD_VAR, DEBUG_LABEL
}
@@ -276,7 +277,8 @@ class CodeFragmentParameterAnalyzer(
is ValueDescriptor -> {
parameters.getOrPut(target) {
val type = target.type
Smart(Dumb(Kind.ORDINARY, target.name.asString()), type, target)
val kind = if (target is LocalVariableDescriptor && target.isDelegated) Kind.DELEGATED else Kind.ORDINARY
Smart(Dumb(kind, target.name.asString()), type, target)
}
}
else -> null
@@ -134,7 +134,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
sealed class VariableKind(val asmType: AsmType) {
abstract fun capturedNameMatches(name: String): Boolean
class Ordinary(val name: String, asmType: AsmType) : VariableKind(asmType) {
class Ordinary(val name: String, asmType: AsmType, val isDelegated: Boolean) : VariableKind(asmType) {
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
}
@@ -184,7 +184,8 @@ class VariableFinder private constructor(private val context: ExecutionContext,
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
return when (parameter.kind) {
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType))
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = false))
Kind.DELEGATED -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = true))
Kind.FAKE_JAVA_OUTER_CLASS -> frameProxy.thisObject()?.let { Result(it) }
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
@@ -421,10 +422,15 @@ class VariableFinder private constructor(private val context: ExecutionContext,
return variables.namedEntitySequence()
.filter { isReceiverOrPassedThis(it.name) }
.mapNotNull { findCapturedVariable(kind, it.value()) }
.mapNotNull { findCapturedVariable(kind, it.value) }
.firstOrNull()
}
private fun findCapturedVariable(kind: VariableKind, parentFactory: () -> Value?): Result? {
val parent = getUnwrapDelegate(kind, parentFactory)
return findCapturedVariable(kind, parent)
}
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
@@ -444,7 +450,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
// Recursive search in captured receivers
fields.namedEntitySequence(parent)
.filter { isCapturedReceiverFieldName(it.name) }
.mapNotNull { findCapturedVariable(kind, it.value()) }
.mapNotNull { findCapturedVariable(kind, it.value) }
.firstOrNull()
?.let { return it }
}
@@ -452,24 +458,43 @@ class VariableFinder private constructor(private val context: ExecutionContext,
// Recursive search in outer and captured this
fields.namedEntitySequence(parent)
.filter { it.name == AsmUtil.THIS_IN_DEFAULT_IMPLS || it.name == AsmUtil.CAPTURED_THIS_FIELD }
.mapNotNull { findCapturedVariable(kind, it.value()) }
.mapNotNull { findCapturedVariable(kind, it.value) }
.firstOrNull()
?.let { return it }
return null
}
private fun getUnwrapDelegate(kind: VariableKind, valueFactory: () -> Value?): Value? {
val rawValue = valueFactory()
if (kind !is VariableKind.Ordinary || !kind.isDelegated) {
return rawValue
}
val delegateValue = rawValue as? ObjectReference ?: return rawValue
val getValueMethod = delegateValue.referenceType()
.methodsByName("getValue", "()Ljava/lang/Object;").firstOrNull()
?: return rawValue
return delegateValue.invokeMethod(context.thread, getValueMethod, emptyList(), context.invokePolicy)
}
private fun isCapturedReceiverFieldName(name: String): Boolean {
return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
}
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
if (this is VariableKind.Ordinary && isDelegated) {
// We can't figure out the actual type of the value yet.
// No worries: it will be checked again (and more carefully) in `unwrapAndCheck()`.
return true
}
return evaluatorValueConverter.typeMatches(asmType, actualType)
}
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
return evaluatorValueConverter.coerce(value(), kind.asmType)
return evaluatorValueConverter.coerce(getUnwrapDelegate(kind, value), kind.asmType)
}
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
@@ -0,0 +1,10 @@
package delegatedVariables
fun main() {
val a by lazy { "foo" }
//Breakpoint!
val b = a
}
// EXPRESSION: a
// RESULT: "foo": Ljava/lang/String;
@@ -0,0 +1,8 @@
LineBreakpoint created at delegatedVariables.kt:6
Run Java
Connected to the target VM
delegatedVariables.kt:6
Compile bytecode for a
Disconnected from the target VM
Process finished with exit code 0
@@ -96,6 +96,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedPropertyInOtherFile.kt");
}
@TestMetadata("delegatedVariables.kt")
public void testDelegatedVariables() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.kt");
}
@TestMetadata("dependentOnFile.kt")
public void testDependentOnFile() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/dependentOnFile.kt");