|
1 /* |
|
2 * Copyright (c) 2015, 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 * @library /lib/testlibrary |
|
29 * @build jdk.testlibrary.FileUtils |
|
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.testlibrary.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 } |