Does anyone know why the generated `Continuation` ...
# coroutines
m
Does anyone know why the generated
Continuation
parameter of a
suspend
function would ever not be the last parameter of the function? The documentation doesn’t say explicitly it has to be the last parameter but some articles I’ve read have said that it always is. We seem to have found a case where it isn’t the last parameter in our own codebase, but we’re not sure why that would happen.
c
its an additional parameter, at the end unless something else has rewritten the bytecode (which would be unusual and likely to break things). What are the specifics of your case?
m
We created a proxy class that affects
suspend
functions only, and have been applying it on a bunch of interfaces. However on a specific function on a specific interface the proxy wasn’t working, but only on release builds. It appears that ProGuard may be rearranging the order of the parameters perhaps, rather than the compiler? Here’s the basic proxy
Copy code
private class MyProxy<Interface>(
    private val interface: Interface,
) : InvocationHandler {
    override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
        val nonNullArgs = args.orEmpty()
        val lastArgument = nonNullArgs.lastOrNull()

        // If the method is not suspending just invoke it as normal.
        if (lastArgument !is Continuation<*>) {
            return method.invoke(interface, *nonNullArgs)
        }

        // Do proxy things on suspend function here
    }
}
c
A few things are suspect there: 1. The check for “is suspending function” - seems not so precise to rely on checking the last parameter. Don’t have a reference handy - there should be something on the Method that says its a suspending function; 2. The invocation of suspending functions is special (perhaps that is covered in the last section of code already) Perhaps ProGuard does rewrite that bytecode; have you isolated specific methods that are affected and looked at their bytecode before/after ProGuard?
m
Unfortunately the
InvocationHandler
class is Java and is not Kotlin-aware, so we can’t directly check for suspending functions. I’ve just found this on the web which we could make use of instead https://github.com/shaun-wild/KProxy Good idea to check the bytecode, that’ll be my next step
👍 1
Yep we found out it was ProGuard. When it minifies the function parameters, it places objects first, followed by primitives. This reordering occurs even if the original function parameter order was object, primitive, object. To illustrate, our problematic function has the following signature
Copy code
suspend fun myFunc(
        myString: String,
        myBool: Boolean,
    ): MyReturnType
When decompiled, a
Continuation
object is added at the end.
Copy code
public Object myFunc(@NotNull String myString, @Nullable boolean myBool, @NotNull Continuation var3)
However, once minified, the method parameters’ order changes to
myString
,
Continuation
,
myBool
. We confirmed this behaviour by making
myString
nullable (
myString: Boolean?
). Doing so forced the Kotlin compiler to decompile it into a Java
Boolean
object, thereby maintaining the correct order of parameters.
Copy code
public Object myFunc(@NotNull String myString, @Nullable Boolean myBool, @NotNull Continuation var3)
This happens in the optimisation step of ProGuard, so we’re adding a rule to allow the other steps but stop the optimisitation
Copy code
-keep,allowobfuscation, allowshrinking class * implements com.example.MyProxy  { *; }
👍 2