changeset 13609:0da125166b2b jdk8u222-b09

8179098: Crypto AES/ECB encryption/decryption performance regression (introduced in jdk9b73) 8135248: Add utility methods to check indexes and ranges 8142493: Utility methods to check indexes and ranges doesn't specify behavior when function produces null 8146458: Improve exception reporting for Objects.checkIndex/checkFromToIndex/checkFromIndexSize 8155794: Move Objects.checkIndex BiFunction accepting methods to an internal package Summary: Do bounds check per encryption/decryption call instead of per block Reviewed-by: mbalao
author andrew
date Sat, 06 Jul 2019 14:35:27 +0100
parents bdf644065d87
children 384c9ca971ec
files src/share/classes/com/sun/crypto/provider/AESCrypt.java src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java src/share/classes/com/sun/crypto/provider/CipherFeedback.java src/share/classes/com/sun/crypto/provider/CounterMode.java src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java src/share/classes/com/sun/crypto/provider/OutputFeedback.java src/share/classes/com/sun/crypto/provider/PCBC.java src/share/classes/jdk/internal/util/Preconditions.java src/share/classes/sun/security/util/ArrayUtil.java test/java/util/Objects/CheckIndex.java
diffstat 11 files changed, 750 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/crypto/provider/AESCrypt.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/AESCrypt.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -38,7 +38,6 @@
 
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
-import java.util.Objects;
 
 /**
  * Rijndael --pronounced Reindaal-- is a symmetric cipher with a 128-bit
@@ -348,8 +347,8 @@
      */
     void encryptBlock(byte[] in, int inOffset,
                       byte[] out, int outOffset) {
-        cryptBlockCheck(in, inOffset);
-        cryptBlockCheck(out, outOffset);
+        // Array bound checks are done in caller code, i.e.
+        // FeedbackCipher.encrypt/decrypt(...) to improve performance.
         implEncryptBlock(in, inOffset, out, outOffset);
     }
 
@@ -426,8 +425,8 @@
      */
     void decryptBlock(byte[] in, int inOffset,
                       byte[] out, int outOffset) {
-        cryptBlockCheck(in, inOffset);
-        cryptBlockCheck(out, outOffset);
+        // Array bound checks are done in caller code, i.e.
+        // FeedbackCipher.encrypt/decrypt(...) to improve performance.
         implDecryptBlock(in, inOffset, out, outOffset);
     }
 
@@ -588,26 +587,6 @@
         out[outOffset  ] = (byte)(Si[(a0       ) & 0xFF] ^ (t1       ));
     }
 
-    // Used to perform all checks required by the Java semantics
-    // (i.e., null checks and bounds checks) on the input parameters
-    // to encryptBlock and to decryptBlock.
-    // Normally, the Java Runtime performs these checks, however, as
-    // encryptBlock and decryptBlock are possibly replaced with
-    // compiler intrinsics, the JDK performs the required checks instead.
-    // Does not check accesses to class-internal (private) arrays.
-    private static void cryptBlockCheck(byte[] array, int offset) {
-        Objects.requireNonNull(array);
-
-        if (offset < 0 || offset >= array.length) {
-            throw new ArrayIndexOutOfBoundsException(offset);
-        }
-
-        int largestIndex = offset + AES_BLOCK_SIZE - 1;
-        if (largestIndex < 0 || largestIndex >= array.length) {
-            throw new ArrayIndexOutOfBoundsException(largestIndex);
-        }
-    }
-
     /**
      * Expand a user-supplied key material into a session key.
      *
--- a/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
 import java.security.ProviderException;
 import java.util.Objects;
 
+import sun.security.util.ArrayUtil;
 
 /**
  * This class represents ciphers in cipher block chaining (CBC) mode.
@@ -143,9 +144,9 @@
         if (plainLen <= 0) {
             return plainLen;
         }
-        cryptBlockSizeCheck(plainLen);
-        cryptNullAndBoundsCheck(plain, plainOffset, plainLen);
-        cryptNullAndBoundsCheck(cipher, cipherOffset, plainLen);
+        ArrayUtil.blockSizeCheck(plainLen, blockSize);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
         return implEncrypt(plain, plainOffset, plainLen,
                            cipher, cipherOffset);
     }
@@ -193,9 +194,9 @@
         if (cipherLen <= 0) {
             return cipherLen;
         }
-        cryptBlockSizeCheck(cipherLen);
-        cryptNullAndBoundsCheck(cipher, cipherOffset, cipherLen);
-        cryptNullAndBoundsCheck(plain, plainOffset, cipherLen);
+        ArrayUtil.blockSizeCheck(cipherLen, blockSize);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, cipherLen);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, cipherLen);
         return implDecrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
     }
 
@@ -214,23 +215,4 @@
         }
         return cipherLen;
     }
-
-    private void cryptBlockSizeCheck(int len) {
-        if ((len % blockSize) != 0) {
-            throw new ProviderException("Internal error in input buffering");
-        }
-    }
-
-    private static void cryptNullAndBoundsCheck(byte[] array, int offset, int len) {
-        Objects.requireNonNull(array);
-
-        if (offset < 0 || offset >= array.length) {
-            throw new ArrayIndexOutOfBoundsException(offset);
-        }
-
-        int endIndex = offset + len - 1;
-        if (endIndex < 0 || endIndex >= array.length) {
-            throw new ArrayIndexOutOfBoundsException(endIndex);
-        }
-    }
 }
--- a/src/share/classes/com/sun/crypto/provider/CipherFeedback.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/CipherFeedback.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.security.InvalidKeyException;
 import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
 
 /**
  * This class represents ciphers in cipher-feedback (CFB) mode.
@@ -149,9 +150,9 @@
      */
     int encrypt(byte[] plain, int plainOffset, int plainLen,
                 byte[] cipher, int cipherOffset) {
-        if ((plainLen % numBytes) != 0) {
-            throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(plainLen, numBytes);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
 
         int nShift = blockSize - numBytes;
         int loopCount = plainLen / numBytes;
@@ -225,9 +226,10 @@
      */
     int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
                 byte[] plain, int plainOffset) {
-        if ((cipherLen % numBytes) != 0) {
-            throw new ProviderException("Internal error in input buffering");
-        }
+
+        ArrayUtil.blockSizeCheck(cipherLen, numBytes);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, cipherLen);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, cipherLen);
 
         int nShift = blockSize - numBytes;
         int loopCount = cipherLen / numBytes;
