#5294: widen Math.abs to long in BytesRaw.shift for Integer.MIN_VALUE#5406
Closed
gemshrine wants to merge 1 commit into
Closed
#5294: widen Math.abs to long in BytesRaw.shift for Integer.MIN_VALUE#5406gemshrine wants to merge 1 commit into
gemshrine wants to merge 1 commit into
Conversation
…er.MIN_VALUE Math.abs(Integer.MIN_VALUE) returns Integer.MIN_VALUE itself, because 2147483648 has no positive int representation. That made mod and offset in shift(int) come out negative, so the shiftLeft branch indexed into bytes[] with a deeply negative source and raised an uncaught ArrayIndexOutOfBoundsException instead of a domain error. Reachable from EO via bytes.left -2147483648 (i32 min value, whose neg is itself). Casting bits to long before Math.abs makes the positive amount 2147483648L, so mod becomes 0 and offset becomes 268435456: both non-negative and large enough that shiftLeft (and shiftRight for Integer.MAX_VALUE, which had the mirror large-offset case) takes the source-out-of-range branch and returns all-zero bytes, which is the correct wrap for a shift bigger than the array width. Added two targeted tests on BytesOf(long): shift(Integer.MIN_VALUE) and shift(Integer.MAX_VALUE) both yield 0L instead of crashing. Existing checksShift/checksShifts parameterized cases are untouched by the widening because their bits fit int and the arithmetic path is unchanged.
|
Contributor
Author
|
Closing as duplicate of #5300, which already applies the same |
Contributor
🚀 Performance AnalysisAll benchmarks are within the acceptable range. No critical degradation detected (threshold is 100%). Please refer to the detailed report for more information. Click to see the detailed report
✅ Performance gain: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Closes #5294.
BytesRaw.shift(int)computedMath.abs(bits) % Byte.SIZEandMath.abs(bits) / Byte.SIZEformodandoffset.Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE(the classic Java sign-bit overflow), so forbits == Integer.MIN_VALUEboth derived values came out negative —offset = -268435456. Sincebits < 0, the negative-offset value went straight intoshiftLeft, wheresource = index + offsetis deeply negative and doesn't satisfysource >= bytes.length, so the else branch executedbytes[source]with a negative index and threw an uncaughtArrayIndexOutOfBoundsExceptionrather than the domain-levelorg.eolang.erroran EO caller expects.Fix
Widen the absolute-value computation to
long:Math.abs((long) Integer.MIN_VALUE)is2147483648L— the positive representation the original code intended.positive % 8 = 0andpositive / 8 = 268435456, bothint-safe. The rest of the method is untouched.The offset then dominates any real byte-array length, so
shiftLeft'ssource >= bytes.lengthguard fires on every index and the result is all-zero bytes — the semantically correct answer for a shift bigger than the array width.Integer.MAX_VALUEhad the same mirror behaviour (huge positive offset viaMath.absthere was fine, but the shift result was accidentally correct only becausebits >= 0pickedshiftRight); with the widening it stays correct explicitly.Tests
Added two
@Testmethods next to the existing shift coverage inBytesOfTest:shiftsByIntegerMinValueYieldsZeroWithoutCrashing—BytesOf(0xFFFFFFFFL).shift(Integer.MIN_VALUE).asNumber(Long.class)returns0Linstead of raising AIOOBE. This is the exact reproducer path from the issue.shiftsByIntegerMaxValueYieldsZero— mirror on the positive side, guarding theshiftRightbranch's large-offset behaviour.The existing
checksShiftandchecksShiftsparameterised cases are untouched — theirbitsvalues fit in anintafterMath.abs, so the arithmetic path they exercise is byte-for-byte identical before and after the widening.Reachability from EO
Cited in the issue:
bytes.eodefinesright x.neg > [x] > left, andi32.min-value.negis itself (two's-complement identity), so an EO caller of<bytes>.left <i32-min>lands directly in the buggy path. After this fix that call returns all-zero bytes without a JVM-level exception.