Description
reproduction steps
using Scala 2.13.6
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
import java.lang.invoke.{MethodHandleInfo, SerializedLambda}
import scala.tools.nsc.util
trait F1[-A, +B] {
def apply(a: A): B
}
trait StringF1[+B] extends F1[String, B] with java.io.Serializable {
def apply(s: String): B
}
class C extends java.io.Serializable {
val sf1s = List[StringF1[String]](
(s) => s.reverse,
(s) => s.reverse,
)
@annotation.unused private def foo(): Unit = {
assert(false, "should not be called!!!")
}
}
object Test {
def main(args: Array[String]): Unit = {
allRealLambdasRoundTrip()
}
def allRealLambdasRoundTrip(): Unit = {
new C().sf1s.map(x => {
val y = serializeDeserialize(x)
y.apply("foo");
def applyF1[A, B](f: F1[A, B])(a: A): B = f(a)
applyF1(y)("foo")
})
}
def serializeDeserialize[T <: AnyRef](obj: T) = {
val buffer = new ByteArrayOutputStream
val out = new ObjectOutputStream(buffer)
out.writeObject(obj)
val in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray))
in.readObject.asInstanceOf[T]
}
}
problem
Exception in thread "main" java.lang.AbstractMethodError: Receiver class C$$Lambda$569/0x0000000800346c40 does not define or inherit an implementation of the resolved method 'abstract java.lang.Object apply(java.lang.Object)' of interface F1.
at Test$.applyF1$1(lambda-serialization.scala:53)
at Test$.$anonfun$allRealLambdasRoundTrip$2(lambda-serialization.scala:54)
at scala.collection.immutable.List.map(List.scala:246)
notes
To suppose lambda deserialiation, we add a synthetic $deserializeLambda$
method to each class that hosts lambda implementation methods. The JDK routes deserialization through this method. It checks that the request actually targets a method that backs a lambda (and not some random, maybe private method), by checking in a statically contructed map of all impl methods. If so, the lambda metatafactory is invoked. The metafactory invokation lives, generically, in scala.runtime.LambdaDeserializer
. It does not correctly replicate the invokedynamic
call at the lambda capture site as it does not include bridges.
LambdaDeserializer
itself is a workaround for a problem we hit when we directly replicated the Java's strategy of simply having an invoke-dynamic-call per-lamnbda in the $deserializeLambda$
. Because Scala lambdas are serializable by default, and lambda use is widespread, this method got very large, especially in DSL-like code. The large method was a problem in that it could be get too large to JIT or even too large to fit in a single method.
Maybe we can switch to a hybrid strategy where bridged lambdas have direct invokedynamic calls and scala.FunctionN
cojntinue to use LambdaDeserializer
. This will be cross-compatible with newly compiled user-code and the exising versions of scala.runtime.