Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Leaking 'this' in constructor" warning should apply to final classes as well as open ones?

Tags:

kotlin

In Kotlin if you have an open class which refers to this in its constructor or init block, you (quite rightly) get a compiler warning:

Leaking 'this' in constructor of non-final class

The reason for this is explained here.

My question is: why is this not reported when the class is final? If this is used in the init block before that block has completed, the object is still not in a fully constructed state, so shouldn't the warning apply there too?

This can even lead to a situation where a val property seems to change at runtime. Take this code as an example:

class Listener {
    fun onCreated(leaker: Leaker) = println("Listener hears that leaker created with a value of ${leaker.myVal}")
}

class Leaker(listener: Listener) {
    val myVal: Int

    init {
        listener.onCreated(this)
        myVal = 1
        println("Leaker knows that it's been created with a value of $myVal")
    }
}

Using these objects as follows:

Leaker(Listener())

will result in the following output:

Listener hears that leaker created with a value of 0
Leaker knows that it's been created with a value of 1

Notice that myVal is initially reported as being 0, then as being 1.

As can be seen, Leaker passes an instance of itself to Listener before Leaker has been fully constructed. Listener can then access the myVal property before it's been initialized, so it'll have the default value (0 in this case as it's an integer). Later on Listener then changes the value of this property (to 1 in this example). This means that the program behaves as if a val has changed.

Should the compiler warn you about this?

like image 419
Yoni Gibbs Avatar asked Dec 20 '18 10:12

Yoni Gibbs


1 Answers

tl;dr: https://youtrack.jetbrains.com/issue/KT-22044 is a good fit regarding this issue.

I will cite what Intellij IDEAs inspection called "Leaking 'this' in constructor" says about this:

This inspection reports dangerous operations inside constructors including:

  • Accessing a non-final property in constructor
  • Calling a non-final function in constructor
  • Using this as a function argument in a constructor of a non-final class

These operations are dangerous because your class can be inherited, and a 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
}

I think that there should be warning nonetheless as you were able to access a non-initialized variable. The reason: the compiler already disallows direct access to uninitialized variables, i.e. the following will not compile:

class Demo {
  val some : Int
  init {
    println(some) // Variable 'some' must be initialized

but accessing it indirectly compiles and shows the default value of the variable type:

class Demo2 {
  val some : Int
  val someString : String
  init {
    fun Demo2.indirectSome() = some
    fun Demo2.indirectSomeString() = someString
    println(indirectSome()) // prints 0
    println(indirectSomeString()) // prints null; and yes.. this can lead to NullPointerExceptions

and there we also have a "leak", basically accessing some before it should ;-)

like image 98
Roland Avatar answered Nov 15 '22 05:11

Roland