diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index c30ec9f8f8b..59c5da9d81c 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -165,6 +165,8 @@ unchecked_nonstatic_field(Array, _data, sizeof(u2)) \ nonstatic_field(Array, _length, int) \ nonstatic_field(Array, _data[0], Klass*) \ + unchecked_nonstatic_field(Array, _data, sizeof(jushort)) \ + nonstatic_field(Array, _length, int) \ \ volatile_nonstatic_field(BasicLock, _metadata, uintptr_t) \ \ @@ -221,6 +223,7 @@ volatile_nonstatic_field(InstanceKlass, _init_thread, JavaThread*) \ nonstatic_field(InstanceKlass, _misc_flags._flags, u2) \ nonstatic_field(InstanceKlass, _annotations, Annotations*) \ + nonstatic_field(InstanceKlass, _permitted_subclasses, Array*) \ \ volatile_nonstatic_field(JavaFrameAnchor, _last_Java_sp, intptr_t*) \ volatile_nonstatic_field(JavaFrameAnchor, _last_Java_pc, address) \ diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java index 3ef4d30e304..67fcf1e027f 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java @@ -34,6 +34,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.ByteOrder; +import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -100,7 +102,7 @@ static HotSpotResolvedObjectTypeImpl getJavaLangObject() { /** * Gets the JVMCI mirror from a HotSpot type. - * + *

* Called from the VM. * * @param klassPointer a native pointer to the Klass* @@ -183,6 +185,67 @@ public ResolvedJavaType getComponentType() { return this.equals(componentType) ? null : componentType; } + @Override + public List getPermittedSubclasses() { + if (isArray() || isPrimitive()) { + return null; + } + HotSpotVMConfig config = config(); + final long metaspacePermittedSubclasses = UNSAFE.getAddress(getKlassPointer() + config.instanceKlassPermittedSubclassesOffset); + if (metaspacePermittedSubclasses == 0) { + return null; + } + final int length = UNSAFE.getInt(metaspacePermittedSubclasses + config.arrayJUShortLengthOffset); + if (length == 0) { + return null; + } + // is_sealed + ArrayList permittedSubclasses = new ArrayList<>(length); + for (long i = 0; i < length; i++) { + int cpIndex = UNSAFE.getShort(metaspacePermittedSubclasses + config.arrayJUShortDataOffset + compilerToVM().ARRAY_SHORT_INDEX_SCALE * i); + Object cpEntry = getConstantPool().lookupConstant(cpIndex); + if (cpEntry instanceof ResolvedJavaType rjt) { + if (isDirectSubType(rjt)) { + // only adding direct subtypes + permittedSubclasses.add(rjt); + } + } else if (cpEntry instanceof UnresolvedJavaType ujr) { + // Unresolved - cannot tell if it is a direct or indirect subtype + permittedSubclasses.add(ujr); + } else { + throw new InternalError("Unexpected ConstantPool entry: " + cpEntry); + } + } + return Collections.unmodifiableList(permittedSubclasses); + } + + @Override + public boolean isSealed() { + if (isArray()) { + return false; + } + HotSpotVMConfig config = config(); + final long metaspacePermittedSubclasses = UNSAFE.getAddress(getKlassPointer() + config.instanceKlassPermittedSubclassesOffset); + if (metaspacePermittedSubclasses == 0) { + return false; + } + final int length = UNSAFE.getInt(metaspacePermittedSubclasses + config.arrayJUShortLengthOffset); + return length != 0; + } + + private boolean isDirectSubType(ResolvedJavaType c) { + if (isInterface()) { + for (ResolvedJavaType i : c.getInterfaces()) { + if (i == this) { + return true; + } + } + } else { + return c.getSuperclass() == this; + } + return false; + } + @Override public AssumptionResult findLeafConcreteSubtype() { if (isLeaf()) { @@ -694,11 +757,11 @@ static class FieldInfo { /** * Creates a field info with the provided indices. * - * @param nameIndex index of field's name in the constant pool - * @param signatureIndex index of field's signature in the constant pool - * @param offset field's offset - * @param classfileFlags field's access flags (from the class file) - * @param internalFlags field's internal flags (from the VM) + * @param nameIndex index of field's name in the constant pool + * @param signatureIndex index of field's signature in the constant pool + * @param offset field's offset + * @param classfileFlags field's access flags (from the class file) + * @param internalFlags field's internal flags (from the VM) * @param initializerIndex field's initial value index in the constant pool */ FieldInfo(int nameIndex, int signatureIndex, int offset, int classfileFlags, int internalFlags, int initializerIndex) { @@ -737,6 +800,7 @@ public int getOffset() { /** * Returns the name of this field as a {@link String}. If the field is an internal field the * name index is pointing into the vmSymbols table. + * * @param klass field's holder class */ public String getName(HotSpotResolvedObjectTypeImpl klass) { @@ -746,6 +810,7 @@ public String getName(HotSpotResolvedObjectTypeImpl klass) { /** * Returns the signature of this field as {@link String}. If the field is an internal field * the signature index is pointing into the vmSymbols table. + * * @param klass field's holder class */ public String getSignature(HotSpotResolvedObjectTypeImpl klass) { @@ -828,7 +893,7 @@ public ResolvedJavaField[] getStaticFields() { * Gets the instance or static fields of this class. * * @param retrieveStaticFields specifies whether to return instance or static fields - * @param prepend an array to be prepended to the returned result + * @param prepend an array to be prepended to the returned result */ private HotSpotResolvedJavaField[] getFields(boolean retrieveStaticFields, HotSpotResolvedJavaField[] prepend) { HotSpotVMConfig config = config(); @@ -954,7 +1019,7 @@ public boolean isDefinitelyResolvedWithRespectTo(ResolvedJavaType accessingClass private boolean hasSameClassLoader(HotSpotResolvedObjectTypeImpl otherMirror) { return UnsafeAccess.UNSAFE.getAddress(getKlassPointer() + config().classLoaderDataOffset) == UnsafeAccess.UNSAFE.getAddress( - otherMirror.getKlassPointer() + config().classLoaderDataOffset); + otherMirror.getKlassPointer() + config().classLoaderDataOffset); } @Override diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java index 7dfdb96f290..fac3caf8160 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java @@ -102,6 +102,16 @@ public ResolvedJavaType getElementalType() { return this; } + @Override + public List getPermittedSubclasses() { + return null; + } + + @Override + public boolean isSealed() { + return false; + } + @Override public ResolvedJavaType getComponentType() { return null; diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java index bc2e121fe90..b83981a6a74 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java @@ -26,7 +26,6 @@ import static jdk.vm.ci.hotspot.UnsafeAccess.UNSAFE; import jdk.vm.ci.common.JVMCIError; -import jdk.vm.ci.services.Services; import jdk.internal.misc.Unsafe; import jdk.internal.util.Architecture; @@ -97,6 +96,7 @@ static String getHostArchitectureName() { final int instanceKlassConstantsOffset = getFieldOffset("InstanceKlass::_constants", Integer.class, "ConstantPool*"); final int instanceKlassFieldInfoStreamOffset = getFieldOffset("InstanceKlass::_fieldinfo_stream", Integer.class, "Array*"); final int instanceKlassAnnotationsOffset = getFieldOffset("InstanceKlass::_annotations", Integer.class, "Annotations*"); + final int instanceKlassPermittedSubclassesOffset = getFieldOffset("InstanceKlass::_permitted_subclasses", Integer.class, "Array*"); final int instanceKlassMiscFlagsOffset = getFieldOffset("InstanceKlass::_misc_flags._flags", Integer.class, "u2"); final int klassMiscFlagsOffset = getFieldOffset("Klass::_misc_flags._flags", Integer.class, "u1"); final int klassVtableStartOffset = getFieldValue("CompilerToVM::Data::Klass_vtable_start_offset", Integer.class, "int"); @@ -113,6 +113,8 @@ static String getHostArchitectureName() { final int arrayU1LengthOffset = getFieldOffset("Array::_length", Integer.class, "int"); final int arrayU1DataOffset = getFieldOffset("Array::_data", Integer.class); final int arrayU2DataOffset = getFieldOffset("Array::_data", Integer.class); + final int arrayJUShortDataOffset = getFieldOffset("Array::_data", Integer.class); + final int arrayJUShortLengthOffset = getFieldOffset("Array::_length", Integer.class, "int"); final int jvmAccHasFinalizer = getConstant("KlassFlags::_misc_has_finalizer", Integer.class); final int jvmFieldFlagInternalShift = getConstant("FieldInfo::FieldFlags::_ff_injected", Integer.class); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java index cfe455f1782..c951d43b3de 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java @@ -247,6 +247,42 @@ default ResolvedJavaType getElementalType() { @Override ResolvedJavaType getArrayClass(); + /** + * Returns an unmodifiable list of {@link JavaType} objects representing the subclasses or + * subinterfaces that are explicitly permitted to extend or implement this sealed class or + * interface, as declared in its PermittedSubclasses class file attribute. Only direct + * subtypes listed by the sealing declaration are included. + *

+ * For each subtype, if it is resolved, the entry will be a {@link ResolvedJavaType}; otherwise + * it will be an {@link UnresolvedJavaType}. For unresolved subtypes, callers must not assume + * whether a type is a direct or indirect permitted subtype. The order of the results matches + * that of {@link Class#getPermittedSubclasses()}. + *

+ * If the type is not sealed, returns {@code null}. + * + * @return unmodifiable list of permitted subtypes, or {@code null} if this type is not sealed + * @see Class#getPermittedSubclasses() + */ + List getPermittedSubclasses(); + + /** + * Returns {@code true} if and only if this type represents a sealed class or + * interface. If this type represents a primitive type, {@code void}, or an array + * type, this method returns {@code false}. A sealed class or interface has + * (possibly zero) permitted subclasses; {@link #getPermittedSubclasses()} returns + * a non-null but possibly empty value for a sealed class or interface. + * + * @return {@code true} if and only if this type represents a sealed class + * or interface. + * @see Class#isSealed() + */ + default boolean isSealed() { + if (isArray() || isPrimitive()) { + return false; + } + return getPermittedSubclasses() != null; + } + /** * Resolves the method implementation for virtual dispatches on objects of this dynamic type. * This resolution process only searches "up" the class hierarchy of this type. A broader search diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java index 615dd3713e5..f67832407b8 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java @@ -59,6 +59,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.DataInputStream; import java.io.IOException; @@ -708,6 +709,48 @@ public void getArrayClassTest() { } } + private static sealed class SealedTestClass + permits PermittedTestClass1, PermittedTestClass3 { + } + + private static sealed class PermittedTestClass1 extends SealedTestClass + permits PermittedTestClass2 { + } + + private static final class PermittedTestClass2 extends PermittedTestClass1 { + } + + private static final class PermittedTestClass3 extends SealedTestClass { + } + + private static class UnsealedTestClass { + } + + private static final class UnsealedTestSubClass extends UnsealedTestClass { + } + + @Test + public void getPermittedSubclassesTest() { + assertGetPermittedSubclasses(int.class); + assertGetPermittedSubclasses(void.class); + assertGetPermittedSubclasses(UnsealedTestClass.class); + assertGetPermittedSubclasses(SealedTestClass.class); + } + + private void assertGetPermittedSubclasses(Class clazz) { + ResolvedJavaType type = metaAccess.lookupJavaType(clazz); + assertEquals("Sealed status mismatch for class '" + clazz.getName(), clazz.isSealed(), type.isSealed()); + List actual = type.getPermittedSubclasses(); + Class[] expected = clazz.getPermittedSubclasses(); + if (expected == null) { + assertNull(actual); + } else { + Set actualSet = new HashSet<>(actual); + Set expectedSet = Arrays.stream(expected).map(metaAccess::lookupJavaType).collect(Collectors.toSet()); + assertEquals(expectedSet, actualSet); + } + } + static class Declarations { final Method implementation; @@ -1313,6 +1356,7 @@ public void getAnnotationDataTest() throws Exception { "getElementalType", "getEnclosingType", "lookupType", + "isSealed", // tested with getPermittedSubsclasses "resolveField", "$jacocoInit" };