< 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 >