/* Garbage detector of checker.
   Copyright 1993, 1994 Tristan Gingold
		  Written August 1993 by Tristan Gingold
		  
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU 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
General Public License for more details.

You should have received a copy of the GNU General Public License 
along with this program; see the file COPYING.  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 
*/

#define _MALLOC_INTERNAL
#include "malloc.h"
#include "message.h"

#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 nbr_pot_leaks;	/* How many potentiel leak */
static unsigned int memory_lost;	/* How much memory is lost by leaks */

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

static void search_in_block(struct malloc_header *block);

/* Check an address
 * Does a recursive descent if on heap. */
__ptr_t
chkr_address (__ptr_t ptr)
{
  struct malloc_header *block;
  
  /* Return now if ptr is not inside the heap */
  if(_lastblock == NULL_HEADER 
     || ptr < (__ptr_t)_firstblock
     || ptr > (__ptr_t)((u_int)_lastblock + _lastblock->size))
   return (__ptr_t)0;
   
  block = find_header(ptr);
  /* Must never happens */
  if (block == NULL_HEADER)
    return (__ptr_t)0;
    
  /* Don't forget that ptr can point to anywhere */
  if (block->state != MDBUSY)
    return (__ptr_t)0;	/* pointer on free block */

  /* Can't change the status when the block is already pointed 
   * No progression ( POINT_MAYBE -> POINT_SURE) because of inhiterance */
  if (block->garbage_t != POINT_NOT)
    return block;
  /* if ptr == real_ptr + (...), ptr points on the begin of the block */
  if( ptr == ((__ptr_t)block + be_red_zone + HEADER_SIZE))
    block->garbage_t = POINT_SURE;
  else 
    block->garbage_t = POINT_MAYBE;
  search_in_block(block);
  return block;
}

/* all the hdr_red_zone->garbage_t = 0, so all memory are garbage */
static void
init_detector(void)
{
  struct malloc_header *tmp;
  for (tmp = _firstblock; tmp != NULL_HEADER; tmp = tmp->next)
    {
      tmp->garbage_t = POINT_NOT;
      tmp->g_flag = G_ALONE;
    }
}

/* POINT_LEAK becomes POINT_SEEN */
static void
init_evol_detector(void)
{
 struct malloc_header *tmp;
 
 for (tmp = _firstblock; tmp != NULL_HEADER; tmp = tmp->next)
   {
     if(tmp->garbage_t == POINT_LEAK || tmp->garbage_t == POINT_SEEN)
       tmp->garbage_t = POINT_SEEN;
     else
       {
         tmp->garbage_t = POINT_NOT;
         tmp->g_flag = G_ALONE;
       }
   }
}

/* 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(void)
{
 __ptr_t *ptr;
 __ptr_t min=_firstblock;
 __ptr_t max=(__ptr_t)_lastblock + (_lastblock == NULL_HEADER ? 0 : _lastblock->size);
 
 for(ptr = &etext; ptr < &end; ptr++)
 {
   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(struct malloc_header *block)
{
 __ptr_t min=_firstblock;
 __ptr_t max=_lastblock + (_lastblock == NULL_HEADER ? 0 : _lastblock->size);
 __ptr_t *ptr;
 __ptr_t *ptr1;
 __ptr_t ptr2;
 
 /* block_ptr is a begin of a block or a fragment */

 /* Returns if already checked */
 if (block->garbage_t == POINT_JUST_SEEN)
     return;

 /* Check each pointer in this block */
 ptr = (__ptr_t*)( (char*)block + be_red_zone + HEADER_SIZE);
 ptr1 =(__ptr_t*)( (char*)ptr + block->info.busy.real_size);
 for(; ptr < ptr1; ptr++)
 {
   if(*ptr >= min && *ptr < max)
   {
     ptr2 = chkr_address(*ptr);
     if (ptr2 != (__ptr_t)0)
     {
       int saved = block->garbage_t;
       block->garbage_t = POINT_JUST_SEEN;
       search_in_block(ptr2); 	/* recursive search */
       block->garbage_t = saved;
     }
   }  
 }
}

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

/*
 * search for leaks in all segments
 */
static void
all_search(void)
{
  /* 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
      libraries.
     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(void)
{
  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(void)
{
  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 malloc_header*)*(__ptr_t*)ptr2 )->info.busy.g_link.g_number -
         ( (struct malloc_header*)*(__ptr_t*)ptr1 )->info.busy.g_link.g_number;
}

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

/* gether the brother of zone.
 * 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 malloc_header* zone) 
{
 struct malloc_header *block;

 /* check all blocks */
 for(block = zone; block != NULL_HEADER; block = block->next)
 {
   if (block->state != MDBUSY)
     continue;
   if (block->garbage_t == POINT_NOT && block->g_flag == G_ALONE
       && comp_stack(zone, block))
     {
       /* 'block' is a brother of 'zone'. It is marked known */
       block->g_flag = G_CHILD;
       block->garbage_t = POINT_LEAK;
       block->info.busy.g_link.g_parent = zone;
       zone->info.busy.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(void)
{
 struct malloc_header *block;

 /* Search all block */
 for(block = _firstblock; block != NULL_HEADER; block = block->next)
 {
   if (block->state != MDBUSY)
     continue;
   if ((block->garbage_t == POINT_NOT /*|| block->garbage_t == POINT_MAYBE */)
        && block->g_flag == G_ALONE)
     {
       /* a block has been found. It becomes the parent of the family */
       block->g_flag = G_PARENT;
       block->garbage_t = POINT_LEAK;
       block->info.busy.g_link.g_number = 1;
       nbr_leaks++;
       if (block->garbage_t == POINT_MAYBE)
         nbr_pot_leaks++;
       /* regroup brother of this block */
       regroup_brother(block); /* , _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)
{
 u_int i;
 struct malloc_header **leaks;
 struct malloc_header **pleaks;
 struct malloc_header *block;

 nbr_leaks = 0; 
 nbr_pot_leaks = 0;
 memory_lost = 0;
 
 regroup_stack();
 
 chkr_header("");
 if (nbr_leaks == 0)
   chkr_printf(M_NO_LEAK);
 else if (nbr_leaks == 1)
   chkr_printf(M_ONE_LEAK, nbr_pot_leaks);
 else
   chkr_printf(M_NBR_LEAKS, nbr_leaks, nbr_pot_leaks);
   
 if( nbr_leaks > 0)
 { 		
   /* store the pointers to the leaks */
   pleaks = leaks = (struct malloc_header**) __alloca(nbr_leaks * sizeof(void*));

   for(block = _firstblock; block != NULL_HEADER; block = block->next) /* check all block */
   {
     if (block->state != MDBUSY)
       continue;
     if (block->garbage_t == POINT_LEAK && block->g_flag == G_PARENT)
       {
         *pleaks++ = block;
         memory_lost += block->info.busy.g_link.g_number * block->info.busy.real_size;
       }
   }
   pleaks = leaks;
   chkr_printf(M_LEAKS_USE_N_BYTE, memory_lost,
   			memory_lost/1024, ((u_int)_lastblock + _lastblock->size)/1024);
   i = (memory_lost * 100) / (((u_int)_lastblock + _lastblock->size) / 100);
   chkr_printf(M_P_MEM_IS_LOST, i / 100, i % 100);
   qsort(leaks, nbr_leaks, sizeof(void*), comp_leak);
   
   for(i=0; i<nbr_leaks; i++, pleaks++)
   {
     chkr_printf(M_FOUND_N2_BYTES,
   		(*pleaks)->info.busy.g_link.g_number,
     		(*pleaks)->info.busy.real_size);
     chkr_printf(M_LOST_IS_PTR, 
		    (*pleaks) + be_red_zone + HEADER_SIZE);
#if CHKR_SAVESTACK
     if (status == 0)
       chkr_printf(M_CANT_USE_SYMTAB);
     else
     {
       leaks= (struct malloc_header**)((char*)(*pleaks) - af_red_zone + HEADER_SIZE + (*pleaks)->size);
       for(; *leaks!=(__ptr_t)0; leaks++)
	 chkr_show_addr((__ptr_t*)(leaks));
     }
#endif /* CHKR_SAVESTACK */
   }
 }
}

/* show all garbage zone */
static void
chkr_show_garbage(void)
{
#ifdef CHKR_SAVESTACK
 chkr_load_symtab();
 disp_leaks(symtab_available);
 chkr_unload_symtab();
#else
 disp_leaks(0);
#endif /* CHKR_SAVESTACK */
}

#endif /* CHKR_GARBAGE */
