Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConcurrentReferenceHashMap's entrySet violates the Map contract #27454

Closed
ben-manes opened this issue Sep 23, 2021 · 2 comments
Closed

ConcurrentReferenceHashMap's entrySet violates the Map contract #27454

ben-manes opened this issue Sep 23, 2021 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Milestone

Comments

@ben-manes
Copy link
Contributor

ben-manes commented Sep 23, 2021

A quick sanity test of this map using Guava's testlib found some simple violations. For example entrySet().iterator() does not throw an exception for the sequence [hasNext, hasNext, next, remove, remove]. In this case Iterator.remove() failed to null out the last property after the first call, so a subsequent call does not throw an IllegalStateException. As the keySet and values views delegate to entrySet, this error is found multiple times in Guava's suite. You might consider using this suite on other custom collections.

Unit Tests
import java.util.Map;

import org.springframework.util.ConcurrentReferenceHashMap;

import com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder;
import com.google.common.collect.testing.TestStringMapGenerator;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.MapFeature;

import junit.framework.Test;
import junit.framework.TestCase;

/** Guava testlib map tests. */
public final class ConcurrentReferenceHashMapTests extends TestCase {

  public static Test suite() {
    var suite = ConcurrentMapTestSuiteBuilder
        .using(new TestStringMapGenerator() {
          @Override protected Map<String, String> create(Map.Entry<String, String>[] entries) {
            var map = new ConcurrentReferenceHashMap<String, String>();
            for (var entry : entries) {
              map.put(entry.getKey(), entry.getValue());
            }
            return map;
          }
        })
        .named("ConcurrentReferenceHashMap")
        .withFeatures(
            MapFeature.GENERAL_PURPOSE,
            MapFeature.ALLOWS_ANY_NULL_QUERIES,
            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
            CollectionSize.ANY);
    return suite.createTestSuite();
  }
}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 23, 2021
@jhoeller jhoeller self-assigned this Sep 23, 2021
@jhoeller jhoeller added in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 23, 2021
@jhoeller jhoeller added this to the 5.3.11 milestone Sep 23, 2021
@jhoeller jhoeller changed the title ConcurrentReferenceHashMap violates the Map contract ConcurrentReferenceHashMap's entrySet violates the Map contract Sep 23, 2021
@jhoeller jhoeller added the for: backport-to-5.2.x Marks an issue as a candidate for backport to 5.2.x label Sep 23, 2021
@spring-projects-issues spring-projects-issues added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.2.x Marks an issue as a candidate for backport to 5.2.x labels Sep 23, 2021
@ben-manes
Copy link
Contributor Author

@jhoeller Thanks for the quick fix. I checked with Guava's tests and it looks like the other failures (like Map.contains) were resolved thanks to this and perhaps other fixes on 6.0-SNAPSHOT (I had only checked the last 5.x release). You might consider adding Guava's testlib dependency (their testing utilities, com.google.guava:guava-testlib) and the above suite to yours, and verify other collections like LinkedCaseInsensitiveMap (passes below test).

public static Test suite() {
  var suite = MapTestSuiteBuilder
      .using(new TestStringMapGenerator() {
        @Override protected Map<String, String> create(Map.Entry<String, String>[] entries) {
          var map = new LinkedCaseInsensitiveMap<String>();
          for (var entry : entries) {
            map.put(entry.getKey(), entry.getValue());
          }
          return map;
        }
      })
      .named("LinkedCaseInsensitiveMap")
      .withFeatures(
          MapFeature.GENERAL_PURPOSE,
          MapFeature.ALLOWS_NULL_VALUES,
          CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
          CollectionSize.ANY);
  return suite.createTestSuite();
}
<details>

@jhoeller
Copy link
Contributor

@ben-manes Thanks for raising this to begin with! Those violations remained completely unnoticed before, in particular the buggy equals comparison in the EntrySet.contains implementation. I suppose that they are simply not part of the regular usage model for that particular collection. I wouldn't be surprised if noone ever called entrySet() on a ConcurrentReferenceHashMap before since that collection is only really used for very specific interaction patterns.

LinkedCaseInsensitiveMap has certainly seen wider-spread use against the actual Map contract, also with the Java 8 additions to the Map interface, with quite a few contract violation bugs reported over the years.

I forgot to comment on guava-testlib yesterday: I locally gave it a try and used it to verify the fix - but also added individual unit tests for those particular changes. I'll raise guava-testlib in our next team call, it would indeed be a fine additional regression testing measure for our test suite, even with it being in old JUnit 3.8 shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants