1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * SPDX-License-Identifier: MPL-2.0
5  *
6  * This Source Code Form is subject to the terms of the Mozilla Public
7  * License, v. 2.0. If a copy of the MPL was not distributed with this
8  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9  *
10  * See the COPYRIGHT file distributed with this work for additional
11  * information regarding copyright ownership.
12  */
13 
14 #if HAVE_CMOCKA
15 
16 #include <fcntl.h>
17 #include <sched.h> /* IWYU pragma: keep */
18 #include <setjmp.h>
19 #include <stdarg.h>
20 #include <stddef.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 
24 #define UNIT_TESTING
25 #include <cmocka.h>
26 
27 #include <isc/file.h>
28 #include <isc/mem.h>
29 #include <isc/mutex.h>
30 #include <isc/os.h>
31 #include <isc/print.h>
32 #include <isc/result.h>
33 #include <isc/stdio.h>
34 #include <isc/thread.h>
35 #include <isc/time.h>
36 #include <isc/util.h>
37 
38 #include "../mem_p.h"
39 #include "isctest.h"
40 
41 static int
_setup(void ** state)42 _setup(void **state) {
43 	isc_result_t result;
44 
45 	UNUSED(state);
46 
47 	result = isc_test_begin(NULL, true, 0);
48 	assert_int_equal(result, ISC_R_SUCCESS);
49 
50 	return (0);
51 }
52 
53 static int
_teardown(void ** state)54 _teardown(void **state) {
55 	UNUSED(state);
56 
57 	isc_test_end();
58 
59 	return (0);
60 }
61 
62 #define MP1_FREEMAX  10
63 #define MP1_FILLCNT  10
64 #define MP1_MAXALLOC 30
65 
66 #define MP2_FREEMAX 25
67 #define MP2_FILLCNT 25
68 
69 /* general memory system tests */
70 static void
isc_mem_test(void ** state)71 isc_mem_test(void **state) {
72 	void *items1[50];
73 	void *items2[50];
74 	void *tmp;
75 	isc_mempool_t *mp1 = NULL, *mp2 = NULL;
76 	unsigned int i, j;
77 	int rval;
78 
79 	UNUSED(state);
80 
81 	isc_mempool_create(test_mctx, 24, &mp1);
82 	isc_mempool_create(test_mctx, 31, &mp2);
83 
84 	isc_mempool_setfreemax(mp1, MP1_FREEMAX);
85 	isc_mempool_setfillcount(mp1, MP1_FILLCNT);
86 	isc_mempool_setmaxalloc(mp1, MP1_MAXALLOC);
87 
88 	/*
89 	 * Allocate MP1_MAXALLOC items from the pool.  This is our max.
90 	 */
91 	for (i = 0; i < MP1_MAXALLOC; i++) {
92 		items1[i] = isc_mempool_get(mp1);
93 		assert_non_null(items1[i]);
94 	}
95 
96 	/*
97 	 * Try to allocate one more.  This should fail.
98 	 */
99 	tmp = isc_mempool_get(mp1);
100 	assert_null(tmp);
101 
102 	/*
103 	 * Free the first 11 items.  Verify that there are 10 free items on
104 	 * the free list (which is our max).
105 	 */
106 	for (i = 0; i < 11; i++) {
107 		isc_mempool_put(mp1, items1[i]);
108 		items1[i] = NULL;
109 	}
110 
111 #if !__SANITIZE_ADDRESS__
112 	rval = isc_mempool_getfreecount(mp1);
113 	assert_int_equal(rval, 10);
114 #endif /* !__SANITIZE_ADDRESS__ */
115 
116 	rval = isc_mempool_getallocated(mp1);
117 	assert_int_equal(rval, 19);
118 
119 	/*
120 	 * Now, beat up on mp2 for a while.  Allocate 50 items, then free
121 	 * them, then allocate 50 more, etc.
122 	 */
123 
124 	isc_mempool_setfreemax(mp2, 25);
125 	isc_mempool_setfillcount(mp2, 25);
126 
127 	for (j = 0; j < 500000; j++) {
128 		for (i = 0; i < 50; i++) {
129 			items2[i] = isc_mempool_get(mp2);
130 			assert_non_null(items2[i]);
131 		}
132 		for (i = 0; i < 50; i++) {
133 			isc_mempool_put(mp2, items2[i]);
134 			items2[i] = NULL;
135 		}
136 	}
137 
138 	/*
139 	 * Free all the other items and blow away this pool.
140 	 */
141 	for (i = 11; i < MP1_MAXALLOC; i++) {
142 		isc_mempool_put(mp1, items1[i]);
143 		items1[i] = NULL;
144 	}
145 
146 	isc_mempool_destroy(&mp1);
147 	isc_mempool_destroy(&mp2);
148 
149 	isc_mempool_create(test_mctx, 2, &mp1);
150 
151 	tmp = isc_mempool_get(mp1);
152 	assert_non_null(tmp);
153 
154 	isc_mempool_put(mp1, tmp);
155 
156 	isc_mempool_destroy(&mp1);
157 }
158 
159 /* test TotalUse calculation */
160 static void
isc_mem_total_test(void ** state)161 isc_mem_total_test(void **state) {
162 	isc_mem_t *mctx2 = NULL;
163 	size_t before, after;
164 	ssize_t diff;
165 	int i;
166 
167 	UNUSED(state);
168 
169 	/* Local alloc, free */
170 	mctx2 = NULL;
171 	isc_mem_create(&mctx2);
172 
173 	before = isc_mem_total(mctx2);
174 
175 	for (i = 0; i < 100000; i++) {
176 		void *ptr;
177 
178 		ptr = isc_mem_allocate(mctx2, 2048);
179 		isc_mem_free(mctx2, ptr);
180 	}
181 
182 	after = isc_mem_total(mctx2);
183 	diff = after - before;
184 
185 	/* 2048 +8 bytes extra for size_info */
186 	assert_int_equal(diff, (2048 + 8) * 100000);
187 
188 	/* ISC_MEMFLAG_INTERNAL */
189 
190 	before = isc_mem_total(test_mctx);
191 
192 	for (i = 0; i < 100000; i++) {
193 		void *ptr;
194 
195 		ptr = isc_mem_allocate(test_mctx, 2048);
196 		isc_mem_free(test_mctx, ptr);
197 	}
198 
199 	after = isc_mem_total(test_mctx);
200 	diff = after - before;
201 
202 	/* 2048 +8 bytes extra for size_info */
203 	assert_int_equal(diff, (2048 + 8) * 100000);
204 
205 	isc_mem_destroy(&mctx2);
206 }
207 
208 /* test InUse calculation */
209 static void
isc_mem_inuse_test(void ** state)210 isc_mem_inuse_test(void **state) {
211 	isc_mem_t *mctx2 = NULL;
212 	size_t before, after;
213 	ssize_t diff;
214 	void *ptr;
215 
216 	UNUSED(state);
217 
218 	mctx2 = NULL;
219 	isc_mem_create(&mctx2);
220 
221 	before = isc_mem_inuse(mctx2);
222 	ptr = isc_mem_allocate(mctx2, 1024000);
223 	isc_mem_free(mctx2, ptr);
224 	after = isc_mem_inuse(mctx2);
225 
226 	diff = after - before;
227 
228 	assert_int_equal(diff, 0);
229 
230 	isc_mem_destroy(&mctx2);
231 }
232 
233 #if ISC_MEM_TRACKLINES
234 
235 /* test mem with no flags */
236 static void
isc_mem_noflags_test(void ** state)237 isc_mem_noflags_test(void **state) {
238 	isc_result_t result;
239 	isc_mem_t *mctx2 = NULL;
240 	char buf[4096], *p, *q;
241 	FILE *f;
242 	void *ptr;
243 
244 	result = isc_stdio_open("mem.output", "w", &f);
245 	assert_int_equal(result, ISC_R_SUCCESS);
246 
247 	UNUSED(state);
248 
249 	isc_mem_create(&mctx2);
250 	isc_mem_debugging = 0;
251 	ptr = isc_mem_get(mctx2, 2048);
252 	assert_non_null(ptr);
253 	isc__mem_printactive(mctx2, f);
254 	isc_mem_put(mctx2, ptr, 2048);
255 	isc_mem_destroy(&mctx2);
256 	isc_stdio_close(f);
257 
258 	memset(buf, 0, sizeof(buf));
259 	result = isc_stdio_open("mem.output", "r", &f);
260 	assert_int_equal(result, ISC_R_SUCCESS);
261 	result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL);
262 	assert_int_equal(result, ISC_R_EOF);
263 	isc_stdio_close(f);
264 	isc_file_remove("mem.output");
265 
266 	buf[sizeof(buf) - 1] = 0;
267 
268 	p = strchr(buf, '\n');
269 	assert_non_null(p);
270 	assert_in_range(p, 0, buf + sizeof(buf) - 3);
271 	p += 2;
272 	q = strchr(p, '\n');
273 	assert_non_null(q);
274 	*q = '\0';
275 	assert_string_equal(p, "None.");
276 
277 	isc_mem_debugging = ISC_MEM_DEBUGRECORD;
278 }
279 
280 /* test mem with record flag */
281 static void
isc_mem_recordflag_test(void ** state)282 isc_mem_recordflag_test(void **state) {
283 	isc_result_t result;
284 	isc_mem_t *mctx2 = NULL;
285 	char buf[4096], *p;
286 	FILE *f;
287 	void *ptr;
288 
289 	result = isc_stdio_open("mem.output", "w", &f);
290 	assert_int_equal(result, ISC_R_SUCCESS);
291 
292 	UNUSED(state);
293 
294 	isc_mem_create(&mctx2);
295 	ptr = isc_mem_get(mctx2, 2048);
296 	assert_non_null(ptr);
297 	isc__mem_printactive(mctx2, f);
298 	isc_mem_put(mctx2, ptr, 2048);
299 	isc_mem_destroy(&mctx2);
300 	isc_stdio_close(f);
301 
302 	memset(buf, 0, sizeof(buf));
303 	result = isc_stdio_open("mem.output", "r", &f);
304 	assert_int_equal(result, ISC_R_SUCCESS);
305 	result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL);
306 	assert_int_equal(result, ISC_R_EOF);
307 	isc_stdio_close(f);
308 	isc_file_remove("mem.output");
309 
310 	buf[sizeof(buf) - 1] = 0;
311 
312 	p = strchr(buf, '\n');
313 	assert_non_null(p);
314 	assert_in_range(p, 0, buf + sizeof(buf) - 3);
315 	assert_memory_equal(p + 2, "ptr ", 4);
316 	p = strchr(p + 1, '\n');
317 	assert_non_null(p);
318 	assert_int_equal(strlen(p), 1);
319 }
320 
321 /* test mem with trace flag */
322 static void
isc_mem_traceflag_test(void ** state)323 isc_mem_traceflag_test(void **state) {
324 	isc_result_t result;
325 	isc_mem_t *mctx2 = NULL;
326 	char buf[4096], *p;
327 	FILE *f;
328 	void *ptr;
329 
330 	/* redirect stderr so we can check trace output */
331 	f = freopen("mem.output", "w", stderr);
332 	assert_non_null(f);
333 
334 	UNUSED(state);
335 
336 	isc_mem_create(&mctx2);
337 	isc_mem_debugging = ISC_MEM_DEBUGTRACE;
338 	ptr = isc_mem_get(mctx2, 2048);
339 	assert_non_null(ptr);
340 	isc__mem_printactive(mctx2, f);
341 	isc_mem_put(mctx2, ptr, 2048);
342 	isc_mem_destroy(&mctx2);
343 	isc_stdio_close(f);
344 
345 	memset(buf, 0, sizeof(buf));
346 	result = isc_stdio_open("mem.output", "r", &f);
347 	assert_int_equal(result, ISC_R_SUCCESS);
348 	result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL);
349 	assert_int_equal(result, ISC_R_EOF);
350 	isc_stdio_close(f);
351 	isc_file_remove("mem.output");
352 
353 	/* return stderr to TTY so we can see errors */
354 	f = freopen("/dev/tty", "w", stderr);
355 
356 	buf[sizeof(buf) - 1] = 0;
357 
358 	assert_memory_equal(buf, "add ", 4);
359 	p = strchr(buf, '\n');
360 	assert_non_null(p);
361 	p = strchr(p + 1, '\n');
362 	assert_non_null(p);
363 	assert_in_range(p, 0, buf + sizeof(buf) - 3);
364 	assert_memory_equal(p + 2, "ptr ", 4);
365 	p = strchr(p + 1, '\n');
366 	assert_non_null(p);
367 	assert_memory_equal(p + 1, "del ", 4);
368 
369 	isc_mem_debugging = ISC_MEM_DEBUGRECORD;
370 }
371 #endif /* if ISC_MEM_TRACKLINES */
372 
373 #if !defined(__SANITIZE_THREAD__)
374 
375 #define ITERS	  512
376 #define NUM_ITEMS 1024 /* 768 */
377 #define ITEM_SIZE 65534
378 
379 static isc_threadresult_t
mem_thread(isc_threadarg_t arg)380 mem_thread(isc_threadarg_t arg) {
381 	void *items[NUM_ITEMS];
382 	size_t size = *((size_t *)arg);
383 
384 	for (int i = 0; i < ITERS; i++) {
385 		for (int j = 0; j < NUM_ITEMS; j++) {
386 			items[j] = isc_mem_get(test_mctx, size);
387 		}
388 		for (int j = 0; j < NUM_ITEMS; j++) {
389 			isc_mem_put(test_mctx, items[j], size);
390 		}
391 	}
392 
393 	return ((isc_threadresult_t)0);
394 }
395 
396 static void
isc_mem_benchmark(void ** state)397 isc_mem_benchmark(void **state) {
398 	int nthreads = ISC_MAX(ISC_MIN(isc_os_ncpus(), 32), 1);
399 	isc_thread_t threads[32];
400 	isc_time_t ts1, ts2;
401 	double t;
402 	isc_result_t result;
403 	size_t size = ITEM_SIZE;
404 
405 	UNUSED(state);
406 
407 	result = isc_time_now(&ts1);
408 	assert_int_equal(result, ISC_R_SUCCESS);
409 
410 	for (int i = 0; i < nthreads; i++) {
411 		isc_thread_create(mem_thread, &size, &threads[i]);
412 		size = size / 2;
413 	}
414 	for (int i = 0; i < nthreads; i++) {
415 		isc_thread_join(threads[i], NULL);
416 	}
417 
418 	result = isc_time_now(&ts2);
419 	assert_int_equal(result, ISC_R_SUCCESS);
420 
421 	t = isc_time_microdiff(&ts2, &ts1);
422 
423 	printf("[ TIME     ] isc_mem_benchmark: "
424 	       "%d isc_mem_{get,put} calls, %f seconds, %f calls/second\n",
425 	       nthreads * ITERS * NUM_ITEMS, t / 1000000.0,
426 	       (nthreads * ITERS * NUM_ITEMS) / (t / 1000000.0));
427 }
428 
429 #endif /* __SANITIZE_THREAD */
430 
431 /*
432  * Main
433  */
434 
435 int
main(void)436 main(void) {
437 	const struct CMUnitTest tests[] = {
438 		cmocka_unit_test_setup_teardown(isc_mem_test, _setup,
439 						_teardown),
440 		cmocka_unit_test_setup_teardown(isc_mem_total_test, _setup,
441 						_teardown),
442 		cmocka_unit_test_setup_teardown(isc_mem_inuse_test, _setup,
443 						_teardown),
444 
445 #if !defined(__SANITIZE_THREAD__)
446 		cmocka_unit_test_setup_teardown(isc_mem_benchmark, _setup,
447 						_teardown),
448 #endif /* __SANITIZE_THREAD__ */
449 #if ISC_MEM_TRACKLINES
450 		cmocka_unit_test_setup_teardown(isc_mem_noflags_test, _setup,
451 						_teardown),
452 		cmocka_unit_test_setup_teardown(isc_mem_recordflag_test, _setup,
453 						_teardown),
454 		/*
455 		 * traceflag_test closes stderr, which causes weird
456 		 * side effects for any next test trying to use libuv.
457 		 * This test has to be the last one to avoid problems.
458 		 */
459 		cmocka_unit_test_setup_teardown(isc_mem_traceflag_test, _setup,
460 						_teardown),
461 #endif /* if ISC_MEM_TRACKLINES */
462 	};
463 
464 	return (cmocka_run_group_tests(tests, NULL, NULL));
465 }
466 
467 #else /* HAVE_CMOCKA */
468 
469 #include <stdio.h>
470 
471 int
main(void)472 main(void) {
473 	printf("1..0 # Skipped: cmocka not available\n");
474 	return (SKIPPED_TEST_EXIT_CODE);
475 }
476 
477 #endif /* if HAVE_CMOCKA */
478