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.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
|
||||
|
||||
+31
-6
@@ -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> {
|
||||
|
||||
+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");
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
Reference in New Issue
Block a user