Clarifying record reflective support

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue Dec 3 12:18:17 UTC 2019


Hi Chris,
I like your proposal; first, it makes isRecord/getRecordComponents in 
sync with other similar pair of methods like isArray/getComponentType. 
Secondly, it allows tighter tests to be written e.g. by the JCK team, 
which is, I think, where the value really is.

In your updated sped we say:

"Returns {@code true} if and only if this class is a record class."

Which is fine, but it doesn't define precisely how to establish as to 
whether a class _is_ a record class.

That is fine, I think, from a spec perspective, but I think that, moving 
forward, the implementation should be more deeply tested to make sure 
that the definition of what's a record and what's not remain stable over 
time. More specifically, while establishing as to whether a class 
extends j.l.Record is a straightforward binary check, ensuring that the 
record attribute is present leads to problems - because the attribute 
could be there, but parts of it might not be well-formed (e.g. one of 
the signature attribute point to an invalid CP entry). In such cases we 
need (at least) an internal agreement of what should happen - both at 
JVM classfile parse time _and_ at javac classfile parse time (although I 
realize these might implementation details).

In other words, I'm a bit concerned because, e.g. in enums we could 
piggy back on ACC_ENUM, which is another straightforward binary check. 
Here we're relying on a classfile attribute and, more specifically, on 
what happens when wrong bits of such attributes are discovered by the 
JVM; this in principle opens up issues where different VM 
implementations could have different recovery strategy, which might in 
turn lead to different answer to Class::isRecord.

The only way I see to tighten this up a bit is for the VM to reject a 
class whose record attribute contains wrong bits, rather than throw the 
attribute on the floor (like it happens with annotations).

Maurizio


On 03/12/2019 11:26, Chris Hegarty wrote:
> A number of issues and/or concerns have been come up recently relating
> to the reflective support for records. These arose when finalizing and
> completing the runtime CSR. Taken together they seem to lead back to a
> few small, but significant, omissions in the spec that would be good to
> tighten up. It is important that it be possible to reflectively reason
> about record classes in a way that is unambiguous and provides
> certainty that the record class is well-formed.
>
> For a class to be a record class, then:
>
>    1) It's direct superclass must be java.lang.Record, and
>    2) It must have a Record attribute.
>
> The entry point to the reflective API for records is the two methods:
> Class::isRecord and Class::getRecordComponents.
>
> The isRecord method should, in it's specification, guaranteed both of
> point 1 and point 2 above. That is to say, for a class to be considered
> a record class, then its direct superclass must be java.lang.Record, and
> it must have a Record attribute. The implementation already behaves
> this way, but the specification should require it.
>
> The getRecordComponents method currently returns an empty array for both
> a record class with no components and a non-record class. We thought it
> kinda nice to be able to avoid returning null, but with hindsight I
> think that it would be better to remove this potential ambiguity. The
> getRecordComponents method should only return a non-null value if both
> point 1 ( the class is a record class ) and point 2 above are true.
> There are many other null returning methods in Class, so this is not
> unusual or out of place. The implementation only requires a minor change
> to support this ( return null for non-record classes ).
>
> The most significant part of the changes proposed are to the
> specification, so I've included that here inline. The proposed changes
> tightly couple the pair of methods as part of their specification,
> something that will hopefully be cleaner to do (or even unnecessary)
> when we have full pattern matching.
>
>
> /src/java.base/share/classes/java/lang/Class.java
>
>       /**
>        * {@preview Associated with records, a preview feature of the Java language.
>        *
>        *           This method is associated with <i>records</i>, a preview
>        *           feature of the Java language. Preview features
>        *           may be removed in a future release, or upgraded to permanent
>        *           features of the Java language.}
>        *
>        * Returns {@code true} if and only if this class is a record class.
> -     * It returns {@code false} otherwise. Note that class {@link Record} is not a
> -     * record type and thus invoking this method on class {@link java.lang.Record}
> -     * returns {@code false}.
> -     *
> -     * @return true if and only if this class is a record class
> +     *
> +     * <p> The {@linkplain #getSuperclass() direct superclass} of a record
> +     * class is {@code java.lang.Record}. A record class has (possibly empty)
> +     * record components, that is, {@link #getRecordComponents()} returns a
> +     * non-null value.
> +     *
> +     * <p> Note that class {@link Record} is not a record type and thus invoking
> +     * this method on class {@code Record} returns {@code false}.
> +     *
> +     * @return true if and only if this class is a record class, otherwise false
>        * @jls 8.10 Record Types
>        * @since 14
>        */
>       public boolean isRecord() { ... }
>
>       /**
>        * {@preview Associated with records, a preview feature of the Java language.
>        *
>        *           This method is associated with <i>records</i>, a preview
>        *           feature of the Java language. Preview features
>        *           may be removed in a future release, or upgraded to permanent
>        *           features of the Java language.}
>        *
> -     * Returns an array containing {@code RecordComponent} objects reflecting all the
> -     * declared record components of the record represented by this {@code Class} object.
> -     * The components are returned in the same order that they are declared in the
> -     * record header.
> -     *
> -     * @return  The array of {@code RecordComponent} objects representing all the
> -     *          record components of this record. The array is empty if this class
> -     *          is not a record, or if this class is a record with no components.
> +     * Returns an array of {@code RecordComponent} objects representing all the
> +     * record components of this record class, or {@code null} if this class is
> +     * not a record class.
> +     *
> +     * <p> The components are returned in the same order that they are declared
> +     * in the record header. The array is empty if this record class has no
> +     * components. If the class is not a record class, that is {@link
> +     * #isRecord()} returns false, then this method returns null. Conversely, if
> +     * {@link #isRecord()} returns true, then this method returns a non-null
> +     * value.
> +     *
> +     * @return  An array of {@code RecordComponent} objects representing all the
> +     *          record components of this record class, or {@code null} if this
> +     *          class is not a record class
>        * @throws  SecurityException
>        *          ...
>        *
>        * @jls 8.10 Record Types
>        * @since 14
>        */
>       public RecordComponent[] getRecordComponents() { … }
>
>
> -Chris.
>


More information about the amber-spec-experts mailing list