1 /*
   2  * Copyright (c) 2014, 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  * @bug 6904367
  27  * @summary IdentityHashMap reallocates storage when inserting expected
  28  *          number of elements
  29  */
  30 
  31 import java.lang.reflect.Field;
  32 import java.lang.reflect.Modifier;
  33 import java.util.IdentityHashMap;
  34 import java.util.Random;
  35 
  36 public class Capacity {
  37     static final Field tableField;
  38     static final Field maxCapField;
  39     static final Field modifiersField;
  40     static final Random random = new Random();
  41 
  42     static {
  43         try {
  44             tableField = IdentityHashMap.class
  45                     .getDeclaredField("table");
  46             tableField.setAccessible(true);
  47             maxCapField = IdentityHashMap.class
  48                     .getDeclaredField("MAXIMUM_CAPACITY");
  49             maxCapField.setAccessible(true);
  50             modifiersField = Field.class
  51                     .getDeclaredField("modifiers");
  52             modifiersField.setAccessible(true);
  53             modifiersField.setInt(maxCapField,
  54                     maxCapField.getModifiers() & ~Modifier.FINAL);
  55         } catch (NoSuchFieldException | IllegalAccessException e) {
  56             throw new RuntimeException(e);
  57         }
  58     }
  59 
  60     /**
  61      * Checks that expected number of items can be inserted into
  62      * the map without resizing of the internal storage
  63      */
  64     static void test1(int size) throws Throwable {
  65         IdentityHashMap<Object,Object> ihm = new IdentityHashMap<>(size);
  66         Object[] tableBefore = (Object[])tableField.get(ihm);
  67         for (int j = 0; j < size; ++j) {
  68             ihm.put(new Object(), new Object());
  69         }
  70         Object[] tableAfter = (Object[])tableField.get(ihm);
  71         if (tableBefore.length != tableAfter.length) {
  72             String message = "Expected size: " + size + ". "
  73                     + "Capacity changed from " + tableBefore.length
  74                     + " to " + tableAfter.length;
  75             throw new RuntimeException(message);
  76         }
  77     }
  78 
  79     /**
  80      * Given the expected size, computes such a number N of items that
  81      * inserting (N+1) items will trigger resizing of the internal storage
  82      */
  83 
  84     static int threshold(int size) throws Throwable {
  85         IdentityHashMap<Object,Object> ihm = new IdentityHashMap<>(size);
  86         Object[] tableBefore = (Object[])tableField.get(ihm);
  87         while (true) {
  88             ihm.put(new Object(), new Object());
  89             Object[] tableAfter = (Object[])tableField.get(ihm);
  90             if (tableBefore.length != tableAfter.length)
  91                 return ihm.size() - 1;
  92         }
  93     }
  94 
  95     /**
  96      * Checks that inserting (threshold+1) item causes resizing
  97      * of the internal storage
  98      */
  99     static void test2(int size) throws Throwable {
 100         final int threshold = threshold(size);
 101         IdentityHashMap<Object,Object> ihm = new IdentityHashMap<>(threshold);
 102         Object[] tableBefore = (Object[])tableField.get(ihm);
 103         Object[] tableAfter;
 104         for (int i = 0; i < threshold; ++i) {
 105             ihm.put(new Object(), new Object());
 106             tableAfter = (Object[])tableField.get(ihm);
 107             if (tableBefore.length != tableAfter.length) {
 108                 String message = "Expected to insert: " + threshold
 109                         + ", but could only insert: " + (ihm.size() - 1)
 110                         + " items without resize";
 111                 throw new RuntimeException(message);
 112             }
 113         }
 114         ihm.put(new Object(), new Object());
 115         tableAfter = (Object[])tableField.get(ihm);
 116         if (tableBefore.length == tableAfter.length) {
 117             String message = "Expected resize after putting " + threshold
 118                     + " items";
 119             throw new RuntimeException(message);
 120         }
 121     }
 122 
 123     /**
 124      * Checks that it's not possible to insert more than
 125      * (MAXIMUM_CAPACITY-1) elements, and that failing to insert
 126      * an element does not cause resizing of the internal storage
 127      */
 128     static void test3() throws Throwable {
 129         final int MAXIMUM_CAPACITY = 1 << 10;
 130         IdentityHashMap<Object,Object> ihm = new IdentityHashMap<>();
 131         maxCapField.setInt(ihm, MAXIMUM_CAPACITY);
 132 
 133         for (int i = 0; i < MAXIMUM_CAPACITY - 1; ++i) {
 134             ihm.put(new Object(), new Object());
 135         }
 136         Object[] tableBefore = (Object[])tableField.get(ihm);
 137         try {
 138             ihm.put(new Object(), new Object());
 139             throw new RuntimeException("No IllegalStateException thrown");
 140         } catch (IllegalStateException expected) {
 141         }
 142         Object[] tableAfter = (Object[])tableField.get(ihm);
 143         if (tableBefore.length != tableAfter.length) {
 144             String message = "Resize during unsuccessful put";
 145             throw new RuntimeException(message);
 146         }
 147     }
 148 
 149     public static void main(String[] args) throws Throwable {
 150         // some numbers known to demonstrate the bug
 151         int[] sizesToCheck = {
 152             2, 3, 5, 10, 11, 21, 42, 43, 85, 170, 171, 341,
 153             682, 683, 1365, 2730, 2731, 5461, 10922, 10923
 154         };
 155         for (int size : sizesToCheck) {
 156             test1(size);
 157             test2(size);
 158         }
 159 
 160         // a few more random sizes to try
 161         for (int i = 0; i != 128; ++i) {
 162             int size = random.nextInt(50000);
 163             test1(size);
 164             test2(size);
 165         }
 166 
 167         // testing upper bound for insertion
 168         test3();
 169     }
 170 }