RFR: JDK-8225056 VM support for sealed classes

David Holmes david.holmes at oracle.com
Mon Jun 1 01:34:00 UTC 2020


Hi Harold,

On 1/06/2020 8:57 am, Harold Seigel wrote:
> Thanks for the comments.
> 
> Here's version 3 of the JDK and VM changes for sealed classes.
> 
> full webrev: 
> http://cr.openjdk.java.net/~hseigel/sealedClasses.8225056.3/webrev/
> 
> The new webrev contains just the following three changes:
> 
>  1. The sealed classes API's in Class.java (permittedSubclasses() and
>     isSealed()) were revised and, in particular, API
>     permittedSubclasses() no longer uses reflection.

For those following along we have presently abandoned the attempt to 
cache the array in ReflectionData.

Current changes look okay. But I note from the CSR there appears to be a 
further minor update to the javadoc coming.

>  2. An unneeded 'if' statement was removed from
>     JVM_GetPermittedSubclasses() (pointed out by David.)

Looks good.

>  3. VM runtime test files SealedUnnamedModuleIntfTest.java and
>     Permitted.java were changed to add a test case for a non-public
>     permitted subclass and its sealed superclass being in the same
>     module and package.

Looks good.

> Additionally, two follow on RFE's will be filed.  One to add additional 
> VM sealed classes tests 

Thanks. I think there is a more mechanical approach to testing here that 
will allow the complete matrix to be easily covered with minimal 
duplication between testing for named and unnamed modules.

> and one to improve the implementations of the 
> sealed classes API's in Class.java.

Thanks.

David
-----

> Thanks, Harold
> 
> On 5/28/2020 8:30 PM, David Holmes wrote:
>>
>> Hi Harold,
>>
>> Sorry Mandy's comment raised a couple of issues ...
>>
>> On 29/05/2020 7:12 am, Mandy Chung wrote:
>>> Hi Harold,
>>>
>>> On 5/27/20 1:35 PM, Harold Seigel wrote:
>>>>
>>>> Incremental webrev: 
>>>> http://cr.openjdk.java.net/~hseigel/sealedClasses.8225056.incr.2/
>>>>
>>>> full webrev: 
>>>> http://cr.openjdk.java.net/~hseigel/sealedClasses.8225056.2/webrev/
>>>>
>>> Class.java
>>>
>>> 4406 ReflectionData<T> rd = reflectionData();
>>> 4407 ClassDesc[] tmp = rd.permittedSubclasses;
>>> 4408 if (tmp != null) {
>>> 4409 return tmp;
>>> 4410 }
>>> 4411
>>> 4412 if (isArray() || isPrimitive()) {
>>> 4413 rd.permittedSubclasses = new ClassDesc[0];
>>> 4414 return rd.permittedSubclasses;
>>> 4415 }
>>>
>>> This causes an array class or primitive type to create a 
>>> ReflectionData.   It should first check if this is non-sealed class 
>>> and returns a constant empty array.
>>
>> It can't check if this is a non-sealed class as the isSealed() check 
>> calls the above code! But for arrays and primitives which can't be 
>> sealed we should just do:
>>
>> 4412 if (isArray() || isPrimitive()) {
>> 4413 return new ClassDesc[0];
>> 4414 }
>>
>> But this then made me realize that we need to be creating defensive 
>> copies of the returned arrays, as happens with other APIs that use 
>> ReflectionData.
>>
>> Backing up a bit I complained that:
>>
>> public boolean isSealed() {
>> return permittedSubclasses().length != 0;
>> }
>>
>> is a very inefficient way to answer the question as to whether a class 
>> is sealed, so I suggested that the result of permittedSubclasses() be 
>> cached. Caching is not without its own issues as we are discovering, 
>> and when you add in defensive copies this seems to be trading one 
>> inefficiency for another. For nestmates we don't cache 
>> getNestMembers() because we don;t think it will be called often - it 
>> is there to complete the introspection API of Class rather than being 
>> anticipated as used in a regular programmatic sense. I expect the same 
>> is true for permittedSubclasses(). Do we expect isSealed() to be used 
>> often or is it too just there for completeness? If just for 
>> completeness then perhaps a VM query would be a better compromise on 
>> the efficiency front? Otherwise I can accept the current 
>> implementation of isSealed(), and a non-caching permittedClasses() for 
>> this initial implementation of sealed classes. If efficiency turns out 
>> to be a problem for isSealed() then we can revisit it then.
>>
>> Thanks,
>> David
>>
>>
>>> In fact, ReflectionData caches the derived names and reflected 
>>> members for performance and also they may be invalidated when the 
>>> class is redefined.   It might be okay to add 
>>> ReflectionData::permittedSubclasses while `PermittedSubclasses` 
>>> attribute can't be redefined and getting this attribute is not 
>>> performance sensitive.   For example, the result of `getNestMembers` 
>>> is not cached in ReflectionData.  It may be better not to add it in 
>>> ReflectionData for modifiable and performance-sensitive data.
>>>
>>>
>>> 4421 tmp = new ClassDesc[subclassNames.length];
>>> 4422 int i = 0;
>>> 4423 for (String subclassName : subclassNames) {
>>> 4424 try {
>>> 4425 tmp[i++] = ClassDesc.of(subclassName.replace('/', '.'));
>>> 4426 } catch (IllegalArgumentException iae) {
>>> 4427 throw new InternalError("Invalid type in permitted subclasses 
>>> information: " + subclassName, iae);
>>> 4428 }
>>> 4429 }
>>> Nit: rename tmp to some other name e.g. descs
>>>
>>> I read the JVMS but it isn't clear to me that the VM will validate 
>>> the names in `PermittedSubclasses`attribute are valid class 
>>> descriptors.   I see ConstantPool::is_klass_or_reference check but 
>>> can't find where it validates the name is a valid class descriptor - 
>>> can you point me there?   (otherwise, maybe define it to be unspecified?)
>>>
>>>
>>> W.r.t. the APIs. I don't want to delay this review.  I see that you 
>>> renamed the method to new API style as I brought up.  OTOH,  I expect 
>>> more discussion is needed.  Below is a recent comment from John on 
>>> this topic [1]
>>>
>>>> One comment, really for the future, regarding the shape of the Java 
>>>> API here: It uses Optional and omits the "get" prefix on accessors. 
>>>> This is the New Style, as opposed to the Classic Style using null 
>>>> (for "empty" results) and a "get" prefix ("getComponentType") to get 
>>>> a related type. We may choose to to use the New Style for new 
>>>> reflection API points, and if so let's not choose N different New 
>>>> Styles, but one New Style. Personally I like removing "get"; I 
>>>> accept Optional instead of null; and I also suggest that arrays (if 
>>>> any) be replaced by (immutable) Lists in the New Style
>>>
>>> There are a few existing Class APIs that use the new API style:
>>> Class<?> arrayClass();
>>> Optional<ClassDesc> describeConstable();
>>> String descriptorString();
>>>
>>> This will set up a precedence of the new API style in this class.  
>>> Should this new permittedSubclasses method return a List instead of 
>>> an array?  It's okay with me if you prefer to revert back to the old 
>>> API style and follow this up after integration.
>>>
>>> 4442 * Returns true if this {@linkplain Class} is sealed.
>>> 4443 *
>>> 4444 * @return returns true if this class is sealed
>>>
>>> NIt: {@code true} instead of true -  consistent with the style this 
>>> class uses (in most methods)
>>>
>>> test/jdk/java/lang/reflect/sealed_classes/SealedClassesReflectionTest.java
>>>
>>> Nit: s/sealed_classes/sealedClasses/
>>> - the test directory/file naming convention use camel case or java 
>>> variable name convention.
>>>
>>> Thanks
>>> [1] https://github.com/openjdk/valhalla/pull/53#issuecomment-633116043


More information about the serviceability-dev mailing list