Execute Rust code carefully, with extra checking along the way

Overview

cargo-careful

cargo careful is a tool to run your Rust code extra carefully -- opting into a bunch of nightly-only extra checks that help detect Undefined Behavior, and using a standard library with debug assertions.

To use cargo careful, first install it:

cargo install cargo-careful

and then run the following in your project:

cargo +nightly careful test

Running cargo careful requires a recent nightly toolchain. You can also cargo +nightly careful run to execute a binary crate. All cargo test and cargo run flags are supported.

The first time you run cargo careful, it needs to run some setup steps, which requires the rustc-src rustup component -- the tool will offer to install it for you if needed.

What does it do?

The most important thing cargo careful does is that it builds the standard library with debug assertions. The standard library already contains quite a few sanity checks that are enabled as debug assertions, but the usual rustup distrubtion compiles them all away to avoid run-time checks. Furthermore, cargo careful sets some flags that tell rustc to insert extra run-time checks.

Here are some of the checks this enables:

  • get_unchecked in slices performs bounds checks.
  • copy, copy_nonoverlapping, and write_bytes check that pointers are aligned and non-null and (if applicable) non-overlapping.
  • {NonNull,NonZero*,...}::new_unchecked check that the value is valid.
  • unreachable_unchecked checks that it actually is not being reached.
  • The collection types perform plenty of internal consistency checks.
  • mem::zeroed and the deprecated mem::uninitialized panic if the type does not allow that kind of initialization (with a check that is stricter than the default). (This is -Zstrict-init-checks.)
  • Extra UB-checking is done during const-evaluation. (This is -Zextra-const-ub-checks.)
  • Layout of repr(Rust) types is randomized, to help detect code that makes incorrect layout assumptions. (This is -Zrandomize-layout.)

That said, there is a lot of Undefined Behavior that is not detected by cargo careful; check out Miri if you want to be more exhaustively covered. The advantage of cargo careful over Miri is that it works on all code, supprts using arbitrary system and C FFI functions, and is much faster.

