Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8370849

Core reflection setter type conversion spec bugs

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Unresolved
    • Icon: P4 P4
    • 26
    • 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.set
      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
      @@ -297,17 +297,20 @@ 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.  If the array has a primitive
      +     * component type, the new value is first converted to a value of a
      +     * primitive type by an unboxing conversion.  The possibly unboxed new
      +     * value is converted to the array's component type by an identity or
      +     * widening conversion and stored into the array.
      +     *
            * @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
      @@ -317,7 +320,9 @@ public static native void set(Object array, int index, Object value)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code boolean} value.
      +     * object to the specified {@code boolean} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param z the new value of the indexed component
      @@ -337,7 +342,9 @@ public static native void setBoolean(Object array, int index, boolean z)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code byte} value.
      +     * object to the specified {@code byte} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param b the new value of the indexed component
      @@ -357,7 +364,9 @@ public static native void setByte(Object array, int index, byte b)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code char} value.
      +     * object to the specified {@code char} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param c the new value of the indexed component
      @@ -377,7 +386,9 @@ public static native void setChar(Object array, int index, char c)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code short} value.
      +     * object to the specified {@code short} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param s the new value of the indexed component
      @@ -397,7 +408,9 @@ public static native void setShort(Object array, int index, short s)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code int} value.
      +     * object to the specified {@code int} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param i the new value of the indexed component
      @@ -417,7 +430,9 @@ public static native void setInt(Object array, int index, int i)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code long} value.
      +     * object to the specified {@code long} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param l the new value of the indexed component
      @@ -437,7 +452,9 @@ public static native void setLong(Object array, int index, long l)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code float} value.
      +     * object to the specified {@code float} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param f the new value of the indexed component
      @@ -457,7 +474,9 @@ public static native void setFloat(Object array, int index, float f)
      
           /**
            * Sets the value of the indexed component of the specified array
      -     * object to the specified {@code double} value.
      +     * object to the specified {@code double} value. This method never performs
      +     * a boxing conversion.
      +     *
            * @param array the array
            * @param index the index into the array
            * @param d the new value of the indexed component
      --- a/src/java.base/share/classes/java/lang/reflect/Field.java
      +++ b/src/java.base/share/classes/java/lang/reflect/Field.java
      @@ -49,9 +49,9 @@
        * 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>A {@code Field} permits widening primitive conversions to occur during a
      + * primitive get or set access operation, but throws an {@code IllegalArgumentException}
      + * if a narrowing primitive conversion would occur.
        *
        * @see Member
        * @see java.lang.Class
      @@ -812,8 +812,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 +836,10 @@ 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param z   the new value for the field of {@code obj}
      @@ -851,7 +852,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,10 +875,10 @@ 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
      -     * {@code bObj.byteValue() == b}.
      +     * 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param b   the new value for the field of {@code obj}
      @@ -890,7 +891,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 +914,10 @@ 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param c   the new value for the field of {@code obj}
      @@ -929,7 +930,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,10 +953,10 @@ 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
      -     * {@code sObj.shortValue() == s}.
      +     * 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param s   the new value for the field of {@code obj}
      @@ -968,7 +969,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 +992,10 @@ 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param i   the new value for the field of {@code obj}
      @@ -1007,7 +1008,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,10 +1031,10 @@ 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
      -     * {@code lObj.longValue() == l}.
      +     * 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param l   the new value for the field of {@code obj}
      @@ -1046,7 +1047,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,10 +1070,10 @@ 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
      -     * {@code fObj.floatValue() == f}.
      +     * 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param f   the new value for the field of {@code obj}
      @@ -1085,7 +1086,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,10 +1109,10 @@ 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
      -     * {@code dObj.doubleValue() == d}.
      +     * 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}. This method never performs a boxing
      +     * conversion.
            *
            * @param obj the object whose field should be modified
            * @param d   the new value for the field of {@code obj}
      @@ -1124,7 +1125,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

            liach Chen Liang
            nthompsosunw Nathanael Thompson (Inactive)
            Alan Bateman
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated: