Skip to content

Commit b595fa6

Browse files
committed
Factor out fuzzing code
1 parent ff317a1 commit b595fa6

File tree

4 files changed

+147
-93
lines changed

4 files changed

+147
-93
lines changed

testcrate/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ doctest = false
1111
[build-dependencies]
1212
rand = "0.7"
1313

14-
[dev-dependencies]
14+
[dependencies]
1515
# For fuzzing tests we want a deterministic seedable RNG. We also eliminate potential
1616
# problems with system RNGs on the variety of platforms this crate is tested on.
1717
# `xoshiro128**` is used for its quality, size, and speed at generating `u32` shift amounts.

testcrate/src/lib.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,140 @@
11
#![no_std]
2+
3+
use compiler_builtins::int::Int;
4+
5+
use rand_xoshiro::rand_core::{RngCore, SeedableRng};
6+
use rand_xoshiro::Xoshiro128StarStar;
7+
8+
/// Random fuzzing step. When run several times, it results in excellent fuzzing entropy such as:
9+
/// 11110101010101011110111110011111
10+
/// 10110101010100001011101011001010
11+
/// 1000000000000000
12+
/// 10000000000000110111110000001010
13+
/// 1111011111111101010101111110101
14+
/// 101111111110100000000101000000
15+
/// 10000000110100000000100010101
16+
/// 1010101010101000
17+
fn fuzz_step<I: Int>(rng: &mut Xoshiro128StarStar, x: &mut I) {
18+
let ones = !I::ZERO;
19+
let bit_indexing_mask: u32 = I::BITS - 1;
20+
21+
// Randomly OR, AND, and XOR randomly sized and shifted continuous strings of
22+
// ones with `lhs` and `rhs`.
23+
let r0 = bit_indexing_mask & rng.next_u32();
24+
let r1 = bit_indexing_mask & rng.next_u32();
25+
let mask = ones.wrapping_shl(r0).rotate_left(r1);
26+
match rng.next_u32() % 4 {
27+
0 => *x |= mask,
28+
1 => *x &= mask,
29+
// both 2 and 3 to make XORs as common as ORs and ANDs combined
30+
_ => *x ^= mask,
31+
}
32+
33+
// Alternating ones and zeros (e.x. 0b1010101010101010). This catches second-order
34+
// problems that might occur for algorithms with two modes of operation (potentially
35+
// there is some invariant that can be broken and maintained via alternating between modes,
36+
// breaking the algorithm when it reaches the end).
37+
let mut alt_ones = I::ONE;
38+
for _ in 0..(I::BITS / 2) {
39+
alt_ones <<= 2;
40+
alt_ones |= I::ONE;
41+
}
42+
let r0 = bit_indexing_mask & rng.next_u32();
43+
let r1 = bit_indexing_mask & rng.next_u32();
44+
let mask = alt_ones.wrapping_shl(r0).rotate_left(r1);
45+
match rng.next_u32() % 4 {
46+
0 => *x |= mask,
47+
1 => *x &= mask,
48+
_ => *x ^= mask,
49+
}
50+
}
51+
52+
/// Feeds a series of fuzzing inputs to `f`. The fuzzer first uses an algorithm designed to find
53+
/// corner cases, followed by a more random fuzzer that runs `n` times.
54+
pub fn fuzz<I: Int, F: Fn(I)>(n: usize, f: F) {
55+
// corner case tester. Calls `f` 210 times for u128.
56+
// zero gets skipped by the loop
57+
f(I::ZERO);
58+
for i0 in 0..I::FUZZ_NUM {
59+
let mask_lo = (!I::UnsignedInt::ZERO).wrapping_shr(I::FUZZ_LENGTHS[i0] as u32);
60+
for i1 in i0..I::FUZZ_NUM {
61+
let mask_hi = (!I::UnsignedInt::ZERO).wrapping_shl(I::FUZZ_LENGTHS[i1 - i0] as u32);
62+
f(I::from_unsigned(mask_lo & mask_hi));
63+
}
64+
}
65+
66+
// random fuzzer
67+
let mut rng = Xoshiro128StarStar::seed_from_u64(0);
68+
let mut x: I = Int::ZERO;
69+
for _ in 0..n {
70+
fuzz_step(&mut rng, &mut x);
71+
f(x)
72+
}
73+
}
74+
75+
/// The same as `fuzz`, except `f` has two inputs.
76+
pub fn fuzz_2<I: Int, F: Fn(I, I)>(n: usize, f: F) {
77+
// The corner case tester is crucial for checking cases like where both inputs are equal and
78+
// equal to special values lik iX::MIN, which is unlikely for the random fuzzer to encounter.
79+
80+
// Check cases where the first and second inputs are zero. Both call `f` 210 times for `u128`.
81+
for i0 in 0..I::FUZZ_NUM {
82+
let mask_lo = (!I::UnsignedInt::ZERO).wrapping_shr(I::FUZZ_LENGTHS[i0] as u32);
83+
for i1 in i0..I::FUZZ_NUM {
84+
let mask_hi = (!I::UnsignedInt::ZERO).wrapping_shl(I::FUZZ_LENGTHS[i1 - i0] as u32);
85+
f(I::ZERO, I::from_unsigned(mask_lo & mask_hi));
86+
}
87+
}
88+
for i0 in 0..I::FUZZ_NUM {
89+
let mask_lo = (!I::UnsignedInt::ZERO).wrapping_shr(I::FUZZ_LENGTHS[i0] as u32);
90+
for i1 in i0..I::FUZZ_NUM {
91+
let mask_hi = (!I::UnsignedInt::ZERO).wrapping_shl(I::FUZZ_LENGTHS[i1 - i0] as u32);
92+
f(I::from_unsigned(mask_lo & mask_hi), I::ZERO);
93+
}
94+
}
95+
96+
// Nested corner tester. Calls `f` 44100 times for `u128`.
97+
for i0 in 0..I::FUZZ_NUM {
98+
let mask0_lo = (!I::UnsignedInt::ZERO).wrapping_shr(I::FUZZ_LENGTHS[i0] as u32);
99+
for i1 in i0..I::FUZZ_NUM {
100+
let mask0_hi = (!I::UnsignedInt::ZERO).wrapping_shl(I::FUZZ_LENGTHS[i1 - i0] as u32);
101+
for i2 in 0..I::FUZZ_NUM {
102+
let mask1_lo = (!I::UnsignedInt::ZERO).wrapping_shr(I::FUZZ_LENGTHS[i2] as u32);
103+
for i3 in i2..I::FUZZ_NUM {
104+
let mask1_hi =
105+
(!I::UnsignedInt::ZERO).wrapping_shl(I::FUZZ_LENGTHS[i3 - i2] as u32);
106+
f(
107+
I::from_unsigned(mask0_lo & mask0_hi),
108+
I::from_unsigned(mask1_lo & mask1_hi),
109+
);
110+
}
111+
}
112+
}
113+
}
114+
115+
// random fuzzer
116+
let mut rng = Xoshiro128StarStar::seed_from_u64(0);
117+
let mut x: I = I::ZERO;
118+
let mut y: I = I::ZERO;
119+
for _ in 0..n {
120+
// the rng has less overhead when updating only one of the inputs for every pass
121+
if (rng.next_u32() & 1) == 0 {
122+
fuzz_step(&mut rng, &mut x);
123+
} else {
124+
fuzz_step(&mut rng, &mut y);
125+
}
126+
f(x, y)
127+
}
128+
}
129+
130+
/// Tester for shift functions
131+
pub fn fuzz_shift<I: Int, F: Fn(I, u32)>(f: F) {
132+
// Shift functions are very simple and do not need anything other than shifting a small
133+
// set of random patterns for every fuzz length.
134+
let mut rng = Xoshiro128StarStar::seed_from_u64(0);
135+
let mut x: I = Int::ZERO;
136+
for i in 0..I::FUZZ_NUM {
137+
fuzz_step(&mut rng, &mut x);
138+
f(x, I::FUZZ_LENGTHS[i] as u32);
139+
}
140+
}

testcrate/tests/div_rem.rs

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
use rand_xoshiro::rand_core::{RngCore, SeedableRng};
2-
use rand_xoshiro::Xoshiro128StarStar;
3-
41
use compiler_builtins::int::sdiv::{__divmoddi4, __divmodsi4, __divmodti4};
52
use compiler_builtins::int::udiv::{__udivmoddi4, __udivmodsi4, __udivmodti4};
63

@@ -17,6 +14,10 @@ macro_rules! test {
1714
#[test]
1815
fn $test_name() {
1916
fn assert_invariants(lhs: $uX, rhs: $uX) {
17+
if rhs == 0 {
18+
return;
19+
}
20+
2021
let rem: &mut $uX = &mut 0;
2122
let quo: $uX = $unsigned_name(lhs, rhs, Some(rem));
2223
let rem = *rem;
@@ -66,68 +67,7 @@ macro_rules! test {
6667
}
6768
}
6869

69-
// Specially designed random fuzzer
70-
let mut rng = Xoshiro128StarStar::seed_from_u64(0);
71-
let mut lhs: $uX = 0;
72-
let mut rhs: $uX = 0;
73-
// all ones constant
74-
let ones: $uX = !0;
75-
// Alternating ones and zeros (e.x. 0b1010101010101010). This catches second-order
76-
// problems that might occur for algorithms with two modes of operation (potentially
77-
// there is some invariant that can be broken for large `duo` and maintained via
78-
// alternating between modes, breaking the algorithm when it reaches the end).
79-
let mut alt_ones: $uX = 1;
80-
for _ in 0..($n / 2) {
81-
alt_ones <<= 2;
82-
alt_ones |= 1;
83-
}
84-
// creates a mask for indexing the bits of the type
85-
let bit_indexing_mask = $n - 1;
86-
for _ in 0..1_000_000 {
87-
// Randomly OR, AND, and XOR randomly sized and shifted continuous strings of
88-
// ones with `lhs` and `rhs`. This results in excellent fuzzing entropy such as:
89-
// lhs:10101010111101000000000100101010 rhs: 1010101010000000000000001000001
90-
// lhs:10101010111101000000000101001010 rhs: 1010101010101010101010100010100
91-
// lhs:10101010111101000000000101001010 rhs:11101010110101010101010100001110
92-
// lhs:10101010000000000000000001001010 rhs:10100010100000000000000000001010
93-
// lhs:10101010000000000000000001001010 rhs: 10101010101010101000
94-
// lhs:10101010000000000000000001100000 rhs:11111111111101010101010101001111
95-
// lhs:10101010000000101010101011000000 rhs:11111111111101010101010100000111
96-
// lhs:10101010101010101010101011101010 rhs: 1010100000000000000
97-
// lhs:11111111110101101010101011010111 rhs: 1010100000000000000
98-
// The msb is set half of the time by the fuzzer, but `assert_invariants` tests
99-
// both the signed and unsigned functions.
100-
let r0: u32 = bit_indexing_mask & rng.next_u32();
101-
let r1: u32 = bit_indexing_mask & rng.next_u32();
102-
let mask = ones.wrapping_shr(r0).rotate_left(r1);
103-
match rng.next_u32() % 8 {
104-
0 => lhs |= mask,
105-
1 => lhs &= mask,
106-
// both 2 and 3 to make XORs as common as ORs and ANDs combined, otherwise
107-
// the entropy gets destroyed too often
108-
2 | 3 => lhs ^= mask,
109-
4 => rhs |= mask,
110-
5 => rhs &= mask,
111-
_ => rhs ^= mask,
112-
}
113-
// do the same for alternating ones and zeros
114-
let r0: u32 = bit_indexing_mask & rng.next_u32();
115-
let r1: u32 = bit_indexing_mask & rng.next_u32();
116-
let mask = alt_ones.wrapping_shr(r0).rotate_left(r1);
117-
match rng.next_u32() % 8 {
118-
0 => lhs |= mask,
119-
1 => lhs &= mask,
120-
// both 2 and 3 to make XORs as common as ORs and ANDs combined, otherwise
121-
// the entropy gets destroyed too often
122-
2 | 3 => lhs ^= mask,
123-
4 => rhs |= mask,
124-
5 => rhs &= mask,
125-
_ => rhs ^= mask,
126-
}
127-
if rhs != 0 {
128-
assert_invariants(lhs, rhs);
129-
}
130-
}
70+
testcrate::fuzz_2(100_000, |lhs, rhs| assert_invariants(lhs, rhs));
13171
}
13272
};
13373
}