Comments
  • empty libtest harness under cargo-careful fails valgrind

    empty libtest harness under cargo-careful fails valgrind

    Steps to reproduce:

    mkdir /tmp/memory-error && cd /tmp/memory-error
    cargo init --lib
    cargo +nightly careful test --no-run
    valgrind --track-origins=yes target/debug/deps/memory_error-394e695bdd4a1e0f
    

    Valgrind output:

    ==741342== Memcheck, a memory error detector
    ==741342== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
    ==741342== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
    ==741342== Command: target/debug/deps/memory_error-394e695bdd4a1e0f
    ==741342== 
    ==741342== Conditional jump or move depends on uninitialised value(s)
    ==741342==    at 0x16673A: runtime<u8> (raw.rs:93)
    ==741342==    by 0x16673A: from_raw_parts<u8> (intrinsics.rs:2220)
    ==741342==    by 0x16673A: deref<u8, alloc::alloc::Global> (mod.rs:2533)
    ==741342==    by 0x16673A: index (string.rs:2185)
    ==741342==    by 0x16673A: eq (string.rs:2185)
    ==741342==    by 0x16673A: eq (lib.rs:626)
    ==741342==    by 0x16673A: eq<getopts::Name, getopts::Name> (lib.rs:933)
    ==741342==    by 0x16673A: {closure#0} (lib.rs:933)
    ==741342==    by 0x16673A: position<getopts::Opt, getopts::find_opt::{closure_env#0}> (macros.rs:295)
    ==741342==    by 0x16673A: getopts::find_opt (lib.rs:933)
    ==741342==    by 0x166084: getopts::Matches::opt_vals (lib.rs:797)
    ==741342==    by 0x16634A: getopts::Matches::opt_present (lib.rs:813)
    ==741342==    by 0x13C548: test::cli::parse_opts (cli.rs:207)
    ==741342==    by 0x15A0FC: test::test_main (lib.rs:95)
    ==741342==    by 0x15B0D1: test::test_main_static (lib.rs:131)
    ==741342==    by 0x123112: memory_error::main (lib.rs:1)
    ==741342==    by 0x12307A: core::ops::function::FnOnce::call_once (function.rs:251)
    ==741342==    by 0x12315D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
    ==741342==    by 0x122CE0: std::rt::lang_start::{{closure}} (rt.rs:166)
    ==741342==    by 0x18AA53: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:286)
    ==741342==    by 0x18AA53: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:464)
    ==741342==    by 0x18AA53: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:428)
    ==741342==    by 0x18AA53: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:137)
    ==741342==    by 0x18AA53: {closure#2} (rt.rs:148)
    ==741342==    by 0x18AA53: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:464)
    ==741342==    by 0x18AA53: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:428)
    ==741342==    by 0x18AA53: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:137)
    ==741342==    by 0x18AA53: std::rt::lang_start_internal (rt.rs:148)
    ==741342==    by 0x122CB9: std::rt::lang_start (rt.rs:165)
    ==741342==  Uninitialised value was created by a stack allocation
    ==741342==    at 0x165FE7: getopts::Matches::opt_vals (lib.rs:796)
    ==741342== 
    ==741342== Conditional jump or move depends on uninitialised value(s)
    ==741342==    at 0x16680E: runtime<u8> (raw.rs:93)
    ==741342==    by 0x16680E: from_raw_parts<u8> (intrinsics.rs:2220)
    ==741342==    by 0x16680E: deref<u8, alloc::alloc::Global> (mod.rs:2533)
    ==741342==    by 0x16680E: index (string.rs:2185)
    ==741342==    by 0x16680E: eq (string.rs:2185)
    ==741342==    by 0x16680E: eq (lib.rs:626)
    ==741342==    by 0x16680E: eq<getopts::Name, getopts::Name> (lib.rs:940)
    ==741342==    by 0x16680E: {closure#1} (lib.rs:940)
    ==741342==    by 0x16680E: any<getopts::Opt, getopts::find_opt::{closure_env#1}> (macros.rs:242)
    ==741342==    by 0x16680E: getopts::find_opt (lib.rs:940)
    ==741342==    by 0x166084: getopts::Matches::opt_vals (lib.rs:797)
    ==741342==    by 0x16634A: getopts::Matches::opt_present (lib.rs:813)
    ==741342==    by 0x13C548: test::cli::parse_opts (cli.rs:207)
    ==741342==    by 0x15A0FC: test::test_main (lib.rs:95)
    ==741342==    by 0x15B0D1: test::test_main_static (lib.rs:131)
    ==741342==    by 0x123112: memory_error::main (lib.rs:1)
    ==741342==    by 0x12307A: core::ops::function::FnOnce::call_once (function.rs:251)
    ==741342==    by 0x12315D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
    ==741342==    by 0x122CE0: std::rt::lang_start::{{closure}} (rt.rs:166)
    ==741342==    by 0x18AA53: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:286)
    ==741342==    by 0x18AA53: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:464)
    ==741342==    by 0x18AA53: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:428)
    ==741342==    by 0x18AA53: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:137)
    ==741342==    by 0x18AA53: {closure#2} (rt.rs:148)
    ==741342==    by 0x18AA53: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:464)
    ==741342==    by 0x18AA53: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:428)
    ==741342==    by 0x18AA53: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:137)
    ==741342==    by 0x18AA53: std::rt::lang_start_internal (rt.rs:148)
    ==741342==    by 0x122CB9: std::rt::lang_start (rt.rs:165)
    ==741342==  Uninitialised value was created by a stack allocation
    ==741342==    at 0x165FE7: getopts::Matches::opt_vals (lib.rs:796)
    ==741342== 
    ==741342== Conditional jump or move depends on uninitialised value(s)
    ==741342==    at 0x16673A: runtime<u8> (raw.rs:93)
    ==741342==    by 0x16673A: from_raw_parts<u8> (intrinsics.rs:2220)
    ==741342==    by 0x16673A: deref<u8, alloc::alloc::Global> (mod.rs:2533)
    ==741342==    by 0x16673A: index (string.rs:2185)
    ==741342==    by 0x16673A: eq (string.rs:2185)
    ==741342==    by 0x16673A: eq (lib.rs:626)
    ==741342==    by 0x16673A: eq<getopts::Name, getopts::Name> (lib.rs:933)
    ==741342==    by 0x16673A: {closure#0} (lib.rs:933)
    ==741342==    by 0x16673A: position<getopts::Opt, getopts::find_opt::{closure_env#0}> (macros.rs:295)
    ==741342==    by 0x16673A: getopts::find_opt (lib.rs:933)
    ==741342==    by 0x166084: getopts::Matches::opt_vals (lib.rs:797)
    ==741342==    by 0x166643: getopts::Matches::opt_str (lib.rs:804)
    ==741342==    by 0x13C764: get_allow_unstable (cli.rs:471)
    ==741342==    by 0x13C764: parse_opts_impl (cli.rs:252)
    ==741342==    by 0x13C764: test::cli::parse_opts (cli.rs:214)
    ==741342==    by 0x15A0FC: test::test_main (lib.rs:95)
    ==741342==    by 0x15B0D1: test::test_main_static (lib.rs:131)
    ==741342==    by 0x123112: memory_error::main (lib.rs:1)
    ==741342==    by 0x12307A: core::ops::function::FnOnce::call_once (function.rs:251)
    ==741342==    by 0x12315D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
    ==741342==    by 0x122CE0: std::rt::lang_start::{{closure}} (rt.rs:166)
    ==741342==    by 0x18AA53: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:286)
    ==741342==    by 0x18AA53: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:464)
    ==741342==    by 0x18AA53: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:428)
    ==741342==    by 0x18AA53: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:137)
    ==741342==    by 0x18AA53: {closure#2} (rt.rs:148)
    ==741342==    by 0x18AA53: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:464)
    ==741342==    by 0x18AA53: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:428)
    ==741342==    by 0x18AA53: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:137)
    ==741342==    by 0x18AA53: std::rt::lang_start_internal (rt.rs:148)
    ==741342==    by 0x122CB9: std::rt::lang_start (rt.rs:165)
    ==741342==  Uninitialised value was created by a stack allocation
    ==741342==    at 0x165FE7: getopts::Matches::opt_vals (lib.rs:796)
    ==741342== 
    
    running 1 test
    test tests::it_works ... ok
    
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
    
    ==741342== 
    ==741342== HEAP SUMMARY:
    ==741342==     in use at exit: 0 bytes in 0 blocks
    ==741342==   total heap usage: 479 allocs, 479 frees, 53,506 bytes allocated
    ==741342== 
    ==741342== All heap blocks were freed -- no leaks are possible
    ==741342== 
    ==741342== For lists of detected and suppressed errors, rerun with: -s
    ==741342== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
    

    I've tried to debug this with gdb and adding prints, but it disappears if you look at it too closely so gave up after a bit of trying

    opened by elichai 17
  • Build failure on rust-sfml: possibly newer version of crate `core`

    Build failure on rust-sfml: possibly newer version of crate `core`

    via reddit: running cargo careful test on https://github.com/jeremyletang/rust-sfml seems to result in an error

    error[E0460]: found possibly newer version of crate `core` which `link_cplusplus` depends on
    --> /home/dew/projects/github/rust-sfml/src/lib.rs:50:1
    |
    50 | extern crate link_cplusplus;
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: perhaps that crate needs to be recompiled?
    
    opened by RalfJung 17
  • Use AddressSanitizer when available

    Use AddressSanitizer when available

    opened by Jules-Bertholet 12
  • Sysroot build fails on Nix shell

    Sysroot build fails on Nix shell

    I'm trying to use this tool in a nix shell. For some reason, it's sticking it in a write only directory (/tmp/nix-shell.NL1aJs/rustc-build-sysroot.6BErGTCriFwN). Could the sysroot directory be made configurable via flag or environment variable? I'm happy to implement if you'll pull it in :)

    opened by DieracDelta 12
  • Showcase Example

    Showcase Example

    Thanks for this interesting tool. It would be great to have a short (realistic) piece of code in the README on which careful would trigger.

    Is there some?

    opened by netzdoktor 8
  • `build.rustflags` is ignored

    `build.rustflags` is ignored

    via reddit:

    I'm using special flags, like:

    $ cat .cargo/config.toml 
    [build]
    rustflags = ["--cfg", "tokio_unstable"]
    

    cargo careful does not seem to use this flag.

    This is probably because we are setting rustflags ourselves, cargo stops using the flags from the toml file.

    opened by RalfJung 6
  • Add

    Add "-Zrandomize-layout" flag

    Adds the -Zrandomize-layout flag to CAREFUL_FLAGS. This flag randomizes the layout of repr(Rust) types, to help catch code relying on unatable details of that layout.

    opened by Jules-Bertholet 4
  • README: Recommend `--target` for sanitized builds

    README: Recommend `--target` for sanitized builds

    Recommended here:

    • https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html
    • https://github.com/japaric/rust-san

    I got a build error on proc-macro-hack, but adding this flag fixed it for me!

    opened by langston-barrett 1
  • Add `-Coverflow-checks=on` to the list of 'careful flags'

    Add `-Coverflow-checks=on` to the list of 'careful flags'

    I believe this mostly matters for if the stdlib will have these on, since normally it would be on for non-release builds of user code. This perhaps indicates a bug in the stdlib more than in the user's code, but I am not sure if that applies to 100% of the cases this could catch.

    opened by thomcc 1
  • Should `cargo-careful` set a `--cfg` flag?

    Should `cargo-careful` set a `--cfg` flag?

    Should cargo-careful set a --cfg careful in rustflags? IMO yes, so that extra checking can be enabled. Here are some use cases:

    1. When building vendored C code in FFI crates, it's hard[^1] to know if you should turn on assertions, so I usually don't. If the library has extra levels of stronger/slower assertions, I never turn those on (and generally consider it bad for FFI crates to do so without opt-in from the user).

      However, if I can tell a user is using cargo-careful, I can assume they are willing to pay for extra checking, and so I should turn on all the assertions, even if they're slow and/or verify things which would be redundant with safety provided by e.g. Rust's typesystem or something.

    2. Even for pure rust, sometimes I have checks only under #[cfg(test)] rather than debug_assertions. This is usually because they're too costly to consider making a user pay for it for their own debugging (perhaps they increase the time complexity of an algorithm, or whatever). Depending on the case (for example, if safety depends on the correctness I very likely would), I may enable this under cargo-careful as well.

    I can think of some other use cases as well, of similar reasoning.

    [^1]: Sadly, there's no way to reliably detect if the user has debug_assertions enabled in a build script. Ideally cargo would pass CARGO_CFG_DEBUG_ASSERTIONS to build scripts but because of the way it discovers cfgs, it cannot, and IIUC this is unlikely to change. So the closest you get is checking PROFILE for being "debug" or not, which is... not quite right. (Note that the DEBUG env var is for debuginfo, not assertions).

    opened by thomcc 1
  • Publish static Linux binaries with releases

    Publish static Linux binaries with releases

    Hi, thanks for the awesome tool!

    I'd like to use this in a CI build. The build would go faster and use fewer resources if I were able to download a statically-linked Linux executable from the Github releases, rather than use cargo install. It's actually pretty easy to set up Github Actions to automatically create draft releases with attached static binaries whenever you push a git tag. An example CI setup from one of my own projects is available here: https://github.com/langston-barrett/mdlynx/blob/main/.github/workflows/release.yml

    Feel free to close this issue if this doesn't appeal to you or sounds like too much work, I just figured I'd share my use-case and the above link in case it's helpful!

    opened by langston-barrett 1
