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 a505d1efac9..dadd4b8893a 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 @@ -7,12 +7,8 @@ package org.jetbrains.kotlin.idea.debugger import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebuggerManagerThreadImpl -import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.impl.DebuggerContextImpl -import com.intellij.debugger.impl.DebuggerUtilsEx -import com.intellij.debugger.jdi.LocalVariableProxyImpl -import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.psi.PsiElement import com.sun.jdi.* import com.sun.tools.jdi.LocalVariableImpl @@ -21,6 +17,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousCl import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes +import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches @@ -33,95 +30,18 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.inline.InlineUtil +import org.jetbrains.org.objectweb.asm.Type as AsmType import java.util.* -fun StackFrameProxyImpl.visibleVariablesSafe(): List { - try { - return visibleVariables() - } catch (e: AbsentInformationEvaluateException) { - // Current implementation of visibleVariables() wraps an AbsentInformationException into EvaluateException - return emptyList() - } catch (e: AbsentInformationException) { - return emptyList() +fun calculateInlineDepth(variableName: String): Int { + var lastIndex = variableName.lastIndex + var depth = 0 + + while (variableName.lastIndexOf(INLINE_FUN_VAR_SUFFIX, startIndex = lastIndex) >= 0) { + lastIndex -= INLINE_FUN_VAR_SUFFIX.length + depth++ } -} - -fun Method.safeAllLineLocations(): List { - return DebuggerUtilsEx.allLineLocations(this) ?: emptyList() -} - -fun ReferenceType.safeAllLineLocations(): List { - return DebuggerUtilsEx.allLineLocations(this) ?: emptyList() -} - -fun Method.safeLocationsOfLine(line: Int): List { - return try { - locationsOfLine(line) - } catch (e: AbsentInformationException) { - emptyList() - } -} - -fun Method.safeVariables(): List? { - return try { - variables() - } catch (e: AbsentInformationException) { - null - } -} - -fun Method.safeArguments(): List? { - return try { - arguments() - } catch (e: AbsentInformationException) { - null - } -} - -fun Method.safeReturnType(): Type? { - return try { - returnType() - } catch (e: ClassNotLoadedException) { - null - } -} - -fun LocalVariable.safeType(): Type? { - return try { - return type() - } catch (e: ClassNotLoadedException) { - null - } -} - -fun Location.safeSourceName(): String? { - return try { - sourceName() - } catch (e: AbsentInformationException) { - null - } catch (e: InternalError) { - null - } -} - -fun Location.safeLineNumber(): Int { - return DebuggerUtilsEx.getLineNumber(this, false) -} - -fun Location.safeSourceLineNumber(): Int { - return DebuggerUtilsEx.getLineNumber(this, true) -} - -fun Location.safeMethod(): Method? { - return DebuggerUtilsEx.getMethod(this) -} - -fun isInsideInlineFunctionBody(visibleVariables: List): Boolean { - return visibleVariables.any { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } -} - -fun numberOfInlinedFunctions(visibleVariables: List): Int { - return visibleVariables.count { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } + return depth } fun isInsideInlineArgument( @@ -340,4 +260,32 @@ fun PropertyDescriptor.getBackingFieldName(): String? { val jvmNameAnnotation = DescriptorUtils.findJvmNameAnnotation(this) ?: return name.asString() return jvmNameAnnotation.allValueArguments.values.singleOrNull()?.toString() -} \ No newline at end of file +} + +fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className)) + +fun Type.isSubtype(type: AsmType): Boolean { + if (this.signature() == type.descriptor) { + return true + } + + if (type.sort != AsmType.OBJECT || this !is ClassType) { + return false + } + + val superTypeName = type.className + + if (allInterfaces().any { it.name() == superTypeName }) { + return true + } + + var superClass = superclass() + while (superClass != null) { + if (superClass.name() == superTypeName) { + return true + } + superClass = superClass.superclass() + } + + return false +} 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 deleted file mode 100644 index 5a77c9680f5..00000000000 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * 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.evaluate - -import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil -import com.intellij.debugger.engine.evaluation.EvaluationContextImpl -import com.intellij.openapi.diagnostic.Attachment -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 -import org.jetbrains.kotlin.idea.debugger.DebuggerUtils -import org.jetbrains.kotlin.idea.debugger.isInsideInlineFunctionBody -import org.jetbrains.kotlin.idea.debugger.numberOfInlinedFunctions -import org.jetbrains.kotlin.idea.util.application.runReadAction -import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.jvm.AsmTypes -import org.jetbrains.kotlin.resolve.jvm.JvmClassName -import org.jetbrains.org.objectweb.asm.Type - -class FrameVisitor(val context: EvaluationContextImpl) { - private val scope = context.debugProcess.searchScope - private val frame = context.frameProxy - - companion object { - val OBJECT_TYPE = Type.getType(Any::class.java) - } - - fun findValue(name: String, asmType: Type?, checkType: Boolean, failIfNotFound: Boolean): Value? { - val frame = this.frame?.stackFrame ?: return null - - try { - when (name) { - THIS_NAME -> { - val thisValue = findThis(frame, asmType) - if (thisValue != null) { - return thisValue - } - } - else -> { - if (isInsideInlineFunctionBody(frame.visibleVariables())) { - val number = numberOfInlinedFunctions(frame.visibleVariables()) - for (inlineFunctionIndex in number downTo 1) { - val inlineFunVar = findLocalVariableForInlineArgument(name, inlineFunctionIndex, asmType, true) - if (inlineFunVar != null) { - return inlineFunVar - } - } - } - - if (isFunctionType(asmType)) { - val variableForLocalFun = findLocalVariableForLocalFun(name, asmType, checkType) - if (variableForLocalFun != null) { - return variableForLocalFun - } - } - - val localVariable = findLocalVariable(name, asmType, checkType) - - if (localVariable != null) { - return localVariable - } - - getCapturedFieldNames(name).asSequence() - .mapNotNull { findCapturedLocalVariable(it, asmType, checkType) } - .firstOrNull() - ?.let { return it } - } - } - - return fail("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}", failIfNotFound) - } - catch(e: InvalidStackFrameException) { - throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame") - } - } - - private fun fail(message: String, shouldFail: Boolean): Value? { - if (!shouldFail) { - return null - } - - val location = frame?.location() - - val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available" - - val sourceName = location?.sourceName() - val declaringTypeName = location?.declaringType()?.name()?.replace('.', '/')?.let { JvmClassName.byInternalName(it) } - - val sourceFile = if (sourceName != null && declaringTypeName != null) { - DebuggerUtils.findSourceFileForClassIncludeLibrarySources(context.project, scope, declaringTypeName, sourceName, location) - } else { - null - } - - val sourceFileText = runReadAction { sourceFile?.text } - - if (sourceName != null && sourceFileText != null) { - val attachments = mergeAttachments(Attachment(sourceName, sourceFileText), Attachment("location.txt", locationText)) - LOG.error(message, attachments) - } - - throw EvaluateExceptionUtil.createEvaluateException(message) - } - - 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) { - return inlineFunVar - } - } - - // 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 - } - - 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` - - return null - } - - private fun findLocalVariableForLocalFun(name: String, asmType: Type?, checkType: Boolean): Value? { - return findLocalVariable(name + "$", asmType, checkType) - } - - private fun findLocalVariableForInlineArgument(name: String, number: Int, asmType: Type?, checkType: Boolean): Value? { - 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) - } - - private fun findLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? { - val localVariable = frame!!.visibleVariableByName(name) ?: return null - - val eval4jValue = frame.getValue(localVariable).asValue() - val sharedVarValue = getValueIfSharedVar(eval4jValue, asmType, checkType) - if (sharedVarValue != null) { - return sharedVarValue - } - - if (isValueOfCorrectType(eval4jValue, asmType, checkType)) { - return eval4jValue - } - - return null - } - - private fun findCapturedLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? { - val thisObject = frame?.thisObject() ?: return null - - var thisObj: Value? = thisObject.asValue() - var capturedVal: Value? = null - while (capturedVal == null && thisObj != null) { - capturedVal = getField(thisObj, name, asmType, checkType) - if (capturedVal == null) { - thisObj = getField(thisObj, AsmUtil.CAPTURED_THIS_FIELD, null, false) - } - } - - if (capturedVal != null) { - val sharedVarValue = getValueIfSharedVar(capturedVal, asmType, checkType) - if (sharedVarValue != null) { - return sharedVarValue - } - return capturedVal - } - - return null - } - - private fun isValueOfCorrectType(value: Value, asmType: Type?, shouldCheckType: Boolean): Boolean { - if (!shouldCheckType || asmType == null || value.asmType == asmType) return true - - if (asmType == OBJECT_TYPE) return true - - if ((value.obj() as? com.sun.jdi.ObjectReference)?.referenceType().isSubclass(asmType.className)) { - return true - } - - val thisDesc = value.asmType.getClassDescriptor(scope) - val expDesc = asmType.getClassDescriptor(scope) - return thisDesc != null && expDesc != null && runReadAction { DescriptorUtils.isSubclass(thisDesc, expDesc) } - } - - private fun getField(owner: Value, name: String, asmType: Type?, checkType: Boolean): Value? { - try { - val obj = owner.asJdiValue(frame!!.virtualMachine.virtualMachine, owner.asmType) - if (obj !is ObjectReference) return null - - val _class = obj.referenceType() - val field = _class.fieldByName(name) ?: return null - - val fieldValue = obj.getValue(field).asValue() - if (isValueOfCorrectType(fieldValue, asmType, checkType)) return fieldValue - return null - } - catch (e: Exception) { - return null - } - } - - private fun Value.isSharedVar(): Boolean { - return this.asmType.sort == Type.OBJECT && this.asmType.internalName.startsWith(AsmTypes.REF_TYPE_PREFIX) - } - - fun getValueIfSharedVar(value: Value, expectedType: Type?, checkType: Boolean): Value? { - if (!value.isSharedVar()) return null - - val sharedVarValue = getField(value, "element", expectedType, checkType) - if (sharedVarValue != null && isValueOfCorrectType(sharedVarValue, expectedType, checkType)) { - return sharedVarValue - } - return null - } - - private fun getCapturedFieldNames(name: String): List = when (name) { - RECEIVER_NAME -> listOf(AsmUtil.CAPTURED_RECEIVER_FIELD) - THIS_NAME -> listOf(AsmUtil.CAPTURED_THIS_FIELD) - AsmUtil.CAPTURED_RECEIVER_FIELD -> listOf(name) - AsmUtil.CAPTURED_THIS_FIELD -> listOf(name) - else -> { - val simpleName = "$$name" - listOf(simpleName, simpleName + INLINE_TRANSFORMATION_SUFFIX) - } - } - - private fun com.sun.jdi.Type?.isSubclass(superClassName: String): Boolean { - if (this !is ClassType) return false - if (allInterfaces().any { it.name() == superClassName }) { - return true - } - - var superClass = this.superclass() - while (superClass != null) { - if (superClass.name() == superClassName) { - return true - } - superClass = superClass.superclass() - } - return false - } -} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt index 5ff452f5854..9bf908b4cef 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt @@ -32,6 +32,7 @@ import com.intellij.util.containers.MultiMap import org.apache.log4j.Logger import org.jetbrains.annotations.TestOnly import org.jetbrains.eval4j.Value +import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.state.GenerationState @@ -206,11 +207,12 @@ class KotlinDebuggerCaches(project: Project) { } private fun canBeEvaluatedInThisContext(compiledData: CompiledDataDescriptor, context: EvaluationContextImpl): Boolean { - val frameVisitor = FrameVisitor(context) + val variableFinder = VariableFinder.instance(context) ?: return false + return compiledData.parameters.all { p -> val (name, jetType) = p - val value = frameVisitor.findValue(name, asmType = null, checkType = false, failIfNotFound = false) - if (value == null) return@all false + val lookupResult = variableFinder.find(name, null) ?: return@all false + val value = lookupResult.value.asValue() val thisDescriptor = value.asmType.getClassDescriptor(context.debugProcess.searchScope) val superClassDescriptor = jetType.constructor.declarationDescriptor as? ClassDescriptor diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index 6d3ca00a616..5465f123f47 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -50,6 +50,8 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.codegen.* +import org.jetbrains.kotlin.codegen.AsmUtil.LABELED_THIS_PARAMETER +import org.jetbrains.kotlin.codegen.AsmUtil.RECEIVER_PARAMETER_NAME import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.config.CompilerConfiguration @@ -93,7 +95,6 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode import org.jetbrains.org.objectweb.asm.tree.MethodNode import java.util.* -internal val RECEIVER_NAME = "\$receiver" internal val THIS_NAME = "this" internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator") internal val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun" @@ -411,7 +412,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } if (!AsmUtil.isPrimitive(parameterType) && AsmUtil.isPrimitive(argumentType)) { - if (parameterType == FrameVisitor.OBJECT_TYPE || parameterType == AsmUtil.boxType(argumentType)) { + if (parameterType.descriptor == "Ljava/lang/Object;" || parameterType == AsmUtil.boxType(argumentType)) { return eval.boxType(argumentValue) } } @@ -437,8 +438,14 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } val vm = context.debugProcess.virtualMachineProxy.virtualMachine - val sharedVar = FrameVisitor(context).getValueIfSharedVar(jdiValue, jdiValue.asmType, false) - return sharedVar?.asJdiValue(vm, sharedVar.asmType) ?: jdiValue.asJdiValue(vm, jdiValue.asmType) + + val sharedVar = getValueIfSharedVar(jdiValue) + return sharedVar?.value ?: jdiValue.asJdiValue(vm, jdiValue.asmType) + } + + private fun getValueIfSharedVar(value: Value): VariableFinder.Result? { + val obj = value.obj(value.asmType) as? ObjectReference ?: return null + return VariableFinder.Result(VariableFinder.unwrapRefValue(obj)) } private fun ExtractionResult.getParametersForDebugger( @@ -505,12 +512,11 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour parameters: List, parameterTypes: Array ): List { - val frameVisitor = FrameVisitor(this) + val variableFinder = VariableFinder.instance(this) ?: error("No stack frame available") return parameters.zip(parameterTypes).map { (parameter, type) -> parameter.error?.let { throw it } - val result = parameter.value - ?: frameVisitor.findValue(parameter.callText, type, checkType = false, failIfNotFound = true)!! + val result = parameter.value ?: variableFinder.get(parameter.callText, type).asValue() if (LOG.isDebugEnabled) { LOG.debug("Parameter for eval4j: name = ${parameter.callText}, type = $type, value = $result") @@ -549,10 +555,14 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour CompilerConfiguration.EMPTY ).generateDeclaredClassFilter(generateClassFilter).build() - val frameVisitor = FrameVisitor(context) + val variableFinder = VariableFinder.instance(context) ?: error("No stack frame available") + + val extractedFunctionName = extractedFunction.name + ?: error("Extracted function has an empty name: ${extractedFunction.text}") extractedFunction.receiverTypeReference?.let { - state.bindingTrace.recordAnonymousType(it, THIS_NAME, frameVisitor) + val name = AsmUtil.getLabeledThisName(extractedFunctionName, LABELED_THIS_PARAMETER, RECEIVER_PARAMETER_NAME) + state.bindingTrace.recordAnonymousType(it, name, variableFinder) } val valueParameters = extractedFunction.valueParameters @@ -569,7 +579,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour exception("An exception occurs during Evaluate Expression Action") } - state.bindingTrace.recordAnonymousType(paramRef, param.callText, frameVisitor) + state.bindingTrace.recordAnonymousType(paramRef, param.callText, variableFinder) } KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION) @@ -578,13 +588,19 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } } - private fun BindingTrace.recordAnonymousType(typeReference: KtTypeReference, localVariableName: String, visitor: FrameVisitor) { + private fun BindingTrace.recordAnonymousType( + typeReference: KtTypeReference, + localVariableName: String, + variableFinder: VariableFinder + ) { val paramAnonymousType = typeReference.debugTypeInfo if (paramAnonymousType != null) { val declarationDescriptor = paramAnonymousType.constructor.declarationDescriptor if (declarationDescriptor is ClassDescriptor) { - val localVariable = visitor.findValue(localVariableName, asmType = null, checkType = false, failIfNotFound = false) - ?: exception("Couldn't find local variable this in current frame to get classType for anonymous type $paramAnonymousType}") + val lookupResult = variableFinder.find(localVariableName, null) + ?: exception("Couldn't find local variable this in current frame to get classType for anonymous type $paramAnonymousType}") + val localVariable = lookupResult.value.asValue() + record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType) if (LOG.isDebugEnabled) { LOG.debug("Asm type ${localVariable.asmType.className} was recorded for ${declarationDescriptor.name}") diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt new file mode 100644 index 00000000000..5e211f3358b --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt @@ -0,0 +1,342 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.evaluate + +import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl +import com.intellij.debugger.jdi.LocalVariableProxyImpl +import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.intellij.openapi.diagnostic.Attachment +import com.sun.jdi.* +import org.jetbrains.kotlin.codegen.AsmUtil +import org.jetbrains.kotlin.codegen.AsmUtil.getCapturedFieldName +import org.jetbrains.kotlin.codegen.AsmUtil.getLabeledThisName +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 +import org.jetbrains.kotlin.codegen.topLevelClassAsmType +import org.jetbrains.kotlin.idea.debugger.* +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments +import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT +import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION +import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType +import org.jetbrains.org.objectweb.asm.Type as AsmType +import com.sun.jdi.Type as JdiType + +class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) { + companion object { + fun instance(context: EvaluationContextImpl): VariableFinder? { + val frameProxy = context.frameProxy ?: return null + return VariableFinder(context, frameProxy) + } + + fun unwrapRefValue(value: ObjectReference): Value? { + return NamedEntity("", value.type()) { value }.unwrap().value() + } + + private val inlinedThisRegex = getLocalVariableNameRegexInlineAware(AsmUtil.THIS + "_") + + private fun getCapturedVariableNameRegex(capturedName: String): Regex { + val escapedName = Regex.escape(capturedName) + val escapedSuffix = Regex.escape(INLINE_TRANSFORMATION_SUFFIX) + return Regex("^$escapedName(?:$escapedSuffix)?$") + } + + private fun getLocalVariableNameRegexInlineAware(name: String): Regex { + val escapedName = Regex.escape(name) + val escapedSuffix = Regex.escape(INLINE_FUN_VAR_SUFFIX) + return Regex("^$escapedName(?:$escapedSuffix)*$") + } + + private fun AsmType.isFunctionType(): Boolean { + return sort == AsmType.OBJECT && internalName.startsWith(NUMBERED_FUNCTION_PREFIX) + } + + fun getInlineDepth(variables: List): Int { + val inlineFunVariables = variables.filter { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } + if (inlineFunVariables.isEmpty()) { + return 0 + } + + val closestInlineFun = inlineFunVariables.maxBy { it.variable }!!.variable + val inlineLambdaDepth = variables + .count { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) && it.variable > closestInlineFun } + + return maxOf(0, inlineFunVariables.size - inlineLambdaDepth) + } + + fun getInlineDepth(variableName: String): Int { + var endIndex = variableName.length + var depth = 0 + + val suffixLen = INLINE_FUN_VAR_SUFFIX.length + while (endIndex >= suffixLen) { + if (variableName.substring(endIndex - suffixLen, endIndex) != INLINE_FUN_VAR_SUFFIX) { + break + } + + depth++ + endIndex -= suffixLen + } + + return depth + } + } + + sealed class VariableKind(val type: AsmType?) { + abstract fun capturedNameMatches(name: String): Boolean + + class Ordinary(val name: String, type: AsmType?) : VariableKind(type) { + private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name)) + override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name) + } + + class UnlabeledThis(type: AsmType?) : VariableKind(type) { + override fun capturedNameMatches(name: String) = + (name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(AsmUtil.getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))) + } + + class LabeledThis(val label: String, type: AsmType?) : VariableKind(type) { + private val capturedNameRegex = getCapturedVariableNameRegex( + getCapturedFieldName(getLabeledThisName(label, AsmUtil.LABELED_THIS_FIELD, AsmUtil.CAPTURED_RECEIVER_FIELD)) + ) + + override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name) + } + + fun typeMatches(type: JdiType?): Boolean { + if (type == null) return true + val asmType = this.type ?: return true + + // Main path + if (asmType.descriptor == "Ljava/lang/Object;" || type.isSubtype(asmType)) { + return true + } + + // The latter is for boxing interventions + fun box(desc: String) = JvmPrimitiveType.getByDesc(desc)?.wrapperFqName?.topLevelClassAsmType()?.descriptor + + val asmTypeDescriptor = asmType.descriptor + val jdiTypeDescriptor = type.signature() + + val boxedAsmType = box(asmTypeDescriptor) ?: asmTypeDescriptor + val boxedJdiType = box(jdiTypeDescriptor) ?: jdiTypeDescriptor + return boxedAsmType == boxedJdiType + } + } + + class Result(val value: Value?) + + private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) { + companion object { + fun of(field: Field, owner: ObjectReference): NamedEntity { + return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }.unwrap() + } + + fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity { + return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }.unwrap() + } + } + + fun unwrap(): NamedEntity { + if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) { + return this + } + + val obj = this.value() as? ObjectReference ?: return this + val field = type.fieldByName("element") ?: return this + + val unwrappedValue = obj.getValue(field) + val unwrappedType = if (field.type() is PrimitiveType) field.type() else unwrappedValue?.type() + return NamedEntity(name, unwrappedType) { unwrappedValue } + } + } + + fun get(name: String, type: AsmType?): Value? { + val result = find(name, type) ?: throw variableNotFound(buildString { + append("Cannot find local variable: name = '").append(name).append("'") + if (type != null) { + append(", type = " + type.className) + } + }) + + return result.value + } + + fun find(name: String, type: AsmType?): Result? { + return when { + name.startsWith(AsmUtil.THIS + "@") -> { + val label = name.drop(AsmUtil.THIS.length + 1).also { require(it.isNotEmpty()) { "Invalid name '$name'" } } + findLabeledThis(VariableKind.LabeledThis(label, type)) + } + name == AsmUtil.THIS -> findUnlabeledThis(VariableKind.UnlabeledThis(type)) + else -> findOrdinary(VariableKind.Ordinary(name, type)) + } + } + + private fun findOrdinary(kind: VariableKind.Ordinary): Result? { + val variables = frameProxy.safeVisibleVariables() + + // Local variables – direct search + findLocalVariable(variables, kind, kind.name)?.let { return it } + + // Recursive search in local receiver variables + findCapturedVariableInReceiver(variables, kind)?.let { return it } + + // Recursive search in captured this + val containingThis = frameProxy.thisObject() ?: return null + return findCapturedVariable(kind, containingThis) + } + + private fun findLabeledThis(kind: VariableKind.LabeledThis): Result? { + val variables = frameProxy.safeVisibleVariables() + + // Local variables – direct search + findLocalVariable(variables, kind, AsmUtil.LABELED_THIS_PARAMETER + kind.label)?.let { return it } + + // Recursive search in local receiver variables + findCapturedVariableInReceiver(variables, kind)?.let { return it } + + // Recursive search in captured this + val containingThis = frameProxy.thisObject() + if (containingThis != null) { + findCapturedVariable(kind, containingThis)?.let { return it } + } + + // Fallback: find an unlabeled this with the compatible type + return findUnlabeledThis(VariableKind.UnlabeledThis(kind.type)) + } + + private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? { + val variables = frameProxy.safeVisibleVariables() + + // Recursive search in local receiver variables + findCapturedVariableInReceiver(variables, kind)?.let { return it } + + val containingThis = frameProxy.thisObject() ?: return null + return findCapturedVariable(kind, containingThis) + } + + private fun findLocalVariable(variables: List, kind: VariableKind, name: String): Result? { + variables.namedEntitySequence() + .filter { it.name == name && kind.typeMatches(it.type) } + .firstOrNull() + ?.let { return Result(it.value()) } + + val canBeLocalFunction = kind is VariableKind.Ordinary + + if (canBeLocalFunction && kind.type?.isFunctionType() == true) { + @Suppress("ConvertToStringTemplate") + variables.namedEntitySequence() + .filter { it.name == name + "$" && kind.typeMatches(it.type) } + .firstOrNull() + ?.let { return Result(it.value()) } + } + + val nameInlineAwareRegex = getLocalVariableNameRegexInlineAware(name) + + val inlineDepth = getInlineDepth(variables) + variables.namedEntitySequence() + .filter { it.name.matches(nameInlineAwareRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) } + .toList() // Sorted by will make a list anyway + .firstOrNull() + ?.let { return Result(it.value()) } + + return null + } + + private fun findCapturedVariableInReceiver(variables: List, kind: VariableKind): Result? { + fun isReceiverOrPassedThis(name: String) = + name.startsWith(AsmUtil.LABELED_THIS_PARAMETER) + || name == AsmUtil.RECEIVER_PARAMETER_NAME + || name == AsmUtil.getCapturedFieldName(AsmUtil.THIS) + || inlinedThisRegex.matches(name) // org.jetbrains.kotlin.codegen.inline.MethodInliner.prepareNode + + if (kind is VariableKind.LabeledThis) { + variables.namedEntitySequence() + .filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) } + .firstOrNull() + ?.let { return Result(it.value()) } + } + + return variables.namedEntitySequence() + .filter { isReceiverOrPassedThis(it.name) && it.type is ReferenceType? } + .mapNotNull { findCapturedVariable(kind, it.value()) } + .firstOrNull() + } + + private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? { + if (parent !is ObjectReference) return null + + if (kind is VariableKind.UnlabeledThis && kind.typeMatches(parent.type())) { + return Result(parent) + } + + val fields = parent.referenceType().fields() + + // Captured variables - direct search + fields.namedEntitySequence(parent) + .filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) } + .firstOrNull() + ?.let { return Result(it.value()) } + + // Recursive search in captured receivers + fields.namedEntitySequence(parent) + .filter { isCapturedReceiverFieldName(it.name) && it.type is ReferenceType? } + .mapNotNull { findCapturedVariable(kind, it.value()) } + .firstOrNull() + ?.let { return it } + + // Recursive search in outer and captured this + fields.namedEntitySequence(parent) + .filter { it.name == AsmUtil.getCapturedFieldName(AsmUtil.THIS) || it.name == AsmUtil.CAPTURED_THIS_FIELD } + .filter { it.type is ReferenceType? } + .firstOrNull() + ?.let { return findCapturedVariable(kind, it.value()) } + + return null + } + + private fun isCapturedReceiverFieldName(name: String): Boolean { + return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)) + || name == AsmUtil.CAPTURED_RECEIVER_FIELD + } + + private fun variableNotFound(message: String): Exception { + val location = frameProxy.location() + val scope = context.debugProcess.searchScope + + val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available" + + val sourceName = location?.sourceName() + val declaringTypeName = location?.declaringType()?.name()?.replace('.', '/')?.let { JvmClassName.byInternalName(it) } + + val sourceFile = if (sourceName != null && declaringTypeName != null) { + DebuggerUtils.findSourceFileForClassIncludeLibrarySources(context.project, scope, declaringTypeName, sourceName, location) + } else { + null + } + + val sourceFileText = runReadAction { sourceFile?.text } + + if (sourceName != null && sourceFileText != null) { + val attachments = mergeAttachments( + Attachment(sourceName, sourceFileText), + Attachment("location.txt", locationText) + ) + + LOG.error(message, attachments) + } + + return EvaluateExceptionUtil.createEvaluateException(message) + } + + private fun List.namedEntitySequence(owner: ObjectReference) = asSequence().map { NamedEntity.of(it, owner) } + private fun List.namedEntitySequence() = asSequence().map { NamedEntity.of(it, frameProxy) } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt new file mode 100644 index 00000000000..a2d9d789a8c --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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 + +import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException +import com.intellij.debugger.impl.DebuggerUtilsEx +import com.intellij.debugger.jdi.LocalVariableProxyImpl +import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.sun.jdi.* + +fun StackFrameProxyImpl.safeVisibleVariables(): List { + return try { + visibleVariables() + } catch (e: AbsentInformationEvaluateException) { + // Current implementation of visibleVariables() wraps an AbsentInformationException into EvaluateException + emptyList() + } catch (e: AbsentInformationException) { + emptyList() + } +} + +fun Method.safeAllLineLocations(): List { + return DebuggerUtilsEx.allLineLocations(this) ?: emptyList() +} + +fun ReferenceType.safeAllLineLocations(): List { + return DebuggerUtilsEx.allLineLocations(this) ?: emptyList() +} + +fun Method.safeLocationsOfLine(line: Int): List { + return try { + locationsOfLine(line) + } catch (e: AbsentInformationException) { + emptyList() + } +} + +fun Method.safeVariables(): List? { + return try { + variables() + } catch (e: AbsentInformationException) { + null + } +} + +fun Method.safeArguments(): List? { + return try { + arguments() + } catch (e: AbsentInformationException) { + null + } +} + +fun Location.safeSourceName(): String? { + return try { + sourceName() + } catch (e: AbsentInformationException) { + null + } catch (e: InternalError) { + null + } +} + +fun Location.safeLineNumber(): Int { + return DebuggerUtilsEx.getLineNumber(this, false) +} + +fun Location.safeSourceLineNumber(): Int { + return DebuggerUtilsEx.getLineNumber(this, true) +} + +fun Location.safeMethod(): Method? { + return DebuggerUtilsEx.getMethod(this) +} + +fun LocalVariableProxyImpl.safeType(): Type? { + return try { + type + } catch (e: ClassNotLoadedException) { + null + } +} + +fun Field.safeType(): Type? { + return try { + type() + } catch (e: ClassNotLoadedException) { + null + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt index 356ac09bf16..36a23bfce02 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -523,7 +523,7 @@ private fun SuspendContextImpl.getNextPositionWithFilter( } fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List { - return stackFrame.visibleVariablesSafe() + return stackFrame.safeVisibleVariables() .filter { val name = it.name() name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.out index 45df8f3754c..311943fa860 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.out @@ -41,11 +41,9 @@ Compile bytecode for extClass.testCompPrivate() extensionMemberFunction.kt:88 Compile bytecode for testCompPrivate() extensionMemberFunction.kt:98 -Compile bytecode for testCompPublic() extensionMemberFunction.kt:103 Compile bytecode for this.testCompPublic() extensionMemberFunction.kt:108 -Compile bytecode for testCompPrivate() extensionMemberFunction.kt:113 Compile bytecode for this.testCompPrivate() Disconnected from the target VM diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.out index be7fb158286..5f6d992204f 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberProperty.out @@ -41,11 +41,9 @@ Compile bytecode for extClass.testCompPrivate extensionMemberProperty.kt:92 Compile bytecode for testCompPrivate extensionMemberProperty.kt:102 -Compile bytecode for testCompPublic extensionMemberProperty.kt:107 Compile bytecode for this.testCompPublic extensionMemberProperty.kt:112 -Compile bytecode for testCompPrivate extensionMemberProperty.kt:117 Compile bytecode for this.testCompPrivate Disconnected from the target VM diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt index 82c14795646..2e0678140c8 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt @@ -23,7 +23,7 @@ class Outer { // outer isn't captured in lambda lambda { // EXPRESSION: foo() + 2 - // RESULT: java.lang.AssertionError : Cannot find local variable: name = this + // RESULT: java.lang.AssertionError : Cannot find local variable: name = 'this@Outer', type = funFromOuterClassInLamdba.Outer //Breakpoint! val a = 1 } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt index 033cbccbc66..09b08ca9cdd 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt @@ -16,7 +16,7 @@ fun main(args: Array) { fun myLocalFun3() { // EXPRESSION: myLocalFun1() + 1 - // RESULT: java.lang.AssertionError : Cannot find local variable: name = myLocalFun1 + // RESULT: java.lang.AssertionError : Cannot find local variable: name = 'myLocalFun1', type = kotlin.jvm.functions.Function0 //Breakpoint! myLocalFun1() + 1 } @@ -56,7 +56,7 @@ fun main(args: Array) { i = 1 fun myLocalFun7() { // EXPRESSION: myLocalFun6() + 1 - // RESULT: java.lang.AssertionError : Cannot find local variable: name = myLocalFun6 + // RESULT: java.lang.AssertionError : Cannot find local variable: name = 'myLocalFun6', type = kotlin.jvm.functions.Function0 //Breakpoint! myLocalFun6() + 1 } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.kt index 5b181bfbf4d..803f555aa33 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.kt @@ -15,4 +15,4 @@ fun foo(f: () -> Unit) { // PRINT_FRAME // EXPRESSION: val1 -// RESULT: java.lang.AssertionError : Cannot find local variable: name = val1 \ No newline at end of file +// RESULT: java.lang.AssertionError : Cannot find local variable: name = 'val1', type = int \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.out index 3216889f5d7..6755033c7b3 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameLambdaNotUsed.out @@ -20,7 +20,7 @@ fun foo(f: () -> Unit) { // PRINT_FRAME // EXPRESSION: val1 -// RESULT: java.lang.AssertionError : Cannot find local variable: name = val1 +// RESULT: java.lang.AssertionError : Cannot find local variable: name = 'val1', type = int frame = invoke:7, FrameLambdaNotUsedKt$main$1 {frameLambdaNotUsed} this = this = {frameLambdaNotUsed.FrameLambdaNotUsedKt$main$1@uniqueID}Function0 field = arity: int = 0 (sp = Lambda.!EXT!)