master
c 378 lines 14.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "dictionary-internals.h"
4
5 #if defined(FSANITIZE_ADDRESS)
6
7 // Judyl for tracking all dictionaries ever created
8 static SPINLOCK all_dictionaries_spinlock = SPINLOCK_INITIALIZER;
9 static Pvoid_t all_dictionaries = NULL;
10 static bool all_dictionaries_initialized = false;
11
12 // Initialize the tracking system
13 static void all_dictionaries_initialize(void) {
14 // Only do this once
15 if (all_dictionaries_initialized)
16 return;
17
18 spinlock_lock(&all_dictionaries_spinlock);
19 if (!all_dictionaries_initialized) {
20 all_dictionaries = NULL;
21 all_dictionaries_initialized = true;
22 }
23 spinlock_unlock(&all_dictionaries_spinlock);
24 }
25
26 // Comparison function for sorting stacktraces by count (descending)
27 static int stacktrace_count_compare(const void *a, const void *b) {
28 typedef struct {
29 STACKTRACE stacktrace;
30 Word_t count;
31 } StacktraceInfo;
32
33 const StacktraceInfo *sta = (const StacktraceInfo *)a;
34 const StacktraceInfo *stb = (const StacktraceInfo *)b;
35
36 if (stb->count > sta->count) return 1;
37 if (stb->count < sta->count) return -1;
38 return 0;
39 }
40
41 // Report all allocated dictionaries that are not part of the destroyed list
42 static size_t report_allocated_dictionaries(void) {
43 // Ensure initialization
44 if (!all_dictionaries_initialized)
45 all_dictionaries_initialize();
46
47 spinlock_lock(&all_dictionaries_spinlock);
48
49 Word_t index = 0;
50 Pvoid_t PValue;
51 Word_t count = 0;
52
53 // First count dictionaries
54 PValue = JudyLFirst(all_dictionaries, &index, PJE0);
55 while (PValue != NULL) {
56 count++;
57 PValue = JudyLNext(all_dictionaries, &index, PJE0);
58 }
59
60 if (count > 0) {
61 fprintf(stderr, "\nASAN: ===== DICTIONARY TRACKING: Detected %lu dictionaries that are still allocated =====\n", count);
62 fflush(stderr);
63
64 // First, group by stacktrace
65 // Key: stacktrace pointer, Value: count
66 Pvoid_t stacktrace_counts = NULL;
67 // Key: stacktrace pointer, Value: array of dictionary pointers
68 Pvoid_t stacktrace_dictionaries = NULL;
69
70 // Group dictionaries by stacktrace
71 index = 0;
72 PValue = JudyLFirst(all_dictionaries, &index, PJE0);
73 while (PValue != NULL) {
74 DICTIONARY *dict = (DICTIONARY *)index;
75
76 // Process each stacktrace in the array
77 for (int st_idx = 0; st_idx < dict->stacktraces.num_stacktraces; st_idx++) {
78 STACKTRACE st = dict->stacktraces.stacktraces[st_idx];
79
80 if (st) {
81 Word_t st_key = (Word_t)st;
82
83 // Update count for this stacktrace
84 Pvoid_t PCount;
85 PCount = JudyLIns(&stacktrace_counts, st_key, PJE0);
86 if (PCount) {
87 (*(Word_t*)PCount)++;
88 } else {
89 *(Word_t*)PCount = 1;
90 }
91
92 // Add dictionary to the list for this stacktrace
93 Pvoid_t PDictList;
94 PDictList = JudyLGet(stacktrace_dictionaries, st_key, PJE0);
95 if (!PDictList) {
96 // Create a new array (starting with size 1024)
97 DICTIONARY **dict_list = (DICTIONARY **)callocz(1024, sizeof(DICTIONARY*));
98 if (dict_list) {
99 dict_list[0] = dict;
100 JudyLIns(&stacktrace_dictionaries, st_key, PJE0);
101 PDictList = JudyLGet(stacktrace_dictionaries, st_key, PJE0);
102 if (PDictList) {
103 *(void**)PDictList = dict_list;
104 }
105 }
106 } else {
107 // Add to existing array if not already present
108 DICTIONARY **dict_list = *(DICTIONARY***)PDictList;
109 bool already_added = false;
110 size_t i = 0;
111
112 // Check if dictionary is already in this list
113 while (i < 1024 && dict_list[i]) {
114 if (dict_list[i] == dict) {
115 already_added = true;
116 break;
117 }
118 i++;
119 }
120
121 // Add if not already present
122 if (!already_added && i < 1024) {
123 dict_list[i] = dict;
124 }
125 }
126 }
127 }
128
129 PValue = JudyLNext(all_dictionaries, &index, PJE0);
130 }
131
132 // Now build a sorted array of stacktraces
133 // Use the StacktraceInfo type defined for the comparator
134 typedef struct {
135 STACKTRACE stacktrace;
136 Word_t count;
137 } StacktraceInfo;
138
139 Word_t num_stacktraces = 0;
140
141 // Count stacktraces
142 index = 0;
143 PValue = JudyLFirst(stacktrace_counts, &index, PJE0);
144 while (PValue != NULL) {
145 num_stacktraces++;
146 PValue = JudyLNext(stacktrace_counts, &index, PJE0);
147 }
148
149 if (num_stacktraces > 0) {
150 StacktraceInfo *stacktraces = (StacktraceInfo *)callocz(num_stacktraces, sizeof(StacktraceInfo));
151 if (stacktraces) {
152 // Fill array
153 Word_t i = 0;
154 index = 0;
155 PValue = JudyLFirst(stacktrace_counts, &index, PJE0);
156 while (PValue != NULL) {
157 stacktraces[i].stacktrace = (STACKTRACE)index;
158 stacktraces[i].count = *(Word_t*)PValue;
159 i++;
160 PValue = JudyLNext(stacktrace_counts, &index, PJE0);
161 }
162
163 // Sort by count (descending)
164 qsort(stacktraces, num_stacktraces, sizeof(StacktraceInfo), stacktrace_count_compare);
165
166 // Print stacktraces in order
167 for (i = 0; i < num_stacktraces; i++) {
168 STACKTRACE st = stacktraces[i].stacktrace;
169 Word_t st_count = stacktraces[i].count;
170
171 fprintf(stderr, "\n > DICTIONARY STACKTRACE GROUP %lu/%lu (count: %lu):\n",
172 i+1, num_stacktraces, st_count);
173 fflush(stderr);
174
175 // Print stacktrace
176 BUFFER *wb = buffer_create(16384, NULL);
177 stacktrace_to_buffer(st, wb);
178 fprintf(stderr, "%s\n", buffer_tostring(wb));
179 buffer_free(wb);
180 fflush(stderr);
181
182 // Print dictionary pointers
183 Pvoid_t PDictList = JudyLGet(stacktrace_dictionaries, (Word_t)st, PJE0);
184 if (PDictList) {
185 DICTIONARY **dict_list = *(DICTIONARY***)PDictList;
186 fprintf(stderr, " Dictionary pointers:");
187 int displayed = 0;
188
189 for (int j = 0; j < 1024 && dict_list[j] && displayed < 10; j++) {
190 fprintf(stderr, " %p", dict_list[j]);
191 displayed++;
192 }
193
194 if (st_count > 10) {
195 fprintf(stderr, " ... (plus %lu more)", st_count - 10);
196 }
197 fprintf(stderr, "\n");
198 fflush(stderr);
199 }
200 }
201
202 freez(stacktraces);
203 }
204
205 // Clean up
206 index = 0;
207 PValue = JudyLFirst(stacktrace_dictionaries, &index, PJE0);
208 while (PValue != NULL) {
209 freez(*(void**)PValue);
210 PValue = JudyLNext(stacktrace_dictionaries, &index, PJE0);
211 }
212
213 JudyLFreeArray(&stacktrace_dictionaries, PJE0);
214 JudyLFreeArray(&stacktrace_counts, PJE0);
215 }
216 }
217 else {
218 fprintf(stderr, "\nASAN: ===== DICTIONARY TRACKING: No allocated dictionaries found =====\n");
219 fflush(stderr);
220 }
221
222 spinlock_unlock(&all_dictionaries_spinlock);
223
224 return count;
225 }
226
227 /**
228 * @brief Print information about dictionaries that have delayed destruction
229 *
230 * This function is called during shutdown to print information about dictionaries that
231 * could not be destroyed because they have referenced items
232 */
233 void dictionary_debug_print_delayed_dictionaries(size_t destroyed_dicts) {
234 if (destroyed_dicts > 0) {
235 fprintf(stderr, "\nASAN: ===== DICTIONARY TRACKING: %zu dictionaries with references couldn't be destroyed =====\n",
236 destroyed_dicts);
237 fflush(stderr);
238 }
239 }
240
241 // Public API functions
242 void dictionary_debug_init(void) {
243 all_dictionaries_initialize();
244 }
245
246 void dictionary_debug_track_dict(DICTIONARY *dict) {
247 // Make sure we're initialized
248 if (!all_dictionaries_initialized)
249 all_dictionaries_initialize();
250
251 spinlock_lock(&all_dictionaries_spinlock);
252 // No need to capture return value
253 JudyLIns(&all_dictionaries, (Word_t)dict, PJE0);
254 spinlock_unlock(&all_dictionaries_spinlock);
255 }
256
257 void dictionary_debug_untrack_dict(DICTIONARY *dict) {
258 // Only attempt to untrack if we've initialized the tracking
259 if (!all_dictionaries_initialized)
260 return;
261
262 spinlock_lock(&all_dictionaries_spinlock);
263 // No need to capture return value
264 JudyLDel(&all_dictionaries, (Word_t)dict, PJE0);
265 spinlock_unlock(&all_dictionaries_spinlock);
266 }
267
268 void dictionary_print_still_allocated_stacktraces(void) {
269 size_t allocated = report_allocated_dictionaries();
270 if (allocated > 0) {
271 fprintf(stderr, "\nASAN: ===== DICTIONARY TRACKING: Found %zu dictionaries that are still allocated but not in the destroyed list =====\n",
272 allocated);
273 fflush(stderr);
274 }
275 }
276
277 void dictionary_debug_shutdown(void) {
278 if (!all_dictionaries_initialized)
279 return;
280
281 spinlock_lock(&all_dictionaries_spinlock);
282 JudyLFreeArray(&all_dictionaries, PJE0);
283 spinlock_unlock(&all_dictionaries_spinlock);
284 }
285
286 void dictionary_debug_internal_check_with_trace(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *function, bool allow_null_dict, bool allow_null_item) {
287 if(!allow_null_dict && !dict) {
288 // Create a buffer for the item's dict stacktrace
289 BUFFER *wb = buffer_create(1024, NULL);
290 if (item && item->dict && item->dict->stacktraces.num_stacktraces > 0) {
291 buffer_strcat(wb, "\nItem's dictionary stacktraces:\n");
292 for (int i = 0; i < item->dict->stacktraces.num_stacktraces && i < 3; i++) {
293 if (item->dict->stacktraces.stacktraces[i]) {
294 buffer_sprintf(wb, "Stacktrace #%d:\n", i+1);
295 stacktrace_to_buffer(item->dict->stacktraces.stacktraces[i], wb);
296 buffer_strcat(wb, "\n");
297 }
298 }
299 if (item->dict->stacktraces.num_stacktraces > 3)
300 buffer_sprintf(wb, "...and %d more stacktraces\n", item->dict->stacktraces.num_stacktraces - 3);
301 } else {
302 buffer_strcat(wb, "\nItem's dictionary stacktrace not available");
303 }
304
305 internal_error(
306 item,
307 "DICTIONARY: attempted to %s() with a NULL dictionary, passing an item. %s",
308 function,
309 buffer_tostring(wb));
310
311 buffer_free(wb);
312 fatal("DICTIONARY: attempted to %s() but dict is NULL", function);
313 }
314
315 if(!allow_null_item && !item) {
316 dictionary_internal_error(true, dict,
317 "DICTIONARY: attempted to %s() without an item on a dictionary",
318 function);
319 fatal("DICTIONARY: attempted to %s() but item is NULL", function);
320 }
321
322 if(dict && item && dict != item->dict) {
323 // Create buffer for both dictionaries' stacktraces
324 BUFFER *wb = buffer_create(1024, NULL);
325
326 if (dict->stacktraces.num_stacktraces > 0) {
327 buffer_strcat(wb, "\nDictionary stacktraces:\n");
328 for (int i = 0; i < dict->stacktraces.num_stacktraces && i < 3; i++) {
329 if (dict->stacktraces.stacktraces[i]) {
330 buffer_sprintf(wb, "Stacktrace #%d:\n", i+1);
331 stacktrace_to_buffer(dict->stacktraces.stacktraces[i], wb);
332 buffer_strcat(wb, "\n");
333 }
334 }
335 if (dict->stacktraces.num_stacktraces > 3)
336 buffer_sprintf(wb, "...and %d more stacktraces\n", dict->stacktraces.num_stacktraces - 3);
337 } else {
338 buffer_strcat(wb, "\nDictionary stacktrace not available");
339 }
340
341 if (item->dict && item->dict->stacktraces.num_stacktraces > 0) {
342 buffer_strcat(wb, "\nItem's dictionary stacktraces:\n");
343 for (int i = 0; i < item->dict->stacktraces.num_stacktraces && i < 3; i++) {
344 if (item->dict->stacktraces.stacktraces[i]) {
345 buffer_sprintf(wb, "Stacktrace #%d:\n", i+1);
346 stacktrace_to_buffer(item->dict->stacktraces.stacktraces[i], wb);
347 buffer_strcat(wb, "\n");
348 }
349 }
350 if (item->dict->stacktraces.num_stacktraces > 3)
351 buffer_sprintf(wb, "...and %d more stacktraces\n", item->dict->stacktraces.num_stacktraces - 3);
352 } else {
353 buffer_strcat(wb, "\nItem's dictionary stacktrace not available");
354 }
355
356 internal_error(
357 true,
358 "DICTIONARY: attempted to %s() an item on a dictionary different from the item's dictionary. %s",
359 function,
360 buffer_tostring(wb));
361
362 buffer_free(wb);
363 fatal("DICTIONARY: %s(): item does not belong to this dictionary.", function);
364 }
365
366 if(item) {
367 REFCOUNT refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
368 if (unlikely(refcount <= 0)) {
369 dictionary_internal_error(true, item->dict,
370 "DICTIONARY: attempted to %s() of an item with reference counter = %d on a dictionary",
371 function,
372 refcount);
373 fatal("DICTIONARY: attempted to %s but item is having refcount = %d", function, refcount);
374 }
375 }
376 }
377
378 #endif // FSANITIZE_ADDRESS