changeset 30911:99e027461f4a

8071474: Better failure atomicity for default read object Reviewed-by: plevart, coffeys
author chegar
date Wed, 03 Jun 2015 15:30:44 +0100
parents 1b6a91197762
children 5c720b6875a2
files jdk/src/java.base/share/classes/java/io/ObjectInputStream.java jdk/src/java.base/share/classes/java/io/ObjectStreamClass.java jdk/test/java/io/Serializable/failureAtomicity/Bar.template jdk/test/java/io/Serializable/failureAtomicity/FailureAtomicity.java jdk/test/java/io/Serializable/failureAtomicity/Foo.template jdk/test/java/io/Serializable/failureAtomicity/SerialRef.java
diffstat 6 files changed, 702 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/jdk/src/java.base/share/classes/java/io/ObjectInputStream.java	Wed Jun 03 12:37:52 2015 +0200
+++ b/jdk/src/java.base/share/classes/java/io/ObjectInputStream.java	Wed Jun 03 15:30:44 2015 +0100
@@ -253,9 +253,6 @@
     /** flag set when at end of field value block with no TC_ENDBLOCKDATA */
     private boolean defaultDataEnd = false;
 
-    /** buffer for reading primitive field values */
-    private byte[] primVals;
-
     /** if true, invoke readObjectOverride() instead of readObject() */
     private final boolean enableOverride;
     /** if true, invoke resolveObject() */
@@ -500,7 +497,11 @@
         Object curObj = ctx.getObj();
         ObjectStreamClass curDesc = ctx.getDesc();
         bin.setBlockDataMode(false);
-        defaultReadFields(curObj, curDesc);
+        FieldValues vals = defaultReadFields(curObj, curDesc);
+        if (curObj != null) {
+            defaultCheckFieldValues(curObj, curDesc, vals);
+            defaultSetFieldValues(curObj, curDesc, vals);
+        }
         bin.setBlockDataMode(true);
         if (!curDesc.hasWriteObjectData()) {
             /*
@@ -1881,6 +1882,26 @@
         throws IOException
     {
         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
+        // Best effort Failure Atomicity; slotValues will be non-null if field
+        // values can be set after reading all field data in the hierarchy.
+        // Field values can only be set after reading all data if there are no
+        // user observable methods in the hierarchy, readObject(NoData). The
+        // top most Serializable class in the hierarchy can be skipped.
+        FieldValues[] slotValues = null;
+
+        boolean hasSpecialReadMethod = false;
+        for (int i = 1; i < slots.length; i++) {
+            ObjectStreamClass slotDesc = slots[i].desc;
+            if (slotDesc.hasReadObjectMethod()
+                  || slotDesc.hasReadObjectNoDataMethod()) {
+                hasSpecialReadMethod = true;
+                break;
+            }
+        }
+        // No special read methods, can store values and defer setting.
+        if (!hasSpecialReadMethod)
+            slotValues = new FieldValues[slots.length];
+
         for (int i = 0; i < slots.length; i++) {
             ObjectStreamClass slotDesc = slots[i].desc;
 
@@ -1917,7 +1938,13 @@
                      */
                     defaultDataEnd = false;
                 } else {
-                    defaultReadFields(obj, slotDesc);
+                    FieldValues vals = defaultReadFields(obj, slotDesc);
+                    if (slotValues != null) {
+                        slotValues[i] = vals;
+                    } else if (obj != null) {
+                        defaultCheckFieldValues(obj, slotDesc, vals);
+                        defaultSetFieldValues(obj, slotDesc, vals);
+                    }
                 }
                 if (slotDesc.hasWriteObjectData()) {
                     skipCustomData();
@@ -1933,6 +1960,19 @@
                 }
             }
         }
