1 /*
2  * Copyright (c) 2015, 2020, 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 for int values
27  * @run testng CheckIndex
28  * @bug 8135248 8142493 8155794
29  * @modules java.base/jdk.internal.util
30  */
31 package test.java.util.Objects;
32 
33 import jdk.internal.util.Preconditions;
34 import org.testng.annotations.DataProvider;
35 import org.testng.annotations.Test;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.function.BiConsumer;
41 import java.util.function.BiFunction;
42 import java.util.function.IntSupplier;
43 
44 import static org.testng.Assert.*;
45 
46 public class CheckIndex {
47 
48     static class AssertingOutOfBoundsException extends RuntimeException {
AssertingOutOfBoundsException(String message)49         public AssertingOutOfBoundsException(String message) {
50             super(message);
51         }
52     }
53 
assertingOutOfBounds( String message, String expCheckKind, Integer... expArgs)54     static BiFunction<String, List<Number>, AssertingOutOfBoundsException> assertingOutOfBounds(
55             String message, String expCheckKind, Integer... expArgs) {
56         return (checkKind, args) -> {
57             assertEquals(checkKind, expCheckKind);
58             assertEquals(args, List.of(expArgs));
59             try {
60                 args.clear();
61                 fail("Out of bounds List<Integer> argument should be unmodifiable");
62             } catch (Exception e)  {
63             }
64             return new AssertingOutOfBoundsException(message);
65         };
66     }
67 
assertingOutOfBoundsReturnNull( String expCheckKind, Integer... expArgs)68     static BiFunction<String, List<Number>, AssertingOutOfBoundsException> assertingOutOfBoundsReturnNull(
69             String expCheckKind, Integer... expArgs) {
70         return (checkKind, args) -> {
71             assertEquals(checkKind, expCheckKind);
72             assertEquals(args, List.of(expArgs));
73             return null;
74         };
75     }
76 
77     static final int[] VALUES = {0, 1, Integer.MAX_VALUE - 1, Integer.MAX_VALUE, -1, Integer.MIN_VALUE + 1, Integer.MIN_VALUE};
78 
79     @DataProvider
80     static Object[][] checkIndexProvider() {
81         List<Object[]> l = new ArrayList<>();
82         for (int index : VALUES) {
83             for (int length : VALUES) {
84                 boolean withinBounds = index >= 0 &&
85                                        length >= 0 &&
86                                        index < length;
87                 l.add(new Object[]{index, length, withinBounds});
88             }
89         }
90         return l.toArray(Object[][]::new);
91     }
92 
93     @Test(dataProvider = "checkIndexProvider")
94     public void testCheckIndex(int index, int length, boolean withinBounds) {
95         String expectedMessage = withinBounds
96                                  ? null
97                                  : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
98                 apply("checkIndex", List.of(index, length)).getMessage();
99 
100         BiConsumer<Class<? extends RuntimeException>, IntSupplier> checker = (ec, s) -> {
101             try {
102                 int rIndex = s.getAsInt();
103                 if (!withinBounds)
104                     fail(String.format(
105                             "Index %d is out of bounds of [0, %d), but was reported to be within bounds", index, length));
106                 assertEquals(rIndex, index);
107             }
108             catch (RuntimeException e) {
109                 assertTrue(ec.isInstance(e));
110                 if (withinBounds)
111                     fail(String.format(
112                             "Index %d is within bounds of [0, %d), but was reported to be out of bounds", index, length));
113                 else
114                     assertEquals(e.getMessage(), expectedMessage);
115             }
116         };
117 
118         checker.accept(AssertingOutOfBoundsException.class,
119                      () -> Preconditions.checkIndex(index, length,
120                                                     assertingOutOfBounds(expectedMessage, "checkIndex", index, length)));
121         checker.accept(IndexOutOfBoundsException.class,
122                      () -> Preconditions.checkIndex(index, length,
123                                                     assertingOutOfBoundsReturnNull("checkIndex", index, length)));
124         checker.accept(IndexOutOfBoundsException.class,
125                      () -> Preconditions.checkIndex(index, length, null));
126         checker.accept(IndexOutOfBoundsException.class,
127                      () -> Objects.checkIndex(index, length));
128         checker.accept(ArrayIndexOutOfBoundsException.class,
129                      () -> Preconditions.checkIndex(index, length,
130                                                     Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
131         checker.accept(StringIndexOutOfBoundsException.class,
132                      () -> Preconditions.checkIndex(index, length,
133                                                     Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
134     }
135 
136 
137     @DataProvider
138     static Object[][] checkFromToIndexProvider() {
139         List<Object[]> l = new ArrayList<>();
140         for (int fromIndex : VALUES) {
141             for (int toIndex : VALUES) {
142                 for (int length : VALUES) {
143                     boolean withinBounds = fromIndex >= 0 &&
144                                            toIndex >= 0 &&
145                                            length >= 0 &&
146                                            fromIndex <= toIndex &&
147                                            toIndex <= length;
148                     l.add(new Object[]{fromIndex, toIndex, length, withinBounds});
149                 }
150             }
151         }
152         return l.toArray(Object[][]::new);
153     }
154 
155     @Test(dataProvider = "checkFromToIndexProvider")
156     public void testCheckFromToIndex(int fromIndex, int toIndex, int length, boolean withinBounds) {
157         String expectedMessage = withinBounds
158                                  ? null
159                                  : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
160                 apply("checkFromToIndex", List.of(fromIndex, toIndex, length)).getMessage();
161 
162         BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
163             try {
164                 int rIndex = s.getAsInt();
165                 if (!withinBounds)
166                     fail(String.format(
167                             "Range [%d, %d) is out of bounds of [0, %d), but was reported to be withing bounds", fromIndex, toIndex, length));
168                 assertEquals(rIndex, fromIndex);
169             }
170             catch (RuntimeException e) {
171                 assertTrue(ec.isInstance(e));
172                 if (withinBounds)
173                     fail(String.format(
174                             "Range [%d, %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, toIndex, length));
175                 else
176                     assertEquals(e.getMessage(), expectedMessage);
177             }
178         };
179 
180         check.accept(AssertingOutOfBoundsException.class,
181                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
182                                                           assertingOutOfBounds(expectedMessage, "checkFromToIndex", fromIndex, toIndex, length)));
183         check.accept(IndexOutOfBoundsException.class,
184                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
185                                                           assertingOutOfBoundsReturnNull("checkFromToIndex", fromIndex, toIndex, length)));
186         check.accept(IndexOutOfBoundsException.class,
187                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length, null));
188         check.accept(IndexOutOfBoundsException.class,
189                      () -> Objects.checkFromToIndex(fromIndex, toIndex, length));
190         check.accept(ArrayIndexOutOfBoundsException.class,
191                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
192                                                           Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
193         check.accept(StringIndexOutOfBoundsException.class,
194                      () -> Preconditions.checkFromToIndex(fromIndex, toIndex, length,
195                                                           Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
196     }
197 
198 
199     @DataProvider
200     static Object[][] checkFromIndexSizeProvider() {
201         List<Object[]> l = new ArrayList<>();
202         for (int fromIndex : VALUES) {
203             for (int size : VALUES) {
204                 for (int length : VALUES) {
205                     // Explicitly convert to long
206                     long lFromIndex = fromIndex;
207                     long lSize = size;
208                     long lLength = length;
209                     // Avoid overflow
210                     long lToIndex = lFromIndex + lSize;
211 
212                     boolean withinBounds = lFromIndex >= 0L &&
213                                            lSize >= 0L &&
214                                            lLength >= 0L &&
215                                            lFromIndex <= lToIndex &&
216                                            lToIndex <= lLength;
217                     l.add(new Object[]{fromIndex, size, length, withinBounds});
218                 }
219             }
220         }
221         return l.toArray(Object[][]::new);
222     }
223 
224     @Test(dataProvider = "checkFromIndexSizeProvider")
225     public void testCheckFromIndexSize(int fromIndex, int size, int length, boolean withinBounds) {
226         String expectedMessage = withinBounds
227                                  ? null
228                                  : Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
229                 apply("checkFromIndexSize", List.of(fromIndex, size, length)).getMessage();
230 
231         BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
232             try {
233                 int rIndex = s.getAsInt();
234                 if (!withinBounds)
235                     fail(String.format(
236                             "Range [%d, %d + %d) is out of bounds of [0, %d), but was reported to be withing bounds", fromIndex, fromIndex, size, length));
237                 assertEquals(rIndex, fromIndex);
238             }
239             catch (RuntimeException e) {
240                 assertTrue(ec.isInstance(e));
241                 if (withinBounds)
242                     fail(String.format(
243                             "Range [%d, %d + %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, fromIndex, size, length));
244                 else
245                     assertEquals(e.getMessage(), expectedMessage);
246             }
247         };
248 
249         check.accept(AssertingOutOfBoundsException.class,
250                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
251                                                             assertingOutOfBounds(expectedMessage, "checkFromIndexSize", fromIndex, size, length)));
252         check.accept(IndexOutOfBoundsException.class,
253                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
254                                                             assertingOutOfBoundsReturnNull("checkFromIndexSize", fromIndex, size, length)));
255         check.accept(IndexOutOfBoundsException.class,
256                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length, null));
257         check.accept(IndexOutOfBoundsException.class,
258                      () -> Objects.checkFromIndexSize(fromIndex, size, length));
259         check.accept(ArrayIndexOutOfBoundsException.class,
260                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
261                                                             Preconditions.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
262         check.accept(StringIndexOutOfBoundsException.class,
263                      () -> Preconditions.checkFromIndexSize(fromIndex, size, length,
264                                                             Preconditions.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
265     }
266 
267     @Test
268     public void uniqueMessagesForCheckKinds() {
269         BiFunction<String, List<Number>, IndexOutOfBoundsException> f =
270                 Preconditions.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new);
271 
272         List<String> messages = new ArrayList<>();
273         // Exact arguments
274         messages.add(f.apply("checkIndex", List.of(-1, 0)).getMessage());
275         messages.add(f.apply("checkFromToIndex", List.of(-1, 0, 0)).getMessage());
276         messages.add(f.apply("checkFromIndexSize", List.of(-1, 0, 0)).getMessage());
277         // Unknown check kind
278         messages.add(f.apply("checkUnknown", List.of(-1, 0, 0)).getMessage());
279         // Known check kind with more arguments
280         messages.add(f.apply("checkIndex", List.of(-1, 0, 0)).getMessage());
281         messages.add(f.apply("checkFromToIndex", List.of(-1, 0, 0, 0)).getMessage());
282         messages.add(f.apply("checkFromIndexSize", List.of(-1, 0, 0, 0)).getMessage());
283         // Known check kind with fewer arguments
284         messages.add(f.apply("checkIndex", List.of(-1)).getMessage());
285         messages.add(f.apply("checkFromToIndex", List.of(-1, 0)).getMessage());
286         messages.add(f.apply("checkFromIndexSize", List.of(-1, 0)).getMessage());
287         // Null arguments
288         messages.add(f.apply(null, null).getMessage());
289         messages.add(f.apply("checkNullArguments", null).getMessage());
290         messages.add(f.apply(null, List.of(-1)).getMessage());
291 
292         assertEquals(messages.size(), messages.stream().distinct().count());
293     }
294 }
295