CFA: detect captured writes more precisely

So #KT-14381 Fixed
So #KT-13597 Fixed
Also refactors captured writes detection inside DFA
This commit is contained in:
Mikhail Glukhikh
2017-04-27 13:34:24 +03:00
committed by Mikhail Glukhikh
parent 8fa739ed0f
commit 56e633e345
8 changed files with 116 additions and 50 deletions
@@ -16,7 +16,7 @@
package org.jetbrains.kotlin.cfg package org.jetbrains.kotlin.cfg
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil.getParentOfType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.TailRecursionKind.* import org.jetbrains.kotlin.cfg.TailRecursionKind.*
import org.jetbrains.kotlin.cfg.VariableUseState.* import org.jetbrains.kotlin.cfg.VariableUseState.*
@@ -359,37 +359,34 @@ class ControlFlowInformationProvider private constructor(
} }
} }
private fun getDeclarationDescriptor(declaration: KtDeclaration?): DeclarationDescriptor? {
val descriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration)
return if (descriptor is ClassDescriptor) {
// For a class primary constructor, we cannot directly get ConstructorDescriptor by KtClassInitializer,
// so we have to do additional conversion: KtClassInitializer -> KtClassOrObject -> ClassDescriptor -> ConstructorDescriptor
descriptor.unsubstitutedPrimaryConstructor
}
else {
descriptor
}
}
private fun isCapturedWrite( private fun isCapturedWrite(
variableDescriptor: VariableDescriptor, variableDescriptor: VariableDescriptor,
writeValueInstruction: WriteValueInstruction writeValueInstruction: WriteValueInstruction
): Boolean { ): Boolean {
val containingDeclarationDescriptor = variableDescriptor.containingDeclaration val containingDeclarationDescriptor = variableDescriptor.containingDeclaration
// Do not consider member / top-level properties // Do not consider top-level properties
if (containingDeclarationDescriptor is ClassOrPackageFragmentDescriptor) return false if (containingDeclarationDescriptor is PackageFragmentDescriptor) return false
var parentDeclaration = getElementParentDeclaration(writeValueInstruction.element) var parentDeclaration = getElementParentDeclaration(writeValueInstruction.element)
while (true) { while (true) {
val parentDescriptor = getDeclarationDescriptor(parentDeclaration) val context = trace.bindingContext
if (containingDeclarationDescriptor == parentDescriptor) { val parentDescriptor = getDeclarationDescriptorIncludingConstructors(context, parentDeclaration)
if (parentDescriptor == containingDeclarationDescriptor) {
return false return false
} }
else if (parentDeclaration is KtObjectDeclaration) { when (parentDeclaration) {
// anonymous object counts here the same as its owner is KtObjectDeclaration, is KtClassInitializer -> {
parentDeclaration = getElementParentDeclaration(parentDeclaration) // anonymous objects / initializers count here the same as its owner
} parentDeclaration = getElementParentDeclaration(parentDeclaration)
else { }
return true is KtDeclarationWithBody -> {
if (parentDeclaration is KtFunction && parentDeclaration.isLocal) return true
// miss non-local function or accessor just once
parentDeclaration = getElementParentDeclaration(parentDeclaration)
return getDeclarationDescriptorIncludingConstructors(context, parentDeclaration) != containingDeclarationDescriptor
}
else -> {
return true
}
} }
} }
} }
@@ -403,7 +400,7 @@ class ControlFlowInformationProvider private constructor(
val variableDescriptor = ctxt.variableDescriptor val variableDescriptor = ctxt.variableDescriptor
val propertyDescriptor = variableDescriptor?.referencedProperty val propertyDescriptor = variableDescriptor?.referencedProperty
if (KtPsiUtil.isBackingFieldReference(variableDescriptor) && propertyDescriptor != null) { if (KtPsiUtil.isBackingFieldReference(variableDescriptor) && propertyDescriptor != null) {
val accessor = PsiTreeUtil.getParentOfType(expression, KtPropertyAccessor::class.java) val accessor = getParentOfType(expression, KtPropertyAccessor::class.java)
if (accessor != null) { if (accessor != null) {
val accessorDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, accessor) val accessorDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, accessor)
if (propertyDescriptor.getter === accessorDescriptor) { if (propertyDescriptor.getter === accessorDescriptor) {
@@ -936,7 +933,7 @@ class ControlFlowInformationProvider private constructor(
} }
private fun isInsideTry(element: KtElement) = private fun isInsideTry(element: KtElement) =
PsiTreeUtil.getParentOfType( getParentOfType(
element, element,
KtTryExpression::class.java, KtFunction::class.java, KtAnonymousInitializer::class.java KtTryExpression::class.java, KtFunction::class.java, KtAnonymousInitializer::class.java
) is KtTryExpression ) is KtTryExpression
@@ -1043,9 +1040,23 @@ class ControlFlowInformationProvider private constructor(
companion object { companion object {
// Should return KtDeclarationWithBody or KtClassOrObject // Should return KtDeclarationWithBody, KtClassOrObject, or KtClassInitializer
fun getElementParentDeclaration(element: KtElement) = fun getElementParentDeclaration(element: KtElement) =
PsiTreeUtil.getParentOfType(element, KtDeclarationWithBody::class.java, KtClassOrObject::class.java) getParentOfType(element, KtDeclarationWithBody::class.java, KtClassOrObject::class.java, KtClassInitializer::class.java)
fun getDeclarationDescriptorIncludingConstructors(context: BindingContext, declaration: KtDeclaration?): DeclarationDescriptor? {
val descriptor = context.get(DECLARATION_TO_DESCRIPTOR,
(declaration as? KtClassInitializer)?.containingDeclaration ?: declaration)
return if (descriptor is ClassDescriptor && declaration is KtClassInitializer) {
// For a class primary constructor, we cannot directly get ConstructorDescriptor by KtClassInitializer,
// so we have to do additional conversion: KtClassInitializer -> KtClassOrObject -> ClassDescriptor -> ConstructorDescriptor
descriptor.unsubstitutedPrimaryConstructor
?: (descriptor as? ClassDescriptorWithResolutionScopes)?.scopeForInitializerResolution?.ownerDescriptor
}
else {
descriptor
}
}
private fun isUsedAsResultOfLambda(usages: List<Instruction>): Boolean { private fun isUsedAsResultOfLambda(usages: List<Instruction>): Boolean {
for (usage in usages) { for (usage in usages) {
@@ -269,19 +269,6 @@ object DataFlowValueFactory {
else -> IdentifierInfo.NO else -> IdentifierInfo.NO
} }
private fun getVariableContainingDeclaration(variableDescriptor: VariableDescriptor): DeclarationDescriptor {
val containingDeclarationDescriptor = variableDescriptor.containingDeclaration
return if (containingDeclarationDescriptor is ConstructorDescriptor && containingDeclarationDescriptor.isPrimary) {
// This code is necessary just because JetClassInitializer has no associated descriptor in trace
// Because of it we have to use class itself instead of initializer,
// otherwise we could not find this descriptor inside isAccessedInsideClosure below
containingDeclarationDescriptor.containingDeclaration
}
else {
containingDeclarationDescriptor
}
}
private fun isAccessedInsideClosure( private fun isAccessedInsideClosure(
variableContainingDeclaration: DeclarationDescriptor, variableContainingDeclaration: DeclarationDescriptor,
bindingContext: BindingContext, bindingContext: BindingContext,
@@ -290,7 +277,8 @@ object DataFlowValueFactory {
val parent = ControlFlowInformationProvider.getElementParentDeclaration(accessElement) val parent = ControlFlowInformationProvider.getElementParentDeclaration(accessElement)
return if (parent != null) return if (parent != null)
// Access is at the same declaration: not in closure, lower: in closure // Access is at the same declaration: not in closure, lower: in closure
variableContainingDeclaration != bindingContext.get(DECLARATION_TO_DESCRIPTOR, parent) ControlFlowInformationProvider.getDeclarationDescriptorIncludingConstructors(bindingContext, parent) !=
variableContainingDeclaration
else else
false false
} }
@@ -304,7 +292,7 @@ object DataFlowValueFactory {
// All writers should be before access element, with the exception: // All writers should be before access element, with the exception:
// writer which is the same with declaration site does not count // writer which is the same with declaration site does not count
writers.filterNotNull().forEach { writer -> writers.filterNotNull().forEach { writer ->
val writerDescriptor = bindingContext.get(DECLARATION_TO_DESCRIPTOR, writer) val writerDescriptor = ControlFlowInformationProvider.getDeclarationDescriptorIncludingConstructors(bindingContext, writer)
// Access is after some writer // Access is after some writer
if (variableContainingDeclaration != writerDescriptor && !accessElement.before(writer)) { if (variableContainingDeclaration != writerDescriptor && !accessElement.before(writer)) {
return false return false
@@ -351,7 +339,7 @@ object DataFlowValueFactory {
if (writers.isEmpty()) return STABLE_VARIABLE if (writers.isEmpty()) return STABLE_VARIABLE
// If access element is inside closure: captured // If access element is inside closure: captured
val variableContainingDeclaration = getVariableContainingDeclaration(variableDescriptor) val variableContainingDeclaration = variableDescriptor.containingDeclaration
if (isAccessedInsideClosure(variableContainingDeclaration, bindingContext, accessElement)) return CAPTURED_VARIABLE if (isAccessedInsideClosure(variableContainingDeclaration, bindingContext, accessElement)) return CAPTURED_VARIABLE
// Otherwise, stable iff considered position is BEFORE all writers except declarer itself // Otherwise, stable iff considered position is BEFORE all writers except declarer itself
@@ -35,13 +35,9 @@ abstract class AssignedVariablesSearcher: KtTreeVisitorVoid() {
override fun visitDeclaration(declaration: KtDeclaration) { override fun visitDeclaration(declaration: KtDeclaration) {
val previous = currentDeclaration val previous = currentDeclaration
if (declaration is KtDeclarationWithBody || declaration is KtClassOrObject) { if (declaration is KtDeclarationWithBody || declaration is KtClassOrObject || declaration is KtAnonymousInitializer) {
currentDeclaration = declaration currentDeclaration = declaration
} }
else if (declaration is KtAnonymousInitializer) {
// Go to class declaration: init -> body -> class
currentDeclaration = declaration.parent.parent as KtDeclaration
}
super.visitDeclaration(declaration) super.visitDeclaration(declaration)
currentDeclaration = previous currentDeclaration = previous
} }
@@ -7,7 +7,7 @@ class Test {
val t = object { val t = object {
fun some() { fun some() {
// See KT-13597 // See KT-13597
a = "12" <!CAPTURED_VAL_INITIALIZATION!>a<!> = "12"
} }
} }
@@ -41,8 +41,27 @@ class Test4 {
init { init {
exec { exec {
// See KT-14381 // See KT-14381
a = "12" <!CAPTURED_VAL_INITIALIZATION!>a<!> = "12"
} }
a = "34" a = "34"
} }
} }
// Additional tests to prevent something broken
class Test5 {
val y: Int
val z: String
init {
val x: String
x = ""
z = x
}
constructor(y: Int) {
this.y = y
}
}
@@ -25,3 +25,12 @@ public final class Test4 {
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
} }
public final class Test5 {
public constructor Test5(/*0*/ y: kotlin.Int)
public final val y: kotlin.Int
public final val z: kotlin.String
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,22 @@
class Your {
init {
var y: String? = "xyz"
if (y != null) {
// Bug that should be fixed
// Problem: descriptorToDeclaration cannot get here init block by its descriptor
// See PreliminaryDeclarationVisitor.getVisitorByVariable
<!SMARTCAST_IMPOSSIBLE!>y<!>.hashCode()
}
}
constructor()
}
class Normal {
init {
var y: String? = "xyz"
if (y != null) {
<!DEBUG_INFO_SMARTCAST!>y<!>.hashCode()
}
}
}
@@ -0,0 +1,15 @@
package
public final class Normal {
public constructor Normal()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class Your {
public constructor Your()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -19867,6 +19867,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("varInInitNoPrimary.kt")
public void testVarInInitNoPrimary() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varInInitNoPrimary.kt");
doTest(fileName);
}
@TestMetadata("varInInitializer.kt") @TestMetadata("varInInitializer.kt")
public void testVarInInitializer() throws Exception { public void testVarInInitializer() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varInInitializer.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varInInitializer.kt");