From 16266259f5f88ddb8bd0f136f15ee59209ede69b Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 21 Feb 2019 00:31:10 +0300 Subject: [PATCH] Evaluator: Handle function context gracefully. Use file scope as a fallback instead of error scope --- .../caches/resolve/CodeFragmentAnalyzer.kt | 37 ++++++++++++------- .../evaluate/KotlinEvaluationBuilder.kt | 13 +++++++ .../defaultParameterValues.kt | 19 ++++++++++ .../defaultParameterValues.out | 9 +++++ .../defaultParameterValues2.kt | 20 ++++++++++ .../defaultParameterValues2.out | 9 +++++ ...KotlinEvaluateExpressionTestGenerated.java | 10 +++++ 7 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.out diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt index f2e04e12655..c90f68e483f 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt @@ -49,8 +49,8 @@ class CodeFragmentAnalyzer( return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult) } - private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace { - val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult + private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextInfo: ContextInfo): BindingTrace { + val (bindingContext, scope, dataFlowInfo) = contextInfo val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis") when (val contentElement = codeFragment.getContentElement()) { @@ -79,19 +79,19 @@ class CodeFragmentAnalyzer( return bindingTrace } - private data class ContextAnalysisResult( - val bindingContext: BindingContext, - val scope: LexicalScope, - val dataFlowInfo: DataFlowInfo - ) + private data class ContextInfo(val bindingContext: BindingContext, val scope: LexicalScope, val dataFlowInfo: DataFlowInfo) - private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextAnalysisResult { + private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextInfo { fun resolutionFactory(element: KtElement): BindingContext { return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode) } val context = refineContextElement(codeFragment.context) + val info = getContextInfo(context, ::resolutionFactory) + return info.copy(scope = enrichScopeWithImports(info.scope, codeFragment)) + } + private fun getContextInfo(context: PsiElement?, resolutionFactory: (KtElement) -> BindingContext): ContextInfo { var bindingContext: BindingContext = BindingContext.EMPTY var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY var scope: LexicalScope? = null @@ -99,7 +99,7 @@ class CodeFragmentAnalyzer( when (context) { is KtPrimaryConstructor -> { val containingClass = context.getContainingClassOrObject() - val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory) + val resolutionResult = getClassDescriptor(containingClass, resolutionFactory) if (resolutionResult != null) { bindingContext = resolutionResult.bindingContext scope = resolutionResult.descriptor.scopeForInitializerResolution @@ -113,12 +113,22 @@ class CodeFragmentAnalyzer( } } is KtClassOrObject -> { - val resolutionResult = getClassDescriptor(context, ::resolutionFactory) + val resolutionResult = getClassDescriptor(context, resolutionFactory) if (resolutionResult != null) { bindingContext = resolutionResult.bindingContext scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution } } + is KtFunction -> { + val bindingContextForFunction = resolutionFactory(context) + val functionDescriptor = bindingContextForFunction[BindingContext.FUNCTION, context] + if (functionDescriptor != null) { + bindingContext = bindingContextForFunction + val outerScope = getContextInfo(context.getParentOfType(true), resolutionFactory).scope + val localRedeclarationChecker = LocalRedeclarationChecker.DO_NOTHING + scope = FunctionDescriptorUtil.getFunctionInnerScope(outerScope, functionDescriptor, localRedeclarationChecker) + } + } is KtFile -> { bindingContext = resolveSession.bindingContext scope = resolveSession.fileScopeProvider.getFileResolutionScope(context) @@ -131,15 +141,14 @@ class CodeFragmentAnalyzer( } if (scope == null) { - val containingKtFile = codeFragment.context?.containingFile as? KtFile + val containingKtFile = context?.containingFile as? KtFile if (containingKtFile != null) { bindingContext = resolveSession.bindingContext scope = resolveSession.fileScopeProvider.getFileResolutionScope(containingKtFile) } } - val scopeWithImports = enrichScopeWithImports(scope ?: createEmptyScope(resolveSession.moduleDescriptor), codeFragment) - return ContextAnalysisResult(bindingContext, scopeWithImports, dataFlowInfo) + return ContextInfo(bindingContext, scope ?: createEmptyScope(resolveSession.moduleDescriptor), dataFlowInfo) } private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes) @@ -164,7 +173,7 @@ class CodeFragmentAnalyzer( private fun refineContextElement(context: PsiElement?): KtElement? { return when (context) { - is KtParameter -> context.getParentOfType(true) + is KtParameter -> context.getParentOfType(true)?.let { it } is KtProperty -> context.delegateExpressionOrInitializer is KtConstructor<*> -> context is KtFunctionLiteral -> context.bodyExpression?.statements?.lastOrNull() 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 e7ad1558ca0..999a5fbfe9e 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 @@ -61,6 +61,8 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.* import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder +import org.jetbrains.kotlin.idea.debugger.safeLocation +import org.jetbrains.kotlin.idea.debugger.safeMethod import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runReadAction @@ -70,6 +72,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.isInlineClassType +import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.Type @@ -343,10 +346,18 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour if (result == null) { val name = parameter.debugString + fun isInsideDefaultInterfaceMethod(): Boolean { + val method = evaluationContext.frameProxy?.safeLocation()?.safeMethod() ?: return false + val desc = method.signature() + return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") } + } + if (parameter in compiledData.crossingBounds) { evaluationException("'$name' is not captured") } else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) { evaluationException("Cannot find the backing field '${parameter.name}'") + } else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) { + evaluationException("Parameter evaluation is not supported for '\$default' methods") } else { throw VariableFinder.variableNotFound(evaluationContext, buildString { append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className) @@ -363,6 +374,8 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour companion object { private val IGNORED_DIAGNOSTICS: Set> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS + private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER) + private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? { val jdiValue = when (this) { is ValueReturned -> result diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.kt new file mode 100644 index 00000000000..3d7650da74a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.kt @@ -0,0 +1,19 @@ +package defaultParameterValues + +fun main() { + foo(3) +} + +fun foo( + b: Int, + //Breakpoint! + a: Int = 5 +) { + val c = 5 +} + +// EXPRESSION: a +// RESULT: Parameter evaluation is not supported for '$default' methods + +// EXPRESSION: b +// RESULT: Parameter evaluation is not supported for '$default' methods \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.out new file mode 100644 index 00000000000..a735e8443c6 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.out @@ -0,0 +1,9 @@ +LineBreakpoint created at defaultParameterValues.kt:10 +Run Java +Connected to the target VM +defaultParameterValues.kt:10 +Compile bytecode for a +Compile bytecode for b +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.kt new file mode 100644 index 00000000000..6679e7afae6 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.kt @@ -0,0 +1,20 @@ +package defaultParameterValues2 + +fun main() { + Foo().foo() +} + +interface IFoo { + //Breakpoint! + fun foo(a: Int = 1) +} + +class Foo : IFoo { + override fun foo(a: Int) {} +} + +// EXPRESSION: Foo() +// RESULT: instance of defaultParameterValues2.Foo(id=ID): LdefaultParameterValues2/Foo; + +// EXPRESSION: a +// RESULT: Parameter evaluation is not supported for '$default' methods \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.out new file mode 100644 index 00000000000..f6f653c1bb1 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.out @@ -0,0 +1,9 @@ +LineBreakpoint created at defaultParameterValues2.kt:9 +Run Java +Connected to the target VM +defaultParameterValues2.kt:9 +Compile bytecode for Foo() +Compile bytecode for a +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index 37441363406..f9537a61912 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -91,6 +91,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/collections.kt"); } + @TestMetadata("defaultParameterValues.kt") + public void testDefaultParameterValues() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues.kt"); + } + + @TestMetadata("defaultParameterValues2.kt") + public void testDefaultParameterValues2() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/defaultParameterValues2.kt"); + } + @TestMetadata("delegatedPropertyInOtherFile.kt") public void testDelegatedPropertyInOtherFile() throws Exception { runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/delegatedPropertyInOtherFile.kt");