Skip to content

Commit e610a09

Browse files
committed
add SpecBot invariant checks
1 parent c9b9c8c commit e610a09

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

src/page-queue.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {
246246
}
247247

248248

249+
#if MI_DEBUG >= 3
250+
static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue);
251+
#endif
252+
249253
static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
250254
mi_assert_internal(mi_page_heap(page) == heap);
251255
mi_assert_internal(!mi_page_queue_contains(queue, page));
@@ -272,8 +276,35 @@ static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_
272276
// update direct
273277
mi_heap_queue_first_update(heap, queue);
274278
heap->page_count++;
279+
280+
// [specbot Q-NEW-1] first/last must be both null or both non-null (L1, O(1))
281+
mi_assert_internal((queue->first == NULL) == (queue->last == NULL));
282+
// [specbot Q-NEW-2] last element must have no next (L1, O(1))
283+
mi_assert_internal(queue->last == NULL || queue->last->next == NULL);
284+
mi_assert_expensive(mi_page_queue_is_consistent(queue));
275285
}
276286

287+
#if MI_DEBUG >= 3
288+
// [specbot Q-NEW-3] Verify doubly-linked list forward/backward consistency (L2, O(n))
289+
static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue) {
290+
if (queue->first == NULL) return (queue->last == NULL);
291+
if (queue->last == NULL) return false;
292+
// forward: first -> ... -> last
293+
const mi_page_t* p = queue->first;
294+
const mi_page_t* prev = NULL;
295+
size_t count = 0;
296+
while (p != NULL) {
297+
mi_assert_internal(p->prev == prev);
298+
prev = p;
299+
p = p->next;
300+
count++;
301+
if (count > 100000) return false; // cycle guard
302+
}
303+
mi_assert_internal(prev == queue->last);
304+
return true;
305+
}
306+
#endif
307+
277308
static void mi_page_queue_move_to_front(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
278309
mi_assert_internal(mi_page_heap(page) == heap);
279310
mi_assert_internal(mi_page_queue_contains(queue, page));

src/page.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,17 @@ static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) {
7979

8080
static bool mi_page_is_valid_init(mi_page_t* page) {
8181
mi_assert_internal(mi_page_block_size(page) > 0);
82+
8283
mi_assert_internal(page->used <= page->capacity);
8384
mi_assert_internal(page->capacity <= page->reserved);
8485

86+
// [specbot P-NEW-1] block_size_shift must be consistent with block_size (L1, O(1))
87+
mi_assert_internal(page->block_size_shift == 0 || (mi_page_block_size(page) == ((size_t)1 << page->block_size_shift)));
88+
// [specbot P-NEW-2] capacity must be nonzero when blocks are in use (L1, O(1))
89+
mi_assert_internal(page->used == 0 || page->capacity > 0);
90+
// [specbot P-NEW-4] page_start must be non-null when capacity > 0 (L1, O(1))
91+
mi_assert_internal(page->capacity == 0 || mi_page_start(page) != NULL);
92+
8593
uint8_t* start = mi_page_start(page);
8694
mi_assert_internal(start == _mi_segment_page_start(_mi_page_segment(page), page, NULL));
8795
mi_assert_internal(page->is_huge == (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE));
@@ -90,6 +98,18 @@ static bool mi_page_is_valid_init(mi_page_t* page) {
9098
mi_assert_internal(mi_page_list_is_valid(page,page->free));
9199
mi_assert_internal(mi_page_list_is_valid(page,page->local_free));
92100

101+
// [specbot P-NEW-7] All free list blocks are aligned to block_size (L2, O(n))
102+
{
103+
size_t bsize = mi_page_block_size(page);
104+
uint8_t* pstart = mi_page_start(page);
105+
for (mi_block_t* b = page->free; b != NULL; b = mi_block_next(page, b)) {
106+
mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0);
107+
}
108+
for (mi_block_t* b = page->local_free; b != NULL; b = mi_block_next(page, b)) {
109+
mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0);
110+
}
111+
}
112+
93113
#if MI_DEBUG>3 // generally too expensive to check this
94114
if (page->free_is_zero) {
95115
const size_t ubsize = mi_page_usable_block_size(page);
@@ -122,6 +142,9 @@ bool _mi_page_is_valid(mi_page_t* page) {
122142
if (mi_page_heap(page)!=NULL) {
123143
mi_segment_t* segment = _mi_page_segment(page);
124144

145+
// [specbot P-NEW-6] heap_tag must match owning heap's tag (L1, O(1))
146+
mi_assert_internal(page->heap_tag == mi_page_heap(page)->tag);
147+
125148
mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id);
126149
#if MI_HUGE_PAGE_ABANDON
127150
if (segment->kind != MI_SEGMENT_HUGE)

src/segment.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,23 @@ static bool mi_segment_is_valid(mi_segment_t* segment, mi_segments_tld_t* tld) {
275275
mi_assert_internal(segment->abandoned <= segment->used);
276276
mi_assert_internal(segment->thread_id == 0 || segment->thread_id == _mi_thread_id());
277277
mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); // can only decommit committed blocks
278+
279+
// [specbot S-NEW-1] segment must have at least one slice (L1, O(1))
280+
mi_assert_internal(segment->segment_slices > 0);
281+
// [specbot S-NEW-2] info slices must leave room for at least one data slice (L1, O(1))
282+
mi_assert_internal(segment->segment_info_slices < segment->segment_slices);
283+
// [specbot S-NEW-3] slice_entries must not exceed array bounds (L1, O(1))
284+
mi_assert_internal(segment->slice_entries <= MI_SLICES_PER_SEGMENT);
285+
278286
//mi_assert_internal(segment->segment_info_size % MI_SEGMENT_SLICE_SIZE == 0);
279287
mi_slice_t* slice = &segment->slices[0];
280288
const mi_slice_t* end = mi_segment_slices_end(segment);
281289
size_t used_count = 0;
290+
size_t total_slice_count = 0;
282291
mi_span_queue_t* sq;
283292
while(slice < end) {
284293
mi_assert_internal(slice->slice_count > 0);
294+
total_slice_count += slice->slice_count;
285295
mi_assert_internal(slice->slice_offset == 0);
286296
size_t index = mi_slice_index(slice);
287297
size_t maxindex = (index + slice->slice_count >= segment->slice_entries ? segment->slice_entries : index + slice->slice_count) - 1;
@@ -316,6 +326,8 @@ static bool mi_segment_is_valid(mi_segment_t* segment, mi_segments_tld_t* tld) {
316326
slice = &segment->slices[maxindex+1];
317327
}
318328
mi_assert_internal(slice == end);
329+
// [specbot S-NEW-5] Total slice counts must sum to segment_slices (L2, O(n))
330+
mi_assert_internal(total_slice_count == segment->segment_slices);
319331
mi_assert_internal(used_count == segment->used + 1);
320332
return true;
321333
}

0 commit comments

Comments
 (0)