-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8178355: IdentityHashMap uses identity-based comparison for values everywhere except remove(K,V) and replace(K,V,V) #8259
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
Conversation
…erywhere except remove(K,V) and replace(K,V,V)
/csr |
👋 Welcome back liach! A progress list of the required criteria for merging this PR into |
@liach this pull request will not be integrated until the CSR request JDK-8284901 for issue JDK-8178355 has been approved. |
Webrevs
|
public static void main(String[] args) { | ||
final String key = "key"; | ||
final String internedValue = "value"; | ||
final String constructedValue = new String(new char[]{'v', 'a', 'l', 'u', 'e'}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using:
final String constructedValue = new String(new char[]{'v', 'a', 'l', 'u', 'e'}); | |
final String constructedValue = new String(internedValue); |
will allow the internal String.value
array to be shared:
jdk/src/java.base/share/classes/java/lang/String.java
Lines 259 to 265 in bdf8a2a
@IntrinsicCandidate | |
public String(String original) { | |
this.value = original.value; | |
this.coder = original.coder; | |
this.hash = original.hash; | |
this.hashIsZero = original.hashIsZero; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know. This is just to get rid of intellij idea's warning.
@stuart-marks Mind peek at this given you are the assignee of the original JBS issue? |
Thanks for working on this. Yes I can review and sponsor this change. Sorry I got a bit distracted. I started looking at the test here, and this lead me to inquire about what other tests we have for |
public boolean remove(Object o) { | ||
return o instanceof Entry<?, ?> entry | ||
&& removeMapping(entry.getKey(), entry.getValue()); | ||
&& IdentityHashMap.this.remove(entry.getKey(), entry.getValue()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep the internal removeMapping
method and have other methods call it, instead of calling a public method. In particular the EntrySet.remove()
method here should call an internal method to perform the actual removal instead of calling the public remove()
method, since that potentially exposes the "self-use" to subclasses. The the public remove()
method on IDHM could call removeMapping
.
return false; | ||
i = nextKeyIndex(i, len); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately there's some mostly-duplicate code here. However, there's similar logic and code sprinkled throughout this class, so more duplication isn't necessarily the wrong thing to do. However, trying to unify this logic results in much more intrusive refactoring, which is harder to review, and which isn't backed up with tests (see JDK-8285295) which I wouldn't encourage pursuing right now. In other words, I'm ok with this duplicate logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On intrusive logic: I planned address it in https://bugs.openjdk.java.net/browse/JDK-8277520 (#6532), and if this one is integrated first, it may be possible to fix it up there.
throw new AssertionError("Failed to remove value by identity"); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall comments on this test. It does test the right set of four cases (positive and negative calls to replace
and remove
). However, it's one test that tests the four cases, instead of four tests. Using the same state makes it hard to add or maintain test cases in the future. It also trusts the return value of the method calls and doesn't make any assertions over the actual contents of the map. Without assertions over the expected contents, a method could behavior incorrectly and cause unrelated and confusing errors downstream, or even cause errors to be missed. I'd thus recommend the following:
- Convert this to a Test-NG test and use a
@BeforeTest
method to set up a test fixture consisting of a map containing the desired entries. - Make each test case into its own test method that performs the method call and then makes assertions over the return value and expected result state of the map. Individual test methods is probably fine; no need to use a data provider for this.
- Probably add a "null hypothesis" test to ensure that the test fixture itself has the right properties, similar to the assertions at lines 38-44.
- Instead of fiddling around with strings, which have additional complexity caused by interning of string literals, I'd suggest using the technique I used in JDK-8285295 and create a
record Box(int i) { }
class. With this it's easy to get multiple instances that are != but are equals. - After further thought, maybe additional cases are necessary. For example, tests of calls to
remove
andreplace
with a key that is equals() but != to a key in the map.
@stuart-marks Updated. In addition, for API docs change, should we add apiNote on object equality code, like call computeIfPresent? |
@stuart-marks I have updated the tests to be based off that from JDK-8285295. Anything else that needs an update? |
@stuart-marks Just curious, would this fix target 19 or 20 at the current state? |
* <p>More formally, if this map contains a mapping from a key | ||
* {@code k} to a value {@code v} such that {@code key == k} | ||
* and {@code value == v}, then this method removes the mapping | ||
* for this key and returns {@code true}; otherwise it returns | ||
* {@code false}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API documentation of containsKey() and containsValue() should probably be updated to mention reference equality too. This doesn't need to be carried out in this PR, but maybe a new issue should be logged to double check the completeness of the IdentityHashMap API documentation and see where adding some similar text makes sense.
@liach This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
Could anyone take a look at this collections-related patch? |
OK, I will sponsor this. |
@liach This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
Is there any update that I should push to this patch? |
No action needed from you at this time. This is mostly waiting for me to get some time to focus on this. Thanks for your patience. |
@liach This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
Let's wait |
@liach This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
I swear, I'll get to this soon! I even pulled over the patch and started looking at it. @liach Actually one thing that would be helpful is if you could merge a recent master into the PR branch. Thanks. |
@liach Thanks for merging in a recent master. I started looking at this now. The tests and implementation look good. I've run this through our internal build&test system and the results look good too. Per @dfuch comment of June 1 I also looked at various method specs to see whether they would need to be updated to have reference-equality semantics. The specs are all in mostly pretty good shape, but I did add a few clauses here and there and I also added some cross-references, so I think the spec is overall much tighter now. I've pushed a branch with my proposed spec changes. Please merge from https://github.com/stuart-marks/jdk/tree/JDK-8178355-IdentityHashMap which should be based on the current head of this PR branch. The next step would be the CSR. I'd suggest updating in the (slightly) revised specs of remove() and replace() in the Specification section, but otherwise leaving them pretty much as they are, and not bothering with diffs for all the other tweaks I did. I'll generate a specdiff (internal tool, sorry, basically a structured HTML diff) and then post that to the CSR to provide a complete record of the changes. |
I have updated the specification section of the CSR with the updated |
OK, thanks for the updates. I've attached a specdiff and have tweaked the CSR in a few places and I've marked it reviewed. Go ahead and mark it Finalized. (I could actually do this myself, but I'd have to reassign the CSR to myself, make it Finalized, then reassign it back to you. That would save a messaging round-trip, but it would generate more notification spam. Plus I figured I'd give you the pleasure of hitting the Finalize button. :-D ) |
Now that the CSR is approved, this should be close to ready to go in. I just ran this through our CI system and there were a bunch of failures. Most likely they are unrelated, but I need to track this down and rerun the tests. I'll post an update when I get this figured out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, the issues with the test failures were a distraction. I had used the wrong version of an internal test suite. Once I figured that out and used the right version, everything passes.
OK to integrate! I will sponsor this.
@liach This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 441 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@stuart-marks) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
Thanks! /integrate |
/sponsor |
Going to push as commit 53905e6.
Your commit was automatically rebased without conflicts. |
@stuart-marks @liach Pushed as commit 53905e6. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Explicitly implement
remove
andreplace
inIdentityHashMap
to compare values by identity. Updated API documentation of these two methods (Preview) to mention such behavior.Progress
Issues
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/8259/head:pull/8259
$ git checkout pull/8259
Update a local copy of the PR:
$ git checkout pull/8259
$ git pull https://git.openjdk.org/jdk pull/8259/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 8259
View PR using the GUI difftool:
$ git pr show -t 8259
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/8259.diff