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

Fix IntegerOverflow exception in postings encoding as group-varint #13376

Merged
merged 10 commits into from
May 17, 2024

Conversation

easyice
Copy link
Contributor

@easyice easyice commented May 16, 2024

Closes: #13373

This exception occurs because a negative integer value stores as positive long. In line 376, after a long value << 1, if the sign bit of the integer value is 1, it will be a negative number as integer, but a positive numbers as long, when we stores this value as positive long, it would cause Math.toIntExact to throw ArithmeticException exception.

// Group vInt encode the remaining doc deltas and freqs:
if (writeFreqs) {
for (int i = 0; i < docBufferUpto; i++) {
docDeltaBuffer[i] = (docDeltaBuffer[i] << 1) | (freqBuffer[i] == 1 ? 1 : 0);
}
}
docOut.writeGroupVInts(docDeltaBuffer, docBufferUpto);

POC code:

    public void testDelta() {
        int delta = 1 << 30;
        long deltaL = delta;
        deltaL = (deltaL << 1) | 1;
        System.out.println(deltaL > Integer.MAX_VALUE); // true
        System.out.println(deltaL); // 2147483649
        System.out.println((int) deltaL); // -2147483647
        Math.toIntExact(deltaL); // ArithmeticException: integer overflow
    }

TODO:

  • add more tests

@easyice
Copy link
Contributor Author

easyice commented May 16, 2024

This change keeps the input values of writeGroupVInts explained as integer, instead of a big number greater than Integer.MAX_VALUE

@easyice
Copy link
Contributor Author

easyice commented May 16, 2024

The essence of this issue is how to deal with the integer value with the sign bit as 1 (like this integer overflow case). We have two options.

  • The first is pass a negative long value to writeGroupVInts, and also return a negative value in readGroupVInt. Previously, we didn't consider encoding a negative value in group-varint. Currently, readGroupVInt may return a positive or negative value in different implementations.
  • The second approach is only pass positive numbers to writeGroupVInts / readGroupVInt, and replace Math.toIntExact with (int).

The first approach feels more reasonable.

@jpountz
Copy link
Contributor

jpountz commented May 17, 2024

Thanks for looking into it! Your approach works, but I'm tempted to fix it the other way around, by no longer checking if values are in the expected range with Math.toIntExact but rather by checking that the value is in the [0, 2**32) range?

@easyice
Copy link
Contributor Author

easyice commented May 17, 2024

That's also a good idea! by this approach we can make writeGroupVInts /readGroupVInt use positive only. it's actually handled as an unsigned integer, so we don't need to consider the case of encoding negative in group-varint. i will update the code.

@easyice
Copy link
Contributor Author

easyice commented May 17, 2024

I pushed the requested changes, @jpountz . No rush, just wanted to let you know.

final int v = 1 << 30;
final long[] values = new long[4];
values[0] = v;
values[0] <<= 1; // values[0] = 2147483648 as long, but as int it is -2147483648
Copy link
Contributor

Choose a reason for hiding this comment

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

why not do values[0] = 1L << 31 directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

* @param values the values to write
* @param values the values to write. Note: if original integer is negative, it should also be
* negative as long, not positive which is greater than Integer.MAX_VALUE, that will cause
* integer overflow exception in {@link Math#toIntExact(long)}.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not mention this implementation detail.

Suggested change
* integer overflow exception in {@link Math#toIntExact(long)}.
* integer overflow exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change has reverted, there is no change in DataOutput in current fix approach.

private static int toInt(long value) {
if (value < 0 || value > 0xFFFFFFFFL) {
throw new ArithmeticException("integer overflow");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use Long.compareUnsigned? (if (Long.compareUnsigned(value, 0xFFFFFFFFL) > 0))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great!

Copy link
Contributor Author

@easyice easyice left a comment

Choose a reason for hiding this comment

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

Thanks for reviewing!

private static int toInt(long value) {
if (value < 0 || value > 0xFFFFFFFFL) {
throw new ArithmeticException("integer overflow");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great!

@mikemccand
Copy link
Member

It's hard for me to tell what the expected user impact here is? Does the exception happen because the remainder part of a postings list (after all length 128 blocks are done), which we now encode with GroupVInt, had a docID delta that was >= 1<<30, when the postings are also storing freqs?

I guess because we don't see too many users reporting this, it is likely rare-ish. But is the GroupVInt change released in 9.x? Is this maybe enough to warrant a bugfix release if so?

@easyice
Copy link
Contributor Author

easyice commented May 17, 2024

Does the exception happen because the remainder part of a postings list (after all length 128 blocks are done), which we now encode with GroupVInt, had a docID delta that was >= 1<<30, when the postings are also storing freqs?

Yes, exactly. I guess the docID delta that was >= 1<<30 s likely rare-ish

But is the GroupVInt change released in 9.x? Is this maybe enough to warrant a bugfix release if so?

GroupVInt was released at 9.9.0 https://lucene.apache.org/core/9_9_0/changes/Changes.html#v9.9.0.optimizations
+1 for bugfix release, what do you think?

@easyice easyice merged commit 22ddd48 into apache:main May 17, 2024
3 checks passed
@jpountz
Copy link
Contributor

jpountz commented May 17, 2024

+1 to a bugfix release

@jpountz
Copy link
Contributor

jpountz commented May 17, 2024

Can you backport to the 9.10 branch?

@easyice
Copy link
Contributor Author

easyice commented May 17, 2024

Okay, I will backport to 9.10/branch_9x.

asfgit pushed a commit that referenced this pull request May 17, 2024
…13376)

The exception happen because the tail postings list block, which encoding with GroupVInt, had a docID delta that was >= 1<<30, when the postings are also storing freqs.
asfgit pushed a commit that referenced this pull request May 17, 2024
…13376)

The exception happen because the tail postings list block, which encoding with GroupVInt, had a docID delta that was >= 1<<30, when the postings are also storing freqs.
@easyice
Copy link
Contributor Author

easyice commented May 17, 2024

Backport completed and added an entry under 9.10.1 Bug Fixes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DataOutput.writeGroupVInts throws IntegerOverflow exception during merging
3 participants