Serializable lambda and bytecode rewriters

Remi Forax forax at univ-mlv.fr
Sun Jul 13 12:15:38 UTC 2014


Hi guys,
there is, i think, a serious issue in the way the deserialization of 
lambda is done by javac (or ecj which uses the same scheme), exactly in 
the code of the generated method $deserializeLambda$. Bytecode rewriting 
tools like jarjar or proguard works by rewriting all reference to types 
in class files so they need to distinguish if a reference is let say a 
String of a method descriptor. The actual code of $deserializeLambda$ 
generated by javac breaks that assumption, all parameters of a 
deserialized lambda are checked as String thus are not rewritten 
correctly by the bytecode rewriters.

So currently, it's no possible to use a bytecode rewriting tools with 
the jdk8 if there is a serializable lambda somewhere in the code.
My bad on that, I should have figure out that before the release of 8.

To fix this issue, the parameters that reference a lambda inside 
$deserializeLambda$ should be typed as constant method handles and 
constant method types but given that the serializable form a lambda is a 
bunch of strings, doing the mapping between the two forms is not that 
obvious.
Moreover, there is maybe a performance problem because
- the switch on the String that represent the name of the implementation 
method can not be used because it encodes the hashCode of a name that 
should be rewritten by the static tools.
- constant method handles and constant method types are not required to 
be interned by the VM thus may be re-created each time 
$deserializeLambda$ is called if the code use ldc.

The invokedynamic evangelist in me think that $deserializeLambda$ can be 
implemented as a unique invokedynamic call taking a SerializedLambda as 
parameter with all the parameters of all the serializable lambdas 
declared in the current class as bootstrap arguments. The bootstrap 
method will create a HashMap of a all methods names,
and at runtime check if a serialized lambda is valid or not.

Something like:
static class LambdaParameters {
   ...
   public boolean verify(SerializedLambda  serializedLambda) {
     // check all parameters
   }
}

public static CallSite deserializeLambda(Lookup lookup, String name, 
MethodType type, Object[] lambdaParameters) {
   HashMap<String, LambdaParameters> map = new HashMap<>();
   for(int i=0; i < lambdaParameters.length; i+=3) {
     ...
     MethodHandle mh = (MethodHandle) lambdaParameters[i + 1];
     String name = lookup.revealDirect(mh).getName();
     ...
     map.put(name, new LambdaParameters(...));
   }
   return new ConstantCallSite(CHECK.bindTo(map));
}
private static final MethodHandle = ::check;

private static boolean check(HashMap<String, LambdaParameters> map, 
SerializedLambda serializedLambda) {
   LambdaParameters parameters = 
map.get(serializedLambda.getImplMethodName());
   return parameters != null && parameters.verify(serializedLambda);
}

A bootstrap method can not have more than 252 arguments, so you can not 
encode more than 84 (252 / 3) serializable lambdas,
but I think that current scheme because it generated a lot of bytecodes 
will hit the 65 535 bytecodes near that number too.
Anyway, if there is more than 84 serializable lambdas, 
$deserializableLambda$ can call more than one invokedynamic in chain.

As you can see, the solution seems complex but may be somebody has a 
better solution :)

regards,
Rémi






More information about the core-libs-dev mailing list