-
Notifications
You must be signed in to change notification settings - Fork 280
Description
Version: CBMC 6.7.1
OS: Ubuntu 24.04
I'm trying to write function contracts for a quicksort program, and I'm running into some difficulties verifying contracts. This is my program, qsort.c
:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stddef.h>
#include <limits.h>
// Function that swaps the integer values pointed to by a and b
void swap(int* a, int* b)
__CPROVER_requires(__CPROVER_r_ok(a) && __CPROVER_r_ok(b) && \
__CPROVER_w_ok(a) && __CPROVER_w_ok(b))
__CPROVER_ensures(*a == __CPROVER_old(*b) && *b == __CPROVER_old(*a))
__CPROVER_assigns(*a, *b)
{
int t = *a;
*a = *b;
*b = t;
}
// Divides the array into two partitions
int partition (int arr[], int low, int high)
__CPROVER_requires(arr != NULL && \
low >= 0 && high >= 0 && low <= high && high < INT_MAX && \
__CPROVER_is_fresh(arr, sizeof(int) * (high + 1)))
__CPROVER_ensures(__CPROVER_return_value >= low && __CPROVER_return_value <= high)
__CPROVER_assigns(__CPROVER_object_whole(arr))
{
int pivot = arr[high]; // Choose the last element as the pivot
int i = low - 1; // Temporary pivot index
for (int j = low; j <= high - 1; j++) {
// If the current element is less than or equal to the pivot
if (arr[j] <= pivot) {
// Move the temporary pivot index forward
i++;
// Swap the current element with the element at the temporary pivot index
swap(&arr[i], &arr[j]);
}
}
// Swap the pivot with the last element
swap(&arr[i + 1], &arr[high]);
return i + 1; // the pivot index
}
// Sorts (a portion of) an array, divides it into partitions, then sorts those
void quickSort(int arr[], int low, int high)
__CPROVER_requires(arr != NULL && \
low >= 0 && high >= 0 && low <= high && high < INT_MAX && \
__CPROVER_is_fresh(arr, sizeof(int) * (high + 1)))
__CPROVER_ensures(
__CPROVER_forall{int i; (low <= i && i < high) ==> arr[i] <= arr[i + 1]}
)
__CPROVER_assigns(__CPROVER_object_whole(arr))
{
// Ensure indices are in correct order
if (low < high) {
// Partition array and get the pivot index
int i = partition(arr, low, high);
// Sort the two partitions
quickSort(arr, low, i - 1); // Left side of pivot
quickSort(arr, i + 1, high); // Right side of pivot
}
}
I'm then compiling these to goto programs with goto-cc
, instrumenting them to enforce contracts using goto-instrument
, and finally running verification for each function with cbmc
. This process works fine for swap
and partition
, but when I try it for quickSort
, it fails.
(Note - ...
indicates that some lines of output have been omitted.)
> goto-cc -o quicksort.goto qsort.c --function quickSort
...
> goto-instrument --partial-loops --unwind 5 quicksort.goto quicksort.goto
> goto-instrument --replace-call-with-contract partition quicksort.goto quicksort.goto
> goto-instrument --replace-call-with-contract swap quicksort.goto quicksort.goto
> goto-instrument --enforce-contract-rec quickSort quicksort.goto checking-quicksort-contracts.goto
> cbmc checking-quicksort-contracts.goto --function quickSort --depth 100
...
...
Running propositional reduction
SAT checker: instance is UNSATISFIABLE
** Results:
qsort_gold_specs.c function partition
...
[partition.precondition.1] line 62 Check requires clause of partition in quickSort: FAILURE
qsort_gold_specs.c function quickSort
[quickSort.overflow.1] line 64 arithmetic overflow on signed - in i - 1: FAILURE
[quickSort.overflow.2] line 65 arithmetic overflow on signed + in i + 1: FAILURE
qsort_gold_specs.c function swap
...
** 3 of 508 failed (4 iterations)
VERIFICATION FAILED
To investigate what was exactly was failing, I added assertions just before the call to partition
from quickSort
, like this:
void quickSort(int arr[], int low, int high)
__CPROVER_requires(arr != NULL && \
low >= 0 && high >= 0 && low <= high && high < INT_MAX && \
__CPROVER_is_fresh(arr, sizeof(int) * (high + 1)))
__CPROVER_ensures(
__CPROVER_forall{int i; (low <= i && i < high) ==> arr[i] <= arr[i + 1]}
)
__CPROVER_assigns(__CPROVER_object_whole(arr))
{
// Ensure indices are in correct order
if (low < high) {
__CPROVER_assert(arr != NULL, "arr non-NULL");
__CPROVER_assert(low >= 0, "low ≥ 0");
__CPROVER_assert(high >= 0, "high ≥ 0");
__CPROVER_assert(low <= high, "low ≤ high");
__CPROVER_assert(high < INT_MAX, "high < INT_MAX");
// Partition array and get the pivot index
int i = partition(arr, low, high);
// Sort the two partitions
quickSort(arr, low, i - 1); // Left side of pivot
quickSort(arr, i + 1, high); // Right side of pivot
}
}
Surprisingly, all the assertions failed except for low ≤ high
.
qsort_gold_specs.c function quickSort
[quickSort.assertion.1] line 61 arr non-NULL: FAILURE
[quickSort.assertion.2] line 62 low ≥ 0: FAILURE
[quickSort.assertion.3] line 63 high ≥ 0: FAILURE
[quickSort.assertion.4] line 64 low ≤ high: SUCCESS
[quickSort.assertion.5] line 65 high < INT_MAX: FAILURE
[quickSort.overflow.1] line 69 arithmetic overflow on signed - in i - 1: FAILURE
[quickSort.overflow.2] line 70 arithmetic overflow on signed + in i + 1: FAILURE
This indicates that the predicates in __CPROVER_requires
are not being assumed at all when verifying the function body. I observed this behaviour only for the recursive function quickSort
using --enforce-contract-rec
, not with the other functions.