Evaluator: Support delegated local variables
This commit is contained in:
+4
-2
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.AsmUtil
|
|||||||
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
|
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
|
||||||
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
|
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||||
@@ -38,7 +39,7 @@ interface CodeFragmentParameter {
|
|||||||
val debugString: String
|
val debugString: String
|
||||||
|
|
||||||
enum class Kind {
|
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
|
FAKE_JAVA_OUTER_CLASS, FIELD_VAR, DEBUG_LABEL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +277,8 @@ class CodeFragmentParameterAnalyzer(
|
|||||||
is ValueDescriptor -> {
|
is ValueDescriptor -> {
|
||||||
parameters.getOrPut(target) {
|
parameters.getOrPut(target) {
|
||||||
val type = target.type
|
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
|
else -> null
|
||||||
|
|||||||
+31
-6
@@ -134,7 +134,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
|
|||||||
sealed class VariableKind(val asmType: AsmType) {
|
sealed class VariableKind(val asmType: AsmType) {
|
||||||
abstract fun capturedNameMatches(name: String): Boolean
|
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))
|
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
|
||||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(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? {
|
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
|
||||||
return when (parameter.kind) {
|
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.FAKE_JAVA_OUTER_CLASS -> frameProxy.thisObject()?.let { Result(it) }
|
||||||
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
|
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
|
||||||
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(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()
|
return variables.namedEntitySequence()
|
||||||
.filter { isReceiverOrPassedThis(it.name) }
|
.filter { isReceiverOrPassedThis(it.name) }
|
||||||
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||||
.firstOrNull()
|
.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? {
|
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
|
||||||
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
|
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
|
||||||
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
|
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
|
// Recursive search in captured receivers
|
||||||
fields.namedEntitySequence(parent)
|
fields.namedEntitySequence(parent)
|
||||||
.filter { isCapturedReceiverFieldName(it.name) }
|
.filter { isCapturedReceiverFieldName(it.name) }
|
||||||
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { return it }
|
?.let { return it }
|
||||||
}
|
}
|
||||||
@@ -452,24 +458,43 @@ class VariableFinder private constructor(private val context: ExecutionContext,
|
|||||||
// Recursive search in outer and captured this
|
// Recursive search in outer and captured this
|
||||||
fields.namedEntitySequence(parent)
|
fields.namedEntitySequence(parent)
|
||||||
.filter { it.name == AsmUtil.THIS_IN_DEFAULT_IMPLS || it.name == AsmUtil.CAPTURED_THIS_FIELD }
|
.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()
|
.firstOrNull()
|
||||||
?.let { return it }
|
?.let { return it }
|
||||||
|
|
||||||
return null
|
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 {
|
private fun isCapturedReceiverFieldName(name: String): Boolean {
|
||||||
return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))
|
return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))
|
||||||
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
|
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)
|
return evaluatorValueConverter.typeMatches(asmType, actualType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
|
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> {
|
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package delegatedVariables
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val a by lazy { "foo" }
|
||||||
|
//Breakpoint!
|
||||||
|
val b = a
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: a
|
||||||
|
// RESULT: "foo": Ljava/lang/String;
|
||||||
+8
@@ -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
|
||||||
Generated
+5
@@ -96,6 +96,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedPropertyInOtherFile.kt");
|
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")
|
@TestMetadata("dependentOnFile.kt")
|
||||||
public void testDependentOnFile() throws Exception {
|
public void testDependentOnFile() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/dependentOnFile.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/dependentOnFile.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user