diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java index c095e8a851b..6e89df155d7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java @@ -94,7 +94,7 @@ public class AsmUtil { public static final String THIS = "this"; - private static final String LABELED_THIS_FIELD = THIS + "_"; + public static final String LABELED_THIS_FIELD = THIS + "_"; public static final String CAPTURED_THIS_FIELD = "this$0"; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt index e065f017954..d61e6274718 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt @@ -69,7 +69,7 @@ internal const val DEFAULT_LAMBDA_FAKE_CALL = "$$\$DEFAULT_LAMBDA_FAKE_CALL$$$" internal const val CAPTURED_FIELD_FOLD_PREFIX = "$$$" private const val NON_LOCAL_RETURN = "$$$$\$NON_LOCAL_RETURN$$$$$" -private const val CAPTURED_FIELD_PREFIX = "$" +const val CAPTURED_FIELD_PREFIX = "$" private const val NON_CAPTURED_FIELD_PREFIX = "$$" private const val INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker" private const val INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall" diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinSourcePositionProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinSourcePositionProvider.kt index 44cb4e5ce48..97e412861fc 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinSourcePositionProvider.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinSourcePositionProvider.kt @@ -86,7 +86,11 @@ class KotlinSourcePositionProvider: SourcePositionProvider() { private fun computeSourcePosition(descriptor: FieldDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? { val fieldName = descriptor.field.name() - if (fieldName == AsmUtil.CAPTURED_THIS_FIELD || fieldName == AsmUtil.CAPTURED_RECEIVER_FIELD) { + + if (fieldName == AsmUtil.CAPTURED_THIS_FIELD + || fieldName == AsmUtil.CAPTURED_RECEIVER_FIELD + || fieldName.startsWith(AsmUtil.LABELED_THIS_FIELD) + ) { return null } diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index f4e4ca060a5..53224102643 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -100,11 +100,11 @@ fun Location.safeMethod(): Method? { return DebuggerUtilsEx.getMethod(this) } -fun isInsideInlineFunctionBody(visibleVariables: List): Boolean { +fun isInsideInlineFunctionBody(visibleVariables: List): Boolean { return visibleVariables.any { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } } -fun numberOfInlinedFunctions(visibleVariables: List): Int { +fun numberOfInlinedFunctions(visibleVariables: List): Int { return visibleVariables.count { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } } diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt index 4ce1ebbdf57..84f8ca7ef25 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt @@ -19,14 +19,17 @@ package org.jetbrains.kotlin.idea.debugger.evaluate import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.openapi.diagnostic.Attachment +import com.intellij.patterns.uast.capture import com.sun.jdi.ClassType import com.sun.jdi.InvalidStackFrameException import com.sun.jdi.ObjectReference +import com.sun.jdi.StackFrame import org.jetbrains.eval4j.Value import org.jetbrains.eval4j.jdi.asJdiValue import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.eval4j.obj import org.jetbrains.kotlin.codegen.AsmUtil +import org.jetbrains.kotlin.codegen.inline.CAPTURED_FIELD_PREFIX import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX import org.jetbrains.kotlin.codegen.inline.NUMBERED_FUNCTION_PREFIX @@ -49,12 +52,12 @@ class FrameVisitor(val context: EvaluationContextImpl) { } fun findValue(name: String, asmType: Type?, checkType: Boolean, failIfNotFound: Boolean): Value? { - if (frame == null) return null + val frame = this.frame?.stackFrame ?: return null try { when (name) { THIS_NAME -> { - val thisValue = findThis(asmType) + val thisValue = findThis(frame, asmType) if (thisValue != null) { return thisValue } @@ -125,8 +128,8 @@ class FrameVisitor(val context: EvaluationContextImpl) { throw EvaluateExceptionUtil.createEvaluateException(message) } - private fun findThis(asmType: Type?): Value? { - if (isInsideInlineFunctionBody(frame!!.visibleVariables())) { + private fun findThis(frame: StackFrame, asmType: Type?): Value? { + if (isInsideInlineFunctionBody(frame.visibleVariables())) { val number = numberOfInlinedFunctions(frame.visibleVariables()) val inlineFunVar = findLocalVariableForInlineArgument("this_", number, asmType, true) if (inlineFunVar != null) { @@ -134,18 +137,34 @@ class FrameVisitor(val context: EvaluationContextImpl) { } } + // Captured labeled 'this' + frame.visibleVariables() + .asSequence() + .filter { it.name().startsWith(AsmUtil.LABELED_THIS_FIELD) } + .map { it to frame.getValue(it).asValue() } + .filter { isValueOfCorrectType(it.second, asmType, true) } + .maxBy { it.first } + ?.let { return frame.getValue(it.first).asValue() } + val thisObject = frame.thisObject() if (thisObject != null) { val eval4jValue = thisObject.asValue() - if (isValueOfCorrectType(eval4jValue, asmType, true)) return eval4jValue + if (isValueOfCorrectType(eval4jValue, asmType, true)) { + return eval4jValue + } + + findThisInCapturedThis(thisObject, asmType)?.let { return it } } + // TODO this is probably not needed anymore (covered by 'findThisInCapturedThis') val receiver = findValue(RECEIVER_NAME, asmType, checkType = true, failIfNotFound = false) if (receiver != null) return receiver + // TODO this is probably not needed anymore (covered by 'findThisInCapturedThis') val this0 = findValue(AsmUtil.CAPTURED_THIS_FIELD, asmType, checkType = true, failIfNotFound = false) if (this0 != null) return this0 + // TODO this is probably not needed anymore (used only in JS) val `$this` = findValue("\$this", asmType, checkType = false, failIfNotFound = false) if (`$this` != null) return `$this` @@ -160,6 +179,32 @@ class FrameVisitor(val context: EvaluationContextImpl) { return findLocalVariable(name + INLINE_FUN_VAR_SUFFIX.repeat(number), asmType, checkType) } + private fun findThisInCapturedThis(capturedThis: ObjectReference, asmType: Type?): Value? { + for (field in capturedThis.referenceType().fields()) { + val name = field.name() + if (name == AsmUtil.CAPTURED_THIS_FIELD) { + val value = capturedThis.getValue(field) + if (value is ObjectReference) { + findThisInCapturedThis(value, asmType)?.let { return it } + } + } + + if (name.startsWith(CAPTURED_FIELD_PREFIX + AsmUtil.LABELED_THIS_FIELD) + || name == AsmUtil.CAPTURED_RECEIVER_FIELD + || name == AsmUtil.CAPTURED_THIS_FIELD + ) { + val value = capturedThis.getValue(field) + val evalValue = value.asValue() + + if (isValueOfCorrectType(evalValue, asmType, true)) { + return evalValue + } + } + } + + return null + } + private fun isFunctionType(type: Type?): Boolean { return type?.sort == Type.OBJECT && type.internalName.startsWith(NUMBERED_FUNCTION_PREFIX) diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameExtFunExtFun.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameExtFunExtFun.out index c391fc83f2e..50c24da1ea5 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameExtFunExtFun.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameExtFunExtFun.out @@ -89,10 +89,10 @@ fun lambda(f: () -> Unit) { field = lcProp: int = 1 (sp = frameExtFunExtFun.kt, 19) field = this$0: frameExtFunExtFun.Outer = {frameExtFunExtFun.Outer@uniqueID} (sp = null) field = outerProp: int = 1 (sp = frameExtFunExtFun.kt, 13) - field = receiver$0: frameExtFunExtFun.A = {frameExtFunExtFun.A@uniqueID} (sp = null) + field = $this_foo: frameExtFunExtFun.A = {frameExtFunExtFun.A@uniqueID} (sp = null) field = aProp: int = 1 (sp = frameExtFunExtFun.kt, 8) field = $valFoo: int = 1 (sp = null) - field = receiver$0: frameExtFunExtFun.B = {frameExtFunExtFun.B@uniqueID} (sp = null) + field = $this_test: frameExtFunExtFun.B = {frameExtFunExtFun.B@uniqueID} (sp = null) field = bProp: int = 1 (sp = frameExtFunExtFun.kt, 41) field = $valTest: int = 1 (sp = null) field = arity: int = 0 (sp = Lambda.!EXT!) diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameThis0Ext.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameThis0Ext.out index 0530387adeb..afcda494f6e 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameThis0Ext.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameThis0Ext.out @@ -67,7 +67,7 @@ fun foo(f: () -> Unit) { this = this = {frameThis0Ext.A$testExt$1@uniqueID}Function0 field = this$0: frameThis0Ext.A = {frameThis0Ext.A@uniqueID} (sp = null) field = prop1: int = 1 (sp = frameThis0Ext.kt, 8) - field = receiver$0: frameThis0Ext.AExt = {frameThis0Ext.AExt@uniqueID} (sp = null) + field = $this_testExt: frameThis0Ext.AExt = {frameThis0Ext.AExt@uniqueID} (sp = null) field = prop2: int = 1 (sp = frameThis0Ext.kt, 25) field = $val1: int = 1 (sp = null) field = arity: int = 0 (sp = Lambda.!EXT!)