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
fun PsiElement.processAllExactUsages(
options: () -> FindUsagesOptions,
options: FindUsagesOptions,
processor: (UsageInfo) -> Unit
) {
fun elementsToCheckReferenceAgainst(reference: PsiReference): List<PsiElement> {
@@ -62,7 +62,7 @@ fun PsiElement.processAllExactUsages(
}
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.pseudocodeTraverser.TraversalOrder
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.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.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.refactoring.changeSignature.KotlinValVar
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
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.calls.model.DefaultValueArgument
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.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
@@ -79,17 +80,23 @@ private fun KtDeclaration.processHierarchyUpward(scope: AnalysisScope, processor
.forEach(processor)
}
private fun KtFunction.processCalls(scope: SearchScope, processor: (UsageInfo) -> Unit) {
processAllExactUsages(
{
KotlinFunctionFindUsagesOptions(project).apply {
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope.intersectWith(useScope)
private fun KtFunction.processCalls(scope: SearchScope, exactFunctionOnly: Boolean, processor: (UsageInfo) -> Unit) {
val options = KotlinFunctionFindUsagesOptions(project).apply {
isSearchForTextOccurrences = false
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 {
@@ -102,16 +109,13 @@ private fun KtDeclaration.processVariableAccesses(
processor: (UsageInfo) -> Unit
) {
processAllExactUsages(
{
KotlinPropertyFindUsagesOptions(project).apply {
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
isReadWriteAccess = kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope.intersectWith(useScope)
}
KotlinPropertyFindUsagesOptions(project).apply {
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
isReadWriteAccess = kind == AccessKind.WRITE_WITH_OPTIONAL_READ || kind == AccessKind.READ_OR_WRITE
isSearchForTextOccurrences = false
isSkipImportStatements = true
searchScope = scope.intersectWith(useScope)
},
processor
)
@@ -231,7 +235,6 @@ class InflowSlicer(
if (!canProcess()) return
val function = ownerFunction ?: return
if (function.isOverridable()) return
if (function is KtPropertyAccessor && function.isSetter) {
function.property.processPropertyAssignments()
@@ -251,7 +254,7 @@ class InflowSlicer(
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 refParent = refElement.parent
@@ -259,8 +262,9 @@ class InflowSlicer(
refElement is KtExpression -> {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body
val resolvedCall = callElement.resolveToCall() ?: return@body
val valueArguments = resolvedCall.originalValueArguments
when (val resolvedArgument = valueArguments[parameterDescriptor] ?: return@body) {
val callParameterDescriptor = resolvedCall.resultingDescriptor.valueParameters[parameterDescriptor.index]
val resolvedArgument = resolvedCall.valueArguments[callParameterDescriptor] ?: return@body
when (resolvedArgument) {
is DefaultValueArgument -> defaultValue
is ExpressionValueArgument -> resolvedArgument.valueArgument?.getArgumentExpression()
else -> null
@@ -431,7 +435,7 @@ class OutflowSlicer(
if (this is KtConstructor<*> || this is KtNamedFunction && name != null) {
processHierarchyUpward(parentUsage.scope) {
if (this is KtFunction) {
processCalls(parentUsage.scope.toSearchScope()) {
processCalls(parentUsage.scope.toSearchScope(), exactFunctionOnly = true) {
when (val refElement = it.element) {
null -> (it.reference as? LightMemberReference)?.element?.passToProcessor()
is KtExpression -> {
@@ -536,17 +540,3 @@ private val DeclarationDescriptorWithSource.originalSource: SourceElement
}
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 x = <bold>(::bar)(1)</bold>
5 [LAMBDA] val x = <bold>(::bar)</bold>(1)
4 fun <bold>bar(n: Int) = n</bold>
4 fun bar(n: Int) = <bold>n</bold>
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 bar(n: Int) = <bold>n</bold>
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);
}
@TestMetadata("abstractFun.kt")
public void testAbstractFun() throws Exception {
runTest("idea/testData/slicer/inflow/abstractFun.kt");
}
public void testAllFilesPresentInInflow() throws Exception {
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");
}
@TestMetadata("overrideFun.kt")
public void testOverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
}
@TestMetadata("overridingFunctionResult.kt")
public void testOverridingFunctionResult() throws Exception {
runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt");
@@ -24,6 +24,11 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
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 {
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");
}
@TestMetadata("overrideFun.kt")
public void testOverrideFun() throws Exception {
runTest("idea/testData/slicer/inflow/overrideFun.kt");
}
@TestMetadata("overridingFunctionResult.kt")
public void testOverridingFunctionResult() throws Exception {
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);
}
@TestMetadata("inflow/abstractFun.kt")
public void testInflow_AbstractFun() throws Exception {
runTest("idea/testData/slicer/inflow/abstractFun.kt");
}
@TestMetadata("inflow/anonymousFunBodyExpression.kt")
public void testInflow_AnonymousFunBodyExpression() throws Exception {
runTest("idea/testData/slicer/inflow/anonymousFunBodyExpression.kt");
@@ -223,6 +228,11 @@ public class SlicerTreeTestGenerated extends AbstractSlicerTreeTest {
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")
public void testInflow_OverridingFunctionResult() throws Exception {
runTest("idea/testData/slicer/inflow/overridingFunctionResult.kt");