diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameter.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameter.java index 1e9e18e35b4..0b0eb16837f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameter.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameter.java @@ -183,7 +183,7 @@ public class KtParameter extends KtNamedDeclarationStub imp } @Nullable - public KtFunction getOwnerFunction() { + public KtDeclarationWithBody getOwnerFunction() { PsiElement parent = getParentByStub(); if (!(parent instanceof KtParameterList)) return null; return ((KtParameterList) parent).getOwnerFunction(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java index aa66ee0734c..71f068dece3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java @@ -74,10 +74,10 @@ public class KtParameterList extends KtElementImplStub(Kt override fun processValues(ktParameter: KtParameter, context: ProcessingContext, processor: PairProcessor): 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 { diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt index 3b35238a79c..240cf1e20e1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt b/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt index 797efa4b33d..0ed82919f2c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/Slicer.kt @@ -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()?.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 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 { 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() } } diff --git a/idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt b/idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt new file mode 100644 index 00000000000..66ee8f8902c --- /dev/null +++ b/idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt @@ -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 = foo + foo = 1 + } +} \ No newline at end of file diff --git a/idea/testData/slicer/inflow/defaultGetterFieldInSetter.results.txt b/idea/testData/slicer/inflow/defaultGetterFieldInSetter.results.txt new file mode 100644 index 00000000000..481509af331 --- /dev/null +++ b/idea/testData/slicer/inflow/defaultGetterFieldInSetter.results.txt @@ -0,0 +1,7 @@ +10 val x = foo +4 var foo: Int +6 field = if (b) value else 0 +6 field = if (b) value else 0 +5 set(value) { +11 foo = 1 +6 field = if (b) value else 0 diff --git a/idea/testData/slicer/inflow/delegateGetter.kt b/idea/testData/slicer/inflow/delegateGetter.kt new file mode 100644 index 00000000000..813c6dab0d3 --- /dev/null +++ b/idea/testData/slicer/inflow/delegateGetter.kt @@ -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 x = foo +} \ No newline at end of file diff --git a/idea/testData/slicer/inflow/delegateGetter.results.txt b/idea/testData/slicer/inflow/delegateGetter.results.txt new file mode 100644 index 00000000000..02eee372039 --- /dev/null +++ b/idea/testData/slicer/inflow/delegateGetter.results.txt @@ -0,0 +1,5 @@ +12 val x = foo +12 val x = foo +9 val foo: Int by D +6 operator fun getValue(thisRef: Any?, property: KProperty<*>) = 1 +6 operator fun getValue(thisRef: Any?, property: KProperty<*>) = 1 diff --git a/idea/testData/slicer/inflow/getterAndSetterUsingField.kt b/idea/testData/slicer/inflow/getterAndSetterUsingField.kt new file mode 100644 index 00000000000..8ed3b27c040 --- /dev/null +++ b/idea/testData/slicer/inflow/getterAndSetterUsingField.kt @@ -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 = foo + foo = 1 + } +} \ No newline at end of file diff --git a/idea/testData/slicer/inflow/getterAndSetterUsingField.results.txt b/idea/testData/slicer/inflow/getterAndSetterUsingField.results.txt new file mode 100644 index 00000000000..f1dea8294be --- /dev/null +++ b/idea/testData/slicer/inflow/getterAndSetterUsingField.results.txt @@ -0,0 +1,10 @@ +11 val x = foo +4 var foo: Int +5 get() = if (b) field else 0 +5 get() = if (b) field else 0 +7 field = if (b) value else 0 +7 field = if (b) value else 0 +6 set(value) { +12 foo = 1 +7 field = if (b) value else 0 +5 get() = if (b) field else 0 diff --git a/idea/testData/slicer/inflow/getterExpressionBody.kt b/idea/testData/slicer/inflow/getterExpressionBody.kt new file mode 100644 index 00000000000..b332ac528e0 --- /dev/null +++ b/idea/testData/slicer/inflow/getterExpressionBody.kt @@ -0,0 +1,8 @@ +// FLOW: IN + +val foo: Int + get() = 0 + +fun test() { + val x = foo +} \ No newline at end of file diff --git a/idea/testData/slicer/inflow/getterExpressionBody.results.txt b/idea/testData/slicer/inflow/getterExpressionBody.results.txt new file mode 100644 index 00000000000..30090a4b8f5 --- /dev/null +++ b/idea/testData/slicer/inflow/getterExpressionBody.results.txt @@ -0,0 +1,4 @@ +7 val x = foo +7 val x = foo +3 val foo: Int +4 get() = 0 diff --git a/idea/testData/slicer/inflow/getterReturnExpression.kt b/idea/testData/slicer/inflow/getterReturnExpression.kt new file mode 100644 index 00000000000..d33628bb238 --- /dev/null +++ b/idea/testData/slicer/inflow/getterReturnExpression.kt @@ -0,0 +1,10 @@ +// FLOW: IN + +val foo: Int + get(): Int { + return 0 + } + +fun test() { + val x = foo +} \ No newline at end of file diff --git a/idea/testData/slicer/inflow/getterReturnExpression.results.txt b/idea/testData/slicer/inflow/getterReturnExpression.results.txt new file mode 100644 index 00000000000..6f4cd786678 --- /dev/null +++ b/idea/testData/slicer/inflow/getterReturnExpression.results.txt @@ -0,0 +1,4 @@ +9 val x = foo +9 val x = foo +3 val foo: Int +5 return 0 diff --git a/idea/testData/slicer/inflow/noFieldInGetter.kt b/idea/testData/slicer/inflow/noFieldInGetter.kt new file mode 100644 index 00000000000..edb4f5c552f --- /dev/null +++ b/idea/testData/slicer/inflow/noFieldInGetter.kt @@ -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 = foo + } +} \ No newline at end of file diff --git a/idea/testData/slicer/inflow/noFieldInGetter.results.txt b/idea/testData/slicer/inflow/noFieldInGetter.results.txt new file mode 100644 index 00000000000..9e747bd8702 --- /dev/null +++ b/idea/testData/slicer/inflow/noFieldInGetter.results.txt @@ -0,0 +1,3 @@ +11 val x = foo +4 var foo: Int +5 get() = 1 diff --git a/idea/testData/slicer/outflow/getterExpressionBody.kt b/idea/testData/slicer/outflow/getterExpressionBody.kt new file mode 100644 index 00000000000..fbce01170c1 --- /dev/null +++ b/idea/testData/slicer/outflow/getterExpressionBody.kt @@ -0,0 +1,8 @@ +// FLOW: OUT + +val foo: Int + get() = 0 + +fun test() { + val x = foo +} \ No newline at end of file diff --git a/idea/testData/slicer/outflow/getterExpressionBody.results.txt b/idea/testData/slicer/outflow/getterExpressionBody.results.txt new file mode 100644 index 00000000000..6a5d4c5187b --- /dev/null +++ b/idea/testData/slicer/outflow/getterExpressionBody.results.txt @@ -0,0 +1,3 @@ +4 get() = 0 +4 get() = 0 +7 val x = foo diff --git a/idea/testData/slicer/outflow/getterReturnExpression.kt b/idea/testData/slicer/outflow/getterReturnExpression.kt new file mode 100644 index 00000000000..2484515455d --- /dev/null +++ b/idea/testData/slicer/outflow/getterReturnExpression.kt @@ -0,0 +1,10 @@ +// FLOW: OUT + +val foo: Int + get(): Int { + return 0 + } + +fun test() { + val x = foo +} \ No newline at end of file diff --git a/idea/testData/slicer/outflow/getterReturnExpression.results.txt b/idea/testData/slicer/outflow/getterReturnExpression.results.txt new file mode 100644 index 00000000000..30aa9dde0af --- /dev/null +++ b/idea/testData/slicer/outflow/getterReturnExpression.results.txt @@ -0,0 +1,3 @@ +5 return 0 +4 get(): Int { +9 val x = foo diff --git a/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestGenerated.java index 52d8be55413..596d8697686 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestGenerated.java @@ -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");