Analyze Data Flow: Support properties with custom accessors

#KT-11994 In Progress
This commit is contained in:
Alexey Sedunov
2017-05-30 19:40:14 +03:00
parent d61ddaccb6
commit ea7d535ae7
25 changed files with 267 additions and 32 deletions
@@ -183,7 +183,7 @@ public class KtParameter extends KtNamedDeclarationStub<KotlinParameterStub> imp
}
@Nullable
public KtFunction getOwnerFunction() {
public KtDeclarationWithBody getOwnerFunction() {
PsiElement parent = getParentByStub();
if (!(parent instanceof KtParameterList)) return null;
return ((KtParameterList) parent).getOwnerFunction();
@@ -74,10 +74,10 @@ public class KtParameterList extends KtElementImplStub<KotlinPlaceHolderStub<KtP
removeParameter(getParameters().get(index));
}
public KtFunction getOwnerFunction() {
public KtDeclarationWithBody getOwnerFunction() {
PsiElement parent = getParentByStub();
if (!(parent instanceof KtFunction)) return null;
return (KtFunction) parent;
if (!(parent instanceof KtDeclarationWithBody)) return null;
return (KtDeclarationWithBody) parent;
}
@Nullable
@@ -155,7 +155,7 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
private fun searchNamedArguments(parameter: KtParameter) {
val parameterName = parameter.name ?: return
val function = parameter.ownerFunction ?: return
val function = parameter.ownerFunction as? KtFunction ?: return
if (function.nameAsName?.isSpecial ?: true) return
val project = function.project
var namedArgsScope = function.useScope.intersectWith(queryParameters.scopeDeterminedByUser)
@@ -327,7 +327,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
if (functionType.receiverTypeReference != null) return null
val lambdaType = functionType.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL)) ?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val typeParameterIndex = functionType.parameters.indexOf(parameter)
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(typeParameterIndex, functionParameterIndex, lambdaType, ownerFunction)
@@ -195,7 +195,7 @@ class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntent
?.getReceiverTypeFromFunctionType()
?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(functionParameterIndex, lambdaReceiverType, ownerFunction)
}
@@ -99,7 +99,8 @@ class KtParameterPattern : PsiElementPattern<KtParameter, KtParameterPattern>(Kt
override fun processValues(ktParameter: KtParameter,
context: ProcessingContext,
processor: PairProcessor<KtFunction, ProcessingContext>): Boolean {
return processor.process(ktParameter.ownerFunction, context)
val function = ktParameter.ownerFunction as? KtFunction ?: return true
return processor.process(function, context)
}
override fun accepts(ktParameter: KtParameter, context: ProcessingContext): Boolean {
@@ -36,7 +36,7 @@ class KotlinSliceProvider : SliceLanguageSupportProvider {
?.firstOrNull {
it is KtProperty ||
it is KtParameter ||
it is KtFunction ||
it is KtDeclarationWithBody ||
(it is KtExpression && it !is KtDeclaration)
}
?.let { KtPsiUtil.safeDeparenthesize(it as KtExpression) } ?: return null
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.slicer
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.slicer.SliceUsage
import com.intellij.usageView.UsageInfo
@@ -33,14 +34,16 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruc
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
@@ -85,13 +88,6 @@ private fun KtDeclaration.processVariableAccesses(
)
}
private fun KtProperty.canProcess(): Boolean {
if (hasDelegate()) return false
if (isLocal) return true
val descriptor = resolveToDescriptor() as? PropertyDescriptor ?: return false
return descriptor.accessors.all { it.isDefault }
}
private fun KtParameter.canProcess(): Boolean {
return !(isLoopParameter || isVarArg || hasValOrVar())
}
@@ -131,26 +127,69 @@ class InflowSlicer(
) : Slicer(element, processor, parentUsage) {
private fun PsiElement.passToProcessorAsValue(lambdaLevel: Int = parentUsage.lambdaLevel) = passToProcessor(lambdaLevel, true)
private fun KtProperty.processProperty() {
if (!canProcess()) return
initializer?.passToProcessorAsValue()
processVariableAccesses(parentUsage.scope.toSearchScope(), Access.Write) body@ {
private fun KtProperty.processAssignments() {
val analysisScope = parentUsage.scope.toSearchScope()
val accessSearchScope = if (isVar) analysisScope else {
val containerScope = getStrictParentOfType<KtDeclaration>()?.let { LocalSearchScope(it) } ?: return
analysisScope.intersectWith(containerScope)
}
processVariableAccesses(accessSearchScope, Access.Write) body@ {
val refExpression = it.element as? KtExpression ?: return@body
val rhs = KtPsiUtil.safeDeparenthesize(refExpression).getAssignmentByLHS()?.right ?: return@body
rhs.passToProcessorAsValue()
}
}
private fun KtPropertyAccessor.processBackingFieldAssignments() {
forEachDescendantOfType<KtBinaryExpression> body@ {
if (it.operationToken != KtTokens.EQ) return@body
val lhs = it.left?.let { KtPsiUtil.safeDeparenthesize(it) } ?: return@body
val rhs = it.right ?: return@body
if (!lhs.isBackingFieldReference()) return@body
rhs.passToProcessor()
}
}
private fun KtProperty.processProperty() {
val bindingContext by lazy { analyzeFully() }
if (hasDelegateExpression()) {
val getter = (resolveToDescriptor() as VariableDescriptorWithAccessors).getter
val delegateGetterResolvedCall = getter?.let { bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it] }
(delegateGetterResolvedCall?.resultingDescriptor?.source?.getPsi() as? KtDeclarationWithBody)?.passToProcessor()
return
}
initializer?.passToProcessor()
getter?.processFunction()
val isDefaultGetter = getter?.bodyExpression == null
val isDefaultSetter = setter?.bodyExpression == null
if (isDefaultGetter) {
if (isDefaultSetter) {
processAssignments()
}
else {
setter!!.processBackingFieldAssignments()
}
}
}
private fun KtParameter.processParameter() {
if (!canProcess()) return
val function = ownerFunction ?: return
if (function.isOverridable()) return
if (function is KtPropertyAccessor && function.isSetter) {
function.property.processAssignments()
return
}
val parameterDescriptor = resolveToDescriptor() as ValueParameterDescriptor
function.processCalls(parentUsage.scope.toSearchScope()) body@ {
(function as? KtFunction)?.processCalls(parentUsage.scope.toSearchScope()) body@ {
val refExpression = it.element as? KtExpression ?: return@body
val callElement = refExpression.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body
val resolvedCall = callElement.getResolvedCall(callElement.analyze()) ?: return@body
@@ -164,8 +203,9 @@ class InflowSlicer(
}
}
private fun KtFunction.processFunction() {
val pseudocode = pseudocodeCache[bodyExpression ?: return] ?: return
private fun KtDeclarationWithBody.processFunction() {
val bodyExpression = bodyExpression ?: return
val pseudocode = pseudocodeCache[bodyExpression] ?: return
pseudocode.traverse(TraversalOrder.FORWARD) { instr ->
if (instr is ReturnValueInstruction && instr.subroutine == this) {
(instr.returnExpressionIfAny?.returnedExpression ?: instr.element as? KtExpression)?.passToProcessorAsValue()
@@ -181,6 +221,12 @@ class InflowSlicer(
}
}
private fun KtExpression.isBackingFieldReference(): Boolean {
return this is KtSimpleNameExpression &&
getReferencedName() == SyntheticFieldDescriptor.NAME.asString() &&
analyze()[BindingContext.REFERENCE_TARGET, this] is SyntheticFieldDescriptor
}
private fun KtExpression.processExpression() {
val lambda = when (this) {
is KtLambdaExpression -> functionLiteral
@@ -206,7 +252,19 @@ class InflowSlicer(
}
return
}
(createdAt.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor()
val accessedDescriptor = createdAt.target.accessedDescriptor ?: return
val accessedDeclaration = accessedDescriptor.source.getPsi() as? KtDeclaration ?: return
if (accessedDescriptor is SyntheticFieldDescriptor) {
val property = accessedDeclaration as? KtProperty ?: return
if (accessedDescriptor.propertyDescriptor.setter?.isDefault ?: true) {
property.processAssignments()
}
else {
property.setter?.processBackingFieldAssignments()
}
return
}
accessedDeclaration.passToProcessor()
}
is MergeInstruction -> createdAt.passInputsToProcessor()
@@ -241,7 +299,7 @@ class InflowSlicer(
when (element) {
is KtProperty -> element.processProperty()
is KtParameter -> element.processParameter()
is KtFunction -> element.processFunction()
is KtDeclarationWithBody -> element.processFunction()
else -> element.processExpression()
}
}
@@ -253,11 +311,8 @@ class OutflowSlicer(
parentUsage: KotlinSliceUsage
) : Slicer(element, processor, parentUsage) {
private fun KtDeclaration.processVariable() {
when (this) {
is KtProperty -> if (!canProcess()) return
is KtParameter -> if (!canProcess()) return
else -> return
}
if (this is KtParameter && !canProcess()) return
val withDereferences = parentUsage.params.showInstanceDereferences
processVariableAccesses(
parentUsage.scope.toSearchScope(),
@@ -368,6 +423,9 @@ class OutflowSlicer(
when (element) {
is KtProperty, is KtParameter -> (element as KtDeclaration).processVariable()
is KtFunction -> element.processFunction()
is KtPropertyAccessor -> if (element.isGetter) {
element.property.processVariable()
}
else -> element.processExpression()
}
}
@@ -0,0 +1,13 @@
// FLOW: IN
class A(var b: Boolean) {
var foo: Int
set(value) {
field = if (b) value else 0
}
fun test() {
val x = <caret>foo
foo = 1
}
}
@@ -0,0 +1,7 @@
10 val x = <bold>foo</bold>
4 var <bold>foo: Int</bold>
6 field = <bold>if (b) value else 0</bold>
6 field = if (b) <bold>value</bold> else 0
5 set(<bold>value</bold>) {
11 foo = <bold>1</bold>
6 field = if (b) value else <bold>0</bold>
+13
View File
@@ -0,0 +1,13 @@
// FLOW: IN
import kotlin.reflect.KProperty
object D {
operator fun getValue(thisRef: Any?, property: KProperty<*>) = 1
}
val foo: Int by D
fun test() {
val <caret>x = foo
}
@@ -0,0 +1,5 @@
12 val <bold>x = foo</bold>
12 val x = <bold>foo</bold>
9 val <bold>foo: Int by D</bold>
6 operator fun <bold>getValue(thisRef: Any?, property: KProperty<*>) = 1</bold>
6 operator fun getValue(thisRef: Any?, property: KProperty<*>) = <bold>1</bold>
@@ -0,0 +1,14 @@
// FLOW: IN
class A(var b: Boolean) {
var foo: Int
get() = if (b) field else 0
set(value) {
field = if (b) value else 0
}
fun test() {
val x = <caret>foo
foo = 1
}
}
@@ -0,0 +1,10 @@
11 val x = <bold>foo</bold>
4 var <bold>foo: Int</bold>
5 get() = <bold>if (b) field else 0</bold>
5 get() = if (b) <bold>field</bold> else 0
7 field = <bold>if (b) value else 0</bold>
7 field = if (b) <bold>value</bold> else 0
6 set(<bold>value</bold>) {
12 foo = <bold>1</bold>
7 field = if (b) value else <bold>0</bold>
5 get() = if (b) field else <bold>0</bold>
+8
View File
@@ -0,0 +1,8 @@
// FLOW: IN
val foo: Int
get() = 0
fun test() {
val <caret>x = foo
}
@@ -0,0 +1,4 @@
7 val <bold>x = foo</bold>
7 val x = <bold>foo</bold>
3 val <bold>foo: Int</bold>
4 get() = <bold>0</bold>
+10
View File
@@ -0,0 +1,10 @@
// FLOW: IN
val foo: Int
get(): Int {
return 0
}
fun test() {
val <caret>x = foo
}
@@ -0,0 +1,4 @@
9 val <bold>x = foo</bold>
9 val x = <bold>foo</bold>
3 val <bold>foo: Int</bold>
5 return <bold>0</bold>
+13
View File
@@ -0,0 +1,13 @@
// FLOW: IN
class A(var b: Boolean) {
var foo: Int
get() = 1
set(value) {
field = value + 1
}
fun test() {
val x = <caret>foo
}
}
@@ -0,0 +1,3 @@
11 val x = <bold>foo</bold>
4 var <bold>foo: Int</bold>
5 get() = <bold>1</bold>
+8
View File
@@ -0,0 +1,8 @@
// FLOW: OUT
val foo: Int
get() = <caret>0
fun test() {
val x = foo
}
@@ -0,0 +1,3 @@
4 get() = <bold>0</bold>
4 <bold>get() = 0</bold>
7 val <bold>x = foo</bold>
+10
View File
@@ -0,0 +1,10 @@
// FLOW: OUT
val foo: Int
get(): Int {
return <caret>0
}
fun test() {
val x = foo
}
@@ -0,0 +1,3 @@
5 return <bold>0</bold>
4 <bold>get(): Int {</bold>
9 val <bold>x = foo</bold>
@@ -54,6 +54,18 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName);
}
@TestMetadata("inflow/defaultGetterFieldInSetter.kt")
public void testInflow_DefaultGetterFieldInSetter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt");
doTest(fileName);
}
@TestMetadata("inflow/delegateGetter.kt")
public void testInflow_DelegateGetter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/delegateGetter.kt");
doTest(fileName);
}
@TestMetadata("inflow/doubleLambdaResult.kt")
public void testInflow_DoubleLambdaResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/doubleLambdaResult.kt");
@@ -102,6 +114,24 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName);
}
@TestMetadata("inflow/getterAndSetterUsingField.kt")
public void testInflow_GetterAndSetterUsingField() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/getterAndSetterUsingField.kt");
doTest(fileName);
}
@TestMetadata("inflow/getterExpressionBody.kt")
public void testInflow_GetterExpressionBody() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/getterExpressionBody.kt");
doTest(fileName);
}
@TestMetadata("inflow/getterReturnExpression.kt")
public void testInflow_GetterReturnExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/getterReturnExpression.kt");
doTest(fileName);
}
@TestMetadata("inflow/ifExpression.kt")
public void testInflow_IfExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/ifExpression.kt");
@@ -168,6 +198,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName);
}
@TestMetadata("inflow/noFieldInGetter.kt")
public void testInflow_NoFieldInGetter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/noFieldInGetter.kt");
doTest(fileName);
}
@TestMetadata("inflow/nonLocalReturn.kt")
public void testInflow_NonLocalReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/nonLocalReturn.kt");
@@ -312,6 +348,18 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
doTest(fileName);
}
@TestMetadata("outflow/getterExpressionBody.kt")
public void testOutflow_GetterExpressionBody() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/getterExpressionBody.kt");
doTest(fileName);
}
@TestMetadata("outflow/getterReturnExpression.kt")
public void testOutflow_GetterReturnExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/getterReturnExpression.kt");
doTest(fileName);
}
@TestMetadata("outflow/getFunCalls.kt")
public void testOutflow_GetFunCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/getFunCalls.kt");