From 77bec27c99c14ea77e39ef34988acb4763b81c35 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Tue, 19 Feb 2019 21:29:36 +0300 Subject: [PATCH] Evaluate parameter value instead of backing field in primary constructor --- .../evaluate/KotlinEvaluationBuilder.kt | 7 +-- .../compilation/CodeFragmentCompiler.kt | 5 +- .../CodeFragmentParameterAnalyzer.kt | 50 ++++++++++++++++--- .../multipleBreakpoints/constructors.kt | 8 ++- .../multipleBreakpoints/constructors.out | 23 +++++---- 5 files changed, 69 insertions(+), 24 deletions(-) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index bc07e2be07c..146ddba0c33 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -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) } diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt index a8e8825aa1d..91807b21967 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt @@ -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, 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 diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt index 6fa1d869182..f16c3e03b6e 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt @@ -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() private val crossingBounds = mutableSetOf() 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(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) { diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt index 5d2564498fa..b03f26a97aa 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt @@ -40,6 +40,7 @@ fun main(args: Array) { 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) \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out index 2e886698e64..cebd30663e2 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out @@ -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