Skip to content

Commit b1532df

Browse files
authored
feat: rich external loop invariant error messages (#342)
* feat: rich error messages * cleanup * cleanup * feat: added types and 1 more test
1 parent 9487f94 commit b1532df

File tree

4 files changed

+73
-10
lines changed

4 files changed

+73
-10
lines changed

crates/move-stackless-bytecode/src/move_loop_invariants.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88

99
use move_model::{
1010
model::{FunId, FunctionEnv, GlobalEnv, QualifiedId},
11+
symbol::Symbol,
1112
ty::{PrimitiveType, Type},
1213
};
1314

@@ -170,6 +171,15 @@ impl MoveLoopInvariantsProcessor {
170171
!env.has_errors()
171172
}
172173

174+
fn parse_var_name(name: &Symbol, builder: &FunctionDataBuilder) -> String {
175+
let name_str = builder.fun_env.symbol_pool().string(*name);
176+
if name_str.contains('#') {
177+
name_str.split('#').next().unwrap().to_string()
178+
} else {
179+
name_str.to_string()
180+
}
181+
}
182+
173183
fn match_invariant_arguments(
174184
builder: &FunctionDataBuilder,
175185
loop_inv_env: &FunctionEnv,
@@ -187,12 +197,9 @@ impl MoveLoopInvariantsProcessor {
187197
// Note: builder.data.name_to_index usually looks like
188198
// n -> 0, i#1#0 -> 1, s#1#0 -> 2
189199
for (name, &idx) in &builder.data.name_to_index {
190-
let name_str = builder.fun_env.symbol_pool().string(*name);
191-
if let Some(base_name) = name_str.split('#').next() {
192-
if base_name == param_name_str.as_ref() {
193-
found_idx = Some(idx);
194-
break;
195-
}
200+
if Self::parse_var_name(name, builder) == *param_name_str {
201+
found_idx = Some(idx);
202+
break;
196203
}
197204
}
198205
}
@@ -217,10 +224,22 @@ impl MoveLoopInvariantsProcessor {
217224
Severity::Error,
218225
&builder.fun_env.get_loc(),
219226
&format!(
220-
"Loop invariant function {} expects parameter '{}' which is not found in function {}",
227+
"Loop invariant function {} expects parameter '{}' which is not found in function {}.\nAvailable variables: ( {} )",
221228
loop_inv_env.get_full_name_str(),
222229
param_name_str,
223-
builder.fun_env.get_full_name_str()
230+
builder.fun_env.get_full_name_str(),
231+
builder.data.name_to_index
232+
.iter()
233+
.map(|(name, idx)|
234+
format!(
235+
"{}: {}",
236+
Self::parse_var_name(name, builder),
237+
builder
238+
.get_local_type(*idx)
239+
.display(&builder.fun_env.module_env.env.get_type_display_ctx())
240+
.to_string()
241+
)
242+
).join(", ")
224243
),
225244
);
226245
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module 0x42::loop_invariant_external_invalid_arg_name_macro_fail;
2+
3+
use prover::prover::ensures;
4+
5+
macro fun test_loop($n: u64): u128 {
6+
let mut s: u128 = 0;
7+
let mut i = 0;
8+
9+
while (i < $n) {
10+
i = i + 1;
11+
s = s + (i as u128);
12+
};
13+
s
14+
}
15+
16+
#[spec_only(loop_inv(target = test_spec))]
17+
#[ext(no_abort)]
18+
fun loop_inv(i: u64, n: u64, compare: u128): bool {
19+
i <= n && (compare == (i as u128) * ((i as u128) + 1) / 2)
20+
}
21+
22+
#[spec(prove)]
23+
fun test_spec(n: u64): u128 {
24+
let res = test_loop!(n);
25+
ensures(res == (n as u128) * ((n as u128) + 1) / 2);
26+
res
27+
}

crates/sui-prover/tests/snapshots/loop_invariant/external_invalid_arg_name.move.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
---
22
source: crates/sui-prover/tests/integration.rs
3-
assertion_line: 262
3+
assertion_line: 271
44
expression: output
55
---
66
exiting with bytecode transformation errors
7-
error: Loop invariant function loop_invariant_external_invalid_arg_name_fail::loop_inv expects parameter 'compare' which is not found in function loop_invariant_external_invalid_arg_name_fail::test_spec
7+
error: Loop invariant function loop_invariant_external_invalid_arg_name_fail::loop_inv expects parameter 'compare' which is not found in function loop_invariant_external_invalid_arg_name_fail::test_spec.
8+
Available variables: ( n: u64, i: u64, s: u128 )
89
┌─ tests/inputs/loop_invariant/external_invalid_arg_name.move:12:1
910
1011
12 │ ╭ fun test_spec(n: u64): u128 {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
source: crates/sui-prover/tests/integration.rs
3+
assertion_line: 271
4+
expression: output
5+
---
6+
exiting with bytecode transformation errors
7+
error: Loop invariant function loop_invariant_external_invalid_arg_name_macro_fail::loop_inv expects parameter 'compare' which is not found in function loop_invariant_external_invalid_arg_name_macro_fail::test_spec.
8+
Available variables: ( n: u64, i: u64, res: u128, s: u128 )
9+
┌─ tests/inputs/loop_invariant/external_invalid_arg_name_macro.move:23:1
10+
11+
23 │ ╭ fun test_spec(n: u64): u128 {
12+
24 │ │ let res = test_loop!(n);
13+
25 │ │ ensures(res == (n as u128) * ((n as u128) + 1) / 2);
14+
26 │ │ res
15+
27 │ │ }
16+
│ ╰─^

0 commit comments

Comments
 (0)