Branch data Line data Source code
1 : : // Copyright (c) 2022 The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #ifndef BITCOIN_TEST_UTIL_POOLRESOURCETESTER_H
6 : : #define BITCOIN_TEST_UTIL_POOLRESOURCETESTER_H
7 : :
8 : : #include <support/allocators/pool.h>
9 : : #include <util/check.h>
10 : :
11 : : #include <algorithm>
12 : : #include <cassert>
13 : : #include <cstddef>
14 : : #include <cstdint>
15 : : #include <vector>
16 : :
17 : : /**
18 : : * Helper to get access to private parts of PoolResource. Used in unit tests and in the fuzzer
19 : : */
20 : : class PoolResourceTester
21 : : {
22 : : struct PtrAndBytes {
23 : : uintptr_t ptr;
24 : : std::size_t size;
25 : :
26 : 1573635 : PtrAndBytes(const void* p, std::size_t s)
27 : 1573635 : : ptr(reinterpret_cast<uintptr_t>(p)), size(s)
28 : : {
29 : : }
30 : :
31 : : /**
32 : : * defines a sort ordering by the pointer value
33 : : */
34 : 28137166 : friend bool operator<(PtrAndBytes const& a, PtrAndBytes const& b)
35 : : {
36 [ + + + + : 27865143 : return a.ptr < b.ptr;
+ + + + +
+ + + + +
+ + + + +
+ - - +
+ ]
37 : : }
38 : : };
39 : :
40 : : public:
41 : : /**
42 : : * Extracts the number of elements per freelist
43 : : */
44 : : template <std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
45 : : static std::vector<std::size_t> FreeListSizes(const PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>& resource)
46 : : {
47 : : auto sizes = std::vector<std::size_t>();
48 : : for (const auto* ptr : resource.m_free_lists) {
49 : : size_t size = 0;
50 : : while (ptr != nullptr) {
51 : : ++size;
52 : : const auto* ptr_ = ptr;
53 : : ASAN_UNPOISON_MEMORY_REGION(ptr_, sizeof(typename PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>::ListNode));
54 : : ptr = ptr->m_next;
55 : : ASAN_POISON_MEMORY_REGION(ptr_, sizeof(typename PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>::ListNode));
56 : : }
57 : : sizes.push_back(size);
58 : : }
59 : : return sizes;
60 : : }
61 : :
62 : : /**
63 : : * How many bytes are still available from the last allocated chunk
64 : : */
65 : : template <std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
66 : : static std::size_t AvailableMemoryFromChunk(const PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>& resource)
67 : : {
68 : : return resource.m_available_memory_end - resource.m_available_memory_it;
69 : : }
70 : :
71 : : /**
72 : : * Once all blocks are given back to the resource, tests that the freelists are consistent:
73 : : *
74 : : * * All data in the freelists must come from the chunks
75 : : * * Memory doesn't overlap
76 : : * * Each byte in the chunks can be accounted for in either the freelist or as available bytes.
77 : : */
78 : : template <std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
79 : 986 : static void CheckAllDataAccountedFor(const PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>& resource)
80 : : {
81 : : // collect all free blocks by iterating all freelists
82 : 986 : std::vector<PtrAndBytes> free_blocks;
83 [ + + ]: 12435 : for (std::size_t freelist_idx = 0; freelist_idx < resource.m_free_lists.size(); ++freelist_idx) {
84 : 11449 : std::size_t bytes = freelist_idx * resource.ELEM_ALIGN_BYTES;
85 : 11449 : auto* ptr = resource.m_free_lists[freelist_idx];
86 [ + + ]: 1138616 : while (ptr != nullptr) {
87 [ + - ]: 1127167 : free_blocks.emplace_back(ptr, bytes);
88 : 1127167 : const auto* ptr_ = ptr;
89 : : ASAN_UNPOISON_MEMORY_REGION(ptr_, sizeof(typename PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>::ListNode));
90 : 1127167 : ptr = ptr->m_next;
91 : : ASAN_POISON_MEMORY_REGION(ptr_, sizeof(typename PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>::ListNode));
92 : : }
93 : : }
94 : : // also add whatever has not yet been used for blocks
95 : 986 : auto num_available_bytes = resource.m_available_memory_end - resource.m_available_memory_it;
96 [ + + ]: 986 : if (num_available_bytes > 0) {
97 [ + - ]: 845 : free_blocks.emplace_back(resource.m_available_memory_it, num_available_bytes);
98 : : }
99 : :
100 : : // collect all chunks
101 : 986 : std::vector<PtrAndBytes> chunks;
102 [ + - + + ]: 446609 : for (const std::byte* ptr : resource.m_allocated_chunks) {
103 [ + - ]: 445623 : chunks.emplace_back(ptr, resource.ChunkSizeBytes());
104 : : }
105 : :
106 : : // now we have all the data from all freelists on the one hand side, and all chunks on the other hand side.
107 : : // To check if all of them match, sort by address and iterate.
108 : 986 : std::sort(free_blocks.begin(), free_blocks.end());
109 : 986 : std::sort(chunks.begin(), chunks.end());
110 : :
111 : 986 : auto chunk_it = chunks.begin();
112 : 986 : auto chunk_ptr_remaining = chunk_it->ptr;
113 : 986 : auto chunk_size_remaining = chunk_it->size;
114 [ + + ]: 1128998 : for (const auto& free_block : free_blocks) {
115 [ + + ]: 1128012 : if (chunk_size_remaining == 0) {
116 [ - + ]: 444637 : assert(chunk_it != chunks.end());
117 [ - + ]: 444637 : ++chunk_it;
118 [ - + ]: 444637 : assert(chunk_it != chunks.end());
119 : 444637 : chunk_ptr_remaining = chunk_it->ptr;
120 : 444637 : chunk_size_remaining = chunk_it->size;
121 : : }
122 [ - + ]: 1128012 : assert(free_block.ptr == chunk_ptr_remaining); // ensure addresses match
123 [ - + ]: 1128012 : assert(free_block.size <= chunk_size_remaining); // ensure no overflow
124 [ - + ]: 1128012 : assert((free_block.ptr & (resource.ELEM_ALIGN_BYTES - 1)) == 0); // ensure correct alignment
125 : 1128012 : chunk_ptr_remaining += free_block.size;
126 : 1128012 : chunk_size_remaining -= free_block.size;
127 : : }
128 : : // ensure we are at the end of the chunks
129 [ - + ]: 986 : assert(chunk_ptr_remaining == chunk_it->ptr + chunk_it->size);
130 [ - + ]: 986 : ++chunk_it;
131 [ - + ]: 986 : assert(chunk_it == chunks.end());
132 [ - + ]: 986 : assert(chunk_size_remaining == 0);
133 : 986 : }
134 : : };
135 : :
136 : : #endif // BITCOIN_TEST_UTIL_POOLRESOURCETESTER_H
|