leakcheck.h


// This conditionalization is for once-only inclusion when
// checking is enabled
#ifndef __LEAKCHECK__
#define __LEAKCHECK__

// Written by Thomas E. Kammeyer.  If this proves useful, it would be
// nice to have that acknowledged, especially since it's the only
// thing I ask in return for this code.  In other words:  only
// a real jerk would delete this notice and pretend to have written
// this code.
/*
namespace version_info {
   const char *const leakcheck_h_id =
         "$Id: leakcheck.h,v 1.7 2005/07/05 18:06:44 tkammeyer Rel $";
}
*/

namespace leakcheck {
   extern int setalloccontext(const char *s); // always returns zero
   extern void clralloccontext();
}

#define lstr(X) lstr1(X) 
#define lstr1(X) #X
#define new leakcheck::setalloccontext(__FILE__ ":" lstr(__LINE__))?0:new
//NB:  the following formulation allows for delete being void-returning
// and binds as a single statement without creating a possible dangling
// else under macro expansion that could change the meaning of the code...
// but keep an eye on it anyway... it's a weird thing to be doing in a
// stmt macro.
#define delete if(leakcheck::setalloccontext(__FILE__ ":" lstr(__LINE__)));else delete

#define CLEARALLOCINFO() leakcheck::clralloccontext()
#define ALLOCFENCE() (new char[0])

#endif

unleakcheck.h


// I've found some cases involving template implementation inclusion in which I'd like to 

// Written by Thomas E. Kammeyer.  If this proves useful, it would be
// nice to have that acknowledged, especially since it's the only
// thing I ask in return for this code.  In other words:  only
// a real jerk would delete this notice and pretend to have written
// this code.
// This conditionalization is for once-only inclusion when
// checking is enabled
#undef __LEAKCHECK__
#undef lstr
#undef lstr1
#undef new
#undef delete
#undef CLEARALLOCINFO
#undef ALLOCFENCE

leakcheck.cpp


// define LEAKCHECK here and recompile (do not change leakcheck.h!)
// to turn on the checker.
// DO NOT include leakcheck.h here
//
// NOT SUPPORTED or utilized here:  
// set_new_handler, set_terminate, set_unexpected
// new as defined here never throws
// certain overloads of new or use of new with placement
// could ignore this tracker.

// Written by Thomas E. Kammeyer.  If this proves useful, it would be
// nice to have that acknowledged, especially since it's the only
// thing I ask in return for this code.  In other words:  only
// a real jerk would delete this notice and pretend to have written
// this code.

/*

namespace version_info {
   const char *const leakcheck_cpp_id =
         "$Id: leakcheck.cpp,v 1.23 2005/08/11 18:49:22 tkammeyer Rel $";
}
*/

#define LEAKCHECK
#if defined(LEAKCHECK) /*&& !defined(_DEBUG)*/

#include 
#include 
//#include 
#include "eh.h"

#include 
#include 
#include 
#include 

// breaknew and breakdel must end with a 0.
// for deterministic code, you can debug by adding
// pointer values to these lists to see where you
// are in the call stack when a given pointer op happens.
// May want to allow specifying a seqnum for each as well.
unsigned long breaknew[] = { 
0
};
unsigned long breakdel[] = {
0
};

using namespace std;

namespace leakcheck {

// handy snippet: MessageBox(NULL, "message", "title", MB_OK);

typedef unsigned char byte_t; // typedef for 1-byte type, sizeof this type should be 1

// allocation map and simple bookkeeping structure
struct eventrec {
   void *p;
   const char *label;
   size_t n;
   int seqnum;
   eventrec *next; // thread a list through these
};

eventrec **em = NULL;
const char *allocctxt = NULL;
int seqnum = 1;


void set(eventrec *er, void *p, const char *l, size_t nb, int sn, eventrec *next)
{
   er->p = p;
   er->label = l;
   er->n = nb;
   er->seqnum = sn;
   er->next = next;
};
void insert(eventrec *er)
{
   eventrec *&head = em[((byte_t*)(er->p)-(byte_t*)0)&0xFFFF];
   er->next=head;
   head=er;
}
eventrec *remove(void *p)
{
   eventrec *&head = em[((byte_t*)p-(byte_t*)0)&0xFFFF];
   eventrec *er = NULL;

   if (head) {
      if (p==head->p && head->seqnum>=0) {
         er = head;
         head=head->next;
      } else {
         eventrec *i;
         for (i = head; i; i=i->next) {
            if (i->next && i->next->p==p && i->next->seqnum>=0) {
               er = i->next;
               i->next = i->next->next;
               break;
            }
         }
      }
   };

   return er;
}

CRITICAL_SECTION cs;

static bool ok = false; // guarantee ok is false until setup called.
static bool okcs = false; // NOTE:  gdel will be called after regexitcheck,
                   // which destroys cs1.  So if we're executing
                   // gnew or gdel with this false, assume the cs is no
                   // longer relevant.

bool setup() {
//DebugBreak();
   if (!em) {
      // need to init the cs vars first...
      // at least cs1 which will be used under a call
      // to gnew when em's new call happens (should fall
      // through the recording part, though).
      InitializeCriticalSection(&cs);
      okcs = true;

      EnterCriticalSection(&cs);
      em = (eventrec**)malloc(sizeof(eventrec*)*(1<<16)); // bin by bottom 16 bits
      LeaveCriticalSection(&cs);

      memset(em, 0, sizeof(eventrec*)*(1<<16));
   }
   ok = (em!=NULL);

   return ok;
}
static bool __force_setup = setup();    // force a start-up call to setup() here...


// don't pass anything volatile to this!
// recommend using literals only.
int setalloccontext(const char *s) {
   allocctxt = s;
   return 0; // this *must* return zero by the way it is used in a macro!
}

void clralloccontext() { allocctxt = NULL; }

volatile bool gnew_re = false;
void *gnew(size_t n)
{
   void *p;

   if (okcs) EnterCriticalSection(&cs);

   // allow the following line to disable really large allocations
   // or modify it and throw on allocations larger than some reasonable
   // bound for the linking application
   //if ((signed long)n<0) throw;

    p = malloc(n);

   for (unsigned long *ulp = breaknew; *ulp; ulp++) if (p == (void*)*ulp) {
       __asm { int 3 }
   }

   // in theory what looks like a cross-thread race here shouldn't
   // be a problem because the only conflict occurs when  a single thread re-enters
   // this area... and that won't be an issue because re is set before the insert
   // is done... note that EnterCriticalSection or TryEnterCriticalSection
   // doesn't block or succeeds, respectively, if the thread holding the lock calls
   // it a second time (re-entry by the thread in the cs is always successful).
   if (!gnew_re && ok) {
      gnew_re=true;
      eventrec *er = (eventrec*)malloc(sizeof(eventrec));
      set(er, p, allocctxt, n, seqnum++, NULL);
      insert(er);
      gnew_re=false;
   }

   if (okcs) LeaveCriticalSection(&cs);

   return p;
}

volatile bool gdel_re = false;
void gdel(void *p)
{
   if (okcs) EnterCriticalSection(&cs);

   for (unsigned long *ulp = breakdel; *ulp; ulp++) if (p == (void*)*ulp) {
      __asm { int 3 }
   }

   if (!gdel_re && ok) {
      gdel_re=true;

      eventrec *er;
      if (p) {
         if (er=remove(p)) { // yes, I meant that assignment
            free(er);
         } else {  // BADDEL
            er = (eventrec*)malloc(sizeof(eventrec));
            set(er, p, allocctxt, 0, -seqnum++, NULL);
            insert(er);
            p = NULL; // this prevents a free call on p below.
         }
      } else { // DELNUL
         /* For now, I've stopped counting NULL deletes since
            they used to be illegal but aren't anymore and haven't
            been for long enough that most compilers now allow them
            without rancor.
         er = (eventrec*)malloc(sizeof(eventrec));
         set(er, p, allocctxt, 0, seqnum++, NULL);
         insert(er);
          */
      }
      gdel_re=false;
   }

   if (p) free(p);

   if (okcs) LeaveCriticalSection(&cs);
}

static void (*prev_t_func)() = NULL;
static void exitcheck() {
   eventrec *er;
   const char *lbl, *etype;
   double tot;
   int i;
   bool baddel;

   if (!ok) return;
   ok = false;   // disable record-keeping

   //kludged during devp., never fixed thereafter
   char buf[1024];
   GetModuleFileName(0, buf, 512);
   sprintf(buf+strlen(buf)+1, "%s_%d_MEM.txt", buf, getpid());
   FILE *fp = fopen(buf+strlen(buf)+1, "w");
   if (fp) {
      fprintf(fp, "%6d  %s\n%6d  PID==%d\n%6d  \n", 0, buf, 0, getpid(), 0);

      tot = 0;
      for (i=0; i<(1<<16); i++) {
         for (er=em[i]; er; er=er->next) {
            baddel = false;

            lbl=er->label;
            if (lbl && strlen(lbl) > 36) lbl += strlen(lbl)-36;

            if (er->p) {
               if (er->seqnum < 0) {
                  er->seqnum = -er->seqnum;
                  etype = " BADDEL";
                  baddel=true;
               } else etype = er->n ? " MEMLK " : " FENCE ";
            } else etype = " DELNUL";

            fprintf(fp, "%6d  %s %s%-36s%s ",
                  er->seqnum, etype,
                  (lbl!=er->label)?"...":"",
                  lbl?lbl:"",
                  (lbl!=er->label)?"":"   ");

            if (baddel || er->n!=0 || er->p==0) {
               if (baddel) fprintf(fp, "        ?b @ 0x%08x\n", er->p);
               else {
                  fprintf(fp, "%9db @ 0x%08x\n", er->n, er->p);
                  tot += er->n;
               }
            } else {
               fprintf(fp, "%12s 0x%08x\n", "---FENCE----", er->p);
            }

#if 0
// switching off content dumps for now...
            if (er->p) {
               // Try not do dump a ridiculously large leackcheck report.
               //
               // if 1/5 of the #bytes is <=32, just allow
               // 32 or all er->n bytes, whichever is smaller.
               // otherwise, round er->n/5 up to the nearest
               // multiple of 32 and allow that many bytes.
               // because we use about 80 charactyers to print
               // 16 bytes of hex dump (a little less but
               // the output of leakcheck has other lines, too),
               // the expansion is roughly 5:1 on dumping.
               // so printing 1/5 of most large sections is meant
               // to hold the size of the leakcheck dump file down
               // to about the same size as the amount of storage leaked.
               size_t allow  =
                  (er->n/5 <= 32)
                     ? (er->n>32 ? 32 : er->n)
                     : ((er->n/5)/32+((er->n/5)%32?1:0))*32
                     ;
               unsigned char *cp = (unsigned char*)er->p;
               unsigned char *ep = cp + allow;

               int j;
               while (cpseqnum, " MEMCON", cp);
                  for (j = 0; j < 16 && cp+j0 && j%4==0) fputc(' ', fp);
                     fprintf(fp, "%02x", cp[j]);
                  }
                  for (;j<16;j++) {
                     if (j>0 && j%4==0) fputc(' ', fp);
                     fputs("  ", fp);
                  }
                  fputc('|', fp);
                  for (j = 0; j < 16 && cp+j0 && j%4==0) fputc(' ', fp);
                     fprintf(fp, "%c", isgraph(cp[j])?cp[j]:'.');
                  }
                  fputc('\n', fp);
                  cp+=j;
               }
            }

            fprintf(fp, "%6d\n", er->seqnum);
#endif
         }
         while (em[i]) { er=em[i]; em[i]=em[i]->next; free(er); }
      }

      fprintf(fp, "%6d  Total leakage:  %.1lfb  <= %.3lfKb  <= %.3lfMb\n",
                  0, tot, tot/1024.0, tot/(1024*1024.0));

      fclose(fp);
   }
   free(em);
   em=NULL;


   if (okcs) DeleteCriticalSection(&cs); // the condition here is pro forma
   okcs = false; // mark cs as no longer valid... gdel gets called
                 // after this (several times) and
                 // tries to use the cs... this allows us to clean up
                 // here and not fail when that call happens
                  // NOTE:  this may not be true any longer since I don't
                  // use an stl container for event storage any longer.
}


void t_func() {
   exitcheck();
   if (prev_t_func) (*prev_t_func)(); else abort();
}


int regexitcheck() {
   prev_t_func = set_terminate(t_func);
   return atexit(exitcheck);
}
static int regexitcheckok = regexitcheck();

}

// Note that these aren't really std compliant since the
// new operators don't call a new handler or throw bad_alloc
// ever... I'm prototyping them this way to avoid compiler warnings
// since I'm specifying functions prototyped in new...
void *operator new(size_t n) throw(std::bad_alloc) { return leakcheck::gnew(n); }
void *operator new[](size_t n) throw(std::bad_alloc) { return leakcheck::gnew(n); }
void operator delete(void *p) throw() { leakcheck::gdel(p); }
void operator delete[](void *p) throw() { leakcheck::gdel(p); }

// These two variants allow overloading the nothrow version
// of new.  They don't work with the macros in leakcheck.h, but they
// allow you to track allocation through third party objects that
// use the none-throwing new by linking against this file.  Using ALLOCFENCE
// might help to localize the entry.  Breaking in these functions might allow
// you to see the call stack in a debugger when non-throwing new calls
// happens.
void *operator new(size_t n, const std::nothrow_t &nt) throw() {
//__asm { int 3 }
   return leakcheck::gnew(n);
};
void *operator new[](size_t n, const std::nothrow_t &nt) throw() {
//__asm { int 3 }
   return leakcheck::gnew(n);
};

#else

#include 
#include 

namespace leakcheck {
   int regexitcheck() { return 0; }
   int setalloccontext(const char *s) { return 0; }
   void clralloccontext() { return; }
}

#endif