JDK-8135248 : Add utility methods to check indexes and ranges
  • Type: Enhancement
  • Component: core-libs
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2015-09-09
  • Updated: 2019-07-17
  • Resolved: 2015-10-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9 Other
9 b86Fixed openjdk7uFixed
Related Reports
Blocks :  
Blocks :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
There are numerous methods in the JDK that check if an index or an absolute/relative range is valid before accessing the contents of an array (or in general a memory region for the case of a direct java.nio.ByteBuffer).

For use-cases see the classes java.lang.String, java.lang.AbstractStringBuilder, java.util.Arrays, java.util.Spliterators, and java.nio.Buffer.
  
Such checks, while not difficult, are often easy to get wrong and optimize correctly, thus there is a risk to the integrity and security of the runtime. It is desirable to locate such functionality in one place, methods in java.util.Arrays, rather than duplicate it in many places.

Three check methods have been identified:

1) Checking whether an index is within bounds

  public static int check(int index, int length) { 
    if (length < 0 || index < 0 || index >= length) throw ...
    return index; 
  } 
  
2) Checking whether an absolute range is within bounds:

  public static int checkStartEndRange(int start, int end, int length) { 
    if (start < 0 || start > end || end > length) throw ... 
    return start; 
  } 

3) Checking whether a relative range is within bounds:

  public static int checkOffsetSizeRange(int offset, int size, int length) { 
    if ((length | offset | size) < 0 || size > length - offset) throw ... 
    return offset; 
  } 

An additional argument to the methods will be required that accepts a functional interface that in turn accepts or captures the arguments and returns an exception, likely extending from IndexOutOfBoundsException, with an appropriate exception message. 

It's possible to model all existing exception reporting use-cases with:

  Supplier<RuntimeException>

However, this will require that the caller capture the argument parameters if such values are to determine the exception type (such as IllegalArgumentException and ArrayIndexOutOfBoundsException, see java.util.Arrays) and exception messages.  

The cost of capture, which requires an allocation-per-call, may be considered too high especially if performed in a loop. Such costs might be mitigated with escape analysis, but the conditions in which it can be reliably applied might be considered too fragile.

A second alternative is to leverage:

  BiFunction<Integer, Integer, RuntimeException>

to support two out of three possible arguments.  Many existing use-cases can be supported (although the existing use-case of java.lang.AbstractStringBuilder cannot be supported and capturing the unreported argument would be required or an exception message would require changing).

A third alternative is to define a custom functional interface e.g.:

  interface RangeExceptionFunction {
    RuntimeException apply(long indexStartOrOffset, long endSizeOrLength, long length)
  }

The apply method declares long parameter types as this interface is reusable for any future check methods that accept long values.

For generic interfaces the runtime boxing cost is not considered an issue as such boxing will occur on the exception throwing path. However, the increased size of the method due to boxing conversion calls (invokestatic of Integer.valueOf per argument) and the cast of the return value (checkcast RuntimeException) might contribute to poor inlining effects. Note that a specific method declared with long parameter types also have an increased cost when called with int values due to conversion (i2l).
 
A further desire for such methods is some or all can be made intrinsic (see JDK-8042997), thus hinting to the HotSpot runtime compiler to use unsigned comparisons and better optimize array access (via aaload/store or Unsafe) in loops (especially those that are unrolled).
Comments
http://cr.openjdk.java.net/~psandoz/jdk9/JDK-8135248-ioobe-check-index-range/webrev/
25-09-2015