rand.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the alphavm library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use rand::{ 17 Rng, 18 SeedableRng, 19 distributions::{Distribution, Standard}, 20 rngs::StdRng, 21 }; 22 use rand_xorshift::XorShiftRng; 23 24 /// A trait for a uniform random number generator. 25 pub trait Uniform: Sized { 26 /// Samples a random value from a uniform distribution. 27 fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self; 28 } 29 30 impl<T> Uniform for T 31 where 32 Standard: Distribution<T>, 33 { 34 #[inline] 35 fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self { 36 rng.sample(Standard) 37 } 38 } 39 40 /// A fast RNG used **solely** for testing and benchmarking, **not** for any real world purposes. 41 pub struct TestRng { 42 seed: u64, 43 rng: XorShiftRng, 44 calls: usize, 45 } 46 47 impl Default for TestRng { 48 fn default() -> Self { 49 // Obtain the initial seed using entropy provided by the OS. 50 let seed = StdRng::from_entropy().r#gen(); 51 52 // Use it as the basis for the underlying Rng. 53 Self::fixed(seed) 54 } 55 } 56 57 impl TestRng { 58 pub fn fixed(seed: u64) -> Self { 59 // Print the seed, so it's displayed if any of the tests using `test_rng` fails. 60 println!("\nInitializing 'TestRng' with seed '{seed}'\n"); 61 62 // Use the seed to initialize a fast, non-cryptographic Rng. 63 Self::from_seed(seed) 64 } 65 66 // This is the preferred method to use once the main instance of TestRng had already 67 // been initialized in a test or benchmark and an auxiliary one is desired without 68 // spamming the stdout. 69 pub fn from_seed(seed: u64) -> Self { 70 Self { seed, rng: XorShiftRng::seed_from_u64(seed), calls: 0 } 71 } 72 73 /// Returns a randomly-sampled `String`, given the maximum size in bytes and an RNG. 74 /// 75 /// Some of the alphavm internal tests involve the random generation of strings, 76 /// which are parsed and tested against the original ones. However, since the string parser 77 /// rejects certain characters, if those characters are randomly generated, the tests fail. 78 /// 79 /// To prevent these failures, as we randomly generate the characters, 80 /// we ensure that they are not among the ones rejected by the parser; 81 /// if they are, we adjust them to be allowed characters. 82 /// 83 /// Note that the randomness of the characters is strictly for **testing** purposes; 84 /// also note that the disallowed characters are a small fraction of the total set of characters, 85 /// and thus the adjustments rarely occur. 86 pub fn next_string(&mut self, max_bytes: u32, is_fixed_size: bool) -> String { 87 /// Adjust an unsafe character. 88 /// 89 /// As our parser rejects certain potentially unsafe characters (see `Sanitizer::parse_safe_char`), 90 /// we need to avoid generating them randomly. This function acts as an adjusting filter: 91 /// it changes an unsafe character to `'0'` (other choices are possible), and leaves other 92 /// characters unchanged. 93 fn adjust_unsafe_char(ch: char) -> char { 94 let code = ch as u32; 95 if code < 9 96 || code == 11 97 || code == 12 98 || (14..=31).contains(&code) 99 || code == 127 100 || (0x202a..=0x202e).contains(&code) 101 || (0x2066..=0x2069).contains(&code) 102 { 103 '0' 104 } else { 105 ch 106 } 107 } 108 109 /// Adjust a backslash and a double quote. 110 /// 111 /// Aside from the characters rejected through the function [adjust_unsafe_char], 112 /// the syntax of strings allows backslash and double quotes only in certain circumstances: 113 /// backslash is used to introduce an escape, and there are constraints on what can occur 114 /// after a backslash; double quotes is only used in escaped form just after a backslash. 115 /// 116 /// If we randomly sample characters, we may end up generating backslashes with 117 /// malformed escape syntax, or double quotes not preceded by backslash. Thus, 118 /// we also adjust backslashes and double quotes as we randomly sample characters. 119 /// 120 /// Note that, this way, we do not test the parsing of any escape sequences; 121 /// to do that, we would need to reify the possible elements of strings, 122 /// namely characters and escapes, and randomly generate such elements. 123 fn adjust_backslash_and_doublequote(ch: char) -> char { 124 if ch == '\\' || ch == '\"' { '0' } else { ch } 125 } 126 127 let range = match is_fixed_size { 128 true => 0..max_bytes, 129 false => 0..self.gen_range(0..max_bytes), 130 }; 131 132 range.map(|_| self.r#gen::<char>()).map(adjust_unsafe_char).map(adjust_backslash_and_doublequote).collect() 133 } 134 } 135 136 impl rand::RngCore for TestRng { 137 fn next_u32(&mut self) -> u32 { 138 self.calls += 1; 139 self.rng.next_u32() 140 } 141 142 fn next_u64(&mut self) -> u64 { 143 self.calls += 1; 144 self.rng.next_u64() 145 } 146 147 fn fill_bytes(&mut self, dest: &mut [u8]) { 148 self.calls += 1; 149 self.rng.fill_bytes(dest) 150 } 151 152 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { 153 self.calls += 1; 154 self.rng.try_fill_bytes(dest) 155 } 156 } 157 158 impl rand::CryptoRng for TestRng {} 159 160 impl Drop for TestRng { 161 fn drop(&mut self) { 162 println!("Called TestRng with seed {} {} times", self.seed, self.calls); 163 } 164 }