/* Garbage detector of checker.
   Copyright 1993 Tristan Gingold
		  Written August 1993 Tristan Gingold

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.

   The author may be reached (Email) at the address marc@david.saclay.cea.fr,
   or (US/French mail) as Tristan Gingold 
   			  8 rue Parmentier
   			  F91120 PALAISEAU
   			  FRANCE */

#ifndef	_MALLOC_INTERNAL
#define _MALLOC_INTERNAL
#include "malloc.h"
#endif

#ifdef CHKR_GARBAGE
char *chkr_prog_path;			/* full path of the program */
static unsigned int nbr_leaks;		/* How many have been detected */
static unsigned int memory_lost;	/* How much memory is lost by leaks */

/* Some prototypes ... */
static void chkr_show_garbage();

/* call func for all busy block */
void foreach_busy(void (*func)(__ptr_t ptr))
{
 int i,j;
 char tags[BLOCKSIZE / sizeof(struct list)];
 struct list *next;
 
 for(i=1; i < _heaplimit; i++)	/* all block */
 {
   if(_heapinfo[i].status.state == MDHEAD)
   {
     if(_heapinfo[i].busy.type == 0) /* a block */
     {
       if (ADDRESS(i) != (__ptr_t)_heapinfo) 
         func(ADDRESS(i));
       i += _heapinfo[i].busy.info.size -1;
       continue;
     }
     /* A fragmented block */
     for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
      tags[j] = 0;	/* fragment is marked allocated */
     if(_heapinfo[i].busy.info.frag.nfree != 0)
     {
       next = (struct list *) ((char *) ADDRESS (i) +
 	       (_heapinfo[i].busy.info.frag.first << _heapinfo[i].busy.type));
       do
        tags[((unsigned int)next - (unsigned int)ADDRESS(i))
        		>> _heapinfo[i].busy.type] = 1;   /* frag is free */
       while((next = next->next) && BLOCK(next) == i);
     }
     for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
       if (!tags[j])	/* if frag is allocated */
         func((__ptr_t)((char*)ADDRESS(i) + (int)(j<<_heapinfo[i].busy.type)));
   }
 }
}

static void search_in_block(__ptr_t* ptr);

/* Check an address */
/* Does a recursive descent if on heap. */
__ptr_t chkr_address (__ptr_t ptr)
{
  size_t block;
  __ptr_t real_ptr;	/* begin of the block */
  int type,i;
  struct list *next;

  if(ptr < (__ptr_t)_heapbase || ptr > ADDRESS(_heaplimit))
   return (__ptr_t)0;	/* not inside the heap */
   
  block = BLOCK (ptr);

  /* Don't forget that ptr can point to anywhere */
  if(_heapinfo[block].status.state!=MDHEAD &&
     _heapinfo[block].status.state!=MDCORP && 
     _heapinfo[block].status.state!=MDTAIL )
    return (__ptr_t)0;	/* pointer on free block */
    
  /* Search the begin of the block */
  while(_heapinfo[block].status.state!=MDHEAD)
    block--;
 
  type=_heapinfo[block].busy.type;
  
  if(type==0)
  {
    real_ptr = ADDRESS(block);	/* found the begin of the block */
    if (real_ptr == (__ptr_t)_heapinfo) 
      return (__ptr_t)0;	/* point on the _heapinfo[] structure */
  }
  else
  {
   real_ptr = (__ptr_t)((unsigned int)ptr & (0xffffffff << type));
   /* Get the address of the first free fragment in this block.  */
   next = (struct list *) ((char *) ADDRESS (block) +
			   (_heapinfo[block].busy.info.frag.first << type));
		
   /* Test if this fragment is free */
   for( i=_heapinfo[block].busy.info.frag.nfree; i > 0; i--)
   {
     if(next==real_ptr)
       return (__ptr_t)0;	/* free block */
     next=next->next;
   }
  }
  if (((struct hdr_red_zone*)real_ptr)->magic != RED_ZONE_MAGIC)
  {
    (*__chkr_trap)(1,"garbage: bad magic number at block %u.",block);
    return (__ptr_t)0;
  }
  /* Can't change the status when the block is already pointed 
   * No progression ( POINT_MAYBE -> POINT_SURE) because of inhiterance */
  if (((struct hdr_red_zone*)real_ptr)->garbage_t != POINT_NOT)
    return real_ptr;
  /* if ptr == real_ptr + (...), ptr points on the begin of the block */
  if( ptr == (real_ptr + be_red_zone + sizeof(struct hdr_red_zone)))
  {
    ((struct hdr_red_zone*)real_ptr)->garbage_t = POINT_SURE;
    search_in_block((__ptr_t*)real_ptr);
  }
  else 
  {
    ((struct hdr_red_zone*)real_ptr)->garbage_t = POINT_MAYBE;
    search_in_block((__ptr_t*)real_ptr);
  }
 return real_ptr;
}

