changeset 59598:40500443661b nestmates

[nestmates] add more test cases for hidden classes
author mchung
date Tue, 31 Mar 2020 11:34:17 -0700
parents d30b62c68646
children fb5c34c883b5
files test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenAnnotation.java test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenEnum.java test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenRecord.java
diffstat 4 files changed, 243 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java	Tue Mar 31 16:43:28 2020 +0000
+++ b/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java	Tue Mar 31 11:34:17 2020 -0700
@@ -23,12 +23,12 @@
 
 /*
  * @test
- * @modules jdk.compiler
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ *          jdk.compiler
  * @library /test/lib
  * @build jdk.test.lib.Utils
  *        jdk.test.lib.compiler.CompilerUtils
- *        BasicTest
- * @run testng/othervm BasicTest
+ * @run testng/othervm --enable-preview BasicTest
  */
 
 import java.io.File;
@@ -48,12 +48,17 @@
 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 jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
 import static org.testng.Assert.*;
 
 interface HiddenTest {
@@ -70,8 +75,8 @@
 
     @BeforeTest
     static void setup() throws IOException {
-        compileSources(SRC_DIR, CLASSES_DIR);
-
+        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
@@ -121,6 +126,7 @@
         checkSetAccessible(c, "test");
     }
 
+    // primitive class is not a hidden class
     @Test
     public void primitiveClass() {
         assertFalse(int.class.isHiddenClass());
@@ -188,6 +194,143 @@
         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;
+        if (nestmate) {
+            hc = lookup().defineHiddenClass(bytes, false, NESTMATE).lookupClass();
+            host = lookup().lookupClass().getNestHost();
+        } else {
+            hc = lookup().defineHiddenClass(bytes, false).lookupClass();
+            host = hc;
+        }
+        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.isHiddenClass());
+        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();
@@ -210,43 +353,6 @@
         }
     }
 
-    @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;
-        if (nestmate) {
-            hc = lookup().defineHiddenClass(bytes, false, NESTMATE).lookupClass();
-            host = lookup().lookupClass().getNestHost();
-        } else {
-            hc = lookup().defineHiddenClass(bytes, false).lookupClass();
-            host = hc;
-        }
-        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");
@@ -371,4 +477,16 @@
         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();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenAnnotation.java	Tue Mar 31 11:34:17 2020 -0700
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface HiddenAnnotation {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenEnum.java	Tue Mar 31 11:34:17 2020 -0700
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+// fail to create HiddenEnum as a hidden class as it fails verification
+// <clinit> initializes the static final HiddenEnum[] $VALUES field
+public enum HiddenEnum {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/HiddenRecord.java	Tue Mar 31 11:34:17 2020 -0700
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+// fail to create HiddenRecord as a hidden class
+// verification fails in the BSM to invoke equals(HiddenRecord, Object) method
+record HiddenRecord(int i) { }