├── .github ├── FUNDING.yml └── workflows │ └── cargo.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── articles ├── fuzzcheck_arbitrary_experiment_graph.png ├── fuzzcheck_experiment_graph.png └── why_not_bytes.md ├── cargo-fuzzcheck ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── lib.rs │ └── main.rs ├── fuzzcheck ├── Cargo.toml ├── README.md ├── build.rs ├── src │ ├── bitset.rs │ ├── bloom_filter.rs │ ├── builder.rs │ ├── code_coverage_sensor │ │ ├── instrumentation_pointers_linux.c │ │ ├── instrumentation_pointers_mac.c │ │ ├── leb128.rs │ │ ├── llvm_coverage.rs │ │ ├── mod.rs │ │ └── serialized.rs │ ├── data_structures.rs │ ├── fenwick_tree.rs │ ├── fuzzer.rs │ ├── lib.rs │ ├── mutators │ │ ├── alternation.rs │ │ ├── arc.rs │ │ ├── array.rs │ │ ├── bool.rs │ │ ├── boxed.rs │ │ ├── char.rs │ │ ├── character_classes.rs │ │ ├── cow.rs │ │ ├── either.rs │ │ ├── enums.rs │ │ ├── filter.rs │ │ ├── fixed_len_vector.rs │ │ ├── grammar │ │ │ ├── ast.rs │ │ │ ├── grammar.rs │ │ │ ├── mod.rs │ │ │ ├── mutators.rs │ │ │ └── regex.rs │ │ ├── integer.rs │ │ ├── integer_within_range.rs │ │ ├── map.rs │ │ ├── mod.rs │ │ ├── mutations │ │ │ └── mod.rs │ │ ├── never.rs │ │ ├── option.rs │ │ ├── range.rs │ │ ├── rc.rs │ │ ├── recursive.rs │ │ ├── result.rs │ │ ├── string.rs │ │ ├── tuples.rs │ │ ├── unique.rs │ │ ├── unit.rs │ │ ├── vector │ │ │ ├── arbitrary.rs │ │ │ ├── copy_element.rs │ │ │ ├── crossover_insert_slice.rs │ │ │ ├── crossover_replace_element.rs │ │ │ ├── insert_element.rs │ │ │ ├── insert_many_elements.rs │ │ │ ├── mod.rs │ │ │ ├── mutate_element.rs │ │ │ ├── only_choose_length.rs │ │ │ ├── remove.rs │ │ │ ├── remove_and_insert_element.rs │ │ │ ├── swap_elements.rs │ │ │ └── vec_mutation.rs │ │ └── vose_alias.rs │ ├── sensors_and_pools │ │ ├── allocations_sensor.rs │ │ ├── and_sensor_and_pool.rs │ │ ├── map_sensor.rs │ │ ├── maximise_each_counter_pool.rs │ │ ├── maximise_observation_pool.rs │ │ ├── mod.rs │ │ ├── most_n_diverse_pool.rs │ │ ├── noop_sensor.rs │ │ ├── simplest_to_activate_counter_pool.rs │ │ ├── static_value_sensor.rs │ │ ├── test_failure_pool.rs │ │ ├── unique_values_pool.rs │ │ └── unit_pool.rs │ ├── serializers │ │ ├── mod.rs │ │ ├── serde_ron_serializer.rs │ │ └── serde_serializer.rs │ ├── signals_handler.rs │ ├── split_string.rs │ ├── subvalue_provider.rs │ ├── traits.rs │ └── world.rs └── tests │ ├── alternation_char_mutators.rs │ ├── char_mutators.rs │ ├── const_generics.rs │ ├── constrained_integer.rs │ ├── derived_enum.rs │ ├── derived_mutually_recursive_structs.rs │ ├── derived_recursive_struct.rs │ ├── derived_recursive_struct_fully_custom.rs │ ├── derived_struct.rs │ ├── enum_with_ignored_variant.rs │ ├── expansions │ ├── empty_structs.rs │ ├── enums_ignore_variant.rs │ ├── enums_with_generic_type_params.rs │ ├── enums_with_items_with_and_without_fields.rs │ ├── enums_with_multiple_empty_items.rs │ ├── enums_with_one_empty_item.rs │ ├── enums_with_one_item_multiple_fields.rs │ ├── enums_with_one_item_one_field.rs │ ├── expanded.rs │ ├── mod.rs │ ├── one_field_structs.rs │ ├── recursive_enum.rs │ ├── recursive_struct.rs │ ├── structs_with_generic_type_params.rs │ └── two_field_structs.rs │ ├── grammar_based_mutators.rs │ ├── lib.rs │ ├── option.rs │ └── vector.rs ├── fuzzcheck_book ├── .gitignore ├── book.toml └── src │ ├── SUMMARY.md │ ├── getting_started.md │ ├── introduction.md │ ├── quick_start.md │ ├── tree.png │ ├── tutorial1.md │ ├── tutorial1_function.md │ ├── tutorial1_mutator.md │ ├── tutorial1_serializer.md │ ├── tutorial1_setup.md │ ├── tutorial1_using_cargo_fuzzcheck.md │ ├── tutorial1_writing_fuzz_target.md │ ├── tutorial2.md │ ├── tutorial2_function.md │ ├── tutorial2_fuzzing.md │ ├── tutorial2_mutator.md │ └── tutorial2_syntax.md ├── fuzzcheck_common ├── Cargo.toml └── src │ ├── arg.rs │ └── lib.rs ├── fuzzcheck_mutators_derive ├── Cargo.toml └── src │ ├── enums.rs │ ├── lib.rs │ ├── single_variant.rs │ ├── structs_and_enums.rs │ ├── token_builder.rs │ └── tuples.rs ├── rust-toolchain ├── rustfmt.toml └── usage_tests ├── basic_crate ├── Cargo.toml └── src │ └── lib.rs └── basic_example.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | schedule: 5 | - cron: "0 6 * * 6" 6 | name: Continuous integration 7 | 8 | jobs: 9 | check: 10 | name: Check 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: nightly 17 | override: true 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: check 21 | 22 | test: 23 | name: Test Suite 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@master 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: nightly 30 | override: true 31 | - uses: actions-rs/cargo@v1 32 | with: 33 | command: test 34 | 35 | fuzz-rust-crate: 36 | name: Fuzz Rust crate 37 | runs-on: ubuntu-latest 38 | # todo: restore MacOS builds eventually 39 | steps: 40 | - uses: actions/checkout@master 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: nightly 44 | override: true 45 | - run: sh ./usage_tests/basic_example.sh 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /fuzz-corpus 4 | /artifacts 5 | 6 | /usage_tests/basic_crate/fuzz 7 | /usage_tests/basic_crate/target 8 | 9 | Cargo.lock 10 | 11 | .vscode 12 | .idea 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "fuzzcheck_common", 5 | "cargo-fuzzcheck", 6 | "fuzzcheck", 7 | "fuzzcheck_mutators_derive", 8 | ] 9 | 10 | exclude = [ 11 | "usage_tests", 12 | ] 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Loïc Lecrenier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /articles/fuzzcheck_arbitrary_experiment_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiclec/fuzzcheck-rs/cab34edee5ae038a4703bcd4885454bcabcae34a/articles/fuzzcheck_arbitrary_experiment_graph.png -------------------------------------------------------------------------------- /articles/fuzzcheck_experiment_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiclec/fuzzcheck-rs/cab34edee5ae038a4703bcd4885454bcabcae34a/articles/fuzzcheck_experiment_graph.png -------------------------------------------------------------------------------- /cargo-fuzzcheck/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-fuzzcheck" 3 | version = "0.13.0" 4 | authors = ["Loïc Lecrenier "] 5 | edition = "2021" 6 | description = "Command line tool to use fuzzcheck-rs" 7 | homepage = "https://fuzzcheck.neocities.org" 8 | repository = "https://github.com/loiclec/fuzzcheck-rs" 9 | readme = "README.md" 10 | license = "MIT" 11 | keywords = ["property", "fuzzer", "fuzzing", "test", "testing"] 12 | categories = ["development-tools::testing"] 13 | 14 | [dependencies.getopts] 15 | version = "0.2" 16 | 17 | [dependencies.fuzzcheck_common] 18 | path = "../fuzzcheck_common" 19 | version = "0.13" 20 | 21 | [[bin]] 22 | name = "cargo-fuzzcheck" 23 | path = "src/main.rs" -------------------------------------------------------------------------------- /cargo-fuzzcheck/README.md: -------------------------------------------------------------------------------- 1 | # cargo-fuzzcheck 2 | 3 | `cargo-fuzzcheck` is the necessary command line tool to use [fuzzcheck], a 4 | coverage-guided, structure-aware fuzzer for Rust functions. 5 | 6 | [fuzzcheck]: https://crates.io/crates/fuzzcheck 7 | -------------------------------------------------------------------------------- /cargo-fuzzcheck/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-env=TARGET={}", std::env::var("TARGET").unwrap()); 3 | } 4 | -------------------------------------------------------------------------------- /fuzzcheck/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzcheck" 3 | version = "0.13.0" 4 | authors = ["Loïc Lecrenier "] 5 | edition = "2021" 6 | description = "A modular, structure-aware, and feedback-driven fuzzing engine for Rust functions" 7 | homepage = "https://fuzzcheck.neocities.org" 8 | repository = "https://github.com/loiclec/fuzzcheck-rs" 9 | readme = "README.md" 10 | license = "MIT" 11 | keywords = ["property", "fuzzer", "fuzzing", "test", "testing"] 12 | categories = ["development-tools::testing"] 13 | 14 | [build-dependencies] 15 | cc = "1.1.28" 16 | 17 | [features] 18 | grammar_mutator = [] 19 | regex_grammar = ["grammar_mutator", "regex-syntax"] 20 | serde_json_serializer = ["serde", "serde_json"] 21 | serde_ron_serializer = ["serde", "ron"] 22 | 23 | default = ["grammar_mutator", "regex_grammar", "serde_json_serializer"] 24 | 25 | [dependencies] 26 | getopts = "0.2.21" 27 | fastrand = "1.9.0" 28 | cfg-if = "1.0.0" 29 | libc = { version = "0.2.126", default-features = false } 30 | 31 | md5 = "0.7.0" 32 | object = { version = "0.29.0", default-features = false, features = ["read"] } 33 | flate2 = { version = "1.0.24", default-features = false, features = ["zlib"] } 34 | 35 | fuzzcheck_common = { path = "../fuzzcheck_common", version = "0.13.0" } 36 | 37 | serde = { version = "1.0.210", features = ["derive"], optional = true } 38 | serde_json = { version = "1.0.128", optional = true } 39 | ron = { version = "0.7.1", optional = true } 40 | 41 | fuzzcheck_mutators_derive = { path = "../fuzzcheck_mutators_derive", version = "0.13.0" } 42 | 43 | ahash = "0.7.8" 44 | 45 | regex-syntax = { version = "0.6.29", optional = true } 46 | nu-ansi-term = "0.46.0" 47 | 48 | bit-vec = "0.6.3" 49 | rustc-demangle = "0.1.24" 50 | 51 | [lib] 52 | name = "fuzzcheck" 53 | bench = false 54 | -------------------------------------------------------------------------------- /fuzzcheck/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); 3 | let file_to_compile = match target_os.as_str() { 4 | "macos" | "ios" => "src/code_coverage_sensor/instrumentation_pointers_mac.c", 5 | "linux" => "src/code_coverage_sensor/instrumentation_pointers_linux.c", 6 | _ => panic!("fuzzcheck only work on macOS and Linux"), 7 | }; 8 | 9 | cc::Build::new() 10 | .file(file_to_compile) 11 | .compile("instrumentation_pointers"); 12 | println!("cargo:rerun-if-changed={}", file_to_compile); 13 | } 14 | -------------------------------------------------------------------------------- /fuzzcheck/src/bloom_filter.rs: -------------------------------------------------------------------------------- 1 | // This file is derived from the lupine crate, which is licensed under MIT 2 | // and available at https://github.com/greglaurent/lupine/ 3 | /* 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | use std::hash::{Hash, Hasher}; 29 | use std::marker::PhantomData; 30 | 31 | use ahash::AHasher; 32 | use bit_vec::BitVec; 33 | 34 | const FALSE_POS_PROB: f64 = -1.0; 35 | const LN_2: f64 = core::f64::consts::LN_2; 36 | const LN_2_SQR: f64 = LN_2 * LN_2; 37 | 38 | /// Representation of a bloom filter 39 | pub struct BloomFilter { 40 | k: u64, 41 | m: usize, 42 | hashers: [AHasher; 2], 43 | pub bitmap: BitVec, 44 | _phantom: PhantomData, 45 | } 46 | 47 | impl BloomFilter { 48 | /// Returns a BloomFilter with an optimized k and m 49 | /// 50 | /// # Arguments 51 | /// 52 | /// * 'size' - A usize that sets the size of the filter 53 | /// * 'false_pos_rate' - The acceptable false positive rate 54 | #[coverage(off)] 55 | pub fn new(size: usize, false_pos_rate: f64) -> Self { 56 | let k = Self::optimal_k(false_pos_rate); 57 | let m = Self::optimal_m(false_pos_rate, size); 58 | let bitmap = BitVec::from_elem(m, false); 59 | let hashers = [ 60 | AHasher::new_with_keys(fastrand::u128(..), fastrand::u128(..)), 61 | AHasher::new_with_keys(fastrand::u128(..), fastrand::u128(..)), 62 | ]; 63 | BloomFilter { 64 | k, 65 | m, 66 | hashers, 67 | bitmap, 68 | _phantom: PhantomData, 69 | } 70 | } 71 | 72 | /// Calculate optimal m value for the filter 73 | /// where m is the optimal number of bits in the bit array 74 | /// while preventing overfill 75 | /// 76 | /// where P is the probability of false positives 77 | /// and n is the acceptable false postive rate 78 | /// k = ( -( n * lnP ) / (ln2)^2 ) 79 | #[coverage(off)] 80 | fn optimal_m(false_pos_rate: f64, size: usize) -> usize { 81 | ((size as f64 * FALSE_POS_PROB * false_pos_rate.ln()) / LN_2_SQR).ceil() as usize 82 | } 83 | 84 | /// Calculate optimal k value for the filter 85 | /// where k is the number of functions to hash input T 86 | /// yielding k indices into the bit array 87 | /// 88 | /// where P is the probability of false positives 89 | /// k = ( - lnP / ln2 ) 90 | #[coverage(off)] 91 | fn optimal_k(false_pos_rate: f64) -> u64 { 92 | (false_pos_rate.ln() * FALSE_POS_PROB / LN_2).ceil() as u64 93 | } 94 | 95 | /// Hash values T for Bloomfilter 96 | #[coverage(off)] 97 | fn hash(&self, t: &T) -> (u64, u64) 98 | where 99 | T: Hash, 100 | { 101 | let hash1 = &mut self.hashers[0].clone(); 102 | let hash2 = &mut self.hashers[1].clone(); 103 | 104 | t.hash(hash1); 105 | t.hash(hash2); 106 | 107 | (hash1.finish(), hash2.finish()) 108 | } 109 | 110 | /// Retrieve the index of indexes by simulating 111 | /// more than 2 hashers 112 | /// 113 | /// Prevent Overflow: 114 | /// wrapping_add: wrapping add around at the boundary type 115 | /// wrapping_mul: wrapping mult around at the boundary type 116 | #[coverage(off)] 117 | fn find_index(&self, i: u64, hash1: u64, hash2: u64) -> usize { 118 | hash1.wrapping_add((i).wrapping_mul(hash2)) as usize % self.m 119 | } 120 | 121 | /// Insert T into the BloomFilter index 122 | #[coverage(off)] 123 | pub fn insert(&mut self, t: &T) 124 | where 125 | T: Hash, 126 | { 127 | let (hash1, hash2) = self.hash(t); 128 | 129 | for i in 0..self.k { 130 | let index = self.find_index(i, hash1, hash2); 131 | self.bitmap.set(index, true); 132 | } 133 | } 134 | 135 | /// Check if t of type T is in the BloomFilter index 136 | #[coverage(off)] 137 | pub fn contains(&mut self, t: &T) -> bool 138 | where 139 | T: Hash, 140 | { 141 | let (hash1, hash2) = self.hash(t); 142 | 143 | for i in 0..self.k { 144 | let index = self.find_index(i, hash1, hash2); 145 | if !self.bitmap.get(index).unwrap() { 146 | return false; 147 | } 148 | } 149 | true 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /fuzzcheck/src/code_coverage_sensor/instrumentation_pointers_linux.c: -------------------------------------------------------------------------------- 1 | int __llvm_profile_runtime = 0; 2 | 3 | extern unsigned long int __start___llvm_prf_cnts; 4 | extern unsigned long int __stop___llvm_prf_cnts; 5 | 6 | extern char __start___llvm_prf_data; 7 | extern char __stop___llvm_prf_data; 8 | 9 | extern char __start___llvm_prf_names; 10 | extern char __stop___llvm_prf_names; 11 | 12 | char *get_start_prf_data() 13 | { 14 | return &__start___llvm_prf_data; 15 | } 16 | char *get_end_prf_data() 17 | { 18 | return &__stop___llvm_prf_data; 19 | } 20 | char *get_start_prf_names() 21 | { 22 | return &__start___llvm_prf_names; 23 | } 24 | char *get_end_prf_names() 25 | { 26 | return &__stop___llvm_prf_names; 27 | } 28 | 29 | unsigned long int *get_start_instrumentation_counters() 30 | { 31 | return &__start___llvm_prf_cnts; 32 | } 33 | unsigned long int *get_end_instrumentation_counters() 34 | { 35 | return &__stop___llvm_prf_cnts; 36 | } 37 | -------------------------------------------------------------------------------- /fuzzcheck/src/code_coverage_sensor/instrumentation_pointers_mac.c: -------------------------------------------------------------------------------- 1 | int __llvm_profile_runtime = 0; 2 | 3 | extern unsigned long int 4 | CountersStart __asm("section$start$__DATA$__llvm_prf_cnts"); // different based on the platform, so double-check it 5 | extern unsigned long int 6 | CountersEnd __asm("section$end$__DATA$__llvm_prf_cnts"); 7 | 8 | unsigned long int *get_start_instrumentation_counters() 9 | { 10 | return &CountersStart; 11 | } 12 | unsigned long int *get_end_instrumentation_counters() 13 | { 14 | return &CountersEnd; 15 | } 16 | 17 | extern char 18 | PrfDataStart __asm("section$start$__DATA$__llvm_prf_data"); // different based on the platform, so double-check it 19 | extern char 20 | PrfDataEnd __asm("section$end$__DATA$__llvm_prf_data"); 21 | 22 | char *get_start_prf_data() 23 | { 24 | return &PrfDataStart; 25 | } 26 | char *get_end_prf_data() 27 | { 28 | return &PrfDataEnd; 29 | } 30 | 31 | extern char 32 | PrfNamesStart __asm("section$start$__DATA$__llvm_prf_names"); // different based on the platform, so double-check it 33 | extern char 34 | PrfNamesEnd __asm("section$end$__DATA$__llvm_prf_names"); 35 | 36 | char *get_start_prf_names() 37 | { 38 | return &PrfNamesStart; 39 | } 40 | char *get_end_prf_names() 41 | { 42 | return &PrfNamesEnd; 43 | } -------------------------------------------------------------------------------- /fuzzcheck/src/code_coverage_sensor/leb128.rs: -------------------------------------------------------------------------------- 1 | // This file is derived from the Rust compiler, located at rust/compiler/rustc_serialize/src/leb128.rs 2 | // The license of the project is copied below: 3 | /* 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | #![macro_use] 30 | 31 | macro_rules! impl_read_unsigned_leb128 { 32 | ($fn_name:ident, $int_ty:ty) => { 33 | #[inline] 34 | #[coverage(off)] 35 | pub fn $fn_name(slice: &[u8]) -> ($int_ty, usize) { 36 | let mut result = 0; 37 | let mut shift = 0; 38 | let mut position = 0; 39 | loop { 40 | let byte = slice[position]; 41 | position += 1; 42 | if (byte & 0x80) == 0 { 43 | result |= (byte as $int_ty) << shift; 44 | return (result, position); 45 | } else { 46 | result |= ((byte & 0x7F) as $int_ty) << shift; 47 | } 48 | shift += 7; 49 | } 50 | } 51 | }; 52 | } 53 | 54 | impl_read_unsigned_leb128!(read_u64_leb128, u64); 55 | -------------------------------------------------------------------------------- /fuzzcheck/src/code_coverage_sensor/serialized.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::CodeCoverageSensor; 7 | 8 | #[derive(Serialize, Deserialize)] 9 | pub struct CoverageMap { 10 | functions: Vec, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | pub struct Function { 15 | name: String, 16 | file: String, 17 | counters: Vec, 18 | inferred_counters: Vec, 19 | } 20 | 21 | #[derive(Serialize, Deserialize)] 22 | pub struct InferredCounter { 23 | regions: Vec, 24 | from_counter_ids: Vec, 25 | } 26 | 27 | #[derive(Serialize, Deserialize)] 28 | pub struct Region { 29 | lines: (usize, usize), 30 | cols: (usize, usize), 31 | } 32 | 33 | #[derive(Serialize, Deserialize)] 34 | pub struct Counter { 35 | id: usize, 36 | regions: Vec, 37 | } 38 | 39 | impl CodeCoverageSensor { 40 | #[coverage(off)] 41 | pub(crate) fn coverage_map(&self) -> CoverageMap { 42 | let mut idx = 0; 43 | let functions = self 44 | .coverage 45 | .iter() 46 | .map( 47 | #[coverage(off)] 48 | |coverage| { 49 | let f_record = &coverage.function_record; 50 | assert!(f_record.filenames.len() == 1); 51 | let name = f_record.name_function.clone(); 52 | let mut counters_by_file = HashMap::>::new(); 53 | 54 | // need to map (expression_idx) -> counter_idx 55 | let mut expression_idx_to_counter_idx = HashMap::new(); 56 | let mut counter_indices_and_regions = vec![]; 57 | for (i, (e, regions)) in f_record.expressions.iter().enumerate() { 58 | if e.add_terms.len() == 1 && e.sub_terms.is_empty() { 59 | counter_indices_and_regions.push((idx, regions)); 60 | expression_idx_to_counter_idx.insert(i, idx); 61 | idx += 1; 62 | } 63 | } 64 | for (i, (e, regions)) in f_record.expressions.iter().enumerate() { 65 | if !(e.add_terms.len() == 1 && e.sub_terms.is_empty()) && !e.add_terms.is_empty() { 66 | counter_indices_and_regions.push((idx, regions)); 67 | expression_idx_to_counter_idx.insert(i, idx); 68 | idx += 1; 69 | } 70 | } 71 | 72 | for (idx, regions) in counter_indices_and_regions { 73 | // assume that all regions are within one file 74 | let file_idx = f_record 75 | .file_id_mapping 76 | .filename_indices 77 | .iter() 78 | .position( 79 | #[coverage(off)] 80 | |idx| *idx == regions[0].filename_index, 81 | ) 82 | .unwrap(); 83 | let file = f_record.filenames[file_idx].clone(); 84 | let counter = Counter { 85 | id: idx, 86 | regions: regions 87 | .iter() 88 | .map( 89 | #[coverage(off)] 90 | |region| Region { 91 | lines: (region.line_start, region.line_end), 92 | cols: (region.col_start, region.col_end), 93 | }, 94 | ) 95 | .collect(), 96 | }; 97 | counters_by_file.entry(file).or_default().push(counter); 98 | } 99 | // assume there is only one file 100 | let (file, counters) = counters_by_file.into_iter().next().unwrap(); 101 | let inferred_counters = f_record 102 | .inferred_expressions 103 | .iter() 104 | .map( 105 | #[coverage(off)] 106 | |(regions, from_expr_idxs)| InferredCounter { 107 | regions: regions 108 | .iter() 109 | .map( 110 | #[coverage(off)] 111 | |region| Region { 112 | lines: (region.line_start, region.line_end), 113 | cols: (region.col_start, region.col_end), 114 | }, 115 | ) 116 | .collect(), 117 | from_counter_ids: from_expr_idxs 118 | .iter() 119 | .map( 120 | #[coverage(off)] 121 | |idx| expression_idx_to_counter_idx[idx], 122 | ) 123 | .collect(), 124 | }, 125 | ) 126 | .collect::>(); 127 | Function { 128 | name, 129 | file: file.to_str().unwrap().to_owned(), 130 | counters, 131 | inferred_counters, 132 | } 133 | }, 134 | ) 135 | .collect(); 136 | CoverageMap { functions } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /fuzzcheck/src/fenwick_tree.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use crate::mutators::gen_f64; 4 | 5 | #[derive(Clone)] 6 | pub struct FenwickTree { 7 | storage: Vec, 8 | } 9 | 10 | #[inline(always)] 11 | #[coverage(off)] 12 | fn least_significant_bit(i: usize) -> usize { 13 | i & (1_usize.wrapping_add(!i)) 14 | } 15 | #[inline(always)] 16 | #[coverage(off)] 17 | fn get_parent(i: usize) -> usize { 18 | i - least_significant_bit(i) 19 | } 20 | #[inline(always)] 21 | #[coverage(off)] 22 | fn get_next(i: usize) -> usize { 23 | i + least_significant_bit(i) 24 | } 25 | 26 | impl FenwickTree { 27 | #[coverage(off)] 28 | pub fn new(mut xs: Vec) -> Self { 29 | let mut i = 1; 30 | while i < xs.len() { 31 | let j = get_next(i); 32 | if j < xs.len() { 33 | xs[j] += xs[i]; 34 | } 35 | i += 1; 36 | } 37 | Self { storage: xs } 38 | } 39 | #[coverage(off)] 40 | pub fn len(&self) -> usize { 41 | self.storage.len() 42 | } 43 | #[coverage(off)] 44 | pub fn prefix_sum(&self, mut idx: usize) -> f64 { 45 | assert!(!self.storage.is_empty()); 46 | let mut sum = self.storage[0]; 47 | while idx != 0 { 48 | sum += self.storage[idx]; 49 | idx = get_parent(idx); 50 | } 51 | sum 52 | } 53 | #[coverage(off)] 54 | pub fn update(&mut self, mut idx: usize, delta: f64) { 55 | if idx == 0 { 56 | self.storage[idx] += delta; 57 | return; 58 | } 59 | while idx < self.storage.len() { 60 | self.storage[idx] += delta; 61 | idx = get_next(idx); 62 | } 63 | } 64 | // Find the first item which has a prefix sum *higher* than the chosen weight. 65 | #[coverage(off)] 66 | pub fn first_index_past_prefix_sum(&self, chosen_weight: f64) -> usize { 67 | binary_search( 68 | self.len(), 69 | #[coverage(off)] 70 | |idx| { 71 | if self.prefix_sum(idx) <= chosen_weight { 72 | Ordering::Less 73 | } else { 74 | Ordering::Greater 75 | } 76 | }, 77 | ) 78 | .unwrap_err() 79 | } 80 | } 81 | 82 | #[coverage(off)] 83 | pub fn binary_search(mut size: usize, mut f: F) -> Result 84 | where 85 | F: FnMut(usize) -> Ordering, 86 | { 87 | let mut left = 0; 88 | let mut right = size; 89 | while left < right { 90 | let mid = left + size / 2; 91 | let cmp = f(mid); 92 | if cmp == Ordering::Less { 93 | left = mid + 1; 94 | } else if cmp == Ordering::Greater { 95 | right = mid; 96 | } else { 97 | return Ok(mid); 98 | } 99 | size = right - left; 100 | } 101 | Err(left) 102 | } 103 | 104 | /* 105 | Note: I can pad the tree with zeros if its size is not a power of two 106 | this will make it possible to use this method below to query it 107 | is it worth it? 108 | 109 | // Find the largest i with prefix_sum(i) <= value. 110 | // NOTE: Requires that all values are non-negative! 111 | unsigned int rank_query(int value) { 112 | int i = 0, j = SIZE - 1; 113 | // j is a power of 2. 114 | 115 | for (; j > 0; j >>= 1) { 116 | if (i + j < SIZE && A[i + j] <= value) { 117 | value -= A[i + j]; 118 | i += j; 119 | } 120 | } 121 | return i; 122 | } 123 | */ 124 | 125 | impl FenwickTree { 126 | #[coverage(off)] 127 | pub fn sample(&self, rng: &fastrand::Rng) -> Option { 128 | if self.len() == 0 { 129 | return None; 130 | } 131 | let most = self.prefix_sum(self.len() - 1); 132 | if most <= 0.0 { 133 | return None; 134 | } 135 | let chosen_weight = gen_f64(rng, 0.0..most); 136 | 137 | // Find the first item which has a weight *higher* than the chosen weight. 138 | let choice = self.first_index_past_prefix_sum(chosen_weight); 139 | Some(choice) 140 | } 141 | } 142 | 143 | #[cfg(test)] 144 | mod tests { 145 | use super::FenwickTree; 146 | 147 | #[coverage(off)] 148 | #[test] 149 | fn test_basic_1() { 150 | let cumulative_probabilities = vec![2.0, 4.0, 1.0, 0.0, 1.2]; 151 | let mut tree = FenwickTree::new(cumulative_probabilities); 152 | for i in 0..tree.storage.len() { 153 | println!("{}", tree.prefix_sum(i)); 154 | } 155 | println!("==="); 156 | tree.update(0, -0.5); 157 | for i in 0..tree.storage.len() { 158 | println!("{}", tree.prefix_sum(i)); 159 | } 160 | println!("==="); 161 | tree.update(1, 0.5); 162 | for i in 0..tree.storage.len() { 163 | println!("{}", tree.prefix_sum(i)); 164 | } 165 | println!("==="); 166 | tree.update(3, 1.); 167 | for i in 0..tree.storage.len() { 168 | println!("{}", tree.prefix_sum(i)); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/bool.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::{DefaultMutator, Mutator}; 4 | 5 | /// Default mutator for `bool` 6 | #[derive(Default)] 7 | pub struct BoolMutator { 8 | rng: fastrand::Rng, 9 | } 10 | 11 | impl DefaultMutator for bool { 12 | type Mutator = BoolMutator; 13 | #[coverage(off)] 14 | fn default_mutator() -> Self::Mutator { 15 | <_>::default() 16 | } 17 | } 18 | 19 | #[doc(hidden)] 20 | #[derive(Clone, Copy)] 21 | pub enum ArbitraryStep { 22 | Never = 0, 23 | Once = 1, 24 | Twice = 2, 25 | } 26 | impl Default for ArbitraryStep { 27 | #[coverage(off)] 28 | fn default() -> Self { 29 | Self::Never 30 | } 31 | } 32 | 33 | const BOOL_COMPLEXITY: f64 = 1.0; 34 | const INITIAL_MUTATION_STEP: bool = false; 35 | 36 | impl Mutator for BoolMutator { 37 | #[doc(hidden)] 38 | type Cache = (); 39 | #[doc(hidden)] 40 | type MutationStep = bool; 41 | #[doc(hidden)] 42 | type ArbitraryStep = ArbitraryStep; 43 | #[doc(hidden)] 44 | type UnmutateToken = bool; 45 | 46 | #[doc(hidden)] 47 | #[coverage(off)] 48 | fn initialize(&self) {} 49 | 50 | #[doc(hidden)] 51 | #[coverage(off)] 52 | fn default_arbitrary_step(&self) -> Self::ArbitraryStep { 53 | <_>::default() 54 | } 55 | 56 | #[doc(hidden)] 57 | #[coverage(off)] 58 | fn is_valid(&self, _value: &bool) -> bool { 59 | true 60 | } 61 | 62 | #[doc(hidden)] 63 | #[coverage(off)] 64 | fn validate_value(&self, _value: &bool) -> Option { 65 | Some(()) 66 | } 67 | 68 | #[doc(hidden)] 69 | #[coverage(off)] 70 | fn default_mutation_step(&self, _value: &bool, _cache: &Self::Cache) -> Self::MutationStep { 71 | INITIAL_MUTATION_STEP 72 | } 73 | 74 | #[doc(hidden)] 75 | #[coverage(off)] 76 | fn global_search_space_complexity(&self) -> f64 { 77 | 1.0 78 | } 79 | #[doc(hidden)] 80 | #[coverage(off)] 81 | fn max_complexity(&self) -> f64 { 82 | BOOL_COMPLEXITY 83 | } 84 | #[doc(hidden)] 85 | #[coverage(off)] 86 | fn min_complexity(&self) -> f64 { 87 | BOOL_COMPLEXITY 88 | } 89 | #[doc(hidden)] 90 | #[coverage(off)] 91 | fn complexity(&self, _value: &bool, _cache: &Self::Cache) -> f64 { 92 | BOOL_COMPLEXITY 93 | } 94 | #[doc(hidden)] 95 | #[coverage(off)] 96 | fn ordered_arbitrary(&self, step: &mut Self::ArbitraryStep, max_cplx: f64) -> Option<(bool, f64)> { 97 | if max_cplx < self.min_complexity() { 98 | return None; 99 | } 100 | match step { 101 | ArbitraryStep::Never => { 102 | *step = ArbitraryStep::Once; 103 | Some((false, BOOL_COMPLEXITY)) 104 | } 105 | ArbitraryStep::Once => { 106 | *step = ArbitraryStep::Twice; 107 | Some((true, BOOL_COMPLEXITY)) 108 | } 109 | ArbitraryStep::Twice => None, 110 | } 111 | } 112 | #[doc(hidden)] 113 | #[coverage(off)] 114 | fn random_arbitrary(&self, _max_cplx: f64) -> (bool, f64) { 115 | (self.rng.bool(), BOOL_COMPLEXITY) 116 | } 117 | #[doc(hidden)] 118 | #[coverage(off)] 119 | fn ordered_mutate( 120 | &self, 121 | value: &mut bool, 122 | _cache: &mut Self::Cache, 123 | step: &mut Self::MutationStep, 124 | _subvalue_provider: &dyn crate::SubValueProvider, 125 | max_cplx: f64, 126 | ) -> Option<(Self::UnmutateToken, f64)> { 127 | if max_cplx < self.min_complexity() { 128 | return None; 129 | } 130 | if !*step { 131 | *step = !*step; 132 | Some((std::mem::replace(value, !*value), BOOL_COMPLEXITY)) 133 | } else { 134 | None 135 | } 136 | } 137 | 138 | #[doc(hidden)] 139 | #[coverage(off)] 140 | fn random_mutate(&self, value: &mut bool, _cache: &mut Self::Cache, _max_cplx: f64) -> (Self::UnmutateToken, f64) { 141 | (std::mem::replace(value, !*value), BOOL_COMPLEXITY) 142 | } 143 | 144 | #[doc(hidden)] 145 | #[coverage(off)] 146 | fn unmutate(&self, value: &mut bool, _cache: &mut Self::Cache, t: Self::UnmutateToken) { 147 | *value = t; 148 | } 149 | 150 | #[doc(hidden)] 151 | #[coverage(off)] 152 | fn visit_subvalues<'a>(&self, _value: &'a bool, _cache: &'a Self::Cache, _visit: &mut dyn FnMut(&'a dyn Any, f64)) { 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/boxed.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::CrossoverStep; 4 | use crate::{DefaultMutator, Mutator, CROSSOVER_RATE}; 5 | 6 | /// Default mutator of `Box` 7 | #[derive(Default)] 8 | pub struct BoxMutator { 9 | mutator: M, 10 | rng: fastrand::Rng, 11 | } 12 | impl BoxMutator { 13 | #[coverage(off)] 14 | pub fn new(mutator: M) -> Self { 15 | Self { 16 | mutator, 17 | rng: fastrand::Rng::new(), 18 | } 19 | } 20 | } 21 | #[derive(Clone)] 22 | pub struct MutationStep { 23 | crossover_step: CrossoverStep, 24 | inner: MS, 25 | } 26 | 27 | pub enum UnmutateToken { 28 | Replace(T), 29 | Inner(U), 30 | } 31 | 32 | impl> Mutator> for BoxMutator { 33 | #[doc(hidden)] 34 | type Cache = M::Cache; 35 | #[doc(hidden)] 36 | type MutationStep = MutationStep; 37 | #[doc(hidden)] 38 | type ArbitraryStep = M::ArbitraryStep; 39 | #[doc(hidden)] 40 | type UnmutateToken = UnmutateToken; 41 | 42 | #[doc(hidden)] 43 | #[coverage(off)] 44 | fn initialize(&self) { 45 | self.mutator.initialize(); 46 | } 47 | 48 | #[doc(hidden)] 49 | #[coverage(off)] 50 | fn default_arbitrary_step(&self) -> Self::ArbitraryStep { 51 | self.mutator.default_arbitrary_step() 52 | } 53 | #[doc(hidden)] 54 | #[coverage(off)] 55 | fn is_valid(&self, value: &Box) -> bool { 56 | self.mutator.is_valid(value) 57 | } 58 | #[doc(hidden)] 59 | #[coverage(off)] 60 | fn validate_value(&self, value: &Box) -> Option { 61 | self.mutator.validate_value(value) 62 | } 63 | #[doc(hidden)] 64 | #[coverage(off)] 65 | fn default_mutation_step(&self, value: &Box, cache: &Self::Cache) -> Self::MutationStep { 66 | MutationStep { 67 | crossover_step: CrossoverStep::default(), 68 | inner: self.mutator.default_mutation_step(value, cache), 69 | } 70 | } 71 | 72 | #[doc(hidden)] 73 | #[coverage(off)] 74 | fn global_search_space_complexity(&self) -> f64 { 75 | self.mutator.global_search_space_complexity() 76 | } 77 | 78 | #[doc(hidden)] 79 | #[coverage(off)] 80 | fn max_complexity(&self) -> f64 { 81 | self.mutator.max_complexity() 82 | } 83 | 84 | #[doc(hidden)] 85 | #[coverage(off)] 86 | fn min_complexity(&self) -> f64 { 87 | self.mutator.min_complexity() 88 | } 89 | 90 | #[doc(hidden)] 91 | #[coverage(off)] 92 | fn complexity(&self, value: &Box, cache: &Self::Cache) -> f64 { 93 | self.mutator.complexity(value, cache) 94 | } 95 | 96 | #[doc(hidden)] 97 | #[coverage(off)] 98 | fn ordered_arbitrary(&self, step: &mut Self::ArbitraryStep, max_cplx: f64) -> Option<(Box, f64)> { 99 | if let Some((value, cache)) = self.mutator.ordered_arbitrary(step, max_cplx) { 100 | Some((Box::new(value), cache)) 101 | } else { 102 | None 103 | } 104 | } 105 | 106 | #[doc(hidden)] 107 | #[coverage(off)] 108 | fn random_arbitrary(&self, max_cplx: f64) -> (Box, f64) { 109 | let (value, cache) = self.mutator.random_arbitrary(max_cplx); 110 | (Box::new(value), cache) 111 | } 112 | 113 | #[doc(hidden)] 114 | #[coverage(off)] 115 | fn ordered_mutate( 116 | &self, 117 | value: &mut Box, 118 | cache: &mut Self::Cache, 119 | step: &mut Self::MutationStep, 120 | subvalue_provider: &dyn crate::SubValueProvider, 121 | max_cplx: f64, 122 | ) -> Option<(Self::UnmutateToken, f64)> { 123 | if self.rng.u8(..CROSSOVER_RATE) == 0 124 | && let Some((subvalue, subcplx)) = step.crossover_step.get_next_subvalue(subvalue_provider, max_cplx) 125 | && self.mutator.is_valid(subvalue) 126 | { 127 | let mut replacer = subvalue.clone(); 128 | std::mem::swap(value.as_mut(), &mut replacer); 129 | return Some((UnmutateToken::Replace(replacer), subcplx)); 130 | } 131 | if let Some((t, cplx)) = self 132 | .mutator 133 | .ordered_mutate(value, cache, &mut step.inner, subvalue_provider, max_cplx) 134 | { 135 | Some((UnmutateToken::Inner(t), cplx)) 136 | } else { 137 | None 138 | } 139 | } 140 | 141 | #[doc(hidden)] 142 | #[coverage(off)] 143 | fn random_mutate(&self, value: &mut Box, cache: &mut Self::Cache, max_cplx: f64) -> (Self::UnmutateToken, f64) { 144 | let (t, cplx) = self.mutator.random_mutate(value, cache, max_cplx); 145 | (UnmutateToken::Inner(t), cplx) 146 | } 147 | 148 | #[doc(hidden)] 149 | #[coverage(off)] 150 | fn unmutate(&self, value: &mut Box, cache: &mut Self::Cache, t: Self::UnmutateToken) { 151 | match t { 152 | UnmutateToken::Replace(x) => **value = x, 153 | UnmutateToken::Inner(t) => self.mutator.unmutate(value, cache, t), 154 | } 155 | } 156 | 157 | #[doc(hidden)] 158 | #[coverage(off)] 159 | fn visit_subvalues<'a>(&self, value: &'a Box, cache: &'a Self::Cache, visit: &mut dyn FnMut(&'a dyn Any, f64)) { 160 | self.mutator.visit_subvalues(value, cache, visit) 161 | } 162 | } 163 | 164 | impl DefaultMutator for Box 165 | where 166 | T: DefaultMutator + 'static, 167 | { 168 | #[doc(hidden)] 169 | type Mutator = BoxMutator<::Mutator>; 170 | #[doc(hidden)] 171 | #[coverage(off)] 172 | fn default_mutator() -> Self::Mutator { 173 | Self::Mutator::new(T::default_mutator()) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/cow.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use super::map::MapMutator; 4 | use crate::{DefaultMutator, Mutator}; 5 | 6 | impl DefaultMutator for Cow<'static, T> 7 | where 8 | T: DefaultMutator + Clone + 'static, 9 | { 10 | type Mutator = impl Mutator>; 11 | 12 | #[coverage(off)] 13 | fn default_mutator() -> Self::Mutator { 14 | MapMutator::new( 15 | T::default_mutator(), 16 | #[coverage(off)] 17 | |t: &Cow| Some(t.clone().into_owned()), 18 | #[coverage(off)] 19 | |t: &T| Cow::Owned(t.clone()), 20 | #[coverage(off)] 21 | |_, cplx| cplx, 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/enums.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::Mutator; 4 | 5 | /// Trait used by the [DefaultMutator derive macro](fuzzcheck_mutators_derive::DefaultMutator) 6 | /// for enums without associated data 7 | pub trait BasicEnumStructure { 8 | fn from_variant_index(item_index: usize) -> Self; 9 | fn get_variant_index(&self) -> usize; 10 | } 11 | 12 | /// A mutator used for enums implementing [BasicEnumStructure] 13 | pub struct BasicEnumMutator { 14 | non_ignored_variant_count: usize, 15 | rng: fastrand::Rng, 16 | cplx: f64, 17 | } 18 | impl BasicEnumMutator { 19 | #[coverage(off)] 20 | pub fn new(non_ignored_variant_count: usize) -> Self 21 | where 22 | T: BasicEnumStructure, 23 | { 24 | Self { 25 | non_ignored_variant_count, 26 | rng: <_>::default(), 27 | cplx: crate::mutators::size_to_cplxity(non_ignored_variant_count), 28 | } 29 | } 30 | } 31 | 32 | const INITIAL_MUTATION_STEP: usize = 1; 33 | 34 | impl Mutator for BasicEnumMutator 35 | where 36 | T: Clone + BasicEnumStructure + 'static, 37 | { 38 | #[doc(hidden)] 39 | type Cache = (); 40 | #[doc(hidden)] 41 | type MutationStep = usize; 42 | #[doc(hidden)] 43 | type ArbitraryStep = usize; 44 | #[doc(hidden)] 45 | type UnmutateToken = usize; 46 | 47 | #[doc(hidden)] 48 | #[coverage(off)] 49 | fn initialize(&self) {} 50 | 51 | #[doc(hidden)] 52 | #[coverage(off)] 53 | fn default_arbitrary_step(&self) -> Self::ArbitraryStep { 54 | 0 55 | } 56 | 57 | #[doc(hidden)] 58 | #[coverage(off)] 59 | fn is_valid(&self, _value: &T) -> bool { 60 | true 61 | } 62 | 63 | #[doc(hidden)] 64 | #[coverage(off)] 65 | fn validate_value(&self, _value: &T) -> Option { 66 | Some(()) 67 | } 68 | 69 | #[doc(hidden)] 70 | #[coverage(off)] 71 | fn default_mutation_step(&self, _value: &T, _cache: &Self::Cache) -> Self::MutationStep { 72 | INITIAL_MUTATION_STEP 73 | } 74 | 75 | #[doc(hidden)] 76 | #[coverage(off)] 77 | fn global_search_space_complexity(&self) -> f64 { 78 | self.cplx 79 | } 80 | 81 | #[doc(hidden)] 82 | #[coverage(off)] 83 | fn max_complexity(&self) -> f64 { 84 | self.cplx 85 | } 86 | 87 | #[doc(hidden)] 88 | #[coverage(off)] 89 | fn min_complexity(&self) -> f64 { 90 | self.cplx 91 | } 92 | 93 | #[doc(hidden)] 94 | #[coverage(off)] 95 | fn complexity(&self, _value: &T, _cache: &Self::Cache) -> f64 { 96 | self.cplx 97 | } 98 | 99 | #[doc(hidden)] 100 | #[coverage(off)] 101 | fn ordered_arbitrary(&self, step: &mut Self::ArbitraryStep, max_cplx: f64) -> Option<(T, f64)> { 102 | if max_cplx < >::min_complexity(self) { 103 | return None; 104 | } 105 | if *step < self.non_ignored_variant_count { 106 | let old_step = *step; 107 | *step += 1; 108 | Some((T::from_variant_index(old_step), self.cplx)) 109 | } else { 110 | None 111 | } 112 | } 113 | 114 | #[doc(hidden)] 115 | #[coverage(off)] 116 | fn random_arbitrary(&self, _max_cplx: f64) -> (T, f64) { 117 | let item_idx = self.rng.usize(..self.non_ignored_variant_count); 118 | (T::from_variant_index(item_idx), self.cplx) 119 | } 120 | 121 | #[doc(hidden)] 122 | #[coverage(off)] 123 | fn ordered_mutate( 124 | &self, 125 | value: &mut T, 126 | _cache: &mut Self::Cache, 127 | step: &mut Self::MutationStep, 128 | _subvalue_provider: &dyn crate::SubValueProvider, 129 | max_cplx: f64, 130 | ) -> Option<(Self::UnmutateToken, f64)> { 131 | if max_cplx < >::min_complexity(self) { 132 | return None; 133 | } 134 | // starts at step = 1 135 | // create new from (get_item_index + step) % nbr_of_items 136 | if *step < self.non_ignored_variant_count { 137 | let old_index = value.get_variant_index(); 138 | let old_step = *step; 139 | *step += 1; 140 | *value = T::from_variant_index((old_index + old_step) % self.non_ignored_variant_count); 141 | Some((old_index, self.cplx)) 142 | } else { 143 | None 144 | } 145 | } 146 | 147 | #[doc(hidden)] 148 | #[coverage(off)] 149 | fn random_mutate(&self, value: &mut T, _cache: &mut Self::Cache, _max_cplx: f64) -> (Self::UnmutateToken, f64) { 150 | let old_index = value.get_variant_index(); 151 | let item_idx = self.rng.usize(..self.non_ignored_variant_count); 152 | *value = T::from_variant_index(item_idx); 153 | (old_index, self.cplx) 154 | } 155 | 156 | #[doc(hidden)] 157 | #[coverage(off)] 158 | fn unmutate(&self, value: &mut T, _cache: &mut Self::Cache, t: Self::UnmutateToken) { 159 | *value = T::from_variant_index(t); 160 | } 161 | 162 | #[doc(hidden)] 163 | #[coverage(off)] 164 | fn visit_subvalues<'a>(&self, _value: &'a T, _cache: &'a Self::Cache, _visit: &mut dyn FnMut(&'a dyn Any, f64)) {} 165 | } 166 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/filter.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::Mutator; 4 | 5 | /// A [`FilterMutator`] provides a way to filter values outputted by a mutator. 6 | /// Given any [`Mutator`] and a function [`Fn(&T) -> bool`] it creates 7 | /// a new mutator which can generate all the values of `T` the underlying 8 | /// mutator can, except those for which the filtering function returns false. 9 | pub struct FilterMutator { 10 | mutator: M, 11 | filter: F, 12 | } 13 | 14 | impl FilterMutator { 15 | /// Creates a new [`FilterMutator`]. 16 | /// 17 | /// Note that the mutator will filter all values for which the filtering 18 | /// function returns _false_. 19 | pub fn new(mutator: M, filter: F) -> FilterMutator 20 | where 21 | M: Mutator, 22 | T: Clone + 'static, 23 | F: Fn(&T) -> bool, 24 | Self: 'static, 25 | { 26 | FilterMutator { mutator, filter } 27 | } 28 | } 29 | 30 | impl Mutator for FilterMutator 31 | where 32 | M: Mutator, 33 | T: Clone + 'static, 34 | F: Fn(&T) -> bool, 35 | Self: 'static, 36 | { 37 | #[doc(hidden)] 38 | type Cache = >::Cache; 39 | #[doc(hidden)] 40 | type MutationStep = >::MutationStep; 41 | #[doc(hidden)] 42 | type ArbitraryStep = >::ArbitraryStep; 43 | #[doc(hidden)] 44 | type UnmutateToken = >::UnmutateToken; 45 | 46 | #[doc(hidden)] 47 | #[coverage(off)] 48 | fn initialize(&self) { 49 | self.mutator.initialize(); 50 | } 51 | 52 | #[doc(hidden)] 53 | #[coverage(off)] 54 | fn default_arbitrary_step(&self) -> Self::ArbitraryStep { 55 | self.mutator.default_arbitrary_step() 56 | } 57 | 58 | #[doc(hidden)] 59 | #[coverage(off)] 60 | fn is_valid(&self, value: &T) -> bool { 61 | self.mutator.is_valid(value) && (self.filter)(value) 62 | } 63 | 64 | #[doc(hidden)] 65 | #[coverage(off)] 66 | fn validate_value(&self, value: &T) -> Option { 67 | let x = self.mutator.validate_value(value); 68 | if x.is_some() && (self.filter)(value) == false { 69 | None 70 | } else { 71 | x 72 | } 73 | } 74 | 75 | #[doc(hidden)] 76 | #[coverage(off)] 77 | fn default_mutation_step(&self, value: &T, cache: &Self::Cache) -> Self::MutationStep { 78 | self.mutator.default_mutation_step(value, cache) 79 | } 80 | 81 | #[doc(hidden)] 82 | #[coverage(off)] 83 | fn max_complexity(&self) -> f64 { 84 | self.mutator.max_complexity() 85 | } 86 | 87 | #[doc(hidden)] 88 | #[coverage(off)] 89 | fn global_search_space_complexity(&self) -> f64 { 90 | self.mutator.global_search_space_complexity() 91 | } 92 | 93 | #[doc(hidden)] 94 | #[coverage(off)] 95 | fn min_complexity(&self) -> f64 { 96 | self.mutator.min_complexity() 97 | } 98 | 99 | #[doc(hidden)] 100 | #[coverage(off)] 101 | fn complexity(&self, value: &T, cache: &Self::Cache) -> f64 { 102 | self.mutator.complexity(value, cache) 103 | } 104 | 105 | #[doc(hidden)] 106 | #[coverage(off)] 107 | fn ordered_arbitrary(&self, step: &mut Self::ArbitraryStep, max_cplx: f64) -> Option<(T, f64)> { 108 | loop { 109 | let x = self.mutator.ordered_arbitrary(step, max_cplx); 110 | if let Some(x) = x { 111 | if (self.filter)(&x.0) { 112 | return Some(x); 113 | } 114 | } else { 115 | return None; 116 | } 117 | } 118 | } 119 | 120 | #[doc(hidden)] 121 | #[coverage(off)] 122 | fn random_arbitrary(&self, max_cplx: f64) -> (T, f64) { 123 | loop { 124 | let x = self.mutator.random_arbitrary(max_cplx); 125 | if (self.filter)(&x.0) { 126 | return x; 127 | } 128 | } 129 | } 130 | 131 | #[doc(hidden)] 132 | #[coverage(off)] 133 | fn ordered_mutate( 134 | &self, 135 | value: &mut T, 136 | cache: &mut Self::Cache, 137 | step: &mut Self::MutationStep, 138 | subvalue_provider: &dyn crate::SubValueProvider, 139 | max_cplx: f64, 140 | ) -> Option<(Self::UnmutateToken, f64)> { 141 | loop { 142 | if let Some((t, cplx)) = self 143 | .mutator 144 | .ordered_mutate(value, cache, step, subvalue_provider, max_cplx) 145 | { 146 | if (self.filter)(value) { 147 | return Some((t, cplx)); 148 | } else { 149 | self.mutator.unmutate(value, cache, t); 150 | } 151 | } else { 152 | return None; 153 | } 154 | } 155 | } 156 | 157 | #[doc(hidden)] 158 | #[coverage(off)] 159 | fn random_mutate(&self, value: &mut T, cache: &mut Self::Cache, max_cplx: f64) -> (Self::UnmutateToken, f64) { 160 | loop { 161 | let (t, cplx) = self.mutator.random_mutate(value, cache, max_cplx); 162 | if (self.filter)(value) { 163 | return (t, cplx); 164 | } else { 165 | self.mutator.unmutate(value, cache, t); 166 | } 167 | } 168 | } 169 | 170 | #[doc(hidden)] 171 | #[coverage(off)] 172 | fn unmutate(&self, value: &mut T, cache: &mut Self::Cache, t: Self::UnmutateToken) { 173 | self.mutator.unmutate(value, cache, t) 174 | } 175 | 176 | #[doc(hidden)] 177 | #[coverage(off)] 178 | fn visit_subvalues<'a>(&self, value: &'a T, cache: &'a Self::Cache, visit: &mut dyn FnMut(&'a dyn Any, f64)) { 179 | self.mutator.visit_subvalues(value, cache, visit) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/grammar/ast.rs: -------------------------------------------------------------------------------- 1 | extern crate self as fuzzcheck; 2 | 3 | #[cfg(feature = "serde_json_serializer")] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// An abstract syntax tree. 7 | /// 8 | #[cfg_attr( 9 | feature = "serde_json_serializer", 10 | doc = "It can be serialized with [`SerdeSerializer`](crate::SerdeSerializer) on crate feature `serde_json_serializer`" 11 | )] 12 | #[cfg_attr( 13 | not(feature = "serde_json_serializer"), 14 | doc = "It can be serialized with `SerdeSerializer` on crate feature `serde_json_serializer`" 15 | )] 16 | #[cfg_attr(feature = "serde_json_serializer", derive(Serialize, Deserialize))] 17 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 18 | pub enum AST { 19 | #[doc(hidden)] 20 | Token(char), 21 | #[doc(hidden)] 22 | Sequence(Vec), 23 | } 24 | 25 | impl AST { 26 | #[coverage(off)] 27 | pub fn generate_string_in(&self, string: &mut String) { 28 | match self { 29 | AST::Token(c) => { 30 | string.push(*c); 31 | } 32 | AST::Sequence(asts) => { 33 | for ast in asts { 34 | ast.generate_string_in(string); 35 | } 36 | } 37 | } 38 | } 39 | 40 | /// Converts the AST to its `String` representation 41 | #[allow(clippy::inherent_to_string)] 42 | #[coverage(off)] 43 | pub fn to_string(&self) -> String { 44 | let mut s = String::with_capacity(64); 45 | self.generate_string_in(&mut s); 46 | s 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/grammar/grammar.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Range, RangeBounds, RangeInclusive}; 2 | use std::rc::{Rc, Weak}; 3 | 4 | #[cfg(feature = "regex_grammar")] 5 | use crate::mutators::grammar::regex::grammar_from_regex; 6 | 7 | #[derive(Clone, Debug)] 8 | /// A grammar which can be used for fuzzing. 9 | /// 10 | /// See [the module documentation](crate::mutators::grammar) for advice on how to create a grammar. 11 | pub enum Grammar { 12 | Literal(Vec>), 13 | Alternation(Vec>), 14 | Concatenation(Vec>), 15 | Repetition(Rc, Range), 16 | Recurse(Weak), 17 | Recursive(Rc), 18 | } 19 | 20 | #[cfg(feature = "regex_grammar")] 21 | #[doc(cfg(feature = "regex_grammar"))] 22 | #[coverage(off)] 23 | pub fn regex(s: &str) -> Rc { 24 | grammar_from_regex(s) 25 | } 26 | 27 | #[coverage(off)] 28 | /// Creates an [`Rc`] which outputs characters in the given range. 29 | /// 30 | /// For example, to generate characters in the range 'a' to 'z' (inclusive), one 31 | /// could use this code 32 | /// 33 | /// ``` 34 | /// # use fuzzcheck::mutators::grammar::literal_ranges; 35 | /// let a_to_z = literal_ranges(vec!['a'..='z']); 36 | /// ``` 37 | pub fn literal_ranges(ranges: Vec>) -> Rc { 38 | Rc::new(Grammar::Literal(ranges)) 39 | } 40 | 41 | #[coverage(off)] 42 | /// Creates an [`Rc`] which matches a single character literal. 43 | /// 44 | /// ``` 45 | /// # use fuzzcheck::mutators::grammar::literal; 46 | /// let l = literal('l'); 47 | /// ``` 48 | pub fn literal(l: char) -> Rc { 49 | Rc::new(Grammar::Literal(vec![l..=l])) 50 | } 51 | #[coverage(off)] 52 | pub fn literal_range(range: R) -> Rc 53 | where 54 | R: RangeBounds, 55 | { 56 | let start = match range.start_bound() { 57 | std::ops::Bound::Included(x) => *x, 58 | std::ops::Bound::Excluded(x) => unsafe { char::from_u32_unchecked((*x as u32) + 1) }, 59 | std::ops::Bound::Unbounded => panic!("The range must have a lower bound"), 60 | }; 61 | let end = match range.end_bound() { 62 | std::ops::Bound::Included(x) => *x, 63 | std::ops::Bound::Excluded(x) => unsafe { char::from_u32_unchecked(*x as u32 - 1) }, 64 | std::ops::Bound::Unbounded => panic!("The range must have an upper bound"), 65 | }; 66 | Rc::new(Grammar::Literal(vec![start..=end])) 67 | } 68 | 69 | /// Produces a grammar which will choose between the provided grammars. 70 | #[coverage(off)] 71 | pub fn alternation(gs: impl IntoIterator>) -> Rc { 72 | let t: Vec> = gs.into_iter().collect(); 73 | if t.is_empty() { 74 | panic!("Error: alternation grammars must not be empty!") 75 | } 76 | Rc::new(Grammar::Alternation(t)) 77 | } 78 | 79 | /// Produces a grammar which will concatenate the output of all the provided 80 | /// grammars together, in order. 81 | /// 82 | /// For example, the grammar 83 | /// ``` 84 | /// # use fuzzcheck::mutators::grammar::regex; 85 | /// # use fuzzcheck::mutators::grammar::concatenation; 86 | /// concatenation([ 87 | /// regex("fuzz"), 88 | /// regex("check") 89 | /// ]); 90 | /// ``` 91 | /// would output "fuzzcheck". 92 | #[coverage(off)] 93 | pub fn concatenation(gs: impl IntoIterator>) -> Rc { 94 | Rc::new(Grammar::Concatenation(gs.into_iter().collect())) 95 | } 96 | 97 | #[coverage(off)] 98 | /// Repeats the provided grammar some number of times in the given range. 99 | pub fn repetition(gs: Rc, range: R) -> Rc 100 | where 101 | R: RangeBounds, 102 | { 103 | let start = match range.start_bound() { 104 | std::ops::Bound::Included(x) => *x, 105 | std::ops::Bound::Excluded(x) => *x + 1, 106 | std::ops::Bound::Unbounded => panic!("The range must have a lower bound"), 107 | }; 108 | let end = match range.end_bound() { 109 | std::ops::Bound::Included(x) => x.saturating_add(1), 110 | std::ops::Bound::Excluded(x) => *x, 111 | std::ops::Bound::Unbounded => usize::MAX, 112 | }; 113 | Rc::new(Grammar::Repetition(gs, start..end)) 114 | } 115 | 116 | #[coverage(off)] 117 | /// Used to indicate a point of recursion to Fuzzcheck. Should be combined with 118 | /// [`recursive`]. 119 | /// 120 | /// See the module documentation ([`super`]) for an example on how to use it. 121 | pub fn recurse(g: &Weak) -> Rc { 122 | Rc::new(Grammar::Recurse(g.clone())) 123 | } 124 | 125 | #[coverage(off)] 126 | /// Creates a recursive grammar. This function should be combined with 127 | /// [`recurse`] to make recursive calls. 128 | /// 129 | /// See the module documentation ([`super`]) for an example on how to use it. 130 | pub fn recursive(data_fn: impl Fn(&Weak) -> Rc) -> Rc { 131 | Rc::new(Grammar::Recursive(Rc::new_cyclic(|g| { 132 | Rc::try_unwrap(data_fn(g)).unwrap() 133 | }))) 134 | } 135 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/grammar/mod.rs: -------------------------------------------------------------------------------- 1 | //! Grammar-based mutators and related utilties. 2 | //! 3 | //! This module provides a grammar-based `impl Mutator` which generates an abstract syntax 4 | //! tree satisfying a grammar, created through [`grammar_based_ast_mutator`]. The resulting mutator can be 5 | //! transformed into a `Mutator<(AST, String)>`, where the second element of the tuple is the string corresponding 6 | //! to the abstract syntax tree, by calling [`.with_string()`](ASTMutator::with_string). 7 | //! 8 | //! To specify a grammar, you should use the following functions: 9 | #![cfg_attr( 10 | feature = "regex_grammar", 11 | doc = "* [`regex`](crate::mutators::grammar::regex) to create a grammar from a regular expression **(only supported on crate feature `regex_grammar`)**" 12 | )] 13 | //! * [`literal`] for a grammar that matches a single character 14 | //! * [`literal_ranges`] for a grammar matching a single character within a specified ranges 15 | //! * [`literal_ranges`] for a grammar matching a single character within any of multiple ranges 16 | //! * [`alternation`] for a grammar matching any of a list of grammar rules 17 | //! * [`concatenation`] matching multiple grammar rules one after the other 18 | //! * [`repetition`] matching a grammar rule multiple times 19 | //! * [`recursive`] and [`recurse`] to create recursive grammar rules 20 | #![cfg_attr( 21 | feature = "regex_grammar", 22 | doc = r###" 23 | Examples: 24 | ``` 25 | use fuzzcheck::mutators::grammar::{alternation, concatenation, literal, literal_range, recurse, recursive, regex, repetition}; 26 | let rule = repetition( 27 | concatenation([ 28 | alternation([ 29 | regex("hello[0-9]"), 30 | regex("world[0-9]?") 31 | ]), 32 | literal(' '), 33 | ]), 34 | 1..5 35 | ); 36 | /* “rule” matches/generates: 37 | hello1 38 | hello2 hello6 world 39 | world8 hello5 world7 world world 40 | ... 41 | */ 42 | let rule = recursive(|rule| { 43 | alternation([ 44 | concatenation([ 45 | regex(r"\(|\["), 46 | recurse(rule), 47 | regex(r"\)|\]"), 48 | ]), 49 | literal_range('a' ..= 'z') 50 | ]) 51 | }); 52 | /* rule matches/generates: 53 | (([a))) 54 | (([[d))]) 55 | z 56 | ... 57 | */ 58 | ``` 59 | "### 60 | )] 61 | #![allow(clippy::type_complexity)] 62 | #![allow(clippy::module_inception)] 63 | #![allow(clippy::nonstandard_macro_braces)] 64 | 65 | mod ast; 66 | mod grammar; 67 | mod mutators; 68 | 69 | #[cfg(feature = "regex_grammar")] 70 | mod regex; 71 | 72 | #[doc(inline)] 73 | pub use ast::AST; 74 | #[cfg(feature = "regex_grammar")] 75 | #[doc(inline)] 76 | #[doc(cfg(feature = "regex_grammar"))] 77 | pub use grammar::regex; 78 | #[doc(inline)] 79 | pub use grammar::Grammar; 80 | #[doc(inline)] 81 | pub use grammar::{alternation, concatenation, literal, literal_range, literal_ranges, recurse, recursive, repetition}; 82 | #[doc(inline)] 83 | pub use mutators::grammar_based_ast_mutator; 84 | #[doc(inline)] 85 | pub use mutators::ASTMutator; 86 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/grammar/regex.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use regex_syntax::hir::{Class, HirKind, Literal, RepetitionKind, RepetitionRange}; 4 | 5 | use crate::mutators::grammar::{alternation, concatenation, literal, literal_ranges, repetition, Grammar}; 6 | 7 | #[coverage(off)] 8 | pub(crate) fn grammar_from_regex(regex: &str) -> Rc { 9 | let mut parser = regex_syntax::Parser::new(); 10 | let hir = parser.parse(regex).unwrap(); 11 | grammar_from_regex_hir_kind(hir.kind()) 12 | } 13 | #[coverage(off)] 14 | pub fn grammar_from_regex_hir_kind(hir: &HirKind) -> Rc { 15 | match hir { 16 | HirKind::Empty => panic!("empty regexes are not supported"), 17 | HirKind::Literal(l) => match l { 18 | Literal::Unicode(l) => literal(*l), 19 | Literal::Byte(_) => panic!("non-unicode regexes are not supported"), 20 | }, 21 | HirKind::Class(class) => match class { 22 | Class::Unicode(class) => { 23 | let ranges = class 24 | .ranges() 25 | .iter() 26 | .map( 27 | #[coverage(off)] 28 | |r| r.start()..=r.end(), 29 | ) 30 | .collect::>(); 31 | literal_ranges(ranges) 32 | } 33 | Class::Bytes(_) => panic!("non-unicode regexes are not supported"), 34 | }, 35 | HirKind::Anchor(_) => panic!("anchors are not supported"), 36 | HirKind::WordBoundary(_) => panic!("word boundaries are not supported"), 37 | HirKind::Repetition(rep) => { 38 | let range = match rep.kind.clone() { 39 | RepetitionKind::ZeroOrOne => 0..=1u32, 40 | RepetitionKind::ZeroOrMore => 0..=u32::MAX, 41 | RepetitionKind::OneOrMore => 1..=u32::MAX, 42 | RepetitionKind::Range(range) => match range { 43 | RepetitionRange::Exactly(n) => n..=n, 44 | RepetitionRange::AtLeast(n) => n..=u32::MAX, 45 | RepetitionRange::Bounded(n, m) => n..=m, 46 | }, 47 | }; 48 | let range = (*range.start() as usize)..=(*range.end() as usize); 49 | let grammar = grammar_from_regex_hir_kind(rep.hir.kind()); 50 | repetition(grammar, range) 51 | } 52 | HirKind::Group(group) => grammar_from_regex_hir_kind(group.hir.kind()), 53 | HirKind::Concat(concat) => concatenation(concat.iter().map( 54 | #[coverage(off)] 55 | |hir| grammar_from_regex_hir_kind(hir.kind()), 56 | )), 57 | HirKind::Alternation(alt) => alternation(alt.iter().map( 58 | #[coverage(off)] 59 | |hir| grammar_from_regex_hir_kind(hir.kind()), 60 | )), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/mutations/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{Mutator, SubValueProvider}; 2 | 3 | // maybe the mutator should be a generic type parameter of the MutateOperation trait, maybe the T and C should be too 4 | // but the step and revert should not 5 | pub trait Mutation: Sized 6 | where 7 | Value: Clone + 'static, 8 | M: Mutator, 9 | { 10 | type RandomStep: Clone; 11 | type Step: Clone; 12 | type Concrete<'a> 13 | where 14 | M: 'a, 15 | Value: 'a; 16 | type Revert: RevertMutation; 17 | 18 | fn default_random_step(&self, mutator: &M, value: &Value) -> Option; 19 | 20 | fn random<'a>( 21 | mutator: &M, 22 | value: &Value, 23 | cache: &M::Cache, 24 | random_step: &Self::RandomStep, 25 | max_cplx: f64, 26 | ) -> Self::Concrete<'a>; 27 | 28 | fn default_step(&self, mutator: &M, value: &Value, cache: &M::Cache) -> Option; 29 | fn from_step<'a>( 30 | mutator: &M, 31 | value: &Value, 32 | cache: &M::Cache, 33 | step: &'a mut Self::Step, 34 | subvalue_provider: &dyn SubValueProvider, 35 | max_cplx: f64, 36 | ) -> Option>; 37 | 38 | fn apply<'a>( 39 | mutation: Self::Concrete<'a>, 40 | mutator: &M, 41 | value: &mut Value, 42 | cache: &mut M::Cache, 43 | subvalue_provider: &dyn SubValueProvider, 44 | max_cplx: f64, 45 | ) -> (Self::Revert, f64); 46 | } 47 | pub trait RevertMutation 48 | where 49 | Value: Clone + 'static, 50 | M: Mutator, 51 | { 52 | fn revert(self, mutator: &M, value: &mut Value, cache: &mut M::Cache); 53 | } 54 | 55 | pub struct NoMutation; 56 | impl RevertMutation for NoMutation 57 | where 58 | Value: Clone + 'static, 59 | M: Mutator, 60 | { 61 | #[coverage(off)] 62 | fn revert(self, _mutator: &M, _value: &mut Value, _cache: &mut M::Cache) {} 63 | } 64 | impl Mutation for NoMutation 65 | where 66 | Value: Clone + 'static, 67 | M: Mutator, 68 | { 69 | type RandomStep = (); 70 | type Step = (); 71 | type Concrete<'a> = (); 72 | 73 | type Revert = NoMutation; 74 | 75 | #[coverage(off)] 76 | fn default_random_step(&self, _mutator: &M, _value: &Value) -> Option { 77 | None 78 | } 79 | 80 | #[coverage(off)] 81 | fn random<'a>( 82 | _mutator: &M, 83 | _value: &Value, 84 | _cache: &M::Cache, 85 | _random_step: &Self::RandomStep, 86 | _max_cplx: f64, 87 | ) -> Self::Concrete<'a> { 88 | } 89 | 90 | #[coverage(off)] 91 | fn default_step(&self, _mutator: &M, _value: &Value, _cache: &M::Cache) -> Option { 92 | None 93 | } 94 | 95 | #[coverage(off)] 96 | fn from_step<'a>( 97 | _mutator: &M, 98 | _value: &Value, 99 | _cache: &M::Cache, 100 | _step: &'a mut Self::Step, 101 | _subvalue_provider: &dyn SubValueProvider, 102 | _max_cplx: f64, 103 | ) -> Option> { 104 | None 105 | } 106 | 107 | #[coverage(off)] 108 | fn apply<'a>( 109 | _mutation: Self::Concrete<'a>, 110 | mutator: &M, 111 | value: &mut Value, 112 | cache: &mut M::Cache, 113 | _subvalue_provider: &dyn SubValueProvider, 114 | _max_cplx: f64, 115 | ) -> (Self::Revert, f64) { 116 | (NoMutation, mutator.complexity(value, cache)) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/option.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck_mutators_derive::make_mutator; 2 | extern crate self as fuzzcheck; 3 | 4 | make_mutator! { 5 | name: OptionMutator, 6 | default: true, 7 | type: pub enum Option { 8 | Some(T), 9 | None, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/range.rs: -------------------------------------------------------------------------------- 1 | extern crate self as fuzzcheck; 2 | 3 | use std::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; 4 | 5 | use fuzzcheck_mutators_derive::make_mutator; 6 | 7 | use crate::mutators::map::MapMutator; 8 | use crate::mutators::tuples::{Tuple2, Tuple2Mutator, TupleMutatorWrapper}; 9 | use crate::mutators::Wrapper; 10 | use crate::{DefaultMutator, Mutator}; 11 | 12 | make_mutator! { 13 | name: RangeMutator, 14 | default: true, 15 | type: pub struct Range { 16 | start: Idx, 17 | end: Idx, 18 | } 19 | } 20 | 21 | make_mutator! { 22 | name: RangeFromMutator, 23 | default: true, 24 | type: pub struct RangeFrom { 25 | start: Idx, 26 | } 27 | } 28 | 29 | make_mutator! { 30 | name: RangeToMutator, 31 | default: true, 32 | type: pub struct RangeTo { 33 | end: Idx, 34 | } 35 | } 36 | 37 | make_mutator! { 38 | name: RangeToInclusiveMutator, 39 | default: true, 40 | type: pub struct RangeToInclusive { 41 | end: Idx, 42 | } 43 | } 44 | 45 | make_mutator! { 46 | name: RangeFullMutator, 47 | default: true, 48 | type: pub struct RangeFull; 49 | } 50 | 51 | make_mutator! { 52 | name: BoundMutator, 53 | default: true, 54 | type: pub enum Bound { 55 | Included(T), 56 | Excluded(T), 57 | Unbounded, 58 | } 59 | } 60 | 61 | #[coverage(off)] 62 | fn range_inclusive_from_tuple(t: &(T, T)) -> RangeInclusive { 63 | t.0.clone()..=t.1.clone() 64 | } 65 | #[coverage(off)] 66 | fn tuple_from_range_inclusive(r: &RangeInclusive) -> Option<(T, T)> { 67 | Some((r.start().clone(), r.end().clone())) 68 | } 69 | 70 | #[coverage(off)] 71 | fn range_cplx(_r: &RangeInclusive, orig_cplx: f64) -> f64 { 72 | orig_cplx 73 | } 74 | 75 | pub type RangeInclusiveMutator = Wrapper< 76 | MapMutator< 77 | (T, T), 78 | RangeInclusive, 79 | TupleMutatorWrapper, Tuple2>, 80 | fn(&RangeInclusive) -> Option<(T, T)>, 81 | fn(&(T, T)) -> RangeInclusive, 82 | fn(&RangeInclusive, f64) -> f64, 83 | >, 84 | >; 85 | 86 | impl RangeInclusiveMutator 87 | where 88 | T: Clone, 89 | M: Mutator + Clone, 90 | { 91 | #[coverage(off)] 92 | pub fn new(m: M) -> Self { 93 | Wrapper(MapMutator::new( 94 | TupleMutatorWrapper::new(Tuple2Mutator::new(m.clone(), m)), 95 | tuple_from_range_inclusive, 96 | range_inclusive_from_tuple, 97 | range_cplx, 98 | )) 99 | } 100 | } 101 | impl DefaultMutator for RangeInclusive 102 | where 103 | T: 'static + Clone + DefaultMutator, 104 | T::Mutator: Clone, 105 | { 106 | type Mutator = RangeInclusiveMutator; 107 | #[coverage(off)] 108 | fn default_mutator() -> Self::Mutator { 109 | Self::Mutator::new(T::default_mutator()) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/result.rs: -------------------------------------------------------------------------------- 1 | use std::result::Result; 2 | 3 | use fuzzcheck_mutators_derive::make_mutator; 4 | extern crate self as fuzzcheck; 5 | 6 | make_mutator! { 7 | name: ResultMutator, 8 | default: true, 9 | type: pub enum Result { 10 | Ok(T), 11 | Err(E) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/string.rs: -------------------------------------------------------------------------------- 1 | use crate::mutators::map::MapMutator; 2 | use crate::{DefaultMutator, Mutator}; 3 | 4 | /// The default mutator for strings. It is not very good and will be replaced by a different 5 | /// one in the future. 6 | /// 7 | /// Construct it with: 8 | /// ```rust 9 | /// use fuzzcheck::DefaultMutator; 10 | /// 11 | /// let m = String::default_mutator(); 12 | /// // or: 13 | /// use fuzzcheck::mutators::string::string_mutator; 14 | /// 15 | /// let m = string_mutator(); 16 | /// ``` 17 | pub type StringMutator = impl Mutator; 18 | 19 | #[coverage(off)] 20 | #[define_opaque(StringMutator)] 21 | pub fn string_mutator() -> StringMutator { 22 | MapMutator::new( 23 | // the base mutator produces values of type Vector 24 | >::default_mutator(), 25 | // the parse function: given a string, how can I get a vector? 26 | #[coverage(off)] 27 | |string: &String| Some(string.as_bytes().to_vec()), 28 | // the map function: how can I get a string from a vector? 29 | #[coverage(off)] 30 | |xs| String::from_utf8_lossy(xs).to_string(), 31 | // the complexity function 32 | #[coverage(off)] 33 | |value, _cplx| (value.as_bytes().len() * 8) as f64, 34 | ) 35 | } 36 | 37 | impl DefaultMutator for String { 38 | type Mutator = impl Mutator; 39 | 40 | #[coverage(off)] 41 | fn default_mutator() -> Self::Mutator { 42 | string_mutator() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/unit.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::marker::PhantomData; 3 | 4 | use crate::{DefaultMutator, Mutator}; 5 | 6 | pub type VoidMutator = UnitMutator<()>; 7 | 8 | impl DefaultMutator for () { 9 | type Mutator = VoidMutator; 10 | #[coverage(off)] 11 | fn default_mutator() -> Self::Mutator { 12 | Self::Mutator::new((), 0.0) 13 | } 14 | } 15 | 16 | pub type PhantomDataMutator = UnitMutator>; 17 | impl DefaultMutator for PhantomData 18 | where 19 | T: 'static, 20 | { 21 | type Mutator = PhantomDataMutator; 22 | #[coverage(off)] 23 | fn default_mutator() -> Self::Mutator { 24 | Self::Mutator::new(PhantomData, 0.0) 25 | } 26 | } 27 | 28 | #[derive(Clone)] 29 | pub struct UnitMutator 30 | where 31 | T: Clone, 32 | { 33 | value: T, 34 | complexity: f64, 35 | } 36 | 37 | impl UnitMutator 38 | where 39 | T: Clone, 40 | { 41 | #[coverage(off)] 42 | pub fn new(value: T, complexity: f64) -> Self { 43 | Self { value, complexity } 44 | } 45 | } 46 | 47 | impl Mutator for UnitMutator 48 | where 49 | T: Clone + 'static, 50 | { 51 | #[doc(hidden)] 52 | type Cache = (); 53 | #[doc(hidden)] 54 | type MutationStep = (); 55 | #[doc(hidden)] 56 | type ArbitraryStep = bool; 57 | #[doc(hidden)] 58 | type UnmutateToken = (); 59 | 60 | #[doc(hidden)] 61 | #[coverage(off)] 62 | fn initialize(&self) {} 63 | 64 | #[doc(hidden)] 65 | #[coverage(off)] 66 | fn default_arbitrary_step(&self) -> Self::ArbitraryStep { 67 | false 68 | } 69 | 70 | #[doc(hidden)] 71 | #[coverage(off)] 72 | fn is_valid(&self, _value: &T) -> bool { 73 | true 74 | } 75 | 76 | #[doc(hidden)] 77 | #[coverage(off)] 78 | fn validate_value(&self, _value: &T) -> Option { 79 | Some(()) 80 | } 81 | 82 | #[doc(hidden)] 83 | #[coverage(off)] 84 | fn default_mutation_step(&self, _value: &T, _cache: &Self::Cache) -> Self::MutationStep {} 85 | 86 | #[doc(hidden)] 87 | #[coverage(off)] 88 | fn global_search_space_complexity(&self) -> f64 { 89 | 0.0 90 | } 91 | 92 | #[doc(hidden)] 93 | #[coverage(off)] 94 | fn max_complexity(&self) -> f64 { 95 | self.complexity 96 | } 97 | 98 | #[doc(hidden)] 99 | #[coverage(off)] 100 | fn min_complexity(&self) -> f64 { 101 | self.complexity 102 | } 103 | 104 | #[doc(hidden)] 105 | #[coverage(off)] 106 | fn complexity(&self, _value: &T, _cache: &Self::Cache) -> f64 { 107 | self.complexity 108 | } 109 | 110 | #[doc(hidden)] 111 | #[coverage(off)] 112 | fn ordered_arbitrary(&self, step: &mut Self::ArbitraryStep, _max_cplx: f64) -> Option<(T, f64)> { 113 | if !*step { 114 | *step = true; 115 | Some((self.value.clone(), self.complexity)) 116 | } else { 117 | None 118 | } 119 | } 120 | 121 | #[doc(hidden)] 122 | #[coverage(off)] 123 | fn random_arbitrary(&self, _max_cplx: f64) -> (T, f64) { 124 | (self.value.clone(), self.complexity) 125 | } 126 | 127 | #[doc(hidden)] 128 | #[coverage(off)] 129 | fn ordered_mutate( 130 | &self, 131 | _value: &mut T, 132 | _cache: &mut Self::Cache, 133 | _step: &mut Self::MutationStep, 134 | _subvalue_provider: &dyn crate::SubValueProvider, 135 | _max_cplx: f64, 136 | ) -> Option<(Self::UnmutateToken, f64)> { 137 | None 138 | } 139 | 140 | #[doc(hidden)] 141 | #[coverage(off)] 142 | fn random_mutate(&self, _value: &mut T, _cache: &mut Self::Cache, _max_cplx: f64) -> (Self::UnmutateToken, f64) { 143 | ((), self.complexity) 144 | } 145 | 146 | #[doc(hidden)] 147 | #[coverage(off)] 148 | fn unmutate(&self, _value: &mut T, _cache: &mut Self::Cache, _t: Self::UnmutateToken) {} 149 | 150 | #[doc(hidden)] 151 | #[coverage(off)] 152 | fn visit_subvalues<'a>(&self, _value: &'a T, _cache: &'a Self::Cache, _visit: &mut dyn FnMut(&'a dyn Any, f64)) {} 153 | } 154 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/arbitrary.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::{Mutator, SubValueProvider}; 4 | 5 | pub struct Arbitrary; 6 | 7 | #[derive(Clone)] 8 | pub struct ArbitraryStep; 9 | 10 | pub struct ConcreteArbitrary { 11 | value: Vec, 12 | cplx: f64, 13 | } 14 | pub struct RevertArbitrary { 15 | value: Vec, 16 | } 17 | 18 | impl RevertMutation, VecMutator> for RevertArbitrary 19 | where 20 | T: Clone + 'static, 21 | M: Mutator, 22 | { 23 | #[coverage(off)] 24 | fn revert( 25 | mut self, 26 | _mutator: &VecMutator, 27 | value: &mut Vec, 28 | _cache: &mut as Mutator>>::Cache, 29 | ) { 30 | std::mem::swap(value, &mut self.value); 31 | } 32 | } 33 | 34 | impl Mutation, VecMutator> for Arbitrary 35 | where 36 | T: Clone + 'static, 37 | M: Mutator, 38 | { 39 | type RandomStep = ArbitraryStep; 40 | type Step = ArbitraryStep; 41 | type Concrete<'a> = ConcreteArbitrary; 42 | type Revert = RevertArbitrary; 43 | 44 | #[coverage(off)] 45 | fn default_random_step(&self, mutator: &VecMutator, _value: &Vec) -> Option { 46 | if mutator.m.max_complexity() == 0. { 47 | return None; 48 | } 49 | Some(ArbitraryStep) 50 | } 51 | 52 | #[coverage(off)] 53 | fn random<'a>( 54 | mutator: &VecMutator, 55 | _value: &Vec, 56 | _cache: & as Mutator>>::Cache, 57 | _random_step: &Self::RandomStep, 58 | max_cplx: f64, 59 | ) -> Self::Concrete<'a> { 60 | let (new_value, new_cplx) = mutator.random_arbitrary(max_cplx); 61 | ConcreteArbitrary { 62 | value: new_value, 63 | cplx: new_cplx, 64 | } 65 | } 66 | 67 | #[coverage(off)] 68 | fn default_step( 69 | &self, 70 | mutator: &VecMutator, 71 | value: &Vec, 72 | _cache: & as Mutator>>::Cache, 73 | ) -> Option { 74 | self.default_random_step(mutator, value) 75 | } 76 | 77 | #[coverage(off)] 78 | fn from_step<'a>( 79 | mutator: &VecMutator, 80 | value: &Vec, 81 | cache: & as Mutator>>::Cache, 82 | step: &'a mut Self::Step, 83 | _subvalue_provider: &dyn SubValueProvider, 84 | max_cplx: f64, 85 | ) -> Option> { 86 | Some(Self::random(mutator, value, cache, step, max_cplx)) 87 | } 88 | 89 | #[coverage(off)] 90 | fn apply<'a>( 91 | mut mutation: Self::Concrete<'a>, 92 | _mutator: &VecMutator, 93 | value: &mut Vec, 94 | _cache: &mut as Mutator>>::Cache, 95 | _subvalue_provider: &dyn SubValueProvider, 96 | _max_cplx: f64, 97 | ) -> (Self::Revert, f64) { 98 | std::mem::swap(value, &mut mutation.value); 99 | (RevertArbitrary { value: mutation.value }, mutation.cplx) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/copy_element.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::{Mutator, SubValueProvider}; 4 | 5 | pub struct CopyElement; 6 | 7 | #[derive(Clone)] 8 | pub struct CopyElementRandomStep; 9 | 10 | #[derive(Clone)] 11 | pub struct CopyElementStep { 12 | from_idx: usize, 13 | to_idx: usize, 14 | } 15 | pub struct ConcreteCopyElement { 16 | el: T, 17 | cplx: f64, 18 | idx: usize, 19 | } 20 | pub struct RevertCopyElement { 21 | idx: usize, 22 | } 23 | 24 | impl RevertMutation, VecMutator> for RevertCopyElement 25 | where 26 | T: Clone + 'static, 27 | M: Mutator, 28 | { 29 | #[coverage(off)] 30 | fn revert( 31 | self, 32 | _mutator: &VecMutator, 33 | value: &mut Vec, 34 | _cache: &mut as Mutator>>::Cache, 35 | ) { 36 | let _ = value.remove(self.idx); 37 | } 38 | } 39 | 40 | impl Mutation, VecMutator> for CopyElement 41 | where 42 | T: Clone + 'static, 43 | M: Mutator, 44 | { 45 | type RandomStep = CopyElementRandomStep; 46 | type Step = CopyElementStep; 47 | type Concrete<'a> = ConcreteCopyElement; 48 | type Revert = RevertCopyElement; 49 | 50 | #[coverage(off)] 51 | fn default_random_step(&self, mutator: &VecMutator, value: &Vec) -> Option { 52 | if mutator.m.max_complexity() == 0. { 53 | return None; 54 | } 55 | if value.is_empty() || value.len() >= *mutator.len_range.end() { 56 | None 57 | } else { 58 | Some(CopyElementRandomStep) 59 | } 60 | } 61 | 62 | #[coverage(off)] 63 | fn random<'a>( 64 | mutator: &VecMutator, 65 | value: &Vec, 66 | cache: & as Mutator>>::Cache, 67 | _random_step: &Self::RandomStep, 68 | _max_cplx: f64, 69 | ) -> Self::Concrete<'a> { 70 | let from_idx = mutator.rng.usize(..value.len()); 71 | let to_idx = mutator.rng.usize(..value.len()); 72 | 73 | let (el, el_cache) = (&value[from_idx], &cache.inner[from_idx]); 74 | let cplx = mutator.m.complexity(el, el_cache); 75 | 76 | ConcreteCopyElement { 77 | el: el.clone(), 78 | cplx, 79 | idx: to_idx, 80 | } 81 | } 82 | 83 | #[coverage(off)] 84 | fn default_step( 85 | &self, 86 | mutator: &VecMutator, 87 | value: &Vec, 88 | _cache: & as Mutator>>::Cache, 89 | ) -> Option { 90 | if mutator.m.max_complexity() == 0. { 91 | return None; 92 | } 93 | if value.is_empty() || value.len() >= *mutator.len_range.end() { 94 | None 95 | } else { 96 | Some(Self::Step { from_idx: 0, to_idx: 0 }) 97 | } 98 | } 99 | 100 | #[coverage(off)] 101 | fn from_step<'a>( 102 | mutator: &VecMutator, 103 | value: &Vec, 104 | cache: & as Mutator>>::Cache, 105 | step: &'a mut Self::Step, 106 | subvalue_provider: &dyn SubValueProvider, 107 | max_cplx: f64, 108 | ) -> Option> { 109 | // The step.from_idx increments from 0 to value.len() 110 | // once it reaches value.len(), we have tried every single possibility 111 | if step.from_idx == value.len() { 112 | return None; 113 | } 114 | // It doesn't make sense to copy an element to its same index 115 | // e.g. [0, 1, 2, 3] 116 | // copy 0 to 0 -> [0, 0, 1, 2, 3] 117 | // ok, but then: 118 | // copy 0 to 1 -> [0, 0, 1, 2, 3] 119 | // they're the same thing 120 | if step.from_idx == step.to_idx { 121 | step.to_idx += 1; 122 | } 123 | 124 | let value_cplx = mutator.complexity(value, cache); 125 | let spare_cplx = max_cplx - value_cplx; 126 | 127 | let (el, el_cache) = (&value[step.from_idx], &cache.inner[step.from_idx]); 128 | let cplx = mutator.m.complexity(el, el_cache); 129 | 130 | // cannot copy an element that would make the value exceed the maximum complexity 131 | // so we try another one 132 | if cplx > spare_cplx { 133 | step.from_idx += 1; 134 | step.to_idx = 0; 135 | Self::from_step(mutator, value, cache, step, subvalue_provider, max_cplx) 136 | } else { 137 | let concrete = ConcreteCopyElement { 138 | el: el.clone(), 139 | cplx, 140 | idx: step.to_idx, 141 | }; 142 | step.to_idx = (step.to_idx + 1) % (value.len() + 1); 143 | if step.to_idx == 0 { 144 | // then we have tried copying the element at from_idx to every other index 145 | // time to copy a different element 146 | step.from_idx += 1; 147 | } 148 | Some(concrete) 149 | } 150 | } 151 | 152 | #[coverage(off)] 153 | fn apply<'a>( 154 | mutation: Self::Concrete<'a>, 155 | mutator: &VecMutator, 156 | value: &mut Vec, 157 | cache: &mut as Mutator>>::Cache, 158 | _subvalue_provider: &dyn SubValueProvider, 159 | _max_cplx: f64, 160 | ) -> (Self::Revert, f64) { 161 | value.insert(mutation.idx, mutation.el); 162 | let new_cplx = mutator.complexity_from_inner(cache.sum_cplx + mutation.cplx, value.len()); 163 | (RevertCopyElement { idx: mutation.idx }, new_cplx) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/crossover_replace_element.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::mutators::CrossoverStep; 4 | use crate::{Mutator, SubValueProvider}; 5 | 6 | pub struct CrossoverReplaceElement; 7 | 8 | #[derive(Clone)] 9 | pub struct CrossoverReplaceElementStep { 10 | crossover_steps: Vec>, 11 | } 12 | pub enum ConcreteCrossoverReplaceElement { 13 | Random(usize), 14 | ReplaceElement { el: T, cplx: f64, idx: usize }, 15 | } 16 | pub enum RevertCrossoverReplaceElement { 17 | Random(UT, usize), 18 | ReplaceElement { el: T, idx: usize }, 19 | } 20 | 21 | impl RevertMutation, VecMutator> for RevertCrossoverReplaceElement 22 | where 23 | T: Clone + 'static, 24 | M: Mutator, 25 | { 26 | #[coverage(off)] 27 | fn revert( 28 | self, 29 | mutator: &VecMutator, 30 | value: &mut Vec, 31 | cache: &mut as Mutator>>::Cache, 32 | ) { 33 | match self { 34 | RevertCrossoverReplaceElement::Random(token, idx) => { 35 | mutator.m.unmutate(&mut value[idx], &mut cache.inner[idx], token); 36 | } 37 | RevertCrossoverReplaceElement::ReplaceElement { mut el, idx } => std::mem::swap(&mut value[idx], &mut el), 38 | } 39 | } 40 | } 41 | 42 | impl Mutation, VecMutator> for CrossoverReplaceElement 43 | where 44 | T: Clone + 'static, 45 | M: Mutator, 46 | { 47 | type RandomStep = !; 48 | type Step = CrossoverReplaceElementStep; 49 | type Concrete<'a> = ConcreteCrossoverReplaceElement; 50 | type Revert = RevertCrossoverReplaceElement; 51 | 52 | #[coverage(off)] 53 | fn default_random_step(&self, _mutator: &VecMutator, _value: &Vec) -> Option { 54 | None 55 | } 56 | 57 | #[coverage(off)] 58 | fn random<'a>( 59 | _mutator: &VecMutator, 60 | _value: &Vec, 61 | _cache: & as Mutator>>::Cache, 62 | _random_step: &Self::RandomStep, 63 | _max_cplx: f64, 64 | ) -> Self::Concrete<'a> { 65 | unreachable!() 66 | } 67 | 68 | #[coverage(off)] 69 | fn default_step( 70 | &self, 71 | mutator: &VecMutator, 72 | value: &Vec, 73 | _cache: & as Mutator>>::Cache, 74 | ) -> Option { 75 | if mutator.m.global_search_space_complexity() == 0. { 76 | return None; 77 | } 78 | if value.is_empty() { 79 | None 80 | } else { 81 | Some(CrossoverReplaceElementStep { 82 | crossover_steps: vec![CrossoverStep::default(); value.len()], 83 | }) 84 | } 85 | } 86 | 87 | #[coverage(off)] 88 | fn from_step<'a>( 89 | mutator: &VecMutator, 90 | value: &Vec, 91 | cache: & as Mutator>>::Cache, 92 | step: &'a mut Self::Step, 93 | subvalue_provider: &dyn SubValueProvider, 94 | max_cplx: f64, 95 | ) -> Option> { 96 | let value_cplx = mutator.complexity(value, cache); 97 | let spare_cplx = max_cplx - value_cplx; 98 | let choice = mutator.rng.usize(..value.len()); 99 | let step = &mut step.crossover_steps[choice]; 100 | if let Some((el, el_cplx)) = step.get_next_subvalue(subvalue_provider, spare_cplx) { 101 | if mutator.m.is_valid(el) { 102 | let el = el.clone(); 103 | return Some(ConcreteCrossoverReplaceElement::ReplaceElement { 104 | el, 105 | cplx: el_cplx, 106 | idx: choice, 107 | }); 108 | } 109 | } 110 | Some(ConcreteCrossoverReplaceElement::Random(choice)) 111 | } 112 | 113 | #[coverage(off)] 114 | fn apply<'a>( 115 | mutation: Self::Concrete<'a>, 116 | mutator: &VecMutator, 117 | value: &mut Vec, 118 | cache: &mut as Mutator>>::Cache, 119 | _subvalue_provider: &dyn SubValueProvider, 120 | max_cplx: f64, 121 | ) -> (Self::Revert, f64) { 122 | match mutation { 123 | ConcreteCrossoverReplaceElement::Random(idx) => { 124 | let old_cplx = mutator.complexity(value, cache); 125 | let old_el_cplx = mutator.m.complexity(&value[idx], &cache.inner[idx]); 126 | let spare_cplx = max_cplx - (old_cplx - old_el_cplx); 127 | let (token, new_el_cplx) = mutator 128 | .m 129 | .random_mutate(&mut value[idx], &mut cache.inner[idx], spare_cplx); 130 | ( 131 | RevertCrossoverReplaceElement::Random(token, idx), 132 | mutator.complexity_from_inner(cache.sum_cplx - old_el_cplx + new_el_cplx, value.len()), 133 | ) 134 | } 135 | ConcreteCrossoverReplaceElement::ReplaceElement { mut el, cplx, idx } => { 136 | let old_el_cplx = mutator.m.complexity(&value[idx], &cache.inner[idx]); 137 | std::mem::swap(&mut value[idx], &mut el); 138 | let r = RevertCrossoverReplaceElement::ReplaceElement { el, idx }; 139 | ( 140 | r, 141 | mutator.complexity_from_inner(cache.sum_cplx - old_el_cplx + cplx, value.len()), 142 | ) 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/insert_element.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::{Mutator, SubValueProvider}; 4 | 5 | pub struct InsertElement; 6 | 7 | #[derive(Clone)] 8 | pub struct InsertElementRandomStep; 9 | 10 | #[derive(Clone)] 11 | pub struct InsertElementStep { 12 | arbitrary_steps: Vec<(usize, A)>, 13 | } 14 | pub struct ConcreteInsertElement { 15 | el: T, 16 | cplx: f64, 17 | idx: usize, 18 | } 19 | pub struct RevertInsertElement { 20 | pub idx: usize, 21 | } 22 | 23 | impl RevertMutation, VecMutator> for RevertInsertElement 24 | where 25 | T: Clone + 'static, 26 | M: Mutator, 27 | { 28 | #[coverage(off)] 29 | fn revert( 30 | self, 31 | _mutator: &VecMutator, 32 | value: &mut Vec, 33 | _cache: &mut as Mutator>>::Cache, 34 | ) { 35 | let _ = value.remove(self.idx); 36 | } 37 | } 38 | 39 | impl Mutation, VecMutator> for InsertElement 40 | where 41 | T: Clone + 'static, 42 | M: Mutator, 43 | { 44 | type RandomStep = InsertElementRandomStep; 45 | type Step = InsertElementStep; 46 | type Concrete<'a> = ConcreteInsertElement; 47 | type Revert = RevertInsertElement; 48 | 49 | #[coverage(off)] 50 | fn default_random_step(&self, mutator: &VecMutator, value: &Vec) -> Option { 51 | if mutator.m.max_complexity() == 0. { 52 | return None; 53 | } 54 | if value.len() >= *mutator.len_range.end() { 55 | None 56 | } else { 57 | Some(InsertElementRandomStep) 58 | } 59 | } 60 | 61 | #[coverage(off)] 62 | fn random<'a>( 63 | mutator: &VecMutator, 64 | value: &Vec, 65 | cache: & as Mutator>>::Cache, 66 | _random_step: &Self::RandomStep, 67 | max_cplx: f64, 68 | ) -> Self::Concrete<'a> { 69 | let value_cplx = mutator.complexity(value, cache); 70 | let spare_cplx = max_cplx - value_cplx; 71 | 72 | let (el, cplx) = mutator.m.random_arbitrary(spare_cplx); 73 | ConcreteInsertElement { 74 | el, 75 | cplx, 76 | idx: mutator.rng.usize(..=value.len()), 77 | } 78 | } 79 | 80 | #[coverage(off)] 81 | fn default_step( 82 | &self, 83 | mutator: &VecMutator, 84 | value: &Vec, 85 | _cache: & as Mutator>>::Cache, 86 | ) -> Option { 87 | if mutator.m.max_complexity() == 0. { 88 | return None; 89 | } 90 | if value.len() >= *mutator.len_range.end() { 91 | None 92 | } else { 93 | Some(InsertElementStep { 94 | arbitrary_steps: (0..=value.len()) 95 | .map( 96 | #[coverage(off)] 97 | |i| (i, mutator.m.default_arbitrary_step()), 98 | ) 99 | .collect(), 100 | }) 101 | } 102 | } 103 | 104 | #[coverage(off)] 105 | fn from_step<'a>( 106 | mutator: &VecMutator, 107 | value: &Vec, 108 | cache: & as Mutator>>::Cache, 109 | step: &'a mut Self::Step, 110 | subvalue_provider: &dyn SubValueProvider, 111 | max_cplx: f64, 112 | ) -> Option> { 113 | if step.arbitrary_steps.is_empty() { 114 | return None; 115 | } 116 | let value_cplx = mutator.complexity(value, cache); 117 | let spare_cplx = max_cplx - value_cplx; 118 | let choice = mutator.rng.usize(..step.arbitrary_steps.len()); 119 | let (idx, arbitrary_step) = &mut step.arbitrary_steps[choice]; 120 | 121 | if let Some((el, cplx)) = mutator.m.ordered_arbitrary(arbitrary_step, spare_cplx) { 122 | Some(ConcreteInsertElement { el, cplx, idx: *idx }) 123 | } else { 124 | step.arbitrary_steps.remove(choice); 125 | Self::from_step(mutator, value, cache, step, subvalue_provider, max_cplx) 126 | } 127 | } 128 | 129 | #[coverage(off)] 130 | fn apply<'a>( 131 | mutation: Self::Concrete<'a>, 132 | mutator: &VecMutator, 133 | value: &mut Vec, 134 | cache: &mut as Mutator>>::Cache, 135 | _subvalue_provider: &dyn SubValueProvider, 136 | _max_cplx: f64, 137 | ) -> (Self::Revert, f64) { 138 | value.insert(mutation.idx, mutation.el); 139 | let new_cplx = mutator.complexity_from_inner(cache.sum_cplx + mutation.cplx, value.len()); 140 | (RevertInsertElement { idx: mutation.idx }, new_cplx) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/only_choose_length.rs: -------------------------------------------------------------------------------- 1 | //! This mutation is chosen when the element mutator is a “unit” mutator, 2 | //! meaning that it can only produce a single value. In this case, the 3 | //! vector mutator’s role is simply to choose a length. 4 | //! 5 | //! For example, if we have: 6 | //! ``` 7 | //! use fuzzcheck::{Mutator, DefaultMutator}; 8 | //! use fuzzcheck::mutators::vector::VecMutator; 9 | //! 10 | //! let m /* : impl Mutator> */ = VecMutator::new(<()>::default_mutator(), 2..=5); 11 | //! ``` 12 | //! Then the values that `m` can produce are only: 13 | //! ```txt 14 | //! [(), ()] 15 | //! [(), (), ()] 16 | //! [(), (), (), ()] 17 | //! [(), (), (), (), ()] 18 | //! ``` 19 | //! and nothing else. 20 | //! 21 | //! We can detect if the element mutator is a unit mutator by calling 22 | //! `m.global_search_space_complexity()`. If the complexity is `0.0`, then the 23 | //! mutator is only capable of producing a single value. 24 | 25 | use super::VecMutator; 26 | use crate::mutators::mutations::{Mutation, RevertMutation}; 27 | use crate::{Mutator, SubValueProvider}; 28 | 29 | pub struct OnlyChooseLength; 30 | 31 | #[derive(Clone)] 32 | pub struct OnlyChooseLengthStep { 33 | length: usize, 34 | } 35 | #[derive(Clone)] 36 | pub struct OnlyChooseLengthRandomStep; 37 | 38 | pub struct ConcreteOnlyChooseLength { 39 | length: usize, 40 | } 41 | pub struct RevertOnlyChooseLength { 42 | replace_by: Vec, 43 | } 44 | 45 | impl RevertMutation, VecMutator> for RevertOnlyChooseLength 46 | where 47 | T: Clone + 'static, 48 | M: Mutator, 49 | { 50 | #[coverage(off)] 51 | fn revert( 52 | mut self, 53 | _mutator: &VecMutator, 54 | value: &mut Vec, 55 | _cache: &mut as Mutator>>::Cache, 56 | ) { 57 | std::mem::swap(value, &mut self.replace_by); 58 | } 59 | } 60 | 61 | impl Mutation, VecMutator> for OnlyChooseLength 62 | where 63 | T: Clone + 'static, 64 | M: Mutator, 65 | { 66 | type RandomStep = OnlyChooseLengthRandomStep; 67 | type Step = OnlyChooseLengthStep; 68 | type Concrete<'a> = ConcreteOnlyChooseLength; 69 | type Revert = RevertOnlyChooseLength; 70 | #[coverage(off)] 71 | fn default_random_step(&self, mutator: &VecMutator, _value: &Vec) -> Option { 72 | if mutator.m.global_search_space_complexity() <= 0.0 { 73 | Some(OnlyChooseLengthRandomStep) 74 | } else { 75 | None 76 | } 77 | } 78 | #[coverage(off)] 79 | fn random<'a>( 80 | mutator: &VecMutator, 81 | _value: &Vec, 82 | _cache: & as Mutator>>::Cache, 83 | _random_step: &Self::RandomStep, 84 | max_cplx: f64, 85 | ) -> Self::Concrete<'a> { 86 | let cplx_element = mutator.m.min_complexity(); 87 | assert_eq!(cplx_element, mutator.m.max_complexity(), "A mutator of type {:?} has a global_search_space_complexity of 0.0 (indicating that it can produce only one value), but its min_complexity() is different than its max_complexity(), which is a contradiction.", std::any::type_name::()); 88 | 89 | let cplx_element = if mutator.inherent_complexity { 90 | // then each element adds an additional 1.0 of complexity 91 | 1.0 + cplx_element 92 | } else { 93 | cplx_element 94 | }; 95 | 96 | let upperbound = std::cmp::max( 97 | std::cmp::min(*mutator.len_range.end(), ((max_cplx - 1.0) / cplx_element) as usize), 98 | *mutator.len_range.start(), 99 | ); 100 | ConcreteOnlyChooseLength { 101 | length: mutator.rng.usize(*mutator.len_range.start()..=upperbound), 102 | } 103 | } 104 | #[coverage(off)] 105 | fn default_step( 106 | &self, 107 | mutator: &VecMutator, 108 | _value: &Vec, 109 | _cache: & as Mutator>>::Cache, 110 | ) -> Option { 111 | if mutator.m.global_search_space_complexity() <= 0.0 { 112 | Some(OnlyChooseLengthStep { 113 | length: *mutator.len_range.start(), 114 | }) 115 | } else { 116 | None 117 | } 118 | } 119 | #[coverage(off)] 120 | fn from_step<'a>( 121 | mutator: &VecMutator, 122 | _value: &Vec, 123 | _cache: & as Mutator>>::Cache, 124 | step: &'a mut Self::Step, 125 | _subvalue_provider: &dyn SubValueProvider, 126 | max_cplx: f64, 127 | ) -> Option> { 128 | let cplx_element = mutator.m.min_complexity(); 129 | if step.length <= *mutator.len_range.end() 130 | && mutator.complexity_from_inner(cplx_element * step.length as f64, step.length) < max_cplx 131 | { 132 | let x = ConcreteOnlyChooseLength { length: step.length }; 133 | step.length += 1; 134 | Some(x) 135 | } else { 136 | None 137 | } 138 | } 139 | #[coverage(off)] 140 | fn apply<'a>( 141 | mutation: Self::Concrete<'a>, 142 | mutator: &VecMutator, 143 | value: &mut Vec, 144 | _cache: &mut as Mutator>>::Cache, 145 | _subvalue_provider: &dyn SubValueProvider, 146 | _max_cplx: f64, 147 | ) -> (Self::Revert, f64) { 148 | let (el, el_cplx) = mutator.m.random_arbitrary(0.0); 149 | let mut value_2 = std::iter::repeat(el).take(mutation.length).collect(); 150 | std::mem::swap(value, &mut value_2); 151 | let cplx = mutator.complexity_from_inner(el_cplx * mutation.length as f64, mutation.length); 152 | (RevertOnlyChooseLength { replace_by: value_2 }, cplx) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/remove.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::{Mutator, SubValueProvider}; 4 | 5 | pub struct Remove; 6 | 7 | #[derive(Clone)] 8 | pub struct RemoveStep { 9 | pub idx: usize, 10 | } 11 | 12 | pub struct ConcreteRemove { 13 | pub idx: usize, 14 | } 15 | pub struct RevertRemove { 16 | pub idx: usize, 17 | pub element: T, 18 | } 19 | 20 | impl RevertMutation, VecMutator> for RevertRemove 21 | where 22 | T: Clone + 'static, 23 | M: Mutator, 24 | { 25 | #[coverage(off)] 26 | fn revert( 27 | self, 28 | _mutator: &VecMutator, 29 | value: &mut Vec, 30 | _cache: &mut as Mutator>>::Cache, 31 | ) { 32 | value.insert(self.idx, self.element); 33 | } 34 | } 35 | 36 | impl Mutation, VecMutator> for Remove 37 | where 38 | T: Clone + 'static, 39 | M: Mutator, 40 | { 41 | type RandomStep = RemoveStep; 42 | type Step = RemoveStep; 43 | type Concrete<'a> = ConcreteRemove; 44 | type Revert = RevertRemove; 45 | 46 | #[coverage(off)] 47 | fn default_random_step(&self, mutator: &VecMutator, value: &Vec) -> Option { 48 | if mutator.m.max_complexity() == 0. { 49 | return None; 50 | } 51 | if value.len() <= *mutator.len_range.start() { 52 | None 53 | } else { 54 | Some(RemoveStep { 55 | idx: mutator.rng.usize(..value.len()), 56 | }) 57 | } 58 | } 59 | #[coverage(off)] 60 | fn random<'a>( 61 | _mutator: &VecMutator, 62 | _value: &Vec, 63 | _cache: & as Mutator>>::Cache, 64 | random_step: &Self::RandomStep, 65 | _max_cplx: f64, 66 | ) -> Self::Concrete<'a> { 67 | ConcreteRemove { idx: random_step.idx } 68 | } 69 | #[coverage(off)] 70 | fn default_step( 71 | &self, 72 | mutator: &VecMutator, 73 | value: &Vec, 74 | _cache: & as Mutator>>::Cache, 75 | ) -> Option { 76 | if mutator.m.max_complexity() == 0. { 77 | return None; 78 | } 79 | if value.len() <= *mutator.len_range.start() { 80 | None 81 | } else { 82 | Some(RemoveStep { idx: 0 }) 83 | } 84 | } 85 | #[coverage(off)] 86 | fn from_step<'a>( 87 | _mutator: &VecMutator, 88 | value: &Vec, 89 | _cache: & as Mutator>>::Cache, 90 | step: &'a mut Self::Step, 91 | _subvalue_provider: &dyn SubValueProvider, 92 | _max_cplx: f64, 93 | ) -> Option> { 94 | if step.idx < value.len() { 95 | let x = ConcreteRemove { idx: step.idx }; 96 | step.idx += 1; 97 | Some(x) 98 | } else { 99 | None 100 | } 101 | } 102 | #[coverage(off)] 103 | fn apply<'a>( 104 | mutation: Self::Concrete<'a>, 105 | mutator: &VecMutator, 106 | value: &mut Vec, 107 | cache: &mut as Mutator>>::Cache, 108 | _subvalue_provider: &dyn SubValueProvider, 109 | _max_cplx: f64, 110 | ) -> (Self::Revert, f64) { 111 | let removed = value.remove(mutation.idx); 112 | let removed_cplx = mutator.m.complexity(&removed, &cache.inner[mutation.idx]); 113 | let new_cplx = mutator.complexity_from_inner(cache.sum_cplx - removed_cplx, value.len()); 114 | ( 115 | RevertRemove { 116 | idx: mutation.idx, 117 | element: removed, 118 | }, 119 | new_cplx, 120 | ) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/remove_and_insert_element.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::{Mutator, SubValueProvider}; 4 | 5 | pub struct RemoveAndInsertElement; 6 | 7 | #[derive(Clone)] 8 | pub struct RemoveAndInsertElementRandomStep; 9 | 10 | pub struct ConcreteRemoveAndInsertElement { 11 | remove_idx: usize, 12 | insert_idx: usize, 13 | inserted_el: T, 14 | new_cplx: f64, 15 | } 16 | pub struct RevertRemoveAndInsertElement { 17 | pub remove_at_idx: usize, 18 | pub insert_at_idx: usize, 19 | pub insert_el: T, 20 | } 21 | 22 | impl RevertMutation, VecMutator> for RevertRemoveAndInsertElement 23 | where 24 | T: Clone + 'static, 25 | M: Mutator, 26 | { 27 | #[coverage(off)] 28 | fn revert( 29 | self, 30 | _mutator: &VecMutator, 31 | value: &mut Vec, 32 | _cache: &mut as Mutator>>::Cache, 33 | ) { 34 | let _ = value.remove(self.remove_at_idx); 35 | value.insert(self.insert_at_idx, self.insert_el); 36 | } 37 | } 38 | 39 | impl Mutation, VecMutator> for RemoveAndInsertElement 40 | where 41 | T: Clone + 'static, 42 | M: Mutator, 43 | { 44 | type RandomStep = RemoveAndInsertElementRandomStep; 45 | type Step = RemoveAndInsertElementRandomStep; 46 | type Concrete<'a> = ConcreteRemoveAndInsertElement; 47 | type Revert = RevertRemoveAndInsertElement; 48 | #[coverage(off)] 49 | fn default_random_step(&self, mutator: &VecMutator, value: &Vec) -> Option { 50 | if mutator.m.max_complexity() == 0. { 51 | return None; 52 | } 53 | if value.len() <= 1 { 54 | // we'd remove an element and then insert another one at the same index 55 | // it's best to just mutate that element instead 56 | return None; 57 | } 58 | Some(RemoveAndInsertElementRandomStep) 59 | } 60 | #[coverage(off)] 61 | fn random<'a>( 62 | mutator: &VecMutator, 63 | value: &Vec, 64 | cache: & as Mutator>>::Cache, 65 | _random_step: &Self::RandomStep, 66 | max_cplx: f64, 67 | ) -> Self::Concrete<'a> { 68 | let old_cplx = mutator.complexity(value, cache); 69 | 70 | let remove_idx = mutator.rng.usize(0..value.len()); 71 | // let removed_el = value.remove(removal_idx); 72 | let removed_el_cplx = mutator.m.complexity(&value[remove_idx], &cache.inner[remove_idx]); 73 | 74 | let choice_insertion = mutator.rng.usize(..value.len() - 1); 75 | let insert_idx = if choice_insertion < remove_idx { 76 | choice_insertion 77 | } else { 78 | choice_insertion + 1 79 | }; 80 | 81 | let spare_cplx = max_cplx - old_cplx + removed_el_cplx; 82 | 83 | let (inserted_el, inserted_el_cplx) = mutator.m.random_arbitrary(spare_cplx); 84 | 85 | let new_cplx = old_cplx - removed_el_cplx + inserted_el_cplx; 86 | 87 | ConcreteRemoveAndInsertElement { 88 | remove_idx, 89 | insert_idx, 90 | inserted_el, 91 | new_cplx, 92 | } 93 | } 94 | #[coverage(off)] 95 | fn default_step( 96 | &self, 97 | mutator: &VecMutator, 98 | value: &Vec, 99 | _cache: & as Mutator>>::Cache, 100 | ) -> Option { 101 | self.default_random_step(mutator, value) 102 | } 103 | #[coverage(off)] 104 | fn from_step<'a>( 105 | mutator: &VecMutator, 106 | value: &Vec, 107 | cache: & as Mutator>>::Cache, 108 | step: &'a mut Self::Step, 109 | _subvalue_provider: &dyn SubValueProvider, 110 | max_cplx: f64, 111 | ) -> Option> { 112 | Some(Self::random(mutator, value, cache, step, max_cplx)) 113 | } 114 | #[coverage(off)] 115 | fn apply<'a>( 116 | mutation: Self::Concrete<'a>, 117 | _mutator: &VecMutator, 118 | value: &mut Vec, 119 | _cache: &mut as Mutator>>::Cache, 120 | _subvalue_provider: &dyn SubValueProvider, 121 | _max_cplx: f64, 122 | ) -> (Self::Revert, f64) { 123 | let removed_el = value.remove(mutation.remove_idx); 124 | value.insert(mutation.insert_idx, mutation.inserted_el); 125 | ( 126 | RevertRemoveAndInsertElement { 127 | remove_at_idx: mutation.insert_idx, 128 | insert_at_idx: mutation.remove_idx, 129 | insert_el: removed_el, 130 | }, 131 | mutation.new_cplx, 132 | ) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vector/swap_elements.rs: -------------------------------------------------------------------------------- 1 | use super::VecMutator; 2 | use crate::mutators::mutations::{Mutation, RevertMutation}; 3 | use crate::{Mutator, SubValueProvider}; 4 | 5 | pub struct SwapElements; 6 | 7 | // for now, everything random 8 | // but could decide to which two elements to swap 9 | #[derive(Clone)] 10 | pub struct SwapElementsStep { 11 | idx_1: usize, 12 | idx_2: usize, 13 | } 14 | pub struct ConcreteSwapElements { 15 | idx_1: usize, 16 | idx_2: usize, 17 | } 18 | pub struct RevertSwapElements { 19 | idx_1: usize, 20 | idx_2: usize, 21 | } 22 | 23 | impl RevertMutation, VecMutator> for RevertSwapElements 24 | where 25 | T: Clone + 'static, 26 | M: Mutator, 27 | { 28 | #[coverage(off)] 29 | fn revert( 30 | self, 31 | _mutator: &VecMutator, 32 | value: &mut Vec, 33 | _cache: &mut as Mutator>>::Cache, 34 | ) { 35 | value.swap(self.idx_1, self.idx_2); 36 | } 37 | } 38 | 39 | impl Mutation, VecMutator> for SwapElements 40 | where 41 | T: Clone + 'static, 42 | M: Mutator, 43 | { 44 | type RandomStep = SwapElementsStep; 45 | type Step = SwapElementsStep; 46 | type Concrete<'a> = ConcreteSwapElements; 47 | type Revert = RevertSwapElements; 48 | #[coverage(off)] 49 | fn default_random_step(&self, mutator: &VecMutator, value: &Vec) -> Option { 50 | if mutator.m.max_complexity() == 0. { 51 | return None; 52 | } 53 | if value.len() <= 1 { 54 | None 55 | } else { 56 | let idx_1 = mutator.rng.usize(..value.len()); 57 | let choice_other = mutator.rng.usize(..value.len() - 1); 58 | let idx_2 = if choice_other < idx_1 { 59 | choice_other 60 | } else { 61 | choice_other + 1 62 | }; 63 | Some(SwapElementsStep { idx_1, idx_2 }) 64 | } 65 | } 66 | #[coverage(off)] 67 | fn random<'a>( 68 | _mutator: &VecMutator, 69 | _value: &Vec, 70 | _cache: & as Mutator>>::Cache, 71 | random_step: &Self::RandomStep, 72 | _max_cplx: f64, 73 | ) -> Self::Concrete<'a> { 74 | ConcreteSwapElements { 75 | idx_1: random_step.idx_1, 76 | idx_2: random_step.idx_2, 77 | } 78 | } 79 | #[coverage(off)] 80 | fn default_step( 81 | &self, 82 | mutator: &VecMutator, 83 | value: &Vec, 84 | _cache: & as Mutator>>::Cache, 85 | ) -> Option { 86 | if mutator.m.max_complexity() == 0. { 87 | return None; 88 | } 89 | if value.len() <= 1 { 90 | None 91 | } else { 92 | Some(SwapElementsStep { idx_1: 0, idx_2: 1 }) 93 | } 94 | } 95 | #[coverage(off)] 96 | fn from_step<'a>( 97 | _mutator: &VecMutator, 98 | value: &Vec, 99 | _cache: & as Mutator>>::Cache, 100 | step: &'a mut Self::Step, 101 | _subvalue_provider: &dyn SubValueProvider, 102 | _max_cplx: f64, 103 | ) -> Option> { 104 | if step.idx_1 >= value.len() - 1 { 105 | None 106 | } else { 107 | let x = ConcreteSwapElements { 108 | idx_1: step.idx_1, 109 | idx_2: step.idx_2, 110 | }; 111 | step.idx_2 += 1; 112 | if step.idx_2 == value.len() { 113 | step.idx_1 += 1; 114 | step.idx_2 = step.idx_1 + 1; 115 | } 116 | Some(x) 117 | } 118 | } 119 | #[coverage(off)] 120 | fn apply<'a>( 121 | mutation: Self::Concrete<'a>, 122 | mutator: &VecMutator, 123 | value: &mut Vec, 124 | cache: &mut as Mutator>>::Cache, 125 | _subvalue_provider: &dyn SubValueProvider, 126 | _max_cplx: f64, 127 | ) -> (Self::Revert, f64) { 128 | let cplx = mutator.complexity(value, cache); 129 | value.swap(mutation.idx_1, mutation.idx_2); 130 | ( 131 | RevertSwapElements { 132 | idx_1: mutation.idx_1, 133 | idx_2: mutation.idx_2, 134 | }, 135 | cplx, 136 | ) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /fuzzcheck/src/mutators/vose_alias.rs: -------------------------------------------------------------------------------- 1 | //! An efficient data structure to sample from a discrete, fixed distribution. 2 | //! 3 | //! See: for an explanation. 4 | 5 | use std::fmt::Debug; 6 | 7 | use fastrand::Rng; 8 | 9 | /// An efficient data structure to sample from a discrete, fixed distribution. 10 | /// 11 | /// ``` 12 | /// use fuzzcheck::mutators::vose_alias::VoseAlias; 13 | /// 14 | /// // the discrete distribution is a vector of floats which must add up to 1.0 15 | /// let probabilities = vec![0.5, 0.1, 0.2, 0.2]; 16 | /// // create the Vose alias. The `probabilities` vector is moved to `alias.original_probabilities`. 17 | /// let alias = VoseAlias::new(probabilities); 18 | /// 19 | /// // index has a 50% chance of being 0, 10% chance of being 1, 20% chance of being 2, and 20% chance of being 2 20 | /// let index = alias.sample(); 21 | /// 22 | /// assert!((0 .. 4).contains(&index)); 23 | /// ``` 24 | #[derive(Debug, Clone)] 25 | pub struct VoseAlias { 26 | pub original_probabilities: Vec, 27 | alias: Vec, 28 | prob: Vec, 29 | rng: Rng, 30 | } 31 | impl PartialEq for VoseAlias { 32 | #[coverage(off)] 33 | fn eq(&self, other: &Self) -> bool { 34 | self.alias.eq(&other.alias) && self.prob.eq(&other.prob) 35 | } 36 | } 37 | 38 | // implementation from https://www.keithschwarz.com/darts-dice-coins/ 39 | impl VoseAlias { 40 | /// Create a new Vose alias with the given discrete probability distribution. 41 | /// 42 | /// Important: the probabilities must sum up to ~ 1.0 43 | #[coverage(off)] 44 | pub fn new(mut probabilities: Vec) -> VoseAlias { 45 | let original_probabilities = probabilities.clone(); 46 | // Step 0: ensure sum of probabilities is equal to 1 47 | assert!(!probabilities.is_empty()); 48 | let sum = probabilities.iter().fold( 49 | 0.0, 50 | #[coverage(off)] 51 | |sum, p| sum + p, 52 | ); 53 | #[allow(clippy::float_cmp)] 54 | if sum != 1.0 { 55 | for p in &mut probabilities { 56 | *p /= sum; 57 | } 58 | } 59 | let sum = probabilities.iter().fold( 60 | 0.0, 61 | #[coverage(off)] 62 | |sum, p| sum + p, 63 | ); 64 | assert!((sum - 1.0).abs() < 0.1); 65 | 66 | // Step 1 and 2 67 | let size = probabilities.len(); 68 | let mut small = Vec::with_capacity(size); 69 | let mut large = Vec::with_capacity(size); 70 | let mut alias: Vec = vec![0; size]; 71 | let mut prob: Vec = vec![0.0; size]; 72 | 73 | // Step 3 and 4 74 | for (i, p) in probabilities.iter_mut().enumerate() { 75 | *p *= size as f64; 76 | if *p < 1.0 { 77 | small.push(i); 78 | } else { 79 | large.push(i); 80 | } 81 | } 82 | // Step 5, 6, 7 83 | loop { 84 | match (small.pop(), large.pop()) { 85 | // Step 5 86 | (Some(l), Some(g)) => { 87 | let p_l = probabilities[l]; 88 | prob[l] = p_l; // 5.3 89 | alias[l] = g; // 5.4 90 | 91 | let p_g = probabilities[g]; 92 | let p_g = (p_g + p_l) - 1.0; 93 | probabilities[g] = p_g; // 5.5 94 | if p_g < 1.0 { 95 | small.push(g); // 5.6 96 | } else { 97 | large.push(g); // 5.7 98 | } 99 | } 100 | // Step 7 101 | (Some(l), None) => { 102 | prob[l] = 1.0; 103 | } 104 | // Step 6 105 | (None, Some(g)) => { 106 | prob[g] = 1.0; 107 | } 108 | (None, None) => break, 109 | } 110 | } 111 | 112 | VoseAlias { 113 | original_probabilities, 114 | alias, 115 | prob, 116 | rng: Rng::default(), 117 | } 118 | } 119 | 120 | /// Sample the Vose alias. 121 | /// 122 | /// It returns an index within `0` .. `original_probabilities.len()`. 123 | #[coverage(off)] 124 | pub fn sample(&self) -> usize { 125 | // Step 1 126 | let i = self.rng.usize(..self.prob.len()); 127 | // Step 2 128 | if self.rng.f64() <= unsafe { *self.prob.get_unchecked(i) } { 129 | // Step 3 130 | i 131 | } else { 132 | // Step 4 133 | unsafe { *self.alias.get_unchecked(i) } 134 | } 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | use super::VoseAlias; 141 | #[test] 142 | #[coverage(off)] 143 | fn test_probabilities_1() { 144 | let alias = VoseAlias::new(vec![0.1, 0.4, 0.2, 0.3]); 145 | let mut choices = vec![0, 0, 0, 0]; 146 | for _ in 0..100_000 { 147 | let i = alias.sample(); 148 | choices[i] += 1; 149 | } 150 | println!("{:?}", choices); 151 | } 152 | #[test] 153 | #[coverage(off)] 154 | fn test_probabilities_2() { 155 | let alias = VoseAlias::new(vec![0.1, 0.4, 0.2, 0.3]); 156 | let mut choices = vec![0, 0, 0, 0]; 157 | for _ in 0..100_000 { 158 | let i = alias.sample(); 159 | choices[i] += 1; 160 | } 161 | println!("{:?}", choices); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /fuzzcheck/src/sensors_and_pools/map_sensor.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{SaveToStatsFolder, Sensor}; 4 | 5 | /// The result of [`sensor.map(..)`](crate::SensorExt::map) 6 | pub struct MapSensor 7 | where 8 | S: Sensor, 9 | F: Fn(S::Observations) -> ToObservations, 10 | { 11 | sensor: S, 12 | map_f: F, 13 | _phantom: PhantomData, 14 | } 15 | 16 | impl MapSensor 17 | where 18 | S: Sensor, 19 | F: Fn(S::Observations) -> ToObservations, 20 | { 21 | #[coverage(off)] 22 | pub fn new(sensor: S, map_f: F) -> Self { 23 | Self { 24 | sensor, 25 | map_f, 26 | _phantom: PhantomData, 27 | } 28 | } 29 | } 30 | impl SaveToStatsFolder for MapSensor 31 | where 32 | S: Sensor, 33 | F: Fn(S::Observations) -> ToObservations, 34 | { 35 | #[coverage(off)] 36 | fn save_to_stats_folder(&self) -> Vec<(std::path::PathBuf, Vec)> { 37 | self.sensor.save_to_stats_folder() 38 | } 39 | } 40 | impl Sensor for MapSensor 41 | where 42 | S: Sensor, 43 | F: Fn(S::Observations) -> ToObservations, 44 | Self: 'static, 45 | { 46 | type Observations = ToObservations; 47 | 48 | #[coverage(off)] 49 | fn start_recording(&mut self) { 50 | self.sensor.start_recording(); 51 | } 52 | 53 | #[coverage(off)] 54 | fn stop_recording(&mut self) { 55 | self.sensor.stop_recording(); 56 | } 57 | 58 | #[coverage(off)] 59 | fn get_observations(&mut self) -> Self::Observations { 60 | let observations = self.sensor.get_observations(); 61 | (self.map_f)(observations) 62 | } 63 | } 64 | pub trait WrapperSensor: Sensor { 65 | type Wrapped: Sensor; 66 | fn wrapped(&self) -> &Self::Wrapped; 67 | } 68 | 69 | impl WrapperSensor for MapSensor 70 | where 71 | S: Sensor, 72 | F: Fn(S::Observations) -> ToObservations, 73 | Self: 'static, 74 | { 75 | type Wrapped = S; 76 | #[coverage(off)] 77 | fn wrapped(&self) -> &S { 78 | &self.sensor 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /fuzzcheck/src/sensors_and_pools/maximise_observation_pool.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display}; 2 | use std::path::PathBuf; 3 | 4 | use crate::traits::{CorpusDelta, Pool, SaveToStatsFolder, Stats}; 5 | use crate::{CSVField, CompatibleWithObservations, PoolStorageIndex, ToCSV}; 6 | 7 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 8 | struct Unit; 9 | 10 | struct Input { 11 | input_id: PoolStorageIndex, 12 | complexity: f64, 13 | } 14 | 15 | /// A pool that finds a single test case maximising a value given by a sensor. 16 | pub struct MaximiseObservationPool { 17 | name: String, 18 | current_best: Option<(T, Input)>, 19 | } 20 | #[derive(Clone)] 21 | pub struct MaximiseObservationPoolStats { 22 | name: String, 23 | best: T, 24 | } 25 | impl Display for MaximiseObservationPoolStats 26 | where 27 | T: Debug, 28 | { 29 | #[coverage(off)] 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | write!(f, "{}({:?})", self.name, self.best) 32 | } 33 | } 34 | impl ToCSV for MaximiseObservationPoolStats 35 | where 36 | T: Debug, 37 | { 38 | #[coverage(off)] 39 | fn csv_headers(&self) -> Vec { 40 | vec![CSVField::String(self.name.clone())] 41 | } 42 | #[coverage(off)] 43 | fn to_csv_record(&self) -> Vec { 44 | vec![CSVField::String(format!("{:?}", self.best))] 45 | } 46 | } 47 | impl Stats for MaximiseObservationPoolStats where T: Debug + Default + 'static {} 48 | 49 | impl MaximiseObservationPool { 50 | #[coverage(off)] 51 | pub fn new(name: &str) -> Self { 52 | Self { 53 | name: name.to_string(), 54 | current_best: None, 55 | } 56 | } 57 | } 58 | impl Pool for MaximiseObservationPool 59 | where 60 | T: Clone + Debug + Default + 'static, 61 | { 62 | type Stats = MaximiseObservationPoolStats; 63 | 64 | #[coverage(off)] 65 | fn stats(&self) -> Self::Stats { 66 | MaximiseObservationPoolStats { 67 | name: self.name.clone(), 68 | best: self 69 | .current_best 70 | .as_ref() 71 | .map( 72 | #[coverage(off)] 73 | |z| z.0.clone(), 74 | ) 75 | .unwrap_or_default(), 76 | } 77 | } 78 | #[coverage(off)] 79 | fn get_random_index(&mut self) -> Option { 80 | if let Some(best) = &self.current_best { 81 | Some(best.1.input_id) 82 | } else { 83 | None 84 | } 85 | } 86 | } 87 | impl SaveToStatsFolder for MaximiseObservationPool { 88 | #[coverage(off)] 89 | fn save_to_stats_folder(&self) -> Vec<(PathBuf, Vec)> { 90 | vec![] 91 | } 92 | } 93 | 94 | impl CompatibleWithObservations for MaximiseObservationPool 95 | where 96 | T: Clone + Debug + Default + PartialOrd + 'static, 97 | { 98 | #[coverage(off)] 99 | fn process(&mut self, input_id: PoolStorageIndex, observations: &T, complexity: f64) -> Vec { 100 | let observations = observations.clone(); 101 | let is_interesting = if let Some((counter, cur_input)) = &self.current_best { 102 | observations > *counter || (observations == *counter && cur_input.complexity > complexity) 103 | } else { 104 | true 105 | }; 106 | if !is_interesting { 107 | return vec![]; 108 | } 109 | let delta = CorpusDelta { 110 | path: PathBuf::new().join(&self.name), 111 | add: true, 112 | remove: if let Some(best) = &self.current_best { 113 | vec![best.1.input_id] 114 | } else { 115 | vec![] 116 | }, 117 | }; 118 | let new = Input { input_id, complexity }; 119 | self.current_best = Some((observations, new)); 120 | vec![delta] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /fuzzcheck/src/sensors_and_pools/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Types implementing the [Sensor](crate::Sensor) and [Pool](crate::Pool) traits. 3 | */ 4 | 5 | mod allocations_sensor; 6 | mod and_sensor_and_pool; 7 | mod map_sensor; 8 | mod maximise_each_counter_pool; 9 | mod maximise_observation_pool; 10 | mod most_n_diverse_pool; 11 | mod noop_sensor; 12 | mod simplest_to_activate_counter_pool; 13 | mod static_value_sensor; 14 | mod test_failure_pool; 15 | mod unique_values_pool; 16 | mod unit_pool; 17 | 18 | #[doc(inline)] 19 | pub use allocations_sensor::{AllocationSensor, CountingAllocator}; 20 | #[doc(inline)] 21 | pub use and_sensor_and_pool::{AndPool, AndSensor, AndSensorAndPool, DifferentObservations, SameObservations}; 22 | #[doc(inline)] 23 | pub use map_sensor::MapSensor; 24 | #[doc(inline)] 25 | pub use map_sensor::WrapperSensor; 26 | #[doc(inline)] 27 | pub use maximise_each_counter_pool::MaximiseEachCounterPool; 28 | #[doc(inline)] 29 | pub use maximise_observation_pool::MaximiseObservationPool; 30 | #[doc(inline)] 31 | pub use most_n_diverse_pool::MostNDiversePool; 32 | #[doc(inline)] 33 | pub use noop_sensor::NoopSensor; 34 | #[doc(inline)] 35 | pub use simplest_to_activate_counter_pool::SimplestToActivateCounterPool; 36 | #[doc(inline)] 37 | pub use static_value_sensor::StaticValueSensor; 38 | #[doc(inline)] 39 | pub use test_failure_pool::TestFailure; 40 | #[doc(inline)] 41 | pub use test_failure_pool::TestFailurePool; 42 | #[doc(inline)] 43 | pub use test_failure_pool::TestFailureSensor; 44 | pub(crate) use test_failure_pool::TEST_FAILURE; 45 | #[doc(inline)] 46 | pub use unique_values_pool::UniqueValuesPool; 47 | #[doc(inline)] 48 | pub use unit_pool::UnitPool; 49 | 50 | #[doc(inline)] 51 | pub use crate::code_coverage_sensor::CodeCoverageSensor; 52 | use crate::{Pool, Sensor}; 53 | 54 | /// A trait for convenience methods automatically implemented for all types that conform to Pool. 55 | pub trait PoolExt: Pool + Sized { 56 | /// Create an [`AndPool`](crate::sensors_and_pools::AndPool) from both `Self` and `P`. 57 | /// 58 | /// ## Arguments 59 | /// - `p` is the other pool to combine with `self` 60 | /// - `override_weight` determines the relative chance of selecting `p` when the resulting [`AndPool`](crate::sensors_and_pools::AndPool) 61 | /// is asked to provide a test case. If `None`, [`p.weight()`](crate::Pool::weight) will be used. The weight of `self` is always `self.weight()`. 62 | /// - `_sensor_marker` tells whether `self` and `p` operate on the same observations or not. If they do, pass [`SameObservations`](crate::sensors_and_pools::SameObservations). 63 | /// Otherwise, pass [`DifferentObservations`](`crate::sensors_and_pools::DifferentObservations`). See the documentation of [`AndPool`](crate::sensors_and_pools::AndPool) for more details. 64 | fn and(self, p: P, override_weight: Option, _sensor_marker: SM) -> AndPool 65 | where 66 | P: Pool, 67 | { 68 | let self_weight = self.weight(); 69 | let p_weight = p.weight(); 70 | AndPool::<_, _, SM>::new(self, p, self_weight, override_weight.unwrap_or(p_weight)) 71 | } 72 | } 73 | 74 | impl

