-
Notifications
You must be signed in to change notification settings - Fork 100
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
proof_for_contract
rejects code accepted by proof
#3796
Comments
Hi @BusyBeaver-42. Thanks for filing this issue, and thanks for the minimal example! Apparently, the logic for checking if the variables are assignable is incorrectly applied to a local variable. We will take a look. |
I diagnosed two problems:
StorageLive problemThe instrumented goto program for the function looks like this:
DECL var_5::iter : Range<i32>
CALL __CPROVER_contracts_write_set_add_decl(__write_set_to_check, address_of(var_5::iter))
DECL var_11::iter : Range<i32>
CALL __CPROVER_contracts_write_set_add_decl(__write_set_to_check, address_of(var_11::iter)) They are added to the write set right after they are declared, which makes them writable. However we see that the inner iterator variable 7: ASSIGN var_10 := { 0, 2 }
CALL var_9 := into_iter(var_10, __write_set_to_check)
ASSIGN __CPROVER_dead_object := (__CPROVER_dead_object = cast(address_of(var_11::iter), empty*) ? NULL : __CPROVER_dead_object)
ASSIGN var_11::iter := var_9
8: ASSIGN var_13 := address_of(var_11::iter)
CALL var_12 := next(var_13, __write_set_to_check)
ASSIGN var_14 := cast(var_12.case, signedbv[64])
LOCATION
IF var_14 = 0 THEN GOTO 9
IF var_14 = 1 THEN GOTO 10
GOTO 11
9: GOTO 13
10: GOTO 12
11: ASSERT false // unreachable code
ASSUME false
12: GOTO 8
13: ASSIGN __CPROVER_dead_object := (side_effect statement="nondet" ? cast(address_of(var_11::iter), empty*) : __CPROVER_dead_object) The statement at label 13 encodes a CALL __CPROVER_contracts_write_set_record_dead(__write_set_to_check, cast(address_of(var_11::iter), empty*)) This can explain the failure, since the write set will only contain
ASSIGN __CPROVER_dead_object := (__CPROVER_dead_object = cast(address_of(var_11::iter), empty*) ? NULL : __CPROVER_dead_object) Right now Symex problemI also looked at Symex uses the same object // 584 file src/main.rs line 5 column 18 function proof::foo
(132) SHARED_WRITE(iter!0@1#1)
// 592 file src/main.rs line 6 column 22 function proof::foo
(133) SHARED_WRITE(iter!0@1#1) So what could also be happening is that is that the symex engine confounds the two iterators into a single iterator. When the first occurrence of the inner loop terminates the object Here's the details:
(470) SHARED_WRITE(dynamic_object$13#1)
// 481 file <builtin-library-__CPROVER_contracts_library> line 260 function __CPROVER_contracts_obj_set_create_indexed_by_object_id
(471) dynamic_object$13#1 == ARRAY_OF(NULL)
// 481 file <builtin-library-__CPROVER_contracts_library> line 260 function __CPROVER_contracts_obj_set_create_indexed_by_object_id
(472) malloc_value!0@9#2 == (const void *)dynamic_object$13
// 482 file <builtin-library-__CPROVER_contracts_library> line 254 function __CPROVER_contracts_obj_set_create_indexed_by_object_id
(486) __contract_write_set!0@1#2..allocated..elems == dynamic_object$13 Object (539) SHARED_WRITE(dynamic_object$13#2)
// 526 file <builtin-library-__CPROVER_contracts_library> line 634 function __CPROVER_contracts_write_set_add_decl
(540) dynamic_object$13#2 == ARRAY_OF(NULL) WITH [(long int)__CPROVER_POINTER_OBJECT(&iter!0@1):=(const void *)&iter!0@1]
// 526 file <builtin-library-__CPROVER_contracts_library> line 634 function __CPROVER_contracts_write_set_add_decl
(549) SHARED_WRITE(dynamic_object$13#3)
// 526 file <builtin-library-__CPROVER_contracts_library> line 634 function __CPROVER_contracts_write_set_add_decl
(550) dynamic_object$13#3 == dynamic_object$13#2 WITH [(long int)__CPROVER_POINTER_OBJECT(&iter!0@1):=(const void *)&iter!0@1] We see assigns clause checks like this (this one should pass because we know the object is in // 397 file <builtin-library-__CPROVER_contracts_library> line 784 function __CPROVER_contracts_write_set_check_assignment
(674) \guard#1 == !(dynamic_object$13#3[(long int)__CPROVER_POINTER_OBJECT(&iter!0@1)] == NULL)
// 398 file <builtin-library-__CPROVER_contracts_library> line 785 function __CPROVER_contracts_write_set_check_assignment
(675) __CPROVER_contracts_write_set_check_assignment!0#1 == TRUE
guard: \guard#1
// 401 file <builtin-library-__CPROVER_contracts_library> line 789 function __CPROVER_contracts_write_set_check_assignment
(676) __CPROVER_contracts_write_set_check_assignment!0#2 == FALSE
guard: !\guard#1
// 402 file <builtin-library-__CPROVER_contracts_library> line 789 function __CPROVER_contracts_write_set_check_assignment
// 427 file <builtin-library-__CPROVER_contracts_library> line 818 function __CPROVER_contracts_write_set_check_assignment
(677) __CPROVER_contracts_write_set_check_assignment!0#3 == \guard#1
// 427 file <builtin-library-__CPROVER_contracts_library> line 818 function __CPROVER_contracts_write_set_check_assignment
// 1119 file /Users/delmasrd/.rustup/toolchains/nightly-2024-12-18-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/range.rs line 767 column 13 function <std::ops::Range<i32> as std::iter::range::RangeIteratorImpl>::spec_next
(678) __check_lhs_assignment!0@1#2 == __CPROVER_contracts_write_set_check_assignment!0#3
// 1120 file /Users/delmasrd/.rustup/toolchains/nightly-2024-12-18-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/range.rs line 767 column 13 function <std::ops::Range<i32> as std::iter::range::RangeIteratorImpl>::spec_next
(679) ASSERT(__check_lhs_assignment!0@1#2) However later in the formula we see that // 390 file <builtin-library-__CPROVER_contracts_library> line 660 function __CPROVER_contracts_write_set_record_dead
(1061) dynamic_object$13#4 == dynamic_object$13#3 WITH [(long int)__CPROVER_POINTER_OBJECT(&iter!0@1):=NULL] And we immediately check that if the object is writable which is where the analysis fails // 397 file <builtin-library-__CPROVER_contracts_library> line 784 function __CPROVER_contracts_write_set_check_assignment
(1170) \guard#4 == !(dynamic_object$13#4[(long int)__CPROVER_POINTER_OBJECT(&iter!0@1)] == NULL)
// 398 file <builtin-library-__CPROVER_contracts_library> line 785 function __CPROVER_contracts_write_set_check_assignment
(1171) __CPROVER_contracts_write_set_check_assignment!0#10 == TRUE
guard: \guard#4
// 399 file <builtin-library-__CPROVER_contracts_library> line 785 function __CPROVER_contracts_write_set_check_assignment
// 400 file <builtin-library-__CPROVER_contracts_library> line 788 function __CPROVER_contracts_write_set_check_assignment
// 401 file <builtin-library-__CPROVER_contracts_library> line 789 function __CPROVER_contracts_write_set_check_assignment
(1172) __CPROVER_contracts_write_set_check_assignment!0#11 == FALSE
guard: !\guard#4
// 402 file <builtin-library-__CPROVER_contracts_library> line 789 function __CPROVER_contracts_write_set_check_assignment
// 427 file <builtin-library-__CPROVER_contracts_library> line 818 function __CPROVER_contracts_write_set_check_assignment
(1173) __CPROVER_contracts_write_set_check_assignment!0#12 == \guard#4
// 427 file <builtin-library-__CPROVER_contracts_library> line 818 function __CPROVER_contracts_write_set_check_assignment
// 1119 file /Users/delmasrd/.rustup/toolchains/nightly-2024-12-18-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/range.rs line 767 column 13 function <std::ops::Range<i32> as std::iter::range::RangeIteratorImpl>::spec_next
(1174) __check_lhs_assignment!0@4#2 == __CPROVER_contracts_write_set_check_assignment!0#12
// 1120 file /Users/delmasrd/.rustup/toolchains/nightly-2024-12-18-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/range.rs line 767 column 13 function <std::ops::Range<i32> as std::iter::range::RangeIteratorImpl>::spec_next
(1175) ASSERT(__check_lhs_assignment!0@4#2) From the immediate preceding context this is triggered at line 5 of the program, i.e. in the outer loop, not the inner loop. // 861 file src/main.rs line 5 column 18 function proof::foo::{closure#2}::{closure#0}
(1064) __write_set_to_check!0@5#1 == &__contract_write_set!0@1 So this would mean that symex indeed confuses both iterators. |
Looking at the translation rules from MIR to GOTO here ,
With this encoding a place is considered live by default, but is it really the case according to MIR semantics ? At GOTO level shouldn't we immediately declare the place dead after declaration ? |
Update: this CBMC PR fixes the problem diffblue/cbmc#8554, by ignoring both storageLive and storageDead events at the contract instrumentation level. This eliminates the spurious CEX caused by only tracking storageDead and not storageLive at the contracts level. |
Re-opening as this should only be closed for good once Kani actually uses the fixed CBMC version. |
I tried this code:
using the following command line invocation:
with Kani version: cargo-kani 0.57.0
I expected to see this happen:
Instead, this happened:
Note: Verification using
kani::proof
works as expected.The text was updated successfully, but these errors were encountered: