8256867: Classes with empty PermittedSubclasses attribute cannot be extended

Reviewed-by: lfoltan, mchung, jlahoda, chegar
This commit is contained in:
Harold Seigel 2020-12-09 19:07:11 +00:00
parent e6b4c4d716
commit d33a689b96
8 changed files with 154 additions and 61 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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

View File

@ -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();

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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")

View File

@ -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);
}
}