Skip to content

Commit

Permalink
Merge pull request #149 from Tpt/fuzzer
Browse files Browse the repository at this point in the history
Makes fuzzer support more recovery edge cases
  • Loading branch information
arkpar authored Oct 18, 2022
2 parents fac2d1a + 927c724 commit f418284
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 256 deletions.
177 changes: 66 additions & 111 deletions fuzz/fuzz_targets/refcounted_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
use parity_db_fuzz::*;
use std::{cmp::min, collections::HashMap, path::Path};

const NUMBER_OF_POSSIBLE_KEYS: usize = 256;
use std::cmp::min;

#[derive(Arbitrary, Debug, Clone, Copy)]
enum Operation {
Expand All @@ -19,115 +17,70 @@ enum Operation {
Reference(u8),
}

#[derive(Clone, Debug)]
struct Layer {
// The number of references per key (or None if the key never existed in the database)
counts: [Option<usize>; NUMBER_OF_POSSIBLE_KEYS],
written: bool,
}

type Model = Vec<Layer>;

struct Simulator;

impl DbSimulator for Simulator {
type ValueType = usize;
type Operation = Operation;
type Model = Model;

fn build_options(config: &Config, path: &Path) -> parity_db::Options {
parity_db::Options {
path: path.to_owned(),
columns: vec![parity_db::ColumnOptions {
ref_counted: true,
preimage: true,
compression: config.compression.into(),
btree_index: config.btree_index,
..parity_db::ColumnOptions::default()
}],
sync_wal: true,
sync_data: true,
stats: false,
salt: None,
compression_threshold: HashMap::new(),
always_flush: true,
with_background_thread: false,
fn build_column_options(config: &Config) -> parity_db::ColumnOptions {
parity_db::ColumnOptions {
ref_counted: true,
preimage: true,
compression: config.compression.into(),
btree_index: config.btree_index,
..parity_db::ColumnOptions::default()
}
}

fn apply_operations_on_model<'a>(
operations: impl IntoIterator<Item = &'a Operation>,
model: &mut Model,
fn apply_operations_on_values<'a>(
operations: impl IntoIterator<Item = &'a Self::Operation>,
values: &mut [Option<usize>; NUMBER_OF_POSSIBLE_KEYS],
) {
let mut counts = model.last().map_or([None; NUMBER_OF_POSSIBLE_KEYS], |l| l.counts);
for operation in operations {
match *operation {
Operation::Set(k) => *counts[usize::from(k)].get_or_insert(0) += 1,
Operation::Set(k) => *values[usize::from(k)].get_or_insert(0) += 1,
Operation::Dereference(k) =>
if counts[usize::from(k)].unwrap_or(0) > 0 {
*counts[usize::from(k)].get_or_insert(0) -= 1;
if values[usize::from(k)].unwrap_or(0) > 0 {
*values[usize::from(k)].get_or_insert(0) -= 1;
},
Operation::Reference(k) =>
if counts[usize::from(k)].unwrap_or(0) > 0 {
*counts[usize::from(k)].get_or_insert(0) += 1;
if values[usize::from(k)].unwrap_or(0) > 0 {
*values[usize::from(k)].get_or_insert(0) += 1;
},
}
}

model.push(Layer { counts, written: false });
}

fn write_first_layer_to_disk(model: &mut Model) {
for layer in model {
if !layer.written {
layer.written = true;
break
}
fn is_layer_state_compatible_with_disk_state(
layer_values: &[Option<usize>; NUMBER_OF_POSSIBLE_KEYS],
state: &[(u8, u8)],
) -> bool {
if !state.iter().all(|(k, v)| k == v) {
return false // keys and values should be equal
}
}

fn attempt_to_reset_model_to_disk_state(model: &Model, state: &[(u8, u8)]) -> Option<Model> {
let expected = {
let mut is_present = [false; NUMBER_OF_POSSIBLE_KEYS];
for (k, _) in state {
is_present[usize::from(*k)] = true;
}
is_present
};

let mut candidates = Vec::new();
for layer in model.iter().rev() {
if !layer.written {
continue
layer_values.iter().enumerate().all(|(i, c)| {
let key = i as u8;
match c {
None => state.iter().all(|(k, _)| *k != key),
Some(0) => true,
Some(_) => state.iter().any(|(k, _)| *k == key),
}
})
}

// Is it equal to current state?
let is_equal = expected.iter().enumerate().all(|(k, is_present)| {
if *is_present {
layer.counts[k].is_some()
} else {
layer.counts[k].unwrap_or(0) == 0
}
});
if is_equal {
// We found a correct last layer
candidates.push(layer);
}
}
if candidates.is_empty() {
return if state.is_empty() { Some(Vec::new()) } else { None }
}

fn build_best_layer_for_recovery(layers: &[&Layer<usize>]) -> Layer<usize> {
// if we are multiple candidates, we are unsure. We pick the lower count per candidate
let mut new_state_safe_counts = [None; NUMBER_OF_POSSIBLE_KEYS];
for layer in candidates {
for layer in layers {
for i in u8::MIN..=u8::MAX {
if let Some(c) = layer.counts[usize::from(i)] {
if let Some(c) = layer.values[usize::from(i)] {
new_state_safe_counts[usize::from(i)] =
Some(min(c, new_state_safe_counts[usize::from(i)].unwrap_or(usize::MAX)));
}
}
}
Some(vec![Layer { counts: new_state_safe_counts, written: true }])
Layer { values: new_state_safe_counts, written: true }
}

fn map_operation(operation: &Operation) -> parity_db::Operation<Vec<u8>, Vec<u8>> {
Expand All @@ -138,44 +91,46 @@ impl DbSimulator for Simulator {
}
}

fn model_required_content(model: &Model) -> Vec<(Vec<u8>, Vec<u8>)> {
if let Some(last) = model.last() {
last.counts
.iter()
.enumerate()
.filter_map(|(k, count)| {
if count.unwrap_or(0) > 0 {
Some((vec![k as u8], vec![k as u8]))
} else {
None
}
})
.collect()
} else {
Vec::new()
}
fn layer_required_content(
values: &[Option<usize>; NUMBER_OF_POSSIBLE_KEYS],
) -> Vec<(Vec<u8>, Vec<u8>)> {
values
.iter()
.enumerate()
.filter_map(|(k, count)| {
if count.unwrap_or(0) > 0 {
Some((vec![k as u8], vec![k as u8]))
} else {
None
}
})
.collect()
}

fn model_optional_content(model: &Model) -> Vec<(Vec<u8>, Vec<u8>)> {
if let Some(last) = model.last() {
last.counts
.iter()
.enumerate()
.filter_map(|(k, count)| {
fn layer_optional_content(
values: &[Option<usize>; NUMBER_OF_POSSIBLE_KEYS],
) -> Vec<(Vec<u8>, Vec<u8>)> {
values
.iter()
.enumerate()
.filter_map(
|(k, count)| {
if count.is_some() {
Some((vec![k as u8], vec![k as u8]))
} else {
None
}
})
.collect()
} else {
Vec::new()
}
},
)
.collect()
}

fn model_removed_content(_model: &Model) -> Vec<Vec<u8>> {
Vec::new()
fn layer_removed_content(values: &[Option<usize>; NUMBER_OF_POSSIBLE_KEYS]) -> Vec<Vec<u8>> {
values
.iter()
.enumerate()
.filter_map(|(k, count)| if count.is_some() { None } else { Some(vec![k as u8]) })
.collect()
}
}

Expand Down
Loading

0 comments on commit f418284

Please sign in to comment.