
org.sonar.l10n.java.rules.java.S3034.html Maven / Gradle / Ivy
Why is this an issue?
In Java, numeric promotions happen when two operands of an arithmetic expression have different sizes. More specifically, narrower operands get
promoted to the type of wider operands. For instance, an operation between a byte
and an int
, will trigger a promotion of
the byte
operand, converting it into an int
.
When this happens, the sequence of 8 bits that represents the byte
will need to be extended to match the 32-bit long sequence that
represents the int
operand. Since Java uses two’s complement notation for signed number types, the promotion will fill the missing
leading bits with zeros or ones, depending on the sign of the value. For instance, the byte 0b1000_0000
(equal to -128
in
decimal notation), when promoted to int
, will become 0b1111_1111_1111_1111_1111_1111_1000_0000
.
When performing shifting or bitwise operations without considering that bytes are signed, the bits added during the promotion may have unexpected
effects on the final result of the operations.
How to fix it
This rule raises an issue any time a byte
value is used as an operand combined with shifts without being masked.
To prevent such accidental value conversions, you can mask promoted bytes to only consider the least significant 8 bits. Masking can be achieved
with the bitwise AND operator &
and the appropriate mask of 0xff
(255 in decimal and 0b1111_1111
in binary)
or, since Java 8, with the more convenient Byte.toUnsignedInt(byte b)
or Byte.toUnsignedLong(byte b)
.
Code examples
Noncompliant code example
public static void main(String[] args) {
byte[] bytes12 = BigInteger.valueOf(12).toByteArray(); // This byte array will be simply [12]
System.out.println(intFromBuffer(bytes12)); // In this case, the bytes promotion will not cause any issues, and "12" will be printed.
// Here the bytes will be [2, -128] since 640 in binary is represented as 0b0000_0010_1000_0000
// which is equivalent to the concatenation of 2 bytes: 0b0000_0010 = 2, and 0b1000_0000 = -128
byte[] bytes640 = BigInteger.valueOf(640).toByteArray();
// In this case, the shifting operation combined with the bitwise OR, will produce the wrong binary string and "-128" will be printed.
System.out.println(intFromBuffer(bytes640));
}
static int intFromBuffer(byte[] bytes) {
int originalInt = 0;
for (int i = 0; i < bytes.length; i++) {
// Here the right operand of the bitwise OR, which is a byte, will be promoted to an `int`
// and if its value was negative, the added ones in front of the binary string will alter the value of the `originalInt`
originalInt = (originalInt << 8) | bytes[i]; // Noncompliant
}
return originalInt;
}
Compliant solution
public static void main(String[] args) {
byte[] bytes12 = BigInteger.valueOf(12).toByteArray(); // This byte array will be simply [12]
System.out.println(intFromBuffer(bytes12)); // In this case, the bytes promotion will not cause any issues, and "12" will be printed.
// Here the bytes will be [2, -128] since 640 in binary is represented as 0b0000_0010_1000_0000
// which is equivalent to the concatenation of 2 bytes: 0b0000_0010 = 2, and 0b1000_0000 = -128
byte[] bytes640 = BigInteger.valueOf(640).toByteArray();
// This will correctly print "640" now.
System.out.println(intFromBuffer(bytes640));
}
static int intFromBuffer(byte[] bytes) {
int originalInt = 0;
for (int i = 0; i < bytes.length; i++) {
originalInt = (originalInt << 8) | Byte.toUnsignedInt(bytes[i]); // Compliant, only the relevant 8 least significant bits will affect the bitwise OR
}
return originalInt;
}
References
- CERT, NUM52-J. - Be aware of numeric promotion behavior
- Wikipedia - Two’s complement