Skip to content

Commit

Permalink
Merge pull request #383 from c410-f3r/fuzz
Browse files Browse the repository at this point in the history
Add fuzz targets to exercize checked functions
  • Loading branch information
paupino authored Jun 7, 2021
2 parents 16b41cc + ab3bec8 commit 23750d8
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 12 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,33 @@ jobs:
command: clippy
args: --workspace --all-features

fuzz:
name: Fuzz
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
override: true
profile: minimal
toolchain: nightly

- uses: davidB/rust-cargo-make@v1

- uses: actions-rs/install@v0.1
with:
crate: cargo-fuzz
use-tool-cache: true

- name: Run fuzz tests
uses: actions-rs/cargo@v1
with:
command: make
args: fuzz

minimum_rust_version:
name: Check minimum rust version
runs-on: ubuntu-latest
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ Cargo.lock
.fuzz/

# IDE support
.idea/
.idea/

# Fuzz
artifacts
corpus
target
21 changes: 21 additions & 0 deletions Makefile.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[config]
default_to_workspace = false

[env]
FUZZ_RUNS = 10000000

[tasks.build]
command = "cargo"
args = ["build"]
Expand Down Expand Up @@ -71,6 +74,24 @@ install_crate = "rustfmt"
command = "cargo"
args = ["fmt", "--", "--emit=files"]

[tasks.fuzz]
dependencies = [
"fuzz-arithmetic",
"fuzz-constructors"
]

[tasks.fuzz-arithmetic]
toolchain = "nightly"
install_crate = "cargo-fuzz"
command = "cargo"
args = ["fuzz", "run", "arithmetic", "--", "-runs=${FUZZ_RUNS}"]

[tasks.fuzz-constructors]
toolchain = "nightly"
install_crate = "cargo-fuzz"
command = "cargo"
args = ["fuzz", "run", "constructors", "--", "-runs=${FUZZ_RUNS}"]

[tasks.outdated]
install_crate = "cargo-outdated"
command = "cargo"
Expand Down
29 changes: 29 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[[bin]]
doc = false
name = "arithmetic"
path = "fuzz_targets/arithmetic.rs"
test = false

[[bin]]
doc = false
name = "constructors"
path = "fuzz_targets/constructors.rs"
test = false

[dependencies]
arbitrary = { features = ["derive"], version = "1.0" }
libfuzzer-sys = "0.4"
rust_decimal = { features = ["maths", "rust-fuzz"], path = ".." }

[package]
authors = ["Automatically generated"]
edition = "2018"
name = "rust-decimal-fuzz"
publish = false
version = "0.0.0"

[package.metadata]
cargo-fuzz = true

[workspace]
members = ["."]
31 changes: 31 additions & 0 deletions fuzz/fuzz_targets/arithmetic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#![no_main]

use rust_decimal::{Decimal, MathematicalOps};

#[derive(Debug, arbitrary::Arbitrary)]
struct Data {
a: Decimal,
b: Decimal,
exp_f64: f64,
exp_i64: i64,
exp_u64: u64,
}

libfuzzer_sys::fuzz_target!(|data: Data| {
let fun = || {
let _ = data.a.checked_add(data.b)?;
let _ = data.a.checked_div(data.b)?;
//let _ = data.a.checked_exp_with_tolerance(data.b)?;
//let _ = data.a.checked_exp()?;
//let _ = data.a.checked_mul(data.b)?;
//let _ = data.a.checked_norm_pdf()?;
//let _ = data.a.checked_powd(data.b)?;
//let _ = data.a.checked_powf(data.exp_f64)?;
//let _ = data.a.checked_powi(data.exp_i64)?;
//let _ = data.a.checked_powu(data.exp_u64)?;
//let _ = data.a.checked_sub(data.b)?;

Some(())
};
let _ = fun();
});
22 changes: 22 additions & 0 deletions fuzz/fuzz_targets/constructors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#![no_main]

use rust_decimal::Decimal;

#[derive(Debug, arbitrary::Arbitrary)]
struct Data<'a> {
from_scientific_value: &'a str,

try_from_i128_with_scale_num: i128,
try_from_i128_with_scale_scale: u32,

try_new_num: i64,
try_new_scale: u32,
}

libfuzzer_sys::fuzz_target!(|data: Data<'_>| {
let _ = Decimal::from_scientific(data.from_scientific_value);

let _ = Decimal::try_from_i128_with_scale(data.try_from_i128_with_scale_num, data.try_from_i128_with_scale_scale);

let _ = Decimal::try_new(data.try_new_num, data.try_new_scale);
});
34 changes: 24 additions & 10 deletions src/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,28 +160,42 @@ impl Decimal {
/// ```
#[must_use]
pub fn new(num: i64, scale: u32) -> Decimal {
match Self::try_new(num, scale) {
Err(e) => panic!("{}", e),
Ok(d) => d,
}
}

/// Checked version of `Decimal::new`. Will return `Err` instead of panicking at run-time.
///
/// # Example
///
/// ```rust
/// use rust_decimal::Decimal;
///
/// let max = Decimal::try_new(i64::MAX, u32::MAX);
/// assert!(max.is_err());
/// ```
pub const fn try_new(num: i64, scale: u32) -> crate::Result<Decimal> {
if scale > MAX_PRECISION {
panic!(
"Scale exceeds the maximum precision allowed: {} > {}",
scale, MAX_PRECISION
);
return Err(Error::ScaleExceedsMaximumPrecision(scale));
}
let flags: u32 = scale << SCALE_SHIFT;
if num < 0 {
let pos_num = num.wrapping_neg() as u64;
return Decimal {
return Ok(Decimal {
flags: flags | SIGN_MASK,
hi: 0,
lo: (pos_num & U32_MASK) as u32,
mid: ((pos_num >> 32) & U32_MASK) as u32,
};
});
}
Decimal {
Ok(Decimal {
flags,
hi: 0,
lo: (num as u64 & U32_MASK) as u32,
mid: ((num as u64 >> 32) & U32_MASK) as u32,
}
})
}

/// Creates a `Decimal` using a 128 bit signed `m` representation and corresponding `e` scale.
Expand Down Expand Up @@ -211,7 +225,7 @@ impl Decimal {
}
}

/// Checked version of `from_i128_with_scale`. Will return `Err` instead
/// Checked version of `Decimal::from_i128_with_scale`. Will return `Err` instead
/// of panicking at run-time.
///
/// # Example
Expand All @@ -222,7 +236,7 @@ impl Decimal {
/// let max = Decimal::try_from_i128_with_scale(i128::MAX, u32::MAX);
/// assert!(max.is_err());
/// ```
pub fn try_from_i128_with_scale(num: i128, scale: u32) -> crate::Result<Decimal> {
pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> crate::Result<Decimal> {
if scale > MAX_PRECISION {
return Err(Error::ScaleExceedsMaximumPrecision(scale));
}
Expand Down
6 changes: 5 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ impl fmt::Display for Error {
write!(f, "Number less than minimum value that can be represented.")
}
Self::ScaleExceedsMaximumPrecision(ref scale) => {
write!(f, "Scale exceeds maximum precision: {} > {}", scale, MAX_PRECISION)
write!(
f,
"Scale exceeds the maximum precision allowed: {} > {}",
scale, MAX_PRECISION
)
}
}
}
Expand Down

0 comments on commit 23750d8

Please sign in to comment.