Skip to content

goto-instrument --enforce-contract-rec does nothing #8702

@vikramnitin9

Description

@vikramnitin9

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions