xref: /freebsd/contrib/mandoc/mandoc_dbg.c (revision c1c95add8c80843ba15d784f95c361d795b1f593)
1 /* $Id: mandoc_dbg.c,v 1.1 2022/04/14 16:43:44 schwarze Exp $ */
2 /*
3  * Copyright (c) 2021, 2022 Ingo Schwarze <schwarze@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include "config.h"
18 
19 #include <sys/types.h>
20 
21 #if HAVE_ERR
22 #include <err.h>
23 #endif
24 #include <stdarg.h>
25 #include <stddef.h>
26 #include <stdint.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #if HAVE_OHASH
33 #include <ohash.h>
34 #else
35 #include "compat_ohash.h"
36 #endif
37 
38 #define DEBUG_NODEF 1
39 #include "mandoc_aux.h"
40 #include "mandoc_dbg.h"
41 #include "mandoc.h"
42 
43 /* Store information about one allocation. */
44 struct dhash_entry {
45 	const char	*file;
46 	int		 line;
47 	const char	*func;
48 	size_t		 num;
49 	size_t		 size;
50 	void		*ptr;
51 };
52 
53 /* Store information about all allocations. */
54 static struct ohash	  dhash_table;
55 static FILE		 *dhash_fp;
56 static int		  dhash_aflag;
57 static int		  dhash_fflag;
58 static int		  dhash_lflag;
59 static int		  dhash_nflag;
60 static int		  dhash_sflag;
61 
62 static	void		 *dhash_alloc(size_t, void *);
63 static	void		 *dhash_calloc(size_t, size_t, void *);
64 static	void		  dhash_free(void *, void *);
65 static	unsigned int	  dhash_slot(void *);
66 static	void		  dhash_register(const char *, int, const char *,
67 				size_t, size_t, void *, const char *);
68 static	void		  dhash_print(struct dhash_entry *);
69 static	void		  dhash_purge(const char *, int, const char *, void *);
70 
71 
72 /* *** Debugging wrappers of public API functions. ************************ */
73 
74 int
mandoc_dbg_asprintf(const char * file,int line,char ** dest,const char * fmt,...)75 mandoc_dbg_asprintf(const char *file, int line,
76     char **dest, const char *fmt, ...)
77 {
78 	va_list	 ap;
79 	int	 ret;
80 
81 	va_start(ap, fmt);
82 	ret = vasprintf(dest, fmt, ap);
83 	va_end(ap);
84 
85 	if (ret == -1)
86 		err((int)MANDOCLEVEL_SYSERR, NULL);
87 
88 	dhash_register(file, line, "asprintf", 1, strlen(*dest) + 1,
89 	    *dest, *dest);
90 
91 	return ret;
92 }
93 
94 void *
mandoc_dbg_calloc(size_t num,size_t size,const char * file,int line)95 mandoc_dbg_calloc(size_t num, size_t size, const char *file, int line)
96 {
97 	void *ptr = mandoc_calloc(num, size);
98 	dhash_register(file, line, "calloc", num, size, ptr, NULL);
99 	return ptr;
100 }
101 
102 void *
mandoc_dbg_malloc(size_t size,const char * file,int line)103 mandoc_dbg_malloc(size_t size, const char *file, int line)
104 {
105 	void *ptr = mandoc_malloc(size);
106 	dhash_register(file, line, "malloc", 1, size, ptr, NULL);
107 	return ptr;
108 }
109 
110 void *
mandoc_dbg_realloc(void * ptr,size_t size,const char * file,int line)111 mandoc_dbg_realloc(void *ptr, size_t size, const char *file, int line)
112 {
113 	dhash_purge(file, line, "realloc", ptr);
114 	ptr = mandoc_realloc(ptr, size);
115 	dhash_register(file, line, "realloc", 1, size, ptr, NULL);
116 	return ptr;
117 }
118 
119 void *
mandoc_dbg_reallocarray(void * ptr,size_t num,size_t size,const char * file,int line)120 mandoc_dbg_reallocarray(void *ptr, size_t num, size_t size,
121     const char *file, int line)
122 {
123 	dhash_purge(file, line, "reallocarray", ptr);
124 	ptr = mandoc_reallocarray(ptr, num, size);
125 	dhash_register(file, line, "reallocarray", num, size, ptr, NULL);
126 	return ptr;
127 }
128 
129 void *
mandoc_dbg_recallocarray(void * ptr,size_t oldnum,size_t num,size_t size,const char * file,int line)130 mandoc_dbg_recallocarray(void *ptr, size_t oldnum, size_t num, size_t size,
131     const char *file, int line)
132 {
133 	dhash_purge(file, line, "recallocarray", ptr);
134 	ptr = mandoc_recallocarray(ptr, oldnum, num, size);
135 	dhash_register(file, line, "recallocarray", num, size, ptr, NULL);
136 	return ptr;
137 }
138 
139 char *
mandoc_dbg_strdup(const char * ptr,const char * file,int line)140 mandoc_dbg_strdup(const char *ptr, const char *file, int line)
141 {
142 	char *p = mandoc_strdup(ptr);
143 	dhash_register(file, line, "strdup", 1, strlen(p) + 1, p, ptr);
144 	return p;
145 }
146 
147 char *
mandoc_dbg_strndup(const char * ptr,size_t sz,const char * file,int line)148 mandoc_dbg_strndup(const char *ptr, size_t sz, const char *file, int line)
149 {
150 	char *p = mandoc_strndup(ptr, sz);
151 	dhash_register(file, line, "strndup", 1, strlen(p) + 1, p, NULL);
152 	return p;
153 }
154 
155 void
mandoc_dbg_free(void * ptr,const char * file,int line)156 mandoc_dbg_free(void *ptr, const char *file, int line)
157 {
158 	dhash_purge(file, line, "free", ptr);
159 	free(ptr);
160 }
161 
162 
163 /* *** Memory allocation callbacks for the debugging table. *************** */
164 
165 static void *
dhash_alloc(size_t sz,void * arg)166 dhash_alloc(size_t sz, void *arg)
167 {
168         return malloc(sz);
169 }
170 
171 static void *
dhash_calloc(size_t n,size_t sz,void * arg)172 dhash_calloc(size_t n, size_t sz, void *arg)
173 {
174         return calloc(n, sz);
175 }
176 
177 static void
dhash_free(void * p,void * arg)178 dhash_free(void *p, void *arg)
179 {
180         free(p);
181 }
182 
183 
184 /* *** Debugging utility functions. *************************************** */
185 
186 /* Initialize the debugging table, to be called from the top of main(). */
187 void
mandoc_dbg_init(int argc,char * argv[])188 mandoc_dbg_init(int argc, char *argv[])
189 {
190 	struct ohash_info	  info;
191 	char			 *dhash_fn;
192 	int			  argi;
193 
194 	info.alloc = dhash_alloc;
195 	info.calloc = dhash_calloc;
196 	info.free = dhash_free;
197 	info.data = NULL;
198 	info.key_offset = offsetof(struct dhash_entry, ptr);
199 	ohash_init(&dhash_table, 18, &info);
200 
201 	dhash_fp = stderr;
202 	if ((dhash_fn = getenv("DEBUG_MEMORY")) == NULL)
203 		return;
204 
205 	dhash_sflag = 1;
206 	for(;; dhash_fn++) {
207 		switch (*dhash_fn) {
208 		case '\0':
209 			break;
210 		case 'A':
211 			dhash_aflag = 1;
212 			continue;
213 		case 'F':
214 			dhash_fflag = 1;
215 			continue;
216 		case 'L':
217 			dhash_lflag = 1;
218 			continue;
219 		case 'N':
220 			dhash_nflag = 1;
221 			continue;
222 		case '/':
223 			if ((dhash_fp = fopen(dhash_fn, "a+e")) == NULL)
224 				err((int)MANDOCLEVEL_SYSERR, "%s", dhash_fn);
225 			break;
226 		default:
227 			errx((int)MANDOCLEVEL_BADARG,
228 			    "invalid char '%c' in $DEBUG_MEMORY",
229 			    *dhash_fn);
230 		}
231 		break;
232 	}
233 	if (setvbuf(dhash_fp, NULL, _IOLBF, 0) != 0)
234 		err((int)MANDOCLEVEL_SYSERR, "setvbuf");
235 
236 	fprintf(dhash_fp, "P %d", getpid());
237 	for (argi = 0; argi < argc; argi++)
238 		fprintf(dhash_fp, " [%s]", argv[argi]);
239 	fprintf(dhash_fp, "\n");
240 }
241 
242 void
mandoc_dbg_name(const char * name)243 mandoc_dbg_name(const char *name)
244 {
245 	if (dhash_nflag)
246 		fprintf(dhash_fp, "N %s\n", name);
247 }
248 
249 /* Hash a pointer and return the table slot currently used for it. */
250 static unsigned int
dhash_slot(void * ptr)251 dhash_slot(void *ptr)
252 {
253 	const char	*ks, *ke;
254 	uint32_t	 hv;
255 
256 	ks = (const char *)&ptr;
257 	ke = ks + sizeof(ptr);
258 	hv = ohash_interval(ks, &ke);
259 	return ohash_lookup_memory(&dhash_table, ks, sizeof(ptr), hv);
260 }
261 
262 /* Record one allocation in the debugging table. */
263 static void
dhash_register(const char * file,int line,const char * func,size_t num,size_t size,void * ptr,const char * str)264 dhash_register(const char *file, int line, const char *func,
265     size_t num, size_t size, void *ptr, const char *str)
266 {
267 	struct dhash_entry	*e;
268 	unsigned int		 slot;
269 
270 	slot = dhash_slot(ptr);
271 	e = ohash_find(&dhash_table, slot);
272 	if (dhash_aflag || e != NULL) {
273 		fprintf(dhash_fp, "A %s:%d %s(%zu, %zu) = %p",
274 		    file, line, func, num, size, ptr);
275 		if (str != NULL)
276 			fprintf(dhash_fp, " \"%s\"", str);
277 		fprintf(dhash_fp, "\n");
278 	}
279 	if (e != NULL) {
280 		dhash_print(e);
281 		fprintf(dhash_fp, "E duplicate address %p\n", e->ptr);
282 		errx((int)MANDOCLEVEL_BADARG, "duplicate address %p", e->ptr);
283 	}
284 
285 	if ((e = malloc(sizeof(*e))) == NULL)
286 		err(1, NULL);
287 	e->file = file;
288 	e->line = line;
289 	e->func = func;
290 	e->num  = num;
291 	e->size = size;
292 	e->ptr  = ptr;
293 
294 	ohash_insert(&dhash_table, slot, e);
295 }
296 
297 /* Remove one allocation from the debugging table. */
298 static void
dhash_purge(const char * file,int line,const char * func,void * ptr)299 dhash_purge(const char *file, int line, const char *func, void *ptr)
300 {
301 	struct dhash_entry	*e;
302 	unsigned int		 slot;
303 
304 	if (ptr == NULL)
305 		return;
306 
307 	if (dhash_fflag)
308 		fprintf(dhash_fp, "F %s:%d %s(%p)\n", file, line, func, ptr);
309 
310 	slot = dhash_slot(ptr);
311 	e = ohash_remove(&dhash_table, slot);
312 	free(e);
313 }
314 
315 /* Pretty-print information about one allocation. */
316 static void
dhash_print(struct dhash_entry * e)317 dhash_print(struct dhash_entry *e)
318 {
319 	fprintf(dhash_fp, "L %s:%d %s(%zu, %zu) = %p\n",
320 	    e->file, e->line, e->func, e->num, e->size, e->ptr);
321 }
322 
323 /* Pretty-print information about all active allocations. */
324 void
mandoc_dbg_finish(void)325 mandoc_dbg_finish(void)
326 {
327 	struct dhash_entry	*e;
328 	unsigned int		 errcount, slot;
329 
330 	errcount = ohash_entries(&dhash_table);
331 	e = ohash_first(&dhash_table, &slot);
332 	while (e != NULL) {
333 		if (dhash_lflag)
334 			dhash_print(e);
335 		free(e);
336 		e = ohash_next(&dhash_table, &slot);
337 	}
338 	ohash_delete(&dhash_table);
339 	if (dhash_sflag)
340 		fprintf(dhash_fp, "S %u memory leaks found\n", errcount);
341 	if (dhash_fp != stderr)
342 		fclose(dhash_fp);
343 }
344