diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt index f42eb5e5674..172161ed34a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ConstructorConsistencyChecker.kt @@ -24,9 +24,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.ReadValueInstruction import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingTrace @@ -34,6 +32,8 @@ import org.jetbrains.kotlin.types.expressions.OperatorConventions 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 ConstructorConsistencyChecker private constructor( @@ -43,6 +43,7 @@ class ConstructorConsistencyChecker private constructor( private val pseudocode: Pseudocode, private val variablesData: PseudocodeVariablesData ) { + private val finalClass = classDescriptor.isFinalClass private fun safeThisUsage(expression: KtThisExpression): Boolean { val referenceDescriptor = trace.get(BindingContext.REFERENCE_TARGET, expression.instanceReference) @@ -81,10 +82,16 @@ class ConstructorConsistencyChecker private constructor( } fun handleLeakingThis(expression: KtExpression) { - val uninitializedProperty = firstUninitializedNotNullProperty() - if (uninitializedProperty != null) { + if (!finalClass) { trace.record(BindingContext.LEAKING_THIS, target(expression), - LeakingThisDescriptor.PropertyIsNull(uninitializedProperty, classOrObject)) + LeakingThisDescriptor.NonFinalClass(classDescriptor, classOrObject)) + } + else { + val uninitializedProperty = firstUninitializedNotNullProperty() + if (uninitializedProperty != null) { + trace.record(BindingContext.LEAKING_THIS, target(expression), + LeakingThisDescriptor.PropertyIsNull(uninitializedProperty, classOrObject)) + } } } diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/open.kt b/compiler/testData/diagnostics/tests/constructorConsistency/open.kt new file mode 100644 index 00000000000..355dfca1cce --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/open.kt @@ -0,0 +1,34 @@ +open class Base { + init { + register(this) + foo() + } + + open fun foo() {} +} + +fun register(arg: Base) { + arg.foo() +} + +class Derived(val x: Int) : Base() { + override fun foo() { + x.hashCode() // NPE in Base constructor + } +} + +enum class MyEnum { + FIRST() { + val x: Int = 42 + + override fun foo() { + x.hashCode() // NPE in MyEnum constructor + } + }; + + init { + foo() + } + + abstract fun foo() +} diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/open.txt b/compiler/testData/diagnostics/tests/constructorConsistency/open.txt new file mode 100644 index 00000000000..d35cd37d51b --- /dev/null +++ b/compiler/testData/diagnostics/tests/constructorConsistency/open.txt @@ -0,0 +1,40 @@ +package + +public fun register(/*0*/ arg: Base): kotlin.Unit + +public open class Base { + public constructor Base() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Derived : Base { + public constructor Derived(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final enum class MyEnum : kotlin.Enum { + enum entry FIRST + + private constructor MyEnum() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public abstract fun foo(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MyEnum + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/testData/diagnostics/tests/scopes/inheritance/methodsPriority.kt b/compiler/testData/diagnostics/tests/scopes/inheritance/methodsPriority.kt index 3d7b5aae0f4..e0e5ba34eeb 100644 --- a/compiler/testData/diagnostics/tests/scopes/inheritance/methodsPriority.kt +++ b/compiler/testData/diagnostics/tests/scopes/inheritance/methodsPriority.kt @@ -10,7 +10,7 @@ fun foo() = "" open class B: A() { init { - val a: Int = foo() + val a: Int = foo() } } diff --git a/compiler/testData/diagnostics/tests/scopes/inheritance/statics/companionObject/inheritFromContainingClass.kt b/compiler/testData/diagnostics/tests/scopes/inheritance/statics/companionObject/inheritFromContainingClass.kt index e3d50f4fc08..f16b5fef490 100644 --- a/compiler/testData/diagnostics/tests/scopes/inheritance/statics/companionObject/inheritFromContainingClass.kt +++ b/compiler/testData/diagnostics/tests/scopes/inheritance/statics/companionObject/inheritFromContainingClass.kt @@ -10,8 +10,8 @@ open class A : J() { init { foo() bar() - val a: Int = baz() - val b: T = baz() + val a: Int = baz() + val b: T = baz() } fun test1() { diff --git a/compiler/testData/diagnostics/tests/scopes/inheritance/statics/staticVsMember.kt b/compiler/testData/diagnostics/tests/scopes/inheritance/statics/staticVsMember.kt index 434f1c18063..e6920274b35 100644 --- a/compiler/testData/diagnostics/tests/scopes/inheritance/statics/staticVsMember.kt +++ b/compiler/testData/diagnostics/tests/scopes/inheritance/statics/staticVsMember.kt @@ -26,7 +26,7 @@ open class C: A() { fun foo() = "" init { - val a: String = foo() + val a: String = foo() val b: String = bar } } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index d188752380e..6f5e64363c8 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -2973,6 +2973,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("open.kt") + public void testOpen() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/open.kt"); + doTest(fileName); + } + @TestMetadata("outer.kt") public void testOuter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/outer.kt");