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? {
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context.debugProcess)
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
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)
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
@@ -211,7 +212,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val moduleDescriptor = analysisResult.moduleDescriptor
val result = CodeFragmentCompiler.compile(codeFragment, bindingContext, moduleDescriptor)
val result = CodeFragmentCompiler(evaluationContext).compile(codeFragment, bindingContext, moduleDescriptor)
return CompiledDataDescriptor.from(result, sourcePosition)
}
@@ -5,6 +5,7 @@
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.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.codegen.*
@@ -42,7 +43,7 @@ import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.org.objectweb.asm.Type
object CodeFragmentCompiler {
class CodeFragmentCompiler(val evaluationContext: EvaluationContextImpl) {
data class CompilationResult(
val classes: List<ClassToLoad>,
val parameterInfo: CodeFragmentParameterInfo,
@@ -77,7 +78,7 @@ object CodeFragmentCompiler {
bindingContext, listOf(codeFragment), compilerConfiguration
).build()
val parameterInfo = CodeFragmentParameterAnalyzer(codeFragment, bindingContext).analyze()
val parameterInfo = CodeFragmentParameterAnalyzer(evaluationContext, codeFragment, bindingContext).analyze()
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.AsmUtil
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.compilation.CodeFragmentParameter.*
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.load.java.sam.SingleAbstractMethodUtils
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.
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 crossingBounds = mutableSetOf<Dumb>()
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 {
onceUsedChecker.trigger()
@@ -112,15 +125,19 @@ class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, pr
if (dispatchReceiver is ImplicitReceiver) {
val descriptor = dispatchReceiver.declarationDescriptor
val parameter = processReceiver(dispatchReceiver)
checkBounds(descriptor, expression, parameter)
processed = true
if (parameter != null) {
checkBounds(descriptor, expression, parameter)
processed = true
}
}
if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) {
val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor
val parameter = processSyntheticFieldVariable(descriptor)
checkBounds(descriptor, expression, parameter)
processed = true
if (parameter != null) {
checkBounds(descriptor, expression, parameter)
processed = true
}
}
// 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? {
if (descriptor.kind == ClassKind.OBJECT) {
if (descriptor.kind == ClassKind.OBJECT || containingPrimaryConstructor != null) {
return null
}
@@ -239,8 +256,13 @@ class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, pr
}
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? {
if ((target as? DeclarationDescriptorWithVisibility)?.visibility != Visibilities.LOCAL) {
// No need to capture non-local declarations
val isLocalTarget = (target as? DeclarationDescriptorWithVisibility)?.visibility == Visibilities.LOCAL
val isPrimaryConstructorParameter = !isLocalTarget
&& target is PropertyDescriptor
&& isContainingPrimaryConstructorParameter(target)
if (!isLocalTarget && !isPrimaryConstructorParameter) {
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? {
if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) {
return parameters.getOrPut(target) {
@@ -40,6 +40,7 @@ fun main(args: Array<String>) {
C(1)
D()
E(1)
F("foo")
}
// EXPRESSION: 1 + 1
@@ -53,7 +54,7 @@ class A
class B()
// EXPRESSION: a
// RESULT: 0: I
// RESULT: 1: I
//Breakpoint!
class C(val a: Int)
@@ -69,3 +70,8 @@ class E {
//Breakpoint!
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:20
LineBreakpoint created at constructors.kt:28
LineBreakpoint created at constructors.kt:48
LineBreakpoint created at constructors.kt:53
LineBreakpoint created at constructors.kt:58
LineBreakpoint created at constructors.kt:64
LineBreakpoint created at constructors.kt:70
LineBreakpoint created at constructors.kt:49
LineBreakpoint created at constructors.kt:54
LineBreakpoint created at constructors.kt:59
LineBreakpoint created at constructors.kt:65
LineBreakpoint created at constructors.kt:71
LineBreakpoint created at constructors.kt:77
Run Java
Connected to the target VM
constructors.kt:9
@@ -17,16 +18,18 @@ constructors.kt:20
Compile bytecode for p1 + p2
constructors.kt:28
Compile bytecode for i1
constructors.kt:48
constructors.kt:49
Compile bytecode for 1 + 1
constructors.kt:53
constructors.kt:54
Compile bytecode for 1 + 2
constructors.kt:58
constructors.kt:59
Compile bytecode for a
constructors.kt:64
constructors.kt:65
Compile bytecode for 1 + 3
constructors.kt:70
constructors.kt:71
Compile bytecode for i
constructors.kt:77
Compile bytecode for a
Disconnected from the target VM
Process finished with exit code 0