OpenBSD-4.6/gnu/usr.bin/lynx/src/LYLeaks.c

/*
 *	Copyright (c) 1994, University of Kansas, All Rights Reserved
 *
 *	This code will be used only if LY_FIND_LEAKS is defined.
 *
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 *	10-30-97	modified to handle StrAllocCopy() and
 *			  StrAllocCat(). - KW & FM
 */

/*
 *	Disable the overriding of the memory routines for this file.
 */
#define NO_MEMORY_TRACKING

#include <HTUtils.h>
#include <LYexit.h>
#include <LYLeaks.h>
#include <LYUtils.h>
#include <LYGlobalDefs.h>

#ifdef LY_FIND_LEAKS

static AllocationList *ALp_RunTimeAllocations = NULL;

#define LEAK_SUMMARY

#ifdef LEAK_SUMMARY

static long now_allocated = 0;
static long peak_alloced = 0;

static long total_alloced = 0;
static long total_freed = 0;

static long count_mallocs = 0;
static long count_frees = 0;

static void CountMallocs(long size)
{
    ++count_mallocs;
    total_alloced += size;
    now_allocated += size;
    if (peak_alloced < now_allocated)
	peak_alloced = now_allocated;
}

static void CountFrees(long size)
{
    ++count_frees;
    total_freed += size;
    now_allocated -= size;
}

#else
#define CountMallocs() ++count_mallocs
#define CountFrees()		/* nothing */
#endif

/*
 *  Purpose:	Add a new allocation item to the list.
 *  Arguments:		ALp_new The new item to add.
 *  Return Value:	void
 *  Remarks/Portability/Dependencies/Restrictions:
 *		Static function made to make code reusable in projects beyond
 *		Lynx (some might ask why not use HTList).
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
static void AddToList(AllocationList * ALp_new)
{
    /*
     * Just make this the first item in the list.
     */
    ALp_new->ALp_Next = ALp_RunTimeAllocations;
    ALp_RunTimeAllocations = ALp_new;
}

/*
 *  Purpose:	Find the place in the list where vp_find is currently
 *		tracked.
 *  Arguments:		vp_find A pointer to look for in the list.
 *  Return Value:	AllocationList *	Either vp_find's place in the
 *						list or NULL if not found.
 *  Remarks/Portability/Dependencies/Restrictions:
 *		Static function made to make code reusable in projects outside
 *		of Lynx (some might ask why not use HTList).
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
static AllocationList *FindInList(void *vp_find)
{
    AllocationList *ALp_find = ALp_RunTimeAllocations;

    /*
     * Go through the list of allocated pointers until end of list or vp_find
     * is found.
     */
    while (ALp_find != NULL) {
	if (ALp_find->vp_Alloced == vp_find) {
	    break;
	}
	ALp_find = ALp_find->ALp_Next;
    }

    return (ALp_find);
}

/*
 *  Purpose:	Remove the specified item from the list.
 *  Arguments:		ALp_del The item to remove from the list.
 *  Return Value:	void
 *  Remarks/Portability/Dependencies/Restrictions:
 *		Static function made to make code reusable in projects outside
 *		of Lynx (some might ask why not use HTList).
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
static void RemoveFromList(AllocationList * ALp_del)
{
    AllocationList *ALp_findbefore = ALp_RunTimeAllocations;

    /*
     * There is one special case, where the item to remove is the first in the
     * list.
     */
    if (ALp_del == ALp_findbefore) {
	ALp_RunTimeAllocations = ALp_del->ALp_Next;
	return;
    }

    /*
     * Loop through checking all of the next values, if a match don't continue. 
     * Always assume the item will be found.
     */
    while (ALp_findbefore->ALp_Next != ALp_del) {
	ALp_findbefore = ALp_findbefore->ALp_Next;
    }

    /*
     * We are one item before the one to get rid of.  Get rid of it.
     */
    ALp_findbefore->ALp_Next = ALp_del->ALp_Next;
}

/*
 * Make the malloc-sequence available for debugging/tracing.
 */
#ifndef LYLeakSequence
long LYLeakSequence(void)
{
    return count_mallocs;
}
#endif

/*
 *  Purpose:	Print a report of all memory left unallocated by
 *		Lynx code or attempted unallocations on
 *		pointers that are not valid and then free
 *		all unfreed memory.
 *  Arguments:		void
 *  Return Value:	void
 *  Remarks/Portability/Dependencies/Restrictions:
 *		This function should be registered for execution with the
 *		atexit (stdlib.h) function as the first statement
 *		in main.
 *		All output of this function is sent to the file defined in
 *		the header LYLeaks.h (LEAKAGE_SINK).
 */
void LYLeaks(void)
{
    AllocationList *ALp_head;
    size_t st_total = (size_t) 0;
    FILE *Fp_leakagesink;

    if (LYfind_leaks == FALSE)
	return;

    /*
     * Open the leakage sink to take all the output.  Recreate the file each
     * time.  Do nothing if unable to open the file.
     */
    Fp_leakagesink = LYNewTxtFile(LEAKAGE_SINK);
    if (Fp_leakagesink == NULL) {
	return;
    }

    while (ALp_RunTimeAllocations != NULL) {
	/*
	 * Take the head off of the run time allocation list.
	 */
	ALp_head = ALp_RunTimeAllocations;
	ALp_RunTimeAllocations = ALp_head->ALp_Next;

	/*
	 * Print the type of leak/error.  Free off memory when we no longer
	 * need it.
	 */
	if (ALp_head->vp_Alloced == NULL) {
	    /*
	     * If there is realloc information on the bad request, then it was
	     * a bad pointer value in a realloc statement.
	     */
	    fprintf(Fp_leakagesink, "%s.\n",
		    gettext("Invalid pointer detected."));
	    fprintf(Fp_leakagesink, "%s\t%ld\n",
		    gettext("Sequence:"),
		    ALp_head->st_Sequence);
	    fprintf(Fp_leakagesink, "%s\t%p\n",
		    gettext("Pointer:"), ALp_head->vp_BadRequest);

	    /*
	     * Don't free the bad request, it is an invalid pointer.  If the
	     * free source information is empty, we should check the realloc
	     * information too since it can get passed bad pointer values also.
	     */
	    if (ALp_head->SL_memory.cp_FileName == NULL) {
		fprintf(Fp_leakagesink, "%s\t%s\n",
			gettext("FileName:"),
			ALp_head->SL_realloc.cp_FileName);
		fprintf(Fp_leakagesink, "%s\t%d\n",
			gettext("LineCount:"),
			ALp_head->SL_realloc.ssi_LineNumber);
	    } else {
		fprintf(Fp_leakagesink, "%s\t%s\n",
			gettext("FileName:"),
			ALp_head->SL_memory.cp_FileName);
		fprintf(Fp_leakagesink, "%s\t%d\n",
			gettext("LineCount:"),
			ALp_head->SL_memory.ssi_LineNumber);
	    }
	} else {
	    size_t i_counter;
	    char *value = (char *) (ALp_head->vp_Alloced);

	    /*
	     * Increment the count of total memory lost and then print the
	     * information.
	     */
	    st_total += ALp_head->st_Bytes;

	    fprintf(Fp_leakagesink, "%s\n",
		    gettext("Memory leak detected."));
	    fprintf(Fp_leakagesink, "%s\t%ld\n",
		    gettext("Sequence:"),
		    ALp_head->st_Sequence);
	    fprintf(Fp_leakagesink, "%s\t%p\n",
		    gettext("Pointer:"),
		    ALp_head->vp_Alloced);
	    fprintf(Fp_leakagesink, "%s\t",
		    gettext("Contains:"));
	    for (i_counter = 0;
		 i_counter < ALp_head->st_Bytes &&
		 i_counter < MAX_CONTENT_LENGTH;
		 i_counter++) {
		if (isprint(UCH(value[i_counter]))) {
		    fprintf(Fp_leakagesink, "%c", value[i_counter]);
		} else {
		    fprintf(Fp_leakagesink, "|");
		}
	    }
	    fprintf(Fp_leakagesink, "\n");
	    fprintf(Fp_leakagesink, "%s\t%d\n",
		    gettext("ByteSize:"),
		    (int) (ALp_head->st_Bytes));
	    fprintf(Fp_leakagesink, "%s\t%s\n",
		    gettext("FileName:"),
		    ALp_head->SL_memory.cp_FileName);
	    fprintf(Fp_leakagesink, "%s\t%d\n",
		    gettext("LineCount:"),
		    ALp_head->SL_memory.ssi_LineNumber);
	    /*
	     * Give the last time the pointer was realloced if it happened
	     * also.
	     */
	    if (ALp_head->SL_realloc.cp_FileName != NULL) {
		fprintf(Fp_leakagesink, "%s\t%s\n",
			gettext("realloced:"),
			ALp_head->SL_realloc.cp_FileName);
		fprintf(Fp_leakagesink, "%s\t%d\n",
			gettext("LineCount:"),
			ALp_head->SL_realloc.ssi_LineNumber);
	    }
	    fflush(Fp_leakagesink);
	    FREE(ALp_head->vp_Alloced);
	}

	/*
	 * Create a blank line and release the memory held by the item.
	 */
	fprintf(Fp_leakagesink, "\n");
	FREE(ALp_head);
    }

    /*
     * Give a grand total of the leakage.  Close the output file.
     */
    fprintf(Fp_leakagesink, "%s\t%u\n",
	    gettext("Total memory leakage this run:"),
	    (unsigned) st_total);
#ifdef LEAK_SUMMARY
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Peak allocation"), peak_alloced);
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Bytes allocated"), total_alloced);
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Total mallocs"), count_mallocs);
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Total frees"), count_frees);
#endif
    fclose(Fp_leakagesink);

    HTSYS_purge(LEAKAGE_SINK);
#if defined(NCURSES) && defined(HAVE__NC_FREEALL)
    _nc_freeall();
#endif
}

/*
 *  Purpose:	Capture allocations using malloc (stdlib.h) and track
 *		the information in a list.
 *  Arguments:	st_bytes	The size of the allocation requested
 *				in bytes.
 *		cp_File		The file from which the request for
 *				allocation came from.
 *		ssi_Line	The line number in cp_File where the
 *				allocation request came from.
 *  Return Value:	void *	A pointer to the allocated memory or NULL on
 *				failure as per malloc (stdlib.h)
 *  Remarks/Portability/Dependencies/Restrictions:
 *		If no memory is allocated, then no entry is added to the
 *		allocation list.
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
void *LYLeakMalloc(size_t st_bytes, const char *cp_File,
		   const short ssi_Line)
{
    void *vp_malloc;

    if (LYfind_leaks == FALSE)
	return (void *) malloc(st_bytes);

    /*
     * Do the actual allocation.
     */
    vp_malloc = (void *) malloc(st_bytes);
    CountMallocs(st_bytes);

    /*
     * Only on successful allocation do we track any information.
     */
    if (vp_malloc != NULL) {
	/*
	 * Further allocate memory to store the information.  Just return on
	 * failure to allocate more.
	 */
	AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    return (vp_malloc);
	}
	/*
	 * Copy over the relevant information.  There is no need to allocate
	 * more memory for the file name as it is a static string anyhow.
	 */
	ALp_new->st_Sequence = count_mallocs;
	ALp_new->vp_Alloced = vp_malloc;
	ALp_new->st_Bytes = st_bytes;
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 * Add the new item to the allocation list.
	 */
	AddToList(ALp_new);
    }

    return (vp_malloc);
}

/*
 *  Purpose:	Add information about new allocation to the list,
 *		after a call to malloc or calloc or an equivalent
 *		function which may or may not have already created
 *		a list entry.
 *  Arguments:	vp_malloc	The pointer to newly allocated memory.
 *  Arguments:	st_bytes	The size of the allocation requested
 *				in bytes.
 *		cp_File		The file from which the request for
 *				allocation came from.
 *		ssi_Line	The line number in cp_File where the
 *				allocation request came from.
 *  Return Value:	void *	A pointer to the allocated memory or NULL on
 *				failure.
 *  Remarks/Portability/Dependencies/Restrictions:
 *		If no memory is allocated, then no entry is added to the
 *		allocation list.
 *  Revision History:
 *	1999-02-08	created, modelled after LYLeakMalloc - kw
 */
AllocationList *LYLeak_mark_malloced(void *vp_malloced,
				     size_t st_bytes,
				     const char *cp_File,
				     const short ssi_Line)
{
    AllocationList *ALp_new = NULL;

    if (LYfind_leaks == FALSE)
	return NULL;

    /*
     * The actual allocation has already been done!
     *
     * Only on successful allocation do we track any information.
     */
    if (vp_malloced != NULL) {
	/*
	 * See if there is already an entry.  If so, just update the source
	 * location info.
	 */
	ALp_new = FindInList(vp_malloced);
	if (ALp_new) {
	    ALp_new->SL_memory.cp_FileName = cp_File;
	    ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
	    return (ALp_new);
	}
	/*
	 * Further allocate memory to store the information.  Just return on
	 * failure to allocate more.
	 */
	ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    return (NULL);
	}
	/*
	 * Copy over the relevant information.
	 */
	ALp_new->vp_Alloced = vp_malloced;
	ALp_new->st_Bytes = st_bytes;
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 * Add the new item to the allocation list.
	 */
	AddToList(ALp_new);
    }

    return (ALp_new);
}

/*
 *  Purpose:	Capture allocations by calloc (stdlib.h) and
 *		save relevant information in a list.
 *  Arguments:	st_number	The number of items to allocate.
 *		st_bytes	The size of each item.
 *		cp_File		The file which wants to allocation.
 *		ssi_Line	The line number in cp_File requesting
 *				the allocation.
 *  Return Value:	void *	The allocated memory, or NULL on failure as
 *				per calloc (stdlib.h)
 *  Remarks/Portability/Dependencies/Restrictions:
 *		If no memory can be allocated, then no entry will be added
 *		to the list.
 *  Revision History:
 *		05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
void *LYLeakCalloc(size_t st_number, size_t st_bytes, const char *cp_File,
		   const short ssi_Line)
{
    void *vp_calloc;

    if (LYfind_leaks == FALSE)
	return (void *) calloc(st_number, st_bytes);

    /*
     * Allocate the requested memory.
     */
    vp_calloc = (void *) calloc(st_number, st_bytes);
    CountMallocs(st_bytes);

    /*
     * Only if the allocation was a success do we track information.
     */
    if (vp_calloc != NULL) {
	/*
	 * Allocate memory for the item to be in the list.  If unable, just
	 * return.
	 */
	AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    return (vp_calloc);
	}

	/*
	 * Copy over the relevant information.  There is no need to allocate
	 * memory for the file name as it is a static string anyway.
	 */
	ALp_new->st_Sequence = count_mallocs;
	ALp_new->vp_Alloced = vp_calloc;
	ALp_new->st_Bytes = (st_number * st_bytes);
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 * Add the item to the allocation list.
	 */
	AddToList(ALp_new);
    }

    return (vp_calloc);
}

/*
 *  Purpose:	Capture any realloc (stdlib.h) calls in order to
 *		properly keep track of our run time allocation
 *		table.
 *  Arguments:	vp_Alloced	The previously allocated block of
 *				memory to resize.  If NULL,
 *				realloc works just like
 *				malloc.
 *		st_newBytes	The new size of the chunk of memory.
 *		cp_File		The file containing the realloc.
 *		ssi_Line	The line containing the realloc in cp_File.
 *  Return Value:	void *	The new pointer value (could be the same) or
 *				NULL if unable to resize (old block
 *				still exists).
 *  Remarks/Portability/Dependencies/Restrictions:
 *		If unable to resize vp_Alloced, then no change in the
 *		allocation list will be made.
 *		If vp_Alloced is an invalid pointer value, the program will
 *		exit after one last entry is added to the allocation list.
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
void *LYLeakRealloc(void *vp_Alloced,
		    size_t st_newBytes,
		    const char *cp_File,
		    const short ssi_Line)
{
    void *vp_realloc;
    AllocationList *ALp_renew;

    if (LYfind_leaks == FALSE)
	return (void *) realloc(vp_Alloced, st_newBytes);

    /*
     * If we are asked to resize a NULL pointer, this is just a malloc call.
     */
    if (vp_Alloced == NULL) {
	return (LYLeakMalloc(st_newBytes, cp_File, ssi_Line));
    }

    /*
     * Find the current vp_Alloced block in the list.  If NULL, this is an
     * invalid pointer value.
     */
    ALp_renew = FindInList(vp_Alloced);
    if (ALp_renew == NULL) {
	/*
	 * Track the invalid pointer value and then exit.  If unable to
	 * allocate, just exit.
	 */
	auto AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    exit_immediately(EXIT_FAILURE);
	}

	/*
	 * Set the information up; no need to allocate file name since it is a
	 * static string.
	 */
	ALp_new->vp_Alloced = NULL;
	ALp_new->vp_BadRequest = vp_Alloced;
	ALp_new->SL_realloc.cp_FileName = cp_File;
	ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;

	/*
	 * Add the item to the list.  Exit.
	 */
	AddToList(ALp_new);
	exit_immediately(EXIT_FAILURE);
    }

    /*
     * Perform the resize.  If not NULL, record the information.
     */
    vp_realloc = (void *) realloc(vp_Alloced, st_newBytes);
    CountMallocs(st_newBytes);
    CountFrees(ALp_renew->st_Bytes);

    if (vp_realloc != NULL) {
	ALp_renew->st_Sequence = count_mallocs;
	ALp_renew->vp_Alloced = vp_realloc;
	ALp_renew->st_Bytes = st_newBytes;

	/*
	 * Update the realloc information, too.  No need to allocate file name,
	 * static string.
	 */
	ALp_renew->SL_realloc.cp_FileName = cp_File;
	ALp_renew->SL_realloc.ssi_LineNumber = ssi_Line;
    }

    return (vp_realloc);
}

/*
 *  Purpose:	Add information about reallocated memory to the list,
 *		after a call to realloc or an equivalent
 *		function which has not already created or updated
 *		a list entry.
 *  Arguments:	ALp_old		List entry for previously allocated
 *				block of memory to resize.  If NULL,
 *				mark_realloced works just like
 *				mark_malloced.
 *		vp_realloced	The new pointer, after resizing.
 *		st_newBytes	The new size of the chunk of memory.
 *		cp_File		The file to record.
 *		ssi_Line	The line to record.
 *  Return Value:		Pointer to new or updated list entry
 *				for this memory block.
 *				NULL on allocation error.
 *  Revision History:
 *	1999-02-11	created kw
 */
#if defined(LY_FIND_LEAKS) && defined(LY_FIND_LEAKS_EXTENDED)
static AllocationList *mark_realloced(AllocationList * ALp_old, void *vp_realloced,
				      size_t st_newBytes,
				      const char *cp_File,
				      const short ssi_Line)
{
    /*
     * If there is no list entry for the old allocation, treat this as if a new
     * allocation had happened.
     */
    if (ALp_old == NULL) {
	return (LYLeak_mark_malloced(vp_realloced, st_newBytes, cp_File, ssi_Line));
    }

    /*
     * ALp_old represents the memory block before reallocation.  Assume that if
     * we get here, there isn't yet a list entry for the new, possibly
     * different, address after realloc, that is our list hasn't been updated -
     * so we're going to do that now.
     */

    if (vp_realloced != NULL) {
	ALp_old->vp_Alloced = vp_realloced;
	ALp_old->st_Bytes = st_newBytes;
	ALp_old->SL_realloc.cp_FileName = cp_File;
	ALp_old->SL_realloc.ssi_LineNumber = ssi_Line;
    }

    return (ALp_old);
}
#endif /* not LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED */

