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