[8] Review request for 8004970, 8004971, and 8006817: implement serialization in lambda metafactory and metafactory fix

Robert Field robert.field at oracle.com
Fri Feb 22 19:05:30 PST 2013


Thanks much Peter, we are investigating this issue and your suggestion.

-Robert

On 02/22/13 04:27, Peter Levart wrote:
> Hi Robert,
>
> What I was trying to say in my previous post is illustrated in the 
> following minimal test-case:
>
>
> package test;
>
> import sun.misc.IOUtils;
>
> import java.io.ByteArrayInputStream;
> import java.io.ByteArrayOutputStream;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.ObjectInputStream;
> import java.io.ObjectOutputStream;
> import java.io.Serializable;
>
> public class SerializedLambdaTest {
>
>     public interface SerializableRunnable extends Runnable, 
> Serializable {}
>
>     public static class MyCode implements SerializableRunnable {
>
>         private byte[] serialize(Object o) {
>             ByteArrayOutputStream baos;
>             try (
>                 ObjectOutputStream oos =
>                     new ObjectOutputStream(baos = new 
> ByteArrayOutputStream())
>             ) {
>                 oos.writeObject(o);
>             }
>             catch (IOException e) {
>                 throw new RuntimeException(e);
>             }
>             return baos.toByteArray();
>         }
>
>         private <T> T deserialize(byte[] bytes) {
>             try (
>                 ObjectInputStream ois =
>                     new ObjectInputStream(new ByteArrayInputStream(bytes))
>             ) {
>                 return (T) ois.readObject();
>             }
>             catch (IOException | ClassNotFoundException e) {
>                 throw new RuntimeException(e);
>             }
>         }
>
>         @Override
>         public void run() {
>             System.out.println("                this: " + this);
>
>             SerializableRunnable deSerializedThis = 
> deserialize(serialize(this));
>             System.out.println("    deSerializedThis: " + 
> deSerializedThis);
>
>             SerializableRunnable runnable = () -> 
> {System.out.println("HELLO");};
>             System.out.println("            runnable: " + runnable);
>
>             SerializableRunnable deSerializedRunnable = 
> deserialize(serialize(runnable));
>             System.out.println("deSerializedRunnable: " + 
> deSerializedRunnable);
>         }
>     }
>
>     public static void main(String[] args) throws Exception {
>         ClassLoader myCl = new MyClassLoader(
>             SerializedLambdaTest.class.getClassLoader()
>         );
>         Class<?> myCodeClass = Class.forName(
>             SerializedLambdaTest.class.getName() + "$MyCode",
>             true,
>             myCl
>         );
>         Runnable myCode = (Runnable) myCodeClass.newInstance();
>         myCode.run();
>     }
>
>     static class MyClassLoader extends ClassLoader {
>         MyClassLoader(ClassLoader parent) {
>             super(parent);
>         }
>
>         @Override
>         protected Class<?> loadClass(String name, boolean resolve) 
> throws ClassNotFoundException {
>             if (name.startsWith("test.")) {
>                 synchronized (getClassLoadingLock(name)) {
>                     Class<?> c = findLoadedClass(name);
>                     if (c == null) {
>                         c = findClass(name);
>                     }
>                     if (resolve) {
>                         resolveClass(c);
>                     }
>                     return c;
>                 }
>             } else {
>                 return super.loadClass(name, resolve);
>             }
>         }
>
>         @Override
>         protected Class<?> findClass(String name) throws 
> ClassNotFoundException {
>             String path = name.replace('.', '/').concat(".class");
>             try (InputStream is = getResourceAsStream(path)) {
>                 if (is != null) {
>                     byte[] bytes = IOUtils.readFully(is, -1, true);
>                     return defineClass(name, bytes, 0, bytes.length);
>                 } else {
>                     throw new ClassNotFoundException(name);
>                 }
>             }
>             catch (IOException e) {
>                 throw new ClassNotFoundException(name, e);
>             }
>         }
>     }
> }
>
>
> ... which produces the following output:
>
>
>                 this: test.SerializedLambdaTest$MyCode at 5e481248
>     deSerializedThis: test.SerializedLambdaTest$MyCode at d716361
>             runnable: test.SerializedLambdaTest$MyCode$$Lambda$1 at eed1f14
> Exception in thread "main" java.lang.ClassCastException: 
> test.SerializedLambdaTest$MyCode$$Lambda$2 cannot be cast to 
> test.SerializedLambdaTest$SerializableRunnable
>
>
> If capturing class' Class object is not available at SAM proxy 
> serialization time (but only it's name), it can be resolved at that 
> time rather than at de-serialization time, when the caller 
> class-loader is not obvious. Like for example in this patch:
>
>
> Index: jdk/src/java/lang/invoke/SerializedLambda.java
> ===================================================================
> --- jdk/src/java/lang/invoke/SerializedLambda.java (date 1361524994000)
> +++ jdk/src/java/lang/invoke/SerializedLambda.java (revision )
> @@ -24,6 +24,8 @@
>   */
>  package java.lang.invoke;
>
> +import sun.reflect.Reflection;
> +
>  import java.io.Serializable;
>  import java.lang.reflect.Method;
>  import java.security.AccessController;
> @@ -39,7 +41,7 @@
>   * @see LambdaMetafactory
>   */
>  public final class SerializedLambda implements Serializable {
> -    private final String capturingClass;
> +    private final Class<?> capturingClass;
>      private final String functionalInterfaceClass;
>      private final String functionalInterfaceMethodName;
>      private final String functionalInterfaceMethodSignature;
> @@ -73,7 +75,7 @@
>                              Object[] capturedArgs) throws 
> ReflectiveOperationException {
>          MethodHandleInfo samMhi = new 
> MethodHandleInfo(Objects.requireNonNull(functionalInterface));
>          MethodHandleInfo implMhi = new 
> MethodHandleInfo(Objects.requireNonNull(implementation));
> -        this.capturingClass = 
> Objects.requireNonNull(capturingClass).getName();
> +        this.capturingClass = Objects.requireNonNull(capturingClass);
>          this.capturedArgs = Objects.requireNonNull(capturedArgs).clone();
>          this.functionalInterfaceClass = 
> samMhi.getDeclaringClass().getName();
>          this.functionalInterfaceMethodName = samMhi.getName();
> @@ -118,7 +120,17 @@
>                              String implMethodSignature,
>                              String instantiatedMethodType,
>                              Object[] capturedArgs) {
> -        this.capturingClass = capturingClass;
> +        try {
> +            this.capturingClass = Class.forName(
> +                capturingClass.replace('/', '.'),
> +                false,
> + Reflection.getCallerClass(2).getClassLoader()
> +            );
> +        }
> +        catch (ClassNotFoundException e) {
> +            throw (Error) new NoClassDefFoundError(e.getMessage())
> +                .initCause(e);
> +        }
>          this.functionalInterfaceMethodKind = 
> functionalInterfaceMethodKind;
>          this.functionalInterfaceClass = functionalInterfaceClass;
>          this.functionalInterfaceMethodName = 
> functionalInterfaceMethodName;
> @@ -133,7 +145,7 @@
>
>      /** Get the name of the class that captured this lambda */
>      public String getCapturingClass() {
> -        return capturingClass;
> +        return capturingClass.getName().replace('.', '/');
>      }
>
>      /** Get the name of the functional interface class to which this 
> lambda has been converted */
> @@ -200,9 +212,7 @@
>              Method deserialize = AccessController.doPrivileged(new 
> PrivilegedExceptionAction<Method>() {
>                  @Override
>                  public Method run() throws Exception {
> -                    Class<?> clazz = 
> Class.forName(capturingClass.replace('/', '.'), true,
> - Thread.currentThread().getContextClassLoader());
> -                    Method m = 
> clazz.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class);
> +                    Method m = 
> capturingClass.getDeclaredMethod("$deserializeLambda$", 
> SerializedLambda.class);
>                      m.setAccessible(true);
>                      return m;
>                  }
>
>
>
> Regards, Peter
>
>
> On 02/12/2013 08:10 PM, Peter Levart wrote:
>> Hi Robert,
>>
>> Just a minor note on ClassLoaders.
>>
>> In SerializedLambda.readResolve(), the capturingClass is resolved 
>> from it's name using the current thread's context ClassLoader. I 
>> don't know if this is the right thing to do. ObjectInputStream has 
>> it's own (pluggable) mechanism for resolving classes, which by 
>> default uses the so called "latest user defined loader" (see 
>> ObjectInputStream.resolveClass()). It would be better if 
>> SerializedLambda kept a reference to j.l.Class object representing 
>> capturingClass and leave to the serialization infrastructure do the 
>> resolving. But there is a SerializedLambda constructor that only 
>> takes a String, hm...
>>
>> I guess other classes that are resolved inside the capturingClass'  
>> $deserializeLambda$ method are using the caprutingClass' class 
>> loader, which is ok.
>>
>> Regards, Peter
>>
>> On 02/12/2013 04:23 PM, Robert Field wrote:
>>> Please review the fixes for CRs:
>>>
>>>         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8004970
>>>
>>>         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8004971
>>>
>>>         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8006817
>>>
>>>
>>>
>>> Webrev:
>>>
>>>         http://cr.openjdk.java.net/~rfield/8004970
>>>
>>> Thank,
>>> Robert
>>>
>>>
>>
>



More information about the lambda-dev mailing list