1 /* Test the allocate_once function.
2 Copyright (C) 2018-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19 #include <allocate_once.h>
20 #include <mcheck.h>
21 #include <string.h>
22 #include <support/check.h>
23 #include <support/support.h>
24
25 /* Allocate a new string. */
26 static void *
allocate_string(void * closure)27 allocate_string (void *closure)
28 {
29 return xstrdup (closure);
30 }
31
32 /* Allocation and deallocation functions which are not expected to be
33 called. */
34
35 static void *
allocate_not_called(void * closure)36 allocate_not_called (void *closure)
37 {
38 FAIL_EXIT1 ("allocation function called unexpectedly (%p)", closure);
39 }
40
41 static void
deallocate_not_called(void * closure,void * ptr)42 deallocate_not_called (void *closure, void *ptr)
43 {
44 FAIL_EXIT1 ("deallocate function called unexpectedly (%p, %p)",
45 closure, ptr);
46 }
47
48 /* Counter for various function calls. */
49 static int function_called;
50
51 /* An allocation function which returns NULL and records that it has
52 been called. */
53 static void *
allocate_return_null(void * closure)54 allocate_return_null (void *closure)
55 {
56 /* The function should only be called once. */
57 TEST_COMPARE (function_called, 0);
58 ++function_called;
59 return NULL;
60 }
61
62
63 /* The following is used to check the retry logic, by causing a fake
64 race condition. */
65 static void *fake_race_place;
66 static char fake_race_region[3]; /* To obtain unique addresses. */
67
68 static void *
fake_race_allocate(void * closure)69 fake_race_allocate (void *closure)
70 {
71 TEST_VERIFY (closure == &fake_race_region[0]);
72 TEST_COMPARE (function_called, 0);
73 ++function_called;
74 /* Fake allocation by another thread. */
75 fake_race_place = &fake_race_region[1];
76 return &fake_race_region[2];
77 }
78
79 static void
fake_race_deallocate(void * closure,void * ptr)80 fake_race_deallocate (void *closure, void *ptr)
81 {
82 /* Check that the pointer returned from fake_race_allocate is
83 deallocated (and not the one stored in fake_race_place). */
84 TEST_VERIFY (ptr == &fake_race_region[2]);
85
86 TEST_VERIFY (fake_race_place == &fake_race_region[1]);
87 TEST_VERIFY (closure == &fake_race_region[0]);
88 TEST_COMPARE (function_called, 1);
89 ++function_called;
90 }
91
92 /* Similar to fake_race_allocate, but expects to be paired with free
93 as the deallocation function. */
94 static void *
fake_race_allocate_for_free(void * closure)95 fake_race_allocate_for_free (void *closure)
96 {
97 TEST_VERIFY (closure == &fake_race_region[0]);
98 TEST_COMPARE (function_called, 0);
99 ++function_called;
100 /* Fake allocation by another thread. */
101 fake_race_place = &fake_race_region[1];
102 return xstrdup ("to be freed");
103 }
104
105 static int
do_test(void)106 do_test (void)
107 {
108 mtrace ();
109
110 /* Simple allocation. */
111 void *place1 = NULL;
112 char *string1 = allocate_once (&place1, allocate_string,
113 deallocate_not_called,
114 (char *) "test string 1");
115 TEST_VERIFY_EXIT (string1 != NULL);
116 TEST_VERIFY (strcmp ("test string 1", string1) == 0);
117 /* Second call returns the first pointer, without calling any
118 callbacks. */
119 TEST_VERIFY (string1
120 == allocate_once (&place1, allocate_not_called,
121 deallocate_not_called,
122 (char *) "test string 1a"));
123
124 /* Different place should result in another call. */
125 void *place2 = NULL;
126 char *string2 = allocate_once (&place2, allocate_string,
127 deallocate_not_called,
128 (char *) "test string 2");
129 TEST_VERIFY_EXIT (string2 != NULL);
130 TEST_VERIFY (strcmp ("test string 2", string2) == 0);
131 TEST_VERIFY (string1 != string2);
132
133 /* Check error reporting (NULL return value from the allocation
134 function). */
135 void *place3 = NULL;
136 char *string3 = allocate_once (&place3, allocate_return_null,
137 deallocate_not_called, NULL);
138 TEST_VERIFY (string3 == NULL);
139 TEST_COMPARE (function_called, 1);
140
141 /* Check that the deallocation function is called if the race is
142 lost. */
143 function_called = 0;
144 TEST_VERIFY (allocate_once (&fake_race_place,
145 fake_race_allocate,
146 fake_race_deallocate,
147 &fake_race_region[0])
148 == &fake_race_region[1]);
149 TEST_COMPARE (function_called, 2);
150 function_called = 3;
151 TEST_VERIFY (allocate_once (&fake_race_place,
152 fake_race_allocate,
153 fake_race_deallocate,
154 &fake_race_region[0])
155 == &fake_race_region[1]);
156 TEST_COMPARE (function_called, 3);
157
158 /* Similar, but this time rely on that free is called. */
159 function_called = 0;
160 fake_race_place = NULL;
161 TEST_VERIFY (allocate_once (&fake_race_place,
162 fake_race_allocate_for_free,
163 NULL,
164 &fake_race_region[0])
165 == &fake_race_region[1]);
166 TEST_COMPARE (function_called, 1);
167 function_called = 3;
168 TEST_VERIFY (allocate_once (&fake_race_place,
169 fake_race_allocate_for_free,
170 NULL,
171 &fake_race_region[0])
172 == &fake_race_region[1]);
173 TEST_COMPARE (function_called, 3);
174
175 free (place2);
176 free (place1);
177
178 return 0;
179 }
180
181 #include <support/test-driver.c>
182