+
+        if (obj != null && slotValues != null) {
+            // Check that the non-primitive types are assignable for all slots
+            // before assigning.
+            for (int i = 0; i < slots.length; i++) {
+                if (slotValues[i] != null)
+                    defaultCheckFieldValues(obj, slots[i].desc, slotValues[i]);
+            }
+            for (int i = 0; i < slots.length; i++) {
+                if (slotValues[i] != null)
+                    defaultSetFieldValues(obj, slots[i].desc, slotValues[i]);
+            }
+        }
     }
 
     /**
@@ -1964,12 +2004,22 @@
         }
     }
 
+    private class FieldValues {
+        final byte[] primValues;
+        final Object[] objValues;
+
+        FieldValues(byte[] primValues, Object[] objValues) {
+            this.primValues = primValues;
+            this.objValues = objValues;
+        }
+    }
+
     /**
      * Reads in values of serializable fields declared by given class
-     * descriptor.  If obj is non-null, sets field values in obj.  Expects that
-     * passHandle is set to obj's handle before this method is called.
+     * descriptor. Expects that passHandle is set to obj's handle before this
+     * method is called.
      */
-    private void defaultReadFields(Object obj, ObjectStreamClass desc)
+    private FieldValues defaultReadFields(Object obj, ObjectStreamClass desc)
         throws IOException
     {
         Class<?> cl = desc.forClass();
@@ -1977,22 +2027,19 @@
             throw new ClassCastException();
         }
 
+        byte[] primVals = null;
         int primDataSize = desc.getPrimDataSize();
         if (primDataSize > 0) {
-            if (primVals == null || primVals.length < primDataSize) {
-                primVals = new byte[primDataSize];
-            }
+            primVals = new byte[primDataSize];
             bin.readFully(primVals, 0, primDataSize, false);
-            if (obj != null) {
-                desc.setPrimFieldValues(obj, primVals);
-            }
         }
 
+        Object[] objVals = null;
         int numObjFields = desc.getNumObjFields();
         if (numObjFields > 0) {
             int objHandle = passHandle;
             ObjectStreamField[] fields = desc.getFields(false);
-            Object[] objVals = new Object[numObjFields];
+            objVals = new Object[numObjFields];
             int numPrimFields = fields.length - objVals.length;
             for (int i = 0; i < objVals.length; i++) {
                 ObjectStreamField f = fields[numPrimFields + i];
@@ -2001,11 +2048,30 @@
                     handles.markDependency(objHandle, passHandle);
                 }
             }
-            if (obj != null) {
-                desc.setObjFieldValues(obj, objVals);
-            }
             passHandle = objHandle;
         }
+
+        return new FieldValues(primVals, objVals);
+    }
+
+    /** Throws ClassCastException if any value is not assignable. */
+    private void defaultCheckFieldValues(Object obj, ObjectStreamClass desc,
+                                         FieldValues values) {
+        Object[] objectValues = values.objValues;
+        if (objectValues != null)
+            desc.checkObjFieldValueTypes(obj, objectValues);
+    }
+
+    /** Sets field values in obj. */
+    private void defaultSetFieldValues(Object obj, ObjectStreamClass desc,
+                                       FieldValues values) {
+        byte[] primValues = values.primValues;
+        Object[] objectValues = values.objValues;
+
+        if (primValues != null)
+            desc.setPrimFieldValues(obj, primValues);
+        if (objectValues != null)
+            desc.setObjFieldValues(obj, objectValues);
     }
 
     /**
--- a/jdk/src/java.base/share/classes/java/io/ObjectStreamClass.java	Wed Jun 03 12:37:52 2015 +0200
+++ b/jdk/src/java.base/share/classes/java/io/ObjectStreamClass.java	Wed Jun 03 15:30:44 2015 +0100
@@ -1253,6 +1253,15 @@
     }
 
     /**
+     * Checks that the given values, from array vals starting at offset 0,
+     * are assignable to the given serializable object fields.
+     * @throws ClassCastException if any value is not assignable
+     */
+    void checkObjFieldValueTypes(Object obj, Object[] vals) {
+        fieldRefl.checkObjectFieldValueTypes(obj, vals);
+    }
+
+    /**
      * Sets the serializable object fields of object obj using values from
      * array vals starting at offset 0.  It is the responsibility of the caller
      * to ensure that obj is of the proper type if non-null.
@@ -2070,6 +2079,15 @@
         }
 
         /**
+         * Checks that the given values, from array vals starting at offset 0,
+         * are assignable to the given serializable object fields.
+         * @throws ClassCastException if any value is not assignable
+         */
+        void checkObjectFieldValueTypes(Object obj, Object[] vals) {
+            setObjFieldValues(obj, vals, true);
+        }
+
+        /**
          * Sets the serializable object fields of object obj using values from
          * array vals starting at offset 0.  The caller is responsible for
          * ensuring that obj is of the proper type; however, attempts to set a
@@ -2077,6 +2095,10 @@
          * ClassCastException.
          */
         void setObjFieldValues(Object obj, Object[] vals) {
+            setObjFieldValues(obj, vals, false);
+        }
+
+        private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) {
             if (obj == null) {
                 throw new NullPointerException();
             }
@@ -2101,7 +2123,8 @@
                                 f.getType().getName() + " in instance of " +
                                 obj.getClass().getName());
                         }
