8217969: Base64.Decoder.decode methods do not need to throw OOME due to integer overflow
authornishjain
Wed, 06 Feb 2019 13:57:19 +0530
changeset 53655 c6e8196e4b54
parent 53654 7054249afee5
child 53656 645ba889ee5f
8217969: Base64.Decoder.decode methods do not need to throw OOME due to integer overflow 8218265: java/util/Base64/TestEncodingDecodingLength.java failing Reviewed-by: rriggs, naoto
src/java.base/share/classes/java/util/Base64.java
test/jdk/java/util/Base64/TestEncodingDecodingLength.java
--- a/src/java.base/share/classes/java/util/Base64.java	Tue Feb 05 20:18:00 2019 -0500
+++ b/src/java.base/share/classes/java/util/Base64.java	Wed Feb 06 13:57:19 2019 +0530
@@ -251,7 +251,7 @@
          * @return length of the encoded bytes, or -1 if the length overflows
          *
          */
-        private final int outLength(int srclen, boolean throwOOME) {
+        private final int encodedOutLength(int srclen, boolean throwOOME) {
             int len = 0;
             try {
                 if (doPadding) {
@@ -286,7 +286,7 @@
          *          encoded bytes.
          */
         public byte[] encode(byte[] src) {
-            int len = outLength(src.length, true);          // dst array size
+            int len = encodedOutLength(src.length, true);          // dst array size
             byte[] dst = new byte[len];
             int ret = encode0(src, 0, src.length, dst);
             if (ret != dst.length)
@@ -314,7 +314,7 @@
          *          space for encoding all input bytes.
          */
         public int encode(byte[] src, byte[] dst) {
-            int len = outLength(src.length, false);         // dst array size
+            int len = encodedOutLength(src.length, false);         // dst array size
             if (dst.length < len || len == -1)
                 throw new IllegalArgumentException(
                     "Output byte array is too small for encoding all input bytes");
@@ -359,7 +359,7 @@
          * @return  A newly-allocated byte buffer containing the encoded bytes.
          */
         public ByteBuffer encode(ByteBuffer buffer) {
-            int len = outLength(buffer.remaining(), true);
+            int len = encodedOutLength(buffer.remaining(), true);
             byte[] dst = new byte[len];
             int ret = 0;
             if (buffer.hasArray()) {
@@ -560,7 +560,7 @@
          *          if {@code src} is not in valid Base64 scheme
          */
         public byte[] decode(byte[] src) {
-            byte[] dst = new byte[outLength(src, 0, src.length, true)];
+            byte[] dst = new byte[decodedOutLength(src, 0, src.length)];
             int ret = decode0(src, 0, src.length, dst);
             if (ret != dst.length) {
                 dst = Arrays.copyOf(dst, ret);
@@ -613,7 +613,7 @@
          *          does not have enough space for decoding all input bytes.
          */
         public int decode(byte[] src, byte[] dst) {
-            int len = outLength(src, 0, src.length, false);
+            int len = decodedOutLength(src, 0, src.length);
             if (dst.length < len || len == -1)
                 throw new IllegalArgumentException(
                     "Output byte array is too small for decoding all input bytes");
@@ -657,7 +657,7 @@
                     sp = 0;
                     sl = src.length;
                 }
-                byte[] dst = new byte[outLength(src, sp, sl, true)];
+                byte[] dst = new byte[decodedOutLength(src, sp, sl)];
                 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
             } catch (IllegalArgumentException iae) {
                 buffer.position(pos0);
@@ -691,13 +691,11 @@
          * @param src the byte array to decode
          * @param sp the source  position
          * @param sl the source limit
-         * @param throwOOME if true, throws OutOfMemoryError if the length of
-         *                  the decoded bytes overflows; else returns the
-         *                  length
-         * @return length of the decoded bytes, or -1 if the length overflows
+         *
+         * @return length of the decoded bytes
          *
          */
-        private int outLength(byte[] src, int sp, int sl, boolean throwOOME) {
+        private int decodedOutLength(byte[] src, int sp, int sl) {
             int[] base64 = isURL ? fromBase64URL : fromBase64;
             int paddings = 0;
             int len = sl - sp;
@@ -733,18 +731,12 @@
             if (paddings == 0 && (len & 0x3) !=  0)
                 paddings = 4 - (len & 0x3);
 
-            try {
-                len = Math.multiplyExact(3, (Math.addExact(len, 3) / 4)) - paddings;
-            } catch (ArithmeticException ex) {
-                if (throwOOME) {
-                    throw new OutOfMemoryError("Decoded size is too large");
-                } else {
-                    // let the caller know that the decoded bytes length
-                    // is too large
-                    len = -1;
-                }
-            }
-            return len;
+            // If len is near to Integer.MAX_VALUE, (len + 3)
+            // can possibly overflow, perform this operation as
+            // long and cast it back to integer when the value comes under
+            // integer limit. The final value will always be in integer
+            // limits
+            return 3 * (int) ((len + 3L) / 4) - paddings;
         }
 
         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
--- a/test/jdk/java/util/Base64/TestEncodingDecodingLength.java	Tue Feb 05 20:18:00 2019 -0500
+++ b/test/jdk/java/util/Base64/TestEncodingDecodingLength.java	Wed Feb 06 13:57:19 2019 +0530
@@ -22,22 +22,23 @@
  */
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.Base64;
 
 /**
  * @test
- * @bug 8210583
- * @summary Tests Base64.Encoder.encode/Decoder.decode for the large size
- *          of resulting bytes which can not be allocated
- * @requires os.maxMemory >= 6g
- * @run main/othervm -Xms4g -Xmx6g TestEncodingDecodingLength
+ * @bug 8210583 8217969 8218265
+ * @summary Tests Base64.Encoder.encode and Base64.Decoder.decode
+ *          with the large size of input array/buffer
+ * @requires os.maxMemory >= 8g
+ * @run main/othervm -Xms6g -Xmx8g TestEncodingDecodingLength
  *
  */
 
 public class TestEncodingDecodingLength {
 
     public static void main(String[] args) {
-        int size = Integer.MAX_VALUE - 2;
+        int size = Integer.MAX_VALUE - 8;
         byte[] inputBytes = new byte[size];
         byte[] outputBytes = new byte[size];
 
@@ -46,13 +47,15 @@
         checkOOM("encode(byte[])", () -> encoder.encode(inputBytes));
         checkIAE("encode(byte[] byte[])", () -> encoder.encode(inputBytes, outputBytes));
         checkOOM("encodeToString(byte[])", () -> encoder.encodeToString(inputBytes));
-        checkOOM("encode(ByteBuffer)", () -> encoder.encode(ByteBuffer.allocate(size)));
+        checkOOM("encode(ByteBuffer)", () -> encoder.encode(ByteBuffer.wrap(inputBytes)));
 
-        // Check decoder with large array length
+        // Check decoder with large array length,
+        // should not throw any exception
+        Arrays.fill(inputBytes, (byte) 86);
         Base64.Decoder decoder = Base64.getDecoder();
-        checkOOM("decode(byte[])", () -> decoder.decode(inputBytes));
-        checkIAE("decode(byte[], byte[])", () -> decoder.decode(inputBytes, outputBytes));
-        checkOOM("decode(ByteBuffer)", () -> decoder.decode(ByteBuffer.allocate(size)));
+        decoder.decode(inputBytes);
+        decoder.decode(inputBytes, outputBytes);
+        decoder.decode(ByteBuffer.wrap(inputBytes));
     }
 
     private static final void checkOOM(String methodName, Runnable r) {