Evaluate parameter value instead of backing field in primary constructor

This commit is contained in:
Yan Zhulanow
2019-02-19 21:29:36 +03:00
parent 592083bc5b
commit 77bec27c99
5 changed files with 69 additions and 24 deletions
@@ -166,7 +166,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
private fun evaluateSafe(context: EvaluationContextImpl): Any? { private fun evaluateSafe(context: EvaluationContextImpl): Any? {
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context.debugProcess) fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory) val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
val classLoaderRef = loadClassesSafely(context, compiledData.classes) val classLoaderRef = loadClassesSafely(context, compiledData.classes)
@@ -194,7 +194,8 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
} }
private fun compileCodeFragment(debugProcess: DebugProcessImpl): CompiledDataDescriptor { private fun compileCodeFragment(evaluationContext: EvaluationContextImpl): CompiledDataDescriptor {
val debugProcess = evaluationContext.debugProcess
var analysisResult = checkForErrors(codeFragment, debugProcess) var analysisResult = checkForErrors(codeFragment, debugProcess)
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) { if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
@@ -211,7 +212,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val moduleDescriptor = analysisResult.moduleDescriptor val moduleDescriptor = analysisResult.moduleDescriptor
val result = CodeFragmentCompiler.compile(codeFragment, bindingContext, moduleDescriptor) val result = CodeFragmentCompiler(evaluationContext).compile(codeFragment, bindingContext, moduleDescriptor)
return CompiledDataDescriptor.from(result, sourcePosition) return CompiledDataDescriptor.from(result, sourcePosition)
} }
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import org.jetbrains.kotlin.backend.common.output.OutputFile import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.*
@@ -42,7 +43,7 @@ import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
object CodeFragmentCompiler { class CodeFragmentCompiler(val evaluationContext: EvaluationContextImpl) {
data class CompilationResult( data class CompilationResult(
val classes: List<ClassToLoad>, val classes: List<ClassToLoad>,
val parameterInfo: CodeFragmentParameterInfo, val parameterInfo: CodeFragmentParameterInfo,
@@ -77,7 +78,7 @@ object CodeFragmentCompiler {
bindingContext, listOf(codeFragment), compilerConfiguration bindingContext, listOf(codeFragment), compilerConfiguration
).build() ).build()
val parameterInfo = CodeFragmentParameterAnalyzer(codeFragment, bindingContext).analyze() val parameterInfo = CodeFragmentParameterAnalyzer(evaluationContext, codeFragment, bindingContext).analyze()
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment( val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME), codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
@@ -15,6 +16,8 @@ import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.* import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -61,12 +64,22 @@ class CodeFragmentParameterInfo(
The purpose of this class is to figure out what parameters the received code fragment captures. The purpose of this class is to figure out what parameters the received code fragment captures.
It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers). It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers).
*/ */
class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, private val bindingContext: BindingContext) { class CodeFragmentParameterAnalyzer(
private val evaluationContext: EvaluationContextImpl,
private val codeFragment: KtCodeFragment,
private val bindingContext: BindingContext
) {
private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>() private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>()
private val crossingBounds = mutableSetOf<Dumb>() private val crossingBounds = mutableSetOf<Dumb>()
private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java) private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java)
private val containingPrimaryConstructor: ConstructorDescriptor? by lazy {
evaluationContext.frameProxy?.safeLocation()?.safeMethod()?.takeIf { it.isConstructor } ?: return@lazy null
val constructor = codeFragment.context?.getParentOfType<KtPrimaryConstructor>(false) ?: return@lazy null
bindingContext[BindingContext.CONSTRUCTOR, constructor]
}
fun analyze(): CodeFragmentParameterInfo { fun analyze(): CodeFragmentParameterInfo {
onceUsedChecker.trigger() onceUsedChecker.trigger()
@@ -112,15 +125,19 @@ class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, pr
if (dispatchReceiver is ImplicitReceiver) { if (dispatchReceiver is ImplicitReceiver) {
val descriptor = dispatchReceiver.declarationDescriptor val descriptor = dispatchReceiver.declarationDescriptor
val parameter = processReceiver(dispatchReceiver) val parameter = processReceiver(dispatchReceiver)
checkBounds(descriptor, expression, parameter) if (parameter != null) {
processed = true checkBounds(descriptor, expression, parameter)
processed = true
}
} }
if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) { if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) {
val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor
val parameter = processSyntheticFieldVariable(descriptor) val parameter = processSyntheticFieldVariable(descriptor)
checkBounds(descriptor, expression, parameter) if (parameter != null) {
processed = true checkBounds(descriptor, expression, parameter)
processed = true
}
} }
// If a reference has receivers, we can calculate its value using them, no need to capture // If a reference has receivers, we can calculate its value using them, no need to capture
@@ -177,7 +194,7 @@ class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, pr
} }
private fun processDispatchReceiver(descriptor: ClassDescriptor): Smart? { private fun processDispatchReceiver(descriptor: ClassDescriptor): Smart? {
if (descriptor.kind == ClassKind.OBJECT) { if (descriptor.kind == ClassKind.OBJECT || containingPrimaryConstructor != null) {
return null return null
} }
@@ -239,8 +256,13 @@ class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, pr
} }
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? { private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? {
if ((target as? DeclarationDescriptorWithVisibility)?.visibility != Visibilities.LOCAL) { val isLocalTarget = (target as? DeclarationDescriptorWithVisibility)?.visibility == Visibilities.LOCAL
// No need to capture non-local declarations
val isPrimaryConstructorParameter = !isLocalTarget
&& target is PropertyDescriptor
&& isContainingPrimaryConstructorParameter(target)
if (!isLocalTarget && !isPrimaryConstructorParameter) {
return null return null
} }
@@ -261,6 +283,18 @@ class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, pr
} }
} }
private fun isContainingPrimaryConstructorParameter(target: PropertyDescriptor): Boolean {
val primaryConstructor = containingPrimaryConstructor ?: return false
for (parameter in primaryConstructor.valueParameters) {
val property = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameter]
if (target == property) {
return true
}
}
return false
}
private fun processCoroutineContextCall(target: DeclarationDescriptor): Smart? { private fun processCoroutineContextCall(target: DeclarationDescriptor): Smart? {
if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) { if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) {
return parameters.getOrPut(target) { return parameters.getOrPut(target) {
@@ -40,6 +40,7 @@ fun main(args: Array<String>) {
C(1) C(1)
D() D()
E(1) E(1)
F("foo")
} }
// EXPRESSION: 1 + 1 // EXPRESSION: 1 + 1
@@ -53,7 +54,7 @@ class A
class B() class B()
// EXPRESSION: a // EXPRESSION: a
// RESULT: 0: I // RESULT: 1: I
//Breakpoint! //Breakpoint!
class C(val a: Int) class C(val a: Int)
@@ -69,3 +70,8 @@ class E {
//Breakpoint! //Breakpoint!
constructor(i: Int) constructor(i: Int)
} }
// EXPRESSION: a
// RESULT: "foo": Ljava/lang/String;
//Breakpoint!
class F(val a: String)
@@ -2,11 +2,12 @@ LineBreakpoint created at constructors.kt:9
LineBreakpoint created at constructors.kt:13 LineBreakpoint created at constructors.kt:13
LineBreakpoint created at constructors.kt:20 LineBreakpoint created at constructors.kt:20
LineBreakpoint created at constructors.kt:28 LineBreakpoint created at constructors.kt:28
LineBreakpoint created at constructors.kt:48 LineBreakpoint created at constructors.kt:49
LineBreakpoint created at constructors.kt:53 LineBreakpoint created at constructors.kt:54
LineBreakpoint created at constructors.kt:58 LineBreakpoint created at constructors.kt:59
LineBreakpoint created at constructors.kt:64 LineBreakpoint created at constructors.kt:65
LineBreakpoint created at constructors.kt:70 LineBreakpoint created at constructors.kt:71
LineBreakpoint created at constructors.kt:77
Run Java Run Java
Connected to the target VM Connected to the target VM
constructors.kt:9 constructors.kt:9
@@ -17,16 +18,18 @@ constructors.kt:20
Compile bytecode for p1 + p2 Compile bytecode for p1 + p2
constructors.kt:28 constructors.kt:28
Compile bytecode for i1 Compile bytecode for i1
constructors.kt:48 constructors.kt:49
Compile bytecode for 1 + 1 Compile bytecode for 1 + 1
constructors.kt:53 constructors.kt:54
Compile bytecode for 1 + 2 Compile bytecode for 1 + 2
constructors.kt:58 constructors.kt:59
Compile bytecode for a Compile bytecode for a
constructors.kt:64 constructors.kt:65
Compile bytecode for 1 + 3 Compile bytecode for 1 + 3
constructors.kt:70 constructors.kt:71
Compile bytecode for i Compile bytecode for i
constructors.kt:77
Compile bytecode for a
Disconnected from the target VM Disconnected from the target VM
Process finished with exit code 0 Process finished with exit code 0