Refactoring: extract FrameVisitor class
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user