--- a/src/share/classes/com/sun/crypto/provider/CounterMode.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/CounterMode.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.security.InvalidKeyException;
 
+import sun.security.util.ArrayUtil;
 
 /**
  * This class represents ciphers in counter (CTR) mode.
@@ -173,6 +174,10 @@
         if (len == 0) {
             return 0;
         }
+
+        ArrayUtil.nullAndBoundsCheck(in, inOff, len);
+        ArrayUtil.nullAndBoundsCheck(out, outOff, len);
+
         int result = len;
         while (len-- > 0) {
             if (used >= blockSize) {
--- a/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.security.InvalidKeyException;
 import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
 
 /**
  * This class represents ciphers in electronic codebook (ECB) mode.
@@ -112,9 +113,10 @@
      * @return the length of the encrypted data
      */
     int encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
-        if ((len % blockSize) != 0) {
-             throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(len, blockSize);
+        ArrayUtil.nullAndBoundsCheck(in, inOff, len);
+        ArrayUtil.nullAndBoundsCheck(out, outOff, len);
+
         for (int i = len; i >= blockSize; i -= blockSize) {
             embeddedCipher.encryptBlock(in, inOff, out, outOff);
             inOff += blockSize;
@@ -141,9 +143,10 @@
      * @return the length of the decrypted data
      */
     int decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
-        if ((len % blockSize) != 0) {
-             throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(len, blockSize);
+        ArrayUtil.nullAndBoundsCheck(in, inOff, len);
+        ArrayUtil.nullAndBoundsCheck(out, outOff, len);
+
         for (int i = len; i >= blockSize; i -= blockSize) {
             embeddedCipher.decryptBlock(in, inOff, out, outOff);
             inOff += blockSize;
--- a/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -30,6 +30,8 @@
 import java.security.*;
 import javax.crypto.*;
 import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
+import sun.security.util.ArrayUtil;
+
 
 /**
  * This class represents ciphers in GaloisCounter (GCM) mode.
@@ -406,8 +408,8 @@
     /**
      * Performs encryption operation.
      *
-     * <p>The input plain text <code>in</code>, starting at <code>inOff</code>
-     * and ending at <code>(inOff + len - 1)</code>, is encrypted. The result
+     * <p>The input plain text <code>in</code>, starting at <code>inOfs</code>
+     * and ending at <code>(inOfs + len - 1)</code>, is encrypted. The result
      * is stored in <code>out</code>, starting at <code>outOfs</code>.
      *
      * @param in the buffer with the input data to be encrypted
@@ -422,15 +424,18 @@
     int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
         checkDataLength(processed, len);
 
-        if ((len % blockSize) != 0) {
-             throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(len, blockSize);
         processAAD();
+
         if (len > 0) {
+            ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
+            ArrayUtil.nullAndBoundsCheck(out, outOfs, len);
+
             gctrPAndC.update(in, inOfs, len, out, outOfs);
             processed += len;
             ghashAllToS.update(out, outOfs, len);
         }
+
         return len;
     }
 
@@ -450,7 +455,10 @@
             throw new ShortBufferException
                 ("Can't fit both data and tag into one buffer");
         }
-        if (out.length - outOfs < (len + tagLenBytes)) {
+        try {
+            ArrayUtil.nullAndBoundsCheck(out, outOfs,
+                (len + tagLenBytes));
+        } catch (ArrayIndexOutOfBoundsException aiobe) {
             throw new ShortBufferException("Output buffer too small");
         }
 
@@ -458,6 +466,8 @@
 
         processAAD();
         if (len > 0) {
+            ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
+
             doLastBlock(in, inOfs, len, out, outOfs, true);
         }
 
@@ -493,15 +503,14 @@
     int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
         checkDataLength(ibuffer.size(), len);
 
-        if ((len % blockSize) != 0) {
-             throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(len, blockSize);
         processAAD();
 
         if (len > 0) {
             // store internally until decryptFinal is called because
             // spec mentioned that only return recovered data after tag
             // is successfully verified
+            ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
             ibuffer.write(in, inOfs, len);
         }
         return 0;
@@ -530,22 +539,28 @@
         if (len < tagLenBytes) {
             throw new AEADBadTagException("Input too short - need tag");
         }
+
         // do this check here can also catch the potential integer overflow
         // scenario for the subsequent output buffer capacity check.
         checkDataLength(ibuffer.size(), (len - tagLenBytes));
 
-        if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) {
+        try {
+            ArrayUtil.nullAndBoundsCheck(out, outOfs,
+                (ibuffer.size() + len) - tagLenBytes);
+        } catch (ArrayIndexOutOfBoundsException aiobe) {
             throw new ShortBufferException("Output buffer too small");
         }
 
         processAAD();
 
+        ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
+
         // get the trailing tag bytes from 'in'
         byte[] tag = new byte[tagLenBytes];
         System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
         len -= tagLenBytes;
 
-        if (len != 0) {
+        if (len > 0) {
             ibuffer.write(in, inOfs, len);
         }
 
--- a/src/share/classes/com/sun/crypto/provider/OutputFeedback.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/OutputFeedback.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.security.InvalidKeyException;
 import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
 
 /**
  * This class represents ciphers in output-feedback (OFB) mode.
@@ -148,10 +149,10 @@
      */
     int encrypt(byte[] plain, int plainOffset, int plainLen,
                 byte[] cipher, int cipherOffset) {
+        ArrayUtil.blockSizeCheck(plainLen, numBytes);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
 
-        if ((plainLen % numBytes) != 0) {
-            throw new ProviderException("Internal error in input buffering");
-        }
         int nShift = blockSize - numBytes;
         int loopCount = plainLen / numBytes;
 
@@ -189,6 +190,9 @@
      */
     int encryptFinal(byte[] plain, int plainOffset, int plainLen,
                      byte[] cipher, int cipherOffset) {
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
+
         int oddBytes = plainLen % numBytes;
         int len = encrypt(plain, plainOffset, (plainLen - oddBytes),
                           cipher, cipherOffset);
--- a/src/share/classes/com/sun/crypto/provider/PCBC.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/com/sun/crypto/provider/PCBC.java	Sat Jul 06 14:35:27 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.security.InvalidKeyException;
 import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
 
 
 /**
@@ -136,9 +137,10 @@
     int encrypt(byte[] plain, int plainOffset, int plainLen,
                 byte[] cipher, int cipherOffset)
     {
-        if ((plainLen % blockSize) != 0) {
-            throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(plainLen, blockSize);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
+
         int i;
         int endIndex = plainOffset + plainLen;
 
@@ -176,9 +178,10 @@
     int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
                 byte[] plain, int plainOffset)
     {
-        if ((cipherLen % blockSize) != 0) {
-             throw new ProviderException("Internal error in input buffering");
-        }
+        ArrayUtil.blockSizeCheck(cipherLen, blockSize);
+        ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, cipherLen);
+        ArrayUtil.nullAndBoundsCheck(plain, plainOffset, cipherLen);
+
         int i;
         int endIndex = cipherOffset + cipherLen;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/jdk/internal/util/Preconditions.java	Sat Jul 06 14:35:27 2019 +0100
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.util;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Utility methods to check if state or arguments are correct.
+ *
+ */
+public class Preconditions {
+
+    /**
+     * Maps out-of-bounds values to a runtime exception.
+     *
+     * @param checkKind the kind of bounds check, whose name may correspond
+     *        to the name of one of the range check methods, checkIndex,
+     *        checkFromToIndex, checkFromIndexSize
+     * @param args the out-of-bounds arguments that failed the range check.
+     *        If the checkKind corresponds a the name of a range check method
+     *        then the bounds arguments are those that can be passed in order
+     *        to the method.
+     * @param oobef the exception formatter that when applied with a checkKind
+     *        and a list out-of-bounds arguments returns a runtime exception.
+     *        If {@code null} then, it is as if an exception formatter was
+     *        supplied that returns {@link IndexOutOfBoundsException} for any
+     *        given arguments.
+     * @return the runtime exception
+     */
+    private static RuntimeException outOfBounds(
+            BiFunction<String, List<Integer>, ? extends RuntimeException> oobef,
+            String checkKind,
+            Integer... args) {
+        List<Integer> largs = Collections.unmodifiableList(Arrays.asList(args));
+        RuntimeException e = oobef == null
+                             ? null : oobef.apply(checkKind, largs);
+        return e == null
+               ? new IndexOutOfBoundsException(outOfBoundsMessage(checkKind, largs)) : e;
+    }
+
+    private static RuntimeException outOfBoundsCheckIndex(
+            BiFunction<String, List<Integer>, ? extends RuntimeException> oobe,
+            int index, int length) {
+        return outOfBounds(oobe, "checkIndex", index, length);
+    }
+
+    private static RuntimeException outOfBoundsCheckFromToIndex(
+            BiFunction<String, List<Integer>, ? extends RuntimeException> oobe,
+            int fromIndex, int toIndex, int length) {
+        return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length);
+    }
+
+    private static RuntimeException outOfBoundsCheckFromIndexSize(
+            BiFunction<String, List<Integer>, ? extends RuntimeException> oobe,
+            int fromIndex, int size, int length) {
+        return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length);
+    }
+
+    /**
+     * Returns an out-of-bounds exception formatter from an given exception
+     * factory.  The exception formatter is a function that formats an
+     * out-of-bounds message from its arguments and applies that message to the
+     * given exception factory to produce and relay an exception.
+     *
+     * <p>The exception formatter accepts two arguments: a {@code String}
+     * describing the out-of-bounds range check that failed, referred to as the
+     * <em>check kind</em>; and a {@code List<Integer>} containing the
+     * out-of-bound integer values that failed the check.  The list of
+     * out-of-bound values is not modified.
+     *
+     * <p>Three check kinds are supported {@code checkIndex},
+     * {@code checkFromToIndex} and {@code checkFromIndexSize} corresponding
+     * respectively to the specified application of an exception formatter as an
+     * argument to the out-of-bounds range check methods
+     * {@link #checkIndex(int, int, BiFunction) checkIndex},
+     * {@link #checkFromToIndex(int, int, int, BiFunction) checkFromToIndex}, and
+     * {@link #checkFromIndexSize(int, int, int, BiFunction) checkFromIndexSize}.
+     * Thus a supported check kind corresponds to a method name and the
+     * out-of-bound integer values correspond to method argument values, in
+     * order, preceding the exception formatter argument (similar in many
+     * respects to the form of arguments required for a reflective invocation of
+     * such a range check method).
+     *
+     * <p>Formatter arguments conforming to such supported check kinds will
+     * produce specific exception messages describing failed out-of-bounds
+     * checks.  Otherwise, more generic exception messages will be produced in
+     * any of the following cases: the check kind is supported but fewer
+     * or more out-of-bounds values are supplied, the check kind is not
+     * supported, the check kind is {@code null}, or the list of out-of-bound
+     * values is {@code null}.
+     *
+     * @apiNote
+     * This method produces an out-of-bounds exception formatter that can be
+     * passed as an argument to any of the supported out-of-bounds range check
+     * methods declared by {@code Objects}.  For example, a formatter producing
+     * an {@code ArrayIndexOutOfBoundsException} may be produced and stored on a
+     * {@code static final} field as follows:
+     * <pre>{@code
+     * static final
+     * BiFunction<String, List<Integer>, ArrayIndexOutOfBoundsException> AIOOBEF =
+     *     outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new);
+     * }</pre>
+     * The formatter instance {@code AIOOBEF} may be passed as an argument to an
+     * out-of-bounds range check method, such as checking if an {@code index}
+     * is within the bounds of a {@code limit}:
+     * <pre>{@code
+     * checkIndex(index, limit, AIOOBEF);
+     * }</pre>
+     * If the bounds check fails then the range check method will throw an
+     * {@code ArrayIndexOutOfBoundsException} with an appropriate exception
+     * message that is a produced from {@code AIOOBEF} as follows:
+     * <pre>{@code
+     * AIOOBEF.apply("checkIndex", List.of(index, limit));
+     * }</pre>
+     *
+     * @param f the exception factory, that produces an exception from a message
+     *        where the message is produced and formatted by the returned
+     *        exception formatter.  If this factory is stateless and side-effect
+     *        free then so is the returned formatter.
+     *        Exceptions thrown by the factory are relayed to the caller
+     *        of the returned formatter.
+     * @param <X> the type of runtime exception to be returned by the given
+     *        exception factory and relayed by the exception formatter
+     * @return the out-of-bounds exception formatter
+     */
+    public static <X extends RuntimeException>
+    BiFunction<String, List<Integer>, X> outOfBoundsExceptionFormatter(Function<String, X> f) {
+        // Use anonymous class to avoid bootstrap issues if this method is
+        // used early in startup
+        return new BiFunction<String, List<Integer>, X>() {
+            @Override
+            public X apply(String checkKind, List<Integer> args) {
+                return f.apply(outOfBoundsMessage(checkKind, args));
+            }
+        };
+    }
+
+    private static String outOfBoundsMessage(String checkKind, List<Integer> args) {
+        if (checkKind == null && args == null) {
+            return String.format("Range check failed");
+        } else if (checkKind == null) {
+            return String.format("Range check failed: %s", args);
+        } else if (args == null) {
+            return String.format("Range check failed: %s", checkKind);
+        }
+
+        int argSize = 0;
+        switch (checkKind) {
+            case "checkIndex":
+                argSize = 2;
+                break;
+            case "checkFromToIndex":
+            case "checkFromIndexSize":
+                argSize = 3;
+                break;
+            default:
+        }
+
+        // Switch to default if fewer or more arguments than required are supplied
+        switch ((args.size() != argSize) ? "" : checkKind) {
+            case "checkIndex":
+                return String.format("Index %d out-of-bounds for length %d",
+                                     args.get(0), args.get(1));
+            case "checkFromToIndex":
+                return String.format("Range [%d, %d) out-of-bounds for length %d",
+                                     args.get(0), args.get(1), args.get(2));
+            case "checkFromIndexSize":
+                return String.format("Range [%d, %<d + %d) out-of-bounds for length %d",
+                                     args.get(0), args.get(1), args.get(2));
+            default:
+                return String.format("Range check failed: %s %s", checkKind, args);
+        }
+    }
+
+    /**
+     * Checks if the {@code index} is within the bounds of the range from
+     * {@code 0} (inclusive) to {@code length} (exclusive).
+     *
+     * <p>The {@code index} is defined to be out-of-bounds if any of the
+     * following inequalities is true:
+     * <ul>
+     *  <li>{@code index < 0}</li>
+     *  <li>{@code index >= length}</li>
+     *  <li>{@code length < 0}, which is implied from the former inequalities</li>
+     * </ul>
+     *
+     * <p>If the {@code index} is out-of-bounds, then a runtime exception is
+     * thrown that is the result of applying the following arguments to the
+     * exception formatter: the name of this method, {@code checkIndex};
+     * and an unmodifiable list integers whose values are, in order, the
+     * out-of-bounds arguments {@code index} and {@code length}.
+     *
+     * @param <X> the type of runtime exception to throw if the arguments are
+     *        out-of-bounds
+     * @param index the index
+     * @param length the upper-bound (exclusive) of the range
+     * @param oobef the exception formatter that when applied with this
+     *        method name and out-of-bounds arguments returns a runtime
+     *        exception.  If {@code null} or returns {@code null} then, it is as
+     *        if an exception formatter produced from an invocation of
+     *        {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
+     *        instead (though it may be more efficient).
+     *        Exceptions thrown by the formatter are relayed to the caller.
+     * @return {@code index} if it is within bounds of the range
+     * @throws X if the {@code index} is out-of-bounds and the exception
+     *         formatter is non-{@code null}
+     * @throws IndexOutOfBoundsException if the {@code index} is out-of-bounds
+     *         and the exception formatter is {@code null}
+     * @since 9
+     *
+     * @implNote
+     * This method is made intrinsic in optimizing compilers to guide them to
+     * perform unsigned comparisons of the index and length when it is known the
+     * length is a non-negative value (such as that of an array length or from
+     * the upper bound of a loop)
+    */
+    public static <X extends RuntimeException>
+    int checkIndex(int index, int length,
+                   BiFunction<String, List<Integer>, X> oobef) {
+        if (index < 0 || index >= length)
+            throw outOfBoundsCheckIndex(oobef, index, length);
+        return index;
+    }
+
+    /**
+     * Checks if the sub-range from {@code fromIndex} (inclusive) to
+     * {@code toIndex} (exclusive) is within the bounds of range from {@code 0}
+     * (inclusive) to {@code length} (exclusive).
+     *
+     * <p>The sub-range is defined to be out-of-bounds if any of the following
+     * inequalities is true:
+     * <ul>
+     *  <li>{@code fromIndex < 0}</li>
+     *  <li>{@code fromIndex > toIndex}</li>
+     *  <li>{@code toIndex > length}</li>
+     *  <li>{@code length < 0}, which is implied from the former inequalities</li>
+     * </ul>
+     *
+     * <p>If the sub-range  is out-of-bounds, then a runtime exception is
+     * thrown that is the result of applying the following arguments to the
+     * exception formatter: the name of this method, {@code checkFromToIndex};
+     * and an unmodifiable list integers whose values are, in order, the
+     * out-of-bounds arguments {@code fromIndex}, {@code toIndex}, and {@code length}.
+     *
+     * @param <X> the type of runtime exception to throw if the arguments are
+     *        out-of-bounds
+     * @param fromIndex the lower-bound (inclusive) of the sub-range
+     * @param toIndex the upper-bound (exclusive) of the sub-range
+     * @param length the upper-bound (exclusive) the range
+     * @param oobef the exception formatter that when applied with this
+     *        method name and out-of-bounds arguments returns a runtime
+     *        exception.  If {@code null} or returns {@code null} then, it is as
+     *        if an exception formatter produced from an invocation of
+     *        {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
+     *        instead (though it may be more efficient).
+     *        Exceptions thrown by the formatter are relayed to the caller.
+     * @return {@code fromIndex} if the sub-range within bounds of the range
+     * @throws X if the sub-range is out-of-bounds and the exception factory
+     *         function is non-{@code null}
+     * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds and
+     *         the exception factory function is {@code null}
+     * @since 9
+     */
+    public static <X extends RuntimeException>
+    int checkFromToIndex(int fromIndex, int toIndex, int length,
+                         BiFunction<String, List<Integer>, X> oobef) {
+        if (fromIndex < 0 || fromIndex > toIndex || toIndex > length)
+            throw outOfBoundsCheckFromToIndex(oobef, fromIndex, toIndex, length);
+        return fromIndex;
+    }
+
+    /**
+     * Checks if the sub-range from {@code fromIndex} (inclusive) to
+     * {@code fromIndex + size} (exclusive) is within the bounds of range from
+     * {@code 0} (inclusive) to {@code length} (exclusive).
+     *
+     * <p>The sub-range is defined to be out-of-bounds if any of the following
+     * inequalities is true:
+     * <ul>
+     *  <li>{@code fromIndex < 0}</li>
+     *  <li>{@code size < 0}</li>
+     *  <li>{@code fromIndex + size > length}, taking into account integer overflow</li>
+     *  <li>{@code length < 0}, which is implied from the former inequalities</li>
+     * </ul>
+     *
+     * <p>If the sub-range  is out-of-bounds, then a runtime exception is
+     * thrown that is the result of applying the following arguments to the
+     * exception formatter: the name of this method, {@code checkFromIndexSize};
+     * and an unmodifiable list integers whose values are, in order, the
+     * out-of-bounds arguments {@code fromIndex}, {@code size}, and
+     * {@code length}.
+     *
+     * @param <X> the type of runtime exception to throw if the arguments are
+     *        out-of-bounds
+     * @param fromIndex the lower-bound (inclusive) of the sub-interval
+     * @param size the size of the sub-range
+     * @param length the upper-bound (exclusive) of the range
+     * @param oobef the exception formatter that when applied with this
+     *        method name and out-of-bounds arguments returns a runtime
+     *        exception.  If {@code null} or returns {@code null} then, it is as
+     *        if an exception formatter produced from an invocation of
+     *        {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
+     *        instead (though it may be more efficient).
+     *        Exceptions thrown by the formatter are relayed to the caller.
+     * @return {@code fromIndex} if the sub-range within bounds of the range
+     * @throws X if the sub-range is out-of-bounds and the exception factory
+     *         function is non-{@code null}
+     * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds and
+     *         the exception factory function is {@code null}
+     * @since 9
+     */
+    public static <X extends RuntimeException>
+    int checkFromIndexSize(int fromIndex, int size, int length,
+                           BiFunction<String, List<Integer>, X> oobef) {
+        if ((length | fromIndex | size) < 0 || size > length - fromIndex)
+            throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length);
+        return fromIndex;
+    }
+}
--- a/src/share/classes/sun/security/util/ArrayUtil.java	Thu Jul 04 18:20:08 2019 +0100
+++ b/src/share/classes/sun/security/util/ArrayUtil.java	Sat Jul 06 14:35:27 2019 +0100
@@ -25,12 +25,34 @@
 
 package sun.security.util;
 
+import java.util.List;
+import java.util.function.BiFunction;
+import java.security.*;
+import jdk.internal.util.Preconditions;
+
+
 /**
  * This class holds the various utility methods for array range checks.
  */
 
 public final class ArrayUtil {
 
+    private static final BiFunction<String, List<Integer>,
+            ArrayIndexOutOfBoundsException> AIOOBE_SUPPLIER =
+            Preconditions.outOfBoundsExceptionFormatter
+            (ArrayIndexOutOfBoundsException::new);
+
+    public static void blockSizeCheck(int len, int blockSize) {
+        if ((len % blockSize) != 0) {
+            throw new ProviderException("Internal error in input buffering");
+        }
+    }
+
+    public static void nullAndBoundsCheck(byte[] array, int offset, int len) {
+        // NPE is thrown when array is null
+        Preconditions.checkFromIndexSize(offset, len, array.length, AIOOBE_SUPPLIER);
+    }
+
     private static void swap(byte[] arr, int i, int j) {
         byte tmp = arr[i];
         arr[i] = arr[j];
@@ -48,4 +70,3 @@
         }
     }
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/Objects/CheckIndex.java	Sat Jul 06 14:35:27 2019 +0100
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2015, 2016 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @summary Objects.checkIndex/jdk.internal.util.Preconditions.checkIndex tests
+ * @run testng CheckIndex
+ * @bug 8135248 8142493 8155794
+ * @modules java.base/jdk.internal.util
+ */
+
+import jdk.internal.util.Preconditions;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.IntSupplier;
+
+import static org.testng.Assert.*;
+
+public class CheckIndex {
+
+    static class AssertingOutOfBoundsException extends RuntimeException {
+        public AssertingOutOfBoundsException(String message) {
+            super(message);
+        }
+    }
+
+    static BiFunction<String, List<Integer>, AssertingOutOfBoundsException> assertingOutOfBounds(
+            String message, String expCheckKind, Integer... expArgs) {
+        return (checkKind, args) -> {
+            assertEquals(checkKind, expCheckKind);
+            assertEquals(args, Collections.unmodifiableList(Arrays.asList(expArgs)));
+            try {
+                args.clear();
+                fail("Out of bounds List<Integer> argument should be unmodifiable");
+            } catch (Exception e)  {
+            }
+            return new AssertingOutOfBoundsException(message);
+        };
+    }
+
+    static BiFunction<String, List<Integer>, AssertingOutOfBoundsException> assertingOutOfBoundsReturnNull(
+            String expCheckKind, Integer... expArgs) {
+        return (checkKind, args) -> {
+            assertEquals(checkKind, expCheckKind);
+            assertEquals(args, Collections.unmodifiableList(Arrays.asList(expArgs)));
+            return null;
+        };
+    }
+
+    static final int[] VALUES = {0, 1, Integer.MAX_VALUE - 1, Integer.MAX_VALUE, -1, Integer.MIN_VALUE + 1, Integer.MIN_VALUE};
+
+    @DataProvider
+    static Object[][] checkIndexProvider() {
+        List<Object[]> l = new ArrayList<>();
+        for (int index : VALUES) {
+            for (int length : VALUES) {
+                boolean withinBounds = index >= 0 &&
+                                       length >= 0 &&
+                                       index < length;
+                l.add(new Object[]{index, length, withinBounds});
+            }
+        }
+        return l.toArray(new Object[0][0]);
+    }
+
+    interface X {
+        int apply(int a, int b, int c);
+    }
+
+    @Test(dataProvider = "checkIndexProvider")
+    public void testCheckIndex(int index, int length, boolean withinBounds) {
+        List<Integer> list = Collections.unmodifiableList(Arrays.asList(new Integer[] { index, length }));
+        String expectedMessage = withinBounds
+                                 ? null
+                                 : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
+                apply("checkIndex", list).getMessage();
+
+        BiConsumer<Class<? extends RuntimeException>, IntSupplier> checker = (ec, s) -> {
+            try {
+                int rIndex = s.getAsInt();
+                if (!withinBounds)
+                    fail(String.format(
+                            "Index %d is out of bounds of [0, %d), but was reported to be within bounds", index, length));
+                assertEquals(rIndex, index);
+            }
+            catch (RuntimeException e) {
+                assertTrue(ec.isInstance(e));
+                if (withinBounds)
+                    fail(String.format(
+                            "Index %d is within bounds of [0, %d), but was reported to be out of bounds", index, length));
+                else
+                    assertEquals(e.getMessage(), expectedMessage);
+            }
+        };
+
+        checker.accept(AssertingOutOfBoundsException.class,
+                     () -> Preconditions.checkIndex(index, length,
+                                                    assertingOutOfBounds(expectedMessage, "checkIndex", index, length)));
+        checker.accept(IndexOutOfBoundsException.class,
+                     () -> Preconditions.checkIndex(index, length,
+                                                    assertingOutOfBoundsReturnNull("checkIndex", index, length)));
+        checker.accept(IndexOutOfBoundsException.class,
+                     () -> Preconditions.checkIndex(index, length, null));
+        checker.accept(ArrayIndexOutOfBoundsException.class,
+                     () -> Preconditions.checkIndex(index, length,
+                                                    Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
+        checker.accept(StringIndexOutOfBoundsException.class,
+                     () -> Preconditions.checkIndex(index, length,
+                                                    Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
+    }
+
+
+    @DataProvider
+    static Object[][] checkFromToIndexProvider() {
+        List<Object[]> l = new ArrayList<>();
+        for (int fromIndex : VALUES) {
+            for (int toIndex : VALUES) {
+                for (int length : VALUES) {
+                    boolean withinBounds = fromIndex >= 0 &&
+                                           toIndex >= 0 &&
+                                           length >= 0 &&
+                                           fromIndex <= toIndex &&
+                                           toIndex <= length;
+                    l.add(new Object[]{fromIndex, toIndex, length, withinBounds});
+                }
+            }
+        }
+        return l.toArray(new Object[0][0]);
+    }
+
+    @Test(dataProvider = "checkFromToIndexProvider")
+    public void testCheckFromToIndex(int fromIndex, int toIndex, int length, boolean withinBounds) {
+        List<Integer> list = Collections.unmodifiableList(Arrays.asList(new Integer[] { fromIndex, toIndex, length }));
+        String expectedMessage = withinBounds
+                                 ? null
+                                 : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
+                apply("checkFromToIndex", list).getMessage();
+
+        BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
+            try {
+                int rIndex = s.getAsInt();
+                if (!withinBounds)
+                    fail(String.format(
+                            "Range [%d, %d) is out of bounds of [0, %d), but was reported to be withing bounds", fromIndex, toIndex, length));
+                assertEquals(rIndex, fromIndex);
+            }
+            catch (RuntimeException e) {
+                assertTrue(ec.isInstance(e));
+                if (withinBounds)
+                    fail(String.format(
+                            "Range [%d, %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, toIndex, length));
+                else
+                    assertEquals(e.getMessage(), expectedMessage);
+            }
+        };
+
+        check.accept(AssertingOutOfBoundsException.class,
+                     () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
+                                                          assertingOutOfBounds(expectedMessage, "checkFromToIndex", fromIndex, toIndex, length)));
+        check.accept(IndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
+                                                          assertingOutOfBoundsReturnNull("checkFromToIndex", fromIndex, toIndex, length)));
+        check.accept(IndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length, null));
+        check.accept(ArrayIndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
+                                                          Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
+        check.accept(StringIndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
+                                                          Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
+    }
+
+
+    @DataProvider
+    static Object[][] checkFromIndexSizeProvider() {
+        List<Object[]> l = new ArrayList<>();
+        for (int fromIndex : VALUES) {
+            for (int size : VALUES) {
+                for (int length : VALUES) {
+                    // Explicitly convert to long
+                    long lFromIndex = fromIndex;
+                    long lSize = size;
+                    long lLength = length;
+                    // Avoid overflow
+                    long lToIndex = lFromIndex + lSize;
+
+                    boolean withinBounds = lFromIndex >= 0L &&
+                                           lSize >= 0L &&
+                                           lLength >= 0L &&
+                                           lFromIndex <= lToIndex &&
+                                           lToIndex <= lLength;
+                    l.add(new Object[]{fromIndex, size, length, withinBounds});
+                }
+            }
+        }
+        return l.toArray(new Object[0][0]);
+    }
+
+    @Test(dataProvider = "checkFromIndexSizeProvider")
+    public void testCheckFromIndexSize(int fromIndex, int size, int length, boolean withinBounds) {
+        List<Integer> list = Collections.unmodifiableList(Arrays.asList(new Integer[] { fromIndex, size, length }));
+        String expectedMessage = withinBounds
+                                 ? null
+                                 : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
+                apply("checkFromIndexSize", list).getMessage();
+
+        BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
+            try {
+                int rIndex = s.getAsInt();
+                if (!withinBounds)
+                    fail(String.format(
+                            "Range [%d, %d + %d) is out of bounds of [0, %d), but was reported to be withing bounds", fromIndex, fromIndex, size, length));
+                assertEquals(rIndex, fromIndex);
+            }
+            catch (RuntimeException e) {
+                assertTrue(ec.isInstance(e));
+                if (withinBounds)
+                    fail(String.format(
+                            "Range [%d, %d + %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, fromIndex, size, length));
+                else
+                    assertEquals(e.getMessage(), expectedMessage);
+            }
+        };
+
+        check.accept(AssertingOutOfBoundsException.class,
+                     () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
+                                                            assertingOutOfBounds(expectedMessage, "checkFromIndexSize", fromIndex, size, length)));
+        check.accept(IndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
+                                                            assertingOutOfBoundsReturnNull("checkFromIndexSize", fromIndex, size, length)));
+        check.accept(IndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromIndexSize(fromIndex, size, length, null));
+        check.accept(ArrayIndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
+                                                            Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
+        check.accept(StringIndexOutOfBoundsException.class,
+                     () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
+                                                            Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
+    }
+
+    @Test
+    public void uniqueMessagesForCheckKinds() {
+        BiFunction<String, List<Integer>, IndexOutOfBoundsException> f =
+                Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new);
+
+        List<String> messages = new ArrayList<>();
+        List<Integer> arg1 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1 }));
+        List<Integer> arg2 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1, 0 }));
+        List<Integer> arg3 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1, 0, 0 }));
+        List<Integer> arg4 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1, 0, 0, 0 }));
+        // Exact arguments
+        messages.add(f.apply("checkIndex", arg2).getMessage());
+        messages.add(f.apply("checkFromToIndex", arg3).getMessage());
+        messages.add(f.apply("checkFromIndexSize", arg3).getMessage());
+        // Unknown check kind
+        messages.add(f.apply("checkUnknown", arg3).getMessage());
+        // Known check kind with more arguments
+        messages.add(f.apply("checkIndex", arg3).getMessage());
+        messages.add(f.apply("checkFromToIndex", arg4).getMessage());
+        messages.add(f.apply("checkFromIndexSize", arg4).getMessage());
+        // Known check kind with fewer arguments
+        messages.add(f.apply("checkIndex", arg1).getMessage());
+        messages.add(f.apply("checkFromToIndex", arg2).getMessage());
+        messages.add(f.apply("checkFromIndexSize", arg2).getMessage());
+        // Null arguments
+        messages.add(f.apply(null, null).getMessage());
+        messages.add(f.apply("checkNullArguments", null).getMessage());
+        messages.add(f.apply(null, arg1).getMessage());
+
+        assertEquals(messages.size(), messages.stream().distinct().count());
+    }
+}