-
Notifications
You must be signed in to change notification settings - Fork 158
Set kani-compiler's required rustc flags unconditionally #4601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lovesegfault
wants to merge
4
commits into
model-checking:main
Choose a base branch
from
lovesegfault:compiler-defaults
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
a4231d0
Set kani-compiler's required rustc flags unconditionally
lovesegfault a680617
Add integration test for kani-compiler default rustc flags
lovesegfault 0187f43
Prove the --check-cfg=cfg(kani) default is applied, not just accepted
lovesegfault 9454d98
Cover conflicting caller flags in the compiler defaults test
lovesegfault File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
tests/script-based-pre/compiler_defaults/compiler_defaults.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| [TEST] minimal: kani-compiler exited 0 | ||
| [TEST] minimal proof_harnesses: 2 | ||
| [TEST] minimal mangled names are v0 | ||
| [TEST] unexpected_cfgs fired for undeclared cfg | ||
| [TEST] no unexpected_cfgs warning for cfg(kani) | ||
| [TEST] conflict: kani-compiler exited 0 | ||
| [TEST] conflict proof_harnesses: 2 | ||
| [TEST] conflict mangled names are v0 |
148 changes: 148 additions & 0 deletions
148
tests/script-based-pre/compiler_defaults/compiler_defaults.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| #!/usr/bin/env bash | ||
| # Copyright Kani Contributors | ||
| # SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| # Test that kani-compiler sets its required rustc flags unconditionally. | ||
| # | ||
| # Case 1 (minimal): invoke kani-compiler directly with the MINIMAL flag set | ||
| # and assert the #[cfg(kani)]-gated harnesses appear in the metadata. Without | ||
| # the defaults, --cfg=kani would be unset, the harness module would be | ||
| # invisible, and the metadata would have 0 proof_harnesses: a vacuous | ||
| # "verification" with nothing verified. | ||
| # | ||
| # The single-token `--flag=value` encoding of the cfg defaults must be parsed | ||
| # AND applied, not just accepted: | ||
| # - --cfg=kani: harnesses appear at all — the whole module is gated on it. | ||
| # - --check-cfg=cfg(kani): fixture.rs also carries a deliberately undeclared | ||
| # cfg. rustc only checks cfg names when at least one --check-cfg argument | ||
| # is present, and none is passed below — so that cfg drawing an | ||
| # unexpected_cfgs warning proves the default was parsed as a check-cfg | ||
| # spec, and #[cfg(kani)] drawing none proves `kani` is the name it | ||
| # registered. | ||
| # | ||
| # Case 2 (conflict): the caller explicitly passes the opposite of three | ||
| # required flags and the required values must still win — see the run_case | ||
| # call below for how each winner is observed. | ||
|
|
||
| set -eu | ||
|
|
||
| OUTDIR="tmp_compiler_defaults" | ||
| rm -rf "${OUTDIR}" | ||
| mkdir "${OUTDIR}" | ||
|
|
||
| # Locate kani-compiler and the kani sysroot in the dev build: `cargo build-dev` | ||
| # populates target/kani/ (KANI_SYSROOT in .cargo/config.toml) with bin/ and | ||
| # lib/, mirroring the release install layout. Same pattern as | ||
| # std_codegen/codegen_std.sh. KANI_HOME is what `kani` itself passes as | ||
| # --sysroot: LibConfig::new() uses the parent of the lib/ folder. | ||
| KANI_DIR=$(git rev-parse --show-toplevel) | ||
| KANI_HOME="${KANI_DIR}/target/kani" | ||
| KANI_COMPILER="${KANI_HOME}/bin/kani-compiler" | ||
| KANI_LIB="${KANI_HOME}/lib" | ||
|
|
||
| [[ -x "${KANI_COMPILER}" ]] || { | ||
| echo "ERROR: kani-compiler not found at ${KANI_COMPILER}" | ||
| echo "Run 'cargo build-dev' first." | ||
| exit 1 | ||
| } | ||
|
|
||
| # Args every case shares. Deliberately omits every flag | ||
| # KANI_REQUIRED_RUSTC_ARGS now defaults: -Cpanic=abort, -Coverflow-checks=on, | ||
| # -Csymbol-mangling-version, -Zalways-encode-mir, -Zpanic_abort_tests, | ||
| # -Zmir-enable-passes, --cfg=kani, --check-cfg=cfg(kani). What remains is | ||
| # what every caller still has to pass: install paths, the routing marker, | ||
| # -Zunstable-options (gates `--extern noprelude:`), -Zcrate-attr (errors on | ||
| # duplicate so it stays caller-supplied), and the reachability intent. | ||
| COMMON_ARGS=( | ||
| --kani-compiler | ||
| --crate-type lib | ||
| --sysroot "${KANI_HOME}" | ||
| -L "${KANI_LIB}" | ||
| -Z unstable-options | ||
| --extern kani | ||
| --extern noprelude:std="${KANI_LIB}/libstd.rlib" | ||
| -Z crate-attr="feature(register_tool)" | ||
| -Z crate-attr="register_tool(kanitool)" | ||
| -Cllvm-args=--reachability=harnesses | ||
| ) | ||
|
|
||
| # The case's metadata must list all of fixture.rs's harnesses (the count | ||
| # observes which cfgs resolved — see fixture.rs) with v0-mangled | ||
| # (`_R`-prefixed; legacy is `_ZN`) symbol names. | ||
| check_metadata() { | ||
| local label="$1" | ||
| local metas=("${OUTDIR}/${label}"/*.kani-metadata.json) | ||
| [[ ${#metas[@]} -eq 1 && -f "${metas[0]}" ]] || { | ||
| echo "ERROR: ${label}: expected exactly one metadata file, got: ${metas[*]}" | ||
| exit 1 | ||
| } | ||
| local count | ||
| count=$(jq '.proof_harnesses | length' "${metas[0]}") | ||
| echo "[TEST] ${label} proof_harnesses: ${count}" | ||
| if ! jq -e '(.proof_harnesses | length) > 0 and all(.proof_harnesses[]; .mangled_name | startswith("_R"))' \ | ||
| "${metas[0]}" > /dev/null; then | ||
| echo "ERROR: ${label}: missing or non-v0 mangled harness names:" | ||
| jq -r '.proof_harnesses[].mangled_name' "${metas[0]}" | ||
| exit 1 | ||
| fi | ||
| echo "[TEST] ${label} mangled names are v0" | ||
| } | ||
|
|
||
| # Compile fixture.rs as one test case: `label` names the case (and the | ||
| # crate), remaining args are the caller flags under test. kani-compiler | ||
| # appends KANI_REQUIRED_RUSTC_ARGS after everything passed here. | ||
| run_case() { | ||
| local label="$1" | ||
| shift | ||
| mkdir "${OUTDIR}/${label}" | ||
| "${KANI_COMPILER}" "${COMMON_ARGS[@]}" \ | ||
| --crate-name "${label}" \ | ||
| --out-dir "${OUTDIR}/${label}" \ | ||
| "$@" \ | ||
| fixture.rs 2> "${OUTDIR}/${label}.stderr" || { | ||
| cat "${OUTDIR}/${label}.stderr" | ||
| exit 1 | ||
| } | ||
| echo "[TEST] ${label}: kani-compiler exited 0" | ||
| check_metadata "${label}" | ||
| } | ||
|
|
||
| # Case 1: minimal invocation — every required flag missing; the defaults | ||
| # must supply them all. | ||
| run_case minimal | ||
|
|
||
| # Negative control: fixture.rs's undeclared cfg must draw unexpected_cfgs. | ||
| # Should rustc ever start checking cfg names without any --check-cfg being | ||
| # passed, this leg turns vacuous — but the absence leg below still catches a | ||
| # dropped or mis-encoded default, because #[cfg(kani)] would then warn. | ||
| WARN='unexpected `cfg` condition name' | ||
| if ! grep -q "${WARN}: \`not_a_kani_cfg\`" "${OUTDIR}/minimal.stderr"; then | ||
| echo 'ERROR: undeclared cfg drew no unexpected_cfgs warning — cfg checking is not active' | ||
| cat "${OUTDIR}/minimal.stderr" | ||
| exit 1 | ||
| fi | ||
| echo "[TEST] unexpected_cfgs fired for undeclared cfg" | ||
|
|
||
| # ...and `kani` must be the name --check-cfg=cfg(kani) registered: with cfg | ||
| # checking proven active, #[cfg(kani)] must not warn. | ||
| if grep -q "${WARN}: \`kani\`" "${OUTDIR}/minimal.stderr"; then | ||
| echo 'ERROR: unexpected_cfgs fired for cfg(kani) — --check-cfg=cfg(kani) did not register `kani`' | ||
| cat "${OUTDIR}/minimal.stderr" | ||
| exit 1 | ||
| fi | ||
| echo "[TEST] no unexpected_cfgs warning for cfg(kani)" | ||
|
|
||
| # Case 2: the caller explicitly passes conflicting values and the required | ||
| # values must still win (today: defaults are appended after caller args and | ||
| # rustc is last-flag-wins for scalar -C/-Z flags; these assertions pin the | ||
| # outcome, not the mechanism). Each winner is observable without CBMC: | ||
| # - -Cpanic=unwind or -Coverflow-checks=off winning is a hard error: kani- | ||
| # compiler's own session gates refuse to run ("Kani can only handle abort | ||
| # panic strategy", "Kani requires overflow checks"), so exiting 0 proves | ||
| # the required values resolved. The #[cfg(panic = "abort")]-gated harness | ||
| # doubles as a count backstop should those gates ever move. | ||
| # - -Csymbol-mangling-version=legacy winning flips mangled names off v0's | ||
| # `_R` prefix, failing check_metadata. | ||
| run_case conflict -Cpanic=unwind -Coverflow-checks=off -Csymbol-mangling-version=legacy | ||
|
|
||
| rm -rf "${OUTDIR}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Copyright Kani Contributors | ||
| # SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| script: compiler_defaults.sh | ||
| expected: compiler_defaults.expected | ||
| exit_code: 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Copyright Kani Contributors | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| //! `#[cfg(kani)]`-gated harnesses. If kani-compiler sets `--cfg=kani` as a | ||
| //! default, the harnesses compile; if not, the module is invisible and 0 | ||
| //! harnesses appear in the metadata — a vacuous "verification" with nothing | ||
| //! verified. | ||
| //! | ||
| //! `control` is a negative control for the `--check-cfg=cfg(kani)` default: | ||
| //! its cfg is deliberately undeclared, so the unexpected_cfgs warning it | ||
| //! draws proves cfg checking is active, while `#[cfg(kani)]` drawing no | ||
| //! warning proves `kani` is the name the default registered. | ||
|
|
||
| #[cfg(kani)] | ||
| mod verify { | ||
| #[kani::proof] | ||
| fn check_with_defaults() { | ||
| let x: u8 = kani::any(); | ||
| assert_eq!(x.wrapping_mul(1), x); | ||
| } | ||
|
|
||
| /// rustc derives `cfg(panic = "abort")` from the RESOLVED panic | ||
| /// strategy, so this harness is in the metadata count only if | ||
| /// `-Cpanic=abort` won the session — a backstop for the conflict case | ||
| /// alongside kani-compiler's own abort-strategy gate. | ||
| #[cfg(panic = "abort")] | ||
| #[kani::proof] | ||
| fn check_panic_abort_wins() { | ||
| let x: u8 = kani::any(); | ||
| assert_eq!(x.wrapping_mul(1), x); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(not_a_kani_cfg)] | ||
| mod control {} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we confirm that rustc accepts these in this single-token
--flag=valueform on this path?The main guarantee of this PR depends on
cfg(kani)definitely being enabled. If rustc expects these as separate argv tokens (--cfg,kaniand--check-cfg,cfg(kani)), then we may not actually be enforcing the invariant we want here.If that form is definitely accepted, a test that proves this exact encoding works would make me more confident. Otherwise, I think these should be passed as split tokens instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, nice catch, i added coverage for this in the compiler_defaults test. the fixture now carries a deliberately undeclared cfg as a negative control :)
FWIW this is the same encoding kani-driver has always passed (
base_rustc_flags()), and rustc'sgetoptsCLI treats--flag=valueand--flag valueidentically