1 /*
  2  * Copyright (c) 2018, Red Hat, Inc. All rights reserved.
  3  * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
  4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  5  *
  6  * This code is free software; you can redistribute it and/or modify it
  7  * under the terms of the GNU General Public License version 2 only, as
  8  * published by the Free Software Foundation.
  9  *
 10  * This code is distributed in the hope that it will be useful, but WITHOUT
 11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 13  * version 2 for more details (a copy is included in the LICENSE file that
 14  * accompanied this code).
 15  *
 16  * You should have received a copy of the GNU General Public License version
 17  * 2 along with this work; if not, write to the Free Software Foundation,
 18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 19  *
 20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 21  * or visit www.oracle.com if you need additional information or have any
 22  * questions.
 23  */
 24 
 25 import org.testng.annotations.DataProvider;
 26 import org.testng.annotations.Test;
 27 
 28 import java.lang.invoke.MethodHandle;
 29 import java.lang.invoke.MethodHandles;
 30 import java.lang.invoke.MethodType;
 31 import java.lang.invoke.VarHandle;
 32 import java.util.AbstractMap;
 33 import java.util.AbstractSet;
 34 import java.util.ArrayList;
 35 import java.util.Arrays;
 36 import java.util.HashMap;
 37 import java.util.HashSet;
 38 import java.util.Iterator;
 39 import java.util.LinkedHashMap;
 40 import java.util.LinkedHashSet;
 41 import java.util.List;
 42 import java.util.Map;
 43 import java.util.Set;
 44 import java.util.WeakHashMap;
 45 import java.util.function.Consumer;
 46 import java.util.function.IntFunction;
 47 import java.util.function.Supplier;
 48 
 49 import static org.testng.Assert.assertEquals;
 50 import static org.testng.Assert.assertNull;
 51 import static org.testng.Assert.assertThrows;
 52 
 53 /*
 54  * @test
 55  * @bug 8186958 8210280 8281631 8285386 8284780
 56  * @modules java.base/java.util:open
 57  * @summary White box tests for HashMap-related internals around table sizing
 58  * @run testng/othervm -Xmx2g WhiteBoxResizeTest
 59  */
 60 public class WhiteBoxResizeTest {
 61 
 62     final MethodHandle TABLE_SIZE_FOR;
 63     final VarHandle HM_TABLE;
 64     final VarHandle WHM_TABLE;
 65     final VarHandle HS_MAP;
 66 
 67     public WhiteBoxResizeTest() throws ReflectiveOperationException {
 68         MethodHandles.Lookup hmlookup = MethodHandles.privateLookupIn(HashMap.class, MethodHandles.lookup());
 69         TABLE_SIZE_FOR = hmlookup.findStatic(
 70                 HashMap.class, "tableSizeFor", MethodType.methodType(int.class, int.class));
 71         HM_TABLE = hmlookup.unreflectVarHandle(HashMap.class.getDeclaredField("table"));
 72 
 73         MethodHandles.Lookup whmlookup = MethodHandles.privateLookupIn(WeakHashMap.class, MethodHandles.lookup());
 74         WHM_TABLE = whmlookup.unreflectVarHandle(WeakHashMap.class.getDeclaredField("table"));
 75 
 76         MethodHandles.Lookup hslookup = MethodHandles.privateLookupIn(HashSet.class, MethodHandles.lookup());
 77         HS_MAP = hslookup.unreflectVarHandle(HashSet.class.getDeclaredField("map"));
 78     }
 79 
 80     /*
 81      * utility methods
 82      */
 83 
 84     int tableSizeFor(int n) {
 85         try {
 86             return (int) TABLE_SIZE_FOR.invoke(n);
 87         } catch (Throwable t) {
 88             throw new AssertionError(t);
 89         }
 90     }
 91 
 92     Object[] table(Map<?, ?> map) {
 93         try {
 94             VarHandle vh = map instanceof WeakHashMap ? WHM_TABLE : HM_TABLE;
 95             return (Object[]) vh.get(map);
 96         } catch (Throwable t) {
 97             throw new AssertionError(t);
 98         }
 99     }
100 
101     int capacity(Map<?, ?> map) {
102         return table(map).length;
103     }
104 
105     // creates a map with size mappings
106     Map<String, String> makeMap(int size) {
107         Map<String, String> map = new HashMap<>();
108         putN(map, size);
109         return map;
110     }
111 
112     // creates a "fake" map: size() returns the given size, but
113     // the entrySet iterator returns only one entry
114     Map<String, String> fakeMap(int size) {
115         return new AbstractMap<>() {
116             public Set<Map.Entry<String, String>> entrySet() {
117                 return new AbstractSet<Map.Entry<String, String>>() {
118                     public int size() {
119                         return size;
120                     }
121 
122                     public Iterator<Map.Entry<String, String>> iterator() {
123                         return Set.of(Map.entry("1", "1")).iterator();
124                     }
125                 };
126             }
127         };
128     }
129 
130     void putN(Map<String, String> map, int n) {
131         for (int i = 0; i < n; i++) {
132             String string = Integer.toString(i);
133             map.put(string, string);
134         }
135     }
136 
137     /*
138      * tests of tableSizeFor
139      */
140 
141     @DataProvider(name = "tableSizeFor")
142     public Object[][] tableSizeForCases() {
143         final int MAX = 1 << 30;
144         return new Object[][] {
145                 // tableSizeFor(arg), expected
146                 { 0,                   1 },
147                 { 1,                   1 },
148                 { 2,                   2 },
149                 { 3,                   4 },
150                 { 4,                   4 },
151                 { 5,                   8 },
152                 { 15,                 16 },
153                 { 16,                 16 },
154                 { 17,                 32 },
155                 { MAX-1,             MAX },
156                 { MAX,               MAX },
157                 { MAX+1,             MAX },
158                 { Integer.MAX_VALUE, MAX }
159         };
160     }
161 
162     @Test(dataProvider = "tableSizeFor")
163     public void tableSizeFor(int arg, int expected) {
164         assertEquals(tableSizeFor(arg), expected);
165     }
166 
167     /*
168      * tests for lazy table allocation
169      */
170 
171     @DataProvider(name = "lazy")
172     public Object[][] lazyTableAllocationCases() {
173         return new Object[][]{
174                 {new HashMap<>()},
175                 // { new WeakHashMap<>() }, // WHM doesn't allocate lazily
176                 {new LinkedHashMap<>()}
177         };
178     }
179 
180     @Test(dataProvider = "lazy")
181     public void lazyTableAllocation(Map<?, ?> map) {
182         assertNull(table(map));
183     }
184 
185     /*
186      * tests for default capacity (no-arg constructor)
187      */
188 
189     @DataProvider(name = "defaultCapacity")
190     public Object[][] defaultCapacityCases() {
191         return new Supplier<?>[][]{
192                 {() -> new HashMap<>()},
193                 {() -> new LinkedHashMap<>()},
194                 {() -> new WeakHashMap<>()}
195         };
196     }
197 
198     @Test(dataProvider = "defaultCapacity")
199     public void defaultCapacity(Supplier<Map<String, String>> s) {
200         Map<String, String> map = s.get();
201         map.put("", "");
202         assertEquals(capacity(map), 16);
203     }
204 
205     /*
206      * tests for requested capacity (int and int+float constructors)
207      */
208 
209     @DataProvider(name = "requestedCapacity")
210     public Iterator<Object[]> requestedCapacityCases() {
211         ArrayList<Object[]> cases = new ArrayList<>();
212         for (int i = 2; i < 64; i++) {
213             int cap = i;
214             cases.add(new Object[]{"rhm1", cap, (Supplier<Map<String, String>>) () -> new HashMap<>(cap)});
215             cases.add(new Object[]{"rhm2", cap, (Supplier<Map<String, String>>) () -> new HashMap<>(cap, 0.75f)});
216             cases.add(new Object[]{"rlm1", cap, (Supplier<Map<String, String>>) () -> new LinkedHashMap<>(cap)});
217             cases.add(new Object[]{"rlm2", cap, (Supplier<Map<String, String>>) () -> new LinkedHashMap<>(cap, 0.75f)});
218             cases.add(new Object[]{"rwm1", cap, (Supplier<Map<String, String>>) () -> new WeakHashMap<>(cap)});
219             cases.add(new Object[]{"rwm2", cap, (Supplier<Map<String, String>>) () -> new WeakHashMap<>(cap, 0.75f)});
220         }
221         return cases.iterator();
222     }
223 
224     @Test(dataProvider = "requestedCapacity")
225     public void requestedCapacity(String label, int cap, Supplier<Map<String, String>> s) {
226         Map<String, String> map = s.get();
227         map.put("", "");
228         assertEquals(capacity(map), tableSizeFor(cap));
229     }
230 
231     /*
232      * Tests for capacity after map is populated with a given number N of mappings.
233      * Maps are populated using a copy constructor on a map with N mappings,
234      * other constructors followed by N put() calls, and other constructors followed
235      * by putAll() on a map with N mappings.
236      *
237      * String labels encode the test case for ease of diagnosis if one of the test cases fails.
238      * For example, "plm2pn" is "populated LinkedHashMap, 2-arg constructor, followed by putN".
239      */
240 
241     // helper method for one populated capacity case, to provide target types for lambdas
242     Object[] pcc(String label,
243                  int size,
244                  int expectedCapacity,
245                  Supplier<Map<String, String>> supplier,
246                  Consumer<Map<String, String>> consumer) {
247         return new Object[]{label, size, expectedCapacity, supplier, consumer};
248     }
249 
250     List<Object[]> genPopulatedCapacityCases(int size, int cap) {
251         return Arrays.asList(
252                 pcc("phmcpy", size, cap, () -> new HashMap<>(makeMap(size)),       map -> { }),
253                 pcc("phm0pn", size, cap, () -> new HashMap<>(),                    map -> { putN(map, size); }),
254                 pcc("phm1pn", size, cap, () -> new HashMap<>(cap),                 map -> { putN(map, size); }),
255                 pcc("phm2pn", size, cap, () -> new HashMap<>(cap, 0.75f),          map -> { putN(map, size); }),
256                 pcc("phm0pa", size, cap, () -> new HashMap<>(),                    map -> { map.putAll(makeMap(size)); }),
257                 pcc("phm1pa", size, cap, () -> new HashMap<>(cap),                 map -> { map.putAll(makeMap(size)); }),
258                 pcc("phm2pa", size, cap, () -> new HashMap<>(cap, 0.75f),          map -> { map.putAll(makeMap(size)); }),
259 
260                 pcc("plmcpy", size, cap, () -> new LinkedHashMap<>(makeMap(size)), map -> { }),
261                 pcc("plm0pn", size, cap, () -> new LinkedHashMap<>(),              map -> { putN(map, size); }),
262                 pcc("plm1pn", size, cap, () -> new LinkedHashMap<>(cap),           map -> { putN(map, size); }),
263                 pcc("plm2pn", size, cap, () -> new LinkedHashMap<>(cap, 0.75f),    map -> { putN(map, size); }),
264                 pcc("plm0pa", size, cap, () -> new LinkedHashMap<>(),              map -> { map.putAll(makeMap(size)); }),
265                 pcc("plm1pa", size, cap, () -> new LinkedHashMap<>(cap),           map -> { map.putAll(makeMap(size)); }),
266                 pcc("plm2pa", size, cap, () -> new LinkedHashMap<>(cap, 0.75f),    map -> { map.putAll(makeMap(size)); }),
267 
268                 pcc("pwmcpy", size, cap, () -> new WeakHashMap<>(makeMap(size)),   map -> { }),
269                 pcc("pwm0pn", size, cap, () -> new WeakHashMap<>(),                map -> { putN(map, size); }),
270                 pcc("pwm1pn", size, cap, () -> new WeakHashMap<>(cap),             map -> { putN(map, size); }),
271                 pcc("pwm2pn", size, cap, () -> new WeakHashMap<>(cap, 0.75f),      map -> { putN(map, size); }),
272                 pcc("pwm0pa", size, cap, () -> new WeakHashMap<>(),                map -> { map.putAll(makeMap(size)); }),
273                 pcc("pwm1pa", size, cap, () -> new WeakHashMap<>(cap),             map -> { map.putAll(makeMap(size)); }),
274                 pcc("pwm2pa", size, cap, () -> new WeakHashMap<>(cap, 0.75f),      map -> { map.putAll(makeMap(size)); })
275         );
276     }
277 
278     List<Object[]> genFakePopulatedCapacityCases(int size, int cap) {
279         return Arrays.asList(
280                 pcc("fhmcpy", size, cap, () -> new HashMap<>(fakeMap(size)),       map -> { }),
281                 pcc("fhm0pa", size, cap, () -> new HashMap<>(),                    map -> { map.putAll(fakeMap(size)); }),
282                 pcc("fhm1pa", size, cap, () -> new HashMap<>(cap),                 map -> { map.putAll(fakeMap(size)); }),
283                 pcc("fhm2pa", size, cap, () -> new HashMap<>(cap, 0.75f),          map -> { map.putAll(fakeMap(size)); }),
284 
285                 pcc("flmcpy", size, cap, () -> new LinkedHashMap<>(fakeMap(size)), map -> { }),
286                 pcc("flm0pa", size, cap, () -> new LinkedHashMap<>(),              map -> { map.putAll(fakeMap(size)); }),
287                 pcc("flm1pa", size, cap, () -> new LinkedHashMap<>(cap),           map -> { map.putAll(fakeMap(size)); }),
288                 pcc("flm2pa", size, cap, () -> new LinkedHashMap<>(cap, 0.75f),    map -> { map.putAll(fakeMap(size)); }),
289 
290                 pcc("fwmcpy", size, cap, () -> new WeakHashMap<>(fakeMap(size)),   map -> { }),
291              // pcc("fwm0pa", size, cap, () -> new WeakHashMap<>(),                map -> { map.putAll(fakeMap(size)); }), // see note
292                 pcc("fwm1pa", size, cap, () -> new WeakHashMap<>(cap),             map -> { map.putAll(fakeMap(size)); }),
293                 pcc("fwm2pa", size, cap, () -> new WeakHashMap<>(cap, 0.75f),      map -> { map.putAll(fakeMap(size)); })
294         );
295 
296         // Test case "fwm0pa" is commented out because WeakHashMap uses a different allocation
297         // policy from the other map implementations: it deliberately under-allocates in this case.
298     }
299 
300     @DataProvider(name = "populatedCapacity")
301     public Iterator<Object[]> populatedCapacityCases() {
302         ArrayList<Object[]> cases = new ArrayList<>();
303         cases.addAll(genPopulatedCapacityCases(12,  16));
304         cases.addAll(genPopulatedCapacityCases(13,  32));
305         cases.addAll(genPopulatedCapacityCases(24,  32));
306         cases.addAll(genPopulatedCapacityCases(25,  64));
307         cases.addAll(genPopulatedCapacityCases(48,  64));
308         cases.addAll(genPopulatedCapacityCases(49, 128));
309 
310         // numbers in this range are truncated by a float computation with 0.75f
311         // but can get an exact result with a double computation with 0.75d
312         cases.addAll(genFakePopulatedCapacityCases(25165824,  33554432));
313         cases.addAll(genFakePopulatedCapacityCases(25165825,  67108864));
314         cases.addAll(genFakePopulatedCapacityCases(50331648,  67108864));
315         cases.addAll(genFakePopulatedCapacityCases(50331649, 134217728));
316 
317         return cases.iterator();
318     }
319 
320     @Test(dataProvider = "populatedCapacity")
321     public void populatedCapacity(String label, // unused, included for diagnostics
322                                   int size,     // unused, included for diagnostics
323                                   int expectedCapacity,
324                                   Supplier<Map<String, String>> s,
325                                   Consumer<Map<String, String>> c) {
326         Map<String, String> map = s.get();
327         c.accept(map);
328         assertEquals(capacity(map), expectedCapacity);
329     }
330 
331     /*
332      * tests for requested size (static factory methods)
333      */
334 
335     // helper method for one requested size case, to provide target types for lambda
336     Object[] rsc(String label,
337                  int size,
338                  int expectedCapacity,
339                  Supplier<Capacitiable> supplier) {
340         return new Object[]{label, size, expectedCapacity, supplier};
341     }
342 
343     List<Object[]> genRequestedSizeCases(int size, int cap) {
344         return Arrays.asList(
345                 rsc("rshm", size, cap, () -> new MapCapacitiable(HashMap.newHashMap(size))),
346                 rsc("rslm", size, cap, () -> new MapCapacitiable(LinkedHashMap.newLinkedHashMap(size))),
347                 rsc("rswm", size, cap, () -> new MapCapacitiable(WeakHashMap.newWeakHashMap(size))),
348                 rsc("rshs", size, cap, () -> new SetCapacitiable(HashSet.newHashSet(size))),
349                 rsc("rsls", size, cap, () -> new SetCapacitiable(LinkedHashSet.newLinkedHashSet(size)))
350         );
351     }
352 
353     @DataProvider(name = "requestedSize")
354     public Iterator<Object[]> requestedSizeCases() {
355         ArrayList<Object[]> cases = new ArrayList<>();
356         cases.addAll(genRequestedSizeCases(12,  16));
357         cases.addAll(genRequestedSizeCases(13,  32));
358         cases.addAll(genRequestedSizeCases(24,  32));
359         cases.addAll(genRequestedSizeCases(25,  64));
360         cases.addAll(genRequestedSizeCases(48,  64));
361         cases.addAll(genRequestedSizeCases(49, 128));
362 
363         // numbers in this range are truncated by a float computation with 0.75f
364         // but can get an exact result with a double computation with 0.75d
365         cases.addAll(genRequestedSizeCases(25165824,  33554432));
366         cases.addAll(genRequestedSizeCases(25165825,  67108864));
367         cases.addAll(genRequestedSizeCases(50331648,  67108864));
368         cases.addAll(genRequestedSizeCases(50331649, 134217728));
369 
370         return cases.iterator();
371     }
372 
373     @Test(dataProvider = "requestedSize")
374     public void requestedSize(String label,  // unused, included for diagnostics
375                               int size,      // unused, included for diagnostics
376                               int expectedCapacity,
377                               Supplier<Capacitiable> s) {
378         Capacitiable capacitiable = s.get();
379         capacitiable.init();
380         assertEquals(capacitiable.capacity(), expectedCapacity);
381     }
382 
383     interface Capacitiable {
384 
385         void init();
386 
387         int capacity();
388 
389     }
390 
391     class MapCapacitiable implements Capacitiable {
392 
393         private final Map<String, String> content;
394 
395         public MapCapacitiable(Map<String, String> content) {
396             this.content = content;
397         }
398 
399         @Override
400         public void init() {
401             content.put("", "");
402         }
403 
404         @Override
405         public int capacity() {
406             return table(content).length;
407         }
408     }
409 
410     class SetCapacitiable implements Capacitiable {
411 
412         private final Set<String> content;
413 
414         public SetCapacitiable(Set<String> content) {
415             this.content = content;
416         }
417 
418         @Override
419         public void init() {
420             content.add("");
421         }
422 
423         @Override
424         public int capacity() {
425             HashMap<?, ?> hashMap = (HashMap<?, ?>) HS_MAP.get(content);
426             return table(hashMap).length;
427         }
428     }
429 
430     @DataProvider(name = "negativeNumMappings")
431     public Iterator<Object[]> negativeNumMappings() {
432         final List<Object[]> methods = new ArrayList<>();
433         methods.add(new Object[] {(IntFunction<?>) HashMap::newHashMap, "HashMap::newHashMap"});
434         methods.add(new Object[] {(IntFunction<?>) LinkedHashMap::newLinkedHashMap,
435                 "LinkedHashMap::newLinkedHashMap"});
436         methods.add(new Object[] {(IntFunction<?>) WeakHashMap::newWeakHashMap,
437                 "WeakHashMap::newWeakHashMap"});
438         methods.add(new Object[] {(IntFunction<?>) HashSet::newHashSet, "HashSet::newHashSet"});
439         methods.add(new Object[] {(IntFunction<?>) LinkedHashSet::newLinkedHashSet,
440                 "LinkedHashSet::newLinkedHashSet"});
441         return methods.iterator();
442     }
443 
444     /**
445      * Tests that the APIs that take {@code numMappings} or {@code numElements} as a parameter for
446      * creating the collection instance (for example: {@link HashMap#newHashMap(int)}), throw
447      * an {@code IllegalArgumentException} when a negative value is passed to them
448      */
449     @Test(dataProvider = "negativeNumMappings")
450     public void testNegativeNumMappings(final IntFunction<?> method, final String methodName) {
451         assertThrows(IllegalArgumentException.class, () -> method.apply(-1));
452     }
453 }