I am having an issue related to object declaration...
# getting-started
x
I am having an issue related to object declaration (something like
object MyObject{ }
). In my app, there's a snippet that looks like this
Copy code
fun myFunction():Any{
    try{
        // do things
    }catch(e:Throwable){
        return MyObject.sideEffect()
    }
}

object MyObject{
    fun sideEffect():Any{
        //....
    }
}
the code inside the try block will throw an StackOverflowError under certain condition. The StackOverflowError is caught, and a side effect is emitted. This side effect is produced by an object declared in a different file, and half of the time, it will throw this exception; while on the other half, it runs just fine.
Copy code
Could not initialize class com.example.MyObject
java.lang.NoClassDefFoundError: Could not initialize class com.example.MyObject
However, if I run this
val mo = MyObject
before calling
myFunction()
, the
NoClassDefFoundError
exception is never thrown. Maybe this has something to do with the fact that MyObject is lazily allocated according to the official document. Could someone point me to a more in-depth document on object declaration? I need to know what is going on badly 🫠 Thank you.
e
not really a Kotlin thing except in how it compiles to Java… this is just a thing with class initialization on the JVM
🙏 1
classes are initialized when they are first used, which invokes the classloader. if an exception is thrown while the class is being initialized, it won't be retried
🙏 1
r
Also, why are you catching stack overflow exceptions? That doesn't seem like a bright idea... With me making an assumption that the exception comes from the fact that
myFunction
is actually recursive. You then catch it at lowest levels (when calling
myFunction()
would throw SO) and ignore the fact that your stack is most likely near-full and you call an another function. Which will just likely throw a SO again... Example:
Copy code
fun myFunction(depth: Int) {
    try {
        myFunction(depth + 1)
    } catch (e: Throwable) {
        println("$depth: $e")
        MyObject.sideEffect()
    }
}

object MyObject {
    fun sideEffect() {
        println("side effect")
    }
}

fun main() {
    myFunction(0)
}
Output:
Copy code
9856: java.lang.StackOverflowError
9855: java.lang.StackOverflowError
9854: java.lang.StackOverflowError
9853: java.lang.StackOverflowError
9852: java.lang.StackOverflowError
9851: java.lang.StackOverflowError
9850: java.lang.StackOverflowError
9849: java.lang.StackOverflowError
9848: java.lang.StackOverflowError
9847: java.lang.StackOverflowError
9846: java.lang.StackOverflowError
9845: java.lang.StackOverflowError
9844: java.lang.StackOverflowError
9843: java.lang.StackOverflowError
9842: java.lang.StackOverflowError
9841: java.lang.StackOverflowError
9840: java.lang.StackOverflowError
9839: java.lang.StackOverflowError
side effect
🙏 1
1
e
likely that recursion is involved, but regardless of how they get there: if the first time the class is referenced is with a stack so deep that it cannot successfully complete initialization, it is completely expected that all future references to the class will throw NoClassDefFoundError regardless of whether the StackOverflowError was handled or not (just expanding what I said earlier)
🙏 1
2
I completely agree that you should not be handling StackOverflowError. that's what gets you into this mess in the first place. rewrite your code so that it doesn't happen.
2
🙏 1
j
Rule of thumb: do not catch
Throwable
unless you really know what you are doing and have carefully considered and decided that it makes sense to catch an
OutOfMemoryError
,
NoClassDefFoundError
,
StackOverflowError
and the likes. Most likely it doesn't. In this specific case, it seems you decided to catch
StackOverflowError
on purpose, and it's unclear why. If you ran into a stack overflow error, there is likely a bug in a recursion that should be fixed instead of caught.
🙏 1
x
@Roukanken @Joffrey @ephemient my app allows users to input their own logic using a rudimentary language, so there are cases in which the users make faulty recursion calls and the app has to handled such error cases. So, the catching of StackOverflowError in my case is unfortunately necessary 🤕
e
you could add your own stack depth limit to prevent blowing out the real stack, or you could use CPS to transform recursion into iteration with a reified call stack that lives on the (comparatively) unbounded heap. Kotlin even comes with a super-handy helper which uses the fact that suspend funs are already to the right form and can be dispatched in a constant amount of real stack space, so you don't have to do the transformation by hand. https://pl.kotl.in/HeRfzBxF4
Copy code
sealed class Expr {
    data class Literal(val value: Int) : Expr()
    data class Add(val a: Expr, val b: Expr) : Expr()
}

fun eval(expr: Expr): Int = when (expr) {
    is Expr.Literal -> expr.value
    is Expr.Add -> eval(expr.a) + eval(expr.b)
}

val evalDeep = DeepRecursiveFunction<Expr, Int> { expr ->
    when (expr) {
        is Expr.Literal -> expr.value
        is Expr.Add -> callRecursive(expr.a) + callRecursive(expr.b)
    }
}

fun main() {
    val expr = (1..10_000).fold(Expr.Literal(0)) { acc: Expr, value -> Expr.Add(acc, Expr.Literal(value)) }
    println(runCatching { eval(expr) }) // => Failure(java.lang.StackOverflowError)
    println(runCatching { evalDeep(expr) }) // => Success(50005000)
}
x
@ephemient thank you, this is safer than using the jvm stack 😄