heap.c revision 90792
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>
11SM_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
43SM_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
48const SM_EXC_TYPE_T SmHeapOutOfMemoryType =
49{
50	SmExcTypeMagic,
51	"F:sm.heap",
52	"",
53	sm_etype_printf,
54	"out of memory",
55};
56
57SM_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
92void *
93sm_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
118void *
119sm_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
141void *
142sm_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
168void *
169sm_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
192void
193sm_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
214int SmHeapGroup = 1;
215int 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
222size_t SmHeapTotal = 0;
223
224/*
225**  High water mark: the most that SmHeapTotal has ever been.
226*/
227
228size_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
236SM_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
244typedef struct sm_heap_item SM_HEAP_ITEM_T;
245struct 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
256static 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
265static 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
302static int
303ptrhash(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
357void *
358sm_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
411void *
412sm_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
468bool
469sm_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
521void *
522sm_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
589void *
590sm_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
656void
657sm_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
718void
719sm_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
749void
750sm_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