testcrate/tests/leading_zeros.rs

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,11 @@
1-
use rand_xoshiro::rand_core::{RngCore, SeedableRng};
2-
use rand_xoshiro::Xoshiro128StarStar;
3-
41
use compiler_builtins::int::__clzsi2;
52
use compiler_builtins::int::leading_zeros::{
63
usize_leading_zeros_default, usize_leading_zeros_riscv,
74
};
85

96
#[test]
107
fn __clzsi2_test() {
11-
// Binary fuzzer. We cannot just send a random number directly to `__clzsi2()`, because we need
12-
// large sequences of zeros to test. This XORs, ANDs, and ORs random length strings of 1s to
13-
// `x`. ORs insure sequences of ones, ANDs insures sequences of zeros, and XORs are not often
14-
// destructive but add entropy.
15-
let mut rng = Xoshiro128StarStar::seed_from_u64(0);
16-
let mut x = 0usize;
17-
// creates a mask for indexing the bits of the type
18-
let bit_indexing_mask = usize::MAX.count_ones() - 1;
19-
// 10000 iterations is enough to make sure edge cases like single set bits are tested and to go
20-
// through many paths.
21-
for _ in 0..10_000 {
22-
let r0 = bit_indexing_mask & rng.next_u32();
23-
// random length of ones
24-
let ones: usize = !0 >> r0;
25-
let r1 = bit_indexing_mask & rng.next_u32();
26-
// random circular shift
27-
let mask = ones.rotate_left(r1);
28-
match rng.next_u32() % 4 {
29-
0 => x |= mask,
30-
1 => x &= mask,
31-
// both 2 and 3 to make XORs as common as ORs and ANDs combined
32-
_ => x ^= mask,
33-
}
8+
testcrate::fuzz(10_000, |x: usize| {
349
let lz = x.leading_zeros() as usize;
3510
let lz0 = __clzsi2(x);
3611
let lz1 = usize_leading_zeros_default(x);
@@ -50,5 +25,5 @@ fn __clzsi2_test() {
5025
x, lz, lz2
5126
);
5227
}
53-
}
28+
})
5429
}

0 commit comments

Comments
 (0)