Skip to content

fix(qcommon,game): cast LHS of left shifts to unsigned to avoid signed-int UB#219

Open
blackden wants to merge 1 commit into
wolfetplayer:mainfrom
blackden:fix/ub-safe-integer-shifts
Open

fix(qcommon,game): cast LHS of left shifts to unsigned to avoid signed-int UB#219
blackden wants to merge 1 commit into
wolfetplayer:mainfrom
blackden:fix/ub-safe-integer-shifts

Conversation

@blackden

@blackden blackden commented Jun 7, 2026

Copy link
Copy Markdown

Summary

Fixes 5 instances of signed-integer left-shift UB caught by UBSAN on a -fsanitize=undefined build of the macOS arm64 port. All sites use bit masks where the integer is treated as a bit pattern, so casting the LHS to unsigned is bit-identical on 2's-complement (i.e., everywhere) but standards-clean.

  • code/qcommon/q_shared.c COM_BitCheck / COM_BitSet / COM_BitClear — 1 << bitNum where bitNum may reach 31.
  • code/qcommon/msg.c MSG_Write/ReadDeltaPlayerstate — 27 sites of 1 << i where i iterates over MAX_STATS / MAX_PERSISTANT / MAX_POWERUPS / MAX_PERKS / MAX_WEAPONS / MAX_AMMO / etc., which can reach 31. Wire format is bit-identical (verified by inspection and by the UBSAN before/after in the test plan).
  • code/game/g_misc.c SP_dlight color pack — ( i / 4 ) << 24 where i/4 routinely exceeds 127.

All three are vanilla-Q3-lineage code; the same lines exist in the id Software 2010 RTCW SP source drop, so this PR is also a candidate to forward to ioquake3 / RTCW MP downstream.

Test plan

  • Builds clean on macOS arm64 (Apple clang 21)
  • UBSAN-only build before/after on the same scripted scenario: 5 left shift of N by M places cannot be represented in type 'int' hits → 0
  • Game launches and plays mission 1 cleanly through a scripted ~90s autorun (map escape1 + give all + s_useOpenAL 0 + snd_restart + find / cmdlist / imagelist / savegame / disconnect / quit); no functional regression observed
  • Multiplayer wire-format unaffected — please confirm with your usual MP smoke test (the MSG_*DeltaPlayerstate changes touch the snapshot serialisation path)

Found during a broader UBSAN triage of the macOS arm64 build alongside the other macOS PRs (#216, #217, #218). A companion PR follows for a separate UBSAN finding in cgame.

🤖 Generated with Claude Code

…d-int UB

UBSAN flagged 5 left-shift patterns where a positive int value, when
shifted, becomes representable only as an unsigned int — undefined
behavior by C11 §6.5.7p4. All sites use bit masks where the eventual
storage type is treated as a bit pattern, so casting the LHS to unsigned
is bit-identical on 2's-complement (i.e., everywhere) but standards-clean.

Sites fixed:
- code/qcommon/q_shared.c COM_BitCheck/Set/Clear: `1 << bitNum` where
  bitNum may reach 31.
- code/qcommon/msg.c MSG_Write/ReadDeltaPlayerstate: 27 occurrences of
  `1 << i` where i iterates over MAX_STATS / MAX_PERSISTANT /
  MAX_POWERUPS / MAX_PERKS / MAX_WEAPONS / etc., which can reach 31.
  Wire format is bit-identical — verified by inspection.
- code/game/g_misc.c SP_dlight: `(i / 4) << 24` where i/4 routinely
  exceeds 127, causing the shift result to overflow signed int.

No behavior change on supported targets.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant