Bit-field of width one declared with a signed type
ID |
objectivec.correctness.signed_bit_field |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Type Safety |
Language |
Objective-C |
Description
Reports a bit-field of width one whose type is signed - signed int, signed char, or a plain int, char, short, long, int8_t or NSInteger, none of which is unsigned. A bit-field with an unsigned type, one wider than a single bit, and a BOOL field - whose underlying type differs between targets - are not reported. Wider bit-fields and unsigned ones are reported by the avoid-bit-fields rule instead, so the two rules never report the same member. A case label, a keyword argument in a message send, a ternary, and occurrences inside a block comment are not reported.
Rationale
A signed bit-field spends its only bit on the sign, so the field holds exactly two values - 0 and -1. Storing 1 into it therefore reads back as -1, and a test written as if (flags.direction == 1) is never true, while if (flags.direction) still works, which is why the defect survives so long: the flag appears to function until something compares it against a value. Whether a plain int bit-field is signed at all is implementation-defined, so the same declaration can behave one way on one target and the other way on the next.
The following code illustrates the pattern detected by this rule:
struct DeviceFlags {
// FLAGGED: Bit-field of width one declared with a signed type
signed int direction : 1;
// FLAGGED: Bit-field of width one declared with a signed type
int polarity : 1;
// FLAGGED: Bit-field of width one declared with a signed type
signed char toggle : 1;
// FLAGGED: Bit-field of width one declared with a signed type
int8_t marker : 1;
// FLAGGED: Bit-field of width one declared with a signed type
NSInteger mark : 1;
unsigned int enabled : 1;
uint8_t retries : 1;
NSUInteger counter : 1;
BOOL ready : 1;
signed int span : 3;
int wide : 4;
int plainMember;
};
Remediation
Declare the field unsigned - unsigned int direction : 1 - so the single bit holds 0 and 1 as written. Where the field genuinely needs to represent a signed value, give it at least two bits, and prefer a separate BOOL member or a named mask over a bit-field where the packing is not actually required.