/*1* tclPreserve.c --2*3* This file contains a collection of procedures that are used4* to make sure that widget records and other data structures5* aren't reallocated when there are nested procedures that6* depend on their existence.7*8* Copyright (c) 1991-1994 The Regents of the University of California.9* Copyright (c) 1994-1995 Sun Microsystems, Inc.10*11* See the file "license.terms" for information on usage and redistribution12* of this file, and for a DISCLAIMER OF ALL WARRANTIES.13*14* SCCS: @(#) tclPreserve.c 1.17 96/07/23 16:15:3415*/1617#include "tclInt.h"1819/*20* The following data structure is used to keep track of all the21* Tcl_Preserve calls that are still in effect. It grows as needed22* to accommodate any number of calls in effect.23*/2425typedef struct {26ClientData clientData; /* Address of preserved block. */27int refCount; /* Number of Tcl_Preserve calls in effect28* for block. */29int mustFree; /* Non-zero means Tcl_EventuallyFree was30* called while a Tcl_Preserve call was in31* effect, so the structure must be freed32* when refCount becomes zero. */33Tcl_FreeProc *freeProc; /* Procedure to call to free. */34} Reference;3536static Reference *refArray; /* First in array of references. */37static int spaceAvl = 0; /* Total number of structures available38* at *firstRefPtr. */39static int inUse = 0; /* Count of structures currently in use40* in refArray. */41#define INITIAL_SIZE 24243/*44* Static routines in this file:45*/4647static void PreserveExitProc _ANSI_ARGS_((ClientData clientData));484950/*51*----------------------------------------------------------------------52*53* PreserveExitProc --54*55* Called during exit processing to clean up the reference array.56*57* Results:58* None.59*60* Side effects:61* Frees the storage of the reference array.62*63*----------------------------------------------------------------------64*/6566/* ARGSUSED */67static void68PreserveExitProc(clientData)69ClientData clientData; /* NULL -Unused. */70{71if (spaceAvl != 0) {72ckfree((char *) refArray);73refArray = (Reference *) NULL;74inUse = 0;75spaceAvl = 0;76}77}7879/*80*----------------------------------------------------------------------81*82* Tcl_Preserve --83*84* This procedure is used by a procedure to declare its interest85* in a particular block of memory, so that the block will not be86* reallocated until a matching call to Tcl_Release has been made.87*88* Results:89* None.90*91* Side effects:92* Information is retained so that the block of memory will93* not be freed until at least the matching call to Tcl_Release.94*95*----------------------------------------------------------------------96*/9798void99Tcl_Preserve(clientData)100ClientData clientData; /* Pointer to malloc'ed block of memory. */101{102Reference *refPtr;103int i;104105/*106* See if there is already a reference for this pointer. If so,107* just increment its reference count.108*/109110for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) {111if (refPtr->clientData == clientData) {112refPtr->refCount++;113return;114}115}116117/*118* Make a reference array if it doesn't already exist, or make it119* bigger if it is full.120*/121122if (inUse == spaceAvl) {123if (spaceAvl == 0) {124Tcl_CreateExitHandler((Tcl_ExitProc *) PreserveExitProc,125(ClientData) NULL);126refArray = (Reference *) ckalloc((unsigned)127(INITIAL_SIZE*sizeof(Reference)));128spaceAvl = INITIAL_SIZE;129} else {130Reference *new;131132new = (Reference *) ckalloc((unsigned)133(2*spaceAvl*sizeof(Reference)));134memcpy((VOID *) new, (VOID *) refArray,135spaceAvl*sizeof(Reference));136ckfree((char *) refArray);137refArray = new;138spaceAvl *= 2;139}140}141142/*143* Make a new entry for the new reference.144*/145146refPtr = &refArray[inUse];147refPtr->clientData = clientData;148refPtr->refCount = 1;149refPtr->mustFree = 0;150refPtr->freeProc = TCL_STATIC;151inUse += 1;152}153154/*155*----------------------------------------------------------------------156*157* Tcl_Release --158*159* This procedure is called to cancel a previous call to160* Tcl_Preserve, thereby allowing a block of memory to be161* freed (if no one else cares about it).162*163* Results:164* None.165*166* Side effects:167* If Tcl_EventuallyFree has been called for clientData, and if168* no other call to Tcl_Preserve is still in effect, the block of169* memory is freed.170*171*----------------------------------------------------------------------172*/173174void175Tcl_Release(clientData)176ClientData clientData; /* Pointer to malloc'ed block of memory. */177{178Reference *refPtr;179int mustFree;180Tcl_FreeProc *freeProc;181int i;182183for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) {184if (refPtr->clientData != clientData) {185continue;186}187refPtr->refCount--;188if (refPtr->refCount == 0) {189190/*191* Must remove information from the slot before calling freeProc192* to avoid reentrancy problems if the freeProc calls Tcl_Preserve193* on the same clientData. Copy down the last reference in the194* array to overwrite the current slot.195*/196197freeProc = refPtr->freeProc;198mustFree = refPtr->mustFree;199inUse--;200if (i < inUse) {201refArray[i] = refArray[inUse];202}203if (mustFree) {204if ((freeProc == TCL_DYNAMIC) ||205(freeProc == (Tcl_FreeProc *) free)) {206ckfree((char *) clientData);207} else {208(*freeProc)((char *) clientData);209}210}211}212return;213}214215/*216* Reference not found. This is a bug in the caller.217*/218219panic("Tcl_Release couldn't find reference for 0x%x", clientData);220}221222/*223*----------------------------------------------------------------------224*225* Tcl_EventuallyFree --226*227* Free up a block of memory, unless a call to Tcl_Preserve is in228* effect for that block. In this case, defer the free until all229* calls to Tcl_Preserve have been undone by matching calls to230* Tcl_Release.231*232* Results:233* None.234*235* Side effects:236* Ptr may be released by calling free().237*238*----------------------------------------------------------------------239*/240241void242Tcl_EventuallyFree(clientData, freeProc)243ClientData clientData; /* Pointer to malloc'ed block of memory. */244Tcl_FreeProc *freeProc; /* Procedure to actually do free. */245{246Reference *refPtr;247int i;248249/*250* See if there is a reference for this pointer. If so, set its251* "mustFree" flag (the flag had better not be set already!).252*/253254for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) {255if (refPtr->clientData != clientData) {256continue;257}258if (refPtr->mustFree) {259panic("Tcl_EventuallyFree called twice for 0x%x\n", clientData);260}261refPtr->mustFree = 1;262refPtr->freeProc = freeProc;263return;264}265266/*267* No reference for this block. Free it now.268*/269270if ((freeProc == TCL_DYNAMIC)271|| (freeProc == (Tcl_FreeProc *) free)) {272ckfree((char *) clientData);273} else {274(*freeProc)((char *)clientData);275}276}277278279