xref: /freebsd/contrib/sendmail/libsm/heap.c (revision 7773002178c8dbc52b44e4d705f07706409af8e4)
1 /*
2  * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  */
9 
10 #include <sm/gen.h>
11 SM_RCSID("@(#)$Id: heap.c,v 1.50 2001/09/11 04:04:48 gshapiro Exp $")
12 
13 /*
14 **  debugging memory allocation package
15 **  See heap.html for documentation.
16 */
17 
18 #include <string.h>
19 
20 #include <sm/assert.h>
21 #include <sm/debug.h>
22 #include <sm/exc.h>
23 #include <sm/heap.h>
24 #include <sm/io.h>
25 #include <sm/signal.h>
26 #include <sm/xtrap.h>
27 
28 /* undef all macro versions of the "functions" so they can be specified here */
29 #undef sm_malloc
30 #undef sm_malloc_x
31 #undef sm_malloc_tagged
32 #undef sm_malloc_tagged_x
33 #undef sm_free
34 #undef sm_free_tagged
35 #undef sm_realloc
36 #if SM_HEAP_CHECK
37 # undef sm_heap_register
38 # undef sm_heap_checkptr
39 # undef sm_heap_report
40 #endif /* SM_HEAP_CHECK */
41 
42 #if SM_HEAP_CHECK
43 SM_DEBUG_T SmHeapCheck = SM_DEBUG_INITIALIZER("sm_check_heap",
44     "@(#)$Debug: sm_check_heap - check sm_malloc, sm_realloc, sm_free calls $");
45 # define HEAP_CHECK sm_debug_active(&SmHeapCheck, 1)
46 #endif /* SM_HEAP_CHECK */
47 
48 const SM_EXC_TYPE_T SmHeapOutOfMemoryType =
49 {
50 	SmExcTypeMagic,
51 	"F:sm.heap",
52 	"",
53 	sm_etype_printf,
54 	"out of memory",
55 };
56 
57 SM_EXC_T SmHeapOutOfMemory = SM_EXC_INITIALIZER(&SmHeapOutOfMemoryType, NULL);
58 
59 
60 /*
61 **  The behaviour of malloc with size==0 is platform dependent (it
62 **  says so in the C standard): it can return NULL or non-NULL.  We
63 **  don't want sm_malloc_x(0) to raise an exception on some platforms
64 **  but not others, so this case requires special handling.  We've got
65 **  two choices: "size = 1" or "return NULL". We use the former in the
66 **  following.
67 **	If we had something like autoconf we could figure out the
68 **	behaviour of the platform and either use this hack or just
69 **	use size.
70 */
71 
72 #define MALLOC_SIZE(size)	((size) == 0 ? 1 : (size))
73 
74 /*
75 **  SM_MALLOC_X -- wrapper around malloc(), raises an exception on error.
76 **
77 **	Parameters:
78 **		size -- size of requested memory.
79 **
80 **	Returns:
81 **		Pointer to memory region.
82 **
83 **	Note:
84 **		sm_malloc_x only gets called from source files in which heap
85 **		debugging is disabled at compile time.  Otherwise, a call to
86 **		sm_malloc_x is macro expanded to a call to sm_malloc_tagged_x.
87 **
88 **	Exceptions:
89 **		F:sm_heap -- out of memory
90 */
91 
92 void *
93 sm_malloc_x(size)
94 	size_t size;
95 {
96 	void *ptr;
97 
98 	ENTER_CRITICAL();
99 	ptr = malloc(MALLOC_SIZE(size));
100 	LEAVE_CRITICAL();
101 	if (ptr == NULL)
102 		sm_exc_raise_x(&SmHeapOutOfMemory);
103 	return ptr;
104 }
105 
106 #if !SM_HEAP_CHECK
107 
108 /*
109 **  SM_MALLOC -- wrapper around malloc()
110 **
111 **	Parameters:
112 **		size -- size of requested memory.
113 **
114 **	Returns:
115 **		Pointer to memory region.
116 */
117 
118 void *
119 sm_malloc(size)
120 	size_t size;
121 {
122 	void *ptr;
123 
124 	ENTER_CRITICAL();
125 	ptr = malloc(MALLOC_SIZE(size));
126 	LEAVE_CRITICAL();
127 	return ptr;
128 }
129 
130 /*
131 **  SM_REALLOC -- wrapper for realloc()
132 **
133 **	Parameters:
134 **		ptr -- pointer to old memory area.
135 **		size -- size of requested memory.
136 **
137 **	Returns:
138 **		Pointer to new memory area, NULL on failure.
139 */
140 
141 void *
142 sm_realloc(ptr, size)
143 	void *ptr;
144 	size_t size;
145 {
146 	void *newptr;
147 
148 	ENTER_CRITICAL();
149 	newptr = realloc(ptr, MALLOC_SIZE(size));
150 	LEAVE_CRITICAL();
151 	return newptr;
152 }
153 
154 /*
155 **  SM_REALLOC_X -- wrapper for realloc()
156 **
157 **	Parameters:
158 **		ptr -- pointer to old memory area.
159 **		size -- size of requested memory.
160 **
161 **	Returns:
162 **		Pointer to new memory area.
163 **
164 **	Exceptions:
165 **		F:sm_heap -- out of memory
166 */
167 
168 void *
169 sm_realloc_x(ptr, size)
170 	void *ptr;
171 	size_t size;
172 {
173 	void *newptr;
174 
175 	ENTER_CRITICAL();
176 	newptr = realloc(ptr, MALLOC_SIZE(size));
177 	LEAVE_CRITICAL();
178 	if (newptr == NULL)
179 		sm_exc_raise_x(&SmHeapOutOfMemory);
180 	return newptr;
181 }
182 /*
183 **  SM_FREE -- wrapper around free()
184 **
185 **	Parameters:
186 **		ptr -- pointer to memory region.
187 **
188 **	Returns:
189 **		none.
190 */
191 
192 void
193 sm_free(ptr)
194 	void *ptr;
195 {
196 	if (ptr == NULL)
197 		return;
198 	ENTER_CRITICAL();
199 	free(ptr);
200 	LEAVE_CRITICAL();
201 	return;
202 }
203 
204 #else /* !SM_HEAP_CHECK */
205 
206 /*
207 **  Each allocated block is assigned a "group number".
208 **  By default, all blocks are assigned to group #1.
209 **  By convention, group #0 is for memory that is never freed.
210 **  You can use group numbers any way you want, in order to help make
211 **  sense of sm_heap_report output.
212 */
213 
214 int SmHeapGroup = 1;
215 int SmHeapMaxGroup = 1;
216 
217 /*
218 **  Total number of bytes allocated.
219 **  This is only maintained if the sm_check_heap debug category is active.
220 */
221 
222 size_t SmHeapTotal = 0;
223 
224 /*
225 **  High water mark: the most that SmHeapTotal has ever been.
226 */
227 
228 size_t SmHeapMaxTotal = 0;
229 
230 /*
231 **  Maximum number of bytes that may be allocated at any one time.
232 **  0 means no limit.
233 **  This is only honoured if sm_check_heap is active.
234 */
235 
236 SM_DEBUG_T SmHeapLimit = SM_DEBUG_INITIALIZER("sm_heap_limit",
237     "@(#)$Debug: sm_heap_limit - max # of bytes permitted in heap $");
238 
239 /*
240 **  This is the data structure that keeps track of all currently
241 **  allocated blocks of memory known to the heap package.
242 */
243 
244 typedef struct sm_heap_item SM_HEAP_ITEM_T;
245 struct sm_heap_item
246 {
247 	void		*hi_ptr;
248 	size_t		hi_size;
249 	char		*hi_tag;
250 	int		hi_num;
251 	int		hi_group;
252 	SM_HEAP_ITEM_T	*hi_next;
253 };
254 
255 #define SM_HEAP_TABLE_SIZE	256
256 static SM_HEAP_ITEM_T *SmHeapTable[SM_HEAP_TABLE_SIZE];
257 
258 /*
259 **  This is a randomly generated table
260 **  which contains exactly one occurrence
261 **  of each of the numbers between 0 and 255.
262 **  It is used by ptrhash.
263 */
264 
265 static unsigned char hashtab[SM_HEAP_TABLE_SIZE] =
266 {
267 	161, 71, 77,187, 15,229,  9,176,221,119,239, 21, 85,138,203, 86,
268 	102, 65, 80,199,235, 32,140, 96,224, 78,126,127,144,  0, 11,179,
269 	 64, 30,120, 23,225,226, 33, 50,205,167,130,240,174, 99,206, 73,
270 	231,210,189,162, 48, 93,246, 54,213,141,135, 39, 41,192,236,193,
271 	157, 88, 95,104,188, 63,133,177,234,110,158,214,238,131,233, 91,
272 	125, 82, 94, 79, 66, 92,151, 45,252, 98, 26,183,  7,191,171,106,
273 	145,154,251,100,113,  5, 74, 62, 76,124, 14,217,200, 75,115,190,
274 	103, 28,198,196,169,219, 37,118,150, 18,152,175, 49,136,  6,142,
275 	 89, 19,243,254, 47,137, 24,166,180, 10, 40,186,202, 46,184, 67,
276 	148,108,181, 81, 25,241, 13,139, 58, 38, 84,253,201, 12,116, 17,
277 	195, 22,112, 69,255, 43,147,222,111, 56,194,216,149,244, 42,173,
278 	232,220,249,105,207, 51,197,242, 72,211,208, 59,122,230,237,170,
279 	165, 44, 68,123,129,245,143,101,  8,209,215,247,185, 57,218, 53,
280 	114,121,  3,128,  4,204,212,146,  2,155, 83,250, 87, 29, 31,159,
281 	 60, 27,107,156,227,182,  1, 61, 36,160,109, 97, 90, 20,168,132,
282 	223,248, 70,164, 55,172, 34, 52,163,117, 35,153,134, 16,178,228
283 };
284 
285 /*
286 **  PTRHASH -- hash a pointer value
287 **
288 **	Parameters:
289 **		p -- pointer.
290 **
291 **	Returns:
292 **		hash value.
293 **
294 **  ptrhash hashes a pointer value to a uniformly distributed random
295 **  number between 0 and 255.
296 **
297 **  This hash algorithm is based on Peter K. Pearson,
298 **  "Fast Hashing of Variable-Length Text Strings",
299 **  in Communications of the ACM, June 1990, vol 33 no 6.
300 */
301 
302 static int
303 ptrhash(p)
304 	void *p;
305 {
306 	int h;
307 
308 	if (sizeof(void*) == 4 && sizeof(unsigned long) == 4)
309 	{
310 		unsigned long n = (unsigned long)p;
311 
312 		h = hashtab[n & 0xFF];
313 		h = hashtab[h ^ ((n >> 8) & 0xFF)];
314 		h = hashtab[h ^ ((n >> 16) & 0xFF)];
315 		h = hashtab[h ^ ((n >> 24) & 0xFF)];
316 	}
317 # if 0
318 	else if (sizeof(void*) == 8 && sizeof(unsigned long) == 8)
319 	{
320 		unsigned long n = (unsigned long)p;
321 
322 		h = hashtab[n & 0xFF];
323 		h = hashtab[h ^ ((n >> 8) & 0xFF)];
324 		h = hashtab[h ^ ((n >> 16) & 0xFF)];
325 		h = hashtab[h ^ ((n >> 24) & 0xFF)];
326 		h = hashtab[h ^ ((n >> 32) & 0xFF)];
327 		h = hashtab[h ^ ((n >> 40) & 0xFF)];
328 		h = hashtab[h ^ ((n >> 48) & 0xFF)];
329 		h = hashtab[h ^ ((n >> 56) & 0xFF)];
330 	}
331 # endif /* 0 */
332 	else
333 	{
334 		unsigned char *cp = (unsigned char *)&p;
335 		int i;
336 
337 		h = 0;
338 		for (i = 0; i < sizeof(void*); ++i)
339 			h = hashtab[h ^ cp[i]];
340 	}
341 	return h;
342 }
343 
344 /*
345 **  SM_MALLOC_TAGGED -- wrapper around malloc(), debugging version.
346 **
347 **	Parameters:
348 **		size -- size of requested memory.
349 **		tag -- tag for debugging.
350 **		num -- additional value for debugging.
351 **		group -- heap group for debugging.
352 **
353 **	Returns:
354 **		Pointer to memory region.
355 */
356 
357 void *
358 sm_malloc_tagged(size, tag, num, group)
359 	size_t size;
360 	char *tag;
361 	int num;
362 	int group;
363 {
364 	void *ptr;
365 
366 	if (!HEAP_CHECK)
367 	{
368 		ENTER_CRITICAL();
369 		ptr = malloc(MALLOC_SIZE(size));
370 		LEAVE_CRITICAL();
371 		return ptr;
372 	}
373 
374 	if (sm_xtrap_check())
375 		return NULL;
376 	if (sm_debug_active(&SmHeapLimit, 1)
377 	    && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
378 		return NULL;
379 	ENTER_CRITICAL();
380 	ptr = malloc(MALLOC_SIZE(size));
381 	LEAVE_CRITICAL();
382 	if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
383 	{
384 		ENTER_CRITICAL();
385 		free(ptr);
386 		LEAVE_CRITICAL();
387 		ptr = NULL;
388 	}
389 	SmHeapTotal += size;
390 	if (SmHeapTotal > SmHeapMaxTotal)
391 		SmHeapMaxTotal = SmHeapTotal;
392 	return ptr;
393 }
394 
395 /*
396 **  SM_MALLOC_TAGGED_X -- wrapper around malloc(), debugging version.
397 **
398 **	Parameters:
399 **		size -- size of requested memory.
400 **		tag -- tag for debugging.
401 **		num -- additional value for debugging.
402 **		group -- heap group for debugging.
403 **
404 **	Returns:
405 **		Pointer to memory region.
406 **
407 **	Exceptions:
408 **		F:sm_heap -- out of memory
409 */
410 
411 void *
412 sm_malloc_tagged_x(size, tag, num, group)
413 	size_t size;
414 	char *tag;
415 	int num;
416 	int group;
417 {
418 	void *ptr;
419 
420 	if (!HEAP_CHECK)
421 	{
422 		ENTER_CRITICAL();
423 		ptr = malloc(MALLOC_SIZE(size));
424 		LEAVE_CRITICAL();
425 		if (ptr == NULL)
426 			sm_exc_raise_x(&SmHeapOutOfMemory);
427 		return ptr;
428 	}
429 
430 	sm_xtrap_raise_x(&SmHeapOutOfMemory);
431 	if (sm_debug_active(&SmHeapLimit, 1)
432 	    && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
433 	{
434 		sm_exc_raise_x(&SmHeapOutOfMemory);
435 	}
436 	ENTER_CRITICAL();
437 	ptr = malloc(MALLOC_SIZE(size));
438 	LEAVE_CRITICAL();
439 	if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
440 	{
441 		ENTER_CRITICAL();
442 		free(ptr);
443 		LEAVE_CRITICAL();
444 		ptr = NULL;
445 	}
446 	if (ptr == NULL)
447 		sm_exc_raise_x(&SmHeapOutOfMemory);
448 	SmHeapTotal += size;
449 	if (SmHeapTotal > SmHeapMaxTotal)
450 		SmHeapMaxTotal = SmHeapTotal;
451 	return ptr;
452 }
453 
454 /*
455 **  SM_HEAP_REGISTER -- register a pointer into the heap for debugging.
456 **
457 **	Parameters:
458 **		ptr -- pointer to register.
459 **		size -- size of requested memory.
460 **		tag -- tag for debugging.
461 **		num -- additional value for debugging.
462 **		group -- heap group for debugging.
463 **
464 **	Returns:
465 **		true iff successfully registered (not yet in table).
466 */
467 
468 bool
469 sm_heap_register(ptr, size, tag, num, group)
470 	void *ptr;
471 	size_t size;
472 	char *tag;
473 	int num;
474 	int group;
475 {
476 	int i;
477 	SM_HEAP_ITEM_T *hi;
478 
479 	if (!HEAP_CHECK)
480 		return true;
481 	SM_REQUIRE(ptr != NULL);
482 	i = ptrhash(ptr);
483 # if SM_CHECK_REQUIRE
484 
485 	/*
486 	** We require that ptr is not already in SmHeapTable.
487 	*/
488 
489 	for (hi = SmHeapTable[i]; hi != NULL; hi = hi->hi_next)
490 	{
491 		if (hi->hi_ptr == ptr)
492 			sm_abort("sm_heap_register: ptr %p is already registered (%s:%d)",
493 				 ptr, hi->hi_tag, hi->hi_num);
494 	}
495 # endif /* SM_CHECK_REQUIRE */
496 	ENTER_CRITICAL();
497 	hi = (SM_HEAP_ITEM_T *) malloc(sizeof(SM_HEAP_ITEM_T));
498 	LEAVE_CRITICAL();
499 	if (hi == NULL)
500 		return false;
501 	hi->hi_ptr = ptr;
502 	hi->hi_size = size;
503 	hi->hi_tag = tag;
504 	hi->hi_num = num;
505 	hi->hi_group = group;
506 	hi->hi_next = SmHeapTable[i];
507 	SmHeapTable[i] = hi;
508 	return true;
509 }
510 /*
511 **  SM_REALLOC -- wrapper for realloc(), debugging version.
512 **
513 **	Parameters:
514 **		ptr -- pointer to old memory area.
515 **		size -- size of requested memory.
516 **
517 **	Returns:
518 **		Pointer to new memory area, NULL on failure.
519 */
520 
521 void *
522 sm_realloc(ptr, size)
523 	void *ptr;
524 	size_t size;
525 {
526 	void *newptr;
527 	SM_HEAP_ITEM_T *hi, **hp;
528 
529 	if (!HEAP_CHECK)
530 	{
531 		ENTER_CRITICAL();
532 		newptr = realloc(ptr, MALLOC_SIZE(size));
533 		LEAVE_CRITICAL();
534 		return newptr;
535 	}
536 
537 	if (ptr == NULL)
538 		return sm_malloc_tagged(size, "realloc", 0, SmHeapGroup);
539 
540 	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
541 	{
542 		if ((**hp).hi_ptr == ptr)
543 		{
544 			if (sm_xtrap_check())
545 				return NULL;
546 			hi = *hp;
547 			if (sm_debug_active(&SmHeapLimit, 1)
548 			    && sm_debug_level(&SmHeapLimit)
549 			       < SmHeapTotal - hi->hi_size + size)
550 			{
551 				return NULL;
552 			}
553 			ENTER_CRITICAL();
554 			newptr = realloc(ptr, MALLOC_SIZE(size));
555 			LEAVE_CRITICAL();
556 			if (newptr == NULL)
557 				return NULL;
558 			SmHeapTotal = SmHeapTotal - hi->hi_size + size;
559 			if (SmHeapTotal > SmHeapMaxTotal)
560 				SmHeapMaxTotal = SmHeapTotal;
561 			*hp = hi->hi_next;
562 			hi->hi_ptr = newptr;
563 			hi->hi_size = size;
564 			hp = &SmHeapTable[ptrhash(newptr)];
565 			hi->hi_next = *hp;
566 			*hp = hi;
567 			return newptr;
568 		}
569 	}
570 	sm_abort("sm_realloc: bad argument (%p)", ptr);
571 	/* NOTREACHED */
572 	return NULL;	/* keep Irix compiler happy */
573 }
574 
575 /*
576 **  SM_REALLOC_X -- wrapper for realloc(), debugging version.
577 **
578 **	Parameters:
579 **		ptr -- pointer to old memory area.
580 **		size -- size of requested memory.
581 **
582 **	Returns:
583 **		Pointer to new memory area.
584 **
585 **	Exceptions:
586 **		F:sm_heap -- out of memory
587 */
588 
589 void *
590 sm_realloc_x(ptr, size)
591 	void *ptr;
592 	size_t size;
593 {
594 	void *newptr;
595 	SM_HEAP_ITEM_T *hi, **hp;
596 
597 	if (!HEAP_CHECK)
598 	{
599 		ENTER_CRITICAL();
600 		newptr = realloc(ptr, MALLOC_SIZE(size));
601 		LEAVE_CRITICAL();
602 		if (newptr == NULL)
603 			sm_exc_raise_x(&SmHeapOutOfMemory);
604 		return newptr;
605 	}
606 
607 	if (ptr == NULL)
608 		return sm_malloc_tagged_x(size, "realloc", 0, SmHeapGroup);
609 
610 	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
611 	{
612 		if ((**hp).hi_ptr == ptr)
613 		{
614 			sm_xtrap_raise_x(&SmHeapOutOfMemory);
615 			hi = *hp;
616 			if (sm_debug_active(&SmHeapLimit, 1)
617 			    && sm_debug_level(&SmHeapLimit)
618 			       < SmHeapTotal - hi->hi_size + size)
619 			{
620 				sm_exc_raise_x(&SmHeapOutOfMemory);
621 			}
622 			ENTER_CRITICAL();
623 			newptr = realloc(ptr, MALLOC_SIZE(size));
624 			LEAVE_CRITICAL();
625 			if (newptr == NULL)
626 				sm_exc_raise_x(&SmHeapOutOfMemory);
627 			SmHeapTotal = SmHeapTotal - hi->hi_size + size;
628 			if (SmHeapTotal > SmHeapMaxTotal)
629 				SmHeapMaxTotal = SmHeapTotal;
630 			*hp = hi->hi_next;
631 			hi->hi_ptr = newptr;
632 			hi->hi_size = size;
633 			hp = &SmHeapTable[ptrhash(newptr)];
634 			hi->hi_next = *hp;
635 			*hp = hi;
636 			return newptr;
637 		}
638 	}
639 	sm_abort("sm_realloc_x: bad argument (%p)", ptr);
640 	/* NOTREACHED */
641 	return NULL;	/* keep Irix compiler happy */
642 }
643 
644 /*
645 **  SM_FREE_TAGGED -- wrapper around free(), debugging version.
646 **
647 **	Parameters:
648 **		ptr -- pointer to memory region.
649 **		tag -- tag for debugging.
650 **		num -- additional value for debugging.
651 **
652 **	Returns:
653 **		none.
654 */
655 
656 void
657 sm_free_tagged(ptr, tag, num)
658 	void *ptr;
659 	char *tag;
660 	int num;
661 {
662 	SM_HEAP_ITEM_T **hp;
663 
664 	if (ptr == NULL)
665 		return;
666 	if (!HEAP_CHECK)
667 	{
668 		ENTER_CRITICAL();
669 		free(ptr);
670 		LEAVE_CRITICAL();
671 		return;
672 	}
673 	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
674 	{
675 		if ((**hp).hi_ptr == ptr)
676 		{
677 			SM_HEAP_ITEM_T *hi = *hp;
678 
679 			*hp = hi->hi_next;
680 
681 			/*
682 			**  Fill the block with zeros before freeing.
683 			**  This is intended to catch problems with
684 			**  dangling pointers.  The block is filled with
685 			**  zeros, not with some non-zero value, because
686 			**  it is common practice in some C code to store
687 			**  a zero in a structure member before freeing the
688 			**  structure, as a defense against dangling pointers.
689 			*/
690 
691 			(void) memset(ptr, 0, hi->hi_size);
692 			SmHeapTotal -= hi->hi_size;
693 			ENTER_CRITICAL();
694 			free(ptr);
695 			free(hi);
696 			LEAVE_CRITICAL();
697 			return;
698 		}
699 	}
700 	sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num);
701 }
702 
703 /*
704 **  SM_HEAP_CHECKPTR_TAGGED -- check whether ptr is a valid argument to sm_free
705 **
706 **	Parameters:
707 **		ptr -- pointer to memory region.
708 **		tag -- tag for debugging.
709 **		num -- additional value for debugging.
710 **
711 **	Returns:
712 **		none.
713 **
714 **	Side Effects:
715 **		aborts if check fails.
716 */
717 
718 void
719 sm_heap_checkptr_tagged(ptr, tag, num)
720 	void *ptr;
721 	char *tag;
722 	int num;
723 {
724 	SM_HEAP_ITEM_T *hp;
725 
726 	if (!HEAP_CHECK)
727 		return;
728 	if (ptr == NULL)
729 		return;
730 	for (hp = SmHeapTable[ptrhash(ptr)]; hp != NULL; hp = hp->hi_next)
731 	{
732 		if (hp->hi_ptr == ptr)
733 			return;
734 	}
735 	sm_abort("sm_heap_checkptr(%p): bad ptr (%s:%d)", ptr, tag, num);
736 }
737 
738 /*
739 **  SM_HEAP_REPORT -- output "map" of used heap.
740 **
741 **	Parameters:
742 **		stream -- the file pointer to write to.
743 **		verbosity -- how much info?
744 **
745 **	Returns:
746 **		none.
747 */
748 
749 void
750 sm_heap_report(stream, verbosity)
751 	SM_FILE_T *stream;
752 	int verbosity;
753 {
754 	int i;
755 	unsigned long group0total, group1total, otherstotal, grandtotal;
756 
757 	if (!HEAP_CHECK || verbosity <= 0)
758 		return;
759 	group0total = group1total = otherstotal = grandtotal = 0;
760 	for (i = 0; i < sizeof(SmHeapTable) / sizeof(SmHeapTable[0]); ++i)
761 	{
762 		SM_HEAP_ITEM_T *hi = SmHeapTable[i];
763 
764 		while (hi != NULL)
765 		{
766 			if (verbosity > 2
767 			    || (verbosity > 1 && hi->hi_group != 0))
768 			{
769 				sm_io_fprintf(stream, SM_TIME_DEFAULT,
770 					"%4d %*lx %7lu bytes",
771 					hi->hi_group,
772 					(int) sizeof(void *) * 2,
773 					(long)hi->hi_ptr,
774 					(unsigned long)hi->hi_size);
775 				if (hi->hi_tag != NULL)
776 				{
777 					sm_io_fprintf(stream, SM_TIME_DEFAULT,
778 						"  %s",
779 						hi->hi_tag);
780 					if (hi->hi_num)
781 					{
782 						sm_io_fprintf(stream,
783 							SM_TIME_DEFAULT,
784 							":%d",
785 							hi->hi_num);
786 					}
787 				}
788 				sm_io_fprintf(stream, SM_TIME_DEFAULT, "\n");
789 			}
790 			switch (hi->hi_group)
791 			{
792 			  case 0:
793 				group0total += hi->hi_size;
794 				break;
795 			  case 1:
796 				group1total += hi->hi_size;
797 				break;
798 			  default:
799 				otherstotal += hi->hi_size;
800 				break;
801 			}
802 			grandtotal += hi->hi_size;
803 			hi = hi->hi_next;
804 		}
805 	}
806 	sm_io_fprintf(stream, SM_TIME_DEFAULT,
807 		"heap max=%lu, total=%lu, ",
808 		(unsigned long) SmHeapMaxTotal, grandtotal);
809 	sm_io_fprintf(stream, SM_TIME_DEFAULT,
810 		"group 0=%lu, group 1=%lu, others=%lu\n",
811 		group0total, group1total, otherstotal);
812 	if (grandtotal != SmHeapTotal)
813 	{
814 		sm_io_fprintf(stream, SM_TIME_DEFAULT,
815 			"BUG => SmHeapTotal: got %lu, expected %lu\n",
816 			(unsigned long) SmHeapTotal, grandtotal);
817 	}
818 }
819 #endif /* !SM_HEAP_CHECK */
820