rust_verify_test coverage (7c1d655)

Coverage Report

Created: 2026-07-20 10:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
vir/src/early_exit_cf.rs
Line
Count
Source
1
use crate::ast::{CallTarget, Expr, ExprX, VirErr};
2
use crate::ast_visitor::{VisitorControlFlow, VisitorScopeMap, expr_visitor_dfs};
3
use crate::messages::Span;
4
use crate::messages::error;
5
use air::scope_map::ScopeMap;
6
7
#[derive(Clone, Debug)]
8
enum StatementType {
9
    Return,
10
    BreakOrContinue { _label: Option<String> },
11
}
12
13
#[derive(Clone, Debug)]
14
struct EarlyExitInst {
15
    span: Span,
16
    _statement: StatementType,
17
}
18
19
837
pub fn assert_no_early_exit_in_inv_block(inv_span: &Span, expr: &Expr) -> Result<(), VirErr> {
20
837
    let v = expr_get_early_exits(expr);
21
837
    if v.len() == 0 {
22
823
        Ok(())
23
    } else {
24
14
        Err(error(inv_span, "invariant block might exit early")
25
14
            .primary_label(&v[0].span, "would exit from here"))
26
    }
27
837
}
28
29
/// Walk the AST and find all return/break/continue statements that would cause an
30
/// 'early exit' from the expression. Does *not* recurse into nested Invariant blocks,
31
/// to avoid quadratic behavior, and to avoid doubling up the errors.
32
837
fn expr_get_early_exits(expr: &Expr) -> Vec<EarlyExitInst> {
33
837
    let mut v = Vec::new();
34
837
    let mut scope_map = ScopeMap::new();
35
837
    expr_get_early_exits_rec(expr, false, &mut scope_map, &mut v);
36
837
    v
37
837
}
38
39
/// While recursing, we keep track of whether we entered a loop or not; then we can know
40
/// if a break/continue would cause an exit at the high-level expr.
41
/// (Well, it will be useful when we implement break/continue, anyway.)
42
863
fn expr_get_early_exits_rec(
43
863
    expr: &Expr,
44
863
    in_loop: bool,
45
863
    scope_map: &mut VisitorScopeMap,
46
863
    results: &mut Vec<EarlyExitInst>,
47
863
) {
48
32.2k
    expr_visitor_dfs::<(), _>(expr, scope_map, &mut |scope_map, expr| {
49
32.2k
        match &expr.x {
50
            ExprX::Const(..)
51
            | ExprX::Var(..)
52
            | ExprX::VarAt(..)
53
            | ExprX::ConstVar(..)
54
            | ExprX::StaticVar(..)
55
            | ExprX::Call { target: CallTarget::Fun(..), .. }
56
            | ExprX::Call { target: CallTarget::FnSpec(..), .. }
57
            | ExprX::Call { target: CallTarget::BuiltinSpecFun(..), .. }
58
            | ExprX::Call { target: CallTarget::AssumeExternal, .. }
59
            | ExprX::ArrayLiteral(..)
60
            | ExprX::Ctor(..)
61
            | ExprX::NullaryOpr(..)
62
            | ExprX::Unary(..)
63
            | ExprX::UnaryOpr(..)
64
            | ExprX::Binary(..)
65
            | ExprX::BinaryOpr(..)
66
            | ExprX::AtomicUpdateInitDummy
67
            | ExprX::Atomically(..)
68
            | ExprX::Update(..)
69
            | ExprX::InvMask(..)
70
            | ExprX::Multi(..)
71
            | ExprX::Assign { .. }
72
            | ExprX::If(..)
73
            | ExprX::Match(..)
74
            | ExprX::Ghost { .. }
75
            | ExprX::ProofInSpec(..)
76
            | ExprX::NeverToAny { .. }
77
            | ExprX::Nondeterministic { .. }
78
            | ExprX::TwoPhaseBorrowMut(_)
79
            | ExprX::BorrowMut(_)
80
            | ExprX::BorrowMutTracked(_)
81
            | ExprX::ImplicitReborrowOrSpecRead(..)
82
            | ExprX::ReadPlace(..)
83
            | ExprX::EvalAndResolve(..)
84
            | ExprX::Old(..)
85
            | ExprX::Block(..)
86
            | ExprX::MatchGuardFreeze(..)
87
            | ExprX::ShrRefStructWrap(..)
88
32.1k
            | ExprX::Await(_) => VisitorControlFlow::Recurse,
89
            ExprX::Quant(..)
90
            | ExprX::Closure(..)
91
            | ExprX::NonSpecClosure { .. }
92
            | ExprX::ExecFnByName { .. }
93
            | ExprX::Choose { .. }
94
            | ExprX::WithTriggers { .. }
95
            | ExprX::AssertCompute(..)
96
            | ExprX::Fuel(..)
97
            | ExprX::Header(..)
98
            | ExprX::AssertAssume { .. }
99
            | ExprX::AssertAssumeUserDefinedTypeInvariant { .. }
100
            | ExprX::AssertBy { .. }
101
            | ExprX::RevealString(_)
102
41
            | ExprX::AirStmt(_) => VisitorControlFlow::Return,
103
0
            ExprX::AssertQuery { .. } => VisitorControlFlow::Return,
104
7
            ExprX::Loop { cond, body, .. } => {
105
7
                if let Some(cond) = cond {
106
3
                    expr_get_early_exits_rec(cond, in_loop, scope_map, results);
107
4
                }
108
7
                expr_get_early_exits_rec(body, true, scope_map, results);
109
7
                VisitorControlFlow::Return
110
            }
111
            ExprX::Return(_) => {
112
6
                results.push(EarlyExitInst {
113
6
                    span: expr.span.clone(),
114
6
                    _statement: StatementType::Return,
115
6
                });
116
6
                VisitorControlFlow::Recurse
117
            }
118
8
            ExprX::BreakOrContinue { label, .. } => {
119
8
                results.push(EarlyExitInst {
120
8
                    span: expr.span.clone(),
121
8
                    _statement: StatementType::BreakOrContinue { _label: label.clone() },
122
8
                });
123
8
                VisitorControlFlow::Recurse
124
            }
125
11
            ExprX::OpenInvariant(expr, ..) | ExprX::TryOpenAtomicUpdate(expr, ..) => {
126
16
                expr_get_early_exits_rec(expr, in_loop, scope_map, results);
127
                // Skip checking nested loops to avoid quadratic behavior:
128
16
                VisitorControlFlow::Return
129
            }
130
        }
131
32.2k
    });
132
863
}