File: aws_hash_table_no_slots_override.c

package info (click to toggle)
aws-crt-python 0.20.4%2Bdfsg-1~bpo12%2B1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-backports
  • size: 72,656 kB
  • sloc: ansic: 381,805; python: 23,008; makefile: 6,251; sh: 4,536; cpp: 699; ruby: 208; java: 77; perl: 73; javascript: 46; xml: 11
file content (245 lines) | stat: -rw-r--r-- 8,856 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include <aws/common/private/hash_table_impl.h>
#include <proof_helpers/nondet.h>

/********************************************************************************
 * This module represents a hash-table that is not backed by any actual memory
 * It just takes non-det actions when given inputs. We know this is safe because
 * we've already proven the c-common hash-table memory safe under these
 * pre/post conditions.
 *
 * Since we're making almost everything nondet, the only externally visible property
 * of the hash-table is the entry_count.
 */

/**
 * As noted above the only externally visible property of the hash-table is the [entry_count]. And it can vary between
 * 0-SIZE_MAX.  So there is really nothing to assert here */
bool aws_hash_table_is_valid(const struct aws_hash_table *map) {
    return map && map->p_impl;
}

/**
 * Given a pointer to a hash_iter, checks that it is well-formed, with all data-structure invariants.
 */
bool aws_hash_iter_is_valid(const struct aws_hash_iter *iter) {
    if (!iter) {
        return false;
    }
    if (!iter->map) {
        return false;
    }
    if (!aws_hash_table_is_valid(iter->map)) {
        return false;
    }
    if (iter->limit != iter->map->p_impl->entry_count) {
        return false;
    }

    switch (iter->status) {
        case AWS_HASH_ITER_STATUS_DONE:
            /* Done iff slot == limit */
            return iter->slot == iter->limit;
        case AWS_HASH_ITER_STATUS_DELETE_CALLED:
            /* iter->slot can underflow to SIZE_MAX after a delete
             * see the comments for aws_hash_iter_delete() */
            return iter->slot <= iter->limit;
        case AWS_HASH_ITER_STATUS_READY_FOR_USE:
            /* A slot must point to a valid location (i.e. hash_code != 0) */
            return iter->slot < iter->limit;
    }
    /* Invalid status code */
    return false;
}

/**
 * Allocate a hash_table_state with no memory for the slots.
 * Since CBMC runs with memory safety assertions on,
 * CBMC will detect any attempt to use the slots.
 * This ensures that no code will ever accidentally use the values
 * in the slots, ensuring maximal nondeterminism.
 */
void make_hash_table_with_no_backing_store(struct aws_hash_table *map, size_t max_table_entries) {
    if (map != NULL) {
        map->p_impl = malloc(sizeof(struct hash_table_state));
        __CPROVER_assume(map->p_impl != NULL);
        __CPROVER_assume(map->p_impl->entry_count <= max_table_entries);
    }
}
/**
 * Nondet clear.  Since the only externally visible property of this
 * table is the entry_count, just set it to 0
 */
void aws_hash_table_clear(struct aws_hash_table *map) {
    AWS_PRECONDITION(aws_hash_table_is_valid(map));
    struct hash_table_state *state = map->p_impl;
    state->entry_count = 0;
    AWS_POSTCONDITION(aws_hash_table_is_valid(map));
}

/**
 * Nondet put.  Since there is no backing store, we just non-determinstically either add it or don't.
 */
int aws_hash_table_put(struct aws_hash_table *map, const void *key, void *value, int *was_created) {
    AWS_PRECONDITION(aws_hash_table_is_valid(map));

    int track_was_created;
    if (was_created) {
        *was_created = track_was_created;
    }

    int rval;

    struct hash_table_state *state = map->p_impl;

    /* Avoid overflow */
    if (state->entry_count == SIZE_MAX) {
        return track_was_created ? AWS_OP_ERR : rval;
    }

    if (rval == AWS_OP_SUCCESS) {
        state->entry_count++;
    }
    AWS_POSTCONDITION(aws_hash_table_is_valid(map));
    return rval;
}

/**
 * Not yet implemented
 */
int aws_hash_table_remove_element(struct aws_hash_table *map, struct aws_hash_element *p_value);

/**
 * Not yet implemented
 */
int aws_hash_table_remove(
    struct aws_hash_table *map,
    const void *key,
    struct aws_hash_element *p_value,
    int *was_present);

/**
 * Not yet implemented
 */
int aws_hash_table_create(
    struct aws_hash_table *map,
    const void *key,
    struct aws_hash_element **p_elem,
    int *was_created);

/**
 * Implements a version of aws_hash_table_find() that non-determinstially either:
 *   1. Return NULL, indicating that the element didn't exist
 *   2. Returns a newly created element.  By default, just create a totally non-determinstic element.
 *      However, if the consumer of the stub uses the element, this may be insufficient.  Use the same
 *      style of generator as the hash_iterator stubs, except with a different signature due to the different
 *      calling context.
 *
 * To declare a genarator:
 * -DHASH_TABLE_FIND_ELEMENT_GENERATOR=the_generator_fn, where the_generator_fn has signature:
 *   the_generator_fnconst struct aws_hash_table *map, const void *key, struct aws_hash_element *p_elem).
 *
 * NOTE: If you want a version of aws_hash_table_find() that that ensures that the table actually has the found value
 * when find returns success, that can be found in aws_hash_table_find_override.c
 */
