blob: 8be6ce1142b3ca3e72df7ef6f85b43a36905442e [file] [log] [blame]
/*
* Copyright (c) 2015, 2017, 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.
* @modules jdk.compiler
* @library /test/lib
* @build jdk.test.lib.Platform
* jdk.test.lib.util.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.test.lib.util.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);
}
}
}
}