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