Ensure arithmetic will never overflow

This commit is contained in:
Juhani Krekelä 2019-07-10 20:25:26 +03:00
parent 40048618ee
commit a624ce5374
1 changed files with 85 additions and 5 deletions

View File

@ -138,6 +138,84 @@ struct timespec ms_in_future(intmax_t ms) {
return ts;
}
intmax_t saturating_add(intmax_t a, intmax_t b) {
if (a < 0 && b < 0) {
// a: [min, -1]
// b: [min, -1]
// min + min -> underflow
// min + -1 -> underflow
// -1 + -1 -> ok
// a + b < INTMAX_MIN || - a
// b < INTMAX_MIN - a
if (b < INTMAX_MIN - a) {
// Underflow
fprintf(stderr, "a+b underflow\n"); // debg
return INTMAX_MIN;
}
} else if (a < 0 && b >= 0) {
// a: [min, -1]
// b: [0, max]
// min + 0 -> ok
// min + max -> ok
// -1 + 0 -> ok
// -1 + max -> ok
} else if (a >= 0 && b < 0) {
// See above but swap a and b
} else if (a >= 0 && b >= 0) {
// a: [0, max]
// b: [0, max]
// 0 + 0 -> ok
// 0 + max -> ok
// max + max -> overflow
// a + b > INTMAX_MAX || -a
// b > INTMAX_MAX - a
if (b > INTMAX_MAX - a) {
// Overflow
fprintf(stderr, "a+b overflow\n"); // debg
return INTMAX_MAX;
}
}
return a + b;
}
intmax_t saturating_sub(intmax_t a, intmax_t b) {
// a - b = a + (-b)
// Only case where -b is not safe is when b < -INTMAX_MAX (because
// INTMAX_MIN can be smaller than -INTMAX_MAX, but INTMAX_MAX can't be
// larger than -INTMAX_MIN)
if (b < -INTMAX_MAX) {
// a - b || + c - c
// a - b + c - c
// a - (b - c) - c
// a + (c - b) - c
fprintf(stderr, "-b overflow\n"); // debg
intmax_t c = saturating_sub(b, -INTMAX_MAX);
return saturating_sub(saturating_add(a, c - b), c);
} else {
return saturating_sub(a, -b);
}
}
intmax_t saturating_mul(intmax_t a, intmax_t b) {
// Doesn't give 100% right results when one parameter is INTMAX_MIN,
// but at least it won't ever overflow
if (a < 0) {
return saturating_sub(0, saturating_mul(saturating_sub(0, a), b));
}
if (b < 0) {
return saturating_sub(0, saturating_mul(a, saturating_sub(0, b)));
}
if (INTMAX_MAX / a < b) {
// Overflow
fprintf(stderr, "a*b overflow\n"); // debg
return INTMAX_MAX;
}
return a * b;
}
int wait_ms_until(struct timespec then) {
// This function basically returns the difference in ms between the
// current time and the given time, clamped to [0, INT_MAX]
@ -151,11 +229,13 @@ int wait_ms_until(struct timespec then) {
err(1, "clock_gettime");
}
// Overflow not checked because fuck overflow checking in C
// TODO: Check overflow
intmax_t sec_diff = then.tv_sec - now.tv_sec;
intmax_t ns_diff = then.tv_nsec - now.tv_nsec;
intmax_t ms = sec_diff * 1000 + ns_diff / 1000 / 1000;
// Uses saturating arithmetic. I guess this might be wrong in some cases
// but can't be bothered to deal with it any other way, especially as a
// clamping the values to even something like [0, 1] would result in
// mostly correct functioning
intmax_t sec_diff = saturating_sub(then.tv_sec, now.tv_sec);
intmax_t ns_diff = saturating_sub(then.tv_nsec, now.tv_nsec);
intmax_t ms = saturating_add(saturating_mul(sec_diff, 1000), ns_diff / 1000 / 1000);
// Clamp
if (ms > INT_MAX) {