JDK-8071667 : HashMap.computeIfAbsent() adds entry that HashMap.get() does not find.
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 8u31
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2015-01-26
  • Updated: 2021-03-03
  • Resolved: 2015-04-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b59Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) Client VM (build 25.31-b07, mixed mode, sharing)


A DESCRIPTION OF THE PROBLEM :
If the function supplied to computeIfAbsent adds items to the same HashTable on which the function is called from and the internal table is enlarged because of this, the new entry will be added to the wrong place in the Map's internal table making it inaccessible.

Javadoc for  Map.computeIfAbsent() is:

     * <pre> {@code
     * if (map.get(key) == null) {
     *     V newValue = mappingFunction.apply(key);
     *     if (newValue != null)
     *         map.put(key, newValue);
     * }
     * }

The implementation does not act this way when the internal table is enlarged.

This was debugged using the source code for HashMap.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile are run the given test case.



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The entry added to the Map will be accessible.  So the assert should not fail.

assert map.containsKey(KEY);
ACTUAL -
Entry added is inaccessible.  Assertion fails.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.HashMap;
import java.util.Map;


public class HashMapTest {
	public static void main(String[] args) {
		final int LIMIT = 64;
		Map<String, String> map = new HashMap<>();
		final String KEY = "key";
		
		map.computeIfAbsent(KEY, k -> {
			for(int i=0; i<LIMIT; ++i)
				map.put(i+"", "junk");
			return "value";
					
		});
		
		System.out.println("map.contains = "+map.containsKey(KEY));
		assert map.containsKey(KEY);
	}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Don't use computeIfAbsent() on HashMap.  You can use the following static method workaround.

	public static <K, V> V computeIfAbsent(Map<K,V> map, K key, Function<? super K, ? extends V> mappingFunction) {
		if (map.containsKey(key))
			return map.get(key);
		else {
			V v = mappingFunction.apply(key);
			map.put(key, v);
			return v;
		}
	}


Comments
URL: http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/0c3aa853064e User: lana Date: 2015-04-08 21:37:52 +0000
08-04-2015

URL: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/0c3aa853064e User: bchristi Date: 2015-04-02 19:33:40 +0000
02-04-2015

Such HashMap methods could check for co-modification, by taking a snapshot of the mod count before the function call, and verifying it is the same after the function call, if different a ConcurrentModificationException is thrown. Unfortunately, it's not possible to do such checks for the default methods.
30-01-2015

Checked this with JDK 8u31 and 9 and could confirm that the entry added to the map is not accessible.
27-01-2015