Skip to content

Added support for Arbitrary - #163

Merged
akubera merged 5 commits into
akubera:trunkfrom
LucaCappelletti94:arbitrary
Sep 21, 2026
Merged

akubera merged 5 commits into
akubera:trunkfrom
LucaCappelletti94:arbitrary

Conversation

@LucaCappelletti94

@LucaCappelletti94 LucaCappelletti94 commented Feb 4, 2026 •

Copy link
Copy Markdown
Contributor

This PR adds support for the arbitrary crate by implementing the Arbitrary trait for BigDecimal. This enables using BigDecimal with fuzzing tools like cargo-fuzz and libfuzzer, and more importantly allows to use the Arbitrary derive in third party structs which include this struct as a field.

A new optional feature fuzz has been added to Cargo.toml:

[features]
fuzz = ["std", "arbitrary", "num-bigint/arbitrary"]

Note: The feature is named fuzz (not arbitrary) to avoid a name collision with the dependency, which is required for compatibility with older Cargo versions (pre-1.60). The feature requires std because the arbitrary crate itself does not support no_std

Implementation Details

To prevent memory allocation failures during fuzzing, the implementation limits the generated scale to ±1,000,000. Without this limit, extremely large scale values could cause the BigDecimal to attempt allocating massive amounts of memory when converted to strings or performing certain operations.

  • New module: src/impl_arbitrary.rs implementing Arbitrary<'a> for BigDecimal
  • Implements all three trait methods:
    • arbitrary() - Generate a random BigDecimal from unstructured data
    • arbitrary_take_rest() - Consume all remaining data to generate a BigDecimal
    • size_hint() - Provide size hints for the fuzzer

Usage

Enable the feature in your Cargo.toml:

[dependencies]
bigdecimal = { version = "0.4", features = ["fuzz"] }

Example fuzz target:

#![no_main]
use libfuzzer_sys::fuzz_target;
use bigdecimal::BigDecimal;

fuzz_target!(|data: BigDecimal| {
    // Your fuzzing logic here
    let _ = data.to_string();
    let _ = data.sqrt();
});

Closes #122

@LucaCappelletti94

Copy link
Copy Markdown
Contributor Author

For added context, this PR is needed to allow for adding Arbitrary derives in sqlparser, as shown here: apache/datafusion-sqlparser-rs#2193

Comment thread src/impl_arbitrary.rs

fn arbitrary_take_rest(mut u: Unstructured<'a>) -> ArbitraryResult<Self> {
// Use arbitrary for scale first (fixed size), then take rest for BigInt
let scale = i64::arbitrary(&mut u)? % SCALE_LIMIT;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the arbitrary scale be symmetric about zero, or should it be biased positive which indicates a positive number of decimal points which is the more likely scenario for using these? Or does it not matter for Arbitrary because every valid state should be representable? And do we care that 0 is twice as likely as other numbers (1 out of a million vs 1 out of 2 million)?

I'm not familiar with Arbitrary so I might be overthinking this. I don't know what the fuzzers are looking for.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also does order matter? arbitrary makes int first then scale, arbitrary_take_rest does scale then int. I feel like a well behaved impl would do those in the same order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding #163 (comment)

I believe it should generate all possible valid states.

If implemented ideally, an Arbitrary impl of a downhill struct should take care of the distribution of the values depending of what values may be expected by the input stream of the downhill code.

Using the derive version should give a "reasonable baseline", and for instance it does not guarantee any self-consistent struct being generated by composition (which in certain harnesses it is the point as it forces the code to deal with any conceivable input it accepts). This would include a range-like struct that has start > end, even when all public methods to construct this struct have a Result that checks that start <= end. In such cases if the dev wants a self-consistent struct they need to write their own impl and avoid the derive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding #163 (comment)

Consistency is better, I will adjust that and also fix a possible regression corner case that I noticed was uncovered in the test. It is not a problem at this time, but better safe than sorry.

@akubera

akubera commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Ok approved. Thanks a lot. Sorry I put this down for so many months.

@akubera
akubera merged commit 0d86275 into akubera:trunk Sep 21, 2026
18 checks passed
@LucaCappelletti94

Copy link
Copy Markdown
Contributor Author

Ok approved. Thanks a lot. Sorry I put this down for so many months.

No worries, it'll be useful for fuzzing downhill!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

impl Arbitrary for BigDecimal

2 participants