Core reflection setter type conversion spec bugs

XMLWordPrintable

    • Type: CSR
    • Resolution: Unresolved
    • Priority: P4
    • 26
    • Component/s: core-libs
    • None
    • behavioral
    • minimal
    • No behavioral change. Just better documentation about long-standing behavior.
    • Java API
    • SE

      Summary

      Update the specification for setter reflection methods in java.lang.reflect.Array and Field to clarify their conversion process and the exceptions.

      Problem

      The reflective setter methods in Array and Field perform only some conversions allowed by JLS 5.2 for assignment.

      In addition, the existing documentation has various shortcomings about the long-standing (unbox) -> identity/widening conversions and their exceptions.

      1. No mention that boxing is never done for primitive-typed setters
      2. Array.set missing description for the final identity or widening conversion (reference or primitive) and the associated IAE condition
      3. Class specification of Field mentions widening/narrowing conversion rules that is not sensible for get/set Object accessors
      4. Field.set misses the identity or widening in IAE clause (but mentioned in main body)
      5. Field primitive setters incorrectly claim they are equivalent to set(instance, wrapper) which is wrong due to lack of boxing conversion
      6. Field primitive setters refer to nonsense "unwrapping conversion"

      Solution

      1. Make sure the unbox -> identity/widen process and the IAE conditions are present in both Field and Array
      2. Add that boxing is absent for all primitive setters
      3. Update Field class spec to mention the narrowing/widening conversion limits are for primitive accessors only
      4. Fix the Field primitive setters' "unwrapping conversion" to be "identity or primitive widening conversion" as in Array primitive setters
      5. Qualify the Field primitive setters assertion with "if this field is of a primitive type" to make it correct

      Specification

      --- a/src/java.base/share/classes/java/lang/reflect/Array.java
      +++ b/src/java.base/share/classes/java/lang/reflect/Array.java
      @@ -31,9 +31,42 @@
        * The {@code Array} class provides static methods to dynamically create and
        * access Java arrays.
        *
      - * <p>{@code Array} permits widening conversions to occur during a get or set
      - * operation, but throws an {@code IllegalArgumentException} if a narrowing
      - * conversion would occur.
      + * <p id="value-conversion">
      + * {@code Array} declares {@link #get get} and {@link #set set} access
      + * operations with overloads for all primitive types.  They convert their
      + * retrieved and new values depending on whether the array's component type
      + * is primitive or reference:
      + * <ul>
      + * <li>If the array component type is a primitive type P:
      + *     <ul>
      + *     <li>{@link #get get} wraps the retrieved value in an instance of the
      + *         {@linkplain java.lang##wrapperClass wrapper class} of P.
      + *     <li>The get overload for primitive type O converts the retrieved value
      + *         from P to O via an identity conversion (JLS {@jls 5.1.1}) or a
      + *         widening primitive conversion (JLS {@jls 5.1.2}), or throws an {@code
      + *         IllegalArgumentException} if no such conversion exists.
      + *     <li>{@link #set set} requires the new value to be an instance of a
      + *         wrapper class, or throws an {@code IllegalArgumentException}. The
      + *         new value is unwrapped (JLS {@jls 5.1.8}) to a value of the wrapper
      + *         class's corresponding primitive type I, and then converted from I to
      + *         P via an identity conversion or a widening primitive conversion, or
      + *         throws an {@code IllegalArgumentException} if no such conversion
      + *         exists.
      + *     <li>The set overload for primitive type I converts the new value from
      + *         I to P via an identity conversion or a widening primitive conversion,
      + *         or throws an {@code IllegalArgumentException} if no such conversion
      + *         exists.
      + *     </ul>
      + * <li>If the array component type is a reference type R:
      + *     <ul>
      + *     <li>{@code get} performs no conversion.
      + *     <li>The primitive get overloads throw an {@code IllegalArgumentException}.
      + *     <li>{@code set} converts the new value to R as if through a narrowing
      + *         reference conversion (JLS {@jls 5.1.6.3}), but throws an {@code
      + *         IllegalArgumentException} instead of a {@code ClassCastException}.
      + *     <li>The primitive set overloads throw an {@code IllegalArgumentException}.
      + *     </ul>
      + * </ul>
        *
        * @author Nakul Saraiya
        * @since 1.1
      @@ -297,17 +330,17 @@ public static native double getDouble(Object array, int index)
       
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified new value.  The new value is first
      -     * automatically unwrapped if the array has a primitive component
      -     * type.
      +     * object to the specified new value after {@linkplain ##value-conversion
      +     * possible conversions}.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param value the new value of the indexed component
            * @throws    NullPointerException If the specified object argument
            * is null
            * @throws    IllegalArgumentException If the specified object argument
      -     * is not an array, or if the array component type is primitive and
      -     * an unwrapping conversion fails
      +     * is not an array, if the array component type is primitive and the
      +     * unboxing conversion fails, or if the identity or widening conversion fails
            * @throws    ArrayIndexOutOfBoundsException If the specified {@code index}
            * argument is negative, or if it is greater than or equal to
            * the length of the specified array
      diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java
      index e26d8b03ff8aa..87477e17d997a 100644
      --- a/src/java.base/share/classes/java/lang/reflect/Field.java
      +++ b/src/java.base/share/classes/java/lang/reflect/Field.java
      @@ -48,10 +48,42 @@
        * A {@code Field} provides information about, and dynamic access to, a
        * single field of a class or an interface.  The reflected field may
        * be a class (static) field or an instance field.
      - *
      - * <p>A {@code Field} permits widening conversions to occur during a get or
      - * set access operation, but throws an {@code IllegalArgumentException} if a
      - * narrowing conversion would occur.
      + * <p id="value-conversion">
      + * A {@code Field} declares {@link #get get} and {@link #set set} access
      + * operations with overloads for all primitive types.  They convert the
      + * retrieved and new values depending on whether the underlying field is of a
      + * primitive or a reference type:
      + * <ul>
      + * <li>If the underlying field is of a primitive type P:
      + *     <ul>
      + *     <li>{@link #get get} wraps the retrieved value in an instance of the
      + *         {@linkplain java.lang##wrapperClass wrapper class} of P.
      + *     <li>The get overload for primitive type O converts the retrieved value
      + *         from P to O via an identity conversion (JLS {@jls 5.1.1}) or a
      + *         widening primitive conversion (JLS {@jls 5.1.2}), or throws an {@code
      + *         IllegalArgumentException} if no such conversion exists.
      + *     <li>{@link #set set} requires the new value to be an instance of a
      + *         wrapper class, or throws an {@code IllegalArgumentException}. The
      + *         new value is unwrapped (JLS {@jls 5.1.8}) to a value of the wrapper
      + *         class's corresponding primitive type I, and then converted from I to
      + *         P via an identity conversion or a widening primitive conversion, or
      + *         throws an {@code IllegalArgumentException} if no such conversion
      + *         exists.
      + *     <li>The set overload for primitive type I converts the new value from
      + *         I to P via an identity conversion or a widening primitive conversion,
      + *         or throws an {@code IllegalArgumentException} if no such conversion
      + *         exists.
      + *     </ul>
      + * <li>If the underlying field is of a reference type R:
      + *     <ul>
      + *     <li>{@code get} performs no conversion.
      + *     <li>The primitive get overloads throw an {@code IllegalArgumentException}.
      + *     <li>{@code set} converts the new value to R as if through a narrowing
      + *         reference conversion (JLS {@jls 5.1.6.3}), but throws an {@code
      + *         IllegalArgumentException} instead of a {@code ClassCastException}.
      + *     <li>The primitive set overloads throw an {@code IllegalArgumentException}.
      + *     </ul>
      + * </ul>
        *
        * @see Member
        * @see java.lang.Class
      @@ -784,20 +816,13 @@ public double getDouble(Object obj)
            * in which other parts of a program continue to use the original
            * value of this field.
            *
      -     * <p>If the underlying field is of a primitive type, an unwrapping
      -     * conversion is attempted to convert the new value to a value of
      -     * a primitive type.  If this attempt fails, the method throws an
      -     * {@code IllegalArgumentException}.
      -     *
      -     * <p>If, after possible unwrapping, the new value cannot be
      -     * converted to the type of the underlying field by an identity or
      -     * widening conversion, the method throws an
      -     * {@code IllegalArgumentException}.
      +     * <p>The new value is {@linkplain ##value-conversion converted to be
      +     * compatible} with the type of the underlying field.
            *
            * <p>If the underlying field is static, the class that declared the
            * field is initialized if it has not already been initialized.
            *
      -     * <p>The field is set to the possibly unwrapped and widened new value.
      +     * <p>The field is set to the possibly converted new value.
            *
            * <p>If the field is hidden in the type of {@code obj},
            * the field's value is set according to the preceding rules.
      @@ -812,8 +837,9 @@ public double getDouble(Object obj)
            *              or if this {@code Field} object has no write access.
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
      -     *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              field (or a subclass or implementor thereof), if an
      +     *              unboxing conversion fails, or if the identity or widening
      +     *              conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -835,10 +861,9 @@ public void set(Object obj, Object value)
       
           /**
            * Sets the value of a field as a {@code boolean} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, zObj)},
      -     * where {@code zObj} is a {@code Boolean} object and
      -     * {@code zObj.booleanValue() == z}.
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, zObj)}, where {@code zObj} is a {@code Boolean} object
      +     * and {@code zObj.booleanValue() == z}.
            *
            * @param obj the object whose field should be modified
            * @param z   the new value for the field of {@code obj}
      @@ -851,7 +876,7 @@ public void set(Object obj, Object value)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -874,9 +899,8 @@ public void setBoolean(Object obj, boolean z)
       
           /**
            * Sets the value of a field as a {@code byte} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, bObj)},
      -     * where {@code bObj} is a {@code Byte} object and
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, bObj)}, where {@code bObj} is a {@code Byte} object and
            * {@code bObj.byteValue() == b}.
            *
            * @param obj the object whose field should be modified
      @@ -890,7 +914,7 @@ public void setBoolean(Object obj, boolean z)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -913,10 +937,9 @@ public void setByte(Object obj, byte b)
       
           /**
            * Sets the value of a field as a {@code char} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, cObj)},
      -     * where {@code cObj} is a {@code Character} object and
      -     * {@code cObj.charValue() == c}.
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, cObj)}, where {@code cObj} is a {@code Character} object
      +     * and {@code cObj.charValue() == c}.
            *
            * @param obj the object whose field should be modified
            * @param c   the new value for the field of {@code obj}
      @@ -929,7 +952,7 @@ public void setByte(Object obj, byte b)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -952,9 +975,8 @@ public void setChar(Object obj, char c)
       
           /**
            * Sets the value of a field as a {@code short} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, sObj)},
      -     * where {@code sObj} is a {@code Short} object and
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, sObj)}, where {@code sObj} is a {@code Short} object and
            * {@code sObj.shortValue() == s}.
            *
            * @param obj the object whose field should be modified
      @@ -968,7 +990,7 @@ public void setChar(Object obj, char c)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -991,10 +1013,9 @@ public void setShort(Object obj, short s)
       
           /**
            * Sets the value of a field as an {@code int} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, iObj)},
      -     * where {@code iObj} is an {@code Integer} object and
      -     * {@code iObj.intValue() == i}.
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, iObj)}, where {@code iObj} is an {@code Integer} object
      +     * and {@code iObj.intValue() == i}.
            *
            * @param obj the object whose field should be modified
            * @param i   the new value for the field of {@code obj}
      @@ -1007,7 +1028,7 @@ public void setShort(Object obj, short s)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -1030,9 +1051,8 @@ public void setInt(Object obj, int i)
       
           /**
            * Sets the value of a field as a {@code long} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, lObj)},
      -     * where {@code lObj} is a {@code Long} object and
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, lObj)}, where {@code lObj} is a {@code Long} object and
            * {@code lObj.longValue() == l}.
            *
            * @param obj the object whose field should be modified
      @@ -1046,7 +1066,7 @@ public void setInt(Object obj, int i)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -1069,9 +1089,8 @@ public void setLong(Object obj, long l)
       
           /**
            * Sets the value of a field as a {@code float} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, fObj)},
      -     * where {@code fObj} is a {@code Float} object and
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, fObj)}, where {@code fObj} is a {@code Float} object and
            * {@code fObj.floatValue() == f}.
            *
            * @param obj the object whose field should be modified
      @@ -1085,7 +1104,7 @@ public void setLong(Object obj, long l)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      @@ -1108,9 +1127,8 @@ public void setFloat(Object obj, float f)
       
           /**
            * Sets the value of a field as a {@code double} on the specified object.
      -     * This method is equivalent to
      -     * {@code set(obj, dObj)},
      -     * where {@code dObj} is a {@code Double} object and
      +     * If this field is of a primitive type, this method is equivalent to
      +     * {@code set(obj, dObj)}, where {@code dObj} is a {@code Double} object and
            * {@code dObj.doubleValue() == d}.
            *
            * @param obj the object whose field should be modified
      @@ -1124,7 +1142,7 @@ public void setFloat(Object obj, float f)
            * @throws    IllegalArgumentException  if the specified object is not an
            *              instance of the class or interface declaring the underlying
            *              field (or a subclass or implementor thereof),
      -     *              or if an unwrapping conversion fails.
      +     *              or if an identity or primitive widening conversion fails
            * @throws    NullPointerException      if the specified object is null
            *              and the field is an instance field.
            * @throws    ExceptionInInitializerError if the initialization provoked
      

            Assignee:
            Chen Liang
            Reporter:
            Nathanael Thompson (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: