Refactoring: extract FrameVisitor class

This commit is contained in:
Natalia Ukhorskaya
2015-04-28 10:54:07 +03:00
parent 4848af5ef6
commit 35bacb4ac7
3 changed files with 216 additions and 162 deletions
@@ -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
}
}
@@ -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)
@@ -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<String>, parameterTypes: Array<Type>): List<Value> {
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"
}
}