Skip to content

Conversation

@tedinski
Copy link
Contributor

@tedinski tedinski commented Jan 8, 2022

Description of changes:

This is a port of our python/bash scripts to native Rust implementations.

  1. There are two entry points to the binary: kani and cargo-kani.
  2. Argument parsing is in arg.rs
  3. We set up a KaniSession from session.rs to serve as the ambient background data/state for an execution.
  4. All calls out to other binaries are organized around call_*.rs files.

Major TODOs:

  • I need to double check the behavior for cargo-kani --visualize
  • I need to audit the old code to be sure I know everything that's been dropped that major.

Currently known to be missing:

  • --gen-c-runnable (producing --symbol-table-passes)
  • "unwind tip" (we should do this over using the new output parsing code instead of what we did previously, possibly create an issue to track this)
  • Checking for dependencies in PATH (we should install them instead)

Resolved issues:

Call-outs:

Testing:

  • How is this change tested? existing regressions

  • Is this a refactor change? no

Checklist

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@adpaco-aws adpaco-aws mentioned this pull request Jan 11, 2022
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

I took a quick look. It's a nice start! Thanks for doing this. Here are a few suggestions:

  1. I think it would be cleaner if we create two modules, one that does the external calls and one that does the parsing. You might as well split the objects too.
  2. Can you also add a bit more documentation to the files and functions?


use crate::context::KaniContext;

impl KaniContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to be inside KaniContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it's using self a lot. (Was even back when you asked, but perhaps only once then. Much more obvious now we have all the flags.)

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a general comment and I think we should address in a separate PR, but I would prefer breaking some of this logic down into more than one struct. I think it would be cleaner if we had smaller structs. We are using self a lot, and that could possible be broken down by calling other objects.

