/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java
523 строки
21 KB
David Simms
8389219: Implement JEP 401: Value Objects (Preview)
31 июл 2026, 03:45
31 июл 2026, 03:45
cc278db
Код
Авторство
О чём код?
/* * Copyright (c) 2019, 2026, 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 8330467 * @modules jdk.compiler * java.base/jdk.internal.misc * @library /test/lib * @compile BadClassFile.jcod * BadClassFile2.jcod * BadClassFileVersion.jcod * @build jdk.test.lib.Utils * jdk.test.lib.compiler.CompilerUtils * @run junit/othervm BasicTest */ import java.io.File; import java.io.IOException; import java.lang.classfile.ClassFile; import java.lang.constant.ClassDesc; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import jdk.internal.misc.PreviewFeatures; import jdk.test.lib.compiler.CompilerUtils; import jdk.test.lib.Utils; import static java.lang.classfile.ClassFile.*; import static java.lang.constant.ConstantDescs.CD_Enum; import static java.lang.constant.ConstantDescs.CD_Object; import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; interface HiddenTest { void test(); } public class BasicTest { private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src"); private static final Path CLASSES_DIR = Paths.get("classes"); private static final Path CLASSES_10_DIR = Paths.get("classes_10"); private static byte[] hiddenClassBytes; @BeforeAll 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"); } static void compileSources(Path sourceFile, Path dest, String... options) throws IOException { Stream<String> ops = Stream.of("-cp", Utils.TEST_CLASSES + File.pathSeparator + CLASSES_DIR); if (options != null && options.length > 0) { ops = Stream.concat(ops, Arrays.stream(options)); } if (!CompilerUtils.compile(sourceFile, dest, ops.toArray(String[]::new))) { throw new RuntimeException("Compilation of the test failed: " + sourceFile); } } static Class<?> defineHiddenClass(String name) throws Exception { byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); assertHiddenClass(hc); singletonNest(hc); return hc; } // basic test on a hidden class @Test public void hiddenClass() throws Throwable { HiddenTest t = (HiddenTest)defineHiddenClass("HiddenClass").newInstance(); t.test(); // sanity check Class<?> c = t.getClass(); Class<?>[] intfs = c.getInterfaces(); assertTrue(c.isHidden()); assertFalse(c.isPrimitive()); assertEquals(1, intfs.length); assertSame(HiddenTest.class, intfs[0]); assertNull(c.getCanonicalName()); String hcName = "HiddenClass"; String hcSuffix = "0x[0-9a-f]+"; assertTrue(c.getName().matches(hcName + "/" + hcSuffix)); assertTrue(c.descriptorString().matches("L" + hcName + "." + hcSuffix + ";"), c.descriptorString()); // test array of hidden class testHiddenArray(c); // 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()); assertEquals(2, Array.getLength(array)); assertFalse(arrayType.isHidden()); String hcName = "HiddenClass"; String hcSuffix = "0x[0-9a-f]+"; assertTrue(arrayType.getName().matches("\\[" + "L" + hcName + "/" + hcSuffix + ";")); assertTrue(arrayType.descriptorString().matches("\\[" + "L" + hcName + "." + hcSuffix + ";")); assertTrue(arrayType.getComponentType().isHidden()); assertSame(type, arrayType.getComponentType()); Object t = type.newInstance(); Array.set(array, 0, t); Object o = Array.get(array, 0); assertSame(t, o); } private void checkSetAccessible(Class<?> c, String name, Class<?>... ptypes) throws Exception { Method m = c.getDeclaredMethod(name, ptypes); assertTrue(m.trySetAccessible()); m.setAccessible(true); } // Define a hidden class that uses lambda // This verifies LambdaMetaFactory supports the caller which is a hidden class @Test public void testLambda() throws Throwable { HiddenTest t = (HiddenTest)defineHiddenClass("Lambda").newInstance(); var e = assertThrows(Error.class, t::test); assertEquals("thrown by " + t.getClass().getName(), e.getMessage()); } // Define a hidden class that uses lambda and contains its implementation // This verifies LambdaMetaFactory supports the caller which is a hidden class @Test public void testHiddenLambda() throws Throwable { HiddenTest t = (HiddenTest)defineHiddenClass("HiddenLambda").newInstance(); var e = assertThrows(Error.class, t::test); assertEquals("thrown by " + t.getClass().getName(), e.getMessage()); } // Verify the nest host and nest members of a hidden class and hidden nestmate class @Test public void testHiddenNestHost() throws Throwable { byte[] hc1 = hiddenClassBytes; Lookup lookup1 = lookup().defineHiddenClass(hc1, false); Class<?> host = lookup1.lookupClass(); byte[] hc2 = Files.readAllBytes(CLASSES_DIR.resolve("Lambda.class")); Lookup lookup2 = lookup1.defineHiddenClass(hc2, false, NESTMATE); Class<?> member = lookup2.lookupClass(); // test nest membership and reflection API assertTrue(host.isNestmateOf(member)); assertSame(host, host.getNestHost()); // getNestHost and getNestMembers return the same value when calling // on a nest member and the nest host assertSame(host.getNestHost(), member.getNestHost()); assertArrayEquals(member.getNestMembers(), host.getNestMembers()); // getNestMembers includes the nest host that can be a hidden class but // only includes static nest members assertEquals(1, host.getNestMembers().length); assertSame(host, host.getNestMembers()[0]); } private static 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. */ @ParameterizedTest @MethodSource("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; } assertSame(host, hc.getNestHost()); assertEquals(1, hc.getNestMembers().length); assertSame(host, hc.getNestMembers()[0]); } private static 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. */ @ParameterizedTest @MethodSource("emptyClasses") public void emptyHiddenClass(String name, int accessFlags) throws Exception { byte[] bytes = (accessFlags == ACC_ENUM) ? classBytes(name, CD_Enum, 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()); int expectedAccessFlags = ACC_PUBLIC | accessFlags; if ((accessFlags & ACC_INTERFACE) == 0 && PreviewFeatures.isEnabled()) { expectedAccessFlags |= ACC_IDENTITY; } assertEquals(hc.getModifiers(), expectedAccessFlags); assertFalse(hc.isLocalClass()); assertFalse(hc.isMemberClass()); assertFalse(hc.isAnonymousClass()); assertFalse(hc.isArray()); } // These class files can't be defined as hidden classes private static 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 */ @ParameterizedTest @MethodSource("cantBeHiddenClasses") public void failToDeriveAsHiddenClass(String name) throws Exception { byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); assertThrows(NoClassDefFoundError.class, () -> 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(); assertEquals(1, intfs.length); assertSame(HiddenTest.class, intfs[0]); var e = assertThrows(NoClassDefFoundError.class, c::getDeclaredMethods); Throwable x = e.getCause(); if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) { throw e; } } @Test public void cantDefineModule() throws Throwable { Path src = Paths.get("module-info.java"); Path dir = CLASSES_DIR.resolve("m"); Files.write(src, List.of("module m {}"), StandardCharsets.UTF_8); compileSources(src, dir); byte[] bytes = Files.readAllBytes(dir.resolve("module-info.class")); assertThrows(IllegalArgumentException.class, () -> lookup().defineHiddenClass(bytes, false)); } @Test public void cantDefineClassInAnotherPackage() throws Throwable { Path src = Paths.get("ClassInAnotherPackage.java"); Files.write(src, List.of("package p;", "public class ClassInAnotherPackage {}"), StandardCharsets.UTF_8); compileSources(src, CLASSES_DIR); byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("p").resolve("ClassInAnotherPackage.class")); assertThrows(IllegalArgumentException.class, () -> lookup().defineHiddenClass(bytes, false)); } @Test public void lessPrivilegedLookup() throws Throwable { Lookup lookup = lookup().dropLookupMode(Lookup.PRIVATE); assertThrows(IllegalAccessException.class, () -> lookup.defineHiddenClass(hiddenClassBytes, false)); } @Test public void badClassFileVersion() throws Throwable { Path dir = Paths.get(System.getProperty("test.classes", ".")); byte[] bytes = Files.readAllBytes(dir.resolve("BadClassFileVersion.class")); assertThrows(UnsupportedClassVersionError.class, () -> lookup().defineHiddenClass(bytes, false)); } // malformed class files private static Object[][] malformedClassFiles() throws IOException { Path dir = Paths.get(System.getProperty("test.classes", ".")); return new Object[][] { // `this_class` has invalid CP entry new Object[] { Files.readAllBytes(dir.resolve("BadClassFile.class")) }, new Object[] { Files.readAllBytes(dir.resolve("BadClassFile2.class")) }, // truncated file new Object[] { new byte[0] }, new Object[] { new byte[] {(byte) 0xCA, (byte) 0xBA, (byte) 0xBE, (byte) 0x00} }, }; } @ParameterizedTest @MethodSource("malformedClassFiles") public void badClassFile(byte[] bytes) throws Throwable { assertThrows(ClassFormatError.class, () -> lookup().defineHiddenClass(bytes, false)); } private static Object[][] nestedTypesOrAnonymousClass() { return new Object[][] { // class file with bad InnerClasses or EnclosingMethod attribute new Object[] { "Outer", null }, new Object[] { "Outer$Inner", "Outer" }, new Object[] { "EnclosingClass", null }, new Object[] { "EnclosingClass$1", "EnclosingClass" }, }; } @ParameterizedTest @MethodSource("nestedTypesOrAnonymousClass") public void hasInnerClassesOrEnclosingMethodAttribute(String className, String badDeclaringClassName) throws Throwable { byte[] bytes = Files.readAllBytes(CLASSES_10_DIR.resolve(className + ".class")); Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); hiddenClassWithBadAttribute(hc, badDeclaringClassName); } // define a hidden class with static nest membership @Test public void hasStaticNestHost() throws Exception { byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer$Inner.class")); Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); hiddenClassWithBadAttribute(hc, "Outer"); } @Test public void hasStaticNestMembers() throws Throwable { byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer.class")); Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); assertHiddenClass(hc); assertSame(hc, hc.getNestHost()); Class<?>[] members = hc.getNestMembers(); assertEquals(1, members.length); assertSame(hc, members[0]); } // a hidden class with bad InnerClasses or EnclosingMethod attribute private void hiddenClassWithBadAttribute(Class<?> hc, String badDeclaringClassName) { assertTrue(hc.isHidden()); assertNull(hc.getCanonicalName()); assertTrue(hc.getName().contains("/")); if (badDeclaringClassName == null) { // the following reflection API assumes a good name in InnerClasses // or EnclosingMethod attribute can successfully be resolved. assertFalse(hc.getSimpleName().isEmpty()); assertFalse(hc.isAnonymousClass()); assertFalse(hc.isLocalClass()); assertFalse(hc.isMemberClass()); } else { declaringClassNotFound(hc, badDeclaringClassName); } // validation of nest membership assertSame(hc, hc.getNestHost()); // validate the static nest membership Class<?>[] members = hc.getNestMembers(); assertEquals(1, members.length); assertSame(hc, members[0]); } // Class::getSimpleName, Class::isMemberClass private void declaringClassNotFound(Class<?> c, String cn) { var e = assertThrows(NoClassDefFoundError.class, c::isMemberClass); if (!e.getMessage().equals(cn)) { throw e; } e = assertThrows(NoClassDefFoundError.class, c::getSimpleName); if (!e.getMessage().equals(cn)) { throw e; } } private static void singletonNest(Class<?> hc) { assertSame(hc, hc.getNestHost()); assertEquals(1, hc.getNestMembers().length); assertSame(hc, hc.getNestMembers()[0]); } private static void assertHiddenClass(Class<?> hc) { assertTrue(hc.isHidden()); assertNull(hc.getCanonicalName()); 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, CD_Object, accessFlags); } private static byte[] classBytes(String classname, ClassDesc superType, int accessFlags) { return ClassFile.of().build(ClassDesc.ofInternalName(classname), clb -> clb .withVersion(JAVA_14_VERSION, 0) .withFlags(accessFlags | ACC_PUBLIC) .withSuperclass(superType)); } }