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