static void f_init_detector(__ptr_t ptr)
{
  ((struct hdr_red_zone*)ptr)->garbage_t = POINT_NOT;
  ((struct hdr_red_zone*)ptr)->g_flag = G_ALONE;
}

/* all the hdr_red_zone->garbage_t=0, so all memory are garbage */
static void init_detector()
{ 
 foreach_busy(f_init_detector);
}

/* POINT_LEAK becomes POINT_SEEN */
static void f_init_evol_detector(__ptr_t ptr1)
{
 struct hdr_red_zone *ptr = (struct hdr_red_zone*)ptr1;
 
 if(ptr->garbage_t == POINT_LEAK || ptr->garbage_t == POINT_SEEN)
   ptr->garbage_t = POINT_SEEN;
 else
 {
   ptr->garbage_t = POINT_NOT;
   ptr->g_flag = G_ALONE;
 }
}

/* POINT_LEAK becomes POINT_SEEN */
static void init_evol_detector()
{
 foreach_busy(f_init_evol_detector);
}

/* all the data are inside the data and bss segments */
extern __ptr_t end;	/* end of the bss segment */
extern __ptr_t etext;	/* begin of the data segment */

/* search pointer inside data and bss segment */
static void search_data()
{
 __ptr_t *ptr;
 __ptr_t min=_heapbase;
 __ptr_t max=ADDRESS(_heaplimit);
 
 for(ptr=&etext; ptr<&end; ptr++)
 {
   if(*ptr == _heapbase)	/* avoid it */
     continue;
   if(*ptr >= min && *ptr < max) /* does not point on heap */
     chkr_address(*ptr);
 }    
}

/* search pointer inside a block of the heap 
 * this is a recursive search: all good pointers are used for searching */
static void search_in_block(__ptr_t* block_ptr)
{
 __ptr_t min=_heapbase;
 __ptr_t max=ADDRESS(_heaplimit);
 __ptr_t *ptr;
 __ptr_t *ptr1;
 __ptr_t ptr2;
 
 /* block_ptr is a begin of a block or a fragment */

 if (((struct hdr_red_zone*)block_ptr)->garbage_t == POINT_JUST_SEEN)
     return; /* alreay checked */

 if(block_ptr == (__ptr_t)_heapinfo)
   return;
 
 ptr = (__ptr_t*)( (char*)block_ptr + be_red_zone 
 			+ sizeof(struct hdr_red_zone));
 ptr1 =(__ptr_t*)((char*)ptr
		  + ((struct hdr_red_zone*)block_ptr)->real_size);
 for(; ptr < ptr1; ptr++)
 {
   if( *ptr == (__ptr_t)_heapinfo )
     continue;
   if(*ptr >= min && *ptr < max)
   {
     ptr2 = chkr_address(*ptr);
     if (ptr2 != (__ptr_t)0)
     {
       int saved = ((struct hdr_red_zone*)block_ptr)->garbage_t;
       ((struct hdr_red_zone*)block_ptr)->garbage_t = POINT_JUST_SEEN;
       search_in_block(ptr2); 	/* recursive search */
       ((struct hdr_red_zone*)block_ptr)->garbage_t = saved;
     }
   }  
 }
}

/* Defined in sysdep.c */
void search_stack();

/*
 * search for leaks in all segments
 */
static void all_search()
{
  /* In fact, Checker think that there are only 2 segments: data and stack
     But, some OS (like linux) may split the segments: all the datas are not
      continuous (e.g. shared library). I don't know how to determine the 
      begin and the end of each sub-segment, so you must link with static
      library (Help me, H.J.)
     search_heap() must be the last, because Checker check only pointed zones
      in order to be surer */
  search_data();	/* looking for pointers ... */ 
  search_stack(); 
  /* search_register() */
  /* search_heap(); *sigh* this is of coarse order dependant.
     Lets do it all from search_data and search_stack, a true recursive search.
     */ /* Must be the last */
}

/* garbage detector 
 * display leaks. All leaks become new leaks 
 */
void chkr_garbage_detector()
{
  if(!__malloc_initialized)
    return;
  init_detector();	/* initialisation */
  all_search();		/* searching */
  chkr_show_garbage();	/* display leaks */
}

/* garbage detector show only the new leaks */
void chkr_evol_detector()
{
  if(!__malloc_initialized)
    return;
  init_evol_detector();	/* initialisation */
  all_search();		/* searching */
  chkr_show_garbage();	/* display leaks */
}

/* Compare two groups of leaks.
   This function is used to reverse sort. Called by qsort() */
static int comp_leak(const __ptr_t ptr1, const __ptr_t ptr2)
{
  return ( (struct hdr_red_zone*)*(__ptr_t*)ptr2 )->g_link.g_number -
         ( (struct hdr_red_zone*)*(__ptr_t*)ptr1 )->g_link.g_number;
}

/* Are the two zones allocated by the same function */
static int comp_stack(__ptr_t *ptr1, __ptr_t *ptr2)
{
 /* Have they the same size ? */
 if( ((struct hdr_red_zone*)ptr1)->real_size != 
 		((struct hdr_red_zone*)ptr2)->real_size )
   return 0; /* different */
 /* point of the top of the stack */
 ptr1= (__ptr_t*)((char*)ptr1 + be_red_zone + sizeof(struct hdr_red_zone) +
 		((struct hdr_red_zone*)ptr1)->real_size);
 ptr2= (__ptr_t*)((char*)ptr2 + be_red_zone + sizeof(struct hdr_red_zone) +
 		((struct hdr_red_zone*)ptr2)->real_size);
 while(*ptr1 && *ptr2 && *ptr1++ == *ptr2++) ;	
 if( *ptr1 == *ptr2 )
  return 1;
 else
  return 0;	/* different */
} 		

/* regroup the brother of zone, which type is type from beg
   Once a leak has been found, regroup_stack call regroup_stack.
   Because a leak has often brother ( other leaks which are allocated by the
    same functions), regroup_stack marks known those brothers and counts 
    them. Regrouping underlines important leaks and avoid to have a very long
    list. */
static void regroup_brother(struct hdr_red_zone* zone, int type, int beg) 
{
 int i,j;
 char tags[BLOCKSIZE / sizeof(struct list)];
 struct list *next;
 __ptr_t ptr;

 for(i=beg; i < _heaplimit; i++) /* check all blocks and frags*/
 {
   if(_heapinfo[i].status.state == MDHEAD && _heapinfo[i].busy.type == type)
   {
     /* busy */
     if(_heapinfo[i].busy.type == 0) /* block */
     {
      ptr=ADDRESS(i);
      if( ptr != (__ptr_t)_heapinfo)
        if( ((struct hdr_red_zone*)ptr)->garbage_t == POINT_NOT &&
            ((struct hdr_red_zone*)ptr)->g_flag == G_ALONE &&
            comp_stack((__ptr_t*)zone, (__ptr_t*)ptr))
        {
          /* this is a brother. It is marked known */
          ((struct hdr_red_zone*)ptr)->g_flag = G_CHILD;
          ((struct hdr_red_zone*)ptr)->garbage_t = POINT_LEAK;
          ((struct hdr_red_zone*)ptr)->g_link.g_parent = zone;
          ((struct hdr_red_zone*)zone)->g_link.g_number++;
	}
      i += _heapinfo[i].busy.info.size -1;
      continue;
     }
     /* A fragmented block */
     /* We must look on busy frag */
     for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
      tags[j] = 0;
     if(_heapinfo[i].busy.info.frag.nfree != 0)
     {
       next = (struct list *) ((char *) ADDRESS (i) +
 	       (_heapinfo[i].busy.info.frag.first << _heapinfo[i].busy.type));
       do
        tags[((unsigned int)next - (unsigned int)ADDRESS(i))
        			 >> _heapinfo[i].busy.type] = 1;
       while((next = next->next) && BLOCK(next) == i);
     }
     for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
       if (!tags[j])
       {
          ptr = (__ptr_t*)((char*)ADDRESS(i) + 
          		(int)(j<<_heapinfo[i].busy.type));
          if( ((struct hdr_red_zone*)ptr)->garbage_t == POINT_NOT &&
                  ((struct hdr_red_zone*)ptr)->g_flag == G_ALONE &&
		  comp_stack(ptr, (__ptr_t*)zone))
	  {
            ((struct hdr_red_zone*)ptr)->g_flag = G_CHILD;
            ((struct hdr_red_zone*)ptr)->garbage_t = POINT_LEAK;
            ((struct hdr_red_zone*)ptr)->g_link.g_parent = zone;
            ((struct hdr_red_zone*)zone)->g_link.g_number++;
          }
       }
   }
 }
}
 
/* regroup the leaks.
   regroup_stack looks for unregistred leaks. 
   Each time it finds a leak, it calls regroup_brother which search brothers.
*/
static void regroup_stack()
{
 int i,j;
 char tags[BLOCKSIZE / sizeof(struct list)];
 struct list *next;
 __ptr_t ptr;

 for(i=1; i < _heaplimit; i++) /* check all block */
 {
   if(_heapinfo[i].status.state == MDHEAD)
   {
     /* A block */
     if(_heapinfo[i].busy.type == 0)
     {
      ptr=ADDRESS(i);
      if( ptr != (__ptr_t)_heapinfo)
        if( ((struct hdr_red_zone*)ptr)->garbage_t == POINT_NOT &&
            ((struct hdr_red_zone*)ptr)->g_flag == G_ALONE)
        {
          /* a block has been found. It becomes the parent of the family */
          ((struct hdr_red_zone*)ptr)->g_flag = G_PARENT;
          ((struct hdr_red_zone*)ptr)->garbage_t = POINT_LEAK;
          ((struct hdr_red_zone*)ptr)->g_link.g_number = 1;
          nbr_leaks++;
          /* regroup brother of this block */
          regroup_brother((struct hdr_red_zone*)ptr, 
          		  _heapinfo[i].busy.type, i);
	}
      i += _heapinfo[i].busy.info.size -1;
      continue;
     }
     /* A fragmented block */
     for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
      tags[j] = 0;
     if(_heapinfo[i].busy.info.frag.nfree != 0)
     {
       next = (struct list *) ((char *) ADDRESS (i) +
 	       (_heapinfo[i].busy.info.frag.first << _heapinfo[i].busy.type));
       do
        tags[((unsigned int)next - (unsigned int)ADDRESS(i))
        			 >> _heapinfo[i].busy.type] = 1;
       while((next = next->next) && BLOCK(next) == i);
     }
     for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
       if (!tags[j])
       {
         ptr = (__ptr_t*)((char*)ADDRESS(i) + 
         		(int)(j<<_heapinfo[i].busy.type));
         if( ((struct hdr_red_zone*)ptr)->garbage_t == POINT_NOT &&
            ((struct hdr_red_zone*)ptr)->g_flag == G_ALONE)
        {
          ((struct hdr_red_zone*)ptr)->g_flag = G_PARENT;
          ((struct hdr_red_zone*)ptr)->garbage_t = POINT_LEAK;
          ((struct hdr_red_zone*)ptr)->g_link.g_number = 1;
          nbr_leaks++;
          regroup_brother((struct hdr_red_zone*)ptr, 
          		  _heapinfo[i].busy.type, i);
	}
       }
   }
 }
}

/* function used to display all leaks...
   In fact, it displays all the groups of leaks. Two leaks belong to the same
   group if their allocators are the same. */
static void disp_leaks(int status)
{
 int i,j;
 char tags[BLOCKSIZE / sizeof(struct list)];
 struct list *next;
 __ptr_t ptr;
 __ptr_t *leaks;
 __ptr_t *pleaks;

 nbr_leaks = 0; 
 memory_lost = 0;
 
 regroup_stack();
 
 (*__chkr_trap)(1,"There %s %d leak%s", nbr_leaks > 1 ? "are" : "is",
 		nbr_leaks, nbr_leaks > 1 ? "s" : ""); 
 
 if( nbr_leaks > 0)
 { 		
   /* store the pointers to the leaks */
   pleaks = leaks = (__ptr_t*) __alloca(nbr_leaks * sizeof(void*));

   for(i=1; i < _heaplimit; i++) /* check all block */
   {
     if(_heapinfo[i].status.state == MDHEAD)
     {
       /* A block */
       if(_heapinfo[i].busy.type == 0)
       {
         ptr = ADDRESS(i);
         if( ptr != (__ptr_t)_heapinfo)
           if( ((struct hdr_red_zone*)ptr)->garbage_t == POINT_LEAK &&
                 ((struct hdr_red_zone*)ptr)->g_flag == G_PARENT)
           {
             *pleaks++ = ptr;
             memory_lost += ((struct hdr_red_zone*)ptr)->g_link.g_number *
             	            ((struct hdr_red_zone*)ptr)->real_size;
           }
         i += _heapinfo[i].busy.info.size -1;
         continue;
       }
       /* A fragmented block */
       for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
         tags[j] = 0;
       if(_heapinfo[i].busy.info.frag.nfree != 0)
       {
         next = (struct list *) ((char *) ADDRESS (i) +
 	       (_heapinfo[i].busy.info.frag.first << _heapinfo[i].busy.type));
         do
           tags[((unsigned int)next - (unsigned int)ADDRESS(i))
        			 >> _heapinfo[i].busy.type] = 1;
         while((next = next->next) && BLOCK(next) == i);
       }
       for(j = (BLOCKSIZE >> _heapinfo[i].busy.type) - 1; j >= 0; j--)
         if (!tags[j])
         {
           ptr = ADDRESS (i) + (j==0 ? 0 : j<<_heapinfo[i].busy.type);
           if (((struct hdr_red_zone*)ptr)->garbage_t == POINT_LEAK &&
                     ((struct hdr_red_zone*)ptr)->g_flag == G_PARENT)
           {
             *pleaks++ = ptr;
              memory_lost += ((struct hdr_red_zone*)ptr)->g_link.g_number *
             	            ((struct hdr_red_zone*)ptr)->real_size;
           }
         }
     }
   }
   pleaks = leaks;
   (*__chkr_trap)(0,"Leaks use %u bytes (%u Kb) / %u Kb.", memory_lost,
   			memory_lost/1024, _heaplimit * BLOCKSIZE / 1024);
   i = (memory_lost * 10000) / (_heaplimit * BLOCKSIZE);
   (*__chkr_trap)(0,"So, %2d.%2d%% of the memory is lost.", 
   		i / 100, i % 100);
   qsort(leaks, nbr_leaks, sizeof(void*), comp_leak);
   
   for(i=0; i<nbr_leaks; i++, pleaks++)
   {
     (*__chkr_trap)(1,"Checker found %u * %u bytes.",
   		((struct hdr_red_zone*)*pleaks)->g_link.g_number,
     		((struct hdr_red_zone*)*pleaks)->real_size);
     (*__chkr_trap)(0,"Lost is ptr=0x%x", 
		    (*pleaks) + be_red_zone + sizeof(struct hdr_red_zone));
#if CHKR_SAVESTACK
     if (status == 0)
       (*__chkr_trap)(0,"Can't access to the symbol table.");
     else
     {
       leaks= (__ptr_t*)(*pleaks + be_red_zone + sizeof(struct hdr_red_zone) +
     			((struct hdr_red_zone*)*pleaks)->real_size);
       for(; *leaks!=(__ptr_t)0; leaks++)
	 chkr_show_addr(leaks);
     }
#endif /* CHKR_SAVESTACK */
   }
 }
}

/* show all garbage zone */
static void chkr_show_garbage()
{
#ifdef CHKR_SAVESTACK
 chkr_use_symtab(disp_leaks);
#else
 disp_leaks(1);
#endif /* CHKR_SAVESTACK */
}
#endif /* CHKR_GARBAGE */
