- 
                Notifications
    You must be signed in to change notification settings 
- Fork 286
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.