Skip to content

Commit

Permalink
Merge #1086
Browse files Browse the repository at this point in the history
1086: Support for library contracts r=JonasAlaif a=jjurm

Summary: Automatically import specifications from dependency crates and inline them when respective functions or types are referenced in the current crate.

This eliminates the need for `#[extern_spec]` blocks for declarations in other crates as long as the crates are compiled in the same cargo session.

With the proposed changes, the following verifies:
`prusti-tests/tests/cargo_verify/library_contracts_test/src/main.rs`:

```rust
extern crate prusti_contracts;
extern crate library_contracts_lib;

use library_contracts_lib::Opt;

// Demonstrating library contracts: the following specification is imported
// and thus the #[extern_spec] block is not needed

/*
use prusti_contracts::*;
#[extern_spec]
impl<T> Opt<T> {
    #[ensures(matches!(*self, Opt::OSome(_)) == result)]
    fn is_some(&self) -> bool;
}
*/

fn main() {
    let a = Opt::OSome(42);
    let b = Opt::ONone::<i32>;

    assert!(a.is_some() == true);
    assert!(b.is_some() == false);
}
```

Closes #2

Co-authored-by: Juraj Mičko <[email protected]>
Co-authored-by: Jonas <[email protected]>
  • Loading branch information
3 people authored Aug 29, 2022
2 parents 05b74c2 + d64d33f commit 01e1455
Show file tree
Hide file tree
Showing 213 changed files with 3,274 additions and 1,664 deletions.
19 changes: 10 additions & 9 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Run cargo tests --release
run: python x.py test --release --all --verbose
- name: Upload Prusti artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: prusti-release-${{ matrix.os }}
if-no-files-found: error
Expand All @@ -46,14 +46,15 @@ jobs:
target/release/prusti-server*
target/release/prusti-rustc*
target/release/cargo-prusti*
target/release/libprusti_contracts.rlib
target/release/deps/libprusti_contracts_internal-*
target/release/deps/prusti_contracts_internal-*.dll
!target/release/prusti-driver.d
!target/release/prusti-server-driver.d
!target/release/prusti-server.d
!target/release/prusti-rustc.d
!target/release/cargo-prusti.d
prusti-contracts/target/verify/release/libprusti_contracts.*
prusti-contracts/target/verify/release/deps/libprusti_contracts_internal-*
prusti-contracts/target/verify/release/deps/prusti_contracts_internal-*.dll
prusti-contracts/target/verify/release/libprusti_contracts_std.*
prusti-contracts/target/verify/release/deps/libprusti_contracts-*
prusti-contracts/target/verify/release/deps/prusti_contracts-*.dll
!target/release/*.d
!prusti-contracts/target/verify/release/*.d
!prusti-contracts/target/verify/release/deps/*.d
# Deploy to a new GitHub pre-release
deploy:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ jobs:
PRUSTI_VIPER_BACKEND: carbon
- name: Check prusti-contracts
run: |
cd prusti-contracts-test/
cd prusti-contracts/prusti-contracts-test/
cargo +stable build
# Run Prusti on itself. Currently disabled because of many bugs in Prusti.
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
perf.*
callgrind.*
core
Prusti.toml
report.csv
*.profraw

Expand Down
47 changes: 10 additions & 37 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
members = [
"analysis",
"prusti",
"prusti-contracts",
"prusti-contracts-impl",
"prusti-contracts-internal",
"prusti-specs",
"prusti-contracts-build",
"prusti-tests",
"prusti-common",
"prusti-interface",
"prusti-viper",
"prusti-server",
"prusti-launch",
"prusti-utils",
"prusti-smt-solver",
"prusti-rustc-interface",
"smt-log-analyzer",
Expand Down
5 changes: 5 additions & 0 deletions docs/dev-guide/src/config/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
| [`LOG_STYLE`](#log_style) | `String` | `"auto"` |
| [`LOG_SMT_WRAPPER_INTERACTION`](#log_smt_wrapper_interaction) | `bool` | `false` |
| [`MAX_LOG_FILE_NAME_LENGTH`](#max_log_file_name_length) | `usize` | `60` |
| [`MIN_PRUSTI_VERSION`](#min_prusti_version) | `Option<String>` | `None` |
| [`NO_VERIFY`](#no_verify) | `bool` | `false` |
| [`NO_VERIFY_DEPS`](#no_verify_deps) | `bool` | `false` |
| [`OPTIMIZATIONS`](#optimizations) | `Vec<String>` | "all" |
Expand Down Expand Up @@ -267,6 +268,10 @@ When enabled, logs all SMT wrapper interaction to a file.

Maximum allowed length of a log file name. If this is exceeded, the file name is truncated.

## `MIN_PRUSTI_VERSION`

Minimum required version of Prusti that is allowed to run. If Prusti detects that its own version is lower than this, it will throw an error and refuse to verify files. Generally [set in a `Prusti.toml` file](providing.md#flags-2) of a crate to enforce a minimum Prusti version.

## `NO_VERIFY`

When enabled, verification is skipped altogether.
Expand Down
2 changes: 1 addition & 1 deletion docs/dev-guide/src/config/providing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Flags can be set in one of four ways, in increasing order of priority:
1. Provided individually as environment variables with the prefix `DEFAULT_PRUSTI_` (for example, `DEFAULT_PRUSTI_CACHE_PATH` for the [`CACHE_PATH`](flags.md#cache_path) flag).
> NOTE: One should only use this to change the default value of flags, such that this can be overridden by a config file
2. Provided lowercase in a file ([allowed formats](https://docs.rs/config/latest/config/enum.FileFormat.html)) with the path in the environment variable `PRUSTI_CONFIG` (for example, `check_overflows` for the [`CHECK_OVERFLOWS`](flags.md#check_overflows) flag). This path defaults to `./Prusti.toml` if the environment variable is not set.
2. Provided lowercase in a file ([allowed formats](https://docs.rs/config/latest/config/enum.FileFormat.html)) with the path in the environment variable `PRUSTI_CONFIG` (for example, `check_overflows` for the [`CHECK_OVERFLOWS`](flags.md#check_overflows) flag). This path defaults to `./Prusti.toml` if the environment variable is not set.<a name="flags-2"></a>

3. Provided individually as environment variables with the prefix `PRUSTI_` (for example, `PRUSTI_ASSERT_TIMEOUT` for the [`ASSERT_TIMEOUT`](flags.md#assert_timeout) flag).

Expand Down
2 changes: 1 addition & 1 deletion prusti-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ serde = { version = "1.0", features = ["derive"] }
lazy_static = "1.4.0"
uuid = { version = "1.0", features = ["v4"] }
regex = "1.5"
prusti-utils = { path = "../prusti-utils" }
prusti-utils = { path = "../prusti-contracts/prusti-utils" }
fxhash = "0.2.1"
20 changes: 19 additions & 1 deletion prusti-common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub mod commandline;
use self::commandline::CommandLine;
use ::config::{Config, Environment, File};
use log::warn;
use prusti_launch::{get_current_executable_dir, find_viper_home};
use prusti_launch::{get_current_executable_dir, find_viper_home, PRUSTI_HELPERS, PRUSTI_LIBS};
use serde::Deserialize;
use std::{collections::HashSet, env, path::PathBuf, sync::RwLock};

Expand Down Expand Up @@ -123,6 +123,7 @@ lazy_static::lazy_static! {
settings.set_default("enable_type_invariants", false).unwrap();
settings.set_default("use_new_encoder", true).unwrap();
settings.set_default::<Option<u8>>("number_of_parallel_verifiers", None).unwrap();
settings.set_default::<Option<String>>("min_prusti_version", None).unwrap();

settings.set_default("print_desugared_specs", false).unwrap();
settings.set_default("print_typeckd_specs", false).unwrap();
Expand Down Expand Up @@ -476,6 +477,18 @@ pub fn use_more_complete_exhale() -> bool {
read_setting("use_more_complete_exhale")
}

pub fn is_prusti_helper_crate() -> bool {
env::var("CARGO_PKG_NAME")
.map(|name| PRUSTI_HELPERS.contains(&name.as_str()))
.unwrap_or(false)
}

pub fn is_prusti_lib_crate() -> bool {
env::var("CARGO_PKG_NAME")
.map(|name| PRUSTI_LIBS.contains(&name.as_str()))
.unwrap_or(false)
}

/// When enabled, prints the items collected for verification.
pub fn print_collected_verification_items() -> bool {
read_setting("print_collected_verification_items")
Expand Down Expand Up @@ -832,6 +845,11 @@ pub fn number_of_parallel_verifiers() -> Option<u8> {
read_setting("number_of_parallel_verifiers")
}

/// Throw a compilation error if using a lower prusti version.
pub fn min_prusti_version() -> Option<String> {
read_setting("min_prusti_version")
}

/// The given basic blocks will be replaced with `assume false`.
pub fn delete_basic_blocks() -> Vec<String> {
read_setting("delete_basic_blocks")
Expand Down
11 changes: 11 additions & 0 deletions prusti-contracts-build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "prusti-contracts-build"
version = "0.1.0"
authors = ["Vytautas Astrauskas <[email protected]>"]
edition = "2021"

[build-dependencies]
prusti-launch = { path = "../prusti-launch" }

[features]
prusti-contracts-dep = []
132 changes: 132 additions & 0 deletions prusti-contracts-build/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::{path::PathBuf, process::Command};

fn main() {
// In theory we should build to here (i.e. set `CARGO_TARGET_DIR` to this),
// but this is hard to find for linking. So instead build to the `prusti-contracts` dir.
let _out_dir = std::env::var("OUT_DIR").unwrap();
// println!("cargo:warning=out_dir: {}", _out_dir);

// Rebuild if any of the `prusti-contracts` crates change
let prusti_contrats = ["..", "prusti-contracts"].iter().collect::<PathBuf>();
let files = std::fs::read_dir(&prusti_contrats).unwrap();
for file in files {
let file = file.unwrap();
let filename = file.file_name();
let filename = filename.to_string_lossy();
if filename != "target" {
println!("cargo:rerun-if-changed={}", file.path().to_string_lossy());
}
}

// Build `prusti-contracts`
let dir = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
// Cargo prusti
let exes = ["cargo-prusti", "prusti-rustc", "prusti-driver"].map(|exe| {
let exe = format!("{}{}", exe, if cfg!(windows) { ".exe" } else { "" });
let exe = ["..", "target", dir, &exe].iter().collect::<PathBuf>();
println!("cargo:rerun-if-changed={}", exe.to_string_lossy());
exe
});
// Rerun if running with e.g. cargo clippy
println!("cargo:rerun-if-env-changed=RUSTC_WORKSPACE_WRAPPER");
// Run only if possible
if exes.iter().all(|exe| exe.exists()) {
let target = prusti_contrats.join("target").join("verify");
let modified = modified_times(&exes);
let should_write_build_info =
check_exes_modified(target.as_path(), &modified).unwrap_or(true);
let args = ["--release", "--features", "prusti"];
match Command::new(&exes[0])
.current_dir(&prusti_contrats)
.args(&args)
.env("CARGO_PRUSTI_COMMAND", "build")
.output()
{
Ok(output) => {
if output.status.success() {
if should_write_build_info {
use std::io::Write;
std::fs::File::create(target.join("build_info.txt"))
.and_then(|mut file| file.write_all(modified.as_bytes()))
.ok();
}
} else {
let stdout = String::from_utf8(output.stdout).unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
println!("cargo:warning=Failed to build `prusti-contracts`!");
let args = args.iter().fold(String::new(), |acc, a| acc + " " + a);
println!(
"cargo:warning=`prusti-contracts-build` ran '{}{}'",
exes[0].to_string_lossy(),
args
);
println!("cargo:warning=-------- stdout: --------");
for line in stdout.lines() {
println!("cargo:warning={}", line);
}
println!("cargo:warning=-------- stderr: --------");
for line in stderr.lines() {
println!("cargo:warning={}", line);
}
// Only panic when running with `x.py` to avoid errors on the first run
if std::env::var("CARGO_FEATURE_PRUSTI_CONTRACTS_DEP").is_ok() {
// Delete files to prevent Catch-22 where these files cannot be rebuilt
for exe in exes {
std::fs::remove_file(exe).ok();
}
panic!("Fix the above Prusti crash and run build again to rebuild Prusti.")
} else {
std::fs::remove_dir_all(target).ok();
}
}
}
Err(e) => {
println!("cargo:warning=Failed to build `prusti-contracts`: {}", e);
}
}
} else {
}
}

/// Cargo will rebuild prusti-contracts if any of those files changed, however we want to
/// rebuild them also if any of the `cargo-prusti`/`prusti-{rustc,driver}` changed, and so
/// we manually force that here if required.
fn check_exes_modified(target: &std::path::Path, modified: &str) -> std::io::Result<bool> {
let contents = std::fs::read_to_string(target.join("build_info.txt"))?;
if contents != modified {
let files = std::fs::read_dir(target.join("release").join("deps"))?;
let libs = prusti_launch::PRUSTI_LIBS.map(|lib| format!("lib{}-", lib.replace('-', "_")));
for file in files {
let file = file.unwrap();
let filename = file.file_name();
let filename = filename.to_string_lossy();
if libs.iter().any(|lib| filename.starts_with(lib)) {
std::fs::remove_file(file.path()).ok();
}
}
Ok(true)
} else {
Ok(false)
}
}

fn modified_times(files: &[PathBuf]) -> String {
files
.iter()
.map(|f| {
f.metadata()
.unwrap()
.modified()
.unwrap()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
.to_string()
})
.collect::<Vec<_>>()
.join("\n")
}
Loading

0 comments on commit 01e1455

Please sign in to comment.