arbitrary icon indicating copy to clipboard operation
arbitrary copied to clipboard

Add support for no_std compilation.

Open tmroeder opened this issue 4 years ago • 11 comments

This adds a "std" feature that enables support for compilation with std. Without this feature, the library becomes compatible with #![no_std] crates.

By default, the library compiles with support for std types, but this can be disabled with --no-default-features.

tmroeder avatar Mar 11 '21 01:03 tmroeder

Note that this should address issue #38 as well.

tmroeder avatar Mar 15 '21 16:03 tmroeder

See https://github.com/rust-fuzz/arbitrary/issues/62#issuecomment-736041629. What are you using arbitrary with? libfuzzer? If so, then you can use std. Even for primarily no-std crates, it is reasonable for the fuzz targets to use std (and they don't force the main crate itself to otherwise use std).

Would like to hear more about whether your use case actually requires arbitrary itself to be no-std.

fitzgen avatar Mar 15 '21 18:03 fitzgen

See #62 (comment). What are you using arbitrary with? libfuzzer? If so, then you can use std. Even for primarily no-std crates, it is reasonable for the fuzz targets to use std (and they don't force the main crate itself to otherwise use std).

Would like to hear more about whether your use case actually requires arbitrary itself to be no-std.

I started out that way in my use case.

The case is a large no_std crate where I want to derive Arbitrary on structs that are defined in that crate. Without no_std support, adding derive(Arbitrary) to a type in a no_std crate fails to compile. The exact use case is in structure-aware fuzzing: you want to fuzz an API with a function that takes as a parameter one of the structs defined in the crate. So, in the fuzz target, you define something like

#[derive(Arbitrary, Debug)]
enum Methods {
    FirstMethod {
        param1: usize,
        param2: MyStruct,
    },
   // ... other methods
}

where MyStruct is defined in the no_std crate. This requires MyStruct to satisfy the Arbitrary trait. My (perhaps incorrect) understanding is that this means that the no_std crate needs to have derive(Arbitrary) on MyStruct.

I realize that there's a workaround: use conditional compilation to turn on std in the crate when fuzzing, and only derive Arbitrary in that context. The value I see in this change is simplifying the task of adding fuzzing to these kinds of crates at the cost of some increased complexity in the arbitrary crate.

tmroeder avatar Mar 15 '21 19:03 tmroeder

I realize that there's a workaround: use conditional compilation to turn on std in the crate when fuzzing, and only derive Arbitrary in that context. The value I see in this change is simplifying the task of adding fuzzing to these kinds of crates at the cost of some increased complexity in the arbitrary crate.

Yes, this is what I was about to suggest, and I do see the value in not requiring users to add a conditional std mode.

However, the benefit of this approach, over making arbitrary itself no-std compatible, is that it doesn't artificially constrain arbitrary's implementation, and we can continue to use std when implementing things inside arbitrary.

fitzgen avatar Mar 15 '21 20:03 fitzgen

I want to fuzz an embedded target, where no_std support comes in handy... ;)

bitwave avatar Aug 19 '21 11:08 bitwave

I realize that there's a workaround: use conditional compilation to turn on std in the crate when fuzzing, and only derive Arbitrary in that context. The value I see in this change is simplifying the task of adding fuzzing to these kinds of crates at the cost of some increased complexity in the arbitrary crate.

This isn't as much a workaround as it is the recommended way to do things, IMO. Arbitrary may need to use std types in its implementation so it's tricky to constrain ourselves to no_std. However, libFuzzer fuzzing always occurs in a std context, so you can use --features std as necessary.

We could potentially still have a std feature (on by default) which when disabled may affect the existence of Arbitrary impls for some no_std types. But I'm not sure if this is worth it.

Manishearth avatar Aug 19 '21 15:08 Manishearth

Yep, you can use no_std crates from std crates, so arbitrary/libfuzzer-sys not being no_std doesn't constrain your no_std crate's implementation. At most you may have to add a std cargo feature when implementing Arbitrary for types inside your no_std crate, but this is something that cargo makes easy.

Re-upping my earlier comment from that other thread once again:

I personally can't think of any solid use case for no-std. Even if the system under test is no-std, the tests/fuzz targets don't have to be, and if your target has enough for libfuzzer, it should have enough for std. [...] Mostly it seems like adding no-std support would be a bunch of busy work that no one would end up actually using but would impose additional maintenance and CI overheads on us.


Before we close this issue, I think we should at least have some docs/examples of how to implement Arbitrary for types in a no_std crate, so that people don't just create a new issue for this again in the future.

fitzgen avatar Aug 19 '21 17:08 fitzgen

I updated the PR in my fork, to address the merging conflicts: https://github.com/bitwave/arbitrary/tree/no_std

bitwave avatar Aug 20 '21 14:08 bitwave

Any updates?

bitwave avatar Nov 23 '21 17:11 bitwave

Even for primarily no-std crates, it is reasonable for the fuzz targets to use std (and they don't force the main crate itself to otherwise use std).

Not if the fuzz target is a custom operating system & I have to use no_std and write mem alloc myself...

bitwave avatar Dec 26 '21 11:12 bitwave

To add as a use case: Chrono may be used as a dependency by some crate with default-features = false, features = "arbitrary". For chrono that implies no_std. Should the arbitrary dependency imply a dependency on std? Or should we put all implementations of Arbitrary behind an extra feature gate #[cfg(all(feature = "arbitrary", feature = "std"))]? I have currently gone with the latter in https://github.com/chronotope/chrono/pull/1336.

It would be easier for a library if we did not need to care, and could specify arbitrary as dependency with default-features = "false" because all we need is to implement its trait.

And a second use case: Suppose we have two different code paths, one that relies on the standard library and a more complex standlone one (as in https://github.com/chronotope/chrono/pull/1163). It would be nice if there was a way to fuzz both, but that is not possible when arbitrary requires std.

pitdicker avatar Oct 01 '23 14:10 pitdicker