From c2f0286183cd9af42eebd4963ce66aa342c9c3aa Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 14 Jun 2019 17:10:09 +0900 Subject: [PATCH] Debugger: Prefer the closest captured values --- .../debugger/stackFrame/CapturedValueData.kt | 1 - .../stackFrame/CapturedValuesSearcher.kt | 134 ++++++++++++++++++ .../debugger/stackFrame/ExistingVariables.kt | 47 ++++++ .../debugger/stackFrame/KotlinStackFrame.kt | 125 +--------------- .../debugger/stackFrame/thisLabelUtils.kt | 38 +++++ .../frame/capturedValues1.out | 6 +- .../singleBreakpoint/frame/capturedValues2.kt | 26 ++-- .../frame/capturedValues2.out | 12 +- 8 files changed, 243 insertions(+), 146 deletions(-) create mode 100644 idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValuesSearcher.kt create mode 100644 idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/ExistingVariables.kt create mode 100644 idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/thisLabelUtils.kt diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValueData.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValueData.kt index d4bbec95ce9..2a1106ee871 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValueData.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValueData.kt @@ -16,7 +16,6 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiExpression import com.intellij.xdebugger.frame.XValueModifier import com.sun.jdi.* -import org.jetbrains.org.objectweb.asm.Type as AsmType data class CapturedValueData( val valueName: String, diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValuesSearcher.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValuesSearcher.kt new file mode 100644 index 00000000000..e617e949169 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/stackFrame/CapturedValuesSearcher.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.stackFrame + +import com.intellij.debugger.impl.descriptors.data.DescriptorData +import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl +import com.sun.jdi.Field +import com.sun.jdi.ObjectReference +import com.sun.jdi.Value +import org.jetbrains.kotlin.codegen.AsmUtil +import org.jetbrains.kotlin.idea.debugger.safeFields +import java.util.* + +private sealed class PendingValue { + class Ordinary(val name: String, val field: Field, val container: Container) : PendingValue() { + override fun addTo(existingVariables: ExistingVariables): CapturedValueData? { + if (!existingVariables.add(ExistingVariable.Ordinary(name))) { + return null + } + + return CapturedValueData(name, container.value, field) + } + } + + class This(val label: String, val value: Value?) : PendingValue() { + override fun addTo(existingVariables: ExistingVariables): DescriptorData? { + val thisName = if (existingVariables.hasThisVariables) { + if (!existingVariables.add(ExistingVariable.LabeledThis(label))) { + // Avoid item duplication + return null + } + + getThisName(label) + } else { + if (!existingVariables.add(ExistingVariable.LabeledThis(label))) { + return null + } + AsmUtil.THIS + } + + return LabeledThisData(label, thisName, value) + } + } + + class Container(val value: ObjectReference) : PendingValue() { + fun getChildren(): List { + return value.referenceType().safeFields() + .filter { it.isApplicable() } + .mapNotNull { createPendingValue(this, it) } + } + + private fun Field.isApplicable(): Boolean { + val name = name() + return name.startsWith(AsmUtil.CAPTURED_PREFIX) || name == AsmUtil.CAPTURED_THIS_FIELD + } + + override fun addTo(existingVariables: ExistingVariables): DescriptorData? { + throw IllegalStateException("Should not be called on a container") + } + } + + abstract fun addTo(existingVariables: ExistingVariables): DescriptorData? +} + +internal fun attachCapturedValues( + containerValue: ObjectReference, + existingVariables: ExistingVariables, + collector: (DescriptorData) -> Unit +) { + val values = collectPendingValues(PendingValue.Container(containerValue)) + + for (value in values) { + val descriptorData = value.addTo(existingVariables) ?: continue + collector(descriptorData) + } +} + +private fun collectPendingValues(container: PendingValue.Container): List { + val queue = ArrayDeque() + queue.offer(container) + val values = mutableListOf() + collectValuesBfs(queue, values) + return values +} + +private tailrec fun collectValuesBfs(queue: Deque, consumer: MutableList) { + val deeperValues = ArrayDeque() + + while (queue.isNotEmpty()) { + val value = queue.removeFirst() ?: break + + if (value is PendingValue.Container) { + val children = value.getChildren() + deeperValues.addAll(children) + continue + } + + consumer += value + } + + if (deeperValues.isNotEmpty()) { + collectValuesBfs(deeperValues, consumer) + } +} + +private fun createPendingValue(container: PendingValue.Container, field: Field): PendingValue? { + val name = field.name() + + if (name == AsmUtil.CAPTURED_THIS_FIELD) { + /* + * Captured entities. + * In case of captured lambda, we just add values captured to the lambda to the list. + * In case of captured this (outer this, for example), we add the 'this' value. + */ + val value = container.value.getValue(field) as? ObjectReference ?: return null + return when (val label = getThisValueLabel(value)) { + null -> PendingValue.Container(value) + else -> PendingValue.This(label, value) + } + } else if (name.startsWith(AsmUtil.CAPTURED_LABELED_THIS_FIELD)) { + // Extension receivers for a new scheme ($this_