8256867: Classes with empty PermittedSubclasses attribute cannot be extended
Reviewed-by: lfoltan, mchung, jlahoda, chegar
This commit is contained in:
parent
e6b4c4d716
commit
d33a689b96
@ -3363,25 +3363,23 @@ u2 ClassFileParser::parse_classfile_permitted_subclasses_attribute(const ClassFi
|
||||
cfs->guarantee_more(2, CHECK_0); // length
|
||||
length = cfs->get_u2_fast();
|
||||
}
|
||||
if (length < 1) {
|
||||
classfile_parse_error("PermittedSubclasses attribute is empty in class file %s", THREAD);
|
||||
return 0;
|
||||
}
|
||||
const int size = length;
|
||||
Array<u2>* const permitted_subclasses = MetadataFactory::new_array<u2>(_loader_data, size, CHECK_0);
|
||||
_permitted_subclasses = permitted_subclasses;
|
||||
|
||||
int index = 0;
|
||||
cfs->guarantee_more(2 * length, CHECK_0);
|
||||
for (int n = 0; n < length; n++) {
|
||||
const u2 class_info_index = cfs->get_u2_fast();
|
||||
check_property(
|
||||
valid_klass_reference_at(class_info_index),
|
||||
"Permitted subclass class_info_index %u has bad constant type in class file %s",
|
||||
class_info_index, CHECK_0);
|
||||
permitted_subclasses->at_put(index++, class_info_index);
|
||||
if (length > 0) {
|
||||
int index = 0;
|
||||
cfs->guarantee_more(2 * length, CHECK_0);
|
||||
for (int n = 0; n < length; n++) {
|
||||
const u2 class_info_index = cfs->get_u2_fast();
|
||||
check_property(
|
||||
valid_klass_reference_at(class_info_index),
|
||||
"Permitted subclass class_info_index %u has bad constant type in class file %s",
|
||||
class_info_index, CHECK_0);
|
||||
permitted_subclasses->at_put(index++, class_info_index);
|
||||
}
|
||||
assert(index == size, "wrong size");
|
||||
}
|
||||
assert(index == size, "wrong size");
|
||||
|
||||
// Restore buffer's current position.
|
||||
cfs->set_current(current_mark);
|
||||
|
@ -741,8 +741,7 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) {
|
||||
|
||||
bool InstanceKlass::is_sealed() const {
|
||||
return _permitted_subclasses != NULL &&
|
||||
_permitted_subclasses != Universe::the_empty_short_array() &&
|
||||
_permitted_subclasses->length() > 0;
|
||||
_permitted_subclasses != Universe::the_empty_short_array();
|
||||
}
|
||||
|
||||
bool InstanceKlass::should_be_initialized() const {
|
||||
|
@ -2124,10 +2124,10 @@ JVM_ENTRY(jobjectArray, JVM_GetPermittedSubclasses(JNIEnv* env, jclass current))
|
||||
Klass* c = java_lang_Class::as_Klass(mirror);
|
||||
assert(c->is_instance_klass(), "must be");
|
||||
InstanceKlass* ik = InstanceKlass::cast(c);
|
||||
{
|
||||
if (ik->is_sealed()) {
|
||||
JvmtiVMObjectAllocEventCollector oam;
|
||||
Array<u2>* subclasses = ik->permitted_subclasses();
|
||||
int length = subclasses == NULL ? 0 : subclasses->length();
|
||||
int length = subclasses->length();
|
||||
objArrayOop r = oopFactory::new_objArray(SystemDictionary::Class_klass(),
|
||||
length, CHECK_NULL);
|
||||
objArrayHandle result(THREAD, r);
|
||||
@ -2156,6 +2156,8 @@ JVM_ENTRY(jobjectArray, JVM_GetPermittedSubclasses(JNIEnv* env, jclass current))
|
||||
return (jobjectArray)JNIHandles::make_local(THREAD, result2());
|
||||
}
|
||||
return (jobjectArray)JNIHandles::make_local(THREAD, result());
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
JVM_END
|
||||
|
@ -4390,10 +4390,13 @@ public final class Class<T> implements java.io.Serializable,
|
||||
*
|
||||
* Returns an array containing {@code Class} objects representing the
|
||||
* direct subinterfaces or subclasses permitted to extend or
|
||||
* implement this class or interface if it is sealed. The order of such elements
|
||||
* is unspecified. If this {@code Class} object represents a primitive type,
|
||||
* implement this class or interface if it is sealed. The order of such elements
|
||||
* is unspecified. The array is empty if this sealed class or interface has no
|
||||
* permitted subclass. If this {@code Class} object represents a primitive type,
|
||||
* {@code void}, an array type, or a class or interface that is not sealed,
|
||||
* an empty array is returned.
|
||||
* that is {@link #isSealed()} returns {@code false}, then this method returns {@code null}.
|
||||
* Conversely, if {@link #isSealed()} returns {@code true}, then this method
|
||||
* returns a non-null value.
|
||||
*
|
||||
* For each class or interface {@code C} which is recorded as a permitted
|
||||
* direct subinterface or subclass of this class or interface,
|
||||
@ -4406,7 +4409,8 @@ public final class Class<T> implements java.io.Serializable,
|
||||
* cannot be obtained, it is silently ignored, and not included in the result
|
||||
* array.
|
||||
*
|
||||
* @return an array of {@code Class} objects of the permitted subclasses of this class or interface
|
||||
* @return an array of {@code Class} objects of the permitted subclasses of this class or interface,
|
||||
* or {@code null} if this class or interface is not sealed.
|
||||
*
|
||||
* @throws SecurityException
|
||||
* If a security manager, <i>s</i>, is present and the caller's
|
||||
@ -4423,8 +4427,8 @@ public final class Class<T> implements java.io.Serializable,
|
||||
@CallerSensitive
|
||||
public Class<?>[] getPermittedSubclasses() {
|
||||
Class<?>[] subClasses;
|
||||
if (isArray() || isPrimitive() || (subClasses = getPermittedSubclasses0()).length == 0) {
|
||||
return EMPTY_CLASS_ARRAY;
|
||||
if (isArray() || isPrimitive() || (subClasses = getPermittedSubclasses0()) == null) {
|
||||
return null;
|
||||
}
|
||||
if (subClasses.length > 0) {
|
||||
if (Arrays.stream(subClasses).anyMatch(c -> !isDirectSubType(c))) {
|
||||
@ -4469,7 +4473,9 @@ public final class Class<T> implements java.io.Serializable,
|
||||
* Returns {@code true} if and only if this {@code Class} object represents
|
||||
* a sealed class or interface. If this {@code Class} object represents a
|
||||
* primitive type, {@code void}, or an array type, this method returns
|
||||
* {@code false}.
|
||||
* {@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 {@code Class} object represents a sealed class or interface.
|
||||
*
|
||||
@ -4483,7 +4489,7 @@ public final class Class<T> implements java.io.Serializable,
|
||||
if (isArray() || isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
return getPermittedSubclasses().length != 0;
|
||||
return getPermittedSubclasses() != null;
|
||||
}
|
||||
|
||||
private native Class<?>[] getPermittedSubclasses0();
|
||||
|
@ -164,7 +164,7 @@ class ExistingClassFile {
|
||||
} // end class ExistingClassFile
|
||||
|
||||
// This class contains an empty PermittedSubclasses attribute. Test that
|
||||
// this causes an exception to get thrown.
|
||||
// this does not cause an exception to get thrown.
|
||||
class NoSubclasses {
|
||||
0xCAFEBABE;
|
||||
65535; // minor version
|
||||
@ -223,17 +223,83 @@ class NoSubclasses {
|
||||
} // methods
|
||||
|
||||
[2] { // Attributes
|
||||
Attr(#11, 2) { // SourceFile at 0xD8
|
||||
#12;
|
||||
} // end SourceFile
|
||||
;
|
||||
Attr(#13, 2) { // PermittedSubclasses at 0xE0
|
||||
0x0000;
|
||||
} // end PermittedSubclasses
|
||||
;
|
||||
Attr(#11, 2) { // SourceFile at 0xD8
|
||||
#12;
|
||||
} // end SourceFile
|
||||
} // Attributes
|
||||
} // end class NoSubclasses
|
||||
|
||||
|
||||
// This class extends NoSubClasses to show that a class with an empty
|
||||
// PermittedSubtypes attribute cannot be subclass-ed.
|
||||
class SubClass {
|
||||
0xCAFEBABE;
|
||||
65535; // minor version
|
||||
60; // version
|
||||
[13] { // Constant Pool
|
||||
; // first element is empty
|
||||
Method #2 #3; // #1 at 0x0A
|
||||
class #4; // #2 at 0x0F
|
||||
NameAndType #5 #6; // #3 at 0x12
|
||||
Utf8 "NoSubclasses"; // #4 at 0x17
|
||||
Utf8 "<init>"; // #5 at 0x26
|
||||
Utf8 "()V"; // #6 at 0x2F
|
||||
class #8; // #7 at 0x35
|
||||
Utf8 "SubClass"; // #8 at 0x38
|
||||
Utf8 "Code"; // #9 at 0x43
|
||||
Utf8 "LineNumberTable"; // #10 at 0x4A
|
||||
Utf8 "SourceFile"; // #11 at 0x5C
|
||||
Utf8 "SubClass.java"; // #12 at 0x69
|
||||
} // Constant Pool
|
||||
|
||||
0x0021; // access [ ACC_PUBLIC ACC_SUPER ]
|
||||
#7;// this_cpx
|
||||
#2;// super_cpx
|
||||
|
||||
[0] { // Interfaces
|
||||
} // Interfaces
|
||||
|
||||
[0] { // Fields
|
||||
} // Fields
|
||||
|
||||
[1] { // Methods
|
||||
{ // method at 0x85
|
||||
0x0001; // access
|
||||
#5; // name_index : <init>
|
||||
#6; // descriptor_index : ()V
|
||||
[1] { // Attributes
|
||||
Attr(#9, 29) { // Code at 0x8D
|
||||
1; // max_stack
|
||||
1; // max_locals
|
||||
Bytes[5]{
|
||||
0x2AB70001B1;
|
||||
}
|
||||
[0] { // Traps
|
||||
} // end Traps
|
||||
[1] { // Attributes
|
||||
Attr(#10, 6) { // LineNumberTable at 0xA4
|
||||
[1] { // line_number_table
|
||||
0 1; // at 0xB0
|
||||
}
|
||||
} // end LineNumberTable
|
||||
} // Attributes
|
||||
} // end Code
|
||||
} // Attributes
|
||||
}
|
||||
} // Methods
|
||||
|
||||
[1] { // Attributes
|
||||
Attr(#11, 2) { // SourceFile at 0xB2
|
||||
#12;
|
||||
} // end SourceFile
|
||||
} // Attributes
|
||||
} // end class SubClass
|
||||
|
||||
|
||||
|
||||
// This class has a PermittedSubclasses attribute but an old class file version.
|
||||
// The PermittedSubclasses attribute should be ignored.
|
||||
|
@ -50,15 +50,16 @@ public class GetPermittedSubclassesTest {
|
||||
|
||||
final class Final4 {}
|
||||
|
||||
public static void testSealedInfo(Class<?> c, String[] expected) {
|
||||
public static void testSealedInfo(Class<?> c, String[] expected, boolean isSealed) {
|
||||
var permitted = c.getPermittedSubclasses();
|
||||
|
||||
if (permitted.length != expected.length) {
|
||||
throw new RuntimeException(
|
||||
"Unexpected number of permitted subclasses for: " + c.toString() + "(" + java.util.Arrays.asList(permitted));
|
||||
}
|
||||
if (isSealed) {
|
||||
if (permitted.length != expected.length) {
|
||||
throw new RuntimeException(
|
||||
"Unexpected number of permitted subclasses for: " + c.toString() +
|
||||
"(" + java.util.Arrays.asList(permitted));
|
||||
}
|
||||
|
||||
if (permitted.length > 0) {
|
||||
if (!c.isSealed()) {
|
||||
throw new RuntimeException("Expected sealed class: " + c.toString());
|
||||
}
|
||||
@ -83,63 +84,86 @@ public class GetPermittedSubclassesTest {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Must not be sealed if no permitted subclasses.
|
||||
if (c.isSealed()) {
|
||||
// Must not be sealed.
|
||||
if (c.isSealed() || permitted != null) {
|
||||
throw new RuntimeException("Unexpected sealed class: " + c.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void testBadSealedClass(String className, String expectedCFEMessage) throws Throwable {
|
||||
public static void testBadSealedClass(String className,
|
||||
Class<?> expectedException,
|
||||
String expectedCFEMessage) throws Throwable {
|
||||
try {
|
||||
Class.forName(className);
|
||||
throw new RuntimeException("Expected ClassFormatError exception not thrown for " + className);
|
||||
} catch (ClassFormatError cfe) {
|
||||
if (ClassFormatError.class != expectedException) {
|
||||
throw new RuntimeException(
|
||||
"Class " + className + " got unexpected exception: " + cfe.getMessage());
|
||||
}
|
||||
if (!cfe.getMessage().contains(expectedCFEMessage)) {
|
||||
throw new RuntimeException(
|
||||
"Class " + className + " got unexpected ClassFormatError exception: " + cfe.getMessage());
|
||||
}
|
||||
} catch (IncompatibleClassChangeError icce) {
|
||||
if (IncompatibleClassChangeError.class != expectedException) {
|
||||
throw new RuntimeException(
|
||||
"Class " + className + " got unexpected exception: " + icce.getMessage());
|
||||
}
|
||||
if (!icce.getMessage().contains(expectedCFEMessage)) {
|
||||
throw new RuntimeException(
|
||||
"Class " + className + " got unexpected IncompatibleClassChangeError exception: " + icce.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Throwable {
|
||||
testSealedInfo(SealedI1.class, new String[] {"GetPermittedSubclassesTest$NotSealed",
|
||||
"GetPermittedSubclassesTest$Sub1",
|
||||
"GetPermittedSubclassesTest$Extender"});
|
||||
"GetPermittedSubclassesTest$Extender"},
|
||||
true);
|
||||
|
||||
testSealedInfo(Sealed1.class, new String[] {"GetPermittedSubclassesTest$Sub1"});
|
||||
testSealedInfo(Final4.class, new String[] { });
|
||||
testSealedInfo(NotSealed.class, new String[] { });
|
||||
testSealedInfo(Sealed1.class, new String[] {"GetPermittedSubclassesTest$Sub1"}, true);
|
||||
testSealedInfo(Final4.class, null, false);
|
||||
testSealedInfo(NotSealed.class, null, false);
|
||||
|
||||
// Test class with PermittedSubclasses attribute but old class file version.
|
||||
testSealedInfo(OldClassFile.class, new String[] { });
|
||||
testSealedInfo(OldClassFile.class, null, false);
|
||||
|
||||
// Test class with an empty PermittedSubclasses attribute.
|
||||
testBadSealedClass("NoSubclasses", "PermittedSubclasses attribute is empty");
|
||||
testSealedInfo(NoSubclasses.class, new String[]{}, true);
|
||||
|
||||
// Test that a class with an empty PermittedSubclasses attribute cannot be subclass-ed.
|
||||
testBadSealedClass("SubClass", IncompatibleClassChangeError.class,
|
||||
"SubClass cannot inherit from sealed class NoSubclasses");
|
||||
|
||||
// Test returning only names of existing classes.
|
||||
testSealedInfo(NoLoadSubclasses.class, new String[]{"ExistingClassFile" });
|
||||
testSealedInfo(NoLoadSubclasses.class, new String[]{"ExistingClassFile" }, true);
|
||||
|
||||
// Test that loading a class with a corrupted PermittedSubclasses attribute
|
||||
// causes a ClassFormatError.
|
||||
testBadSealedClass("BadPermittedAttr",
|
||||
"Permitted subclass class_info_index 15 has bad constant type");
|
||||
testBadSealedClass("BadPermittedAttr", ClassFormatError.class,
|
||||
"Permitted subclass class_info_index 15 has bad constant type");
|
||||
|
||||
// Test that loading a sealed final class with a PermittedSubclasses
|
||||
// attribute causes a ClassFormatError.
|
||||
testBadSealedClass("SealedButFinal", "PermittedSubclasses attribute in final class");
|
||||
testBadSealedClass("SealedButFinal", ClassFormatError.class,
|
||||
"PermittedSubclasses attribute in final class");
|
||||
|
||||
// Test that loading a sealed class with a bad class name in its PermittedSubclasses
|
||||
// attribute causes a ClassFormatError.
|
||||
testBadSealedClass("BadPermittedSubclassEntry", "Illegal class name \"iDont;;Exist\" in class file");
|
||||
// Test that loading a sealed class with an ill-formed class name in its
|
||||
// PermittedSubclasses attribute causes a ClassFormatError.
|
||||
testBadSealedClass("BadPermittedSubclassEntry", ClassFormatError.class,
|
||||
"Illegal class name \"iDont;;Exist\" in class file");
|
||||
|
||||
// Test that loading a sealed class with an empty class name in its PermittedSubclasses
|
||||
// attribute causes a ClassFormatError.
|
||||
testBadSealedClass("EmptyPermittedSubclassEntry", "Illegal class name \"\" in class file");
|
||||
testBadSealedClass("EmptyPermittedSubclassEntry", ClassFormatError.class,
|
||||
"Illegal class name \"\" in class file");
|
||||
|
||||
//test type enumerated in the PermittedSubclasses attribute,
|
||||
//which are not direct subtypes of the current class are not returned:
|
||||
testSealedInfo(noSubclass.BaseC.class, new String[] {"noSubclass.ImplCIntermediate"});
|
||||
testSealedInfo(noSubclass.BaseI.class, new String[] {"noSubclass.ImplIIntermediateI", "noSubclass.ImplIIntermediateC"});
|
||||
testSealedInfo(noSubclass.BaseC.class, new String[] {"noSubclass.ImplCIntermediate"}, true);
|
||||
testSealedInfo(noSubclass.BaseI.class, new String[] {"noSubclass.ImplIIntermediateI", "noSubclass.ImplIIntermediateC"}, true);
|
||||
}
|
||||
}
|
||||
|
@ -110,8 +110,7 @@ public class SealedClassesReflectionTest {
|
||||
@Test(dataProvider = "notSealedClasses")
|
||||
public void testNotSealedClasses(Class<?> cls) {
|
||||
assertTrue(!cls.isSealed());
|
||||
assertTrue(cls.getPermittedSubclasses() != null);
|
||||
assertTrue(cls.getPermittedSubclasses().length == 0);
|
||||
assertTrue(cls.getPermittedSubclasses() == null);
|
||||
}
|
||||
|
||||
@DataProvider(name = "non_sealedClasses")
|
||||
@ -128,8 +127,7 @@ public class SealedClassesReflectionTest {
|
||||
assertTrue(!cls.isSealed());
|
||||
assertTrue(!Modifier.isFinal(cls.getModifiers()));
|
||||
assertTrue((cls.getSuperclass() != null && cls.getSuperclass().isSealed()) || Arrays.stream(cls.getInterfaces()).anyMatch(Class::isSealed));
|
||||
assertTrue(cls.getPermittedSubclasses() != null);
|
||||
assertTrue(cls.getPermittedSubclasses().length == 0);
|
||||
assertTrue(cls.getPermittedSubclasses() == null);
|
||||
}
|
||||
|
||||
@DataProvider(name = "reflectionData")
|
||||
|
@ -633,7 +633,7 @@ public class SealedCompilationTests extends CompilationTestCase {
|
||||
float.class, float[].class, double.class, double[].class, char.class, char[].class, boolean.class, boolean[].class, void.class,
|
||||
String[].class}) {
|
||||
Assert.check(!c.isSealed());
|
||||
Assert.check(c.getPermittedSubclasses().length == 0);
|
||||
Assert.check(c.getPermittedSubclasses() == null);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user