Owner
Ralf Jung
Ralf Jung
The library for those who need a little extra from their windows. ™

WinEx The library for those who need a little extra from their windows. ™ WinEx - Short for Window Extended - is a library whose goal is to implement

Matheus Branco Borella 2 Mar 27, 2022
A run-codes cli front end with some extra features

run-cli Run-cli A run-codes cli front end with some extra features Report Bug · Request Feature Table of Contents About The Project Built With Getting

Matheus Vieira 13 Nov 16, 2022
Execute Javascript code in the Dioxus, and get the return value ( for Dioxus )

Golde Dioxus Execute Javascript code in the Dioxus, and get the return value. This demo can help use Javascript to calc the + operator formula. use di

YuKun Liu 15 Dec 27, 2022
Abuse the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code.

jscythe abuses the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code, even if t

Simone Margaritelli 301 Jan 4, 2023
A cargo subcommand for checking and applying updates to installed executables

cargo-update A cargo subcommand for checking and applying updates to installed executables Documentation Manpage Installation Firstly, ensure you have

наб 827 Jan 4, 2023
`matchable` provides a convenient enum for checking if a piece of text is matching a string or a regex.

matchable matchable provides a convenient enum for checking if a piece of text is matching a string or a regex. The common usage of this crate is used

Pig Fang 6 Dec 19, 2022
CLI tool for checking ProtonDB compatibility of your Steam games.

