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

6187113: DefaultListSelectionModel.removeIndexInterval(0, Integer.MAX_VALUE) fails #10409

Closed
wants to merge 15 commits into from

Conversation

prsadhuk
Copy link
Contributor

@prsadhuk prsadhuk commented Sep 23, 2022

DefaultListSelectionModel.removeIndexInterva accepts int value which allows it to take in Integer.MAX_VALUE theoratically but it does calculation with that value which can results in IOOBE.
Fix is to make sure the calculation stays within bounds.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8295329 to be approved

Issues

  • JDK-6187113: DefaultListSelectionModel.removeIndexInterval(0, Integer.MAX_VALUE) fails
  • JDK-8295329: DefaultListSelectionModel.removeIndexInterval(0, Integer.MAX_VALUE) fails (CSR)

Reviewers

Contributors

  • Alexey Ivanov <aivanov@openjdk.org>

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 10409

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

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/10409.diff

@bridgekeeper
Copy link

bridgekeeper bot commented Sep 23, 2022

👋 Welcome back psadhukhan! 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 Sep 23, 2022

@prsadhuk The following label will be automatically applied to this pull request:

  • client

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the client client-libs-dev@openjdk.org label Sep 23, 2022
@openjdk openjdk bot added the rfr Pull request is ready for review label Sep 23, 2022
@mlbridge
Copy link

mlbridge bot commented Sep 23, 2022

@openjdk openjdk bot removed the rfr Pull request is ready for review label Oct 7, 2022
@openjdk openjdk bot added the rfr Pull request is ready for review label Oct 7, 2022
@openjdk openjdk bot added the csr Pull request needs approved CSR before integration label Oct 14, 2022
@prsadhuk
Copy link
Contributor Author

CSR raised..https://bugs.openjdk.org/browse/JDK-8295329...please review

Copy link
Member

@aivanov-jdk aivanov-jdk left a comment

Choose a reason for hiding this comment

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

I was wrong, disallowing Integer.MAX_VALUE for setSelectionInterval is not enough. Moreover, if Integer.MAX_VALUE isn't accepted for setSelectionInterval and removeIndexInterval, it should be rejected in other places too. Otherwise, selectionModel.isSelectedIndex(Integer.MAX_VALUE)) being valid looks inconsistent.

Even when the gapLength remains positive, the following loop could throw IOOBE because a negative index being accessed. The following code

        selectionModel.setSelectionInterval(Integer.MAX_VALUE - 2, Integer.MAX_VALUE - 1);
        selectionModel.removeIndexInterval(0, Integer.MAX_VALUE - 1);

throws

Exception in thread "main" java.lang.IndexOutOfBoundsException: bitIndex < 0: -2147483648
	at java.base/java.util.BitSet.get(BitSet.java:626)
	at java.desktop/javax.swing.DefaultListSelectionModel.removeIndexInterval(DefaultListSelectionModel.java:713)
	at SelectionModelTest.main(SelectionModelTest.java)

where line 713

I suggested the code to fix that problem.

Then the infinite loop in changeSelection

for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) {

for MAX_VALUE is resolved by reversing it:

-        for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) {
+        for(int i = Math.max(setMax, clearMax); i >= Math.min(setMin, clearMin); i--) {

The proposed changes will resolve the bug.

Comment on lines 712 to 714
for(int i = rmMinIndex; i <= maxIndex; i++) {
if ((i + gapLength) > gapLength) {
setState(i, value.get(i + gapLength));
} else {
setState(i, value.get(gapLength));
}
setState(i, value.get(i + gapLength));
}
Copy link
Member

Choose a reason for hiding this comment

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

The for-loop should look like this:

        for(int i = rmMinIndex; i >= 0 && i <= maxIndex; i++) {
            setState(i, (i <= Integer.MAX_VALUE - gapLength)
                        && (i + gapLength >= minIndex)
                        && value.get(i + gapLength));
        }

It breaks if i becomes negative. In the body, the first condition (i <= Integer.MAX_VALUE - gapLength) prevents accessing indexes greater than Integer.MAX_VALUE, the second condition (i + gapLength >= minIndex) is an optimisation, the values for indexes below minIndex are false, so the call to value.get is skipped. (The second condition is not required.)

@aivanov-jdk
Copy link
Member

There exists at least one other problem because of integer overflow in insertIndexInterval.

This code

        selectionModel.setSelectionInterval(Integer.MAX_VALUE - 1, Integer.MAX_VALUE);
        selectionModel.insertIndexInterval(Integer.MAX_VALUE - 1, Integer.MAX_VALUE, true);

throws

Exception in thread "main" java.lang.IndexOutOfBoundsException: bitIndex < 0: -2
	at java.base/java.util.BitSet.get(BitSet.java:626)
	at java.desktop/javax.swing.DefaultListSelectionModel.set(DefaultListSelectionModel.java:311)
	at java.desktop/javax.swing.DefaultListSelectionModel.setState(DefaultListSelectionModel.java:629)
	at java.desktop/javax.swing.DefaultListSelectionModel.insertIndexInterval(DefaultListSelectionModel.java:657)
	at SelectionModelTest.main(SelectionModelTest.java)

Would you like to include it as part of this fix? Shall I submit a new bug for this?

@prsadhuk
Copy link
Contributor Author

Thanks for your suggestive code..
I guess you are now ok with handling Integer.MAX_VALUE in the code rather than only in javadoc.

BTW, your code change resulted in JCK failure so I have used a mix of what I had in previous iterations and your code and this satisfied JCK as well as the unit tests.
Regarding insertIndexInterval I guess it's better to address in this PR as it has same issue in same file, so I have added a fix for it also in this PR.

Copy link
Member

@aivanov-jdk aivanov-jdk left a comment

Choose a reason for hiding this comment

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

This is tough. The number of test cases has grown to 25. There are many failures.

if ((i + length) >= length) {
setState(i + length, value.get(i));
} else {
setState(i, value.get(i));
Copy link
Member

Choose a reason for hiding this comment

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

This operation in the else block doesn't make sense, you can just skip it.

@@ -654,7 +658,11 @@ public void insertIndexInterval(int index, int length, boolean before)
* insMinIndex).
*/
for(int i = maxIndex; i >= insMinIndex; i--) {
setState(i + length, value.get(i));
if ((i + length) >= length) {
Copy link
Member

Choose a reason for hiding this comment

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

I suggest using i <= Integer.MAX_VALUE - length. Yet I'm still unsure it's enough and correct.

@@ -654,7 +658,11 @@ public void insertIndexInterval(int index, int length, boolean before)
* insMinIndex).
*/
for(int i = maxIndex; i >= insMinIndex; i--) {
Copy link
Member

Choose a reason for hiding this comment

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

insertIndexInterval doesn't verify its parameters: negative index, negative length should be rejected right away.

Comment on lines 716 to 721
if ((i + gapLength) >= gapLength) {
setState(i, value.get(i + gapLength));
} else {
setState(i, value.get(gapLength));
break;
}
Copy link
Member

Choose a reason for hiding this comment

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

This does not work. It must not break. With this code, 5 of my tests fail.

For example,

        selectionModel.setSelectionInterval(0, Integer.MAX_VALUE);
        selectionModel.removeIndexInterval(0, Integer.MAX_VALUE);

must result in empty selection. All the possible indexes were selected, you removed all the possible indexes (so that “the list” contains no elements at all). This test case fails either way, unfortunately: selectionModel.isSelectedIndex(0) still returns true.

This edge case where rmMinIndex = 0 and rmMaxIndex = Integer.MAX_VALUE should probably be short-circuited: setState(i, false) for all the elements. Other values are handled correctly by my suggested code, as far as I can see.

This test case:

        selectionModel.setSelectionInterval(0, Integer.MAX_VALUE);
        selectionModel.removeIndexInterval(1, Integer.MAX_VALUE);

passed before and now it fails again. Here, selectionModel.isSelectedIndex(0) must be true.

Copy link
Member

Choose a reason for hiding this comment

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

If we handle the edge case separately, it looks better and the first test case as well as other tests for remove pass successfully:

    public void removeIndexInterval(int index0, int index1)
    {
        if (index0 < 0 || index1 < 0) {
            throw new IndexOutOfBoundsException("index is negative");
        }

        int rmMinIndex = Math.min(index0, index1);
        int rmMaxIndex = Math.max(index0, index1);

        if (rmMinIndex == 0 && rmMaxIndex == Integer.MAX_VALUE) {
            for (int i = Integer.MAX_VALUE; i >= 0; i--) {
                setState(i, false);
            }
            // min and max are updated automatically by the for-loop

            // TODO Update anchor and lead
            return;
        }

        int gapLength = (rmMaxIndex - rmMinIndex) + 1;

        /* Shift the entire bitset to the left to close the index0, index1
         * gap.
         */
        for(int i = rmMinIndex; i >= 0 && i <= maxIndex; i++) {
            setState(i, (i <= Integer.MAX_VALUE - gapLength)
                        && (i + gapLength >= minIndex)
                        && value.get(i + gapLength));
        }

        // The rest of the method
    }

Comment on lines +61 to +62
selectionModel.setSelectionInterval(Integer.MAX_VALUE - 1, Integer.MAX_VALUE);
selectionModel.insertIndexInterval(Integer.MAX_VALUE - 1, Integer.MAX_VALUE, true);
Copy link
Member

Choose a reason for hiding this comment

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

I think testing that it does not throw an exception is not enough. You should verify that the data selection model holds is correct.

According to the spec, the interval from Integer.MAX_VALUE - 1 to Integer.MAX_VALUE should remain selected.

Comment on lines 651 to 653
if (length < 0 || index < 0) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Throw IOOBE as it's done in other methods. Silently ignoring bad parameters isn't good, likely it means an error in the calling code.

Comment on lines 654 to 656
if (index + length < 0) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Other methods, like removeIndexInterval, accept parameters which result in integer overflow, I think this should allow it too. Yet you have to ensure index + length isn't greater than Integer.MAX_VALUE. I mean length should be normalized in such a case, so that it doesn't cause the overflow.

Something like length = Integer.MAX_VALUE - index, it's off the top of my head, I haven't verified it's correct, it's just an idea on how this should be handled.

Comment on lines 667 to 669
if (length + maxIndex < 0) {
maxIndex = Integer.MAX_VALUE - length;
}
Copy link
Member

Choose a reason for hiding this comment

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

You must not change maxIndex this way — you just broke the class invariant: maxIndex points to the highest selected index. Now it doesn't.

You have to make sure i + length <= Integer.MAX_VALUE but you must not modify maxIndex directly. This would probably be handled automatically if you normalize length. If not, the body of if may need a condition. Alternatively, a better way, the start value of i should be reduced so that is always i + length <= Integer.MAX_VALUE and doesn't cause integer overflow.

Comment on lines 733 to 735
setState(i, (i <= Integer.MAX_VALUE - gapLength)
&& (i + gapLength >= minIndex)
&& value.get(i + gapLength));
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer if the && operators are aligned to the opening parenthesis of the first condition, it makes it clearer that the condition is part of the second parameter. I don't insist on this one, the wrapping style is not agreed on.

Copy link
Member

@aivanov-jdk aivanov-jdk left a comment

Choose a reason for hiding this comment

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

I already mentioned that I wrote a comprehensive unit-test to test the changes to removeIndexInterval and insertIndexInterval. The latest version of my test SelectionModelTest.java.

With the current code in the PR, six (6) test cases still fail. The changes I've suggested should resolve those failures. (I've been testing and proposing fixes to resolve test failures since the start of this code review.)

The test can be run as a jtreg test or as a standalone app. Depending on the hardware, it may take up to 10 minutes; the more cores the system has, the faster the test completes as all the test cases are run in parallel. (The minimum time I've ever seen is around 2–3 minutes.)

I can contribute the test to OpenJDK if deemed useful. I believe such a fix can't be done without extensive testing which verifies all the values in the object remain correct.

Comment on lines 672 to 673
boolean setInsertedValues = ((getSelectionMode() == SINGLE_SELECTION) ?
false : value.get(index));
Copy link
Member

Choose a reason for hiding this comment

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

I propose resolving an IDE warning and simplifying this condition:

Suggested change
boolean setInsertedValues = ((getSelectionMode() == SINGLE_SELECTION) ?
false : value.get(index));
boolean setInsertedValues = (getSelectionMode() != SINGLE_SELECTION
&& value.get(index));

Although this change is unrelated.

@@ -449,6 +449,10 @@ private void changeSelection(int clearMin, int clearMax,
if (shouldClear) {
clear(i);
}
// Integer overlfow
if (i + 1 < i) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (i + 1 < i) {
if (i == Integer.MAX_VALUE) {

Will it be clearer this way? (And one less operation: no addition.)

As I mentioned before, I would prefer reversing the loop and iterating from max to min instead. But it causes a failure in JCK which I can't explain because the effective result is the same. Moreover, I believe iterating from max to min is more efficient as touching the maximum index will ensure the internal storage in the underlying BitSet is allocated right away to fit all the bits whereas the storage gets continuously re-allocated and copied as the accessed indexes grow.

*/
import javax.swing.DefaultListSelectionModel;

public class TestDefListModelException {
Copy link
Member

Choose a reason for hiding this comment

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

I propose moving this test to JList folder which already contains several tests which use ListSelectionModel via JList.

@aivanov-jdk
Copy link
Member

With the current code in the PR, six (6) test cases still fail.

The failure of test10 is expected: you adjust the value of length so that insMaxIndex can't be greater than Integer.MAX_VALUE. It is a valid failure, the test needs changing: lead should be become Integer.MAX_VALUE + 1 = -2147483648 (0x80000000).

Other five failures are valid ones, minIndex and maxIndex in the object don't have the expected values.

@aivanov-jdk
Copy link
Member

Let's keep it open. CSR review has stalled.

@bridgekeeper
Copy link

bridgekeeper bot commented Dec 27, 2022

@prsadhuk 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!

@aivanov-jdk
Copy link
Member

The CSR was approved two days ago, yet the bots didn't update the status of the issue. It should be ready for integration.

@prsadhuk
Copy link
Contributor Author

/label add csr

@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk
The label csr is not a valid label.
These labels are valid:

  • serviceability
  • hotspot
  • hotspot-compiler
  • ide-support
  • kulla
  • i18n
  • shenandoah
  • jdk
  • javadoc
  • security
  • hotspot-runtime
  • jmx
  • build
  • nio
  • client
  • core-libs
  • compiler
  • net
  • hotspot-gc
  • hotspot-jfr

@prsadhuk
Copy link
Contributor Author

/csr

@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk an approved CSR request is already required for this pull request.

@prsadhuk
Copy link
Contributor Author

/csr JDK-8295329

@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk usage: /csr [needed|unneeded], requires that the issue the pull request refers to links to an approved CSR request.

@prsadhuk
Copy link
Contributor Author

/csr unneeded

@openjdk openjdk bot removed the csr Pull request needs approved CSR before integration label Jan 30, 2023
@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk determined that a CSR request is not needed for this pull request.

@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk 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:

6187113: DefaultListSelectionModel.removeIndexInterval(0, Integer.MAX_VALUE) fails

Co-authored-by: Alexey Ivanov <aivanov@openjdk.org>
Reviewed-by: aivanov

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 no new commits pushed to the master branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jan 30, 2023
@prsadhuk
Copy link
Contributor Author

/csr needed

@openjdk openjdk bot added the csr Pull request needs approved CSR before integration label Jan 30, 2023
@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@prsadhuk please create a CSR request for issue JDK-6187113 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Jan 30, 2023
@openjdk openjdk bot added ready Pull request is ready to be integrated and removed csr Pull request needs approved CSR before integration labels Jan 30, 2023
@prsadhuk
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Jan 30, 2023

Going to push as commit c2ebd17.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jan 30, 2023
@openjdk openjdk bot closed this Jan 30, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jan 30, 2023
@openjdk
Copy link

openjdk bot commented Jan 30, 2023

@prsadhuk Pushed as commit c2ebd17.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@prsadhuk prsadhuk deleted the JDK-6187113 branch June 14, 2023 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client client-libs-dev@openjdk.org integrated Pull request has been integrated
3 participants