Analyze Data Flow to Here to work with virtual methods

This commit is contained in:
Valentin Kipyatkov
2020-02-09 22:49:54 +02:00
parent c34286a327
commit cfa1d91aa4
15 changed files with 137 additions and 47 deletions
@@ -36,7 +36,7 @@ import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.SmartList
fun PsiElement.processAllExactUsages( fun PsiElement.processAllExactUsages(
options: () -> FindUsagesOptions, options: FindUsagesOptions,
processor: (UsageInfo) -> Unit processor: (UsageInfo) -> Unit
) { ) {
fun elementsToCheckReferenceAgainst(reference: PsiReference): List<PsiElement> { fun elementsToCheckReferenceAgainst(reference: PsiReference): List<PsiElement> {
@@ -62,7 +62,7 @@ fun PsiElement.processAllExactUsages(
} }
true true
}, },
options() options
) )
} }
@@ -28,14 +28,18 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.idea.core.isOverridable import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference import org.jetbrains.kotlin.idea.references.KtPropertyDelegationMethodsReference
@@ -51,9 +55,6 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
@@ -79,17 +80,23 @@ private fun KtDeclaration.processHierarchyUpward(scope: AnalysisScope, processor
.forEach(processor) .forEach(processor)
} }
private fun KtFunction.processCalls(scope: SearchScope, processor: (UsageInfo) -> Unit) { private fun KtFunction.processCalls(scope: SearchScope, exactFunctionOnly: Boolean, processor: (UsageInfo) -> Unit) {
processAllExactUsages( val options = KotlinFunctionFindUsagesOptions(project).apply {
{ isSearchForTextOccurrences = false
KotlinFunctionFindUsagesOptions(project).apply { isSkipImportStatements = true
isSearchForTextOccurrences = false searchScope = scope.intersectWith(useScope)
isSkipImportStatements = true }
searchScope = scope.intersectWith(useScope) if (exactFunctionOnly) {
processAllExactUsages(options, processor)
} else {
val descriptor = unsafeResolveToDescriptor() as? CallableMemberDescriptor ?: return
for (superDescriptor in descriptor.getDeepestSuperDeclarations()) {
when (val declaration = superDescriptor.originalSource.getPsi()) {
is KtDeclaration -> declaration.processAllUsages(options, processor)
else -> {} //TODO
} }
}, }
processor }
)
} }
private enum class AccessKind { private enum class AccessKind {
@@ -102,16 +109,13 @@ private fun KtDeclaration.processVariableAccesses(
processor: (UsageInfo) -> Unit processor: (UsageInfo) -> Unit
) { ) {
processAllExactUsages( processAllExactUsages(
{ KotlinPropertyFindUsagesOptions(project).apply {
KotlinPropertyFindUsagesOptions(project).apply { isReadAccess = kind == AccessKind.READ_ONLY || kind == AccessKind.READ_OR_WRITE
isReadAccess = kind == AccessKind.READ_ONLY || kind == AccessKind.READ_OR_WRITE isWriteAccess = kind == AccessKind.WRITE_ONLY || kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
isWriteAccess = isReadWriteAccess = kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
kind == AccessKind.WRITE_ONLY || kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE isSearchForTextOccurrences = false
isReadWriteAccess = kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE isSkipImportStatements = true
isSearchForTextOccurrences = false searchScope = scope.intersectWith(useScope)
isSkipImportStatements = true
searchScope = scope.intersectWith(useScope)
}
}, },
processor processor
) )
@@ -231,7 +235,6 @@ class InflowSlicer(
if (!canProcess()) return if (!canProcess()) return
val function = ownerFunction ?: return val function = ownerFunction ?: return
if (function.isOverridable()) return
if (function is KtPropertyAccessor && function.isSetter) { if (function is KtPropertyAccessor && function.isSetter) {
function.property.processPropertyAssignments() function.property.processPropertyAssignments()
@@ -251,7 +254,7 @@ class InflowSlicer(
val parameterDescriptor = resolveToParameterDescriptorIfAny(BodyResolveMode.FULL) ?: return val parameterDescriptor = resolveToParameterDescriptorIfAny(BodyResolveMode.FULL) ?: return
(function as? KtFunction)?.processCalls(parentUsage.scope.toSearchScope()) body@{ (function as? KtFunction)?.processCalls(parentUsage.scope.toSearchScope(), exactFunctionOnly = false) body@{
val refElement = it.element ?: return@body val refElement = it.element ?: return@body
val refParent = refElement.parent val refParent = refElement.parent
@@ -259,8 +262,9 @@ class InflowSlicer(
refElement is KtExpression -> { refElement is KtExpression -> {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body
val resolvedCall = callElement.resolveToCall() ?: return@body val resolvedCall = callElement.resolveToCall() ?: return@body
val valueArguments = resolvedCall.originalValueArguments val callParameterDescriptor = resolvedCall.resultingDescriptor.valueParameters[parameterDescriptor.index]
when (val resolvedArgument = valueArguments[parameterDescriptor] ?: return@body) { val resolvedArgument = resolvedCall.valueArguments[callParameterDescriptor] ?: return@body
when (resolvedArgument) {
is DefaultValueArgument -> defaultValue is DefaultValueArgument -> defaultValue
is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression() is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression()
else -> null else -> null
@@ -431,7 +435,7 @@ class OutflowSlicer(
if (this is KtConstructor<*> || this is KtNamedFunction && name != null) { if (this is KtConstructor<*> || this is KtNamedFunction && name != null) {
processHierarchyUpward(parentUsage.scope) { processHierarchyUpward(parentUsage.scope) {
if (this is KtFunction) { if (this is KtFunction) {
processCalls(parentUsage.scope.toSearchScope()) { processCalls(parentUsage.scope.toSearchScope(), exactFunctionOnly = true) {
when (val refElement = it.element) { when (val refElement = it.element) {
null -> (it.reference as? LightMemberReference)?.element?.passToProcessor() null -> (it.reference as? LightMemberReference)?.element?.passToProcessor()
is KtExpression -> { is KtExpression -> {
@@ -536,17 +540,3 @@ private val DeclarationDescriptorWithSource.originalSource: SourceElement
} }
return descriptor.source return descriptor.source
} }
private val ResolvedCall<*>.originalValueArguments: Map<ValueParameterDescriptor, ResolvedValueArgument>
get() = when (this) {
is NewResolvedCallImpl<*> -> LinkedHashMap<ValueParameterDescriptor, ResolvedValueArgument>().also {
for ((valueParameter, argument) in valueArguments) {
var parameter = valueParameter
while (parameter != parameter.original) {
parameter = parameter.original
}
it[parameter] = argument
}
}
else -> valueArguments
}
+18
View File
@@ -0,0 +1,18 @@
// FLOW: IN
interface I {
fun foo(<caret>p: Int)
}
class C : I {
override fun foo(p: Int) {
}
fun f() {
foo(1)
}
}
fun f(i: I) {
i.foo(2)
}
@@ -0,0 +1,8 @@
12 foo(<bold>1</bold>)
4 fun foo(<bold>p: Int</bold>)
12 foo(<bold>1</bold>)
17 i.foo(<bold>2</bold>)
4 fun foo(<bold>p: Int</bold>)
17 i.foo(<bold>2</bold>)
@@ -0,0 +1,3 @@
[NotNull Values]
4 fun foo(<bold>p: Int</bold>)
4 fun foo(<bold>p: Int</bold>)
+3
View File
@@ -0,0 +1,3 @@
4 fun foo(<bold>p: Int</bold>)
12 foo(<bold>1</bold>)
17 i.foo(<bold>2</bold>)
@@ -1,8 +1,9 @@
4 fun bar(<bold>n: Int</bold>) = n 5 val x = (::bar)(<bold>1</bold>)
5 val <bold>x = (::bar)(1)</bold> 5 val <bold>x = (::bar)(1)</bold>
5 val x = <bold>(::bar)(1)</bold> 5 val x = <bold>(::bar)(1)</bold>
5 [LAMBDA] val x = <bold>(::bar)</bold>(1) 5 [LAMBDA] val x = <bold>(::bar)</bold>(1)
4 fun <bold>bar(n: Int) = n</bold> 4 fun <bold>bar(n: Int) = n</bold>
4 fun bar(n: Int) = <bold>n</bold> 4 fun bar(n: Int) = <bold>n</bold>
4 fun bar(<bold>n: Int</bold>) = n 4 fun bar(<bold>n: Int</bold>) = n
5 val x = (::bar)(<bold>1</bold>)
@@ -4,3 +4,4 @@
4 fun <bold>bar(n: Int) = n</bold> 4 fun <bold>bar(n: Int) = n</bold>
4 fun bar(n: Int) = <bold>n</bold> 4 fun bar(n: Int) = <bold>n</bold>
4 fun bar(<bold>n: Int</bold>) = n 4 fun bar(<bold>n: Int</bold>) = n
5 val x = (::bar)(<bold>1</bold>)
+19
View File
@@ -0,0 +1,19 @@
// FLOW: IN
interface I {
fun foo(p: Int)
}
class C : I {
override fun foo(p: Int) {
println(<caret>p)
}
fun f() {
foo(1)
}
}
fun f(i: I) {
i.foo(2)
}
+10
View File
@@ -0,0 +1,10 @@
13 foo(<bold>1</bold>)
9 println(<bold>p</bold>)
8 override fun foo(<bold>p: Int</bold>) {
13 foo(<bold>1</bold>)
18 i.foo(<bold>2</bold>)
9 println(<bold>p</bold>)
8 override fun foo(<bold>p: Int</bold>) {
18 i.foo(<bold>2</bold>)
@@ -0,0 +1,3 @@
[NotNull Values]
9 println(<bold>p</bold>)
9 println(<bold>p</bold>)
+4
View File
@@ -0,0 +1,4 @@
9 println(<bold>p</bold>)
8 override fun foo(<bold>p: Int</bold>) {
13 foo(<bold>1</bold>)
18 i.foo(<bold>2</bold>)
@@ -24,6 +24,11 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
} }
@TestMetadata("abstractFun.kt")
public void testAbstractFun() throws Exception {
runTest("idea/testData/slicer/inflow/abstractFun.kt");
}
public void testAllFilesPresentInInflow() throws Exception { public void testAllFilesPresentInInflow() throws Exception {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/slicer/inflow"), Pattern.compile("^(.+)\\.kt$"), null); KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/slicer/inflow"), Pattern.compile("^(.+)\\.kt$"), null);
} }
@@ -223,6 +228,11 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt"); runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
} }
@TestMetadata("overrideFun.kt")
public void testOverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
}
@TestMetadata("overridingFunctionResult.kt") @TestMetadata("overridingFunctionResult.kt")
public void testOverridingFunctionResult() throws Exception { public void testOverridingFunctionResult() throws Exception {
runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt"); runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt");
@@ -24,6 +24,11 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
} }
@TestMetadata("abstractFun.kt")
public void testAbstractFun() throws Exception {
runTest("idea/testData/slicer/inflow/abstractFun.kt");
}
public void testAllFilesPresentInInflow() throws Exception { public void testAllFilesPresentInInflow() throws Exception {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/slicer/inflow"), Pattern.compile("^(.+)\\.kt$"), null); KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/slicer/inflow"), Pattern.compile("^(.+)\\.kt$"), null);
} }
@@ -223,6 +228,11 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt"); runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
} }
@TestMetadata("overrideFun.kt")
public void testOverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
}
@TestMetadata("overridingFunctionResult.kt") @TestMetadata("overridingFunctionResult.kt")
public void testOverridingFunctionResult() throws Exception { public void testOverridingFunctionResult() throws Exception {
runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt"); runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt");
@@ -28,6 +28,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/slicer"), Pattern.compile("^(.+)\\.kt$"), null); KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/slicer"), Pattern.compile("^(.+)\\.kt$"), null);
} }
@TestMetadata("inflow/abstractFun.kt")
public void testInflow_AbstractFun() throws Exception {
runTest("idea/testData/slicer/inflow/abstractFun.kt");
}
@TestMetadata("inflow/anonymousFunBodyExpression.kt") @TestMetadata("inflow/anonymousFunBodyExpression.kt")
public void testInflow_AnonymousFunBodyExpression() throws Exception { public void testInflow_AnonymousFunBodyExpression() throws Exception {
runTest("idea/testData/slicer/inflow/anonymousFunBodyExpression.kt"); runTest("idea/testData/slicer/inflow/anonymousFunBodyExpression.kt");
@@ -223,6 +228,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt"); runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
} }
@TestMetadata("inflow/overrideFun.kt")
public void testInflow_OverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
}
@TestMetadata("inflow/overridingFunctionResult.kt") @TestMetadata("inflow/overridingFunctionResult.kt")
public void testInflow_OverridingFunctionResult() throws Exception { public void testInflow_OverridingFunctionResult() throws Exception {
runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt"); runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt");