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:
committed by
Mikhail Glukhikh
parent
8fa739ed0f
commit
56e633e345
@@ -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) {
|
||||||
|
|||||||
+4
-16
@@ -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
|
||||||
|
|||||||
+1
-5
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+9
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user