protondb-check protondb-check is currently in active development stage, there might be bugs or other problems. Table Of Contents About Available comma

Giorgi Anakidze 3 Apr 1, 2024
A rust library that allows you to host the CLR and execute dotnet binaries.

ClrOxide ClrOxide is a rust library that allows you to host the CLR and dynamically execute dotnet binaries. I wanted to call it Kepler for no particu

YK 94 Apr 12, 2023
REC2 (Rusty External Command and Control) is client and server tool allowing auditor to execute command from VirusTotal and Mastodon APIs written in Rust. 🦀

Information: REC2 is an old personal project (early 2023) that I didn't continue development on. It's part of a list of projects that helped me to lea

Quentin Texier (g0h4n) 104 Oct 7, 2023
A very simple third-party cargo subcommand to execute a custom command

cargo-x A very simple third-party cargo subcommand to execute a custom command Usage install cargo-x cargo install cargo-x or upgrade cargo install -

刘冲 9 Dec 26, 2022
👁️ A tool to execute a certain command when a target file is modified.

Ojo Ojo is a simple utility that allows you to execute a specific command each time a certain file is being saved. Usage Let's say you are sick the fo

Tarek Kunze 13 Dec 25, 2022
Workflows make it easy to browse, search, execute and share commands (or a series of commands)--without needing to leave your terminal.

