/ utilities / src / rand.rs
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  }