Skip to content

Commit 9deeec1

Browse files
Add loop invariant support for while let loop (#4279)
This PR adds loop invariant support for `while let` loop, and also fix the bug reported in issue #4275 I don't separate them because the bug fix is simple and necessary for the expected test of `while let` loop. Resolves #4275 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 4e880cf commit 9deeec1

File tree

4 files changed

+69
-7
lines changed

4 files changed

+69
-7
lines changed

kani-compiler/src/kani_middle/transform/loop_contracts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ impl LoopContractPass {
701701
for stmt in &new_body.blocks()[bb_idx].statements {
702702
if let StatementKind::Assign(place, rvalue) = &stmt.kind {
703703
match rvalue {
704-
Rvalue::Ref(_,_,rplace) | Rvalue::CopyForDeref(rplace) => {
704+
Rvalue::Ref(_,_,rplace) | Rvalue::CopyForDeref(rplace) | Rvalue::Use(Operand::Copy(rplace)) => {
705705
if supported_vars.contains(&rplace.local) {
706706
supported_vars.push(place.local);
707707
} }

library/kani_macros/src/sysroot/loop_contracts/mod.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
77
use proc_macro::TokenStream;
88
use proc_macro_error2::abort_call_site;
9-
use quote::{format_ident, quote};
9+
use quote::{format_ident, quote, quote_spanned};
1010
use syn::punctuated::Punctuated;
1111
use syn::spanned::Spanned;
1212
use syn::token::AndAnd;
1313
use syn::{
14-
BinOp, Block, Expr, ExprBinary, Ident, Stmt, Token, parse_macro_input, parse_quote,
14+
BinOp, Block, Expr, ExprBinary, ExprWhile, Ident, Stmt, Token, parse_macro_input, parse_quote,
1515
visit_mut::VisitMut,
1616
};
1717

@@ -232,9 +232,32 @@ fn transform_break_continue(block: &mut Block) {
232232
block.stmts.push(return_stmt);
233233
}
234234

235+
fn while_let_rewrite(loopexpr: Stmt) -> Stmt {
236+
if let Stmt::Expr(ref expr, _) = loopexpr
237+
&& let Expr::While(ExprWhile { cond, body, .. }) = expr
238+
&& let Expr::Let(ref let_expr) = **cond
239+
{
240+
let pat = &let_expr.pat;
241+
let scrutinee = &let_expr.expr;
242+
243+
// Transform to loop with match
244+
return parse_quote! {
245+
loop {
246+
match #scrutinee {
247+
#pat => #body,
248+
_ => break,
249+
}
250+
};
251+
};
252+
}
253+
254+
loopexpr.clone()
255+
}
256+
235257
pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
236258
// parse the stmt of the loop
237259
let mut loop_stmt: Stmt = syn::parse(item.clone()).unwrap();
260+
loop_stmt = while_let_rewrite(loop_stmt);
238261

239262
// name of the loop invariant as closure of the form
240263
// __kani_loop_invariant_#startline_#startcol_#endline_#endcol
@@ -244,6 +267,7 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
244267

245268
// expr of the loop invariant
246269
let mut inv_expr: Expr = syn::parse(attr).unwrap();
270+
let original_span = inv_expr.span();
247271

248272
// adding on_entry variables
249273
let mut onentry_var_prefix: String = "__kani_onentry_var".to_owned();
@@ -302,7 +326,7 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
302326
&mut Stmt::Expr(ref mut e, _) => match *e {
303327
Expr::While(ref mut ew) => {
304328
let new_cond: Expr = syn::parse(
305-
quote!(
329+
quote_spanned!(original_span =>
306330
#register_ident(&||->bool{#inv_expr}, 0))
307331
.into(),
308332
)
@@ -316,7 +340,9 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
316340
}
317341
Expr::Loop(ref mut el) => {
318342
//let retexpr = get_return_statement(&el.body);
319-
let invstmt: Stmt = syn::parse(quote!(if !(#register_ident(&||->bool{#inv_expr}, 0)) {assert!(false); unreachable!()};).into()).unwrap();
343+
let invstmt: Stmt = syn::parse(quote_spanned!(original_span =>
344+
if !(#register_ident(&||->bool{#inv_expr}, 0)) {assert!(false); unreachable!()};)
345+
.into()).unwrap();
320346
let mut new_stmts: Vec<Stmt> = Vec::new();
321347
new_stmts.push(invstmt);
322348
new_stmts.extend(el.body.stmts.clone());
@@ -334,7 +360,7 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
334360
}
335361

336362
if has_prev {
337-
quote!(
363+
quote_spanned!(original_span =>
338364
{
339365
if (#loop_guard) {
340366
#(#onentry_decl_stms)*
@@ -363,7 +389,7 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
363389
})
364390
.into()
365391
} else {
366-
quote!(
392+
quote_spanned!(original_span =>
367393
{
368394
#(#onentry_decl_stms)*
369395
// Dummy function used to force the compiler to capture the environment.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trim_ascii_start.loop_invariant_base.1\
2+
- Status: SUCCESS\
3+
- Description: "Check invariant before entry for loop trim_ascii_start.0"
4+
5+
trim_ascii_start.loop_invariant_step.1\
6+
- Status: SUCCESS\
7+
- Description: "Check invariant after step for loop trim_ascii_start.0"
8+
9+
VERIFICATION:- SUCCESSFUL
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: -Z loop-contracts
5+
6+
//! Check if while-let invariant is correctly applied.
7+
8+
#![feature(proc_macro_hygiene)]
9+
#![feature(stmt_expr_attributes)]
10+
11+
#[kani::proof]
12+
fn trim_ascii_start() {
13+
let mut a: [u8; 10] = kani::any();
14+
let s = a.as_slice();
15+
let mut bytes = s;
16+
#[kani::loop_invariant(
17+
bytes.len() <= s.len() &&
18+
(bytes.len() == 0 || (&s[s.len()-bytes.len()..]).as_ptr() == bytes.as_ptr())
19+
)]
20+
while let [first, rest @ ..] = bytes {
21+
if first.is_ascii_whitespace() {
22+
bytes = rest;
23+
} else {
24+
break;
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)