-                        unsafe.putObject(obj, key, val);
+                        if (!dryRun)
+                            unsafe.putObject(obj, key, val);
                         break;
 
                     default:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/failureAtomicity/Bar.template	Wed Jun 03 15:30:44 2015 +0100
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package $package;
+
+import java.io.IOException;
+import java.io.Serializable;
+import failureAtomicity.SerialRef;
+
+public class Bar extends Foo implements Serializable {
+    static final long serialVersionUID = -0L;
+
+    public final long barPrim;
+    public final String barRef;
+
+    public final SerialRef ref;  // So we can retrieve a reference to check
+    public $zebra_type zebraBar;   // ordered alphabetically, must be last
+
+    public Bar(int fooPrim, String fooRef, $foo_zebra_type fooZebra,
+               long barPrim, String barRef, $zebra_type zebra) {
+        super(fooPrim, fooRef, fooZebra);
+        this.barPrim = barPrim;
+        this.barRef = barRef;
+        this.zebraBar = zebra;
+        this.ref = new SerialRef(this);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("$package.Bar[")
+                .append("barPrim:").append(barPrim)
+                .append(", barRef:").append(barRef)
+                .append(", zebraBar:").append(zebraBar)
+                .append(", " + super.toString())
+                .toString();
+    }
+
+//$has_readObject    private void readObject(java.io.ObjectInputStream in)
+//$has_readObject        throws IOException, ClassNotFoundException
+//$has_readObject    {
+//$has_readObject        in.defaultReadObject();
+//$has_readObject    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/failureAtomicity/FailureAtomicity.java	Wed Jun 03 15:30:44 2015 +0100
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8071474
+ * @summary Better failure atomicity for default read object.
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.FileUtils
+ * @compile FailureAtomicity.java SerialRef.java
+ * @run main failureAtomicity.FailureAtomicity
+ */
+
+package failureAtomicity;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import jdk.testlibrary.FileUtils;
+
+@SuppressWarnings("unchecked")
+public class FailureAtomicity {
+    static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
+    static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
+    static final Path fooTemplate = TEST_SRC.resolve("Foo.template");
+    static final Path barTemplate = TEST_SRC.resolve("Bar.template");
+
+    static final String[] PKGS = { "a.b.c", "x.y.z" };
+
+    public static void main(String[] args) throws Exception {
+        test_Foo();
+        test_BadFoo();  // 'Bad' => incompatible type; cannot be "fully" deserialized
+        test_FooWithReadObject();
+        test_BadFooWithReadObject();
+
+        test_Foo_Bar();
+        test_Foo_BadBar();
+        test_BadFoo_Bar();
+        test_BadFoo_BadBar();
+        test_Foo_BarWithReadObject();
+        test_Foo_BadBarWithReadObject();
+        test_BadFoo_BarWithReadObject();
+        test_BadFoo_BadBarWithReadObject();
+        test_FooWithReadObject_Bar();
+        test_FooWithReadObject_BadBar();
+        test_BadFooWithReadObject_Bar();
+        test_BadFooWithReadObject_BadBar();
+    }
+
+    static final BiConsumer<Object,Object> FOO_FIELDS_EQUAL = (a,b) -> {
+        try {
+            int aPrim = a.getClass().getField("fooPrim").getInt(a);
+            int bPrim = b.getClass().getField("fooPrim").getInt(b);
+            if (aPrim != bPrim)
+                throw new AssertionError("Not equal: (" + aPrim + "!=" + bPrim
+                                         + "), in [" + a + "] [" + b + "]");
+            Object aRef = a.getClass().getField("fooRef").get(a);
+            Object bRef = b.getClass().getField("fooRef").get(b);
+            if (!aRef.equals(bRef))
+                throw new RuntimeException("Not equal: (" + aRef + "!=" + bRef
+                                           + "), in [" + a + "] [" + b + "]");
+        } catch (NoSuchFieldException | IllegalAccessException x) {
+            throw new InternalError(x);
+        }
+    };
+    static final BiConsumer<Object,Object> FOO_FIELDS_DEFAULT = (ignore,b) -> {
+        try {
+            int aPrim = b.getClass().getField("fooPrim").getInt(b);
+            if (aPrim != 0)
+                throw new AssertionError("Expected 0, got:" + aPrim
+                                         + ", in [" + b + "]");
+            Object aRef = b.getClass().getField("fooRef").get(b);
+            if (aRef != null)
+                throw new RuntimeException("Expected null, got:" + aRef
+                                           + ", in [" + b + "]");
+        } catch (NoSuchFieldException | IllegalAccessException x) {
+            throw new InternalError(x);
+        }
+    };
+    static final BiConsumer<Object,Object> BAR_FIELDS_EQUAL = (a,b) -> {
+        try {
+            long aPrim = a.getClass().getField("barPrim").getLong(a);
+            long bPrim = b.getClass().getField("barPrim").getLong(b);
+            if (aPrim != bPrim)
+                throw new AssertionError("Not equal: (" + aPrim + "!=" + bPrim
+                                         + "), in [" + a + "] [" + b + "]");
+            Object aRef = a.getClass().getField("barRef").get(a);
+            Object bRef = b.getClass().getField("barRef").get(b);
+            if (!aRef.equals(bRef))
+                throw new RuntimeException("Not equal: (" + aRef + "!=" + bRef
+                                           + "), in [" + a + "] [" + b + "]");
+        } catch (NoSuchFieldException | IllegalAccessException x) {
+            throw new InternalError(x);
+        }
+    };
+    static final BiConsumer<Object,Object> BAR_FIELDS_DEFAULT = (ignore,b) -> {
+        try {
+            long aPrim = b.getClass().getField("barPrim").getLong(b);
+            if (aPrim != 0L)
+                throw new AssertionError("Expected 0, got:" + aPrim
+                                         + ", in [" + b + "]");
+            Object aRef = b.getClass().getField("barRef").get(b);
+            if (aRef != null)
+                throw new RuntimeException("Expected null, got:" + aRef
+                                           + ", in [" + b + "]");
+        } catch (NoSuchFieldException | IllegalAccessException x) {
+            throw new InternalError(x);
+        }
+    };
+
+    static void test_Foo() {
+        testFoo("Foo", "String", false, false, FOO_FIELDS_EQUAL); }
+    static void test_BadFoo() {
+        testFoo("BadFoo", "byte[]", true, false, FOO_FIELDS_DEFAULT); }
+    static void test_FooWithReadObject() {
+        testFoo("FooWithReadObject", "String", false, true, FOO_FIELDS_EQUAL); }
+    static void test_BadFooWithReadObject() {
+        testFoo("BadFooWithReadObject", "byte[]", true, true, FOO_FIELDS_DEFAULT); }
+
+    static void testFoo(String testName, String xyzZebraType,
+                        boolean expectCCE, boolean withReadObject,
+                        BiConsumer<Object,Object>... resultCheckers) {
+        System.out.println("\nTesting " + testName);
+        try {
+            Path testRoot = testDir(testName);
+            Path srcRoot = Files.createDirectory(testRoot.resolve("src"));
+            List<Path> srcFiles = new ArrayList<>();
+            srcFiles.add(createSrc(PKGS[0], fooTemplate, srcRoot, "String", withReadObject));
+            srcFiles.add(createSrc(PKGS[1], fooTemplate, srcRoot, xyzZebraType, withReadObject));
+
+            Path build = Files.createDirectory(testRoot.resolve("build"));
+            javac(build, srcFiles);
+
+            URLClassLoader loader = new URLClassLoader(new URL[]{ build.toUri().toURL() },
+                                                       FailureAtomicity.class.getClassLoader());
+            Class<?> fooClass = Class.forName(PKGS[0] + ".Foo", true, loader);
+            Constructor<?> ctr = fooClass.getConstructor(
+                    new Class<?>[]{int.class, String.class, String.class});
+            Object abcFoo = ctr.newInstance(5, "chegar", "zebra");
+
+            try {
+                toOtherPkgInstance(abcFoo, loader);
+                if (expectCCE)
+                    throw new AssertionError("Expected CCE not thrown");
+            } catch (ClassCastException e) {
+                if (!expectCCE)
+                    throw new AssertionError("UnExpected CCE: " + e);
+            }
+
+            Object deserialInstance = failureAtomicity.SerialRef.obj;
+
+            System.out.println("abcFoo:           " + abcFoo);
+            System.out.println("deserialInstance: " + deserialInstance);
+
+            for (BiConsumer<Object, Object> rc : resultCheckers)
+                rc.accept(abcFoo, deserialInstance);
+        } catch (IOException x) {
+            throw new UncheckedIOException(x);
+        } catch (ReflectiveOperationException x) {
+            throw new InternalError(x);
+        }
+    }
+
+    static void test_Foo_Bar() {
+        testFooBar("Foo_Bar", "String", "String", false, false, false,
+                   FOO_FIELDS_EQUAL, BAR_FIELDS_EQUAL);
+    }
+    static void test_Foo_BadBar() {
+        testFooBar("Foo_BadBar", "String", "byte[]", true, false, false,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+    static void test_BadFoo_Bar() {
+        testFooBar("BadFoo_Bar", "byte[]", "String", true, false, false,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+    static void test_BadFoo_BadBar() {
+        testFooBar("BadFoo_BadBar", "byte[]", "byte[]", true, false, false,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+    static void test_Foo_BarWithReadObject() {
+        testFooBar("Foo_BarWithReadObject", "String", "String", false, false, true,
+                   FOO_FIELDS_EQUAL, BAR_FIELDS_EQUAL);
+    }
+    static void test_Foo_BadBarWithReadObject() {
+        testFooBar("Foo_BadBarWithReadObject", "String", "byte[]", true, false, true,
+                   FOO_FIELDS_EQUAL, BAR_FIELDS_DEFAULT);
+    }
+    static void test_BadFoo_BarWithReadObject() {
+        testFooBar("BadFoo_BarWithReadObject", "byte[]", "String", true, false, true,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+    static void test_BadFoo_BadBarWithReadObject() {
+        testFooBar("BadFoo_BadBarWithReadObject", "byte[]", "byte[]", true, false, true,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+
+    static void test_FooWithReadObject_Bar() {
+        testFooBar("FooWithReadObject_Bar", "String", "String", false, true, false,
+                   FOO_FIELDS_EQUAL, BAR_FIELDS_EQUAL);
+    }
+    static void test_FooWithReadObject_BadBar() {
+        testFooBar("FooWithReadObject_BadBar", "String", "byte[]", true, true, false,
+                   FOO_FIELDS_EQUAL, BAR_FIELDS_DEFAULT);
+    }
+    static void test_BadFooWithReadObject_Bar() {
+        testFooBar("BadFooWithReadObject_Bar", "byte[]", "String", true, true, false,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+    static void test_BadFooWithReadObject_BadBar() {
+        testFooBar("BadFooWithReadObject_BadBar", "byte[]", "byte[]", true, true, false,
+                   FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
+    }
+
+    static void testFooBar(String testName, String xyzFooZebraType,
+                           String xyzBarZebraType, boolean expectCCE,
+                           boolean fooWithReadObject, boolean barWithReadObject,
+                           BiConsumer<Object,Object>... resultCheckers) {
+        System.out.println("\nTesting " + testName);
+        try {
+            Path testRoot = testDir(testName);
+            Path srcRoot = Files.createDirectory(testRoot.resolve("src"));
+            List<Path> srcFiles = new ArrayList<>();
+            srcFiles.add(createSrc(PKGS[0], fooTemplate, srcRoot, "String",
+                                   fooWithReadObject, "String"));
+            srcFiles.add(createSrc(PKGS[1], fooTemplate, srcRoot, xyzFooZebraType,
+                                   fooWithReadObject, xyzFooZebraType));
+            srcFiles.add(createSrc(PKGS[0], barTemplate, srcRoot, "String",
+                                   barWithReadObject, "String"));
+            srcFiles.add(createSrc(PKGS[1], barTemplate, srcRoot, xyzBarZebraType,
+                                   barWithReadObject, xyzFooZebraType));
+
+            Path build = Files.createDirectory(testRoot.resolve("build"));
+            javac(build, srcFiles);
+
+            URLClassLoader loader = new URLClassLoader(new URL[]{ build.toUri().toURL() },
+                                                       FailureAtomicity.class.getClassLoader());
+            Class<?> fooClass = Class.forName(PKGS[0] + ".Bar", true, loader);
+            Constructor<?> ctr = fooClass.getConstructor(
+                    new Class<?>[]{int.class, String.class, String.class,
+                                   long.class, String.class, String.class});
+            Object abcBar = ctr.newInstance( 5, "chegar", "zebraFoo", 111L, "aBar", "zebraBar");
+
+            try {
+                toOtherPkgInstance(abcBar, loader);
+                if (expectCCE)
+                    throw new AssertionError("Expected CCE not thrown");
+            } catch (ClassCastException e) {
+                if (!expectCCE)
+                    throw new AssertionError("UnExpected CCE: " + e);
+            }
+
+            Object deserialInstance = failureAtomicity.SerialRef.obj;
+
+            System.out.println("abcBar:           " + abcBar);
+            System.out.println("deserialInstance: " + deserialInstance);
+
+            for (BiConsumer<Object, Object> rc : resultCheckers)
+                rc.accept(abcBar, deserialInstance);
+        } catch (IOException x) {
+            throw new UncheckedIOException(x);
+        } catch (ReflectiveOperationException x) {
+            throw new InternalError(x);
+        }
+    }
+
+    static Path testDir(String name) throws IOException {
+        Path testRoot = Paths.get("FailureAtomicity-" + name);
+        if (Files.exists(testRoot))
+            FileUtils.deleteFileTreeWithRetry(testRoot);
+        Files.createDirectory(testRoot);
+        return testRoot;
+    }
+
+    static String platformPath(String p) { return p.replace("/", File.separator); }
+    static String binaryName(String name) { return name.replace(".", "/"); }
+    static String condRemove(String line, String pattern, boolean hasReadObject) {
+        if (hasReadObject) { return line.replaceAll(pattern, ""); }
+        else { return line; }
+    }
+    static String condReplace(String line, String... zebraFooType) {
+        if (zebraFooType.length == 1) {
+            return line.replaceAll("\\$foo_zebra_type", zebraFooType[0]);
+        } else { return line; }
+    }
+    static String nameFromTemplate(Path template) {
+        return template.getFileName().toString().replaceAll(".template", "");
+    }
+
+    static Path createSrc(String pkg, Path srcTemplate, Path srcRoot,
+                          String zebraType, boolean hasReadObject,
+                          String... zebraFooType)
+        throws IOException
+    {
+        Path srcDst = srcRoot.resolve(platformPath(binaryName(pkg)));
+        Files.createDirectories(srcDst);
+        Path srcFile = srcDst.resolve(nameFromTemplate(srcTemplate) + ".java");
+
+        List<String> lines = Files.lines(srcTemplate)
+                .map(s -> s.replaceAll("\\$package", pkg))
+                .map(s -> s.replaceAll("\\$zebra_type", zebraType))
+                .map(s -> condReplace(s, zebraFooType))
+                .map(s -> condRemove(s, "//\\$has_readObject", hasReadObject))
+                .collect(Collectors.toList());
+        Files.write(srcFile, lines);
+        return srcFile;
+    }
+
+    static void javac(Path dest, List<Path> sourceFiles) throws IOException {
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        try (StandardJavaFileManager fileManager =
+                     compiler.getStandardFileManager(null, null, null)) {
+            List<File> files = sourceFiles.stream()
+                                          .map(p -> p.toFile())
+                                          .collect(Collectors.toList());
+            Iterable<? extends JavaFileObject> compilationUnits =
+                    fileManager.getJavaFileObjectsFromFiles(files);
+            fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
+                                    Arrays.asList(dest.toFile()));
+            fileManager.setLocation(StandardLocation.CLASS_PATH,
+                                    Arrays.asList(TEST_CLASSES.toFile()));
+            JavaCompiler.CompilationTask task = compiler
+                    .getTask(null, fileManager, null, null, null, compilationUnits);
+            boolean passed = task.call();
+            if (!passed)
+                throw new RuntimeException("Error compiling " + files);
+        }
+    }
+
+    static Object toOtherPkgInstance(Object obj, ClassLoader loader)
+        throws IOException, ClassNotFoundException
+    {
+        byte[] bytes = serialize(obj);
+        bytes = replacePkg(bytes);
+        return deserialize(bytes, loader);
+    }
+
+    @SuppressWarnings("deprecation")
+    static byte[] replacePkg(byte[] bytes) {
+        String str = new String(bytes, 0);
+        str = str.replaceAll(PKGS[0], PKGS[1]);
+        str.getBytes(0, bytes.length, bytes, 0);
+        return bytes;
+    }
+
+    static byte[] serialize(Object obj) throws IOException {
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+             ObjectOutputStream out = new ObjectOutputStream(baos);) {
+            out.writeObject(obj);
+            return baos.toByteArray();
+        }
+    }
+
+    static Object deserialize(byte[] data, ClassLoader l)
+        throws IOException, ClassNotFoundException
+    {
+        return new WithLoaderObjectInputStream(new ByteArrayInputStream(data), l)
+                .readObject();
+    }
+
+    static class WithLoaderObjectInputStream extends ObjectInputStream {
+        final ClassLoader loader;
+        WithLoaderObjectInputStream(InputStream is, ClassLoader loader)
+            throws IOException
+        {
+            super(is);
+            this.loader = loader;
+        }
+        @Override
+        protected Class<?> resolveClass(ObjectStreamClass desc)
+            throws IOException, ClassNotFoundException {
+            try {
+                return super.resolveClass(desc);
+            } catch (ClassNotFoundException x) {
+                String name = desc.getName();
+                return Class.forName(name, false, loader);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/failureAtomicity/Foo.template	Wed Jun 03 15:30:44 2015 +0100
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package $package;
+
+import java.io.IOException;
+import java.io.Serializable;
+import failureAtomicity.SerialRef;
+
+public class Foo implements Serializable {
+    static final long serialVersionUID = -0L;
+
+    public final int fooPrim;
+    public final String fooRef;
+
+    public final SerialRef ref;    // So we can retrieve a reference to check
+    public $zebra_type zebraFoo;   // ordered alphabetically, must be last
+
+    public Foo(int fooPrim, String fooRef, $zebra_type zebra) {
+        this.fooPrim = fooPrim;
+        this.fooRef = fooRef;
+        this.zebraFoo = zebra;
+        this.ref = new SerialRef(this);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("$package.Foo[")
+                .append("fooPrim:").append(fooPrim)
+                .append(", fooRef:").append(fooRef)
+                .append(", zebraFoo:").append(zebraFoo).append("]")
+                .toString();
+    }
+
+//$has_readObject    private void readObject(java.io.ObjectInputStream in)
+//$has_readObject        throws IOException, ClassNotFoundException
+//$has_readObject    {
+//$has_readObject        in.defaultReadObject();
+//$has_readObject    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/failureAtomicity/SerialRef.java	Wed Jun 03 15:30:44 2015 +0100
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package failureAtomicity;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+// For verification purposes only.
+
+public class SerialRef implements Serializable {
+    static final long serialVersionUID = -0L;
+    public static Object obj;
+
+    private final Object ref;
+
+    public SerialRef(Object ref) {
+        this.ref = ref;
+    }
+
+    private void readObject(ObjectInputStream in)
+            throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        SerialRef.obj = ref;
+    }
+}