Workflows The repo for all public Workflows that appear within Warp and within commands.dev. To learn how to create local or repository workflows, see

Warp 369 Jan 2, 2023
Workflows make it easy to browse, search, execute and share commands (or a series of commands)--without needing to leave your terminal.

Workflows The repo for all public Workflows that appear within Warp and within commands.dev. To learn how to create local or repository workflows, see

Warp 227 Jun 1, 2022
scan markdown files and execute `console` blocks

exec-commands − scan markdown files and execute console blocks exec-commands is a utility to update command-line-tool examples embedded in markdown fi

Hajime Suzuki 3 Nov 27, 2022
Execute KDL files!

kdl-script A Compiler for KDLScript, the KDL-based programming language! KDLScript is a "fake" scripting language that actually just exists to declare

Aria Beingessner 21 Dec 23, 2022
The-way - A code snippets manager for your terminal.

The Way A code snippets manager for your terminal. Record and retrieve snippets you use every day, or once in a blue moon, without having to spin up a

OutOfCheeseError 254 Jan 7, 2023
The dead easy way to use config files in your rust project

Configr The dead easy way to use config files in your project This will load a config.toml file if it exists, otherwise it will create the needed fold

Carsten Kragelund Jørgensen 4 Jul 1, 2022
🚀 A fast & easy way to interface w/ Farcaster.xyz via Rust 🦀

farcaster-rs ?? A simple & easy way to interface with Farcaster via Rust ?? Author: Landon Boles GitHub | Farcaster | Bird App Credits MistApproach To

Landon 29 Dec 15, 2022
An ascii webcam in your console, written as a way of learning rust.

asciicam An ascii webcam in your console, written as a way of learning rust. asciicam picture of me holding a basketball usage only linux is supported

Vilhelm Bergsøe 3 Nov 15, 2022