diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt index 172161ed34a..4fa9c204594 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt @@ -34,6 +34,8 @@ sealed class LeakingThisDescriptor(val classOrObject: KtClassOrObject) { class PropertyIsNull(val property: PropertyDescriptor, classOrObject: KtClassOrObject) : LeakingThisDescriptor(classOrObject) class NonFinalClass(val klass: ClassDescriptor, classOrObject: KtClassOrObject): LeakingThisDescriptor(classOrObject) + + class NonFinalProperty(val property: PropertyDescriptor, classOrObject: KtClassOrObject): LeakingThisDescriptor(classOrObject) } class ConstructorConsistencyChecker private constructor( @@ -45,12 +47,26 @@ class ConstructorConsistencyChecker private constructor( ) { private val finalClass = classDescriptor.isFinalClass + private fun checkOpenPropertyAccess(reference: KtReferenceExpression) { + if (!finalClass) { + val descriptor = trace.get(BindingContext.REFERENCE_TARGET, reference) + if (descriptor is PropertyDescriptor && descriptor.isOverridable) { + trace.record(BindingContext.LEAKING_THIS, reference, LeakingThisDescriptor.NonFinalProperty(descriptor, classOrObject)) + } + } + } + private fun safeThisUsage(expression: KtThisExpression): Boolean { val referenceDescriptor = trace.get(BindingContext.REFERENCE_TARGET, expression.instanceReference) if (referenceDescriptor != classDescriptor) return true val parent = expression.parent return when (parent) { - is KtQualifiedExpression -> parent.selectorExpression is KtSimpleNameExpression + is KtQualifiedExpression -> + if (parent.selectorExpression is KtSimpleNameExpression) { + checkOpenPropertyAccess(parent.selectorExpression as KtSimpleNameExpression) + true + } + else false is KtBinaryExpression -> OperatorConventions.IDENTITY_EQUALS_OPERATIONS.contains(parent.operationToken) else -> false } @@ -109,9 +125,14 @@ class ConstructorConsistencyChecker private constructor( } } is MagicInstruction -> - if (instruction.kind == MagicKind.IMPLICIT_RECEIVER && element is KtCallExpression) { - if (!safeCallUsage(element)) { - handleLeakingThis(element) + if (instruction.kind == MagicKind.IMPLICIT_RECEIVER) { + if (element is KtCallExpression) { + if (!safeCallUsage(element)) { + handleLeakingThis(element) + } + } + else if (element is KtReferenceExpression) { + checkOpenPropertyAccess(element) } } } diff --git a/compiler/testData/diagnostics/tests/backingField/InitOpenSetter.kt b/compiler/testData/diagnostics/tests/backingField/InitOpenSetter.kt index 8173d1ec81b..b708cdd35d7 100644 --- a/compiler/testData/diagnostics/tests/backingField/InitOpenSetter.kt +++ b/compiler/testData/diagnostics/tests/backingField/InitOpenSetter.kt @@ -16,12 +16,12 @@ abstract class My(val v: Int) { set(arg) { w = 2 * arg } constructor(): this(0) { - z = v + z = v } init { - x = 1 - y = 2 - u = 3 + x = 1 + y = 2 + u = 3 } } diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.kt b/compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.kt new file mode 100644 index 00000000000..ad5f9d89b89 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.kt @@ -0,0 +1,11 @@ +interface Base { + val x: Int +} + +open class Impl(override val x: Int) : Base { + init { + if (this.x != 0) foo() + } +} + +fun foo() {} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.txt b/compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.txt new file mode 100644 index 00000000000..be81a025e6a --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.txt @@ -0,0 +1,18 @@ +package + +public fun foo(): kotlin.Unit + +public interface Base { + public abstract val x: kotlin.Int + 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 open class Impl : Base { + public constructor Impl(/*0*/ x: kotlin.Int) + public open override /*1*/ val x: kotlin.Int + 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 +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/openProperty.kt b/compiler/testData/diagnostics/tests/constructorConsistency/openProperty.kt new file mode 100644 index 00000000000..980f541ebf4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/openProperty.kt @@ -0,0 +1,14 @@ +open class Base { + open var x: Int + + open var y: Int + + constructor() { + x = 42 + this.y = 24 + val temp = this.x + this.x = y + y = temp + + } +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/openProperty.txt b/compiler/testData/diagnostics/tests/constructorConsistency/openProperty.txt new file mode 100644 index 00000000000..a0f9b3a38d6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/openProperty.txt @@ -0,0 +1,10 @@ +package + +public open class Base { + public constructor Base() + public open var x: kotlin.Int + public open var y: kotlin.Int + 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/openInSealed.kt b/compiler/testData/diagnostics/tests/smartCasts/openInSealed.kt index 70a19484fba..737e62dc318 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/openInSealed.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/openInSealed.kt @@ -1,8 +1,8 @@ sealed class My(open val x: Int?) { init { - if (x != null) { + if (x != null) { // Should be error: property is open - x.hashCode() + x.hashCode() } } } \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 6f5e64363c8..2054cdcf4a0 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -2943,6 +2943,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("derivedProperty.kt") + public void testDerivedProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/derivedProperty.kt"); + doTest(fileName); + } + @TestMetadata("init.kt") public void testInit() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/init.kt"); @@ -2979,6 +2985,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("openProperty.kt") + public void testOpenProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/openProperty.kt"); + doTest(fileName); + } + @TestMetadata("outer.kt") public void testOuter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/outer.kt");