1 /*
2 * Copyright (c) 2020-2024 Stefan Krah. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27
28 #include <cstdlib>
29 #include <cstdint>
30 #include <cinttypes>
31
32 #include "mpdecimal.h"
33
34 #include "decimal.hh"
35 #include "test.hh"
36
37
38 /******************************************************************************/
39 /* Exceptions */
40 /******************************************************************************/
41
42 namespace test {
what() const43 const char *Failure::what() const noexcept { return m.what(); }
44 } // namespace test
45
46
47 /******************************************************************************/
48 /* Functions */
49 /******************************************************************************/
50
51 namespace test {
52 void
assert_true(const char * file,const int64_t line,const bool p)53 assert_true(const char *file, const int64_t line, const bool p)
54 {
55 if (!(p)) {
56 raise(file, line, "assertion failed (expected true, got false)");
57 }
58 }
59
60 void
assert_false(const char * file,const int64_t line,const bool p)61 assert_false(const char *file, const int64_t line, const bool p)
62 {
63 if (p) {
64 raise(file, line, "assertion failed (expected false, got true)");
65 }
66 }
67 } // namespace test
68
69
70 /******************************************************************************/
71 /* Primary allocation functions (normal or offset) */
72 /******************************************************************************/
73
74 static const size_t OFFSET = 16;
75
76 #ifdef MPD_CONFIG_64
77 static const size_t alloc_limit = 0x4000000000000ULL;
78 #else
79 static thread_local size_t alloc_limit = SIZE_MAX;
80 #endif
81
82 /* malloc with upper limits */
83 static void *
malloc_ceil(size_t size)84 malloc_ceil(size_t size)
85 {
86 if (size > alloc_limit) {
87 return nullptr;
88 }
89
90 return malloc(size);
91 }
92
93 static void *
calloc_ceil(size_t nmemb,size_t size)94 calloc_ceil(size_t nmemb, size_t size)
95 {
96 if (nmemb > alloc_limit / size) {
97 return nullptr;
98 }
99
100 return calloc(nmemb, size);
101 }
102
103 static void *
realloc_ceil(void * ptr,size_t size)104 realloc_ceil(void *ptr, size_t size)
105 {
106 if (size > alloc_limit) {
107 return nullptr;
108 }
109
110 return realloc(ptr, size);
111 }
112
113 static void
free_ceil(void * ptr)114 free_ceil(void *ptr)
115 {
116 free(ptr);
117 }
118
119 /* custom malloc with an offset and upper limits */
120 static void *
malloc_offset(size_t size)121 malloc_offset(size_t size)
122 {
123 if (size == 0 || size > SIZE_MAX - OFFSET) {
124 return nullptr;
125 }
126
127 char *ptr = (char *)malloc_ceil(OFFSET + size);
128
129 return ptr ? ptr + OFFSET : nullptr;
130 }
131
132 static void *
calloc_offset(size_t nmemb,size_t size)133 calloc_offset(size_t nmemb, size_t size)
134 {
135 if (nmemb == 0 || size == 0 || size > SIZE_MAX - OFFSET) {
136 return nullptr;
137 }
138
139 char *ptr = (char *)calloc_ceil(nmemb, OFFSET + size);
140
141 return ptr ? ptr + OFFSET : nullptr;
142 }
143
144 static void *
realloc_offset(void * ptr,size_t size)145 realloc_offset(void *ptr, size_t size)
146 {
147 if (size == 0 || size > SIZE_MAX - OFFSET) {
148 return nullptr;
149 }
150
151 char *c = (char *)ptr - OFFSET;
152 char *p = (char *)realloc_ceil(c, OFFSET + size);
153
154 return p ? p + OFFSET : nullptr;
155 }
156
157 static void
free_offset(void * ptr)158 free_offset(void *ptr)
159 {
160 free((char *)ptr - OFFSET);
161 }
162
163 /* active set of primary allocation functions */
164 static void *(* test_mallocfunc)(size_t size) = malloc_ceil;
165 static void *(* test_callocfunc)(size_t nmemb, size_t size) = calloc_ceil;
166 static void *(* test_reallocfunc)(void *ptr, size_t size) = realloc_ceil;
167 static void (* test_freefunc)(void *ptr) = free_ceil;
168
169
170 /******************************************************************************/
171 /* Secondary allocation functions (count or failure mode) */
172 /******************************************************************************/
173
174 static bool enable_check_alloc = false;
175 static thread_local uint64_t alloc_fail = UINT64_MAX;
176 static thread_local uint64_t alloc_idx = 0;
177
178 static void *
malloc_fail(size_t size)179 malloc_fail(size_t size)
180 {
181 if (++alloc_idx >= alloc_fail) {
182 return nullptr;
183 }
184
185 return test_mallocfunc(size);
186 }
187
188 static void *
calloc_fail(size_t nmemb,size_t size)189 calloc_fail(size_t nmemb, size_t size)
190 {
191 if (++alloc_idx >= alloc_fail) {
192 return nullptr;
193 }
194
195 return test_callocfunc(nmemb, size);
196 }
197
198 static void *
realloc_fail(void * ptr,size_t size)199 realloc_fail(void *ptr, size_t size)
200 {
201 if (++alloc_idx >= alloc_fail) {
202 return nullptr;
203 }
204
205 return test_reallocfunc(ptr, size);
206 }
207
208 namespace test {
209
210 void
init_alloc(bool custom_alloc,bool check_alloc)211 init_alloc(bool custom_alloc, bool check_alloc)
212 {
213 static bool initialized = false;
214
215 if (initialized) {
216 fputs("mpd_init_alloc: error: cannot initialize twice\n", stderr);
217 exit(EXIT_FAILURE);
218 }
219 initialized = true;
220
221 /* initialization for the main thread */
222 #ifdef MPD_CONFIG_32
223 alloc_limit = SIZE_MAX;
224 #endif
225 alloc_fail = UINT64_MAX;
226 alloc_idx = 0;
227
228 enable_check_alloc = check_alloc;
229
230 if (custom_alloc) {
231 test_mallocfunc = malloc_offset;
232 test_callocfunc = calloc_offset;
233 test_reallocfunc = realloc_offset;
234 test_freefunc = free_offset;
235 }
236
237 mpd_mallocfunc = malloc_fail;
238 mpd_callocfunc = calloc_fail;
239 mpd_reallocfunc = realloc_fail;
240 mpd_free = test_freefunc;
241 }
242
243 #ifdef MPD_CONFIG_32
244 void
set_alloc_limit(size_t size)245 set_alloc_limit(size_t size)
246 {
247 alloc_limit = size;
248 }
249 #endif
250
251 void
set_alloc(decimal::Context & ctx)252 set_alloc(decimal::Context &ctx)
253 {
254 ctx.traps(MPD_Malloc_error);
255 alloc_idx = 0;
256 alloc_fail = UINT64_MAX;
257 }
258
259 void
set_alloc_fail(decimal::Context & ctx,uint64_t n)260 set_alloc_fail(decimal::Context &ctx, uint64_t n)
261 {
262 if (enable_check_alloc) {
263 ctx.traps(MPD_Malloc_error);
264 alloc_idx = 0;
265 alloc_fail = n;
266 }
267 }
268
269 } /* namespace test */
270