--- old/src/share/classes/java/util/IdentityHashMap.java 2014-07-04 15:28:51.595958862 +0400 +++ new/src/share/classes/java/util/IdentityHashMap.java 2014-07-04 15:28:51.127730745 +0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -160,6 +160,10 @@ * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<29. + * + * In fact, the map can hold no more than MAXIMUM_CAPACITY-1 items + * because it has to have at least one slot with the key == null + * in order to avoid infinite loops in get(), put(), remove() */ private static final int MAXIMUM_CAPACITY = 1 << 29; @@ -236,20 +240,12 @@ * returns MAXIMUM_CAPACITY. If (3 * expectedMaxSize)/2 is negative, it * is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned. */ - private int capacity(int expectedMaxSize) { - // Compute min capacity for expectedMaxSize given a load factor of 2/3 - int minCapacity = (3 * expectedMaxSize)/2; - - // Compute the appropriate capacity - int result; - if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) { - result = MAXIMUM_CAPACITY; - } else { - result = MINIMUM_CAPACITY; - while (result < minCapacity) - result <<= 1; - } - return result; + private static int capacity(int expectedMaxSize) { + // Doubled minimum capacity given a load factor 2/3 + int minCapacityX2 = 3 * expectedMaxSize; + return (minCapacityX2 >= 2 * MAXIMUM_CAPACITY || minCapacityX2 < 0) + ? MAXIMUM_CAPACITY : (minCapacityX2 <= 2 * MINIMUM_CAPACITY) + ? MINIMUM_CAPACITY : Integer.highestOneBit(minCapacityX2); } /** @@ -445,11 +441,21 @@ i = nextKeyIndex(i, len); } + if (size >= threshold) { + if (size >= MAXIMUM_CAPACITY - 1) { + throw new IllegalStateException("Capacity exhausted."); + } + modCount++; + resize(len); // len == 2 * current capacity. + i = hash(k, len); + while ((item = tab[i]) != null) { + i = nextKeyIndex(i, len); + } + } modCount++; tab[i] = k; tab[i + 1] = value; - if (++size >= threshold) - resize(len); // len == 2 * current capacity. + size++; return null; } @@ -465,8 +471,6 @@ Object[] oldTable = table; int oldLength = oldTable.length; if (oldLength == 2*MAXIMUM_CAPACITY) { // can't expand any further - if (threshold == MAXIMUM_CAPACITY-1) - throw new IllegalStateException("Capacity exhausted."); threshold = MAXIMUM_CAPACITY-1; // Gigantic map! return; } @@ -542,7 +546,6 @@ return null; i = nextKeyIndex(i, len); } - } /** --- /dev/null 2014-07-02 14:18:08.049978897 +0400 +++ new/test/java/util/IdentityHashMap/Capacity.java 2014-07-04 15:28:52.772532077 +0400 @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6904367 + * @summary IdentityHashMap reallocates storage when inserting expected + * number of elements + */ + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.IdentityHashMap; +import java.util.Random; + +public class Capacity { + static final Field tableField; + static final Field maxCapField; + static final Field modifiersField; + static final Random random = new Random(); + + static { + try { + tableField = IdentityHashMap.class + .getDeclaredField("table"); + tableField.setAccessible(true); + maxCapField = IdentityHashMap.class + .getDeclaredField("MAXIMUM_CAPACITY"); + maxCapField.setAccessible(true); + modifiersField = Field.class + .getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(maxCapField, + maxCapField.getModifiers() & ~Modifier.FINAL); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Checks that expected number of items can be inserted into + * the map without resizing of the internal storage + */ + static void test1(int size) throws Throwable { + IdentityHashMap ihm = new IdentityHashMap<>(size); + Object[] tableBefore = (Object[])tableField.get(ihm); + for (int j = 0; j < size; ++j) { + ihm.put(new Object(), new Object()); + } + Object[] tableAfter = (Object[])tableField.get(ihm); + if (tableBefore.length != tableAfter.length) { + String message = "Expected size: " + size + ". " + + "Capacity changed from " + tableBefore.length + + " to " + tableAfter.length; + throw new RuntimeException(message); + } + } + + /** + * Given the expected size, computes such a number N of items that + * inserting (N+1) items will trigger resizing of the internal storage + */ + + static int threshold(int size) throws Throwable { + IdentityHashMap ihm = new IdentityHashMap<>(size); + Object[] tableBefore = (Object[])tableField.get(ihm); + while (true) { + ihm.put(new Object(), new Object()); + Object[] tableAfter = (Object[])tableField.get(ihm); + if (tableBefore.length != tableAfter.length) + return ihm.size() - 1; + } + } + + /** + * Checks that inserting (threshold+1) item causes resizing + * of the internal storage + */ + static void test2(int size) throws Throwable { + final int threshold = threshold(size); + IdentityHashMap ihm = new IdentityHashMap<>(threshold); + Object[] tableBefore = (Object[])tableField.get(ihm); + Object[] tableAfter; + for (int i = 0; i < threshold; ++i) { + ihm.put(new Object(), new Object()); + tableAfter = (Object[])tableField.get(ihm); + if (tableBefore.length != tableAfter.length) { + String message = "Expected to insert: " + threshold + + ", but could only insert: " + (ihm.size() - 1) + + " items without resize"; + throw new RuntimeException(message); + } + } + ihm.put(new Object(), new Object()); + tableAfter = (Object[])tableField.get(ihm); + if (tableBefore.length == tableAfter.length) { + String message = "Expected resize after putting " + threshold + + " items"; + throw new RuntimeException(message); + } + } + + /** + * Checks that it's not possible to insert more than + * (MAXIMUM_CAPACITY-1) elements, and that failing to insert + * an element does not cause resizing of the internal storage + */ + static void test3() throws Throwable { + final int MAXIMUM_CAPACITY = 1 << 10; + IdentityHashMap ihm = new IdentityHashMap<>(); + maxCapField.setInt(ihm, MAXIMUM_CAPACITY); + + for (int i = 0; i < MAXIMUM_CAPACITY - 1; ++i) { + ihm.put(new Object(), new Object()); + } + Object[] tableBefore = (Object[])tableField.get(ihm); + try { + ihm.put(new Object(), new Object()); + throw new RuntimeException("No IllegalStateException thrown"); + } catch (IllegalStateException expected) { + } + Object[] tableAfter = (Object[])tableField.get(ihm); + if (tableBefore.length != tableAfter.length) { + String message = "Resize during unsuccessful put"; + throw new RuntimeException(message); + } + } + + public static void main(String[] args) throws Throwable { + // some numbers known to demonstrate the bug + int[] sizesToCheck = { + 2, 3, 5, 10, 11, 21, 42, 43, 85, 170, 171, 341, + 682, 683, 1365, 2730, 2731, 5461, 10922, 10923 + }; + for (int size : sizesToCheck) { + test1(size); + test2(size); + } + + // a few more random sizes to try + for (int i = 0; i != 128; ++i) { + int size = random.nextInt(50000); + test1(size); + test2(size); + } + + // testing upper bound for insertion + test3(); + } +}