< prev index next >

test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java

Print this page
rev 58565 : 8238358: Implementation of JEP 371: Hidden Classes
Reviewed-by: duke
Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com
rev 58567 : [mq]: rename-isHidden
rev 58568 : [mq]: hidden-class-4

*** 21,36 **** * questions. */ /* * @test * @library /test/lib * @build jdk.test.lib.Utils * jdk.test.lib.compiler.CompilerUtils ! * BasicTest ! * @run testng/othervm BasicTest ! * @run testng/othervm -Xcomp BasicTest */ import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandles.Lookup; --- 21,36 ---- * questions. */ /* * @test + * @modules java.base/jdk.internal.org.objectweb.asm + * jdk.compiler * @library /test/lib * @build jdk.test.lib.Utils * jdk.test.lib.compiler.CompilerUtils ! * @run testng/othervm --enable-preview BasicTest */ import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandles.Lookup;
*** 46,61 **** --- 46,65 ---- import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; + import jdk.internal.org.objectweb.asm.ClassWriter; + import jdk.internal.org.objectweb.asm.Type; import jdk.test.lib.compiler.CompilerUtils; import jdk.test.lib.Utils; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; + + import static jdk.internal.org.objectweb.asm.Opcodes.*; import static org.testng.Assert.*; interface HiddenTest { void test(); }
*** 68,79 **** private static byte[] hiddenClassBytes; @BeforeTest static void setup() throws IOException { ! compileSources(SRC_DIR, CLASSES_DIR); ! hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class")); // compile with --release 10 with no NestHost and NestMembers attribute compileSources(SRC_DIR.resolve("Outer.java"), CLASSES_10_DIR, "--release", "10"); compileSources(SRC_DIR.resolve("EnclosingClass.java"), CLASSES_10_DIR, "--release", "10"); --- 72,83 ---- private static byte[] hiddenClassBytes; @BeforeTest static void setup() throws IOException { ! compileSources(SRC_DIR, CLASSES_DIR, ! "--enable-preview", "-source", String.valueOf(Runtime.version().feature())); hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class")); // compile with --release 10 with no NestHost and NestMembers attribute compileSources(SRC_DIR.resolve("Outer.java"), CLASSES_10_DIR, "--release", "10"); compileSources(SRC_DIR.resolve("EnclosingClass.java"), CLASSES_10_DIR, "--release", "10");
*** 104,114 **** t.test(); // sanity check Class<?> c = t.getClass(); Class<?>[] intfs = c.getInterfaces(); ! assertTrue(c.isHiddenClass()); assertFalse(c.isPrimitive()); assertTrue(intfs.length == 1); assertTrue(intfs[0] == HiddenTest.class); assertTrue(c.getCanonicalName() == null); assertTrue(c.getName().startsWith("HiddenClass/")); --- 108,118 ---- t.test(); // sanity check Class<?> c = t.getClass(); Class<?>[] intfs = c.getInterfaces(); ! assertTrue(c.isHidden()); assertFalse(c.isPrimitive()); assertTrue(intfs.length == 1); assertTrue(intfs[0] == HiddenTest.class); assertTrue(c.getCanonicalName() == null); assertTrue(c.getName().startsWith("HiddenClass/"));
*** 119,144 **** // test setAccessible checkSetAccessible(c, "realTest"); checkSetAccessible(c, "test"); } @Test public void primitiveClass() { ! assertFalse(int.class.isHiddenClass()); ! assertFalse(String.class.isHiddenClass()); } private void testHiddenArray(Class<?> type) throws Exception { // array of hidden class Object array = Array.newInstance(type, 2); Class<?> arrayType = array.getClass(); assertTrue(arrayType.isArray()); assertTrue(Array.getLength(array) == 2); ! assertFalse(arrayType.isHiddenClass()); assertTrue(arrayType.getName().startsWith("[LHiddenClass/"), "unexpected name: " + arrayType.getName()); ! assertTrue(arrayType.getComponentType().isHiddenClass()); assertTrue(arrayType.getComponentType() == type); Object t = type.newInstance(); Array.set(array, 0, t); Object o = Array.get(array, 0); assertTrue(o == t); --- 123,149 ---- // test setAccessible checkSetAccessible(c, "realTest"); checkSetAccessible(c, "test"); } + // primitive class is not a hidden class @Test public void primitiveClass() { ! assertFalse(int.class.isHidden()); ! assertFalse(String.class.isHidden()); } private void testHiddenArray(Class<?> type) throws Exception { // array of hidden class Object array = Array.newInstance(type, 2); Class<?> arrayType = array.getClass(); assertTrue(arrayType.isArray()); assertTrue(Array.getLength(array) == 2); ! assertFalse(arrayType.isHidden()); assertTrue(arrayType.getName().startsWith("[LHiddenClass/"), "unexpected name: " + arrayType.getName()); ! assertTrue(arrayType.getComponentType().isHidden()); assertTrue(arrayType.getComponentType() == type); Object t = type.newInstance(); Array.set(array, 0, t); Object o = Array.get(array, 0); assertTrue(o == t);
*** 186,231 **** // only includes static nest members assertTrue(host.getNestMembers().length == 1); assertTrue(host.getNestMembers()[0] == host); } - @Test - public void hiddenCantReflect() throws Throwable { - HiddenTest t = (HiddenTest)defineHiddenClass("HiddenCantReflect").newInstance(); - t.test(); - - Class<?> c = t.getClass(); - Class<?>[] intfs = c.getInterfaces(); - assertTrue(intfs.length == 1); - assertTrue(intfs[0] == HiddenTest.class); - - try { - // this would cause loading of class HiddenCantReflect and NCDFE due - // to error during verification - c.getDeclaredMethods(); - } catch (NoClassDefFoundError e) { - Throwable x = e.getCause(); - if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) { - throw e; - } - } - } - @DataProvider(name = "hiddenClasses") private Object[][] hiddenClasses() { return new Object[][] { new Object[] { "HiddenInterface", false }, new Object[] { "AbstractClass", false }, // class file with bad NestHost, NestMembers and InnerClasses or EnclosingMethod attribute // define them as nestmate to verify Class::getNestHost and getNestMembers new Object[] { "Outer", true }, new Object[] { "Outer$Inner", true }, new Object[] { "EnclosingClass", true }, new Object[] { "EnclosingClass$1", true }, }; } @Test(dataProvider = "hiddenClasses") public void defineHiddenClass(String name, boolean nestmate) throws Exception { byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); Class<?> hc; Class<?> host; --- 191,223 ---- // only includes static nest members assertTrue(host.getNestMembers().length == 1); assertTrue(host.getNestMembers()[0] == host); } @DataProvider(name = "hiddenClasses") private Object[][] hiddenClasses() { return new Object[][] { new Object[] { "HiddenInterface", false }, new Object[] { "AbstractClass", false }, + // a hidden annotation is useless because it cannot be referenced by any class + new Object[] { "HiddenAnnotation", false }, // class file with bad NestHost, NestMembers and InnerClasses or EnclosingMethod attribute // define them as nestmate to verify Class::getNestHost and getNestMembers new Object[] { "Outer", true }, new Object[] { "Outer$Inner", true }, new Object[] { "EnclosingClass", true }, new Object[] { "EnclosingClass$1", true }, }; } + /* + * Test that class file bytes that can be defined as a normal class + * can be successfully created as a hidden class even it might not + * make sense as a hidden class. For example, a hidden annotation + * is not useful as it cannot be referenced and an outer/inner class + * when defined as a hidden effectively becomes a final top-level class. + */ @Test(dataProvider = "hiddenClasses") public void defineHiddenClass(String name, boolean nestmate) throws Exception { byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); Class<?> hc; Class<?> host;
*** 239,252 **** assertTrue(hc.getNestHost() == host); assertTrue(hc.getNestMembers().length == 1); assertTrue(hc.getNestMembers()[0] == host); } ! @Test(expectedExceptions = NoClassDefFoundError.class) ! public void hiddenSuperClass() throws Exception { ! byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenSuper.class")); Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); } @Test(expectedExceptions = {IllegalArgumentException.class}) public void cantDefineModule() throws Throwable { Path src = Paths.get("module-info.java"); --- 231,357 ---- assertTrue(hc.getNestHost() == host); assertTrue(hc.getNestMembers().length == 1); assertTrue(hc.getNestMembers()[0] == host); } ! @DataProvider(name = "emptyClasses") ! private Object[][] emptyClasses() { ! return new Object[][] { ! new Object[] { "EmptyHiddenSynthetic", ACC_SYNTHETIC }, ! new Object[] { "EmptyHiddenEnum", ACC_ENUM }, ! new Object[] { "EmptyHiddenAbstractClass", ACC_ABSTRACT }, ! new Object[] { "EmptyHiddenInterface", ACC_ABSTRACT|ACC_INTERFACE }, ! new Object[] { "EmptyHiddenAnnotation", ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE }, ! }; ! } ! ! /* ! * Test if an empty class with valid access flags can be created as a hidden class ! * as long as it does not violate the restriction of a hidden class. ! * ! * A meaningful enum type defines constants of that enum type. So ! * enum class containing constants of its type should not be a hidden ! * class. ! */ ! @Test(dataProvider = "emptyClasses") ! public void emptyHiddenClass(String name, int accessFlags) throws Exception { ! byte[] bytes = (accessFlags == ACC_ENUM) ? classBytes(name, Enum.class, accessFlags) ! : classBytes(name, accessFlags); Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + switch (accessFlags) { + case ACC_SYNTHETIC: + assertTrue(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertFalse(hc.isInterface()); + break; + case ACC_ENUM: + assertFalse(hc.isSynthetic()); + assertTrue(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertFalse(hc.isInterface()); + break; + case ACC_ABSTRACT: + assertFalse(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertFalse(hc.isInterface()); + break; + case ACC_ABSTRACT|ACC_INTERFACE: + assertFalse(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertFalse(hc.isAnnotation()); + assertTrue(hc.isInterface()); + break; + case ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE: + assertFalse(hc.isSynthetic()); + assertFalse(hc.isEnum()); + assertTrue(hc.isAnnotation()); + assertTrue(hc.isInterface()); + break; + default: + throw new IllegalArgumentException("unexpected access flag: " + accessFlags); + } + assertTrue(hc.isHidden()); + assertTrue(hc.getModifiers() == (ACC_PUBLIC|accessFlags)); + assertFalse(hc.isLocalClass()); + assertFalse(hc.isMemberClass()); + assertFalse(hc.isAnonymousClass()); + assertFalse(hc.isArray()); + } + + // These class files can't be defined as hidden classes + @DataProvider(name = "cantBeHiddenClasses") + private Object[][] cantBeHiddenClasses() { + return new Object[][] { + // a hidden class can't be a field's declaring type + // enum class with static final HiddenEnum[] $VALUES: + new Object[] { "HiddenEnum" }, + // supertype of this class is a hidden class + new Object[] { "HiddenSuper" }, + // a record class whose equals(HiddenRecord, Object) method + // refers to a hidden class in the parameter type and fails + // verification. Perhaps this method signature should be reconsidered. + new Object[] { "HiddenRecord" }, + }; + } + + /* + * These class files + */ + @Test(dataProvider = "cantBeHiddenClasses", expectedExceptions = NoClassDefFoundError.class) + public void failToDeriveAsHiddenClass(String name) throws Exception { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); + Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); + } + + /* + * A hidden class can be successfully created but fails to be reflected + * if it refers to its own type in the descriptor. + * e.g. Class::getMethods resolves the declaring type of fields, + * parameter types and return type. + */ + @Test + public void hiddenCantReflect() throws Throwable { + HiddenTest t = (HiddenTest)defineHiddenClass("HiddenCantReflect").newInstance(); + t.test(); + + Class<?> c = t.getClass(); + Class<?>[] intfs = c.getInterfaces(); + assertTrue(intfs.length == 1); + assertTrue(intfs[0] == HiddenTest.class); + + try { + // this would cause loading of class HiddenCantReflect and NCDFE due + // to error during verification + c.getDeclaredMethods(); + } catch (NoClassDefFoundError e) { + Throwable x = e.getCause(); + if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) { + throw e; + } + } } @Test(expectedExceptions = {IllegalArgumentException.class}) public void cantDefineModule() throws Throwable { Path src = Paths.get("module-info.java");
*** 310,320 **** assertTrue(members.length == 1 && members[0] == hc); } // a hidden class with bad InnerClasses or EnclosingMethod attribute private void hiddenClassWithBadAttribute(Class<?> hc, String badDeclaringClassName) { ! assertTrue(hc.isHiddenClass()); assertTrue(hc.getCanonicalName() == null); assertTrue(hc.getName().contains("/")); if (badDeclaringClassName == null) { // the following reflection API assumes a good name in InnerClasses --- 415,425 ---- assertTrue(members.length == 1 && members[0] == hc); } // a hidden class with bad InnerClasses or EnclosingMethod attribute private void hiddenClassWithBadAttribute(Class<?> hc, String badDeclaringClassName) { ! assertTrue(hc.isHidden()); assertTrue(hc.getCanonicalName() == null); assertTrue(hc.getName().contains("/")); if (badDeclaringClassName == null) { // the following reflection API assumes a good name in InnerClasses
*** 361,374 **** assertTrue(hc.getNestMembers().length == 1); assertTrue(hc.getNestMembers()[0] == hc); } private static void assertHiddenClass(Class<?> hc) { ! assertTrue(hc.isHiddenClass()); assertTrue(hc.getCanonicalName() == null); assertTrue(hc.getName().contains("/")); assertFalse(hc.isAnonymousClass()); assertFalse(hc.isLocalClass()); assertFalse(hc.isMemberClass()); assertFalse(hc.getSimpleName().isEmpty()); // sanity check } } --- 466,491 ---- assertTrue(hc.getNestMembers().length == 1); assertTrue(hc.getNestMembers()[0] == hc); } private static void assertHiddenClass(Class<?> hc) { ! assertTrue(hc.isHidden()); assertTrue(hc.getCanonicalName() == null); assertTrue(hc.getName().contains("/")); assertFalse(hc.isAnonymousClass()); assertFalse(hc.isLocalClass()); assertFalse(hc.isMemberClass()); assertFalse(hc.getSimpleName().isEmpty()); // sanity check } + + private static byte[] classBytes(String classname, int accessFlags) { + return classBytes(classname, Object.class, accessFlags); + } + + private static byte[] classBytes(String classname, Class<?> supertType, int accessFlags) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + cw.visit(V14, ACC_PUBLIC|accessFlags, classname, null, Type.getInternalName(supertType), null); + cw.visitEnd(); + + return cw.toByteArray(); + } }
< prev index next >