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

8347753: VetoableListDecorator doesn't accept its own sublists for bulk operations #1679

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

mstr2
Copy link
Collaborator

@mstr2 mstr2 commented Jan 15, 2025

Passing a VetoableListDecorator.subList() to any of its bulk operations (addAll, setAll, removeAll, retainAll) throws ConcurrentModificationException. The reason is that the VetoableListDecorator.modCount field is incremented before the underlying list's bulk operation is invoked, which causes a mismatch when the sublist is interrogated by the bulk operation.

However, simply updating the modCount field after the underlying list was modified also doesn't work, as in this case listeners can't see the correct value for modCount in their callback. The fix is to make a defensive copy of the sublist before invoking the underlying list's bulk operation.

/reviewers 2


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8347753: VetoableListDecorator doesn't accept its own sublists for bulk operations (Bug - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1679/head:pull/1679
$ git checkout pull/1679

Update a local copy of the PR:
$ git checkout pull/1679
$ git pull https://git.openjdk.org/jfx.git pull/1679/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1679

View PR using the GUI difftool:
$ git pr show -t 1679

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1679.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jan 15, 2025

👋 Welcome back mstrauss! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jan 15, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Ready for review label Jan 15, 2025
@openjdk
Copy link

openjdk bot commented Jan 15, 2025

@mstr2
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@mlbridge
Copy link

mlbridge bot commented Jan 15, 2025

Webrevs

@@ -387,14 +387,37 @@ public int hashCode() {
return list.hashCode();
}

private class VetoableSubListDecorator implements List<E> {
/**
* Returns the specified collection as an unmodifiable list that can safely be used in all bulk
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it might be easier to create a defensive copy always?

In other words, can we guarantee that it is impossible for the user to create a convoluted code involving maybe two VetoableListDecorators where the second one loops back the changes to the first one, however ridiculous that might sound?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I see it, the situation that erroneously triggers ConcurrentModificationException only happens when VetoableListDecorator accesses its own sublist:

    try {
        modCount++;
        boolean ret = list.addAll(index, c); // --> c is its own sublist
        ...

Since modCount is modified first, and the sublist refers back to the same modified modCount, the exception occurs. It can't occur when we are dealing with another list (or a sublist of another list), since in this case there is no self-referential conflict.

The way ArrayList circumvents this problem is by incrementing modCount only after the operation is done, not before it has started; it doesn't create a defensive copy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for clarification! I'll try to come up with a test (and I think even if I succeed, it does not mean that your solution is not good, since the caller can always create a defensive copy themselves).

BTW, what were you doing to trigger this bug?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, what were you doing to trigger this bug?

This happened when I was writing some tests for the other VetoableListDecorator PRs...

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scenario I was referring to is impossible, I think.

One possible alternative is to create the defensive copy each time, this will save one extra pointer every time an iterator or a sublist gets created (these objects might be long lived). The code in this PR creates a copy in many (most?) cases anyway, and in my opinion, the memory is more precious resource that CPU cycles (i.e. using extra memory costs many more CPU cycles in garbage collection etc.), so please consider that.

  • some minor suggestions

public void testAddAll_subList() {
list.addAll(list.subList(0, 2));
assertSingleCall(new String[] {"foo", "bar"}, new int[] {4, 4});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: also check that the list contains the newly added elements?
(here and in added tests that involve subList?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added checks for the list content in all modified tests.

@kevinrushforth kevinrushforth self-requested a review January 23, 2025 17:17
@mstr2
Copy link
Collaborator Author

mstr2 commented Jan 27, 2025

One possible alternative is to create the defensive copy each time, this will save one extra pointer every time an iterator or a sublist gets created (these objects might be long lived). The code in this PR creates a copy in many (most?) cases anyway, and in my opinion, the memory is more precious resource that CPU cycles (i.e. using extra memory costs many more CPU cycles in garbage collection etc.), so please consider that.

I don't quite understand what you mean. Can you elaborate?
In particular, what does "save one extra pointer" mean?

@andy-goryachev-oracle
Copy link
Contributor

Can you elaborate?
what does "save one extra pointer" mean?

What I mean is simply get rid of the extra pointer like VetoableListIteratorDecorator.parent and the related logic, and simply create a copy of List/Collection each time.

@mstr2
Copy link
Collaborator Author

mstr2 commented Jan 27, 2025

Can you elaborate?
what does "save one extra pointer" mean?

What I mean is simply get rid of the extra pointer like VetoableListIteratorDecorator.parent and the related logic, and simply create a copy of List/Collection each time.

Okay, I see. We can't do that, because a sublist is specified to be a live view onto the source list. After all, list.subList(0, 3).clear() is valid code, and the result is that three elements are removed from the source list.

@andy-goryachev-oracle
Copy link
Contributor

We can't do that, because a sublist is specified to be a live view onto the source list.

good point, thanks for pointing this out.

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfr Ready for review
Development

Successfully merging this pull request may close these issues.

2 participants