706b421cca
Reviewed-by: psandoz, mchung
544 lines
22 KiB
Java
544 lines
22 KiB
Java
/*
|
|
* Copyright (c) 2019, 2024, 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
|
|
* @library /test/lib
|
|
* @enablePreview
|
|
* @comment Change enablePreview with the flag in setup's compileSources
|
|
* @compile BadClassFile.jcod
|
|
* BadClassFile2.jcod
|
|
* BadClassFileVersion.jcod
|
|
* @build jdk.test.lib.Utils
|
|
* jdk.test.lib.compiler.CompilerUtils
|
|
* @run testng/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.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 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.testng.Assert.*;
|
|
|
|
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;
|
|
|
|
@BeforeTest
|
|
static void setup() throws IOException {
|
|
compileSources(SRC_DIR, CLASSES_DIR, "--enable-preview",
|
|
"--release", Integer.toString(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");
|
|
}
|
|
|
|
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());
|
|
assertTrue(intfs.length == 1);
|
|
assertTrue(intfs[0] == HiddenTest.class);
|
|
assertTrue(c.getCanonicalName() == null);
|
|
|
|
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());
|
|
assertTrue(Array.getLength(array) == 2);
|
|
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());
|
|
assertTrue(arrayType.getComponentType() == type);
|
|
Object t = type.newInstance();
|
|
Array.set(array, 0, t);
|
|
Object o = Array.get(array, 0);
|
|
assertTrue(o == t);
|
|
}
|
|
|
|
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();
|
|
try {
|
|
t.test();
|
|
} catch (Error e) {
|
|
if (!e.getMessage().equals("thrown by " + t.getClass().getName())) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
try {
|
|
t.test();
|
|
} catch (Error e) {
|
|
if (!e.getMessage().equals("thrown by " + t.getClass().getName())) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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));
|
|
assertTrue(host.getNestHost() == host);
|
|
// getNestHost and getNestMembers return the same value when calling
|
|
// on a nest member and the nest host
|
|
assertTrue(member.getNestHost() == host.getNestHost());
|
|
assertTrue(Arrays.equals(member.getNestMembers(), host.getNestMembers()));
|
|
// getNestMembers includes the nest host that can be a hidden class but
|
|
// 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;
|
|
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, 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());
|
|
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");
|
|
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"));
|
|
lookup().defineHiddenClass(bytes, false);
|
|
}
|
|
|
|
@Test(expectedExceptions = { IllegalArgumentException.class })
|
|
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"));
|
|
lookup().defineHiddenClass(bytes, false);
|
|
}
|
|
|
|
@Test(expectedExceptions = { IllegalAccessException.class })
|
|
public void lessPrivilegedLookup() throws Throwable {
|
|
Lookup lookup = lookup().dropLookupMode(Lookup.PRIVATE);
|
|
lookup.defineHiddenClass(hiddenClassBytes, false);
|
|
}
|
|
|
|
@Test(expectedExceptions = { UnsupportedClassVersionError.class })
|
|
public void badClassFileVersion() throws Throwable {
|
|
Path dir = Paths.get(System.getProperty("test.classes", "."));
|
|
byte[] bytes = Files.readAllBytes(dir.resolve("BadClassFileVersion.class"));
|
|
lookup().defineHiddenClass(bytes, false);
|
|
}
|
|
|
|
// malformed class files
|
|
@DataProvider(name = "malformedClassFiles")
|
|
private 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} },
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "malformedClassFiles", expectedExceptions = ClassFormatError.class)
|
|
public void badClassFile(byte[] bytes) throws Throwable {
|
|
lookup().defineHiddenClass(bytes, false);
|
|
}
|
|
|
|
@DataProvider(name = "nestedTypesOrAnonymousClass")
|
|
private 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" },
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "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);
|
|
assertTrue(hc.getNestHost() == hc);
|
|
Class<?>[] members = hc.getNestMembers();
|
|
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
|
|
// or EnclosingMethod attribute can successfully be resolved.
|
|
assertTrue(hc.getSimpleName().length() > 0);
|
|
assertFalse(hc.isAnonymousClass());
|
|
assertFalse(hc.isLocalClass());
|
|
assertFalse(hc.isMemberClass());
|
|
} else {
|
|
declaringClassNotFound(hc, badDeclaringClassName);
|
|
}
|
|
|
|
// validation of nest membership
|
|
assertTrue(hc.getNestHost() == hc);
|
|
// validate the static nest membership
|
|
Class<?>[] members = hc.getNestMembers();
|
|
assertTrue(members.length == 1 && members[0] == hc);
|
|
}
|
|
|
|
// Class::getSimpleName, Class::isMemberClass
|
|
private void declaringClassNotFound(Class<?> c, String cn) {
|
|
try {
|
|
// fail to find declaring/enclosing class
|
|
c.isMemberClass();
|
|
assertTrue(false);
|
|
} catch (NoClassDefFoundError e) {
|
|
if (!e.getMessage().equals(cn)) {
|
|
throw e;
|
|
}
|
|
}
|
|
try {
|
|
// fail to find declaring/enclosing class
|
|
c.getSimpleName();
|
|
assertTrue(false);
|
|
} catch (NoClassDefFoundError e) {
|
|
if (!e.getMessage().equals(cn)) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void singletonNest(Class<?> hc) {
|
|
assertTrue(hc.getNestHost() == hc);
|
|
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, 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));
|
|
}
|
|
}
|