PoolExt for P where P: Pool {} 75 | 76 | /// A trait for convenience methods automatically implemented 77 | /// for all types that conform to [`Sensor`]. 78 | pub trait SensorExt: Sensor { 79 | /// Maps the observations of the sensor using the given closure. 80 | /// 81 | /// For example, if a sensor has observations of type `Vec`, then we 82 | /// can create a sensor with observations `(Vec, u64)`, where the 83 | /// second element of the tuple is the sum of all observations: 84 | /// ``` 85 | /// use fuzzcheck::SensorExt; 86 | /// # use fuzzcheck::sensors_and_pools::StaticValueSensor; 87 | /// # static mut COUNTERS: [u64; 2] = [0; 2]; 88 | /// # // inside the fuzz test, you can create the sensor as follows 89 | /// # let sensor = unsafe { StaticValueSensor::new(&mut COUNTERS, [0, 0]) }; 90 | /// let sensor = sensor.map(|observations| { 91 | /// let sum = observations.iter().sum::(); 92 | /// (observations, sum) 93 | /// }); 94 | /// ``` 95 | #[coverage(off)] 96 | fn map(self, map_f: F) -> MapSensor 97 | where 98 | Self: Sized, 99 | F: Fn(Self::Observations) -> ToObservations, 100 | { 101 | MapSensor::new(self, map_f) 102 | } 103 | } 104 | impl SensorExt for T where T: Sensor {} 105 | 106 | /// Each pool has an associated `Stats` type. They're not very interesting, but I don't want to completely hide them, so I have gathered them here. 107 | pub mod stats { 108 | use std::fmt::Display; 109 | 110 | #[doc(inline)] 111 | pub use super::and_sensor_and_pool::AndPoolStats; 112 | #[doc(inline)] 113 | pub use super::maximise_each_counter_pool::MaximiseEachCounterPoolStats; 114 | #[doc(inline)] 115 | pub use super::most_n_diverse_pool::MostNDiversePoolStats; 116 | #[doc(inline)] 117 | pub use super::simplest_to_activate_counter_pool::UniqueCoveragePoolStats; 118 | #[doc(inline)] 119 | pub use super::test_failure_pool::TestFailurePoolStats; 120 | #[doc(inline)] 121 | pub use super::unique_values_pool::UniqueValuesPoolStats; 122 | use crate::traits::Stats; 123 | use crate::{CSVField, ToCSV}; 124 | 125 | /// An empty type that can be used for [`Pool::Stats`](crate::Pool::Stats) 126 | #[derive(Clone, Copy)] 127 | pub struct EmptyStats; 128 | 129 | impl Display for EmptyStats { 130 | #[coverage(off)] 131 | fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 132 | Ok(()) 133 | } 134 | } 135 | impl ToCSV for EmptyStats { 136 | #[coverage(off)] 137 | fn csv_headers(&self) -> Vec { 138 | vec![] 139 | } 140 | #[coverage(off)] 141 | fn to_csv_record(&self) -> Vec { 142 | vec![] 143 | } 144 | } 145 | impl Stats for EmptyStats {} 146 | } 147 | -------------------------------------------------------------------------------- /fuzzcheck/src/sensors_and_pools/noop_sensor.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::traits::{SaveToStatsFolder, Sensor}; 4 | 5 | /// A sensor that does nothing. 6 | /// 7 | /// In practice, it is used in conjunction with [`UnitPool`](crate::sensors_and_pools::UnitPool) to 8 | /// favour one particular test case throughout the whole fuzzing run. This is partly how the `minify` 9 | /// command is implemented. 10 | pub struct NoopSensor; 11 | 12 | impl Sensor for NoopSensor { 13 | type Observations = (); 14 | #[coverage(off)] 15 | fn start_recording(&mut self) {} 16 | #[coverage(off)] 17 | fn stop_recording(&mut self) {} 18 | 19 | #[coverage(off)] 20 | fn get_observations(&mut self) {} 21 | } 22 | impl SaveToStatsFolder for NoopSensor { 23 | #[coverage(off)] 24 | fn save_to_stats_folder(&self) -> Vec<(PathBuf, Vec)> { 25 | vec![] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fuzzcheck/src/sensors_and_pools/static_value_sensor.rs: -------------------------------------------------------------------------------- 1 | use crate::{SaveToStatsFolder, Sensor}; 2 | 3 | /// A custom sensor whose observations are given by a mutable static value. 4 | /// 5 | /// In the example below, we use a `StaticValueSensor` to maximise 6 | /// the value of a variable used in the test function. 7 | /// ``` 8 | /// use fuzzcheck::sensors_and_pools::{StaticValueSensor, MaximiseObservationPool}; 9 | /// use fuzzcheck::{Arguments, ReasonForStopping}; 10 | /// 11 | /// // the “COUNT” variable must be a static item 12 | /// static mut COUNT: usize = 0; 13 | /// 14 | /// fn test_function(xs: &[u8]) -> bool { 15 | /// if xs.len() == 6 { 16 | /// let mut number_correct_guesses = 0; 17 | /// if xs[0] == 98 { number_correct_guesses += 1 } 18 | /// if xs[1] == 18 { number_correct_guesses += 1 } 19 | /// if xs[2] == 9 { number_correct_guesses += 1 } 20 | /// if xs[3] == 203 { number_correct_guesses += 1 } 21 | /// if xs[4] == 45 { number_correct_guesses += 1 } 22 | /// if xs[5] == 165 { number_correct_guesses += 1 } 23 | /// 24 | /// // here, record the value of number_correct_guesses in COUNT 25 | /// unsafe { COUNT = number_correct_guesses; } 26 | /// 27 | /// number_correct_guesses != 6 28 | /// } else { 29 | /// true 30 | /// } 31 | /// } 32 | /// // You can create the sensor as follows. 33 | /// // It is unsafe because of the access to the global mutable variable. 34 | /// // After each run of the test function, the sensor resets `COUNT` 35 | /// // to the second argument (here: 0). It is best if you don't access 36 | /// // `COUNT` outside of the test function. 37 | /// let sensor = unsafe { StaticValueSensor::new(&mut COUNT, 0) }; 38 | /// 39 | /// // The sensor can be paired with any pool which is compatible with 40 | /// // observations of type `usize`. For example, we can use: 41 | /// let pool = MaximiseObservationPool::::new("maximise_count"); 42 | /// 43 | /// // then launch fuzzcheck with this sensor and pool 44 | /// let result = fuzzcheck::fuzz_test(test_function) 45 | /// .default_mutator() 46 | /// .serde_serializer() 47 | /// .sensor_and_pool(sensor, pool) 48 | /// .arguments(Arguments::for_internal_documentation_test()) 49 | /// .stop_after_first_test_failure(true) 50 | /// .launch(); 51 | /// 52 | /// assert!(matches!( 53 | /// result.reason_for_stopping, 54 | /// ReasonForStopping::TestFailure(x) 55 | /// if matches!( 56 | /// x.as_slice(), 57 | /// [98, 18, 9, 203, 45, 165] 58 | /// ) 59 | /// )); 60 | /// ``` 61 | pub struct StaticValueSensor 62 | where 63 | T: 'static + Clone, 64 | { 65 | value: *mut T, 66 | default_value: T, 67 | } 68 | impl StaticValueSensor 69 | where 70 | T: 'static + Clone, 71 | { 72 | pub fn new(value: &'static mut T, default_value: T) -> Self { 73 | Self { value, default_value } 74 | } 75 | } 76 | impl SaveToStatsFolder for StaticValueSensor 77 | where 78 | T: 'static + Clone, 79 | { 80 | fn save_to_stats_folder(&self) -> Vec<(std::path::PathBuf, Vec)> { 81 | vec![] 82 | } 83 | } 84 | impl Sensor for StaticValueSensor 85 | where 86 | T: Clone, 87 | { 88 | type Observations = T; 89 | 90 | fn start_recording(&mut self) { 91 | unsafe { *self.value = self.default_value.clone() }; 92 | } 93 | 94 | fn stop_recording(&mut self) { 95 | // unsafe { 96 | // self.recorded_value = (*self.value).clone(); 97 | // } 98 | } 99 | 100 | fn get_observations(&mut self) -> Self::Observations { 101 | unsafe { (*self.value).clone() } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /fuzzcheck/src/sensors_and_pools/unit_pool.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::sensors_and_pools::stats::EmptyStats; 4 | use crate::traits::{CorpusDelta, Pool, SaveToStatsFolder}; 5 | use crate::{CompatibleWithObservations, PoolStorageIndex}; 6 | 7 | /// A pool that stores only one given test case. 8 | /// 9 | /// Currently, it can only be used by fuzzcheck itself 10 | /// because it requires a `PoolStorageIndex`, which only 11 | /// fuzzcheck can create. This will change at some point. 12 | pub struct UnitPool { 13 | input_index: PoolStorageIndex, 14 | } 15 | impl UnitPool { 16 | #[coverage(off)] 17 | pub(crate) fn new(input_index: PoolStorageIndex) -> Self { 18 | Self { input_index } 19 | } 20 | } 21 | 22 | impl Pool for UnitPool { 23 | type Stats = EmptyStats; 24 | #[coverage(off)] 25 | fn stats(&self) -> Self::Stats { 26 | EmptyStats 27 | } 28 | 29 | #[coverage(off)] 30 | fn get_random_index(&mut self) -> Option { 31 | Some(self.input_index) 32 | } 33 | } 34 | impl SaveToStatsFolder for UnitPool { 35 | #[coverage(off)] 36 | fn save_to_stats_folder(&self) -> Vec<(PathBuf, Vec)> { 37 | vec![] 38 | } 39 | } 40 | 41 | impl CompatibleWithObservations for UnitPool { 42 | #[coverage(off)] 43 | fn process<'a>(&'a mut self, _input_id: PoolStorageIndex, _observations: &O, _complexity: f64) -> Vec { 44 | vec![] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fuzzcheck/src/serializers/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types implementing the [Serializer] trait. 2 | //! 3 | //! There are currently three implementations: 4 | //! 5 | //! * SerdeSerializer uses the `serde` and `serde_json` crate to serialize 6 | //! the test inputs (of arbitrary Serializable type) to a `.json` file. 7 | //! 8 | //! * [ByteSerializer] encodes and decodes values of type `Vec` by simply 9 | //! copy/pasting the bytes from/to the files. The extension is customizable. 10 | //! 11 | //! * [StringSerializer] encodes and decodes values of any type implementing 12 | //! `FromStr` and `ToString` into utf-8 encoded text files. 13 | 14 | #[cfg(feature = "serde_ron_serializer")] 15 | mod serde_ron_serializer; 16 | #[cfg(feature = "serde_json_serializer")] 17 | mod serde_serializer; 18 | 19 | use std::marker::PhantomData; 20 | use std::str::FromStr; 21 | 22 | #[cfg(feature = "serde_ron_serializer")] 23 | pub use serde_ron_serializer::SerdeRonSerializer; 24 | #[cfg(feature = "serde_json_serializer")] 25 | pub use serde_serializer::SerdeSerializer; 26 | 27 | use crate::Serializer; 28 | 29 | /** 30 | A Serializer for `Vec` that simply copies the bytes from/to the files. 31 | */ 32 | pub struct ByteSerializer { 33 | ext: &'static str, 34 | } 35 | 36 | impl ByteSerializer { 37 | /// Create a byte serializer. The only argument is the name of the extension 38 | /// that the created files should have. For example: 39 | /// ``` 40 | /// use fuzzcheck::ByteSerializer; 41 | /// 42 | /// let ser = ByteSerializer::new("png"); 43 | /// ```` 44 | #[coverage(off)] 45 | pub fn new(ext: &'static str) -> Self { 46 | Self { ext } 47 | } 48 | } 49 | 50 | impl crate::traits::Serializer for ByteSerializer { 51 | type Value = Vec; 52 | 53 | #[coverage(off)] 54 | fn extension(&self) -> &str { 55 | self.ext 56 | } 57 | #[coverage(off)] 58 | fn from_data(&self, data: &[u8]) -> Option { 59 | Some(data.into()) 60 | } 61 | #[coverage(off)] 62 | fn to_data(&self, value: &Self::Value) -> Vec { 63 | value.clone() 64 | } 65 | } 66 | 67 | /** 68 | A serializer that encodes and decodes values of any type implementing 69 | `FromStr` and `ToString` into utf-8 encoded text files. 70 | */ 71 | pub struct StringSerializer 72 | where 73 | StringType: ToString + FromStr, 74 | { 75 | pub extension: &'static str, 76 | _phantom: PhantomData, 77 | } 78 | impl StringSerializer 79 | where 80 | StringType: ToString + FromStr, 81 | { 82 | /// Create a string serializer. The only argument is the name of the extension 83 | /// that the created files should have. For example: 84 | /// ``` 85 | /// use fuzzcheck::StringSerializer; 86 | /// 87 | /// let ser = StringSerializer::::new("txt"); 88 | /// ```` 89 | #[coverage(off)] 90 | pub fn new(extension: &'static str) -> Self { 91 | Self { 92 | extension, 93 | _phantom: PhantomData, 94 | } 95 | } 96 | } 97 | impl Serializer for StringSerializer 98 | where 99 | StringType: ToString + FromStr, 100 | { 101 | type Value = StringType; 102 | 103 | #[coverage(off)] 104 | fn extension(&self) -> &str { 105 | self.extension 106 | } 107 | #[coverage(off)] 108 | fn from_data(&self, data: &[u8]) -> Option { 109 | let string = String::from_utf8(data.to_vec()).ok()?; 110 | let value = Self::Value::from_str(&string).ok()?; 111 | Some(value) 112 | } 113 | #[coverage(off)] 114 | fn to_data(&self, value: &Self::Value) -> Vec { 115 | value.to_string().into_bytes() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /fuzzcheck/src/serializers/serde_ron_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | /// A serializer that uses [`serde`] and [`ron`] to serialize the test 4 | /// inputs (of arbitrary type `T: Serializable + for<'e> Deserializable<'e>`) 5 | /// to a "rusty object notation" file. 6 | #[doc(cfg(feature = "serde_ron_serializer"))] 7 | pub struct SerdeRonSerializer { 8 | phantom: PhantomData, 9 | } 10 | 11 | impl Default for SerdeRonSerializer { 12 | #[coverage(off)] 13 | fn default() -> Self { 14 | Self { phantom: PhantomData } 15 | } 16 | } 17 | 18 | impl crate::traits::Serializer for SerdeRonSerializer 19 | where 20 | S: serde::Serialize + for<'e> serde::Deserialize<'e>, 21 | { 22 | type Value = S; 23 | 24 | #[coverage(off)] 25 | fn extension(&self) -> &str { 26 | "ron" 27 | } 28 | #[coverage(off)] 29 | fn from_data(&self, data: &[u8]) -> Option { 30 | let utf8_encoded = std::str::from_utf8(data).ok()?; 31 | ron::from_str(utf8_encoded).ok() 32 | } 33 | #[coverage(off)] 34 | fn to_data(&self, value: &Self::Value) -> Vec { 35 | ron::to_string(value).unwrap().into_bytes() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fuzzcheck/src/serializers/serde_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | /// A serializer that uses `serde` and `serde_json` to serialize the test 4 | /// inputs (of arbitrary type `T: Serializable + for<'e> Deserializable<'e>`) 5 | /// to a json file. 6 | #[doc(cfg(feature = "serde_json_serializer"))] 7 | pub struct SerdeSerializer { 8 | phantom: PhantomData, 9 | } 10 | 11 | impl Default for SerdeSerializer { 12 | #[coverage(off)] 13 | fn default() -> Self { 14 | Self { phantom: PhantomData } 15 | } 16 | } 17 | 18 | impl crate::traits::Serializer for SerdeSerializer 19 | where 20 | S: serde::Serialize + for<'e> serde::Deserialize<'e>, 21 | { 22 | type Value = S; 23 | 24 | #[coverage(off)] 25 | fn extension(&self) -> &str { 26 | "json" 27 | } 28 | #[coverage(off)] 29 | fn from_data(&self, data: &[u8]) -> Option { 30 | serde_json::from_slice(data).ok() 31 | } 32 | #[coverage(off)] 33 | fn to_data(&self, value: &Self::Value) -> Vec { 34 | serde_json::to_vec(value).unwrap() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /fuzzcheck/src/signals_handler.rs: -------------------------------------------------------------------------------- 1 | // ! A small, naive implementation of signal handlers in order to detect and 2 | // ! recover from crashes. 3 | 4 | use std::ptr; 5 | 6 | use libc::{ 7 | sigaction, sigemptyset, SA_NODEFER, SA_ONSTACK, SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGINT, SIGSEGV, 8 | SIGTERM, SIGTRAP, SIG_DFL, 9 | }; 10 | 11 | static mut SIGNAL_HANDLER: Option !>> = None; 12 | 13 | #[coverage(off)] 14 | extern "C" fn os_handler(signal: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) { 15 | // Assuming this always succeeds. Can't really handle errors in any meaningful way. 16 | unsafe { 17 | reset_signal_handlers(); 18 | if let Some(h) = SIGNAL_HANDLER.as_mut() { 19 | (*h)(signal); 20 | } else { 21 | std::process::exit(1); 22 | } 23 | } 24 | } 25 | 26 | /// Set signal handlers to the given function and return the pointer and layout 27 | /// of the alternative stack used by the signal handlers. 28 | #[coverage(off)] 29 | pub unsafe fn set_signal_handlers(f: F) -> (*mut u8, std::alloc::Layout) 30 | where 31 | F: Fn(libc::c_int) -> !, 32 | { 33 | SIGNAL_HANDLER = Some(Box::new(f)); 34 | 35 | // Make sure the alternative stack is big enough. ~65_000 bytes should be okay. 36 | let stack_size = std::cmp::max(libc::SIGSTKSZ, 0b1 << 16); 37 | let stack_layout = std::alloc::Layout::array::(stack_size).unwrap(); 38 | let stack_pointer = std::alloc::alloc_zeroed(stack_layout); 39 | 40 | let signal_stack = libc::stack_t { 41 | ss_sp: stack_pointer as *mut std::ffi::c_void, 42 | ss_size: stack_size, 43 | ss_flags: 0, 44 | }; 45 | 46 | let stack = libc::sigaltstack(&signal_stack, std::ptr::null_mut()); 47 | if stack < 0 { 48 | panic!("could not set alternate stack for handling signals"); 49 | } 50 | 51 | let mut sa: sigaction = std::mem::zeroed(); 52 | sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); 53 | 54 | sa.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK; 55 | sa.sa_sigaction = os_handler as usize; 56 | 57 | let signals = [ 58 | SIGALRM, SIGINT, SIGTERM, SIGSEGV, SIGBUS, SIGABRT, SIGFPE, SIGABRT, SIGTRAP, 59 | ]; 60 | for sig in signals { 61 | if sigaction(sig as i32, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { 62 | panic!("Could not set up signal handler"); 63 | } 64 | } 65 | 66 | (stack_pointer, stack_layout) 67 | } 68 | 69 | #[coverage(off)] 70 | pub(crate) unsafe fn reset_signal_handlers() { 71 | let mut sa: sigaction = std::mem::zeroed(); 72 | sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); 73 | sa.sa_sigaction = SIG_DFL; 74 | 75 | for &signal in &[ 76 | SIGALRM, SIGINT, SIGTERM, SIGSEGV, SIGBUS, SIGABRT, SIGFPE, SIGABRT, SIGTRAP, 77 | ] { 78 | if sigaction(signal, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { 79 | panic!("Could not set up signal handler"); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /fuzzcheck/tests/alternation_char_mutators.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | 3 | use fuzzcheck::mutators::alternation::AlternationMutator; 4 | use fuzzcheck::mutators::char::CharWithinRangeMutator; 5 | use fuzzcheck::mutators::testing_utilities::test_mutator; 6 | 7 | fn test_alternation_char_helper(ranges: impl IntoIterator> + Clone) { 8 | let m = AlternationMutator::new( 9 | ranges.clone().into_iter().map(CharWithinRangeMutator::new).collect(), 10 | 0.0, 11 | ); 12 | test_mutator(m, 100.0, 100.0, false, true, 100, 1000); 13 | let m = AlternationMutator::new( 14 | ranges.clone().into_iter().map(CharWithinRangeMutator::new).collect(), 15 | 0.0, 16 | ); 17 | test_mutator(m, 1.0, 100.0, false, true, 100, 1000); 18 | let m = AlternationMutator::new(ranges.into_iter().map(CharWithinRangeMutator::new).collect(), 0.0); 19 | test_mutator(m, 1.0, 1.0, false, true, 100, 1000); 20 | } 21 | 22 | #[test] 23 | fn test_alternation_char() { 24 | test_alternation_char_helper(['a'..='z', '0'..='0']); 25 | test_alternation_char_helper(['a'..='z']); 26 | test_alternation_char_helper(['a'..='z', '0'..='9']); 27 | // this will fail because the submutators give different complexities byt the letter 'a' is 28 | // a possibility for all three first choices. 29 | // test_alternation_char_helper(['a'..='z', 'a'..='b', 'a'..='c', '0'..='9', '0'..='5']); 30 | } 31 | -------------------------------------------------------------------------------- /fuzzcheck/tests/char_mutators.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::mutators::char::CharWithinRangeMutator; 2 | use fuzzcheck::mutators::testing_utilities::*; 3 | 4 | #[test] 5 | fn other_test_char_mutator() { 6 | test_mutator( 7 | CharWithinRangeMutator::new('a'..='z'), 8 | 100.0, 9 | 100.0, 10 | true, 11 | true, 12 | 100, 13 | 100, 14 | ); 15 | test_mutator(CharWithinRangeMutator::new('a'..='z'), 1.0, 1.0, true, true, 100, 100); 16 | test_mutator(CharWithinRangeMutator::new('a'..='z'), 100.0, 1.0, true, true, 100, 100); 17 | test_mutator( 18 | CharWithinRangeMutator::new('a'..='a'), 19 | 100.0, 20 | 100.0, 21 | true, 22 | true, 23 | 100, 24 | 100, 25 | ); 26 | test_mutator( 27 | CharWithinRangeMutator::new('a'..='b'), 28 | 100.0, 29 | 100.0, 30 | true, 31 | true, 32 | 100, 33 | 100, 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /fuzzcheck/tests/const_generics.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![feature(coverage_attribute)] 3 | 4 | use fuzzcheck::mutators::testing_utilities::test_mutator; 5 | use fuzzcheck::DefaultMutator; 6 | 7 | #[derive(Clone, Debug, PartialEq, Eq, Hash, DefaultMutator)] 8 | struct S { 9 | x: [T; N], 10 | y: [bool; M], 11 | } 12 | 13 | #[test] 14 | fn test_const_generics_mutator() { 15 | let mutator = S::::default_mutator(); 16 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 17 | } 18 | -------------------------------------------------------------------------------- /fuzzcheck/tests/constrained_integer.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::let_unit_value)] 2 | use std::collections::HashSet; 3 | use std::ops::RangeBounds; 4 | 5 | use fuzzcheck::mutators::integer_within_range::I8WithinRangeMutator; 6 | use fuzzcheck::mutators::testing_utilities::test_mutator; 7 | use fuzzcheck::Mutator; 8 | 9 | fn test_arbitrary_for_int_range_mutator(range: impl RangeBounds + IntoIterator + Clone) { 10 | let m = I8WithinRangeMutator::new(range.clone()); 11 | for _ in 0..1000 { 12 | let x = m.random_arbitrary(100.0).0; 13 | assert!(range.contains(&x), "{}", x); 14 | } 15 | let mut step = 0; 16 | let mut all_generated = HashSet::new(); 17 | while let Some((x, _)) = m.ordered_arbitrary(&mut step, 100.0) { 18 | let is_new = all_generated.insert(x); 19 | assert!(is_new); 20 | } 21 | for x in range { 22 | assert!(all_generated.contains(&x)); 23 | } 24 | } 25 | #[test] 26 | fn test_arbitrary_constrained_signed_integer_8() { 27 | test_arbitrary_for_int_range_mutator(-128..12); 28 | test_arbitrary_for_int_range_mutator(5..10); 29 | test_arbitrary_for_int_range_mutator(0..=0); 30 | test_arbitrary_for_int_range_mutator(-128..=127); 31 | test_arbitrary_for_int_range_mutator(-100..50); 32 | } 33 | 34 | #[test] 35 | fn test_mutate_constrained_signed_integer_8() { 36 | let mutator = I8WithinRangeMutator::new(-128..127); 37 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 38 | 39 | let mutator = I8WithinRangeMutator::new(-128..=127); 40 | let mut x = 0; 41 | let mut x_cache = mutator.validate_value(&x).unwrap(); 42 | let mut x_step = mutator.default_mutation_step(&x, &x_cache); 43 | let mut set = HashSet::new(); 44 | for _ in 0..256 { 45 | let (t, _c) = mutator 46 | .ordered_mutate( 47 | &mut x, 48 | &mut x_cache, 49 | &mut x_step, 50 | &fuzzcheck::subvalue_provider::EmptySubValueProvider, 51 | 100.0, 52 | ) 53 | .unwrap(); 54 | set.insert(x); 55 | mutator.unmutate(&mut x, &mut x_cache, t); 56 | } 57 | let mut set = set.into_iter().collect::>(); 58 | set.sort_unstable(); 59 | println!("{} {set:?}", set.len()); 60 | 61 | let mutator = I8WithinRangeMutator::new(-12..17); 62 | let mut x = 0; 63 | let mut x_cache = mutator.validate_value(&x).unwrap(); 64 | let mut x_step = mutator.default_mutation_step(&x, &x_cache); 65 | let mut set = HashSet::new(); 66 | for _ in 0..(12 + 16) { 67 | let (t, _c) = mutator 68 | .ordered_mutate( 69 | &mut x, 70 | &mut x_cache, 71 | &mut x_step, 72 | &fuzzcheck::subvalue_provider::EmptySubValueProvider, 73 | 100.0, 74 | ) 75 | .unwrap(); 76 | set.insert(x); 77 | mutator.unmutate(&mut x, &mut x_cache, t); 78 | } 79 | let mut set = set.into_iter().collect::>(); 80 | set.sort_unstable(); 81 | println!("{} {set:?}", set.len()); 82 | } 83 | -------------------------------------------------------------------------------- /fuzzcheck/tests/derived_enum.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![feature(coverage_attribute)] 3 | 4 | use fuzzcheck::mutators::testing_utilities::test_mutator; 5 | use fuzzcheck::DefaultMutator; 6 | 7 | #[derive(Clone, Debug, PartialEq, Eq, Hash, DefaultMutator)] 8 | enum SampleEnum { 9 | A(u16), 10 | B, 11 | C { x: bool, y: bool }, 12 | } 13 | 14 | #[test] 15 | fn test_derived_enum() { 16 | let mutator = SampleEnum::default_mutator(); 17 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 18 | let mutator = >::default_mutator(); 19 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 20 | } 21 | -------------------------------------------------------------------------------- /fuzzcheck/tests/derived_mutually_recursive_structs.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![feature(coverage_attribute)] 3 | // #![feature(trivial_bounds)] 4 | 5 | use std::fmt::Debug; 6 | 7 | use fuzzcheck::mutators::option::OptionMutator; 8 | use fuzzcheck::mutators::recursive::RecurToMutator; 9 | use fuzzcheck::mutators::testing_utilities::test_mutator; 10 | use fuzzcheck::mutators::vector::VecMutator; 11 | use fuzzcheck::{make_mutator, DefaultMutator}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 14 | struct MutuallyRecursiveA { 15 | b: Vec, 16 | data: Vec, 17 | } 18 | 19 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 20 | struct MutuallyRecursiveB { 21 | a: Option, 22 | data: bool, 23 | } 24 | 25 | make_mutator! { 26 | name: AMutator, 27 | recursive: true, 28 | default: false, 29 | type: 30 | struct MutuallyRecursiveA { 31 | // #[field_mutator(VecMutator::Mutator> = { MutuallyRecursiveB::default_mutator() })] 32 | b: Vec, 33 | #[field_mutator( as DefaultMutator>::Mutator = { >::default_mutator() })] 34 | data: Vec, 35 | } 36 | } 37 | 38 | make_mutator! { 39 | name: BMutator, 40 | recursive: true, 41 | default: true, 42 | type: 43 | struct MutuallyRecursiveB { 44 | #[field_mutator( 45 | OptionMutator>>> 46 | = { 47 | OptionMutator::new(AMutator::new( 48 | VecMutator::new(self_.into(), 0..=usize::MAX), 49 | >::default_mutator(), 50 | )) 51 | })] 52 | a: Option, 53 | #[field_mutator(::Mutator = { ::default_mutator() })] 54 | data: bool 55 | } 56 | } 57 | 58 | #[test] 59 | fn test_derived_struct() { 60 | let mutator = MutuallyRecursiveB::default_mutator(); 61 | test_mutator(mutator, 1000., 1000., false, true, 50, 50); 62 | } 63 | -------------------------------------------------------------------------------- /fuzzcheck/tests/derived_recursive_struct.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![allow(clippy::type_complexity)] 3 | #![feature(coverage_attribute)] 4 | 5 | use std::fmt::Debug; 6 | 7 | use fuzzcheck::mutators::boxed::BoxMutator; 8 | use fuzzcheck::mutators::integer::U8Mutator; 9 | use fuzzcheck::mutators::option::OptionMutator; 10 | use fuzzcheck::mutators::recursive::RecurToMutator; 11 | use fuzzcheck::mutators::testing_utilities::test_mutator; 12 | use fuzzcheck::mutators::tuples::{Tuple2, Tuple2Mutator, TupleMutatorWrapper}; 13 | use fuzzcheck::mutators::vector::VecMutator; 14 | use fuzzcheck::{make_mutator, DefaultMutator, Mutator}; 15 | 16 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 17 | struct SampleStruct { 18 | w: Option>>, 19 | x: T, 20 | y: U, 21 | z: Vec<(u8, SampleStruct)>, 22 | } 23 | 24 | make_mutator! { 25 | name: SampleStructMutator, 26 | recursive: true, 27 | default: true, 28 | type: 29 | struct SampleStruct { 30 | #[field_mutator( 31 | OptionMutator>, BoxMutator>>> = { 32 | OptionMutator::new(BoxMutator::new(self_.into())) 33 | } 34 | )] 35 | w: Option>>, 36 | x: T, 37 | y: U, 38 | #[field_mutator( 39 | VecMutator< 40 | (u8, SampleStruct), 41 | TupleMutatorWrapper< 42 | Tuple2Mutator< 43 | U8Mutator, 44 | RecurToMutator< 45 | SampleStructMutator 46 | > 47 | >, 48 | Tuple2> 49 | > 50 | > = { 51 | VecMutator::new( 52 | TupleMutatorWrapper::new( 53 | Tuple2Mutator::new( 54 | u8::default_mutator(), 55 | self_.into() 56 | ) 57 | ), 58 | 0..=usize::MAX 59 | ) 60 | } 61 | )] 62 | z: Vec<(u8, SampleStruct)>, 63 | } 64 | } 65 | 66 | #[allow(clippy::vec_box)] 67 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 68 | struct SampleStruct2 { 69 | w: Vec>, 70 | } 71 | 72 | make_mutator! { 73 | name: SampleStruct2Mutator, 74 | recursive: true, 75 | default: true, 76 | type: 77 | struct SampleStruct2 { 78 | #[field_mutator( 79 | VecMutator, BoxMutator>> = { 80 | VecMutator::new(BoxMutator::new(self_.into()), 0..=10) 81 | } 82 | )] 83 | w: Vec>, 84 | } 85 | } 86 | 87 | #[test] 88 | fn test_derived_struct() { 89 | let mutator = SampleStruct2::default_mutator(); 90 | // test_mutator(mutator, 100., 100., false, true, 50, 50); 91 | 92 | for _ in 0..1 { 93 | assert!(mutator.validate_value(&SampleStruct2 { w: vec![] }).is_some()); 94 | } 95 | std::hint::black_box(mutator); 96 | let mutator = >>::default_mutator(); 97 | test_mutator(mutator, 500., 500., false, true, 50, 100); 98 | } 99 | -------------------------------------------------------------------------------- /fuzzcheck/tests/derived_recursive_struct_fully_custom.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![feature(coverage_attribute)] 3 | use std::marker::PhantomData; 4 | 5 | use fuzzcheck::mutators::testing_utilities::test_mutator; 6 | use fuzzcheck::mutators::unit::UnitMutator; 7 | use fuzzcheck::{make_mutator, DefaultMutator}; 8 | 9 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 10 | struct SampleStruct { 11 | // #[field_mutator( ::Mutator = { bool::default_mutator() } )] 12 | x: bool, 13 | // #[field_mutator( ::Mutator = { bool::default_mutator() } )] 14 | y: bool, 15 | // #[field_mutator( UnitMutator> = { UnitMutator::new(PhantomData, 0.0) } )] 16 | _phantom: PhantomData, 17 | } 18 | 19 | make_mutator! { 20 | name: SampleStructMutator, 21 | recursive: true, 22 | default: true, 23 | type: 24 | struct SampleStruct { 25 | #[field_mutator( ::Mutator = { bool::default_mutator() } )] 26 | x: bool, 27 | #[field_mutator( ::Mutator = { bool::default_mutator() } )] 28 | y: bool, 29 | #[field_mutator( UnitMutator> = { UnitMutator::new(PhantomData, 0.0) } )] 30 | _phantom: PhantomData, 31 | } 32 | } 33 | 34 | #[test] 35 | fn test_derived_struct() { 36 | let mutator = SampleStruct::::default_mutator(); 37 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 38 | let mutator = >>::default_mutator(); 39 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 40 | } 41 | -------------------------------------------------------------------------------- /fuzzcheck/tests/derived_struct.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![feature(coverage_attribute)] 3 | use fuzzcheck::mutators::testing_utilities::test_mutator; 4 | use fuzzcheck::DefaultMutator; 5 | 6 | #[derive(Clone, Debug, PartialEq, Eq, Hash, DefaultMutator)] 7 | struct SampleStruct { 8 | x: T, 9 | y: U, 10 | } 11 | 12 | #[test] 13 | fn test_derived_struct() { 14 | let mutator = SampleStruct::::default_mutator(); 15 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 16 | let mutator = >>::default_mutator(); 17 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 18 | } 19 | -------------------------------------------------------------------------------- /fuzzcheck/tests/enum_with_ignored_variant.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_attributes)] 2 | #![feature(coverage_attribute)] 3 | 4 | use fuzzcheck::mutators::testing_utilities::test_mutator; 5 | use fuzzcheck::{make_mutator, DefaultMutator, Mutator}; 6 | 7 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 8 | enum Enum { 9 | X(bool, bool), 10 | Y { x: bool, y: T }, 11 | } 12 | 13 | make_mutator! { 14 | name: EnumMutator, 15 | default: true, 16 | type: 17 | enum Enum { 18 | #[ignore_variant] 19 | X(bool, bool), 20 | Y { 21 | x: bool, 22 | y: T, 23 | }, 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_derived_enum_with_ignored_variant() { 29 | let mutator = Enum::::default_mutator(); 30 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 31 | let mutator = >>::default_mutator(); 32 | test_mutator(mutator, 1000., 1000., false, true, 100, 100); 33 | 34 | let mutator = Enum::::default_mutator(); 35 | for _ in 0..100 { 36 | let (v, _) = mutator.random_arbitrary(1000.); 37 | println!("{v:?}"); 38 | } 39 | } 40 | 41 | // this compiles but no value can be produced, the alternation mutator fails with a !mutators.is_empty() assertion 42 | #[derive(Clone, Debug, PartialEq, Eq, Hash, DefaultMutator)] 43 | enum Enum2 { 44 | #[ignore_variant] 45 | X(bool, bool), 46 | #[ignore_variant] 47 | Y { x: bool, y: T }, 48 | } 49 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/empty_structs.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub struct X; 5 | 6 | #[derive(Clone, DefaultMutator)] 7 | pub struct Y {} 8 | 9 | #[derive(Clone, DefaultMutator)] 10 | pub struct Z(); 11 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_ignore_variant.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::{DefaultMutator, Mutator}; 2 | use fuzzcheck_mutators_derive::make_mutator; 3 | 4 | #[derive(Clone, DefaultMutator)] 5 | enum NoAssociatedData { 6 | #[ignore_variant] 7 | A, 8 | B, 9 | #[ignore_variant] 10 | C, 11 | D, 12 | E, 13 | } 14 | 15 | #[derive(Clone)] 16 | enum WithIgnore { 17 | CanMutate(u8), 18 | CannotMutate(CannotMutate), 19 | X, 20 | Y, 21 | Z, 22 | A { flag: bool, item: T }, 23 | } 24 | 25 | #[derive(Clone)] 26 | struct CannotMutate {} 27 | 28 | make_mutator! { 29 | name: WithIgnoreMutator, 30 | recursive: false, 31 | default: true, 32 | type: 33 | enum WithIgnore { 34 | CanMutate(u8), 35 | #[ignore_variant] 36 | CannotMutate(CannotMutate), 37 | #[ignore_variant] 38 | X, 39 | #[ignore_variant] 40 | Y, 41 | #[ignore_variant] 42 | Z, 43 | #[ignore_variant] 44 | A { 45 | flag: bool, 46 | item: T 47 | } 48 | } 49 | } 50 | 51 | #[test] 52 | #[coverage(off)] 53 | fn test_compile() { 54 | let m = WithIgnore::::default_mutator(); 55 | let _ = m.random_arbitrary(10.0); 56 | } 57 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_with_generic_type_params.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::{DefaultMutator, Mutator}; 2 | 3 | #[derive(Clone, Debug, DefaultMutator)] 4 | pub enum X { 5 | A(T), 6 | B(Vec), 7 | } 8 | 9 | #[test] 10 | #[coverage(off)] 11 | fn test_compile() { 12 | let m = X::>::default_mutator(); 13 | let (value, _cache): (X>, _) = m.random_arbitrary(100.0); 14 | 15 | match &value { 16 | X::A(_x) => {} 17 | X::B(_y) => {} 18 | } 19 | println!("{:?}", value); 20 | } 21 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_with_items_with_and_without_fields.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::{DefaultMutator, Mutator}; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub enum A { 5 | X(u8), 6 | } 7 | 8 | #[derive(Clone, DefaultMutator)] 9 | pub enum X { 10 | A(u8), 11 | B, 12 | } 13 | 14 | #[derive(Clone, DefaultMutator)] 15 | pub enum Z { 16 | A(u8), 17 | B(u16), 18 | C, 19 | D(bool), 20 | } 21 | 22 | #[test] 23 | #[coverage(off)] 24 | fn test_compile() { 25 | let m = A::default_mutator(); 26 | let (_alue, _cache): (A, _) = m.random_arbitrary(10.0); 27 | 28 | let m = X::default_mutator(); 29 | let (value, _): (X, _) = m.random_arbitrary(10.0); 30 | 31 | match value { 32 | X::A(_x) => {} 33 | X::B => {} 34 | } 35 | 36 | let m = Z::default_mutator(); 37 | let (_value, _): (Z, _) = m.random_arbitrary(10.0); 38 | } 39 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_with_multiple_empty_items.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub enum X { 5 | A, 6 | B, 7 | } 8 | 9 | #[derive(Clone, DefaultMutator)] 10 | pub enum Y { 11 | A, 12 | B(), 13 | C {}, 14 | } 15 | 16 | #[derive(Clone, DefaultMutator)] 17 | pub enum Z { 18 | A, 19 | B, 20 | C, 21 | D, 22 | E, 23 | F, 24 | G, 25 | H, 26 | I, 27 | J, 28 | K, 29 | L, 30 | } 31 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_with_one_empty_item.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(DefaultMutator, Clone)] 4 | pub enum X { 5 | A, 6 | } 7 | 8 | #[derive(DefaultMutator, Clone)] 9 | pub enum Y { 10 | A(), 11 | } 12 | 13 | #[derive(DefaultMutator, Clone)] 14 | pub enum Z { 15 | A {}, 16 | } 17 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_with_one_item_multiple_fields.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub enum X { 5 | A(u8, bool), 6 | } 7 | 8 | #[derive(Clone, DefaultMutator)] 9 | pub enum Y { 10 | Y { y: Option, z: () }, 11 | } 12 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/enums_with_one_item_one_field.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub enum X { 5 | A(u8), 6 | } 7 | 8 | #[cfg(test)] 9 | mod test { 10 | use fuzzcheck::Mutator; 11 | 12 | use super::*; 13 | #[test] 14 | #[coverage(off)] 15 | fn test_compile() { 16 | let m = X::default_mutator(); 17 | let (_value, _): (X, _) = m.random_arbitrary(10.0); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::nonstandard_macro_braces, clippy::too_many_arguments)] 2 | mod empty_structs; 3 | mod one_field_structs; 4 | mod structs_with_generic_type_params; 5 | mod two_field_structs; 6 | 7 | mod enums_ignore_variant; 8 | mod enums_with_generic_type_params; 9 | mod enums_with_items_with_and_without_fields; 10 | mod enums_with_multiple_empty_items; 11 | mod enums_with_one_empty_item; 12 | mod enums_with_one_item_multiple_fields; 13 | mod enums_with_one_item_one_field; 14 | 15 | mod recursive_enum; 16 | mod recursive_struct; 17 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/one_field_structs.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub struct X(bool); 5 | 6 | #[derive(Clone, DefaultMutator)] 7 | pub struct Y { 8 | x: bool, 9 | } 10 | 11 | #[cfg(test)] 12 | mod test { 13 | use fuzzcheck::Mutator; 14 | 15 | use super::*; 16 | #[test] 17 | #[coverage(off)] 18 | fn test_compile() { 19 | let _m = X::default_mutator(); 20 | let m = Y::default_mutator(); 21 | 22 | let (_y, _) = m.random_arbitrary(10.0); 23 | // assert!(false, "{}", y.x); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/recursive_enum.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::mutators::boxed::BoxMutator; 2 | use fuzzcheck::mutators::recursive::RecurToMutator; 3 | use fuzzcheck::{make_mutator, DefaultMutator, Mutator}; 4 | 5 | #[test] 6 | #[coverage(off)] 7 | fn test_compile() { 8 | let m = S::default_mutator(); 9 | let (x, _) = m.random_arbitrary(10.0); 10 | println!("{:?}", x); 11 | } 12 | 13 | #[derive(Clone, Debug)] 14 | enum S { 15 | A(bool), 16 | B(Box), 17 | } 18 | 19 | make_mutator! { 20 | name: SMutator, 21 | recursive: true, 22 | default: true, 23 | type: 24 | enum S { 25 | A(bool), 26 | B(#[field_mutator(BoxMutator>> = { BoxMutator::new(self_.into()) }) ] Box), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/recursive_struct.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | use fuzzcheck::mutators::bool::BoolMutator; 3 | use fuzzcheck::mutators::boxed::BoxMutator; 4 | use fuzzcheck::mutators::option::OptionMutator; 5 | use fuzzcheck::mutators::recursive::{RecurToMutator, RecursiveMutator}; 6 | use fuzzcheck::{make_mutator, DefaultMutator, Mutator}; 7 | 8 | #[derive(Clone, Debug)] 9 | struct S { 10 | x: bool, 11 | y: Option>, 12 | } 13 | 14 | make_mutator! { 15 | name: SMutator, 16 | recursive: true, 17 | default: true, 18 | type: 19 | struct S { 20 | x: bool, 21 | #[field_mutator(OptionMutator, BoxMutator>>> = { OptionMutator::new(BoxMutator::new(self_.into())) }) ] 22 | y: Option>, 23 | } 24 | } 25 | 26 | #[derive(Clone)] 27 | pub struct R { 28 | x: u8, 29 | y: Option>>, 30 | z: Vec, 31 | } 32 | make_mutator! { 33 | name: RMutator, 34 | recursive: true, 35 | default: true, 36 | type: // repeat the declaration of E 37 | pub struct R { 38 | x: u8, 39 | // for recursive mutators, it is necessary to indicate *where* the recursion is 40 | // and use a `RecurToMutator` as the recursive field's mutator 41 | // M0 is the type parameter for the mutator of the `x` field 42 | #[field_mutator(OptionMutator>, BoxMutator>>> = { OptionMutator::new(BoxMutator::new(self_.into())) })] 43 | // self_.into() creates the RecurToMutator 44 | y: Option>>, 45 | z: Vec 46 | } 47 | } 48 | 49 | mod mutator {} 50 | 51 | #[test] 52 | #[coverage(off)] 53 | fn test_compile() { 54 | let _m = RecursiveMutator::new(|self_| { 55 | SMutator::new(::default_mutator(), { 56 | OptionMutator::new(BoxMutator::new(self_.into())) 57 | }) 58 | }); 59 | let _m: RecursiveMutator> = S::default_mutator(); 60 | let m = S::default_mutator(); 61 | let (x, _) = m.random_arbitrary(10.0); 62 | println!("{:?}", x); 63 | } 64 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/structs_with_generic_type_params.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub struct X(T, Vec); 5 | 6 | #[derive(Clone, DefaultMutator)] 7 | pub struct Y { 8 | _x: Option, 9 | _y: (T, U), 10 | } 11 | -------------------------------------------------------------------------------- /fuzzcheck/tests/expansions/two_field_structs.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::DefaultMutator; 2 | 3 | #[derive(Clone, DefaultMutator)] 4 | pub struct X>(bool, u8, T, U, u8, u8, u8, u8, u8, u8); 5 | 6 | #[derive(Clone, DefaultMutator)] 7 | pub struct Y { 8 | #[field_mutator(::Mutator)] 9 | _x: bool, 10 | _y: Vec>, 11 | } 12 | 13 | #[coverage(off)] 14 | fn _x() { 15 | let _x = X::>::default_mutator(); 16 | let _y = Y::default_mutator(); 17 | } 18 | -------------------------------------------------------------------------------- /fuzzcheck/tests/grammar_based_mutators.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "serde_json_serializer")] 2 | #![allow(unused_attributes)] 3 | #![feature(coverage_attribute)] 4 | 5 | use std::rc::{Rc, Weak}; 6 | 7 | use fuzzcheck::mutators::grammar::*; 8 | use fuzzcheck::mutators::testing_utilities::test_mutator; 9 | // use fuzzcheck::{DefaultMutator, Mutator}; 10 | 11 | #[coverage(off)] 12 | fn text() -> Rc { 13 | regex("([\u{0}-\u{7f}]|.)+|CDATA") 14 | } 15 | #[coverage(off)] 16 | fn whitespace() -> Rc { 17 | regex("[ \t\n\r]+") 18 | } 19 | #[coverage(off)] 20 | fn header(md: &Weak) -> Rc { 21 | concatenation([regex("#+"), recurse(md), regex("#*")]) 22 | } 23 | #[coverage(off)] 24 | pub fn quote() -> Rc { 25 | regex(">+") 26 | } 27 | #[coverage(off)] 28 | pub fn list() -> Rc { 29 | regex("[-*+]|[0-9]*[.)]") 30 | } 31 | #[coverage(off)] 32 | pub fn emphasis(md: &Weak) -> Rc { 33 | concatenation([regex("[*_~`]+"), recurse(md), regex("[*_~`]+")]) 34 | } 35 | #[coverage(off)] 36 | pub fn autolink(md: &Weak) -> Rc { 37 | concatenation([literal('<'), alternation([recurse(md), text(), web()]), literal('>')]) 38 | } 39 | #[coverage(off)] 40 | pub fn reference(md: &Weak) -> Rc { 41 | concatenation([ 42 | regex("!?\\["), 43 | recurse(md), 44 | literal(']'), 45 | repetition(concatenation([literal('('), recurse(md), literal(')')]), 0..=1), 46 | ]) 47 | } 48 | #[coverage(off)] 49 | pub fn reference_definition(md: &Weak) -> Rc { 50 | concatenation([ 51 | literal('['), 52 | recurse(md), 53 | literal(']'), 54 | repetition(whitespace(), 0..=1), 55 | literal(':'), 56 | ]) 57 | } 58 | #[coverage(off)] 59 | pub fn thematic_break_or_setext_or_fence() -> Rc { 60 | alternation([ 61 | regex("[* \t]{3,}"), 62 | regex("[- \t]{3,}"), 63 | regex("[= \t]{3,}"), 64 | regex("[~ \t]{3,}"), 65 | regex("[` \t]{3,}"), 66 | ]) 67 | } 68 | #[coverage(off)] 69 | pub fn backslash() -> Rc { 70 | literal('\\') 71 | } 72 | #[coverage(off)] 73 | pub fn entity() -> Rc { 74 | concatenation([ 75 | literal('&'), 76 | repetition(literal('#'), 0..=1), 77 | repetition(text(), 0..=1), 78 | repetition(literal(';'), 0..=1), 79 | ]) 80 | } 81 | #[coverage(off)] 82 | pub fn task(whole: &Weak) -> Rc { 83 | concatenation([ 84 | regex("-|\\+"), 85 | alternation([whitespace(), text()]), 86 | literal('['), 87 | alternation([regex(r"x|\^"), text(), recurse(whole)]), 88 | literal(']'), 89 | ]) 90 | } 91 | #[coverage(off)] 92 | pub fn indented_block(whole: &Weak) -> Rc { 93 | concatenation([regex("[ \t]+"), recurse(whole)]) 94 | } 95 | #[coverage(off)] 96 | pub fn html() -> Rc { 97 | concatenation([ 98 | regex("'), 112 | ]) 113 | } 114 | #[coverage(off)] 115 | pub fn html_comment(whole: &Weak) -> Rc { 116 | concatenation([regex("<-+"), recurse(whole), regex("-+>")]) 117 | } 118 | #[coverage(off)] 119 | fn quoted(whole: &Weak) -> Rc { 120 | concatenation([regex("[\"']"), alternation([text(), recurse(whole)]), regex("[\"']")]) 121 | } 122 | #[coverage(off)] 123 | fn fenced_block(whole: &Weak) -> Rc { 124 | concatenation([regex("~{3,}|`{3,}"), recurse(whole), regex("~{3,}|`{3,}")]) 125 | } 126 | #[coverage(off)] 127 | fn table(whole: &Weak) -> Rc { 128 | repetition( 129 | // row 130 | concatenation([ 131 | repetition( 132 | // column 133 | concatenation([ 134 | repetition(alternation([text(), recurse(whole), regex(":*-*:*")]), 0..=1), 135 | literal('|'), 136 | alternation([text(), recurse(whole), regex(":*-*:*")]), 137 | ]), 138 | 1..10, 139 | ), 140 | literal_ranges(vec!['\r'..='\r', '\n'..='\n']), 141 | ]), 142 | 1..10, 143 | ) 144 | } 145 | #[coverage(off)] 146 | fn web() -> Rc { 147 | concatenation([regex("(https?://)?(www.)?"), text(), literal('.'), text()]) 148 | } 149 | #[coverage(off)] 150 | fn markdown() -> Rc { 151 | recursive(|md| { 152 | repetition( 153 | alternation([ 154 | whitespace(), 155 | text(), 156 | backslash(), 157 | entity(), 158 | task(md), 159 | header(md), 160 | emphasis(md), 161 | quote(), 162 | list(), 163 | web(), 164 | reference(md), 165 | reference_definition(md), 166 | autolink(md), 167 | thematic_break_or_setext_or_fence(), 168 | indented_block(md), 169 | html(), 170 | html_comment(md), 171 | quoted(md), 172 | fenced_block(md), 173 | table(md), 174 | ]), 175 | 0.., 176 | ) 177 | }) 178 | } 179 | 180 | #[test] 181 | fn test_grammar_based_ast_mutator() { 182 | let mutator = grammar_based_ast_mutator(markdown()); 183 | test_mutator(mutator, 500., 500., false, true, 60, 100); 184 | } 185 | -------------------------------------------------------------------------------- /fuzzcheck/tests/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(coverage_attribute)] 2 | // #![feature(trivial_bounds)] 3 | mod alternation_char_mutators; 4 | mod char_mutators; 5 | mod const_generics; 6 | mod constrained_integer; 7 | mod derived_mutually_recursive_structs; 8 | mod derived_recursive_struct; 9 | mod derived_recursive_struct_fully_custom; 10 | mod derived_struct; 11 | mod enum_with_ignored_variant; 12 | mod expansions; 13 | #[cfg(feature = "regex_grammar")] 14 | mod grammar_based_mutators; 15 | mod option; 16 | mod vector; 17 | -------------------------------------------------------------------------------- /fuzzcheck/tests/option.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | 3 | use fuzzcheck::mutators::integer::U8Mutator; 4 | use fuzzcheck::mutators::option::OptionMutator; 5 | use fuzzcheck::subvalue_provider::{CrossoverSubValueProvider, Generation, SubValueProviderId}; 6 | use fuzzcheck::{DefaultMutator, Mutator, SubValueProvider}; 7 | 8 | #[test] 9 | fn test_crossover_option() { 10 | let crossover_value = vec![None, Some(83638), Some(1), Some(2), Some(3), Some(19373246372)]; 11 | let crossover_mutator = >>::default_mutator(); 12 | crossover_mutator.initialize(); 13 | let crossover_cache = crossover_mutator.validate_value(&crossover_value).unwrap(); 14 | 15 | let subvalue_provider = CrossoverSubValueProvider::new( 16 | SubValueProviderId { 17 | idx: 0, 18 | generation: Generation(0), 19 | }, 20 | &crossover_value, 21 | &crossover_cache, 22 | &crossover_mutator, 23 | ); 24 | 25 | let mut index = 0; 26 | for _ in 0..100 { 27 | let x = subvalue_provider 28 | .get_subvalue(TypeId::of::(), 100.0, &mut index) 29 | .and_then(|(x, cplx)| x.downcast_ref::().map(|x| (x, cplx))); 30 | if let Some((x, cplx)) = x { 31 | println!("{x} {cplx:.2}"); 32 | } else { 33 | break; 34 | } 35 | } 36 | let mutator = >::default_mutator(); 37 | mutator.initialize(); 38 | 39 | let mut value = Option::Some(0); 40 | let mut cache = mutator.validate_value(&value).unwrap(); 41 | let mut step = mutator.default_mutation_step(&value, &cache); 42 | 43 | let mut found = false; 44 | for i in 0..100000 { 45 | let (token, _) = mutator 46 | .ordered_mutate(&mut value, &mut cache, &mut step, &subvalue_provider, 10000.) 47 | .unwrap(); 48 | if value == Some(19373246372) { 49 | found = true; 50 | println!("found after {i} iterations"); 51 | break; 52 | } 53 | mutator.unmutate(&mut value, &mut cache, token); 54 | } 55 | assert!(found); 56 | } 57 | 58 | #[test] 59 | fn test_option() { 60 | let m = OptionMutator::new(U8Mutator::default()); 61 | fuzzcheck::mutators::testing_utilities::test_mutator(m, 100.0, 100.0, false, true, 500, 500); 62 | } 63 | -------------------------------------------------------------------------------- /fuzzcheck/tests/vector.rs: -------------------------------------------------------------------------------- 1 | use fuzzcheck::mutators::integer::U8Mutator; 2 | use fuzzcheck::mutators::vector::VecMutator; 3 | #[test] 4 | fn test_vector_mutator() { 5 | let m = VecMutator::new(VecMutator::new(U8Mutator::default(), 0..=usize::MAX), 0..=usize::MAX); 6 | fuzzcheck::mutators::testing_utilities::test_mutator(m, 500.0, 500.0, false, true, 100, 150); 7 | } 8 | 9 | // #[test] 10 | // fn test_vector_explore() { 11 | // // let m = VecMutator::new(VecMutator::new(U8Mutator::default(), 0..=5), 0..=5); 12 | // let m = VecMutator::new(>::default_mutator(), 0..=32); //VecMutator::new(VecMutator::new(U8Mutator::default(), 0..=5), 0..=10); 13 | // let mut step = m.default_arbitrary_step(); 14 | // // let (x, cplx) = m.ordered_arbitrary(&mut step, 1000.0).unwrap(); 15 | // // println!("{:?}", x); 16 | // // println!("cplx: {}", cplx); 17 | // let mut sum = 0; 18 | // let mut total = 0; 19 | // for _ in 0..10 { 20 | // if let Some((mut x, _cplx)) = m.ordered_arbitrary(&mut step, 1000.0) { 21 | // assert!((0..=32).contains(&x.len())); 22 | // // println!("{:?}", x); 23 | // // println!("cplx: {}", cplx); 24 | // let mut cache = m.validate_value(&x).unwrap(); 25 | // let mut step = m.default_mutation_step(&x, &cache); 26 | // let mut all = HashSet::new(); 27 | // for i in 0..10_000 { 28 | // total += 1; 29 | // if let Some((token, _cplx)) = m.ordered_mutate(&mut x, &mut cache, &mut step, 1000.) { 30 | // all.insert(x.clone()); 31 | // // println!("{:?}", x); 32 | // // println!("\t{:?}", x); 33 | // assert!((0..=32).contains(&x.len()), "{}", x.len()); 34 | // m.unmutate(&mut x, &mut cache, token); 35 | // assert!((0..=32).contains(&x.len()), "{}", x.len()); 36 | // } else { 37 | // println!("!!!!!!! STOP at {} !!!!!!", i); 38 | // break; 39 | // } 40 | // // let (token, _) = m.random_mutate(&mut x, &mut cache, 1000.); 41 | // // assert!((0..=32).contains(&x.len())); 42 | // // all.insert(x.clone()); 43 | // // m.unmutate(&mut x, &mut cache, token); 44 | // } 45 | // sum += all.len(); 46 | // println!("==="); 47 | // } else { 48 | // break; 49 | // } 50 | // } 51 | // println!("{}", sum as f64 / total as f64); 52 | // } 53 | 54 | // #[test] 55 | // fn test_vector_explore2() { 56 | // let m = VecMutator::new(<()>::default_mutator(), 0..=usize::MAX); //VecMutator::new(VecMutator::new(U8Mutator::default(), 0..=5), 0..=10); 57 | // let mut step = m.default_arbitrary_step(); 58 | // for j in 0..36 { 59 | // if let Some((mut x, _cplx)) = m.ordered_arbitrary(&mut step, 32.0) { 60 | // println!("{} {:?}", x.len(), x); 61 | // let mut cache = m.validate_value(&x).unwrap(); 62 | // let mut step = m.default_mutation_step(&x, &cache); 63 | // for i in 0..40 { 64 | // if let Some((token, _cplx)) = m.ordered_mutate(&mut x, &mut cache, &mut step, 32.) { 65 | // println!("{} {:?}", x.len(), x); 66 | // m.unmutate(&mut x, &mut cache, token); 67 | // } else { 68 | // println!("!!!!!!! STOP at {} !!!!!!", i); 69 | // break; 70 | // } 71 | // } 72 | // println!("==="); 73 | // } else { 74 | // println!("no more arbitraries!! {}", j); 75 | // break; 76 | // } 77 | // } 78 | // } 79 | -------------------------------------------------------------------------------- /fuzzcheck_book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | juliaplots 3 | hello_world 4 | pulldown-cmark -------------------------------------------------------------------------------- /fuzzcheck_book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Loïc Lecrenier"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Fuzzcheck Book" 7 | -------------------------------------------------------------------------------- /fuzzcheck_book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | - [Introduction](./introduction.md) 3 | - [Getting Started](./getting_started.md) 4 | - [Quick Start](./quick_start.md) 5 | - [Tutorial 1: binary search tree](./tutorial1.md) 6 | - [Setup](./tutorial1_setup.md) 7 | - [Test Function](./tutorial1_function.md) 8 | - [Mutator](./tutorial1_mutator.md) 9 | - [Serializer](./tutorial1_serializer.md) 10 | - [Writing the Fuzz Target](./tutorial1_writing_fuzz_target.md) 11 | - [Using cargo-fuzzcheck](./tutorial1_using_cargo_fuzzcheck.md) 12 | - [Tutorial 2: pulldown-cmark](./tutorial2.md) 13 | - [Test Function](./tutorial2_function.md) 14 | - [Creating a grammar-based mutator](./tutorial2_mutator.md) 15 | - [A markdown grammar](./tutorial2_syntax.md) 16 | - [Fuzzing](./tutorial2_fuzzing.md) -------------------------------------------------------------------------------- /fuzzcheck_book/src/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Prerequisites 4 | 5 | First, remember that you need to run either Linux or macOS, as 6 | [Windows is not yet supported](https://github.com/loiclec/fuzzcheck-rs/issues/8). 7 | 8 | Fuzzcheck comes with a helper tool that helps you pass the right flags to 9 | compile fuzz targets. It is recommended to use it. To install it, run: 10 | 11 | ```sh 12 | cargo install cargo-fuzzcheck 13 | ``` 14 | 15 | It is also necessary to use Rust nightly. You can install it using `rustup`: 16 | ```sh 17 | rustup install nightly 18 | ``` 19 | 20 | ## What type of function can I fuzz-test? 21 | 22 | ### 1) Pure, fast functions 23 | 24 | It is best if the tested function is “pure”, meaning that it behaves in exactly 25 | the same way every time it is called with the same input. It is also best if the 26 | tested function runs very fast, ideally in less than a tenth of a millisecond. 27 | This is because fuzzcheck often needs hundreds of thousands of iterations to build 28 | a corpus of interesting test cases. 29 | 30 | Functions that perform file or network operations may have unpredictable behaviour 31 | or take too long to run and are thus not a great fit for fuzz-testing. 32 | 33 | Note also that the tested function takes an **immutable reference** as argument. 34 | It is important that it does not mutate its argument through types 35 | such as `Cell`, `RefCell`, or `UnsafeCell`. 36 | 37 | ### 2) No dependence on information that is lost when cloning 38 | 39 | Furthermore, be aware that some types lose information when serialised or cloned. 40 | This is the case for `Vec` and its `capacity` property. If the tested function 41 | changes its behaviour based on these kinds of properties, fuzzcheck will be less 42 | useful. For a concrete example, imagine that the tested function is: 43 | ```rust 44 | fn test_vector_capacity(v: &Vec) -> bool { 45 | v.capacity < 10 46 | } 47 | ``` 48 | Then an empty vector with a capacity of `12` would fail the test. But fuzzcheck 49 | will save this failing test case as: 50 | ```json 51 | [] 52 | ``` 53 | And when the test case is deserialised, the resulting vector may have a capacity 54 | of `0`, and thus will not fail the test anymore. 55 | 56 | Problems also arise when the tested function makes internal decisions based on `capacity`: 57 | ```rust 58 | fn test_vector(v: &Vec) -> bool { 59 | if v.capacity < 10 { 60 | // do something 61 | } else { 62 | // do something else 63 | } 64 | } 65 | ``` 66 | Now the problem is more subtle. Imagine that `test_vector` is called on an 67 | empty vector with capacity `12`. Then the second branch will be taken and 68 | fuzzcheck may judge that the test case is interesting because it explored a 69 | previously uncovered code region. The test case is thus saved to the internal 70 | pool by cloning. But cloning does *not* preserve the `capacity` property. The 71 | vector that is saved in fuzzcheck’s pool is an “impostor” in some sense. This 72 | may stall the fuzzer’s progress. 73 | 74 | 104 | 105 | 106 | ## Getting Started 107 | 108 | To learn how to use fuzzcheck, go to the [quick start](quick_start.md) section or follow one of the tutorials: 109 | * [Tutorial 1](tutorial1.md) sets up a differential property test between a binary search tree and `std::collections::BTreeSet`. Through it, you can learn about the different parts of the fuzzer and learn to interpret the files generated by it. No bug is directly found in this tutorial, but we guess a potential stack overflow by looking at the content of the generated corpus. 110 | * [Tutorial 2](tutorial2.md) fuzz-test the `pulldown-cmark` crate using a grammar-based mutator. We find test cases that trigger a panic at multiple different locations. -------------------------------------------------------------------------------- /fuzzcheck_book/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Fuzzcheck is a crate to easily find bugs in Rust code. Given a test function: 4 | ```rust ignore 5 | fn test_always_true(x: SomeType) -> bool { 6 | // ... 7 | } 8 | ``` 9 | You can use fuzzcheck to find a value `x: SomeType` that causes `test_always_true(x)` to return false or crash 💥. 10 | 11 | Technically, it is called an evolutionary, structure-aware fuzzing engine. It is coverage-guided by default, 12 | but can also use other observations to guide the evolution of the fuzz test. 13 | 14 | ## Goal 🦄 15 | 16 | Fuzzcheck’s goal is to be used as part of your development routine. You can quickly test small or complex 17 | function with it. It can find difficult corner-case bugs, minify complex test cases into easy-to-debug ones. 18 | It can find stack overflows and inputs that cause excessive computations or allocations. 19 | 20 | Over time, by using fuzzcheck, you can automatically build a large corpus of values that trigger most 21 | edge cases in your code. This will help ensure that no new bugs are introduced when refactoring. 22 | 23 | ## Requirements 🎟 24 | 25 | Currently, it is only possible to use fuzzcheck on Linux and macOS. I'd like to add Windows support to it, and 26 | it shouldn't be complicated, but [I need some help with it](https://github.com/loiclec/fuzzcheck-rs/issues/8). 27 | 28 | You also need a nightly version of the Rust compiler. This requirement is unlikely to change soon. 29 | 30 | ## Design ⚙️ 31 | 32 | Fuzzcheck works by repeatedly running the test function `test_always_true(x)` with automatically generated 33 | values for `x`. For each invocation of `test_always_true(x)`, it gathers observations about the code 34 | that was run and determines whether new observations were discovered. This enables it to build a list of values 35 | that are known to be interesting to test, which we call the “pool of interesting values”. 36 | 37 | Then, fuzzcheck repeatedly draws values from that pool, slightly mutates them, and then feeds them to 38 | `test_always_true` again. It continues this process indefinitely, analysing the observations (e.g. code coverage) 39 | triggered by each test case in order to discover more and more interesting values to test. 40 | 41 | Thus, fuzzcheck is composed of three components working together: 42 | * a `Mutator`, which can generate arbitrary values of type `T`, either from scratch or starting from a 43 | known interesting value. 44 | * a `Sensor`, whose role is to gather observations about the execution of a test function. By default, this uses 45 | the code coverage information generated by the `-C instrument-coverage` option of the Rust compiler. But different 46 | kinds of observations can be used. 47 | * a `Pool`, which listens to the observations from the `Sensor` and determines which values are interesting 48 | to test. It internally ranks the different values that were tested and ensures that the most interesting ones 49 | are mutated and re-tested more often. 50 | 51 | In pseudo-code: 52 | ```rust ignore 53 | loop { 54 | let value = pool.get_mut(); 55 | mutator.mutate(value); 56 | let observations = sensor.record(|| { 57 | test_function(value); 58 | }); 59 | if pool.is_interesting(value, observations) { 60 | pool.add(value, observations); 61 | } 62 | } 63 | ``` 64 | 65 | ## Example 👀 66 | 67 | We can illustrate fuzzcheck’s strengths with a simple but unrealistic example. What follows are not instructions 68 | to use fuzzcheck (these start from the next section), but rather an example showing how fuzzcheck is different from 69 | other testing tools such as `quickcheck` or `proptest`. Imagine we have the following function: 70 | 71 | ```rust ignore 72 | fn this_should_always_succeed(xs: &[u8]) -> bool { 73 | if xs.len() >= 4 && xs[0] == 96 && xs[1] == 1 && xs[2] < 78 && xs[3] == 189 { 74 | false 75 | } else { 76 | true 77 | } 78 | } 79 | ``` 80 | 81 | We can find a vector of bytes that makes the function fail by running the following test: 82 | ```rust ignore 83 | #[cfg(test)] 84 | mod tests { 85 | #[test] 86 | fn fuzz_test() { 87 | let result = fuzzcheck::fuzz_test(super::this_should_always_succeed) 88 | .default_options() 89 | .stop_after_first_test_failure(true) 90 | .launch(); 91 | assert!(!result.found_test_failure); 92 | } 93 | } 94 | ``` 95 | We run: 96 | 97 | ```sh 98 | cargo fuzzcheck tests::fuzz_test 99 | ``` 100 | which launches the fuzz test on the library target. Fuzzcheck progressively finds values which 101 | satisfy each condition inside `fn this_should_always_succeed` until, after about 2000 iterations, 102 | it prints: 103 | 104 | ```text 105 | Failing test case found. 106 | Saving at "fuzz/artifacts/tests::fuzz_test/642e66bf463956ed.json" 107 | ``` 108 | 109 | It found the bug and serialized the failing test case to the file `42e66bf463956ed.json`, which contains: 110 | ```json 111 | [96,1,24,189] 112 | ``` 113 | The time to find this bug, on my machine, was 12 milliseconds. 114 | 115 | While this was a simplistic example, the same process can be used for large functions that take very complex 116 | input types as arguments. However, note that the test function should run fairly fast, ideally in 117 | less than a tenth of a millisecond. 118 | -------------------------------------------------------------------------------- /fuzzcheck_book/src/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loiclec/fuzzcheck-rs/cab34edee5ae038a4703bcd4885454bcabcae34a/fuzzcheck_book/src/tree.png -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial1.md: -------------------------------------------------------------------------------- 1 | # Tutorial 1: a binary search tree 2 | 3 | In this tutorial, we write a simple data structure and test its API with fuzzcheck. 4 | The goal is to learn the basics, that is: 5 | 6 | * the requirements for a type or function to be fuzz-testable 7 | * how to create a default `Mutator` for a custom type through fuzzcheck’s procedural macros 8 | * how to use `serde` to serialize interesting values and artifacts to the type system 9 | * how to use the cargo-fuzzcheck command line tool 10 | * how to interpret the files generated by fuzzcheck 11 | 12 | The data structure we choose to implement is a binary search tree. We create a new tree 13 | with `Tree::empty()`, insert a value into the tree with `tree.insert(x)`, and search for 14 | a value in the tree with `tree.contains(&x)`. 15 | 16 | We will create a fuzz target that tests whether the tree 17 | 1. behaves like a proper set 18 | 2. does not crash 19 | 20 | And we will see how, even when fuzzcheck does not find any failures, we can analyze its 21 | output to gain insight into our code. -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial1_function.md: -------------------------------------------------------------------------------- 1 | # Test Function 2 | 3 | Currently, a test function can be of two different types: 4 | * `fn foo(input: &T)` always succeeds unless it crashes 5 | * `fn foo(input: &T) -> bool` fails if it return false 6 | 7 | For this example, we will write a function of the first kind. 8 | 9 | Since our tree acts like a set, we want to compare its behaviour to a correct set implementation, such 10 | as `BTreeSet`. The idea is to start with two empty sets of type `Tree` and `BTreeSet`, apply some 11 | operations to them, and check that the result of each operation is the same for both of them. Our tree 12 | API consists of two methods, which we can model as: 13 | ```rust ignore 14 | #[cfg(all(fuzzing, test))] 15 | mod tests { 16 | use super::*; 17 | 18 | enum Operation { 19 | Insert(T), 20 | Contains(T), 21 | } 22 | } 23 | ``` 24 | The test function takes as input an arbitrary list of operations and applies them to both structures. 25 | ```rust ignore 26 | #[cfg(all(fuzzing, test))] 27 | mod tests { 28 | use super::*; 29 | use std::collections::BTreeSet; 30 | 31 | enum Operation { 32 | Insert(T), 33 | Contains(T), 34 | } 35 | fn compare_with_btree_set(operations: &[Operation]) { 36 | let mut tree = Tree::empty(); 37 | let mut set = BTreeSet::new(); 38 | for op in operations { 39 | match op { 40 | Operation::Insert(x) => { 41 | let a = tree.insert(x.clone()); 42 | let b = set.insert(x.clone()); 43 | assert_eq!(a, b); 44 | } 45 | Operation::Contains(x) => { 46 | let a = tree.contains(x); 47 | let b = set.contains(x); 48 | assert_eq!(a, b); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | ``` 55 | If `compare_with_btree_set` never crashes, no matter the list of operations 56 | that is passed to it, then we can gain _some_ confidence that our 57 | implementation is correct. 58 | 59 | The next few sections will explain the different components that fuzzcheck 60 | needs in order to fuzz test `compare_with_btree_set`. 61 | -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial1_mutator.md: -------------------------------------------------------------------------------- 1 | # Mutator 2 | 3 | A mutator is a value whose type implements the `Mutator` trait, where 4 | `T` is any type that is `Clone + 'static`. 5 | 6 | While it is possible to hand-write mutators, the easiest way to get one 7 | is to use the `DefaultMutator` trait and procedural macro. 8 | 9 | ```rust ignore 10 | use fuzzcheck::DefaultMutator; 11 | #[derive(Clone, DefaultMutator)] 12 | enum Operation { 13 | Insert(T), 14 | Contains(T), 15 | } 16 | ``` 17 | Now, we can get a mutator of `Operation` by writing, for example: 18 | ```rust ignore 19 | fn example() { 20 | let mutator = Operation::::default_mutator(); // impl Mutator> 21 | // or for a mutator of `Vec>` 22 | let mutator = Vec::>::default_mutator(); // impl Mutator>> 23 | // ... 24 | } 25 | ``` 26 | If we have a `Mutator>`, then we can use it for any function whose input type 27 | can be obtained by borrowing `Vec`. So it is valid for: 28 | * `fn test(xs: &Vec)` 29 | * but also `fn test(xs: &[bool])` because `Vec` can be borrowed as `[bool]` 30 | 31 | -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial1_serializer.md: -------------------------------------------------------------------------------- 1 | # Serializer 2 | 3 | We also need to know how to save the generated test cases, 4 | of type `Vec>`, to the file system. 5 | 6 | The easiest way to do that is to use `serde` and its derive macros 7 | `Serialize` and `Deserialize`. 8 | 9 | ```rust ignore 10 | use serde::{Serialize, Deserialize}; 11 | 12 | #[derive(Clone, DefaultMutator, Serialize, Deserialize)] 13 | enum Operation { 14 | Insert(T), 15 | Contains(T), 16 | } 17 | ``` 18 | Then, we will provide a `fuzzcheck::SerdeSerializer` to our fuzz test so that 19 | each interesting `Vec>` is saved as a JSON file. 20 | 21 | There are other ways to serialize test cases. For example, for values of type 22 | `Vec`, you can use a `fuzzcheck::ByteSerializer`, which will simply copy 23 | the content of the vector to the file system. For values of type `String`, one 24 | can use a `fuzzcheck::StringSerializer`. 25 | -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial1_setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | First, create a new empty crate using a library template: 3 | 4 | ``` 5 | cargo new --lib example_1 6 | cd example_1 7 | ``` 8 | 9 | And set the default version of the rust compiler for this crate to nightly; 10 | ``` 11 | rustup override set nightly 12 | ``` 13 | 14 | In `Cargo.toml`, add the following dependencies: 15 | ```toml 16 | [dependencies] 17 | serde = { version = "1.0", features = ["derive"] } 18 | 19 | [dev-dependencies] 20 | fuzzcheck = "0.12" 21 | ``` 22 | 23 | At the top of `src/lib.rs` add the following line: 24 | ```rust ignore 25 | #![cfg_attr(fuzzing, feature(coverage_attribute))] 26 | ``` 27 | This is a rust-nightly feature that fuzzcheck’s procedural macros use. 28 | 29 | Now let's write some code to test. In `src/lib.rs`, we will add a simple implementation of a binary search tree. 30 | 31 | ```rust ignore 32 | pub struct Node { 33 | key: T, 34 | left: Box>, 35 | right: Box>, 36 | } 37 | pub enum Tree { 38 | Node(Node), 39 | Empty, 40 | } 41 | ``` 42 | 43 | And we implement the methods `empty`, `insert`, and `contains`. 44 | 45 | ```rust ignore 46 | use std::cmp::{self, Ordering}; 47 | 48 | impl Tree { 49 | /// Creates a new empty tree 50 | pub fn empty() -> Box { 51 | Box::new(Tree::Empty) 52 | } 53 | /// Returns true iff the tree contains the given value. 54 | pub fn contains(&self, k: &T) -> bool { 55 | match self { 56 | Tree::Node(Node { key, left, right }) => match k.cmp(key) { 57 | Ordering::Less => left.contains(k), 58 | Ordering::Equal => true, 59 | Ordering::Greater => right.contains(k), 60 | }, 61 | Tree::Empty => false, 62 | } 63 | } 64 | /** 65 | Adds a value to the set. 66 | 67 | If the set did not have this value present, true is returned. 68 | If the set did have this value present, false is returned, and the tree is left unmodified. 69 | */ 70 | pub fn insert(self: &mut Box, new_key: T) -> bool { 71 | match self.as_mut() { 72 | Tree::Node(Node { 73 | key, 74 | left, 75 | right, 76 | }) => match new_key.cmp(key) { 77 | Ordering::Less => { 78 | left.insert(new_key) 79 | } 80 | Ordering::Equal => false, 81 | Ordering::Greater => { 82 | right.insert(new_key) 83 | } 84 | }, 85 | Tree::Empty => { 86 | **self = Tree::Node(Node { 87 | key: new_key, 88 | left: Self::empty(), 89 | right: Self::empty(), 90 | }); 91 | true 92 | } 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | We already have enough to start fuzz-testing our tree. To do so, we first add a `tests` module 99 | within the same `src/lib.rs` file, which we conditionally enable only for test builds with `#[cfg(test)]`. 100 | ```rust ignore 101 | #[cfg(test)] 102 | mod tests { 103 | use super::*; 104 | // Note that fuzzcheck is accessible here because it is a dev-dependency. 105 | } 106 | ``` -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial2.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2: pulldown-cmark 2 | 3 | In this tutorial, we clone the popular `pulldown-cmark` crate and find real bugs in it. 4 | 5 | We will start by creating a **grammar-based mutator**. From the generated syntax trees, 6 | we generate strings and then ask `pulldown-cmark` to parse those strings. 7 | 8 | To get started, go to a new folder and clone the `pulldown-cmark` repository: 9 | ``` 10 | git clone https://github.com/raphlinus/pulldown-cmark.git 11 | ``` 12 | and then checkout the repository to its state from 18 September 2021: 13 | ``` 14 | cd pulldown-cmark 15 | git checkout -b fuzz 5088b21d09ef94b424c4d852db7648c9c94fb630 16 | ``` 17 | 18 | > Note that grammar-based mutators are only available when the `grammar_mutator` feature is enabled. Creating grammars from regular expressions is only possible when the `regex_grammar` feature is enabled. Both of these featuress are enabled by default. -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial2_function.md: -------------------------------------------------------------------------------- 1 | # Test Function 2 | 3 | The main function declared by `pulldown-cmark` is called `push_html`. It takes a markdown string as input 4 | and generates an HTML string from it. Our test function will only verify that `push_html` never crashes. 5 | We write it inside `src/lib.rs`. 6 | 7 | ```rust ignore 8 | #[cfg(all(fuzzing, test))] 9 | mod tests { 10 | use crate::{html, Parser}; 11 | fn push_html_does_not_crash(md_string: &str) { 12 | let parser = Parser::new(md_string); 13 | let mut html_output = String::new(); 14 | html::push_html(&mut html_output, parser); 15 | } 16 | } 17 | ``` 18 | 19 | The most important step afterwards is to create a good mutator for generating 20 | markdown strings. This is discussed in the next section. 21 | -------------------------------------------------------------------------------- /fuzzcheck_book/src/tutorial2_fuzzing.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | We know have everything needed to fuzz-test `pulldown-cmark`. 4 | 5 | Inside the `tests` module in `src/lib.rs`, add the following `#[test]` function: 6 | 7 | ```rust ignore 8 | use fuzzcheck::mutators::grammar; 9 | 10 | #[test] 11 | fn fuzz() { 12 | let mutator = grammar::grammar_based_ast_mutator(markdown()).with_string(); 13 | let result = fuzzcheck::fuzz_test(|(_, string): &(AST, String)| { 14 | push_html_does_not_crash(string) 15 | }) 16 | .mutator(mutator) 17 | .serde_serializer() 18 | .default_sensor_and_pool() 19 | .arguments_from_cargo_fuzzcheck() 20 | .launch(); 21 | assert!(!result.found_test_failure); 22 | } 23 | ``` 24 | 25 | We launch the fuzz test using: 26 | ``` 27 | cargo fuzzcheck tests::fuzz 28 | ``` 29 | 30 | Updates about the fuzzing progress will be printed, such as: 31 | ``` 32 | 7s + 68782 simplest_cov(292 cov: 1937/2617 cplx: 31.75) diverse_cov_20(1904) diverse_cov_1(1514) max_each_cov_hits(501 sum: 8601361) max_total_cov_hits(5799778) failures(3) iter/s 10071 33 | ``` 34 | After about a minute, fuzzcheck should have found at least two distinct test failures: 35 | 36 | They are stored in the `fuzz//corpus/test_failures/` folder with the following structure: 37 | ```yaml 38 | - tests::fuzz/corpus/test_failures 39 | # each folder here corresponds to a distinct test failure (e.g. different panic locations) 40 | - 5460776198281478584 41 | # each folder here corresponds to a test case complexity 42 | - 26.0000 43 | # this is an actual failing test case, of complexity 26.0 44 | - 3d3294fac0c0e5e4.json 45 | - c780161f12e87030.json 46 | - 54.0000 47 | - etc. 48 | ``` 49 | The `.json` file contain both the serialized syntax trees and the markdown string. For example, 50 | `3d3294fac0c0e5e4.json` is: 51 | ```json 52 | [ 53 | ">\t`. However, we can still use two approaches to generate 7 | strings. The first approach is to use a `Vec` and then obtain a string from the 8 | slice of bytes using `String::from_utf8_lossy(..)`. The second is to use 9 | *grammar-based mutators* to generate syntax trees and their associated strings. 10 | The shape of the generated trees are described by a *grammar*. 11 | 12 | For example, we can write: 13 | ```rust ignore 14 | use fuzzcheck::mutators::grammar; 15 | 16 | let rule1 = grammar::regex("[a-z](0|ad)[!?]{1,10}") 17 | ``` 18 | which is a grammar that matches/generates the following strings: 19 | ``` 20 | gad! 21 | b0???! 22 | w0? 23 | ``` 24 | but not the following: 25 | ``` 26 | yad (missing the last rule: a repetition of ! and ?) 27 | Gad! (G is not in 'a' ..= 'z') 28 | b0ad?! (what follows the first letter can be either 0 or ad , but not both) 29 | ``` 30 | 31 | We can also write recursive grammars as follows: 32 | 33 | ```rust ignore 34 | use fuzzcheck::mutators::grammar::{regex, literal, alternation, concatenation, recursive, recurse}; 35 | 36 | let grammar = recursive(|g| { 37 | alternation([ 38 | regex("[a-zA-Z]"), 39 | concatenation([ 40 | literal('('), 41 | recurse(g), 42 | literal(')') 43 | ]) 44 | ]) 45 | ); 46 | ``` 47 | which matches the following strings: 48 | ``` 49 | F 50 | s 51 | (a) 52 | ((((H)))) 53 | ((((((((((((((((((((((((((((((((((((((((n)))))))))))))))))))))))))))))))))))))))) 54 | ``` 55 | but not those: 56 | ``` 57 | (a (mismatched parentheses) 58 | ((())) (no letter inside the parentheses) 59 | ``` 60 | 61 | It can be useful to build a grammar piece by piece instead of within a single declaration. 62 | 63 | ```rust ignore 64 | use fuzzcheck::mutators::grammar::{regex, literal, alternation, concatenation, repetition, recursive, recurse}; 65 | use fuzzcheck::mutators::grammar::Grammar; 66 | use std::rc::Rc; 67 | use std::rc::Weak; 68 | 69 | fn whitespace() -> Rc { 70 | regex("[ \t]+") 71 | } 72 | fn simple_short_ascii_word() -> Rc { 73 | regex("[a-zA-Z]{1,10}") 74 | } 75 | fn reference() -> Rc { 76 | concatenation([ 77 | literal('['), 78 | simple_short_ascii_word(), 79 | literal(']') 80 | ]) 81 | } 82 | fn recursing_rule(whole: &Weak) -> Rc { 83 | concatenation([ 84 | literal('<'), 85 | recurse!(whole), 86 | literal('>') 87 | ]) 88 | } 89 | fn final_grammar() -> Rc { 90 | recursive!(|whole_grammar| { 91 | alternation([ 92 | simple_short_ascii_word(), 93 | reference(), 94 | recursing_rule(whole_grammar) 95 | ]) 96 | }) 97 | } 98 | ``` 99 | The above grammar matches the following strings: 100 | ``` 101 | hello 102 | [world] 103 | 104 | <<<[bing]>>> 105 | ``` 106 | 107 | ## Creating a String mutator from a grammar 108 | 109 | Once you have a grammar, of type `Rc`, you can obtain 110 | a mutator that generates syntax trees matching the grammar as well as the 111 | associated string: 112 | 113 | ```rust ignore 114 | use fuzzcheck::mutators::grammar::grammar_based_ast_mutator; 115 | let grammar = regex(/* .. */); 116 | let mutator = grammar_based_ast_mutator(grammar) // : impl Mutator 117 | .with_string(); // : impl Mutator<(AST, String)> 118 | ``` 119 | 120 | Unfortunately, the argument of the test function will need to be `&(AST, String)` instead of 121 | `&str`. (A previous version of fuzzcheck also had a grammar-based *string* mutator, 122 | which implemented `Mutator`. However, I have removed it from the latest version 123 | while I fix some bugs with it). 124 | 125 | The test function given to fuzzcheck will then need to have the following signature: 126 | ```rust ignore 127 | fn test((_, x): &(AST, String)) { 128 | // ... 129 | } 130 | // instead of 131 | fn test(x: &str) { } 132 | ``` 133 | 134 | In the next section, we build a grammar such that it generates interesting markdown strings. 135 | -------------------------------------------------------------------------------- /fuzzcheck_common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzcheck_common" 3 | version = "0.13.0" 4 | authors = ["Loïc Lecrenier "] 5 | edition = "2021" 6 | description = "Common components for both cargo-fuzzcheck and fuzzcheck" 7 | homepage = "https://fuzzcheck.neocities.org" 8 | license = "MIT" 9 | repository = "https://github.com/loiclec/fuzzcheck-rs" 10 | keywords = ["property", "fuzzer", "fuzzing", "test", "testing"] 11 | categories = ["development-tools::testing"] 12 | 13 | [dependencies] 14 | getopts = "0.2.21" 15 | 16 | [lib] 17 | name = "fuzzcheck_common" -------------------------------------------------------------------------------- /fuzzcheck_common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(coverage_attribute)] 2 | 3 | pub mod arg; 4 | 5 | #[derive(Clone, Copy, Default)] 6 | pub struct FuzzerStats { 7 | pub total_number_of_runs: usize, 8 | pub number_of_runs_since_last_reset_time: usize, 9 | pub exec_per_s: usize, 10 | } 11 | 12 | #[derive(Clone, Copy)] 13 | pub enum FuzzerEvent { 14 | Start, 15 | Stop, 16 | End, 17 | CrashNoInput, 18 | Pulse, 19 | Done, 20 | Replace(usize, usize), 21 | DidReadCorpus, 22 | CaughtSignal(i32), 23 | TestFailure, 24 | None, 25 | } 26 | -------------------------------------------------------------------------------- /fuzzcheck_mutators_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzcheck_mutators_derive" 3 | version = "0.13.0" 4 | authors = ["Loïc Lecrenier "] 5 | edition = "2021" 6 | description = "Procedural macros to generate fuzzcheck mutators" 7 | homepage = "https://fuzzcheck.neocities.org" 8 | repository = "https://github.com/loiclec/fuzzcheck-rs" 9 | license = "MIT" 10 | keywords = ["property", "fuzzer", "fuzzing", "test", "testing"] 11 | categories = ["development-tools::testing"] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.87" 18 | quote = "1.0.37" 19 | syn = "1.0.109" 20 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | imports_granularity = "Module" 3 | group_imports = "StdExternalCrate" 4 | use_field_init_shorthand = true -------------------------------------------------------------------------------- /usage_tests/basic_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic_crate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [target.'cfg(fuzzing)'.dev-dependencies] 9 | fuzzcheck = { path = "../../fuzzcheck" } 10 | serde = { version = "1.0", features = ["derive"] } 11 | -------------------------------------------------------------------------------- /usage_tests/basic_crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(fuzzing, feature(coverage_attribute))] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[cfg_attr(fuzzing, derive(fuzzcheck::DefaultMutator))] 5 | #[derive(Clone, Serialize, Deserialize)] 6 | struct SampleStruct { 7 | x: T, 8 | y: U, 9 | } 10 | 11 | #[cfg_attr(fuzzing, derive(fuzzcheck::DefaultMutator))] 12 | #[derive(Clone, Serialize, Deserialize)] 13 | enum SampleEnum { 14 | A(u16), 15 | B, 16 | C { x: bool, y: bool }, 17 | } 18 | 19 | fn should_not_crash(xs: &[SampleStruct]) { 20 | if xs.len() == 3 21 | && xs[0].x == 100 22 | && matches!(xs[0].y, SampleEnum::C { x: false, y: true }) 23 | && xs[1].x == 55 24 | && matches!(xs[1].y, SampleEnum::C { x: true, y: false }) 25 | && xs[2].x == 87 26 | && matches!(xs[2].y, SampleEnum::C { x: false, y: false }) 27 | && xs[3].x == 24 28 | && matches!(xs[3].y, SampleEnum::C { x: true, y: true }) 29 | { 30 | panic!() 31 | } 32 | } 33 | 34 | // fuzz tests reside along your other tests and have the #[test] attribute 35 | #[cfg(all(fuzzing, test))] 36 | mod tests { 37 | #[test] 38 | fn fuzz() { 39 | let result = fuzzcheck::fuzz_test(super::should_not_crash) // the test function to fuzz 40 | .default_mutator() // the mutator to generate values of &[SampleStruct] 41 | .serde_serializer() // save the test cases to the file system using serde 42 | .default_sensor_and_pool() // gather observations using the default sensor (i.e. recording code coverage) 43 | .arguments_from_cargo_fuzzcheck() // take arguments from the cargo-fuzzcheck command line tool 44 | .stop_after_first_test_failure(true) // stop the fuzzer as soon as a test failure is found 45 | .launch(); 46 | assert!(result.found_test_failure); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /usage_tests/basic_example.sh: -------------------------------------------------------------------------------- 1 | cargo install --path cargo-fuzzcheck --force && \ 2 | cd usage_tests/basic_crate && \ 3 | cargo fuzzcheck tests::fuzz && \ 4 | test $(cat fuzz/tests::fuzz/artifacts/*.json) = '[{"x":100,"y":{"C":{"x":false,"y":true}}},{"x":55,"y":{"C":{"x":true,"y":false}}},{"x":87,"y":{"C":{"x":false,"y":false}}}]' && \ 5 | rm -r fuzz 6 | --------------------------------------------------------------------------------