JDK-8208752 : Calling a deserialized Lambda might fail with ClassCastException
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 8,10.0.2,11,12
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2018-08-03
  • Updated: 2024-01-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
A DESCRIPTION OF THE PROBLEM :
Deserializing a method reference might use the wrong signature if there are more than one method reference to the same method in the same class.

This is probably a side effect of JDK-8202922.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Use two (or more) serializable method references in the same class, where signature of the first method reference is more restrictive than the second.

Serialize & deserialize the second method reference.
Call the deserialized method reference with something that is not legal for the first method reference.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The deserialized method reference works as advertised.
ACTUAL -
A ClassCastException is thrown

Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.Object cannot be cast to java.base/java.lang.String
        at SerialLambdaBug.main(SerialLambdaBug.java:19)

---------- BEGIN SOURCE ----------
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.function.Function;


public class SerialLambdaBug {
	
	public static void main(String[]  args) throws Exception {
		
		// If you comment out the next line, this test will not throw an exception.
		Function<String,String> lambda1 = (Function<String,String> & Serializable) Object::toString;
		Function<Object,String> lambda2 = (Function<Object,String> & Serializable) Object::toString;
		
		Function<Object,String> deserial = serialDeserial(lambda2);
		// This throws a class clast exception
		deserial.apply(new Object());
	}
	
	@SuppressWarnings("unchecked")
	static <T> T serialDeserial(T object) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(object);
		oos.close();
		ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
		ObjectInputStream ois = new ObjectInputStream(bais);
		T result = (T) ois.readObject();
		ois.close();
		return result;
	}
	
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Don't use method references, use a lambda expression instead

FREQUENCY : always



Comments
I think the discussion here is related: https://bugs.openjdk.org/browse/JDK-8154236?focusedId=14024713&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14024713 I have an updated version of the PR that fixes this case and passes tier1, I'm curious if anyone has input on whether that's obviously the wrong approach: https://github.com/openjdk/jdk/pull/17233
03-01-2024

I investigated this a bit and found that the generated code for deserialization doesn't check SerializedLambda#getInstantiatedMethodType, which leads to lambdas that differ only in their getInstantiatedMethodType being merged together, and results in the CCE in this bug. I have a draft of a fix in https://github.com/openjdk/jdk/pull/17233 that adds a test for getInstantiatedMethodType. That fixes this bug, but regresses tools/javac/lambda/SerializableObjectMethods.java and tools/javac/lambda/LambdaLambdaSerialized.java For the SerializableObjectMethods test added for https://bugs.openjdk.org/browse/JDK-8282080, it looks like it's exposing another bug: the getImplClass for the serialized lambda is java.lang.Object, but generated deserialization code expects getImplClass for that instance to be 'I2' in that test. Currently it's getting merged into the lambda for I2, and with this fix it starts failing. So I think the fix in the PR might be on the right track, but clearly this needs more work to avoid regressing https://bugs.openjdk.org/browse/JDK-8282080.
03-01-2024

Yes, the "merging" of distinct instances after deserialization observed in JDK-8202922 is counterintuitive but allowable and is not incorrect. However in this case it appears that deserialization "merges" instances when it's unsafe to do so, as the test case shows.
14-08-2018

JDK-8202922 only changed the specification about the identity of serialized method references or lambdas. However, the observed behaviour discussed in JDK-8202922 might be a symptom of a bug such as this one.
03-08-2018

This issue is reproducible in all the versions, starting from 8, 9, 10, 11 and 12 8uxx - Fail 9 GA - Fail 10.0.2 - Fail 11 ea b23 - Fail 12 ea b04 - Fail Below is the output executed in 12 ea build 04 -sh-4.2$ /scratch/fairoz/JAVA/jdk12/jdk-12-ea+04/bin/javac SerialLambdaBug.java -sh-4.2$ /scratch/fairoz/JAVA/jdk12/jdk-12-ea+04/bin/java SerialLambdaBug Exception in thread "main" java.lang.ClassCastException: class java.lang.Object cannot be cast to class java.lang.String (java.lang.Object and java.lang.String are in module java.base of loader 'bootstrap') at SerialLambdaBug.main(SerialLambdaBug.java:19)
03-08-2018