Skip to content

Commit a22e7d3

Browse files
committed
KT-12152 : leaking this inspection: accessing non-final member / this in non-final class
1 parent 3d6bd81 commit a22e7d3

File tree

10 files changed

+301
-0
lines changed

10 files changed

+301
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
class First {
2+
val x: String
3+
4+
init {
5+
use(<!DEBUG_INFO_LEAKING_THIS!>this<!>) // NPE! Leaking this
6+
x = <!DEBUG_INFO_LEAKING_THIS!>foo<!>() // NPE! Own function
7+
}
8+
9+
fun foo() = x
10+
}
11+
12+
fun use(first: First) = first.x.hashCode()
13+
14+
abstract class Second {
15+
val x: String
16+
17+
init {
18+
use(<!DEBUG_INFO_LEAKING_THIS!>this<!>) // Leaking this in non-final
19+
x = <!DEBUG_INFO_LEAKING_THIS!>bar<!>() // Own function in non-final
20+
<!DEBUG_INFO_LEAKING_THIS!>foo<!>() // Non-final function call
21+
}
22+
23+
private fun bar() = foo()
24+
25+
abstract fun foo(): String
26+
}
27+
28+
fun use(second: Second) = second.x
29+
30+
class SecondDerived : Second() {
31+
val y = x // null!
32+
33+
override fun foo() = y
34+
}
35+
36+
open class Third {
37+
open var x: String
38+
39+
constructor() {
40+
<!DEBUG_INFO_LEAKING_THIS!>x<!> = "X" // Non-final property access
41+
}
42+
}
43+
44+
class ThirdDerived : Third() {
45+
override var x: String = "Y"
46+
set(arg) { field = "$arg$y" }
47+
48+
val y = ""
49+
}
50+
51+
class Fourth {
52+
val x: String
53+
get() = y
54+
55+
val y = <!DEBUG_INFO_LEAKING_THIS!>x<!> // null!
56+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package
2+
3+
public fun use(/*0*/ first: First): kotlin.Int
4+
public fun use(/*0*/ second: Second): kotlin.String
5+
6+
public final class First {
7+
public constructor First()
8+
public final val x: kotlin.String
9+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
10+
public final fun foo(): kotlin.String
11+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
12+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
13+
}
14+
15+
public final class Fourth {
16+
public constructor Fourth()
17+
public final val x: kotlin.String
18+
public final val y: kotlin.String
19+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
20+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
21+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
22+
}
23+
24+
public abstract class Second {
25+
public constructor Second()
26+
public final val x: kotlin.String
27+
private final fun bar(): kotlin.String
28+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
29+
public abstract fun foo(): kotlin.String
30+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
31+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
32+
}
33+
34+
public final class SecondDerived : Second {
35+
public constructor SecondDerived()
36+
public final override /*1*/ /*fake_override*/ val x: kotlin.String
37+
public final val y: kotlin.String
38+
invisible_fake final override /*1*/ /*fake_override*/ fun bar(): kotlin.String
39+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
40+
public open override /*1*/ fun foo(): kotlin.String
41+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
42+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
43+
}
44+
45+
public open class Third {
46+
public constructor Third()
47+
public open var x: kotlin.String
48+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
49+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
50+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
51+
}
52+
53+
public final class ThirdDerived : Third {
54+
public constructor ThirdDerived()
55+
public open override /*1*/ var x: kotlin.String
56+
public final val y: kotlin.String = ""
57+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
58+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
59+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
60+
}

compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2973,6 +2973,12 @@ public void testInitwithgetter() throws Exception {
29732973
doTest(fileName);
29742974
}
29752975

2976+
@TestMetadata("inspection.kt")
2977+
public void testInspection() throws Exception {
2978+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/inspection.kt");
2979+
doTest(fileName);
2980+
}
2981+
29762982
@TestMetadata("lambdaInObject.kt")
29772983
public void testLambdaInObject() throws Exception {
29782984
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/constructorConsistency/lambdaInObject.kt");
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<html>
2+
<body>
3+
This inspection reports dangerous operations inside constructors including:
4+
<ul>
5+
<li>Accessing non-final property in constructor</li>
6+
<li>Calling non-final function in constructor</li>
7+
<li>Using 'this' as function argument in constructor of non-final class</li>
8+
</ul>
9+
These operations are dangerous because your class can be inherited,
10+
and derived class is not yet initialized at this moment. Typical example:
11+
<code><pre>
12+
<b>abstract class</b> Base {
13+
<b>val</b> code = calculate()
14+
<b>abstract fun</b> calculate(): Int
15+
}
16+
<b>class</b> Derived(<b>private val</b> x: Int) : Base() {
17+
<b>override fun</b> calculate() = x
18+
}
19+
<b>fun</b> testIt() {
20+
println(Derived(42).code) <i>// Expected: 42, actual: 0</i>
21+
}
22+
</pre></code>
23+
</body>
24+
</html>

idea/src/META-INF/plugin.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,14 @@
15461546
language="kotlin"
15471547
/>
15481548

1549+
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.LeakingThisInspection"
1550+
displayName="Leaking 'this' in constructor"
1551+
groupName="Kotlin"
1552+
enabledByDefault="true"
1553+
level="WARNING"
1554+
language="kotlin"
1555+
/>
1556+
15491557
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
15501558

15511559
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2010-2016 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.inspections
18+
19+
import com.intellij.codeInspection.LocalInspectionToolSession
20+
import com.intellij.codeInspection.ProblemHighlightType.*
21+
import com.intellij.codeInspection.ProblemsHolder
22+
import com.intellij.psi.PsiElementVisitor
23+
import org.jetbrains.kotlin.cfg.LeakingThisDescriptor.*
24+
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
25+
import org.jetbrains.kotlin.psi.*
26+
import org.jetbrains.kotlin.resolve.BindingContext.LEAKING_THIS
27+
28+
class LeakingThisInspection : AbstractKotlinInspection() {
29+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
30+
return object : KtVisitorVoid() {
31+
override fun visitExpression(expression: KtExpression) {
32+
val context = expression.analyzeFully()
33+
val leakingThisDescriptor = context.get(LEAKING_THIS, expression) ?: return
34+
val description = when (leakingThisDescriptor) {
35+
is PropertyIsNull -> null // Not supported yet
36+
is NonFinalClass ->
37+
if (expression is KtThisExpression)
38+
"Leaking 'this' in constructor of non-final class ${leakingThisDescriptor.klass.name}"
39+
else
40+
null // Not supported yet
41+
is NonFinalProperty ->
42+
"Accessing non-final property ${leakingThisDescriptor.property.name} in constructor"
43+
is NonFinalFunction ->
44+
"Calling non-final function ${leakingThisDescriptor.function.name} in constructor"
45+
}
46+
if (description != null) {
47+
holder.registerProblem(
48+
expression, description,
49+
when (leakingThisDescriptor) {
50+
is NonFinalProperty, is NonFinalFunction -> GENERIC_ERROR_OR_WARNING
51+
else -> WEAK_WARNING
52+
}
53+
)
54+
}
55+
}
56+
}
57+
}
58+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<problems>
2+
<problem>
3+
<file>test.kt</file>
4+
<line>18</line>
5+
<module>light_idea_test_case</module>
6+
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
7+
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Leaking 'this' in constructor</problem_class>
8+
<description>Leaking 'this' in constructor of non-final class Second</description>
9+
</problem>
10+
<problem>
11+
<file>test.kt</file>
12+
<line>20</line>
13+
<module>light_idea_test_case</module>
14+
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
15+
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Leaking 'this' in constructor</problem_class>
16+
<description>Calling non-final function foo in constructor</description>
17+
</problem>
18+
<problem>
19+
<file>test.kt</file>
20+
<line>40</line>
21+
<module>light_idea_test_case</module>
22+
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
23+
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Leaking 'this' in constructor</problem_class>
24+
<description>Accessing non-final property x in constructor</description>
25+
</problem>
26+
</problems>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.LeakingThisInspection
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
class First {
2+
val x: String
3+
4+
init {
5+
use(this) // NPE! Leaking this
6+
x = foo() // NPE! Own function
7+
}
8+
9+
fun foo() = x
10+
}
11+
12+
fun use(first: First) = first.x.hashCode()
13+
14+
abstract class Second {
15+
val x: String
16+
17+
init {
18+
use(this) // Leaking this in non-final
19+
x = bar() // Own function in non-final
20+
foo() // Non-final function call
21+
}
22+
23+
private fun bar() = foo()
24+
25+
abstract fun foo(): String
26+
}
27+
28+
fun use(second: Second) = second.x
29+
30+
class SecondDerived : Second() {
31+
val y = x // null!
32+
33+
override fun foo() = y
34+
}
35+
36+
open class Third {
37+
open var x: String
38+
39+
constructor() {
40+
x = "X" // Non-final property access
41+
}
42+
}
43+
44+
class ThirdDerived : Third() {
45+
override var x: String = "Y"
46+
set(arg) { field = "$arg$y" }
47+
48+
val y = ""
49+
}
50+
51+
class Fourth {
52+
val x: String
53+
get() = y
54+
55+
val y = x // null!
56+
}

idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ public void testEqualsAndHashCode_inspectionData_Inspections_test() throws Excep
148148
doTest(fileName);
149149
}
150150

151+
@TestMetadata("leakingThis/inspectionData/inspections.test")
152+
public void testLeakingThis_inspectionData_Inspections_test() throws Exception {
153+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/leakingThis/inspectionData/inspections.test");
154+
doTest(fileName);
155+
}
156+
151157
@TestMetadata("overridingDeprecatedMember/inspectionData/inspections.test")
152158
public void testOverridingDeprecatedMember_inspectionData_Inspections_test() throws Exception {
153159
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/overridingDeprecatedMember/inspectionData/inspections.test");

0 commit comments

Comments
 (0)