1 /*
   2  * Copyright (c) 2015, 2016 Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @summary Objects.checkIndex/jdk.internal.util.Preconditions.checkIndex tests
  27  * @run testng CheckIndex
  28  * @bug 8135248 8142493 8155794
  29  * @modules java.base/jdk.internal.util
  30  */
  31 
  32 import jdk.internal.util.Preconditions;
  33 import org.testng.annotations.DataProvider;
  34 import org.testng.annotations.Test;
  35 
  36 import java.util.ArrayList;
  37 import java.util.List;
  38 import java.util.Objects;
  39 import java.util.function.BiConsumer;
  40 import java.util.function.BiFunction;
  41 import java.util.function.IntSupplier;
  42 
  43 import static org.testng.Assert.*;
  44 
  45 public class CheckIndex {
  46 
  47     static class AssertingOutOfBoundsException extends RuntimeException {
  48         public AssertingOutOfBoundsException(String message) {
  49             super(message);
  50         }
  51     }
  52 
  53     static BiFunction<String, List<Integer>, AssertingOutOfBoundsException> assertingOutOfBounds(
  54             String message, String expCheckKind, Integer... expArgs) {
  55         return (checkKind, args) -> {
  56             assertEquals(checkKind, expCheckKind);
  57             assertEquals(args, List.of(expArgs));
  58             try {
  59                 args.clear();
  60                 fail("Out of bounds List<Integer> argument should be unmodifiable");
  61             } catch (Exception e)  {
  62             }
  63             return new AssertingOutOfBoundsException(message);
  64         };
  65     }
  66 
  67     static BiFunction<String, List<Integer>, AssertingOutOfBoundsException> assertingOutOfBoundsReturnNull(
  68             String expCheckKind, Integer... expArgs) {
  69         return (checkKind, args) -> {
  70             assertEquals(checkKind, expCheckKind);
  71             assertEquals(args, List.of(expArgs));
  72             return null;
  73         };
  74     }
  75 
  76     static final int[] VALUES = {0, 1, Integer.MAX_VALUE - 1, Integer.MAX_VALUE, -1, Integer.MIN_VALUE + 1, Integer.MIN_VALUE};
  77 
  78     @DataProvider
  79     static Object[][] checkIndexProvider() {
  80         List<Object[]> l = new ArrayList<>();
  81         for (int index : VALUES) {
  82             for (int length : VALUES) {
  83                 boolean withinBounds = index >= 0 &&
  84                                        length >= 0 &&
  85                                        index < length;
  86                 l.add(new Object[]{index, length, withinBounds});
  87             }
  88         }
  89         return l.toArray(new Object[0][0]);
  90     }
  91 
  92     interface X {
  93         int apply(int a, int b, int c);
  94     }
  95 
  96     @Test(dataProvider = "checkIndexProvider")
  97     public void testCheckIndex(int index, int length, boolean withinBounds) {
  98         String expectedMessage = withinBounds
  99                                  ? null
 100                                  : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
 101                 apply("checkIndex", List.of(index, length)).getMessage();
 102 
 103         BiConsumer<Class<? extends RuntimeException>, IntSupplier> checker = (ec, s) -> {
 104             try {
 105                 int rIndex = s.getAsInt();
 106                 if (!withinBounds)
 107                     fail(String.format(
 108                             "Index %d is out of bounds of [0, %d), but was reported to be within bounds", index, length));
 109                 assertEquals(rIndex, index);
 110             }
 111             catch (RuntimeException e) {
 112                 assertTrue(ec.isInstance(e));
 113                 if (withinBounds)
 114                     fail(String.format(
 115                             "Index %d is within bounds of [0, %d), but was reported to be out of bounds", index, length));
 116                 else
 117                     assertEquals(e.getMessage(), expectedMessage);
 118             }
 119         };
 120 
 121         checker.accept(AssertingOutOfBoundsException.class,
 122                      () -> Preconditions.checkIndex(index, length,
 123                                                     assertingOutOfBounds(expectedMessage, "checkIndex", index, length)));
 124         checker.accept(IndexOutOfBoundsException.class,
 125                      () -> Preconditions.checkIndex(index, length,
 126                                                     assertingOutOfBoundsReturnNull("checkIndex", index, length)));
 127         checker.accept(IndexOutOfBoundsException.class,
 128                      () -> Preconditions.checkIndex(index, length, null));
 129         checker.accept(IndexOutOfBoundsException.class,
 130                      () -> Objects.checkIndex(index, length));
 131         checker.accept(ArrayIndexOutOfBoundsException.class,
 132                      () -> Preconditions.checkIndex(index, length,
 133                                                     Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
 134         checker.accept(StringIndexOutOfBoundsException.class,
 135                      () -> Preconditions.checkIndex(index, length,
 136                                                     Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
 137     }
 138 
 139 
 140     @DataProvider
 141     static Object[][] checkFromToIndexProvider() {
 142         List<Object[]> l = new ArrayList<>();
 143         for (int fromIndex : VALUES) {
 144             for (int toIndex : VALUES) {
 145                 for (int length : VALUES) {
 146                     boolean withinBounds = fromIndex >= 0 &&
 147                                            toIndex >= 0 &&
 148                                            length >= 0 &&
 149                                            fromIndex <= toIndex &&
 150                                            toIndex <= length;
 151                     l.add(new Object[]{fromIndex, toIndex, length, withinBounds});
 152                 }
 153             }
 154         }
 155         return l.toArray(new Object[0][0]);
 156     }
 157 
 158     @Test(dataProvider = "checkFromToIndexProvider")
 159     public void testCheckFromToIndex(int fromIndex, int toIndex, int length, boolean withinBounds) {
 160         String expectedMessage = withinBounds
 161                                  ? null
 162                                  : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
 163                 apply("checkFromToIndex", List.of(fromIndex, toIndex, length)).getMessage();
 164 
 165         BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
 166             try {
 167                 int rIndex = s.getAsInt();
 168                 if (!withinBounds)
 169                     fail(String.format(
 170                             "Range [%d, %d) is out of bounds of [0, %d), but was reported to be withing bounds", fromIndex, toIndex, length));
 171                 assertEquals(rIndex, fromIndex);
 172             }
 173             catch (RuntimeException e) {
 174                 assertTrue(ec.isInstance(e));
 175                 if (withinBounds)
 176                     fail(String.format(
 177                             "Range [%d, %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, toIndex, length));
 178                 else
 179                     assertEquals(e.getMessage(), expectedMessage);
 180             }
 181         };
 182 
 183         check.accept(AssertingOutOfBoundsException.class,
 184                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
 185                                                           assertingOutOfBounds(expectedMessage, "checkFromToIndex", fromIndex, toIndex, length)));
 186         check.accept(IndexOutOfBoundsException.class,
 187                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
 188                                                           assertingOutOfBoundsReturnNull("checkFromToIndex", fromIndex, toIndex, length)));
 189         check.accept(IndexOutOfBoundsException.class,
 190                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length, null));
 191         check.accept(IndexOutOfBoundsException.class,
 192                      () -> Objects.checkFromToIndex(fromIndex, toIndex, length));
 193         check.accept(ArrayIndexOutOfBoundsException.class,
 194                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
 195                                                           Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
 196         check.accept(StringIndexOutOfBoundsException.class,
 197                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
 198                                                           Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
 199     }
 200 
 201 
 202     @DataProvider
 203     static Object[][] checkFromIndexSizeProvider() {
 204         List<Object[]> l = new ArrayList<>();
 205         for (int fromIndex : VALUES) {
 206             for (int size : VALUES) {
 207                 for (int length : VALUES) {
 208                     // Explicitly convert to long
 209                     long lFromIndex = fromIndex;
 210                     long lSize = size;
 211                     long lLength = length;
 212                     // Avoid overflow
 213                     long lToIndex = lFromIndex + lSize;
 214 
 215                     boolean withinBounds = lFromIndex >= 0L &&
 216                                            lSize >= 0L &&
 217                                            lLength >= 0L &&
 218                                            lFromIndex <= lToIndex &&
 219                                            lToIndex <= lLength;
 220                     l.add(new Object[]{fromIndex, size, length, withinBounds});
 221                 }
 222             }
 223         }
 224         return l.toArray(new Object[0][0]);
 225     }
 226 
 227     @Test(dataProvider = "checkFromIndexSizeProvider")
 228     public void testCheckFromIndexSize(int fromIndex, int size, int length, boolean withinBounds) {
 229         String expectedMessage = withinBounds
 230                                  ? null
 231                                  : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
 232                 apply("checkFromIndexSize", List.of(fromIndex, size, length)).getMessage();
 233 
 234         BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
 235             try {
 236                 int rIndex = s.getAsInt();
 237                 if (!withinBounds)
 238                     fail(String.format(
 239                             "Range [%d, %d + %d) is out of bounds of [0, %d), but was reported to be withing bounds", fromIndex, fromIndex, size, length));
 240                 assertEquals(rIndex, fromIndex);
 241             }
 242             catch (RuntimeException e) {
 243                 assertTrue(ec.isInstance(e));
 244                 if (withinBounds)
 245                     fail(String.format(
 246                             "Range [%d, %d + %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, fromIndex, size, length));
 247                 else
 248                     assertEquals(e.getMessage(), expectedMessage);
 249             }
 250         };
 251 
 252         check.accept(AssertingOutOfBoundsException.class,
 253                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
 254                                                             assertingOutOfBounds(expectedMessage, "checkFromIndexSize", fromIndex, size, length)));
 255         check.accept(IndexOutOfBoundsException.class,
 256                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
 257                                                             assertingOutOfBoundsReturnNull("checkFromIndexSize", fromIndex, size, length)));
 258         check.accept(IndexOutOfBoundsException.class,
 259                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length, null));
 260         check.accept(IndexOutOfBoundsException.class,
 261                      () -> Objects.checkFromIndexSize(fromIndex, size, length));
 262         check.accept(ArrayIndexOutOfBoundsException.class,
 263                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
 264                                                             Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
 265         check.accept(StringIndexOutOfBoundsException.class,
 266                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
 267                                                             Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
 268     }
 269 
 270     @Test
 271     public void uniqueMessagesForCheckKinds() {
 272         BiFunction<String, List<Integer>, IndexOutOfBoundsException> f =
 273                 Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new);
 274 
 275         List<String> messages = new ArrayList<>();
 276         // Exact arguments
 277         messages.add(f.apply("checkIndex", List.of(-1, 0)).getMessage());
 278         messages.add(f.apply("checkFromToIndex", List.of(-1, 0, 0)).getMessage());
 279         messages.add(f.apply("checkFromIndexSize", List.of(-1, 0, 0)).getMessage());
 280         // Unknown check kind
 281         messages.add(f.apply("checkUnknown", List.of(-1, 0, 0)).getMessage());
 282         // Known check kind with more arguments
 283         messages.add(f.apply("checkIndex", List.of(-1, 0, 0)).getMessage());
 284         messages.add(f.apply("checkFromToIndex", List.of(-1, 0, 0, 0)).getMessage());
 285         messages.add(f.apply("checkFromIndexSize", List.of(-1, 0, 0, 0)).getMessage());
 286         // Known check kind with fewer arguments
 287         messages.add(f.apply("checkIndex", List.of(-1)).getMessage());
 288         messages.add(f.apply("checkFromToIndex", List.of(-1, 0)).getMessage());
 289         messages.add(f.apply("checkFromIndexSize", List.of(-1, 0)).getMessage());
 290         // Null arguments
 291         messages.add(f.apply(null, null).getMessage());
 292         messages.add(f.apply("checkNullArguments", null).getMessage());
 293         messages.add(f.apply(null, List.of(-1)).getMessage());
 294 
 295         assertEquals(messages.size(), messages.stream().distinct().count());
 296     }
 297 }