diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/inspection.kt b/compiler/testData/diagnostics/tests/constructorConsistency/inspection.kt
new file mode 100644
index 00000000000..91c74f34021
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/constructorConsistency/inspection.kt
@@ -0,0 +1,56 @@
+class First {
+ val x: String
+
+ init {
+ use(this) // NPE! Leaking this
+ x = foo() // NPE! Own function
+ }
+
+ fun foo() = x
+}
+
+fun use(first: First) = first.x.hashCode()
+
+abstract class Second {
+ val x: String
+
+ init {
+ use(this) // Leaking this in non-final
+ x = bar() // Own function in non-final
+ foo() // Non-final function call
+ }
+
+ private fun bar() = foo()
+
+ abstract fun foo(): String
+}
+
+fun use(second: Second) = second.x
+
+class SecondDerived : Second() {
+ val y = x // null!
+
+ override fun foo() = y
+}
+
+open class Third {
+ open var x: String
+
+ constructor() {
+ x = "X" // Non-final property access
+ }
+}
+
+class ThirdDerived : Third() {
+ override var x: String = "Y"
+ set(arg) { field = "$arg$y" }
+
+ val y = ""
+}
+
+class Fourth {
+ val x: String
+ get() = y
+
+ val y = x // null!
+}
diff --git a/compiler/testData/diagnostics/tests/constructorConsistency/inspection.txt b/compiler/testData/diagnostics/tests/constructorConsistency/inspection.txt
new file mode 100644
index 00000000000..71fbb2e8b45
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/constructorConsistency/inspection.txt
@@ -0,0 +1,60 @@
+package
+
+public fun use(/*0*/ first: First): kotlin.Int
+public fun use(/*0*/ second: Second): kotlin.String
+
+public final class First {
+ public constructor First()
+ public final val x: kotlin.String
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public final fun foo(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+public final class Fourth {
+ public constructor Fourth()
+ public final val x: kotlin.String
+ public final val y: 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
+}
+
+public abstract class Second {
+ public constructor Second()
+ public final val x: kotlin.String
+ private final fun bar(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public abstract fun foo(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+public final class SecondDerived : Second {
+ public constructor SecondDerived()
+ public final override /*1*/ /*fake_override*/ val x: kotlin.String
+ public final val y: kotlin.String
+ invisible_fake final override /*1*/ /*fake_override*/ fun bar(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+ public open override /*1*/ fun foo(): kotlin.String
+ public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
+ public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
+}
+
+public open class Third {
+ public constructor Third()
+ public open var x: 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
+}
+
+public final class ThirdDerived : Third {
+ public constructor ThirdDerived()
+ public open override /*1*/ var x: kotlin.String
+ public final val y: 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
+}
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
index 670b1309e94..637983fe3c2 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("inspection.kt")
+ public void testInspection() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/inspection.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("lambdaInObject.kt")
public void testLambdaInObject() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.kt");
diff --git a/idea/resources/inspectionDescriptions/LeakingThis.html b/idea/resources/inspectionDescriptions/LeakingThis.html
new file mode 100644
index 00000000000..4d5fddb03ba
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/LeakingThis.html
@@ -0,0 +1,24 @@
+
+
+This inspection reports dangerous operations inside constructors including:
+
+ - Accessing non-final property in constructor
+ - Calling non-final function in constructor
+ - Using 'this' as function argument in constructor of non-final class
+
+These operations are dangerous because your class can be inherited,
+and derived class is not yet initialized at this moment. Typical example:
+
+abstract class Base {
+ val code = calculate()
+ abstract fun calculate(): Int
+}
+class Derived(private val x: Int) : Base() {
+ override fun calculate() = x
+}
+fun testIt() {
+ println(Derived(42).code) // Expected: 42, actual: 0
+}
+
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index f3d69badf87..3fdd12e8d0d 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1546,6 +1546,14 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt
new file mode 100644
index 00000000000..5e2c08d9fc2
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.inspections
+
+import com.intellij.codeInspection.LocalInspectionToolSession
+import com.intellij.codeInspection.ProblemHighlightType.*
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.kotlin.cfg.LeakingThisDescriptor.*
+import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.resolve.BindingContext.LEAKING_THIS
+
+class LeakingThisInspection : AbstractKotlinInspection() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitExpression(expression: KtExpression) {
+ val context = expression.analyzeFully()
+ val leakingThisDescriptor = context.get(LEAKING_THIS, expression) ?: return
+ val description = when (leakingThisDescriptor) {
+ is PropertyIsNull -> null // Not supported yet
+ is NonFinalClass ->
+ if (expression is KtThisExpression)
+ "Leaking 'this' in constructor of non-final class ${leakingThisDescriptor.klass.name}"
+ else
+ null // Not supported yet
+ is NonFinalProperty ->
+ "Accessing non-final property ${leakingThisDescriptor.property.name} in constructor"
+ is NonFinalFunction ->
+ "Calling non-final function ${leakingThisDescriptor.function.name} in constructor"
+ }
+ if (description != null) {
+ holder.registerProblem(
+ expression, description,
+ when (leakingThisDescriptor) {
+ is NonFinalProperty, is NonFinalFunction -> GENERIC_ERROR_OR_WARNING
+ else -> WEAK_WARNING
+ }
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspections/leakingThis/inspectionData/expected.xml b/idea/testData/inspections/leakingThis/inspectionData/expected.xml
new file mode 100644
index 00000000000..ce14409a7c1
--- /dev/null
+++ b/idea/testData/inspections/leakingThis/inspectionData/expected.xml
@@ -0,0 +1,26 @@
+
+
+ test.kt
+ 18
+ light_idea_test_case
+
+ Leaking 'this' in constructor
+ Leaking 'this' in constructor of non-final class Second
+
+
+ test.kt
+ 20
+ light_idea_test_case
+
+ Leaking 'this' in constructor
+ Calling non-final function foo in constructor
+
+
+ test.kt
+ 40
+ light_idea_test_case
+
+ Leaking 'this' in constructor
+ Accessing non-final property x in constructor
+
+
diff --git a/idea/testData/inspections/leakingThis/inspectionData/inspections.test b/idea/testData/inspections/leakingThis/inspectionData/inspections.test
new file mode 100644
index 00000000000..3a0a7c421cd
--- /dev/null
+++ b/idea/testData/inspections/leakingThis/inspectionData/inspections.test
@@ -0,0 +1 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.LeakingThisInspection
diff --git a/idea/testData/inspections/leakingThis/test.kt b/idea/testData/inspections/leakingThis/test.kt
new file mode 100644
index 00000000000..deb9cb25b2b
--- /dev/null
+++ b/idea/testData/inspections/leakingThis/test.kt
@@ -0,0 +1,56 @@
+class First {
+ val x: String
+
+ init {
+ use(this) // NPE! Leaking this
+ x = foo() // NPE! Own function
+ }
+
+ fun foo() = x
+}
+
+fun use(first: First) = first.x.hashCode()
+
+abstract class Second {
+ val x: String
+
+ init {
+ use(this) // Leaking this in non-final
+ x = bar() // Own function in non-final
+ foo() // Non-final function call
+ }
+
+ private fun bar() = foo()
+
+ abstract fun foo(): String
+}
+
+fun use(second: Second) = second.x
+
+class SecondDerived : Second() {
+ val y = x // null!
+
+ override fun foo() = y
+}
+
+open class Third {
+ open var x: String
+
+ constructor() {
+ x = "X" // Non-final property access
+ }
+}
+
+class ThirdDerived : Third() {
+ override var x: String = "Y"
+ set(arg) { field = "$arg$y" }
+
+ val y = ""
+}
+
+class Fourth {
+ val x: String
+ get() = y
+
+ val y = x // null!
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
index 90c74ccc155..06a94bae274 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -148,6 +148,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
doTest(fileName);
}
+ @TestMetadata("leakingThis/inspectionData/inspections.test")
+ public void testLeakingThis_inspectionData_Inspections_test() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/leakingThis/inspectionData/inspections.test");
+ doTest(fileName);
+ }
+
@TestMetadata("overridingDeprecatedMember/inspectionData/inspections.test")
public void testOverridingDeprecatedMember_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/overridingDeprecatedMember/inspectionData/inspections.test");