/// Given a `file` (a .symtab.json), produce `{file}.out` by calling symtab2gb
pub fn link_c_lib(&self, inputs: &[PathBuf], output: &Path, function: &str) -> Result<()> {
{
let mut temps = self.temporaries.borrow_mut();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why borrow_mut? Shouldn't self be mutable? Or can we manage temporaries outside of KaniContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm making use of RefCell to let us pass around just a &self (not mut, so we don't need exclusivity), but then still update the list of temporaries.


pub fn kani_rustc(&self) -> Result<PathBuf> {
match self {
Self::DevRepo(repo) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you create a utility function? This seemed to be repeated in the functions bellow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(same as earlier comment: I'll fix it once we have another install type?)

}

/// Represents where we detected Kani, with helper methods for using that information to find critical paths
enum InstallType {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this? Can we use a build.rs instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the code to dynamic detect where all our dependencies are located at runtime. It looks weird right now because I'm not trying to support anything except "checked out repo, globally installed deps" except I was trying to still support a better structure later...

I'll refactor it later if you think we can tolerate it for now. As soon at we actually have another install type this should either become clear or get fixed...


#[derive(Debug, StructOpt)]
pub struct CheckArgs {
// Rust argument parsers (/clap) don't have the convenient '--flag' and '--no-flag' boolean pairs, so approximate
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We currently have two "modes": --default-checks where you write --no-blah-checks for specific categories. Or --no-default-checks where you write --blah-checks for specific categories. I'm supporting that for now. We could revisit eventually?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am in favor of revisiting. This seems to be adding unnecessary complexity and it doesn't seem to be a common practice in rust anyway.


#[derive(Debug, StructOpt)]
pub struct CheckArgs {
// Rust argument parsers (/clap) don't have the convenient '--flag' and '--no-flag' boolean pairs, so approximate
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We currently have two "modes": --default-checks where you write --no-blah-checks for specific categories. Or --no-default-checks where you write --blah-checks for specific categories. I'm supporting that for now. We could revisit eventually?


use crate::context::KaniContext;

impl KaniContext {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it's using self a lot. (Was even back when you asked, but perhaps only once then. Much more obvious now we have all the flags.)

/// Given a `file` (a .symtab.json), produce `{file}.out` by calling symtab2gb
pub fn link_c_lib(&self, inputs: &[PathBuf], output: &Path, function: &str) -> Result<()> {
{
let mut temps = self.temporaries.borrow_mut();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm making use of RefCell to let us pass around just a &self (not mut, so we don't need exclusivity), but then still update the list of temporaries.

}

/// Represents where we detected Kani, with helper methods for using that information to find critical paths
enum InstallType {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the code to dynamic detect where all our dependencies are located at runtime. It looks weird right now because I'm not trying to support anything except "checked out repo, globally installed deps" except I was trying to still support a better structure later...

I'll refactor it later if you think we can tolerate it for now. As soon at we actually have another install type this should either become clear or get fixed...


pub fn kani_rustc(&self) -> Result<PathBuf> {
match self {
Self::DevRepo(repo) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(same as earlier comment: I'll fix it once we have another install type?)

[kani.flags.loudness]
quiet = false

[kani.flags.artifacts.extra.suffixes]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a note for anyone reviewing: these things under kani.flags were pointless and I'm not sure why they were ever there. So I removed these "sections" that shouldn't be there.

Also, I removed --no-quiet and --no-visualize and mangler as options, so I removed those here too.

Comment on lines -333 to +332
.arg(&parent_dir);
.current_dir(&parent_dir);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the --crate flag in favor of just changing directories here.

kani.args(&self.props.kani_flags)
.arg("--input")
.arg(&self.testpaths.file)
kani.arg(&self.testpaths.file)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the --input flag in favor of just passing the argument here.

@tedinski tedinski mentioned this pull request Feb 18, 2022
5 tasks
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

A few more comments. I'm OK leaving the comments related to polishing the code to a separate PR.

/// Produces a set of arguments to pass to "self" (cargo-kani) from a Cargo.toml project file
pub fn config_toml_to_args() -> Result<Vec<OsString>> {
// TODO: `cargo locate-project` maybe?
let file = std::fs::read_to_string("Cargo.toml");
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need to handle --config-toml option?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing is using that flag, so I dropped it.

ctx.run_goto_instrument(&linked_obj)?;

if ctx.args.visualize {
ctx.run_visualize(&linked_obj, "target/report")?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please use PathBuf?

// TODO: currently only cargo-kani pays attention to this.
/// Directory for all generated artifacts
#[structopt(long, parse(from_os_str))]
pub target_dir: Option<PathBuf>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to set a default value here to avoid hard coding the default value in other places?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No: the default is different for standalone/cargo. The "hard coding in other places" is just the two different defaults.

I agree there's a design problem there, but it's not an obvious one to fix. I've deliberately avoided letting a KaniSession know if it's cargo-kani or kani because it's just supposed to represent the flags, but here's an area where it matters, and it gets a little annoying.

// Common arguments for invoking Kani. This gets put into KaniContext, whereas
// anything above is "local" to "main"'s control flow.
#[derive(Debug, StructOpt)]
pub struct KaniArgs {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the output for kani --help after this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now a big jumbled mess:

$ cargo-kani --help
cargo-kani 0.1.0
Verify a Rust crate. For more information, see https://github.com/model-checking/kani

USAGE:
    cargo-kani [FLAGS] [OPTIONS]

FLAGS:
        --allow-cbmc-verification-failure    Do not produce error return code on CBMC verification failure
        --assertion-reach-checks             Turn on assertion reachability checks
        --auto-unwind                        Turn on automatic loop unwinding
        --debug                              Produce full debug information
        --default-checks                     Turn on all default checks
        --dry-run                            Print commands instead of running them
        --gen-c                              Generate C file equivalent to inputted program
    -h, --help                               Prints help information
        --keep-temps                         Keep temporary files generated throughout Kani process
        --memory-safety-checks               Turn on default memory safety checks
        --no-default-checks                  Turn off all default checks
        --no-memory-safety-checks            Turn off default memory safety checks
        --no-overflow-checks                 Turn off default overflow checks
        --no-restrict-vtable                 Disable restricting the targets of virtual table function pointer calls
        --no-undefined-function-checks       Turn off undefined function checks
        --no-unwinding-checks                Turn off default unwinding checks
        --only-codegen                       Kani will only compile the crate
        --overflow-checks                    Turn on default overflow checks
    -q, --quiet                              Produces no output, just an exit code and requested artifacts; overrides
                                             --verbose
        --restrict-vtable                    Restrict the targets of virtual table function pointer calls
        --tests                              Enable test function verification. Only use this option when the entry
                                             point is a test function
        --undefined-function-checks          Turn on undefined function checks
        --unwinding-checks                   Turn on default unwinding checks
        --use-abs                            Use abstractions for the standard library
    -V, --version                            Prints version information
    -v, --verbose                            Output processing stages and commands, along with minor debug information
        --visualize                          Generate visualizer report to <target-dir>/report/html/index.html

OPTIONS:
        --abs-type <abs-type>              Choose abstraction for modules of standard library if available [default:
                                           std]  [possible values: std, kani, c-ffi, no-back]
        --c-lib <c-lib>...                 Link external C files referenced by Rust code
        --cbmc-args <cbmc-args>...         Pass through directly to CBMC; must be the last flag
        --function <function>              Entry point for verification [default: main]
        --object-bits <object-bits>        Specify the number of bits used for representing object IDs in CBMC [default:
                                           16]
        --output-format <output-format>    Toggle between different styles of output [default: regular]  [possible
                                           values: Regular, Terse, Old]
        --target-dir <target-dir>          Directory for all generated artifacts
        --unwind <unwind>                  Specify the value used for loop unwinding in CBMC

IMO, cleaning it up is another PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. It uses the doc comments to generate the help information. It is clean but a bit error prone.

This is a topic for the next PR though. Thanks

let mut temps = self.temporaries.borrow_mut();
temps.push(output_filename.clone());
temps.push(alter_extension(file, "type_map.json"));
temps.push(alter_extension(file, "kani-metadata.json"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we create a little utility class to generate these extensions for us? Another alternative is to create a constant for each type of extension. I noticed that they are hard-coded in different places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While they're hardcoded, they each appear exactly once (except "restrictions.json" which is a design flaw I might try to correct now...)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok. Maybe once you merge this I can add a little utility module that can be used by kani / cargo-kani and the kani-compiler.

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

I think I am OK signing off this PR the way it is since it is a clear improvement to what we have today. Thanks for that.

What's your plan to address the issues that we are leaving for follow up PRs though?

Some things like improving the --help output I think is high priority but I also see a lot of TODO statements that don't have any issue attached to them. There are also a lot of comments here that are going to be pushed to follow up PRs.

// Common arguments for invoking Kani. This gets put into KaniContext, whereas
// anything above is "local" to "main"'s control flow.
#[derive(Debug, StructOpt)]
pub struct KaniArgs {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. It uses the doc comments to generate the help information. It is clean but a bit error prone.

This is a topic for the next PR though. Thanks

}

/// Translates one toml entry into arguments and inserts it into args
fn insert_arg_from_toml(flag: &str, value: &Value, args: &mut Vec<OsString>) -> Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a question, what will happen if the user invokes cargo-kani with an argument in the command line that is also included in the Cargo.toml?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be the same thing as happens on the command line so:

$ cargo-kani --verbose --verbose
error: The argument '--verbose' was provided more than once, but cannot be used multiple times

USAGE:
    cargo-kani --abs-type <abs-type> --function <function> --object-bits <object-bits> --output-format <output-format> --verbose

For more information try --help

Copy link
Contributor

Choose a reason for hiding this comment

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

The command line argument should take precedence over the cargo configuration though. Like it does today. I believe this works today because the python implementation allows you to use an argument more than once.

On clap you can set the following setting AllArgsOverrideSelf to enable that behavior.

print("Json File Input Missing")
sys.exit(1)

if len(sys.argv) == 3:
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe @jaisnan can improve this logic a bit once you merge. I believe he only created the main in order to run some sanity checks.

@tedinski tedinski mentioned this pull request Feb 24, 2022
30 tasks
@tedinski
Copy link
Contributor Author

I combed the PR and updated #701 with all follow up issues we've mentioned.

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Can you please add to your TODO list that command line arguments should take precedence over flags on Cargo.toml?

Thanks

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Good catch

} else {
ctx.run_cbmc(&linked_obj)?;
let result = ctx.run_cbmc(&linked_obj)?;
if result == VerificationStatus::Failure {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: next time you can use if let

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.

cargo-rmc tests fail if we do not add $(pwd) to --build-base

3 participants