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 | } |