#ifdef HASH_TABLE_FIND_ELEMENT_GENERATOR
void HASH_TABLE_FIND_ELEMENT_GENERATOR(
    const struct aws_hash_table *map,
    const void *key,
    struct aws_hash_element *p_elem);
#endif

int aws_hash_table_find(const struct aws_hash_table *map, const void *key, struct aws_hash_element **p_elem) {
    AWS_PRECONDITION(aws_hash_table_is_valid(map), "Input hash_table [map] must be valid.");
    AWS_PRECONDITION(AWS_OBJECT_PTR_IS_WRITABLE(p_elem), "Input pointer [p_elem] must be writable.");
    const struct hash_table_state *const state = map->p_impl;
    if (nondet_bool()) {
        *p_elem = NULL;
    } else {
        *p_elem = malloc(sizeof(struct aws_hash_element));
#ifdef HASH_TABLE_FIND_ELEMENT_GENERATOR
        HASH_TABLE_FIND_ELEMENT_GENERATOR(map, key, *p_elem);
#endif
    }
    AWS_POSTCONDITION(aws_hash_table_is_valid(map), "Output hash_table [map] must be valid.");
    return AWS_OP_SUCCESS;
}

#ifdef HASH_ITER_ELEMENT_GENERATOR
void HASH_ITER_ELEMENT_GENERATOR(struct aws_hash_iter *new_iter, const struct aws_hash_iter *old_iter);
#endif

/* Simple map for what the iterator does: it just chugs along, returns a nondet value, until its gone at most
 * map->entry_count times */
struct aws_hash_iter aws_hash_iter_begin(const struct aws_hash_table *map) {
    /* Leave it as non-det as possible */
    AWS_PRECONDITION(aws_hash_table_is_valid(map));

    /* Build a nondet iterator, set the required fields, and return it */
    struct aws_hash_iter rval;
    rval.map = map;
    rval.limit = map->p_impl->entry_count;
    rval.slot = 0;
    rval.status = (rval.slot == rval.limit) ? AWS_HASH_ITER_STATUS_DONE : AWS_HASH_ITER_STATUS_READY_FOR_USE;
#ifdef HASH_ITER_ELEMENT_GENERATOR
    HASH_ITER_ELEMENT_GENERATOR(&rval, NULL);
#endif
    return rval;
}

bool aws_hash_iter_done(const struct aws_hash_iter *iter) {
    AWS_PRECONDITION(aws_hash_iter_is_valid(iter));
    AWS_PRECONDITION(
        iter->status == AWS_HASH_ITER_STATUS_DONE || iter->status == AWS_HASH_ITER_STATUS_READY_FOR_USE,
        "Input aws_hash_iter [iter] status should either be done or ready to use.");
    bool rval = iter->slot == iter->limit;
    assert(rval == (iter->status == AWS_HASH_ITER_STATUS_DONE));
    return rval;
}

void aws_hash_iter_next(struct aws_hash_iter *iter) {
    AWS_PRECONDITION(aws_hash_iter_is_valid(iter));

    if (iter->slot == iter->limit) {
        iter->status = AWS_HASH_ITER_STATUS_DONE;
        return;
    }

    /* Build a nondet iterator, set the required fields, and copy it over */
    struct aws_hash_iter rval;
    rval.map = iter->map;
    rval.limit = iter->limit;
    rval.slot = iter->slot + 1;
    rval.status = (rval.slot == rval.limit) ? AWS_HASH_ITER_STATUS_DONE : AWS_HASH_ITER_STATUS_READY_FOR_USE;
#ifdef HASH_ITER_ELEMENT_GENERATOR
    HASH_ITER_ELEMENT_GENERATOR(&rval, iter);
#endif

    *iter = rval;
}

/* delete always leaves the element unusable, so we'll just leave the element totally nondet */
void aws_hash_iter_delete(struct aws_hash_iter *iter, bool destroy_contents) {
    AWS_PRECONDITION(aws_hash_iter_is_valid(iter));
    AWS_PRECONDITION(
        iter->status == AWS_HASH_ITER_STATUS_READY_FOR_USE, "Input aws_hash_iter [iter] must be ready for use.");
    AWS_PRECONDITION(aws_hash_iter_is_valid(iter));
    AWS_PRECONDITION(
        iter->map->p_impl->entry_count > 0,
        "The hash_table_state pointed by input [iter] must contain at least one entry.");

    /* reduce the size of the underlying map */
    iter->map->p_impl->entry_count--;

    /* Build a nondet iterator, set the required fields, and copy it over */
    struct aws_hash_iter rval;
    rval.map = iter->map;
    rval.slot = iter->slot;
    rval.limit = iter->limit - 1;
    rval.status = AWS_HASH_ITER_STATUS_DELETE_CALLED;
    *iter = rval;
}