-
Notifications
You must be signed in to change notification settings - Fork 274
Multidimensional decreases clauses #6236
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#include <stdio.h> | ||
|
||
int ackermann(int m, int n); | ||
|
||
int main() | ||
{ | ||
int m = 3; | ||
int n = 5; | ||
int result = ackermann(m, n); | ||
|
||
printf("Result of the Ackermann function: %d\n", result); | ||
return 0; | ||
} | ||
|
||
int ackermann(int m, int n) | ||
// clang-format off | ||
__CPROVER_requires(0 <= m && 0 <= n) | ||
__CPROVER_ensures(__CPROVER_return_value >= 0) | ||
// clang-format on | ||
{ | ||
while(m > 0) | ||
// clang-format off | ||
__CPROVER_loop_invariant(0 <= m && 0 <= n) | ||
__CPROVER_decreases(m, n) | ||
// clang-format on | ||
{ | ||
if(n == 0) | ||
{ | ||
m--; | ||
n = 1; | ||
} | ||
else | ||
{ | ||
n = ackermann(m, n - 1); | ||
m--; | ||
} | ||
} | ||
|
||
return n + 1; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
CORE | ||
main.c | ||
--apply-loop-contracts --replace-all-calls-with-contracts | ||
^\[precondition.1\] .* Check requires clause: SUCCESS$ | ||
^\[precondition.2\] .* Check requires clause: SUCCESS$ | ||
^\[ackermann.1\] .* Check loop invariant before entry: SUCCESS$ | ||
^\[ackermann.2\] .* Check that loop invariant is preserved: SUCCESS$ | ||
^\[ackermann.3\] .* Check decreases clause on loop iteration: SUCCESS$ | ||
^VERIFICATION SUCCESSFUL$ | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
It tests whether we can prove (only partially) the termination of the Ackermann | ||
function using a multidimensional decreases clause. | ||
|
||
Note that this particular implementation of the Ackermann function contains | ||
both a while-loop and recursion. Therefore, to fully prove the termination of | ||
the Ackermann function, we must prove both | ||
(i) the termination of the while-loop and | ||
(ii) the termination of the recursion. | ||
Because CBMC does not support termination proofs of recursions (yet), we cannot | ||
prove the latter, but the former. Hence, the termination proof in the code is | ||
only "partial." | ||
|
||
Furthermore, the Ackermann function has a function contract that the result | ||
is always non-negative. This post-condition is necessary for establishing | ||
the loop invariant. However, in this test, we do not enforce the function | ||
contract. Instead, we assume that the function contract is correct and use it | ||
(i.e. replace a recursive call of the Ackermann function with its contract). | ||
|
||
We cannot verify/enforce the function contract of the Ackermann function, since | ||
CBMC does not support function contracts for recursively defined functions. | ||
As of now, CBMC only supports function contracts for non-recursive functions. | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
int main() | ||
{ | ||
int i = 0; | ||
int j = 0; | ||
int N = 10; | ||
while(i < N) | ||
// clang-format off | ||
__CPROVER_loop_invariant(0 <= i && i <= N && 0 <= j && j <= N) | ||
__CPROVER_decreases(N - i, j) | ||
// clang-format on | ||
{ | ||
if(j < N) | ||
{ | ||
j++; | ||
} | ||
else | ||
{ | ||
i++; | ||
j = 0; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
CORE | ||
main.c | ||
--apply-loop-contracts | ||
^\[main.1\] .* Check loop invariant before entry: SUCCESS$ | ||
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$ | ||
^\[main.3\] .* Check decreases clause on loop iteration: FAILURE$ | ||
^VERIFICATION FAILED$ | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
LongPham7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-- | ||
-- | ||
This test fails because the given multidimensional decreases clause does not | ||
monotonically decrease. A mistake is in the second component of the | ||
decreases clause: j. It should instead be N - j. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
int main() | ||
{ | ||
int i = 0; | ||
int j = 0; | ||
int N = 10; | ||
while(i < N) | ||
// clang-format off | ||
__CPROVER_loop_invariant(0 <= i && i <= N && 0 <= j && j <= N) | ||
__CPROVER_decreases(i, N - j) | ||
// clang-format on | ||
{ | ||
if(j < N) | ||
{ | ||
j++; | ||
} | ||
else | ||
{ | ||
i++; | ||
j = 0; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
CORE | ||
main.c | ||
--apply-loop-contracts | ||
^\[main.1\] .* Check loop invariant before entry: SUCCESS$ | ||
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$ | ||
^\[main.3\] .* Check decreases clause on loop iteration: FAILURE$ | ||
^VERIFICATION FAILED$ | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
LongPham7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-- | ||
-- | ||
This test fails because the given multidimensional decreases clause does not | ||
monotonically decrease. A mistake is in the first component of the | ||
decreases clause: i. It should instead be N - i. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
int main() | ||
{ | ||
int i = 0; | ||
int j = 0; | ||
int N = 10; | ||
while(i < N) | ||
// clang-format off | ||
__CPROVER_loop_invariant(0 <= i && i <= N && 0 <= j && j <= N) | ||
__CPROVER_decreases(N - i, N - j) | ||
// clang-format on | ||
{ | ||
if(j < N) | ||
{ | ||
j++; | ||
} | ||
else | ||
{ | ||
i++; | ||
j = 0; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
CORE | ||
main.c | ||
--apply-loop-contracts | ||
^\[main.1\] .* Check loop invariant before entry: SUCCESS$ | ||
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$ | ||
^\[main.3\] .* Check decreases clause on loop iteration: SUCCESS$ | ||
^VERIFICATION SUCCESSFUL$ | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
LongPham7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
-- | ||
-- | ||
It tests whether a multidimensional decreases clause works properly. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
CORE | ||
main.c | ||
--apply-loop-contracts | ||
activate-multi-line-match | ||
LongPham7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
^main.c.*error: syntax error before '__CPROVER_decreases'\n.*__CPROVER_decreases\(42\)$ | ||
^PARSING ERROR$ | ||
^EXIT=(1|64)$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test fails because we have multiple decreases clauses. CBMC only admits | ||
one decreases clause for each loop. If one wants to write a multidimensional | ||
decreases clause, it should be placed inside a single __CPROVER_decreases(...), | ||
where each component of the multidimensional decreases clause is separated by a | ||
comma. Hence, in this test, the correct syntax is | ||
__CPROVER_decreases(N - i, 42). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! :) These test.desc files are very well written. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we support multiple decreases clauses in the future? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unclear as of now.
Moreover,
reads as "both" X and Y monotonically decrease at each iteration, but for termination we simply need either one to monotonically decrease, and this sort of annotation would just be confusing. |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,14 @@ | ||
CORE | ||
main.c | ||
--apply-loop-contracts | ||
^main.c function main$ | ||
^\[main.1\] .* Check loop invariant before entry: SUCCESS$ | ||
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$ | ||
^\[main.3\] .* Check decreases clause on loop iteration: SUCCESS$ | ||
^.*0 of 3 failed \(1 iterations\)$ | ||
^VERIFICATION SUCCESSFUL$ | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test succeeds. The purpose of this test is to check whether a decreases | ||
clause can successfully prove the termination of a loop (i) whose exit condition | ||
is 1 == 1 (i.e. true) and (ii) that will eventually exit via a break statement. | ||
The purpose of this test is to check whether a decreases clause can | ||
successfully prove the termination of a loop (i) whose exit condition is | ||
1 == 1 (i.e. true) and (ii) that will eventually exit via a break statement. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -828,9 +828,11 @@ void c_typecheck_baset::typecheck_spec_decreases(codet &code) | |
{ | ||
if(code.find(ID_C_spec_decreases).is_not_nil()) | ||
{ | ||
exprt &variant = static_cast<exprt &>(code.add(ID_C_spec_decreases)); | ||
|
||
typecheck_expr(variant); | ||
implicit_typecast_arithmetic(variant); | ||
for(auto &decreases_clause_component : | ||
(static_cast<exprt &>(code.add(ID_C_spec_decreases)).operands())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any guarantees that this cast is safe? Take a look at this comment #5403 (comment). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. As suggested on the other thread, we should create "safe" accessors at some point. However, currently, this issue also applies to invariant contracts, so may be we can table this change for a separate future PR and fix both together? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the link. For now, I will just leave it as it is. When someone cleans up the code for loop invariants, then we will also clean the code for decreases clauses. |
||
{ | ||
typecheck_expr(decreases_clause_component); | ||
implicit_typecast_arithmetic(decreases_clause_component); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kudos for your bar-raising test descriptions! @LongPham7