Tracking of incoming lambda's for outflow analysis

This commit is contained in:
Valentin Kipyatkov
2020-04-12 17:34:21 +03:00
parent a09a9a65ff
commit 8f01427534
9 changed files with 259 additions and 55 deletions
@@ -6,12 +6,10 @@
package org.jetbrains.kotlin.idea.slicer
import com.intellij.psi.PsiCall
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
@@ -27,15 +25,14 @@ import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference
import org.jetbrains.kotlin.idea.references.ReferenceAccess
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.readWriteAccessWithFullExpression
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinTargetElementEvaluator
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -141,10 +138,23 @@ class InflowSlicer(
is KtNamedFunction -> expression.takeIf { expression.name == null }
else -> null
}
val currentBehaviour = mode.currentBehaviour
if (lambda != null) {
if (mode.currentBehaviour is LambdaResultInflowBehaviour) {
if (currentBehaviour is LambdaResultInflowBehaviour) {
lambda.passToProcessor(mode.dropBehaviour())
}
else if (currentBehaviour is LambdaArgumentInflowBehaviour) {
val valueParameters = lambda.valueParameters
if (valueParameters.isEmpty() && lambda is KtFunctionLiteral) {
if (currentBehaviour.argumentIndex == 0) {
lambda.implicitItUsages().forEach {
it.passToProcessor(mode.dropBehaviour())
}
}
} else {
valueParameters.getOrNull(currentBehaviour.argumentIndex)?.passToProcessor(mode.dropBehaviour())
}
}
return
}
@@ -195,12 +205,12 @@ class InflowSlicer(
processCalls(functionLiteral, false, ArgumentSliceProducer(parameterDescriptor))
}
} else {
accessedDeclaration.passDeclarationToProcessorWithOverriders(createdAt.element)
accessedDeclaration.passDeclarationToProcessorWithOverriders()
}
}
else -> {
accessedDeclaration?.passDeclarationToProcessorWithOverriders(createdAt.element)
accessedDeclaration?.passDeclarationToProcessorWithOverriders()
}
}
}
@@ -216,9 +226,10 @@ class InflowSlicer(
val referencedDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, callableRefExpr.callableReference] ?: return
val referencedDeclaration = (referencedDescriptor as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()
?: return
if (mode.currentBehaviour is LambdaResultInflowBehaviour) {
if (currentBehaviour is LambdaResultInflowBehaviour) {
referencedDeclaration.passToProcessor(mode.dropBehaviour())
}
//TODO: LambdaArgumentInflowBehaviour
}
else -> return
@@ -231,7 +242,7 @@ class InflowSlicer(
(resolvedCall.dispatchReceiver as? ExpressionReceiver)?.expression
?.passToProcessorAsValue(mode.withBehaviour(LambdaResultInflowBehaviour))
} else {
resultingDescriptor.originalSource.getPsi()?.passDeclarationToProcessorWithOverriders(createdAt.element)
resultingDescriptor.originalSource.getPsi()?.passToProcessorInCallMode(createdAt.element, withOverriders = true)
}
}
}
@@ -306,27 +317,6 @@ class InflowSlicer(
}
}
private fun PsiElement.passDeclarationToProcessorWithOverriders(callElement: KtElement) {
val newMode = if (this is KtNamedFunction && hasInlineModifier())
mode.withInlineFunctionCall(callElement, this)
else
mode
passToProcessor(newMode)
HierarchySearchRequest(this, analysisScope)
.searchOverriders()
.forEach { it.namedUnwrappedElement?.passToProcessor(newMode) }
if (this is KtCallableDeclaration && isExpectDeclaration()) {
resolveToDescriptorIfAny(BodyResolveMode.FULL)
?.actualsForExpected()
?.forEach {
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor(newMode)
}
}
}
override fun processCalls(callable: KtCallableDeclaration, includeOverriders: Boolean, sliceProducer: SliceProducer) {
if (callable is KtNamedFunction) {
val (newMode, callElement) = mode.popInlineFunctionCall(callable)
@@ -339,4 +329,12 @@ class InflowSlicer(
super.processCalls(callable, includeOverriders, sliceProducer)
}
private fun KtFunctionLiteral.implicitItUsages(): Collection<KtSimpleNameExpression> {
return collectDescendantsOfType(fun(expression: KtSimpleNameExpression): Boolean {
if (expression.getQualifiedExpressionForSelector() != null || expression.getReferencedName() != "it") return false
val lBrace = KotlinTargetElementEvaluator.findLambdaOpenLBraceForGeneratedIt(expression.mainReference) ?: return false
return lBrace == this.lBrace.node.treeNext.psi
})
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.slicer
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.psi.KtElement
data class LambdaArgumentInflowBehaviour(val argumentIndex: Int) : KotlinSliceAnalysisMode.Behaviour {
override fun processUsages(element: KtElement, parent: KotlinSliceUsage, uniqueProcessor: SliceUsageProcessor) {
InflowSlicer(element, uniqueProcessor, parent).processChildren(parent.forcedExpressionMode)
}
override val slicePresentationPrefix: String
get() = TODO()
override val testPresentationPrefix: String
get() = "[LAMBDA ARGUMENT IN] "
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.idea.slicer
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
@@ -16,6 +15,8 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
@@ -23,13 +24,13 @@ import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingElement
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.OperatorNameConventions
class OutflowSlicer(
element: KtElement,
@@ -202,12 +203,46 @@ class OutflowSlicer(
is CallInstruction -> {
if (!processIfReceiverValue(instruction, pseudoValue)) {
instruction.arguments[pseudoValue]?.originalSource?.getPsi()?.passCalledDeclarationToProcessor(instruction.element)
val parameterDescriptor = instruction.arguments[pseudoValue] ?: return@processPseudocodeUsages
val parameter = parameterDescriptor.originalSource.getPsi()
if (parameter != null) {
parameter.passToProcessorInCallMode(instruction.element)
} else {
val function = parameterDescriptor.containingDeclaration as? FunctionDescriptor
if (function != null && function.isOperator && function.name == OperatorNameConventions.INVOKE) {
val receiverPseudoValue = instruction.receiverValues.entries.singleOrNull()?.key
?: return@processPseudocodeUsages
if (receiverPseudoValue.createdAt != null) {
when (val createdAt = receiverPseudoValue.createdAt) {
is ReadValueInstruction -> {
val accessedDescriptor = createdAt.target.accessedDescriptor ?: return@processPseudocodeUsages
val accessedDeclaration = accessedDescriptor.originalSource.getPsi() ?: return@processPseudocodeUsages
when (accessedDescriptor) {
is ValueParameterDescriptor -> {
//TODO: argument index is not always correct - first argument can be receiver
val newMode = mode.withBehaviour(LambdaArgumentInflowBehaviour(accessedDescriptor.index))
accessedDeclaration.passToProcessor(newMode)
}
}
}
}
}
}
}
}
}
is ReturnValueInstruction -> {
instruction.subroutine.passToProcessor()
val subroutine = instruction.subroutine
if (subroutine is KtNamedFunction) {
val (newMode, callElement) = mode.popInlineFunctionCall(subroutine)
if (newMode != null) {
callElement?.passToProcessor(newMode)
return@processPseudocodeUsages
}
}
subroutine.passToProcessor()
}
is MagicInstruction -> {
@@ -228,13 +263,13 @@ class OutflowSlicer(
Call.CallType.DEFAULT -> {
if (receiverValue == resolvedCall.extensionReceiver) {
val targetDeclaration = resolvedCall.resultingDescriptor.originalSource.getPsi()
(targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passCalledDeclarationToProcessor(instruction.element)
(targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessorInCallMode(instruction.element)
}
}
Call.CallType.INVOKE -> {
if (receiverValue == resolvedCall.dispatchReceiver && mode.currentBehaviour is LambdaCallsBehaviour) {
instruction.element.passCalledDeclarationToProcessor(instruction.element, mode)
instruction.element.passToProcessor()
}
}
@@ -278,20 +313,4 @@ class OutflowSlicer(
}
}
}
private fun PsiElement.passCalledDeclarationToProcessor(
callElement: KtElement,
mode: KotlinSliceAnalysisMode = this@OutflowSlicer.mode
) {
var newMode = mode
when (this) {
is KtParameter -> {
val ownerFunction = ownerFunction as? KtNamedFunction
if (ownerFunction != null && ownerFunction.hasInlineModifier()) {
newMode = mode.withInlineFunctionCall(callElement, ownerFunction)
}
}
}
passToProcessor(newMode)
}
}
@@ -11,6 +11,7 @@ import com.intellij.psi.search.SearchScope
import com.intellij.slicer.JavaSliceUsage
import com.intellij.usageView.UsageInfo
import com.intellij.util.containers.addIfNotNull
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode
@@ -25,7 +26,12 @@ import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.expectedDescriptor
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -65,6 +71,52 @@ abstract class Slicer(
processor.process(KotlinSliceUsage(this, parentUsage, mode, forcedExpressionMode = true))
}
protected fun PsiElement.passToProcessorInCallMode(callElement: KtElement, withOverriders: Boolean = false) {
val newMode = when (this) {
is KtNamedFunction -> this.callMode(callElement)
is KtParameter -> ownerFunction.callMode(callElement)
is KtTypeReference -> {
val declaration = parent
require(declaration is KtCallableDeclaration)
require(this == declaration.receiverTypeReference)
declaration.callMode(callElement)
}
else -> mode
}
if (withOverriders) {
passDeclarationToProcessorWithOverriders(newMode)
} else {
passToProcessor(newMode)
}
}
protected fun PsiElement.passDeclarationToProcessorWithOverriders(mode: KotlinSliceAnalysisMode = this@Slicer.mode) {
passToProcessor(mode)
HierarchySearchRequest(this, analysisScope)
.searchOverriders()
.forEach { it.namedUnwrappedElement?.passToProcessor(mode) }
if (this is KtCallableDeclaration && isExpectDeclaration()) {
resolveToDescriptorIfAny(BodyResolveMode.FULL)
?.actualsForExpected()
?.forEach {
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor(mode)
}
}
}
protected fun KtDeclaration?.callMode(callElement: KtElement): KotlinSliceAnalysisMode {
return if (this is KtNamedFunction && hasInlineModifier())
mode.withInlineFunctionCall(callElement, this)
else
mode
}
protected open fun processCalls(
callable: KtCallableDeclaration,
includeOverriders: Boolean,
+18
View File
@@ -0,0 +1,18 @@
// FLOW: OUT
fun foo(<caret>p: String) {
val v1 = p.let { value -> bar(value) }
val v2 = p.let { it }
val v3 = p.let {
val it = "a"
it
}
}
fun bar(s: String) = s
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
+35
View File
@@ -0,0 +1,35 @@
3 fun foo(<bold>p: String</bold>) {
4 val v1 = <bold>p</bold>.let { value -> bar(value) }
16 (INLINE CALL let) inline fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
17 (INLINE CALL let) return block(<bold>this</bold>)
16 (INLINE CALL let) [LAMBDA ARGUMENT IN] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
4 [LAMBDA ARGUMENT IN] val v1 = p.let <bold>{ value -> bar(value) }</bold>
4 val v1 = p.let { <bold>value</bold> -> bar(value) }
4 val v1 = p.let { value -> bar(<bold>value</bold>) }
14 fun bar(<bold>s: String</bold>) = s
14 fun bar(s: String) = <bold>s</bold>
14 fun <bold>bar(s: String) = s</bold>
4 val v1 = p.let { value -> <bold>bar(value)</bold> }
4 val v1 = p.let <bold>{ value -> bar(value) }</bold>
4 [LAMBDA CALLS] val v1 = p.let <bold>{ value -> bar(value) }</bold>
16 (INLINE CALL let) [LAMBDA CALLS] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
17 (INLINE CALL let) return <bold>block(this)</bold>
4 val v1 = p.<bold>let { value -> bar(value) }</bold>
4 val <bold>v1 = p.let { value -> bar(value) }</bold>
6 val v2 = <bold>p</bold>.let { it }
16 (INLINE CALL let) inline fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
17 (INLINE CALL let) return block(<bold>this</bold>)
16 (INLINE CALL let) [LAMBDA ARGUMENT IN] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
6 [LAMBDA ARGUMENT IN] val v2 = p.let <bold>{ it }</bold>
6 val v2 = p.let { <bold>it</bold> }
6 val v2 = p.let <bold>{ it }</bold>
6 [LAMBDA CALLS] val v2 = p.let <bold>{ it }</bold>
16 (INLINE CALL let) [LAMBDA CALLS] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
17 (INLINE CALL let) return <bold>block(this)</bold>
6 val v2 = p.<bold>let { it }</bold>
6 val <bold>v2 = p.let { it }</bold>
8 val v3 = <bold>p</bold>.let {
16 (INLINE CALL let) inline fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
17 (INLINE CALL let) return block(<bold>this</bold>)
16 (INLINE CALL let) [LAMBDA ARGUMENT IN] inline fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
8 [LAMBDA ARGUMENT IN] val v3 = p.let <bold>{</bold>
+18
View File
@@ -0,0 +1,18 @@
// FLOW: OUT
fun foo(<caret>p: String) {
val v1 = p.let { value -> bar(value) }
val v2 = p.let { it }
val v3 = p.let {
val it = "a"
it
}
}
fun bar(s: String) = s
fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
@@ -0,0 +1,33 @@
3 fun foo(<bold>p: String</bold>) {
4 val v1 = <bold>p</bold>.let { value -> bar(value) }
16 fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
17 return block(<bold>this</bold>)
16 [LAMBDA ARGUMENT IN] fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
4 [LAMBDA ARGUMENT IN] val v1 = p.let <bold>{ value -> bar(value) }</bold>
4 val v1 = p.let { <bold>value</bold> -> bar(value) }
4 val v1 = p.let { value -> bar(<bold>value</bold>) }
14 fun bar(<bold>s: String</bold>) = s
14 fun bar(s: String) = <bold>s</bold>
14 fun <bold>bar(s: String) = s</bold>
4 val v1 = p.let { value -> <bold>bar(value)</bold> }
4 val v1 = p.let <bold>{ value -> bar(value) }</bold>
4 [LAMBDA CALLS] val v1 = p.let <bold>{ value -> bar(value) }</bold>
16 [LAMBDA CALLS] fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
17 return <bold>block(this)</bold>
16 fun <T, R> T.<bold>let(block: (T) -> R): R {</bold>
4 val v1 = p.<bold>let { value -> bar(value) }</bold>
4 val <bold>v1 = p.let { value -> bar(value) }</bold>
6 val v2 = p.<bold>let { it }</bold>
6 val <bold>v2 = p.let { it }</bold>
8 val v3 = p.<bold>let {</bold>
8 val <bold>v3 = p.let {</bold>
6 [LAMBDA ARGUMENT IN] val v2 = p.let <bold>{ it }</bold>
6 val v2 = p.let { <bold>it</bold> }
6 val v2 = p.let <bold>{ it }</bold>
6 [LAMBDA CALLS] val v2 = p.let <bold>{ it }</bold>
16 [LAMBDA CALLS] DUPLICATE: fun <T, R> T.let(<bold>block: (T) -> R</bold>): R {
8 [LAMBDA ARGUMENT IN] val v3 = p.let <bold>{</bold>
6 val v2 = <bold>p</bold>.let { it }
16 DUPLICATE: fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
8 val v3 = <bold>p</bold>.let {
16 DUPLICATE: fun <T, R> <bold>T</bold>.let(block: (T) -> R): R {
@@ -623,6 +623,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
runTest("idea/testData/slicer/outflow/lambdaResultWithDirectCallViaAssignment.kt");
}
@TestMetadata("letResult.kt")
public void testLetResult() throws Exception {
runTest("idea/testData/slicer/outflow/letResult.kt");
}
@TestMetadata("localVariableUsages.kt")
public void testLocalVariableUsages() throws Exception {
runTest("idea/testData/slicer/outflow/localVariableUsages.kt");
@@ -633,6 +638,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
runTest("idea/testData/slicer/outflow/memberPropertyUsages.kt");
}
@TestMetadata("nonInlineLetResult.kt")
public void testNonInlineLetResult() throws Exception {
runTest("idea/testData/slicer/outflow/nonInlineLetResult.kt");
}
@TestMetadata("nonLocalReturn.kt")
public void testNonLocalReturn() throws Exception {
runTest("idea/testData/slicer/outflow/nonLocalReturn.kt");