Evaluator: New implementation for variable finder
Support both old and new receiver variable conventions in variable finder.
This commit is contained in:
@@ -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<LocalVariableProxyImpl> {
|
||||
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<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
|
||||
fun ReferenceType.safeAllLineLocations(): List<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
|
||||
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
||||
return try {
|
||||
locationsOfLine(line)
|
||||
} catch (e: AbsentInformationException) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun Method.safeVariables(): List<LocalVariable>? {
|
||||
return try {
|
||||
variables()
|
||||
} catch (e: AbsentInformationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Method.safeArguments(): List<LocalVariable>? {
|
||||
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<LocalVariable>): Boolean {
|
||||
return visibleVariables.any { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
|
||||
}
|
||||
|
||||
fun numberOfInlinedFunctions(visibleVariables: List<LocalVariable>): 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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<String> = 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
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -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
|
||||
|
||||
+29
-13
@@ -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<Parameter>,
|
||||
parameterTypes: Array<Type>
|
||||
): List<Value> {
|
||||
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}")
|
||||
|
||||
@@ -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("<nameForUnwrapOnly>", 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<LocalVariableProxyImpl>): 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<LocalVariableProxyImpl>, 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<LocalVariableProxyImpl>, 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<Field>.namedEntitySequence(owner: ObjectReference) = asSequence().map { NamedEntity.of(it, owner) }
|
||||
private fun List<LocalVariableProxyImpl>.namedEntitySequence() = asSequence().map { NamedEntity.of(it, frameProxy) }
|
||||
}
|
||||
@@ -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<LocalVariableProxyImpl> {
|
||||
return try {
|
||||
visibleVariables()
|
||||
} catch (e: AbsentInformationEvaluateException) {
|
||||
// Current implementation of visibleVariables() wraps an AbsentInformationException into EvaluateException
|
||||
emptyList()
|
||||
} catch (e: AbsentInformationException) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun Method.safeAllLineLocations(): List<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
|
||||
fun ReferenceType.safeAllLineLocations(): List<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
|
||||
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
||||
return try {
|
||||
locationsOfLine(line)
|
||||
} catch (e: AbsentInformationException) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun Method.safeVariables(): List<LocalVariable>? {
|
||||
return try {
|
||||
variables()
|
||||
} catch (e: AbsentInformationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Method.safeArguments(): List<LocalVariable>? {
|
||||
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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -523,7 +523,7 @@ private fun SuspendContextImpl.getNextPositionWithFilter(
|
||||
}
|
||||
|
||||
fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> {
|
||||
return stackFrame.visibleVariablesSafe()
|
||||
return stackFrame.safeVisibleVariables()
|
||||
.filter {
|
||||
val name = it.name()
|
||||
name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
|
||||
|
||||
Vendored
-2
@@ -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
|
||||
|
||||
Vendored
-2
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ fun main(args: Array<String>) {
|
||||
|
||||
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<String>) {
|
||||
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
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ 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
|
||||
+1
-1
@@ -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<kotlin.Unit>
|
||||
field = arity: int = 0 (sp = Lambda.!EXT!)
|
||||
|
||||
Reference in New Issue
Block a user