From 81f4e2fb3b046989285416558d7c69f3fb02f2b3 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Tue, 19 Feb 2019 22:04:21 +0300 Subject: [PATCH] Evaluator: Support delegated local variables --- .../CodeFragmentParameterAnalyzer.kt | 6 ++- .../evaluate/variables/VariableFinder.kt | 37 ++++++++++++++++--- .../singleBreakpoint/delegatedVariables.kt | 10 +++++ .../singleBreakpoint/delegatedVariables.out | 8 ++++ ...KotlinEvaluateExpressionTestGenerated.java | 5 +++ 5 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.out diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt index f16c3e03b6e..3e1e85ffedb 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt @@ -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 diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt index 463b9bfadd1..39052faf528 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt @@ -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.namedEntitySequence(owner: ObjectReference): Sequence { diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.kt new file mode 100644 index 00000000000..db8846f5ef3 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.kt @@ -0,0 +1,10 @@ +package delegatedVariables + +fun main() { + val a by lazy { "foo" } + //Breakpoint! + val b = a +} + +// EXPRESSION: a +// RESULT: "foo": Ljava/lang/String; diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.out new file mode 100644 index 00000000000..23c4adeb450 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedVariables.out @@ -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 diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index 3d437ffe4ab..02444a62ff4 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -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");