5.3.13Build Systems & Toolchain

Undefined Behavior Sanitizer (UBSan)

2,176 words10 min readdifficulty · medium

WHAT is Undefined Behavior?

WHY does UB even exist? So compilers can generate fast code without inserting defensive checks everywhere. The standard says "if the programmer writes X, we assume they never trigger the bad case, so we're free to optimize." That freedom is great for speed — and a nightmare for correctness when you do trigger it.


WHAT is UBSan (the mechanism)?

HOW you use it:

clang -fsanitize=undefined -fno-sanitize-recover=all -g file.c -o prog
./prog
# On the first UB: prints e.g.
# file.c:12:9: runtime error: signed integer overflow:
#   2147483647 + 1 cannot be represented in type 'int'
  • -g → keep debug info so it prints line numbers.
  • -fno-sanitize-recover=allabort on first error (otherwise UBSan reports and continues, which can hide the "first" cause).
  • Often combined: -fsanitize=undefined,address for UB + memory errors.

WHY a check is possible: deriving the overflow test from first principles

Take signed integer overflow for a 32-bit int. Representable range is [231,  2311].[-2^{31},\; 2^{31}-1].

WHY these bounds? Two's complement with n=32n=32 bits: the most significant bit has weight 2n1-2^{n-1}, the rest add positive weights up to 2n112^{n-1}-1. So: INT_MIN=231,INT_MAX=2311.\text{INT\_MIN} = -2^{31},\qquad \text{INT\_MAX} = 2^{31}-1.

Deriving the addition-overflow condition. Compute the true mathematical sum s=a+bs = a + b (in unbounded integers). Overflow = "ss is not representable": overflow(a,b)    s<231  s>2311.\text{overflow}(a,b) \iff s < -2^{31} \ \lor\ s > 2^{31}-1.

Why this step? UBSan can't compute in unbounded integers cheaply, so it uses a hardware fact: adding two same-sign numbers can only overflow toward that same sign. Formally, overflow occurs iff a and b have the same sign and the result's sign differs:


Worked examples


Figure — Undefined Behavior Sanitizer (UBSan)

UBSan vs its siblings


Active recall

Recall Forecast then verify (cover the answers!)
  • What flag enables UBSan? ⇒ -fsanitize=undefined
  • Why add -fno-sanitize-recover=all? ⇒ abort on first error so you see the true root cause
  • Why add -g? ⇒ so reports show file:line
  • Is signed overflow UB? Is unsigned overflow UB? ⇒ signed = UB; unsigned = well-defined (wraps mod 2322^{32})
  • How can n << 32 "work" yet be UB? ⇒ x86 masks the shift to & 31, giving a wrong-but-non-crashing result
Recall Feynman: explain to a 12-year-old

Imagine the language has some "forbidden moves" — like counting past the biggest number your calculator can show. If you make a forbidden move, the rulebook says "anything can happen" and the machine might give a weird answer, or crash, or seem fine and betray you later. UBSan is like a referee who watches the game and blows a whistle the instant you make a forbidden move, pointing at the exact line: "Right here! You tried to add past the maximum!" So instead of a spooky bug that shows up randomly next month, you get caught immediately.


Flashcards

What does UBSan stand for and what does it detect?
Undefined Behavior Sanitizer; a compiler runtime checker for undefined behavior (signed overflow, bad shifts, null/misaligned pointers, INT_MIN/-1, etc.).
Which compiler flag enables UBSan?
-fsanitize=undefined.
Why combine -fno-sanitize-recover=all with UBSan?
So the program aborts on the first violation instead of reporting and continuing, revealing the true root cause.
Is unsigned integer overflow UB in C?
No — unsigned arithmetic is defined to wrap modulo 2n2^n. Only signed overflow is UB.
Give the two's-complement range of a 32-bit int and why.
[231,2311][-2^{31}, 2^{31}-1]; the top bit has weight 231-2^{31}, remaining bits add up to 23112^{31}-1.
When does signed addition overflow (single-bit test)?
When both operands share a sign bit but the wrapped result's sign bit differs — exactly the CPU overflow flag.
Why is INT_MIN / -1 undefined?
The result 2312^{31} exceeds INT_MAX (23112^{31}-1); division overflows and may trap.
Why can n << 32 silently give the wrong answer on x86?
The hardware masks the shift count with & 31, so it computes n << 0; the standard still calls it UB.
Does a clean UBSan run prove the program is UB-free?
No — it only checks executed paths and not every UB category; pair with ASan/MSan/TSan and static analysis.
UBSan vs ASan — one line each.
UBSan = logic/arithmetic/pointer UB (cheap); ASan = memory errors like buffer overflow and use-after-free (~2× slower).
Why does UB exist at all in the standard?
To let compilers optimize aggressively without inserting defensive checks, assuming programmers never trigger the bad cases.

Connections

  • AddressSanitizer (ASan) — memory-error counterpart
  • Compiler Optimization Levels — why UB bites harder at -O2/-O3
  • Two's Complement Representation — basis of the overflow test
  • Integer Overflow & Wraparound
  • Static Analysis vs Dynamic Analysis
  • GCC and Clang Toolchains
  • Debugging with -g and Debug Info

Concept Map

imposes no requirements

enables

invisibly triggers

catches at runtime

enables

works via

inserts

on violation calls

prints file line

halts on first error

often combined with

Undefined Behavior

ISO C/C++ Standard

Fast Optimized Code

Ran fine so correct

UBSan Runtime Checker

-fsanitize=undefined

Compile-time Instrumentation

Check-then-report Code

libubsan Report

-fno-sanitize-recover=all

ASan Memory Errors

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, C/C++ mein kuch operations "Undefined Behavior" (UB) hote hain — matlab standard kehta hai "iska koi rule nahi, agar tum ye karoge toh kuch bhi ho sakta hai." Jaise signed integer ko INT_MAX se aage badhana, array ke bahar access karna, ya null pointer ko dereference karna. Problem ye hai ki UB kabhi-kabhi "chal jaata hai" — program sahi answer de deta hai, aur tumhe lagta hai sab theek hai. Par jaise hi optimizer badalta hai ya doosra CPU aata hai, wahi code galat answer de sakta hai ya crash kar sakta hai. Ye silent bugs sabse khatarnaak hote hain.

UBSan (Undefined Behavior Sanitizer) ek referee ki tarah hai. Tum -fsanitize=undefined -g flag ke saath compile karte ho, aur compiler har risky operation ke aas-paas chhota sa check daal deta hai. Jaise hi UB hota hai, wo turant bata deta hai: "file.c line 12: signed integer overflow, 2147483647 + 1 fit nahi ho raha int mein." Ab spooky bug ek clear error message ban gaya, exact line ke saath. -fno-sanitize-recover=all add karo taaki pehli galti pe hi ruk jaaye — warna wo report karke aage badh jaata hai aur asli root cause chhup jaata hai.

Intuition ye samjho: 32-bit int ka range hai 231-2^{31} se 23112^{31}-1. Two's complement mein overflow tabhi hota hai jab dono numbers ka sign same ho aur result ka sign flip ho jaaye — yehi CPU ka overflow flag ek hi instruction mein bata deta hai, isliye UBSan sasta hai (sirf 1.2x–2x slow). Yaad rakhna: unsigned overflow UB nahi hai (wo mod 2322^{32} wrap karta hai), sirf signed overflow UB hai. Aur ek zaroori baat — UBSan clean matlab bug-free nahi; ye sirf jo code paths tumne run kiye unhe check karta hai. Isliye ASan/MSan/TSan ke saath combine karo aur tests ki coverage badhao.

Go deeper — visual, from zero

Test yourself — Build Systems & Toolchain

Connections