/** * @file bitmasks.c * @brief The AKGL_BITMASK_* macros, including the ways an unparenthesized one breaks. * * These are macros, so the only thing that checks them is a call site. Most of * this file is the ordinary arithmetic; the composition cases at the bottom are * the ones that matter, because until 0.5.0 the macros expanded to bare * expressions and every one of them was a silent misparse waiting for a caller. */ #include #include int main(void) { int mask = 0; int counter = 0; AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_ALIVE); if ( mask != AKGL_ACTOR_STATE_ALIVE ) return 1; AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_LEFT); if ( mask != (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT) ) return 1; AKGL_BITMASK_DEL(mask, AKGL_ACTOR_STATE_ALIVE); if ( mask != (AKGL_ACTOR_STATE_FACE_LEFT) ) return 1; AKGL_BITMASK_CLEAR(mask); if ( mask != 0 ) return 1; AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_LEFT); if ( !(AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_FACE_LEFT)) ) return 1; mask = AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP; AKGL_BITMASK_DEL(mask, AKGL_ACTOR_STATE_FACE_ALL); if ( mask != AKGL_ACTOR_STATE_ALIVE ) return 1; AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_MOVING_DOWN); AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_DOWN); if ( mask != (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_MOVING_DOWN | AKGL_ACTOR_STATE_FACE_DOWN) ) return 1; // ---- composition, which is what the parentheses are for ---- // Negation. Unparenthesized, AKGL_BITMASK_HAS expanded to a bare // `(x & y) == y`, so `!` bound to the `&` and the whole thing read as // `!(mask & bit) == bit`. // // Note which case catches that and which does not. For a bit that *is* // set, `!(nonzero)` is 0 and `0 == bit` is false -- the same answer the // correct parse gives, so a test written that way passes either way. It // only diverges for a bit that is *not* set and whose value is not 1: // `!(0)` is 1, and `1 == 64` is false where the answer should be true. // That is the shape below, and it is why this was worth a real case rather // than an obvious one. mask = AKGL_ACTOR_STATE_ALIVE; if ( !AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_ALIVE) ) return 1; if ( AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_DEAD) ) return 1; if ( !AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_DEAD) ) { counter = 1; } else { return 1; } if ( counter != 1 ) return 1; // The same divergence through AKGL_BITMASK_HASNOT, which was written the // same way: `!(0) != 64` is true where the answer should be false. if ( !AKGL_BITMASK_HASNOT(mask, AKGL_ACTOR_STATE_DEAD) ) return 1; if ( AKGL_BITMASK_HASNOT(mask, AKGL_ACTOR_STATE_ALIVE) ) return 1; // As an operand of a lower-precedence operator. if ( AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_ALIVE) == false ) return 1; if ( (AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_ALIVE) & 1) != 1 ) return 1; // The mutating macros as expressions rather than statements, and with an // argument that is itself an expression: `x |= y` unparenthesized cannot be // used in either position. mask = 0; counter = (AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_DYING)); if ( counter != AKGL_ACTOR_STATE_DYING ) return 1; if ( (AKGL_BITMASK_CLEAR(mask)) != 0 ) return 1; if ( mask != 0 ) return 1; // AKGL_BITMASK_CLEAR carries no trailing semicolon, so it is a statement // like any other and does not swallow the one after it when it is the whole // body of an unbraced `if`. mask = AKGL_ACTOR_STATE_ALIVE; counter = 0; if ( mask != 0 ) AKGL_BITMASK_CLEAR(mask); counter = 1; if ( mask != 0 ) return 1; if ( counter != 1 ) return 1; return 0; }