diff --git a/src/share/classes/com/sun/crypto/provider/AESCrypt.java b/src/share/classes/com/sun/crypto/provider/AESCrypt.java index 0adb07092e5f6482cb4d4aacda5d4ecb58652038..c74f609f3ad4a32ab144b673edbbe8916d14389f 100644 --- a/src/share/classes/com/sun/crypto/provider/AESCrypt.java +++ b/src/share/classes/com/sun/crypto/provider/AESCrypt.java @@ -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 @@ package com.sun.crypto.provider; 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 @@ final class AESCrypt extends SymmetricCipher implements AESConstants */ 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 @@ final class AESCrypt extends SymmetricCipher implements AESConstants */ 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 @@ final class AESCrypt extends SymmetricCipher implements AESConstants 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. * diff --git a/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java b/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java index 0f8040fe20cf0ad74bdc7631a14eec15904260fc..7fdc6e1f67b22162a4b99afdbeef6a7822aabdfa 100644 --- a/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java +++ b/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java @@ -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.InvalidKeyException; 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 @@ class CipherBlockChaining extends FeedbackCipher { 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 @@ class CipherBlockChaining extends FeedbackCipher { 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 @@ class CipherBlockChaining extends FeedbackCipher { } 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); - } - } } diff --git a/src/share/classes/com/sun/crypto/provider/CipherFeedback.java b/src/share/classes/com/sun/crypto/provider/CipherFeedback.java index 84b527efe5b03afb2e7177b3c65b0b91deed4ae9..71e94cc7ffaee5ac1c7648e51e4025dbe6751cf6 100644 --- a/src/share/classes/com/sun/crypto/provider/CipherFeedback.java +++ b/src/share/classes/com/sun/crypto/provider/CipherFeedback.java @@ -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 @@ package com.sun.crypto.provider; 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 @@ final class CipherFeedback extends FeedbackCipher { */ 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 @@ final class CipherFeedback extends FeedbackCipher { */ 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; diff --git a/src/share/classes/com/sun/crypto/provider/CounterMode.java b/src/share/classes/com/sun/crypto/provider/CounterMode.java index b810b8ff8c7611701fcf8d6fea45779635c8d8e2..cda5db2cf8505053fdc9707101627c06f90cf8d5 100644 --- a/src/share/classes/com/sun/crypto/provider/CounterMode.java +++ b/src/share/classes/com/sun/crypto/provider/CounterMode.java @@ -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 @@ package com.sun.crypto.provider; import java.security.InvalidKeyException; +import sun.security.util.ArrayUtil; /** * This class represents ciphers in counter (CTR) mode. @@ -173,6 +174,10 @@ final class CounterMode extends FeedbackCipher { if (len == 0) { return 0; } + + ArrayUtil.nullAndBoundsCheck(in, inOff, len); + ArrayUtil.nullAndBoundsCheck(out, outOff, len); + int result = len; while (len-- > 0) { if (used >= blockSize) { diff --git a/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java b/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java index 334e4245e27a5522000c75b10ea06f1bb2c558b5..24e85adb9ba888ba5471a7a5ccc311f821e7d659 100644 --- a/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java +++ b/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java @@ -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 @@ package com.sun.crypto.provider; 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 @@ final class ElectronicCodeBook extends FeedbackCipher { * @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 @@ final class ElectronicCodeBook extends FeedbackCipher { * @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; diff --git a/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java index cdb22d1217919c632e1b427289382c6899163337..b574848704c44a8b972a56d9433054e16d346560 100644 --- a/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java +++ b/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java @@ -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.io.*; 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 @@ final class GaloisCounterMode extends FeedbackCipher { /** * Performs encryption operation. * - *

The input plain text in, starting at inOff - * and ending at (inOff + len - 1), is encrypted. The result + *

The input plain text in, starting at inOfs + * and ending at (inOfs + len - 1), is encrypted. The result * is stored in out, starting at outOfs. * * @param in the buffer with the input data to be encrypted @@ -422,15 +424,18 @@ final class GaloisCounterMode extends FeedbackCipher { 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 @@ final class GaloisCounterMode extends FeedbackCipher { 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 @@ final class GaloisCounterMode extends FeedbackCipher { processAAD(); if (len > 0) { + ArrayUtil.nullAndBoundsCheck(in, inOfs, len); + doLastBlock(in, inOfs, len, out, outOfs, true); } @@ -493,15 +503,14 @@ final class GaloisCounterMode extends FeedbackCipher { 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 @@ final class GaloisCounterMode extends FeedbackCipher { 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); } diff --git a/src/share/classes/com/sun/crypto/provider/OutputFeedback.java b/src/share/classes/com/sun/crypto/provider/OutputFeedback.java index 47d9d63c93df6efbfa5a9e69a506193a5f210de4..7b518836cd1cfe0c37705acf347218a8a8e3cf89 100644 --- a/src/share/classes/com/sun/crypto/provider/OutputFeedback.java +++ b/src/share/classes/com/sun/crypto/provider/OutputFeedback.java @@ -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 @@ package com.sun.crypto.provider; 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 @@ final class OutputFeedback extends FeedbackCipher { */ 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 @@ final class OutputFeedback extends FeedbackCipher { */ 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); diff --git a/src/share/classes/com/sun/crypto/provider/PCBC.java b/src/share/classes/com/sun/crypto/provider/PCBC.java index fd99bb0cbd51208bc0c9d152df59d0e8fa59beaf..544776ec04fe07d25602708370297221bcbecec5 100644 --- a/src/share/classes/com/sun/crypto/provider/PCBC.java +++ b/src/share/classes/com/sun/crypto/provider/PCBC.java @@ -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 @@ package com.sun.crypto.provider; import java.security.InvalidKeyException; import java.security.ProviderException; +import sun.security.util.ArrayUtil; /** @@ -136,9 +137,10 @@ final class PCBC extends FeedbackCipher { 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 @@ final class PCBC extends FeedbackCipher { 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; diff --git a/src/share/classes/jdk/internal/util/Preconditions.java b/src/share/classes/jdk/internal/util/Preconditions.java new file mode 100644 index 0000000000000000000000000000000000000000..5be620d28a27fb9e7b4506a57387904cfa636554 --- /dev/null +++ b/src/share/classes/jdk/internal/util/Preconditions.java @@ -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, ? extends RuntimeException> oobef, + String checkKind, + Integer... args) { + List 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, ? extends RuntimeException> oobe, + int index, int length) { + return outOfBounds(oobe, "checkIndex", index, length); + } + + private static RuntimeException outOfBoundsCheckFromToIndex( + BiFunction, ? extends RuntimeException> oobe, + int fromIndex, int toIndex, int length) { + return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length); + } + + private static RuntimeException outOfBoundsCheckFromIndexSize( + BiFunction, ? 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. + * + *

The exception formatter accepts two arguments: a {@code String} + * describing the out-of-bounds range check that failed, referred to as the + * check kind; and a {@code List} containing the + * out-of-bound integer values that failed the check. The list of + * out-of-bound values is not modified. + * + *

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). + * + *

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: + *

{@code
+     * static final
+     * BiFunction, ArrayIndexOutOfBoundsException> AIOOBEF =
+     *     outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new);
+     * }
+ * 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}: + *
{@code
+     * checkIndex(index, limit, AIOOBEF);
+     * }
+ * 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: + *
{@code
+     * AIOOBEF.apply("checkIndex", List.of(index, limit));
+     * }
+ * + * @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 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 + BiFunction, X> outOfBoundsExceptionFormatter(Function f) { + // Use anonymous class to avoid bootstrap issues if this method is + // used early in startup + return new BiFunction, X>() { + @Override + public X apply(String checkKind, List args) { + return f.apply(outOfBoundsMessage(checkKind, args)); + } + }; + } + + private static String outOfBoundsMessage(String checkKind, List 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, %The {@code index} is defined to be out-of-bounds if any of the + * following inequalities is true: + *
    + *
  • {@code index < 0}
  • + *
  • {@code index >= length}
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + int checkIndex(int index, int length, + BiFunction, 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). + * + *

The sub-range is defined to be out-of-bounds if any of the following + * inequalities is true: + *

    + *
  • {@code fromIndex < 0}
  • + *
  • {@code fromIndex > toIndex}
  • + *
  • {@code toIndex > length}
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + int checkFromToIndex(int fromIndex, int toIndex, int length, + BiFunction, 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). + * + *

The sub-range is defined to be out-of-bounds if any of the following + * inequalities is true: + *

    + *
  • {@code fromIndex < 0}
  • + *
  • {@code size < 0}
  • + *
  • {@code fromIndex + size > length}, taking into account integer overflow
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + int checkFromIndexSize(int fromIndex, int size, int length, + BiFunction, X> oobef) { + if ((length | fromIndex | size) < 0 || size > length - fromIndex) + throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length); + return fromIndex; + } +} diff --git a/src/share/classes/sun/security/util/ArrayUtil.java b/src/share/classes/sun/security/util/ArrayUtil.java index 5e5fc0aa52e29b6abc30295116f48958e48752a8..331c10079cb91badc089b1bf567b40893e018890 100644 --- a/src/share/classes/sun/security/util/ArrayUtil.java +++ b/src/share/classes/sun/security/util/ArrayUtil.java @@ -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, + 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 @@ public final class ArrayUtil { } } } - diff --git a/test/java/util/Objects/CheckIndex.java b/test/java/util/Objects/CheckIndex.java new file mode 100644 index 0000000000000000000000000000000000000000..4fe72e5ce37e3730fbb4fd5ef8a6bf8beb7b1918 --- /dev/null +++ b/test/java/util/Objects/CheckIndex.java @@ -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, 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 argument should be unmodifiable"); + } catch (Exception e) { + } + return new AssertingOutOfBoundsException(message); + }; + } + + static BiFunction, 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 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 list = Collections.unmodifiableList(Arrays.asList(new Integer[] { index, length })); + String expectedMessage = withinBounds + ? null + : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new). + apply("checkIndex", list).getMessage(); + + BiConsumer, 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 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 list = Collections.unmodifiableList(Arrays.asList(new Integer[] { fromIndex, toIndex, length })); + String expectedMessage = withinBounds + ? null + : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new). + apply("checkFromToIndex", list).getMessage(); + + BiConsumer, 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 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 list = Collections.unmodifiableList(Arrays.asList(new Integer[] { fromIndex, size, length })); + String expectedMessage = withinBounds + ? null + : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new). + apply("checkFromIndexSize", list).getMessage(); + + BiConsumer, 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, IndexOutOfBoundsException> f = + Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new); + + List messages = new ArrayList<>(); + List arg1 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1 })); + List arg2 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1, 0 })); + List arg3 = Collections.unmodifiableList(Arrays.asList(new Integer[] { -1, 0, 0 })); + List 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()); + } +}