bump a number past the edge and it wraps
++ / -- & Integer Overflow
The ++ and -- operators fold a read, a write, and a value into one token — and the value they hand back is not always the one you expect. Push a bounded integer one step too far and it doesn't overflow upward, it wraps: MAX + 1 becomes MIN, and a wrapped length or index can chain straight into a buffer overflow.
Pre- versus post-increment hides off-by-one and double-counting; bump a fixed-width integer past its max and it silently wraps to the bottom.
In the wild
Pre- vs Post-Increment
i++ yields the old value and ++i the new one, so using the result inline changes what gets stored.
int i = 0;
// SMELL: post-increment returns the OLD value
int a = i++; // a = 0, i = 1
// the classic self-assignment trap: i never advances
i = i++; // i is STILL 0 (old value written back)
// RIGHT: ++i returns the new value; or just be explicit
int b = ++i; // b = 2, i = 2
i += 1; // unambiguousi++ -> a = 0, i = 1 i = i++ -> i = 0 (no change!) ++i -> b = 2
const arr = [];
let i = 0;
// SMELL: post-increment indexes with the OLD value
arr[i++] = "a"; // writes index 0, then i = 1
arr[i++] = "b"; // writes index 1, then i = 2
// reading inline is where it bites
let n = 0;
console.log(n++ + " then " + n); // "0 then 1"
// RIGHT: separate the bump from the use
arr.push("a", "b");arr = ['a', 'b'] log: '0 then 1'
Undefined Evaluation Order
Mutating the same variable twice in one expression has no defined order in C — it is undefined behavior.
int i = 0;
int a[4];
/* UNDEFINED BEHAVIOR: i read & written with no sequence point */
a[i] = i++; /* which index? the standard does not say */
int x = i++ + ++i; /* result is not portable -- anything goes */
/* RIGHT: one mutation per statement, ordered explicitly */
a[i] = 0;
i++;UB: result varies by compiler / flags right: deterministic, one step at a time
# Python has no ++ on purpose -- the antidote by design
i = 0
# i++ # SyntaxError
i += 1 # one explicit, atomic step
# +1 in a comprehension stays a value, never a hidden write
nums = [n + 1 for n in range(4)] # [1, 2, 3, 4]i += 1 -> i = 1 nums -> [1, 2, 3, 4]
Wraparound at the Boundary
A fixed-width integer has a ceiling; one bump past it wraps to the floor — and a wrapped size can become a buffer overflow.
/* SMELL: unsigned wraps; signed overflow is undefined behavior */
uint8_t x = 255;
x++; /* x == 0, not 256 */
/* CWE-680: an overflowed length feeds an allocation, then a copy */
uint16_t len = a + b; /* a + b > 65535 wraps to something tiny */
char *buf = malloc(len); /* too small */
memcpy(buf, src, a + b); /* writes past the buffer */
/* RIGHT: check before you compute the size */
if (a > UINT16_MAX - b) return -1; /* would overflow */
size_t n = (size_t)a + b; /* widen, then allocate */wrap: len wraps small -> heap overflow on memcpy guard: oversize input rejected before malloc
// SMELL: int silently overflows -- no exception
int max = Integer.MAX_VALUE; // 2147483647
int oops = max + 1; // -2147483648 (wrapped to MIN)
// the classic: a midpoint that goes negative on big arrays
int mid = (low + high) / 2; // low+high can overflow
// RIGHT: fail loudly, or compute without overflowing
int safe = Math.addExact(max, 1); // throws ArithmeticException
int m = low + (high - low) / 2; // never overflowswrap: MAX + 1 -> MIN (no error) addExact: throws ArithmeticException
// Rust makes the choice explicit instead of guessing.
let x: u8 = 255;
// let y = x + 1; // debug: panics; release: wraps to 0
// Spell out which behavior you actually want:
let wrapped = x.wrapping_add(1); // 0, on purpose
let checked = x.checked_add(1); // None -> you must handle it
let (v, of) = x.overflowing_add(1); // (0, true) -- carry flag
match checked {
Some(n) => println!("{n}"),
None => eprintln!("would overflow"),
}checked_add: None -> handled by match wrapping_add: 0 (wrap chosen deliberately)
Where this compounds
- Lost Update (Read-Modify-Write Race) × Threading & Mutexes
- Off-by-One Index Crash × Index Out of Bounds & Missing Keys
- Integer Overflow Sizes a Buffer Too Small × Index Out of Bounds & Missing Keys
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.