KT-12152 : leaking this inspection: accessing non-final member / this in non-final class

This commit is contained in:
Mikhail Glukhikh
2016-05-24 16:38:00 +03:00
parent 3d6bd81933
commit a22e7d3bcf
10 changed files with 301 additions and 0 deletions
@@ -0,0 +1,56 @@
class First {
val x: String
init {
use(<!DEBUG_INFO_LEAKING_THIS!>this<!>) // NPE! Leaking this
x = <!DEBUG_INFO_LEAKING_THIS!>foo<!>() // NPE! Own function
}
fun foo() = x
}
fun use(first: First) = first.x.hashCode()
abstract class Second {
val x: String
init {
use(<!DEBUG_INFO_LEAKING_THIS!>this<!>) // Leaking this in non-final
x = <!DEBUG_INFO_LEAKING_THIS!>bar<!>() // Own function in non-final
<!DEBUG_INFO_LEAKING_THIS!>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() {
<!DEBUG_INFO_LEAKING_THIS!>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 = <!DEBUG_INFO_LEAKING_THIS!>x<!> // null!
}
@@ -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
}
@@ -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");
@@ -0,0 +1,24 @@
<html>
<body>
This inspection reports dangerous operations inside constructors including:
<ul>
<li>Accessing non-final property in constructor</li>
<li>Calling non-final function in constructor</li>
<li>Using 'this' as function argument in constructor of non-final class</li>
</ul>
These operations are dangerous because your class can be inherited,
and derived class is not yet initialized at this moment. Typical example:
<code><pre>
<b>abstract class</b> Base {
<b>val</b> code = calculate()
<b>abstract fun</b> calculate(): Int
}
<b>class</b> Derived(<b>private val</b> x: Int) : Base() {
<b>override fun</b> calculate() = x
}
<b>fun</b> testIt() {
println(Derived(42).code) <i>// Expected: 42, actual: 0</i>
}
</pre></code>
</body>
</html>
+8
View File
@@ -1546,6 +1546,14 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.LeakingThisInspection"
displayName="Leaking 'this' in constructor"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -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
}
)
}
}
}
}
}
@@ -0,0 +1,26 @@
<problems>
<problem>
<file>test.kt</file>
<line>18</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Leaking 'this' in constructor</problem_class>
<description>Leaking 'this' in constructor of non-final class Second</description>
</problem>
<problem>
<file>test.kt</file>
<line>20</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Leaking 'this' in constructor</problem_class>
<description>Calling non-final function foo in constructor</description>
</problem>
<problem>
<file>test.kt</file>
<line>40</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Leaking 'this' in constructor</problem_class>
<description>Accessing non-final property x in constructor</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.LeakingThisInspection
+56
View File
@@ -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!
}
@@ -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");