Debugger: Support captured values (KT-30740)
This commit is contained in:
@@ -99,15 +99,19 @@ public class AsmUtil {
|
|||||||
.put(JavaVisibilities.PACKAGE_VISIBILITY, NO_FLAG_PACKAGE_PRIVATE)
|
.put(JavaVisibilities.PACKAGE_VISIBILITY, NO_FLAG_PACKAGE_PRIVATE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static final String CAPTURED_PREFIX = "$";
|
||||||
|
|
||||||
public static final String THIS = "this";
|
public static final String THIS = "this";
|
||||||
|
|
||||||
public static final String THIS_IN_DEFAULT_IMPLS = "$this";
|
public static final String THIS_IN_DEFAULT_IMPLS = "$this";
|
||||||
|
|
||||||
public static final String LABELED_THIS_FIELD = THIS + "_";
|
public static final String LABELED_THIS_FIELD = THIS + "_";
|
||||||
|
|
||||||
|
public static final String CAPTURED_LABELED_THIS_FIELD = CAPTURED_PREFIX + LABELED_THIS_FIELD;
|
||||||
|
|
||||||
public static final String INLINE_DECLARATION_SITE_THIS = "this_";
|
public static final String INLINE_DECLARATION_SITE_THIS = "this_";
|
||||||
|
|
||||||
public static final String LABELED_THIS_PARAMETER = "$" + THIS + "$";
|
public static final String LABELED_THIS_PARAMETER = CAPTURED_PREFIX + THIS + "$";
|
||||||
|
|
||||||
public static final String CAPTURED_THIS_FIELD = "this$0";
|
public static final String CAPTURED_THIS_FIELD = "this$0";
|
||||||
|
|
||||||
@@ -145,7 +149,7 @@ public class AsmUtil {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static String getCapturedFieldName(@NotNull String originalName) {
|
public static String getCapturedFieldName(@NotNull String originalName) {
|
||||||
return "$" + originalName;
|
return CAPTURED_PREFIX + originalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
+1
@@ -50,6 +50,7 @@ import org.jetbrains.kotlin.idea.core.util.getLineCount
|
|||||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.stackFrame.KotlinStackFrame
|
||||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.DebuggerContext
|
||||||
|
import com.intellij.debugger.engine.JavaValue
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
|
import com.intellij.debugger.impl.descriptors.data.DescriptorData
|
||||||
|
import com.intellij.debugger.impl.descriptors.data.DisplayKey
|
||||||
|
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
|
||||||
|
import com.intellij.debugger.ui.impl.watch.*
|
||||||
|
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,
|
||||||
|
val obj: ObjectReference,
|
||||||
|
val field: Field
|
||||||
|
) : DescriptorData<ValueDescriptorImpl>() {
|
||||||
|
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
|
||||||
|
val fieldDescriptor = FieldDescriptorImpl(project, obj, field)
|
||||||
|
return CustomFieldDescriptor(valueName, fieldDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomFieldDescriptor(
|
||||||
|
val valueName: String,
|
||||||
|
val delegate: FieldDescriptorImpl
|
||||||
|
) : ValueDescriptorImpl(delegate.project) {
|
||||||
|
override fun getName() = valueName
|
||||||
|
|
||||||
|
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value = delegate.calcValue(evaluationContext)
|
||||||
|
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression = delegate.getDescriptorEvaluation(context)
|
||||||
|
override fun getModifier(value: JavaValue?): XValueModifier = delegate.getModifier(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(field)
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.engine.JavaValue
|
||||||
|
import com.intellij.debugger.ui.impl.watch.*
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.xdebugger.frame.XValue
|
||||||
|
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
|
import org.jetbrains.kotlin.utils.getSafe
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
// Very Dirty Work-around.
|
||||||
|
// We should stop delegating to the Java stack frame and generate our trace elements from scratch.
|
||||||
|
class ExistingInstanceThisRemapper(
|
||||||
|
private val children: XValueChildrenList,
|
||||||
|
private val index: Int,
|
||||||
|
val value: XValue,
|
||||||
|
private val size: Int
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val LOG = Logger.getInstance(this::class.java)
|
||||||
|
|
||||||
|
private const val THIS_NAME = "this"
|
||||||
|
|
||||||
|
fun find(children: XValueChildrenList): ExistingInstanceThisRemapper? {
|
||||||
|
val size = children.size()
|
||||||
|
for (i in 0 until size) {
|
||||||
|
if (children.getName(i) == THIS_NAME) {
|
||||||
|
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
||||||
|
@Suppress("FoldInitializerAndIfToElvis")
|
||||||
|
if (valueDescriptor !is ThisDescriptorImpl) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExistingInstanceThisRemapper(
|
||||||
|
children,
|
||||||
|
i,
|
||||||
|
children.getValue(i),
|
||||||
|
size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remapName(newName: String) {
|
||||||
|
val (names, _) = getLists() ?: return
|
||||||
|
names[index] = newName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove() {
|
||||||
|
val (names, values) = getLists() ?: return
|
||||||
|
names.removeAt(index)
|
||||||
|
values.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLists(): Lists? {
|
||||||
|
if (children.size() != size) {
|
||||||
|
throw IllegalStateException("Children list was modified")
|
||||||
|
}
|
||||||
|
|
||||||
|
var namesList: MutableList<Any?>? = null
|
||||||
|
var valuesList: MutableList<Any?>? = null
|
||||||
|
|
||||||
|
for (field in XValueChildrenList::class.java.declaredFields) {
|
||||||
|
val mods = field.modifiers
|
||||||
|
if (Modifier.isPrivate(mods) && Modifier.isFinal(mods) && !Modifier.isStatic(mods) && field.type == List::class.java) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val list = (field.getSafe(children) as? MutableList<Any?>)?.takeIf { it.size == size } ?: continue
|
||||||
|
|
||||||
|
if (list[index] == THIS_NAME) {
|
||||||
|
namesList = list
|
||||||
|
} else if (list[index] === value) {
|
||||||
|
valuesList = list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namesList != null && valuesList != null) {
|
||||||
|
return Lists(namesList, valuesList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.error(
|
||||||
|
"Can't find name/value lists, existing fields: "
|
||||||
|
+ Arrays.toString(XValueChildrenList::class.java.declaredFields)
|
||||||
|
)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Lists(val names: MutableList<Any?>, val values: MutableList<Any?>)
|
||||||
|
}
|
||||||
+129
-189
@@ -1,47 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
* 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.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger.stackFrame
|
||||||
|
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.DebuggerContext
|
|
||||||
import com.intellij.debugger.engine.JavaStackFrame
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
import com.intellij.debugger.engine.JavaValue
|
import com.intellij.debugger.engine.JavaValue
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
import com.intellij.debugger.impl.descriptors.data.DescriptorData
|
|
||||||
import com.intellij.debugger.impl.descriptors.data.DisplayKey
|
|
||||||
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
|
|
||||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
import com.intellij.debugger.ui.impl.watch.*
|
||||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
|
||||||
import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl
|
|
||||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.psi.JavaPsiFacade
|
|
||||||
import com.intellij.psi.PsiExpression
|
|
||||||
import com.intellij.util.IncorrectOperationException
|
|
||||||
import com.intellij.xdebugger.frame.XValue
|
|
||||||
import com.intellij.xdebugger.frame.XValueChildrenList
|
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.*
|
||||||
import com.sun.jdi.ReferenceType
|
|
||||||
import com.sun.jdi.Type
|
|
||||||
import com.sun.jdi.Value
|
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil.THIS
|
import org.jetbrains.kotlin.codegen.AsmUtil.THIS
|
||||||
import org.jetbrains.kotlin.codegen.DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX
|
import org.jetbrains.kotlin.codegen.DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX
|
||||||
@@ -49,17 +22,21 @@ import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
|||||||
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_CONTINUATION_PARAMETER
|
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_CONTINUATION_PARAMETER
|
||||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
import org.jetbrains.kotlin.utils.getSafe
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerEvaluator
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerEvaluator
|
||||||
import java.lang.reflect.Modifier
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
|
private sealed class ExistingVariable {
|
||||||
private companion object {
|
interface This
|
||||||
private val LOG = Logger.getInstance(this::class.java)
|
object UnlabeledThis : ExistingVariable(), This
|
||||||
}
|
data class LabeledThis(val label: String) : ExistingVariable(), This
|
||||||
|
|
||||||
|
data class Ordinary(val name: String) : ExistingVariable()
|
||||||
|
}
|
||||||
|
|
||||||
|
private typealias ExistingVariables = MutableSet<ExistingVariable>
|
||||||
|
|
||||||
|
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
|
||||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||||
|
|
||||||
private val kotlinEvaluator by lazy {
|
private val kotlinEvaluator by lazy {
|
||||||
@@ -85,41 +62,58 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
val (thisReferences, otherVariables) = visibleVariables
|
val (thisVariables, otherVariables) = visibleVariables
|
||||||
.partition { it.name() == THIS || it is ThisLocalVariable }
|
.partition { it.name() == THIS || it is ThisLocalVariable }
|
||||||
|
|
||||||
if (!removeSyntheticThisObject(evaluationContext, children, thisReferences) && thisReferences.isNotEmpty()) {
|
val existingVariables = getExistingVariables(thisVariables, otherVariables)
|
||||||
val thisLabels = thisReferences.asSequence()
|
|
||||||
.filterIsInstance<ThisLocalVariable>()
|
|
||||||
.mapNotNullTo(hashSetOf()) { it.label }
|
|
||||||
|
|
||||||
remapThisObjectForOuterThis(evaluationContext, children, thisLabels)
|
if (!removeSyntheticThisObject(evaluationContext, children, existingVariables) && thisVariables.isNotEmpty()) {
|
||||||
|
remapThisObjectForOuterThis(evaluationContext, children, existingVariables)
|
||||||
}
|
}
|
||||||
|
|
||||||
thisReferences.forEach(::addItem)
|
thisVariables.forEach(::addItem)
|
||||||
otherVariables.forEach(::addItem)
|
otherVariables.forEach(::addItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getExistingVariables(
|
||||||
|
thisVariables: List<LocalVariableProxyImpl>,
|
||||||
|
ordinaryVariables: List<LocalVariableProxyImpl>
|
||||||
|
): ExistingVariables {
|
||||||
|
val set = HashSet<ExistingVariable>()
|
||||||
|
thisVariables.forEach {
|
||||||
|
val thisVariable = it as? ThisLocalVariable ?: return@forEach
|
||||||
|
val label = thisVariable.label
|
||||||
|
val newExistingVariable: ExistingVariable = when {
|
||||||
|
label != null -> ExistingVariable.LabeledThis(label)
|
||||||
|
else -> ExistingVariable.UnlabeledThis
|
||||||
|
}
|
||||||
|
set.add(newExistingVariable)
|
||||||
|
}
|
||||||
|
ordinaryVariables.forEach { set += ExistingVariable.Ordinary(it.name()) }
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
private fun removeSyntheticThisObject(
|
private fun removeSyntheticThisObject(
|
||||||
evaluationContext: EvaluationContextImpl,
|
evaluationContext: EvaluationContextImpl,
|
||||||
children: XValueChildrenList,
|
children: XValueChildrenList,
|
||||||
thisReferences: List<LocalVariableProxyImpl>
|
existingVariables: ExistingVariables
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
||||||
|
|
||||||
if (thisObject.type().isSubtype(CONTINUATION_TYPE)) {
|
if (thisObject.type().isSubtype(CONTINUATION_TYPE)) {
|
||||||
ExistingInstanceThis.find(children)?.remove()
|
ExistingInstanceThisRemapper.find(children)?.remove()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
val thisObjectType = thisObject.type()
|
val thisObjectType = thisObject.type()
|
||||||
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
|
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
|
||||||
val existingThis = ExistingInstanceThis.find(children)
|
val existingThis = ExistingInstanceThisRemapper.find(children)
|
||||||
if (existingThis != null) {
|
if (existingThis != null) {
|
||||||
existingThis.remove()
|
existingThis.remove()
|
||||||
val javaValue = existingThis.value as? JavaValue
|
val containerJavaValue = existingThis.value as? JavaValue
|
||||||
if (javaValue != null) {
|
val containerValue = containerJavaValue?.descriptor?.calcValue(evaluationContext) as? ObjectReference
|
||||||
attachCapturedThisFromLambda(evaluationContext, children, javaValue, thisReferences)
|
if (containerValue != null) {
|
||||||
|
attachCapturedValues(evaluationContext, children, containerValue, existingVariables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -139,44 +133,89 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inlineDepth > 0 && declarationSiteThis != null) {
|
if (inlineDepth > 0 && declarationSiteThis != null) {
|
||||||
ExistingInstanceThis.find(children)?.remove()
|
ExistingInstanceThisRemapper.find(children)?.remove()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun attachCapturedThisFromLambda(
|
private fun attachCapturedValues(
|
||||||
evaluationContext: EvaluationContextImpl,
|
evaluationContext: EvaluationContextImpl,
|
||||||
children: XValueChildrenList,
|
children: XValueChildrenList,
|
||||||
javaValue: JavaValue,
|
containerValue: ObjectReference,
|
||||||
thisReferences: List<LocalVariableProxyImpl>
|
existingVariables: ExistingVariables
|
||||||
|
) {
|
||||||
|
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return
|
||||||
|
val capturedFields = containerValue.referenceType().safeFields()
|
||||||
|
.filter { field ->
|
||||||
|
val name = field.name()
|
||||||
|
name.startsWith(AsmUtil.CAPTURED_PREFIX) || name == AsmUtil.CAPTURED_THIS_FIELD
|
||||||
|
}
|
||||||
|
|
||||||
|
for (field in capturedFields) {
|
||||||
|
val name = field.name()
|
||||||
|
|
||||||
|
if (name == AsmUtil.CAPTURED_THIS_FIELD) {
|
||||||
|
val value = containerValue.getValue(field) as? ObjectReference ?: continue
|
||||||
|
val label = getThisValueLabel(value)
|
||||||
|
if (label != null) {
|
||||||
|
attachCapturedThis(evaluationContext, nodeManager, children, value, label, existingVariables)
|
||||||
|
} else {
|
||||||
|
attachCapturedValues(evaluationContext, children, value, existingVariables)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if (name.startsWith(AsmUtil.CAPTURED_LABELED_THIS_FIELD)) {
|
||||||
|
val value = containerValue.getValue(field)
|
||||||
|
val label = name.drop(AsmUtil.CAPTURED_LABELED_THIS_FIELD.length)
|
||||||
|
if (label.isNotEmpty()) {
|
||||||
|
attachCapturedThis(evaluationContext, nodeManager, children, value, label, existingVariables)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val capturedValueName = name.drop(1).takeIf { it.isNotEmpty() } ?: continue
|
||||||
|
|
||||||
|
if (!existingVariables.add(ExistingVariable.Ordinary(capturedValueName))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val valueData = CapturedValueData(capturedValueName, containerValue, field)
|
||||||
|
val valueDescriptor = nodeManager.getDescriptor(this.descriptor, valueData)
|
||||||
|
children.add(JavaValue.create(null, valueDescriptor, evaluationContext, nodeManager, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getThisValueLabel(thisValue: ObjectReference): String? {
|
||||||
|
val thisType = thisValue.referenceType()
|
||||||
|
val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return null
|
||||||
|
return checkLabel(unsafeLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun attachCapturedThis(
|
||||||
|
evaluationContext: EvaluationContextImpl,
|
||||||
|
nodeManager: NodeManagerImpl,
|
||||||
|
children: XValueChildrenList,
|
||||||
|
thisValue: Value?,
|
||||||
|
label: String,
|
||||||
|
existingVariables: ExistingVariables
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val value = javaValue.descriptor.calcValue(evaluationContext) as? ObjectReference ?: return
|
val hasThisVariables = existingVariables.any { it is ExistingVariable.This }
|
||||||
val thisField = value.referenceType().fieldByName(AsmUtil.CAPTURED_THIS_FIELD) ?: return
|
|
||||||
val thisValue = value.getValue(thisField) as? ObjectReference ?: return
|
|
||||||
val thisType = thisValue.referenceType()
|
|
||||||
val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return
|
|
||||||
val label = checkLabel(unsafeLabel)
|
|
||||||
|
|
||||||
if (label != null) {
|
val thisName = if (hasThisVariables) {
|
||||||
val thisName = getThisName(label)
|
if (!existingVariables.add(ExistingVariable.LabeledThis(label))) {
|
||||||
|
// Avoid item duplication
|
||||||
if (thisReferences.any { it.name() == thisName }) {
|
|
||||||
// Avoid label duplication
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getThisName(label)
|
||||||
|
} else {
|
||||||
|
existingVariables.add(ExistingVariable.LabeledThis(label))
|
||||||
|
THIS
|
||||||
}
|
}
|
||||||
|
|
||||||
val thisName = when {
|
val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(label, thisName, thisValue))
|
||||||
thisReferences.isEmpty() -> THIS
|
|
||||||
label != null -> getThisName(label)
|
|
||||||
else -> "$THIS (anonymous fun)"
|
|
||||||
}
|
|
||||||
|
|
||||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return
|
|
||||||
val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(thisName, thisValue))
|
|
||||||
children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false))
|
children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false))
|
||||||
} catch (e: EvaluateException) {
|
} catch (e: EvaluateException) {
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -186,13 +225,15 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
private fun remapThisObjectForOuterThis(
|
private fun remapThisObjectForOuterThis(
|
||||||
evaluationContext: EvaluationContextImpl,
|
evaluationContext: EvaluationContextImpl,
|
||||||
children: XValueChildrenList,
|
children: XValueChildrenList,
|
||||||
existingThisLabels: Set<String>
|
existingVariables: ExistingVariables
|
||||||
) {
|
) {
|
||||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
||||||
val variable = ExistingInstanceThis.find(children) ?: return
|
val variable = ExistingInstanceThisRemapper.find(children) ?: return
|
||||||
|
|
||||||
val thisLabel = generateThisLabel(thisObject.referenceType())?.takeIf { it !in existingThisLabels }
|
val thisLabel = generateThisLabel(thisObject.referenceType())
|
||||||
if (thisLabel == null) {
|
val thisName = thisLabel?.let(::getThisName)
|
||||||
|
|
||||||
|
if (thisName == null || !existingVariables.add(ExistingVariable.LabeledThis(thisLabel))) {
|
||||||
variable.remove()
|
variable.remove()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -201,83 +242,6 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
variable.remapName(getThisName(thisLabel))
|
variable.remapName(getThisName(thisLabel))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very Dirty Work-around.
|
|
||||||
// Hopefully, there will be an API for that in 2019.1.
|
|
||||||
private class ExistingInstanceThis(
|
|
||||||
private val children: XValueChildrenList,
|
|
||||||
private val index: Int,
|
|
||||||
val value: XValue,
|
|
||||||
private val size: Int
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
private const val THIS_NAME = "this"
|
|
||||||
|
|
||||||
fun find(children: XValueChildrenList): ExistingInstanceThis? {
|
|
||||||
val size = children.size()
|
|
||||||
for (i in 0 until size) {
|
|
||||||
if (children.getName(i) == THIS_NAME) {
|
|
||||||
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
|
||||||
@Suppress("FoldInitializerAndIfToElvis")
|
|
||||||
if (valueDescriptor !is ThisDescriptorImpl) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExistingInstanceThis(children, i, children.getValue(i), size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remapName(newName: String) {
|
|
||||||
val (names, _) = getLists() ?: return
|
|
||||||
names[index] = newName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove() {
|
|
||||||
val (names, values) = getLists() ?: return
|
|
||||||
names.removeAt(index)
|
|
||||||
values.removeAt(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLists(): Lists? {
|
|
||||||
if (children.size() != size) {
|
|
||||||
throw IllegalStateException("Children list was modified")
|
|
||||||
}
|
|
||||||
|
|
||||||
var namesList: MutableList<Any?>? = null
|
|
||||||
var valuesList: MutableList<Any?>? = null
|
|
||||||
|
|
||||||
for (field in XValueChildrenList::class.java.declaredFields) {
|
|
||||||
val mods = field.modifiers
|
|
||||||
if (Modifier.isPrivate(mods) && Modifier.isFinal(mods) && !Modifier.isStatic(mods) && field.type == List::class.java) {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val list = (field.getSafe(children) as? MutableList<Any?>)?.takeIf { it.size == size } ?: continue
|
|
||||||
|
|
||||||
if (list[index] == THIS_NAME) {
|
|
||||||
namesList = list
|
|
||||||
} else if (list[index] === value) {
|
|
||||||
valuesList = list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (namesList != null && valuesList != null) {
|
|
||||||
return Lists(namesList, valuesList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.error(
|
|
||||||
"Can't find name/value lists, existing fields: "
|
|
||||||
+ Arrays.toString(XValueChildrenList::class.java.declaredFields)
|
|
||||||
)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class Lists(val names: MutableList<Any?>, val values: MutableList<Any?>)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||||
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
||||||
|
|
||||||
@@ -302,8 +266,8 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
.sortedByDescending { it.variable }
|
.sortedByDescending { it.variable }
|
||||||
.let { it.firstOrNull() to it.drop(1) }
|
.let { it.firstOrNull() to it.drop(1) }
|
||||||
|
|
||||||
val remappedMainThis = mainThis?.clone(THIS, null)
|
val remappedMainThis = mainThis?.remapThisVariableIfNeeded(THIS)
|
||||||
val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
|
val remappedOther = (otherThis + otherVariables).map { it.remapThisVariableIfNeeded() }
|
||||||
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
|
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,23 +281,23 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
|| name == SUSPEND_FUNCTION_CONTINUATION_PARAMETER
|
|| name == SUSPEND_FUNCTION_CONTINUATION_PARAMETER
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
|
private fun LocalVariableProxyImpl.remapThisVariableIfNeeded(customName: String? = null): LocalVariableProxyImpl {
|
||||||
val name = this.name().dropInlineSuffix()
|
val name = this.name().dropInlineSuffix()
|
||||||
|
|
||||||
@Suppress("ConvertToStringTemplate")
|
@Suppress("ConvertToStringTemplate")
|
||||||
return when {
|
return when {
|
||||||
isLabeledThisReference() -> {
|
isLabeledThisReference() -> {
|
||||||
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||||
clone(getThisName(label), label)
|
clone(customName ?: getThisName(label), label)
|
||||||
}
|
}
|
||||||
name == AsmUtil.THIS_IN_DEFAULT_IMPLS -> clone(THIS + " (outer)", null)
|
name == AsmUtil.THIS_IN_DEFAULT_IMPLS -> clone(customName ?: THIS + " (outer)", null)
|
||||||
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(THIS + " (receiver)", null)
|
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(customName ?: THIS + " (receiver)", null)
|
||||||
INLINED_THIS_REGEX.matches(name) -> {
|
INLINED_THIS_REGEX.matches(name) -> {
|
||||||
val label = generateThisLabel(frame.getValue(this)?.type())
|
val label = generateThisLabel(frame.getValue(this)?.type())
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
clone(getThisName(label), label)
|
clone(customName ?: getThisName(label), label)
|
||||||
} else {
|
} else {
|
||||||
this@remapVariableNameIfNeeded
|
this@remapThisVariableIfNeeded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
name != this.name() -> {
|
name != this.name() -> {
|
||||||
@@ -341,7 +305,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
override fun name() = name
|
override fun name() = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> this@remapVariableNameIfNeeded
|
else -> this@remapThisVariableIfNeeded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,28 +363,4 @@ private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableP
|
|||||||
|
|
||||||
private fun getThisName(label: String): String {
|
private fun getThisName(label: String): String {
|
||||||
return "$THIS (@$label)"
|
return "$THIS (@$label)"
|
||||||
}
|
|
||||||
|
|
||||||
private class LabeledThisData(val name: String, val value: ObjectReference) : DescriptorData<ValueDescriptorImpl>() {
|
|
||||||
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
|
|
||||||
return object : ValueDescriptorImpl(project, value) {
|
|
||||||
override fun getName() = this@LabeledThisData.name
|
|
||||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = value
|
|
||||||
override fun canSetValue() = false
|
|
||||||
|
|
||||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression {
|
|
||||||
// TODO change to labeled this
|
|
||||||
val elementFactory = JavaPsiFacade.getElementFactory(myProject)
|
|
||||||
try {
|
|
||||||
return elementFactory.createExpressionFromText("this", null)
|
|
||||||
} catch (e: IncorrectOperationException) {
|
|
||||||
throw EvaluateException(e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(this)
|
|
||||||
override fun equals(other: Any?) = other is LabeledThisData && other.name == name
|
|
||||||
override fun hashCode() = name.hashCode()
|
|
||||||
}
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.DebuggerContext
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
|
import com.intellij.debugger.impl.descriptors.data.DescriptorData
|
||||||
|
import com.intellij.debugger.impl.descriptors.data.DisplayKey
|
||||||
|
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
|
||||||
|
import com.intellij.debugger.ui.impl.watch.*
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.JavaPsiFacade
|
||||||
|
import com.intellij.psi.PsiExpression
|
||||||
|
import com.intellij.util.IncorrectOperationException
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
|
|
||||||
|
class LabeledThisData(val label: String, val name: String, val value: Value?) : DescriptorData<ValueDescriptorImpl>() {
|
||||||
|
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
|
||||||
|
return object : ValueDescriptorImpl(project, value) {
|
||||||
|
override fun getName() = this@LabeledThisData.name
|
||||||
|
override fun calcValue(evaluationContext: EvaluationContextImpl?) = value
|
||||||
|
override fun canSetValue() = false
|
||||||
|
|
||||||
|
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression {
|
||||||
|
// TODO change to labeled this
|
||||||
|
val elementFactory = JavaPsiFacade.getElementFactory(myProject)
|
||||||
|
try {
|
||||||
|
return elementFactory.createExpressionFromText("this", null)
|
||||||
|
} catch (e: IncorrectOperationException) {
|
||||||
|
throw EvaluateException(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(this)
|
||||||
|
override fun equals(other: Any?) = other is LabeledThisData && other.name == name
|
||||||
|
override fun hashCode() = name.hashCode()
|
||||||
|
}
|
||||||
@@ -33,6 +33,14 @@ fun ReferenceType.safeSourceName(): String? {
|
|||||||
return wrapAbsentInformationException { sourceName() }
|
return wrapAbsentInformationException { sourceName() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ReferenceType.safeFields(): List<Field> {
|
||||||
|
return try {
|
||||||
|
fields()
|
||||||
|
} catch (e: ClassNotPreparedException) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
||||||
return wrapAbsentInformationException { locationsOfLine(line) } ?: emptyList()
|
return wrapAbsentInformationException { locationsOfLine(line) } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package capturedValues1
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
1000.foo(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.foo(args: Array<String>) {
|
||||||
|
val a = 1
|
||||||
|
block {
|
||||||
|
val b = 2
|
||||||
|
val b2 = 2
|
||||||
|
block("x") place1@ {
|
||||||
|
val c = 3
|
||||||
|
val c2 = 3
|
||||||
|
block("y") place2@ {
|
||||||
|
//Breakpoint!
|
||||||
|
this@place1
|
||||||
|
this@foo
|
||||||
|
b
|
||||||
|
c2
|
||||||
|
args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun block(block: () -> Unit) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> block(obj: T, block: T.() -> Unit) {
|
||||||
|
obj.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
|
// PRINT_FRAME
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
LineBreakpoint created at capturedValues1.kt:17
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
capturedValues1.kt:17
|
||||||
|
frame = invoke:17, CapturedValues1Kt$foo$1$1$1 {capturedValues1}
|
||||||
|
unknown = this (@foo) = 1000
|
||||||
|
unknown = args = {java.lang.String[0]@uniqueID}
|
||||||
|
unknown = b = 2
|
||||||
|
unknown = this (@place1) = x
|
||||||
|
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
|
||||||
|
element = 0 = 'x' 120
|
||||||
|
field = hash: int = 0 (sp = String.!EXT!)
|
||||||
|
unknown = c2 = 3
|
||||||
|
local = this: java.lang.String = y (sp = null)
|
||||||
|
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
|
||||||
|
element = 0 = 'y' 121
|
||||||
|
field = hash: int = 0 (sp = String.!EXT!)
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package capturedValues2
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
1000.foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.foo() {
|
||||||
|
block {
|
||||||
|
val b = 1
|
||||||
|
val b2 = 2
|
||||||
|
block("x") foo@ {
|
||||||
|
//Breakpoint!
|
||||||
|
this@foo + b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun block(block: () -> Unit) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> block(obj: T, block: T.() -> Unit) {
|
||||||
|
obj.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
|
// PRINT_FRAME
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
LineBreakpoint created at capturedValues2.kt:13
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
capturedValues2.kt:13
|
||||||
|
frame = invoke:13, CapturedValues2Kt$foo$1$1 {capturedValues2}
|
||||||
|
unknown = b = 1
|
||||||
|
local = this: java.lang.String = x (sp = null)
|
||||||
|
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
|
||||||
|
element = 0 = 'x' 120
|
||||||
|
field = hash: int = 0 (sp = String.!EXT!)
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
Generated
+10
@@ -685,6 +685,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("capturedValues1.kt")
|
||||||
|
public void testCapturedValues1() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/capturedValues1.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("capturedValues2.kt")
|
||||||
|
public void testCapturedValues2() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/capturedValues2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("catchVariable.kt")
|
@TestMetadata("catchVariable.kt")
|
||||||
public void testCatchVariable() throws Exception {
|
public void testCatchVariable() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/catchVariable.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/catchVariable.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user