1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8071474
  27  * @summary Better failure atomicity for default read object.
  28  * @modules jdk.compiler
  29  * @library /test/lib
  30  * @compile FailureAtomicity.java SerialRef.java
  31  * @run main failureAtomicity.FailureAtomicity
  32  */
  33 
  34 package failureAtomicity;
  35 
  36 import java.io.ByteArrayInputStream;
  37 import java.io.ByteArrayOutputStream;
  38 import java.io.File;
  39 import java.io.IOException;
  40 import java.io.InputStream;
  41 import java.io.ObjectInputStream;
  42 import java.io.ObjectOutputStream;
  43 import java.io.ObjectStreamClass;
  44 import java.io.UncheckedIOException;
  45 import java.lang.reflect.Constructor;
  46 import java.net.URL;
  47 import java.net.URLClassLoader;
  48 import java.nio.file.Files;
  49 import java.nio.file.Path;
  50 import java.nio.file.Paths;
  51 import java.util.ArrayList;
  52 import java.util.Arrays;
  53 import java.util.List;
  54 import java.util.function.BiConsumer;
  55 import java.util.stream.Collectors;
  56 import javax.tools.JavaCompiler;
  57 import javax.tools.JavaFileObject;
  58 import javax.tools.StandardJavaFileManager;
  59 import javax.tools.StandardLocation;
  60 import javax.tools.ToolProvider;
  61 import jdk.test.lib.util.FileUtils;
  62 
  63 @SuppressWarnings("unchecked")
  64 public class FailureAtomicity {
  65     static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
  66     static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
  67     static final Path fooTemplate = TEST_SRC.resolve("Foo.template");
  68     static final Path barTemplate = TEST_SRC.resolve("Bar.template");
  69 
  70     static final String[] PKGS = { "a.b.c", "x.y.z" };
  71 
  72     public static void main(String[] args) throws Exception {
  73         test_Foo();
  74         test_BadFoo();  // 'Bad' => incompatible type; cannot be "fully" deserialized
  75         test_FooWithReadObject();
  76         test_BadFooWithReadObject();
  77 
  78         test_Foo_Bar();
  79         test_Foo_BadBar();
  80         test_BadFoo_Bar();
  81         test_BadFoo_BadBar();
  82         test_Foo_BarWithReadObject();
  83         test_Foo_BadBarWithReadObject();
  84         test_BadFoo_BarWithReadObject();
  85         test_BadFoo_BadBarWithReadObject();
  86         test_FooWithReadObject_Bar();
  87         test_FooWithReadObject_BadBar();
  88         test_BadFooWithReadObject_Bar();
  89         test_BadFooWithReadObject_BadBar();
  90     }
  91 
  92     static final BiConsumer<Object,Object> FOO_FIELDS_EQUAL = (a,b) -> {
  93         try {
  94             int aPrim = a.getClass().getField("fooPrim").getInt(a);
  95             int bPrim = b.getClass().getField("fooPrim").getInt(b);
  96             if (aPrim != bPrim)
  97                 throw new AssertionError("Not equal: (" + aPrim + "!=" + bPrim
  98                                          + "), in [" + a + "] [" + b + "]");
  99             Object aRef = a.getClass().getField("fooRef").get(a);
 100             Object bRef = b.getClass().getField("fooRef").get(b);
 101             if (!aRef.equals(bRef))
 102                 throw new RuntimeException("Not equal: (" + aRef + "!=" + bRef
 103                                            + "), in [" + a + "] [" + b + "]");
 104         } catch (NoSuchFieldException | IllegalAccessException x) {
 105             throw new InternalError(x);
 106         }
 107     };
 108     static final BiConsumer<Object,Object> FOO_FIELDS_DEFAULT = (ignore,b) -> {
 109         try {
 110             int aPrim = b.getClass().getField("fooPrim").getInt(b);
 111             if (aPrim != 0)
 112                 throw new AssertionError("Expected 0, got:" + aPrim
 113                                          + ", in [" + b + "]");
 114             Object aRef = b.getClass().getField("fooRef").get(b);
 115             if (aRef != null)
 116                 throw new RuntimeException("Expected null, got:" + aRef
 117                                            + ", in [" + b + "]");
 118         } catch (NoSuchFieldException | IllegalAccessException x) {
 119             throw new InternalError(x);
 120         }
 121     };
 122     static final BiConsumer<Object,Object> BAR_FIELDS_EQUAL = (a,b) -> {
 123         try {
 124             long aPrim = a.getClass().getField("barPrim").getLong(a);
 125             long bPrim = b.getClass().getField("barPrim").getLong(b);
 126             if (aPrim != bPrim)
 127                 throw new AssertionError("Not equal: (" + aPrim + "!=" + bPrim
 128                                          + "), in [" + a + "] [" + b + "]");
 129             Object aRef = a.getClass().getField("barRef").get(a);
 130             Object bRef = b.getClass().getField("barRef").get(b);
 131             if (!aRef.equals(bRef))
 132                 throw new RuntimeException("Not equal: (" + aRef + "!=" + bRef
 133                                            + "), in [" + a + "] [" + b + "]");
 134         } catch (NoSuchFieldException | IllegalAccessException x) {
 135             throw new InternalError(x);
 136         }
 137     };
 138     static final BiConsumer<Object,Object> BAR_FIELDS_DEFAULT = (ignore,b) -> {
 139         try {
 140             long aPrim = b.getClass().getField("barPrim").getLong(b);
 141             if (aPrim != 0L)
 142                 throw new AssertionError("Expected 0, got:" + aPrim
 143                                          + ", in [" + b + "]");
 144             Object aRef = b.getClass().getField("barRef").get(b);
 145             if (aRef != null)
 146                 throw new RuntimeException("Expected null, got:" + aRef
 147                                            + ", in [" + b + "]");
 148         } catch (NoSuchFieldException | IllegalAccessException x) {
 149             throw new InternalError(x);
 150         }
 151     };
 152 
 153     static void test_Foo() {
 154         testFoo("Foo", "String", false, false, FOO_FIELDS_EQUAL); }
 155     static void test_BadFoo() {
 156         testFoo("BadFoo", "byte[]", true, false, FOO_FIELDS_DEFAULT); }
 157     static void test_FooWithReadObject() {
 158         testFoo("FooWithReadObject", "String", false, true, FOO_FIELDS_EQUAL); }
 159     static void test_BadFooWithReadObject() {
 160         testFoo("BadFooWithReadObject", "byte[]", true, true, FOO_FIELDS_DEFAULT); }
 161 
 162     static void testFoo(String testName, String xyzZebraType,
 163                         boolean expectCCE, boolean withReadObject,
 164                         BiConsumer<Object,Object>... resultCheckers) {
 165         System.out.println("\nTesting " + testName);
 166         try {
 167             Path testRoot = testDir(testName);
 168             Path srcRoot = Files.createDirectory(testRoot.resolve("src"));
 169             List<Path> srcFiles = new ArrayList<>();
 170             srcFiles.add(createSrc(PKGS[0], fooTemplate, srcRoot, "String", withReadObject));
 171             srcFiles.add(createSrc(PKGS[1], fooTemplate, srcRoot, xyzZebraType, withReadObject));
 172 
 173             Path build = Files.createDirectory(testRoot.resolve("build"));
 174             javac(build, srcFiles);
 175 
 176             URLClassLoader loader = new URLClassLoader(new URL[]{ build.toUri().toURL() },
 177                                                        FailureAtomicity.class.getClassLoader());
 178             Class<?> fooClass = Class.forName(PKGS[0] + ".Foo", true, loader);
 179             Constructor<?> ctr = fooClass.getConstructor(
 180                     new Class<?>[]{int.class, String.class, String.class});
 181             Object abcFoo = ctr.newInstance(5, "chegar", "zebra");
 182 
 183             try {
 184                 toOtherPkgInstance(abcFoo, loader);
 185                 if (expectCCE)
 186                     throw new AssertionError("Expected CCE not thrown");
 187             } catch (ClassCastException e) {
 188                 if (!expectCCE)
 189                     throw new AssertionError("UnExpected CCE: " + e);
 190             }
 191 
 192             Object deserialInstance = failureAtomicity.SerialRef.obj;
 193 
 194             System.out.println("abcFoo:           " + abcFoo);
 195             System.out.println("deserialInstance: " + deserialInstance);
 196 
 197             for (BiConsumer<Object, Object> rc : resultCheckers)
 198                 rc.accept(abcFoo, deserialInstance);
 199         } catch (IOException x) {
 200             throw new UncheckedIOException(x);
 201         } catch (ReflectiveOperationException x) {
 202             throw new InternalError(x);
 203         }
 204     }
 205 
 206     static void test_Foo_Bar() {
 207         testFooBar("Foo_Bar", "String", "String", false, false, false,
 208                    FOO_FIELDS_EQUAL, BAR_FIELDS_EQUAL);
 209     }
 210     static void test_Foo_BadBar() {
 211         testFooBar("Foo_BadBar", "String", "byte[]", true, false, false,
 212                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 213     }
 214     static void test_BadFoo_Bar() {
 215         testFooBar("BadFoo_Bar", "byte[]", "String", true, false, false,
 216                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 217     }
 218     static void test_BadFoo_BadBar() {
 219         testFooBar("BadFoo_BadBar", "byte[]", "byte[]", true, false, false,
 220                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 221     }
 222     static void test_Foo_BarWithReadObject() {
 223         testFooBar("Foo_BarWithReadObject", "String", "String", false, false, true,
 224                    FOO_FIELDS_EQUAL, BAR_FIELDS_EQUAL);
 225     }
 226     static void test_Foo_BadBarWithReadObject() {
 227         testFooBar("Foo_BadBarWithReadObject", "String", "byte[]", true, false, true,
 228                    FOO_FIELDS_EQUAL, BAR_FIELDS_DEFAULT);
 229     }
 230     static void test_BadFoo_BarWithReadObject() {
 231         testFooBar("BadFoo_BarWithReadObject", "byte[]", "String", true, false, true,
 232                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 233     }
 234     static void test_BadFoo_BadBarWithReadObject() {
 235         testFooBar("BadFoo_BadBarWithReadObject", "byte[]", "byte[]", true, false, true,
 236                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 237     }
 238 
 239     static void test_FooWithReadObject_Bar() {
 240         testFooBar("FooWithReadObject_Bar", "String", "String", false, true, false,
 241                    FOO_FIELDS_EQUAL, BAR_FIELDS_EQUAL);
 242     }
 243     static void test_FooWithReadObject_BadBar() {
 244         testFooBar("FooWithReadObject_BadBar", "String", "byte[]", true, true, false,
 245                    FOO_FIELDS_EQUAL, BAR_FIELDS_DEFAULT);
 246     }
 247     static void test_BadFooWithReadObject_Bar() {
 248         testFooBar("BadFooWithReadObject_Bar", "byte[]", "String", true, true, false,
 249                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 250     }
 251     static void test_BadFooWithReadObject_BadBar() {
 252         testFooBar("BadFooWithReadObject_BadBar", "byte[]", "byte[]", true, true, false,
 253                    FOO_FIELDS_DEFAULT, BAR_FIELDS_DEFAULT);
 254     }
 255 
 256     static void testFooBar(String testName, String xyzFooZebraType,
 257                            String xyzBarZebraType, boolean expectCCE,
 258                            boolean fooWithReadObject, boolean barWithReadObject,
 259                            BiConsumer<Object,Object>... resultCheckers) {
 260         System.out.println("\nTesting " + testName);
 261         try {
 262             Path testRoot = testDir(testName);
 263             Path srcRoot = Files.createDirectory(testRoot.resolve("src"));
 264             List<Path> srcFiles = new ArrayList<>();
 265             srcFiles.add(createSrc(PKGS[0], fooTemplate, srcRoot, "String",
 266                                    fooWithReadObject, "String"));
 267             srcFiles.add(createSrc(PKGS[1], fooTemplate, srcRoot, xyzFooZebraType,
 268                                    fooWithReadObject, xyzFooZebraType));
 269             srcFiles.add(createSrc(PKGS[0], barTemplate, srcRoot, "String",
 270                                    barWithReadObject, "String"));
 271             srcFiles.add(createSrc(PKGS[1], barTemplate, srcRoot, xyzBarZebraType,
 272                                    barWithReadObject, xyzFooZebraType));
 273 
 274             Path build = Files.createDirectory(testRoot.resolve("build"));
 275             javac(build, srcFiles);
 276 
 277             URLClassLoader loader = new URLClassLoader(new URL[]{ build.toUri().toURL() },
 278                                                        FailureAtomicity.class.getClassLoader());
 279             Class<?> fooClass = Class.forName(PKGS[0] + ".Bar", true, loader);
 280             Constructor<?> ctr = fooClass.getConstructor(
 281                     new Class<?>[]{int.class, String.class, String.class,
 282                                    long.class, String.class, String.class});
 283             Object abcBar = ctr.newInstance( 5, "chegar", "zebraFoo", 111L, "aBar", "zebraBar");
 284 
 285             try {
 286                 toOtherPkgInstance(abcBar, loader);
 287                 if (expectCCE)
 288                     throw new AssertionError("Expected CCE not thrown");
 289             } catch (ClassCastException e) {
 290                 if (!expectCCE)
 291                     throw new AssertionError("UnExpected CCE: " + e);
 292             }
 293 
 294             Object deserialInstance = failureAtomicity.SerialRef.obj;
 295 
 296             System.out.println("abcBar:           " + abcBar);
 297             System.out.println("deserialInstance: " + deserialInstance);
 298 
 299             for (BiConsumer<Object, Object> rc : resultCheckers)
 300                 rc.accept(abcBar, deserialInstance);
 301         } catch (IOException x) {
 302             throw new UncheckedIOException(x);
 303         } catch (ReflectiveOperationException x) {
 304             throw new InternalError(x);
 305         }
 306     }
 307 
 308     static Path testDir(String name) throws IOException {
 309         Path testRoot = Paths.get("FailureAtomicity-" + name);
 310         if (Files.exists(testRoot))
 311             FileUtils.deleteFileTreeWithRetry(testRoot);
 312         Files.createDirectory(testRoot);
 313         return testRoot;
 314     }
 315 
 316     static String platformPath(String p) { return p.replace("/", File.separator); }
 317     static String binaryName(String name) { return name.replace(".", "/"); }
 318     static String condRemove(String line, String pattern, boolean hasReadObject) {
 319         if (hasReadObject) { return line.replaceAll(pattern, ""); }
 320         else { return line; }
 321     }
 322     static String condReplace(String line, String... zebraFooType) {
 323         if (zebraFooType.length == 1) {
 324             return line.replaceAll("\\$foo_zebra_type", zebraFooType[0]);
 325         } else { return line; }
 326     }
 327     static String nameFromTemplate(Path template) {
 328         return template.getFileName().toString().replaceAll(".template", "");
 329     }
 330 
 331     static Path createSrc(String pkg, Path srcTemplate, Path srcRoot,
 332                           String zebraType, boolean hasReadObject,
 333                           String... zebraFooType)
 334         throws IOException
 335     {
 336         Path srcDst = srcRoot.resolve(platformPath(binaryName(pkg)));
 337         Files.createDirectories(srcDst);
 338         Path srcFile = srcDst.resolve(nameFromTemplate(srcTemplate) + ".java");
 339 
 340         List<String> lines = Files.lines(srcTemplate)
 341                 .map(s -> s.replaceAll("\\$package", pkg))
 342                 .map(s -> s.replaceAll("\\$zebra_type", zebraType))
 343                 .map(s -> condReplace(s, zebraFooType))
 344                 .map(s -> condRemove(s, "//\\$has_readObject", hasReadObject))
 345                 .collect(Collectors.toList());
 346         Files.write(srcFile, lines);
 347         return srcFile;
 348     }
 349 
 350     static void javac(Path dest, List<Path> sourceFiles) throws IOException {
 351         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 352         try (StandardJavaFileManager fileManager =
 353                      compiler.getStandardFileManager(null, null, null)) {
 354             List<File> files = sourceFiles.stream()
 355                                           .map(p -> p.toFile())
 356                                           .collect(Collectors.toList());
 357             Iterable<? extends JavaFileObject> compilationUnits =
 358                     fileManager.getJavaFileObjectsFromFiles(files);
 359             fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
 360                                     Arrays.asList(dest.toFile()));
 361             fileManager.setLocation(StandardLocation.CLASS_PATH,
 362                                     Arrays.asList(TEST_CLASSES.toFile()));
 363             JavaCompiler.CompilationTask task = compiler
 364                     .getTask(null, fileManager, null, null, null, compilationUnits);
 365             boolean passed = task.call();
 366             if (!passed)
 367                 throw new RuntimeException("Error compiling " + files);
 368         }
 369     }
 370 
 371     static Object toOtherPkgInstance(Object obj, ClassLoader loader)
 372         throws IOException, ClassNotFoundException
 373     {
 374         byte[] bytes = serialize(obj);
 375         bytes = replacePkg(bytes);
 376         return deserialize(bytes, loader);
 377     }
 378 
 379     @SuppressWarnings("deprecation")
 380     static byte[] replacePkg(byte[] bytes) {
 381         String str = new String(bytes, 0);
 382         str = str.replaceAll(PKGS[0], PKGS[1]);
 383         str.getBytes(0, bytes.length, bytes, 0);
 384         return bytes;
 385     }
 386 
 387     static byte[] serialize(Object obj) throws IOException {
 388         try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
 389              ObjectOutputStream out = new ObjectOutputStream(baos);) {
 390             out.writeObject(obj);
 391             return baos.toByteArray();
 392         }
 393     }
 394 
 395     static Object deserialize(byte[] data, ClassLoader l)
 396         throws IOException, ClassNotFoundException
 397     {
 398         return new WithLoaderObjectInputStream(new ByteArrayInputStream(data), l)
 399                 .readObject();
 400     }
 401 
 402     static class WithLoaderObjectInputStream extends ObjectInputStream {
 403         final ClassLoader loader;
 404         WithLoaderObjectInputStream(InputStream is, ClassLoader loader)
 405             throws IOException
 406         {
 407             super(is);
 408             this.loader = loader;
 409         }
 410         @Override
 411         protected Class<?> resolveClass(ObjectStreamClass desc)
 412             throws IOException, ClassNotFoundException {
 413             try {
 414                 return super.resolveClass(desc);
 415             } catch (ClassNotFoundException x) {
 416                 String name = desc.getName();
 417                 return Class.forName(name, false, loader);
 418             }
 419         }
 420     }
 421 }