101 lines
4.3 KiB
Plaintext
101 lines
4.3 KiB
Plaintext
== 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
|
|
|
|
[source,java,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
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
|
|
|
|
[source,java,diff-id=1,diff-type=compliant]
|
|
----
|
|
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
|
|
|
|
* https://wiki.sei.cmu.edu/confluence/x/kDZGBQ[CERT, NUM52-J.] - Be aware of numeric promotion behavior
|
|
* https://en.wikipedia.org/wiki/Signed_number_representations#Two.27s_complement[Wikipedia] - Two's complement
|
|
|
|
|
|
ifdef::env-github,rspecator-view[]
|
|
|
|
'''
|
|
== Implementation Specification
|
|
(visible only on this page)
|
|
|
|
=== Message
|
|
|
|
Prevent "int" promotion by adding "& 0xff" to this expression.
|
|
|
|
|
|
'''
|
|
== Comments And Links
|
|
(visible only on this page)
|
|
|
|
=== on 22 Feb 2016, 20:10:17 Ann Campbell wrote:
|
|
Thanks for the rewrite [~michael.gumowski]. Looks good
|
|
|
|
endif::env-github,rspecator-view[]
|