/*
 *  Purpose:	Capture all requests to free information and also
 *		remove items from the allocation list.
 *  Arguments:	vp_Alloced	The memory to free.
 *		cp_File		The file calling free.
 *		ssi_Line	The line of cp_File calling free.
 *  Return Value:	void
 *  Remarks/Portability/Dependencies/Restrictions:
 *		If the pointer value is invalid, then an item will be added
 *		to the list and nothing else is done.
 *		I really like the name of this function and one day hope
 *		that Lynx is Leak Free.
 *  Revision History:
 *	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
void LYLeakFree(void *vp_Alloced,
		const char *cp_File,
		const short ssi_Line)
{
    AllocationList *ALp_free;

    if (LYfind_leaks == FALSE) {
	free(vp_Alloced);
	return;
    }

    /*
     * Find the pointer in the allocated list.  If not found, bad pointer.  If
     * found, free list item and vp_Allloced.
     */
    ALp_free = FindInList(vp_Alloced);
    if (ALp_free == NULL) {
	/*
	 * Create the final entry before exiting marking this error.  If unable
	 * to allocate more memory just exit.
	 */
	AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    exit_immediately(EXIT_FAILURE);
	}

	/*
	 * Set up the information, no memory need be allocated for the file
	 * name since it is a static string.
	 */
	ALp_new->vp_Alloced = NULL;
	ALp_new->vp_BadRequest = vp_Alloced;
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 * Add the entry to the list and then return.
	 */
	AddToList(ALp_new);
	return;
    } else {
	/*
	 * Free off the memory.  Take entry out of allocation list.
	 */
	CountFrees(ALp_free->st_Bytes);
	RemoveFromList(ALp_free);
	FREE(ALp_free);
	FREE(vp_Alloced);
    }
}

/*
 *  Allocates a new copy of a string, and returns it.
 *  Tracks allocations by using other LYLeakFoo functions.
 *  Equivalent to HTSACopy in HTUtils.c - KW
 */
char *LYLeakSACopy(char **dest,
		   const char *src,
		   const char *cp_File,
		   const short ssi_Line)
{
    if (src != NULL && src == *dest) {
	CTRACE((tfp,
		"LYLeakSACopy: *dest equals src, contains \"%s\"\n",
		src));
	return *dest;
    }
    if (*dest) {
	LYLeakFree(*dest, cp_File, ssi_Line);
	*dest = NULL;
    }
    if (src) {
	*dest = (char *) LYLeakMalloc(strlen(src) + 1, cp_File, ssi_Line);
	if (*dest == NULL)
	    outofmem(__FILE__, "LYLeakSACopy");
	strcpy(*dest, src);
    }
    return *dest;
}

/*
 *  String Allocate and Concatenate.
 *  Tracks allocations by using other LYLeakFoo functions.
 *  Equivalent to HTSACat in HTUtils.c - KW
 */
char *LYLeakSACat(char **dest,
		  const char *src,
		  const char *cp_File,
		  const short ssi_Line)
{
    if (src && *src) {
	if (src == *dest) {
	    CTRACE((tfp,
		    "LYLeakSACat:  *dest equals src, contains \"%s\"\n",
		    src));
	    return *dest;
	}
	if (*dest) {
	    int length = strlen(*dest);

	    *dest = (char *) LYLeakRealloc(*dest,
					   (length + strlen(src) + 1),
					   cp_File,
					   ssi_Line);
	    if (*dest == NULL)
		outofmem(__FILE__, "LYLeakSACat");
	    strcpy(*dest + length, src);
	} else {
	    *dest = (char *) LYLeakMalloc((strlen(src) + 1),
					  cp_File,
					  ssi_Line);
	    if (*dest == NULL)
		outofmem(__FILE__, "LYLeakSACat");
	    strcpy(*dest, src);
	}
    }
    return *dest;
}

#if defined(LY_FIND_LEAKS) && defined(LY_FIND_LEAKS_EXTENDED)

const char *leak_cp_File_hack = __FILE__;
short leak_ssi_Line_hack = __LINE__;

/*
 * Purpose:	A wrapper around StrAllocVsprintf (the workhorse of
 *		HTSprintf/HTSprintf0, implemented in HTString.c) that
 *		tries to make sure that our allocation list is always
 *		properly updated, whether StrAllocVsprintf itself was
 *		compiled with memory tracking or not (or even a mixture,
 *		like tracking the freeing but not the new allocation).
 *		Some source files can be compiled with LY_FIND_LEAKS_EXTENDED
 *		in effect while others only have LY_FIND_LEAKS in effect,
 *		and as long as HTString.c is complied with memory tracking
 *		(of either kind) string objects allocated by HTSprintf/
 *		HTSprintf0 (or otherwise) can be passed around among them and
 *		manipulated both ways.
 *  Arguments:	dest		As for StrAllocVsprintf.
 *		cp_File		The source file of the caller (i.e. the
 *				caller of HTSprintf/HTSprintf0, hopefully).
 *		ssi_Line	The line of cp_File calling.
 *		inuse,fmt,ap	As for StrAllocVsprintf.
 *  Return Value:	The char pointer to resulting string, as set
 *			by StrAllocVsprintf, or
 *			NULL if dest==0 (wrong use!).
 *  Remarks/Portability/Dependencies/Restrictions:
 *		The price for generality is severe inefficiency: several
 *		list lookups are done to be on the safe side.
 *		We don't get he real allocation size, only a minimum based
 *		on the string length of the result.  So the amount of memory
 *		leakage may get underestimated.
 *		If *dest is an invalid pointer value on entry (i.e. was not
 *		tracked), the program will exit after one last entry is added
 *		to the allocation list.
 *		If StrAllocVsprintf fails to return a valid string via the
 *		indirect string pointer (its first parameter), invalid memory
 *		access will result and the program will probably terminate
 *		with a signal.  This can happen if, on entry, *dest is NULL
 *		and fmt is empty or NULL, so just Don't Do That.
 *  Revision History:
 *	1999-02-11	created kw
 *	1999-10-15	added comments kw
 */
static char *LYLeakSAVsprintf(char **dest,
			      const char *cp_File,
			      const short ssi_Line,
			      size_t inuse,
			      const char *fmt,
			      va_list * ap)
{
    AllocationList *ALp_old;
    void *vp_oldAlloced;

    const char *old_cp_File = __FILE__;
    short old_ssi_Line = __LINE__;

    if (!dest)
	return NULL;

    if (LYfind_leaks == FALSE) {
	StrAllocVsprintf(dest, inuse, fmt, ap);
	return (*dest);
    }

    vp_oldAlloced = *dest;
    if (!vp_oldAlloced) {
	StrAllocVsprintf(dest, inuse, fmt, ap);
	LYLeak_mark_malloced(*dest, strlen(*dest) + 1, cp_File, ssi_Line);
	return (*dest);
    } else {
	void *vp_realloced;

	ALp_old = FindInList(vp_oldAlloced);
	if (ALp_old == NULL) {
	    /*
	     * Track the invalid pointer value and then exit.  If unable to
	     * allocate, just exit.
	     */
	    auto AllocationList *ALp_new = typecalloc(AllocationList);

	    if (ALp_new == NULL) {
		exit_immediately(EXIT_FAILURE);
	    }

	    /*
	     * Set the information up; no need to allocate file name since it
	     * is a static string.
	     */
	    ALp_new->vp_Alloced = NULL;
	    ALp_new->vp_BadRequest = vp_oldAlloced;
	    ALp_new->SL_realloc.cp_FileName = cp_File;
	    ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;

	    /*
	     * Add the item to the list.  Exit.
	     */
	    AddToList(ALp_new);
	    exit_immediately(EXIT_FAILURE);
	}

	old_cp_File = ALp_old->SL_memory.cp_FileName;
	old_ssi_Line = ALp_old->SL_memory.ssi_LineNumber;
	/*
	 * DO THE REAL WORK, by calling StrAllocVsprintf.  If result is not
	 * NULL, record the information.
	 */
	StrAllocVsprintf(dest, inuse, fmt, ap);
	vp_realloced = (void *) *dest;
	if (vp_realloced != NULL) {
	    AllocationList *ALp_new = FindInList(vp_realloced);

	    if (!ALp_new) {
		/* Look up again, list may have changed! - kw */
		ALp_old = FindInList(vp_oldAlloced);
		if (ALp_old == NULL) {
		    LYLeak_mark_malloced(*dest, strlen(*dest) + 1, cp_File, ssi_Line);
		    return (*dest);
		}
		mark_realloced(ALp_old, *dest, strlen(*dest) + 1, cp_File, ssi_Line);
		return (*dest);
	    }
	    if (vp_realloced == vp_oldAlloced) {
		ALp_new->SL_memory.cp_FileName = old_cp_File;
		ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
		ALp_new->SL_realloc.cp_FileName = cp_File;
		ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
		return (*dest);
	    }
	    /* Look up again, list may have changed! - kw */
	    ALp_old = FindInList(vp_oldAlloced);
	    if (ALp_old == NULL) {
		ALp_new->SL_memory.cp_FileName = old_cp_File;
		ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
		ALp_new->SL_realloc.cp_FileName = cp_File;
		ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
	    } else {
		ALp_new->SL_memory.cp_FileName = old_cp_File;
		ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
		ALp_new->SL_realloc.cp_FileName = cp_File;
		ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
	    }
	}
	return (*dest);
    }
}

/* Note: the following may need updating if HTSprintf in HTString.c
 * is changed. - kw */
#ifdef ANSI_VARARGS
static char *LYLeakHTSprintf(char **pstr, const char *fmt,...)
#else
static char *LYLeakHTSprintf(va_alist)
    va_dcl
#endif
{
    char *str;
    size_t inuse = 0;
    va_list ap;

    LYva_start(ap, fmt);
    {
#ifndef ANSI_VARARGS
	char **pstr = va_arg(ap, char **);
	const char *fmt = va_arg(ap, const char *);
#endif
	if (pstr != 0 && *pstr != 0)
	    inuse = strlen(*pstr);
	str = LYLeakSAVsprintf(pstr, leak_cp_File_hack, leak_ssi_Line_hack,
			       inuse, fmt, &ap);
    }
    va_end(ap);
    return str;
}

/* Note: the following may need updating if HTSprintf0 in HTString.c
 * is changed. - kw */
#ifdef ANSI_VARARGS
static char *LYLeakHTSprintf0(char **pstr, const char *fmt,...)
#else
static char *LYLeakHTSprintf0(va_alist)
    va_dcl
#endif
{
    char *str;
    va_list ap;

    LYva_start(ap, fmt);
    {
#ifndef ANSI_VARARGS
	char **pstr = va_arg(ap, char **);
	const char *fmt = va_arg(ap, const char *);
#endif
	str = LYLeakSAVsprintf(pstr, leak_cp_File_hack, leak_ssi_Line_hack,
			       0, fmt, &ap);
    }
    va_end(ap);
    return str;
}

/*
 * HTSprintf and HTSprintf0 will be defined such that they effectively call one
 * of the following two functions that store away a copy to the File & Line
 * info in temporary hack variables, and then call the real function (which is
 * returned here as a function pointer) to the regular HTSprintf/HTSprintf0
 * arguments.  It's probably a bit inefficient, but that shouldn't be
 * noticeable compared to all the time that memory tracking takes up for list
 * traversal.  - kw
 */
HTSprintflike *Get_htsprintf_fn(const char *cp_File,
				const short ssi_Line)
{
    leak_cp_File_hack = cp_File;
    leak_ssi_Line_hack = ssi_Line;
    return &LYLeakHTSprintf;
}

HTSprintflike *Get_htsprintf0_fn(const char *cp_File,
				 const short ssi_Line)
{
    leak_cp_File_hack = cp_File;
    leak_ssi_Line_hack = ssi_Line;
    return &LYLeakHTSprintf0;
}

#endif /* LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED */
#else
/* Standard C forbids an empty file */
void no_leak_checking(void);
void no_leak_checking(void)
{
}
#endif /* LY_FIND_LEAKS */