From 35bacb4ac750d4312fe1de36e738a7df44ca4022 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Tue, 28 Apr 2015 10:54:07 +0300 Subject: [PATCH] Refactoring: extract FrameVisitor class --- .../idea/debugger/evaluate/FrameVisitor.kt | 200 ++++++++++++++++++ .../evaluate/KotlinEvaluateExpressionCache.kt | 3 +- .../evaluate/KotlinEvaluationBuilder.kt | 175 ++------------- 3 files changed, 216 insertions(+), 162 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt new file mode 100644 index 00000000000..f3935d06d0b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt @@ -0,0 +1,200 @@ +/* + * 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.sun.jdi.ClassType +import com.sun.jdi.InvalidStackFrameException +import com.sun.jdi.ObjectReference +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.idea.util.application.runReadAction +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.org.objectweb.asm.Type + +class FrameVisitor(context: EvaluationContextImpl) { + private val project = context.getDebugProcess().getProject() + private val frame = context.getFrameProxy()?.getStackFrame() + + public fun findValue(name: String, asmType: Type?, checkType: Boolean, failIfNotFound: Boolean): Value? { + if (frame == null) return null + + try { + when (name) { + THIS_NAME -> { + val thisValue = findThis(asmType) + if (thisValue != null) { + return thisValue + } + } + else -> { + val localVariable = findLocalVariable(name, asmType, checkType) + if (localVariable != null) { + return localVariable + } + + val capturedValName = getCapturedFieldName(name) + val capturedVal = findCapturedLocalVariable(capturedValName, asmType, checkType) + if (capturedVal != null) { + return capturedVal + } + } + } + + return if (!failIfNotFound) + null + else + throw EvaluateExceptionUtil.createEvaluateException("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}") + } + catch(e: InvalidStackFrameException) { + throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame") + } + } + + private fun findThis(asmType: Type?): Value? { + val thisObject = frame!!.thisObject() + if (thisObject != null) { + val eval4jValue = thisObject.asValue() + if (isValueOfCorrectType(eval4jValue, asmType, true)) return eval4jValue + } + + val receiver = findValue(RECEIVER_NAME, asmType, checkType = true, failIfNotFound = false) + if (receiver != null) return receiver + + val this0 = findValue(AsmUtil.CAPTURED_THIS_FIELD, asmType, checkType = true, failIfNotFound = false) + if (this0 != null) return this0 + + val `$this` = findValue("\$this", asmType, checkType = false, failIfNotFound = false) + if (`$this` != null) return `$this` + + return null + } + + private fun findLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? { + val localVariable = frame!!.visibleVariableByName(name) + if (localVariable == null) 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 (project == null) return false + + if ((value.obj() as? com.sun.jdi.ObjectReference)?.referenceType().isSubclass(asmType.getClassName())) { + return true + } + + val thisDesc = value.asmType.getClassDescriptor(project) + val expDesc = asmType.getClassDescriptor(project) + 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(), owner.asmType) + if (obj !is ObjectReference) return null + + val _class = obj.referenceType() + val field = _class.fieldByName(name) + if (field == null) 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.getSort() == Type.OBJECT && this.asmType.getInternalName().startsWith(AsmTypes.REF_TYPE_PREFIX) + } + + private 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 getCapturedFieldName(name: String) = when (name) { + RECEIVER_NAME -> AsmUtil.CAPTURED_RECEIVER_FIELD + THIS_NAME -> AsmUtil.CAPTURED_THIS_FIELD + AsmUtil.CAPTURED_RECEIVER_FIELD -> name + AsmUtil.CAPTURED_THIS_FIELD -> name + else -> "$$name" + } + + 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/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionCache.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionCache.kt index 4a276a02723..ef104b5cdd4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionCache.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionCache.kt @@ -72,9 +72,10 @@ class KotlinEvaluateExpressionCache(val project: Project) { } private fun canBeEvaluatedInThisContext(compiledData: CompiledDataDescriptor, context: EvaluationContextImpl): Boolean { + val frameVisitor = FrameVisitor(context) return compiledData.parameters.all { p -> val (name, jetType) = p - val value = context.findLocalVariable(name, asmType = null, checkType = false, failIfNotFound = false) + val value = frameVisitor.findValue(name, asmType = null, checkType = false, failIfNotFound = false) if (value == null) return@all false val thisDescriptor = value.asmType.getClassDescriptor(project) diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index b2428ea6dc5..7bf240450d3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -35,15 +35,15 @@ import com.intellij.psi.impl.PsiFileFactoryImpl import com.intellij.psi.search.GlobalSearchScope import com.intellij.testFramework.LightVirtualFile import com.intellij.util.ExceptionUtil -import com.sun.jdi.* +import com.sun.jdi.InvocationException +import com.sun.jdi.ObjectReference +import com.sun.jdi.VMDisconnectedException +import com.sun.jdi.VirtualMachine import com.sun.jdi.request.EventRequest import org.jetbrains.eval4j.* -import org.jetbrains.eval4j.Value import org.jetbrains.eval4j.jdi.JDIEval import org.jetbrains.eval4j.jdi.asJdiValue -import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.eval4j.jdi.makeInitialFrame -import org.jetbrains.kotlin.backend.common.output.OutputFileCollection import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.state.GenerationState @@ -56,8 +56,6 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.idea.JetLanguage import org.jetbrains.kotlin.idea.caches.resolve.JavaResolveExtension import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService -import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult -import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluateExpressionCache.CompiledDataDescriptor import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluateExpressionCache.ParametersDescriptor import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses @@ -75,15 +73,12 @@ import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.types.Flexibility import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.Opcodes.ASM5 -import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.tree.MethodNode -import java.util.* +import java.util.Collections private val RECEIVER_NAME = "\$receiver" private val THIS_NAME = "this" @@ -304,7 +299,8 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, } private fun EvaluationContextImpl.getArgumentsForEval4j(parameterNames: List, parameterTypes: Array): List { - return parameterNames.zip(parameterTypes).map { this.findLocalVariable(it.first, it.second, checkType = false, failIfNotFound = true)!! } + val frameVisitor = FrameVisitor(this) + return parameterNames.zip(parameterTypes).map { frameVisitor.findValue(it.first, it.second, checkType = false, failIfNotFound = true)!! } } private fun createClassFileFactory( @@ -338,8 +334,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, DiagnosticSink.DO_NOTHING, null) + val frameVisitor = FrameVisitor(context) + extractedFunction.getReceiverTypeReference()?.let { - state.recordAnonymousType(it, THIS_NAME, context) + state.recordAnonymousType(it, THIS_NAME, frameVisitor) } for (param in extractedFunction.getValueParameters()) { @@ -353,7 +351,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, exception("An exception occurs during Evaluate Expression Action") } - state.recordAnonymousType(paramRef, paramName, context) + state.recordAnonymousType(paramRef, paramName, frameVisitor) } KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION) @@ -362,12 +360,12 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment, } } - private fun GenerationState.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, context: EvaluationContextImpl) { + private fun GenerationState.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, visitor: FrameVisitor) { val paramAnonymousType = typeReference.debugTypeInfo if (paramAnonymousType != null) { val declarationDescriptor = paramAnonymousType.getConstructor().getDeclarationDescriptor() if (declarationDescriptor is ClassDescriptor) { - val localVariable = context.findLocalVariable(localVariableName, asmType = null, checkType = false, failIfNotFound = false) + val localVariable = visitor.findValue(localVariableName, asmType = null, checkType = false, failIfNotFound = false) if (localVariable == null) { exception("Couldn't find local variable this in current frame to get classType for anonymous type ${paramAnonymousType}}") } @@ -483,142 +481,6 @@ private fun SuspendContext.getInvokePolicy(): Int { return if (getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0 } -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 -} - -fun EvaluationContextImpl.findLocalVariable(name: String, asmType: Type?, checkType: Boolean, failIfNotFound: Boolean): Value? { - val project = getDebugProcess().getProject() - val frame = getFrameProxy()?.getStackFrame() - if (frame == null) return null - - fun isValueOfCorrectType(value: Value, asmType: Type?, shouldCheckType: Boolean): Boolean { - if (!shouldCheckType || asmType == null || value.asmType == asmType) return true - if (project == null) return false - - if ((value.obj() as? com.sun.jdi.ObjectReference)?.referenceType().isSubclass(asmType.getClassName())) { - return true - } - - val thisDesc = value.asmType.getClassDescriptor(project) - val expDesc = asmType.getClassDescriptor(project) - return thisDesc != null && expDesc != null && runReadAction { DescriptorUtils.isSubclass(thisDesc, expDesc) } - } - - - try { - when (name) { - THIS_NAME -> { - val thisObject = frame.thisObject() - if (thisObject != null) { - val eval4jValue = thisObject.asValue() - if (isValueOfCorrectType(eval4jValue, asmType, true)) return eval4jValue - } - - val receiver = findLocalVariable(RECEIVER_NAME, asmType, checkType = true, failIfNotFound = false) - if (receiver != null) return receiver - - val this0 = findLocalVariable(AsmUtil.CAPTURED_THIS_FIELD, asmType, checkType = true, failIfNotFound = false) - if (this0 != null) return this0 - - val `$this` = findLocalVariable("\$this", asmType, checkType = false, failIfNotFound = false) - if (`$this` != null) return `$this` - } - else -> { - - fun getField(owner: Value, name: String, asmType: Type?, checkType: Boolean): Value? { - try { - val obj = owner.asJdiValue(frame.virtualMachine(), owner.asmType) - if (obj !is ObjectReference) return null - - val _class = obj.referenceType() - val field = _class.fieldByName(name) - if (field == null) return null - - val fieldValue = obj.getValue(field).asValue() - if (isValueOfCorrectType(fieldValue, asmType, checkType)) return fieldValue - return null - } - catch (e: Exception) { - return null - } - } - - fun Value.isSharedVar(): Boolean { - return this.asmType.getSort() == Type.OBJECT && this.asmType.getInternalName().startsWith(AsmTypes.REF_TYPE_PREFIX) - } - - fun getValueForSharedVar(value: Value, expectedType: Type?, checkType: Boolean): Value? { - val sharedVarValue = getField(value, "element", expectedType, checkType) - if (sharedVarValue != null && isValueOfCorrectType(sharedVarValue, expectedType, checkType)) { - return sharedVarValue - } - return null - } - - val localVariable = frame.visibleVariableByName(name) - if (localVariable != null) { - val eval4jValue = frame.getValue(localVariable).asValue() - if (eval4jValue.isSharedVar()) { - val sharedVarValue = getValueForSharedVar(eval4jValue, asmType, checkType) - if (sharedVarValue != null) { - return sharedVarValue - } - } - - if (isValueOfCorrectType(eval4jValue, asmType, checkType)) return eval4jValue - } - - fun findCapturedVal(name: String): Value? { - var result: Value? = null - val thisObject = frame.thisObject() ?: return null - var thisObj: Value? = thisObject.asValue() - - while (result == null && thisObj != null) { - result = getField(thisObj, name, asmType, checkType) - if (result == null) { - thisObj = getField(thisObj, AsmUtil.CAPTURED_THIS_FIELD, null, false) - } - } - return result - } - - val capturedValName = getCapturedFieldName(name) - val capturedVal = findCapturedVal(capturedValName) - if (capturedVal != null) { - if (capturedVal.isSharedVar()) { - val sharedVarValue = getValueForSharedVar(capturedVal, asmType, checkType) - if (sharedVarValue != null) { - return sharedVarValue - } - } - return capturedVal - } - } - } - - return if (!failIfNotFound) - null - else - throw EvaluateExceptionUtil.createEvaluateException("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}") - } - catch(e: InvalidStackFrameException) { - throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame") - } -} - fun Type.getClassDescriptor(project: Project): ClassDescriptor? { if (AsmUtil.isPrimitive(this)) return null @@ -635,13 +497,4 @@ fun Type.getClassDescriptor(project: Project): ClassDescriptor? { JavaResolveExtension.getResolver(project, clazz).resolveClass(JavaClassImpl(clazz)) } } -} - -private fun getCapturedFieldName(name: String) = when (name) { - RECEIVER_NAME -> AsmUtil.CAPTURED_RECEIVER_FIELD - THIS_NAME -> AsmUtil.CAPTURED_THIS_FIELD - AsmUtil.CAPTURED_RECEIVER_FIELD -> name - AsmUtil.CAPTURED_THIS_FIELD -> name - else -> "$$name" -} - +} \ No newline at end of file