sl@0: /* sl@0: * tclPreserve.c -- sl@0: * sl@0: * This file contains a collection of procedures that are used sl@0: * to make sure that widget records and other data structures sl@0: * aren't reallocated when there are nested procedures that sl@0: * depend on their existence. sl@0: * sl@0: * Copyright (c) 1991-1994 The Regents of the University of California. sl@0: * Copyright (c) 1994-1998 Sun Microsystems, Inc. sl@0: * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclPreserve.c,v 1.3.34.2 2005/06/24 18:21:41 kennykb Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: sl@0: /* sl@0: * The following data structure is used to keep track of all the sl@0: * Tcl_Preserve calls that are still in effect. It grows as needed sl@0: * to accommodate any number of calls in effect. sl@0: */ sl@0: sl@0: typedef struct { sl@0: ClientData clientData; /* Address of preserved block. */ sl@0: int refCount; /* Number of Tcl_Preserve calls in effect sl@0: * for block. */ sl@0: int mustFree; /* Non-zero means Tcl_EventuallyFree was sl@0: * called while a Tcl_Preserve call was in sl@0: * effect, so the structure must be freed sl@0: * when refCount becomes zero. */ sl@0: Tcl_FreeProc *freeProc; /* Procedure to call to free. */ sl@0: } Reference; sl@0: sl@0: #if !defined(__SYMBIAN32__) || !defined(__WINSCW__) sl@0: static Reference *refArray; /* First in array of references. */ sl@0: static int spaceAvl = 0; /* Total number of structures available sl@0: * at *firstRefPtr. */ sl@0: static int inUse = 0; /* Count of structures currently in use sl@0: * in refArray. */ sl@0: #else sl@0: #define refArray (*(Reference**)get_refArray()) sl@0: #define spaceAvl (*(int *)get_spaceAvl()) sl@0: #define inUse (*(int *)get_inUse()) sl@0: #endif sl@0: #define INITIAL_SIZE 2 sl@0: TCL_DECLARE_MUTEX(preserveMutex)/* To protect the above statics */ sl@0: sl@0: /* sl@0: * The following data structure is used to keep track of whether an sl@0: * arbitrary block of memory has been deleted. This is used by the sl@0: * TclHandle code to avoid the more time-expensive algorithm of sl@0: * Tcl_Preserve(). This mechanism is mainly used when we have lots of sl@0: * references to a few big, expensive objects that we don't want to live sl@0: * any longer than necessary. sl@0: */ sl@0: sl@0: typedef struct HandleStruct { sl@0: VOID *ptr; /* Pointer to the memory block being sl@0: * tracked. This field will become NULL when sl@0: * the memory block is deleted. This field sl@0: * must be the first in the structure. */ sl@0: #ifdef TCL_MEM_DEBUG sl@0: VOID *ptr2; /* Backup copy of the abpve pointer used to sl@0: * ensure that the contents of the handle are sl@0: * not changed by anyone else. */ sl@0: #endif sl@0: int refCount; /* Number of TclHandlePreserve() calls in sl@0: * effect on this handle. */ sl@0: } HandleStruct; sl@0: sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclFinalizePreserve -- sl@0: * sl@0: * Called during exit processing to clean up the reference array. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Frees the storage of the reference array. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: /* ARGSUSED */ sl@0: void sl@0: TclFinalizePreserve() sl@0: { sl@0: Tcl_MutexLock(&preserveMutex); sl@0: if (spaceAvl != 0) { sl@0: ckfree((char *) refArray); sl@0: refArray = (Reference *) NULL; sl@0: inUse = 0; sl@0: spaceAvl = 0; sl@0: } sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_Preserve -- sl@0: * sl@0: * This procedure is used by a procedure to declare its interest sl@0: * in a particular block of memory, so that the block will not be sl@0: * reallocated until a matching call to Tcl_Release has been made. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Information is retained so that the block of memory will sl@0: * not be freed until at least the matching call to Tcl_Release. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_Preserve(clientData) sl@0: ClientData clientData; /* Pointer to malloc'ed block of memory. */ sl@0: { sl@0: Reference *refPtr; sl@0: int i; sl@0: sl@0: /* sl@0: * See if there is already a reference for this pointer. If so, sl@0: * just increment its reference count. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&preserveMutex); sl@0: for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) { sl@0: if (refPtr->clientData == clientData) { sl@0: refPtr->refCount++; sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: return; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Make a reference array if it doesn't already exist, or make it sl@0: * bigger if it is full. sl@0: */ sl@0: sl@0: if (inUse == spaceAvl) { sl@0: if (spaceAvl == 0) { sl@0: refArray = (Reference *) ckalloc((unsigned) sl@0: (INITIAL_SIZE*sizeof(Reference))); sl@0: spaceAvl = INITIAL_SIZE; sl@0: } else { sl@0: Reference *new; sl@0: sl@0: new = (Reference *) ckalloc((unsigned) sl@0: (2*spaceAvl*sizeof(Reference))); sl@0: memcpy((VOID *) new, (VOID *) refArray, sl@0: spaceAvl*sizeof(Reference)); sl@0: ckfree((char *) refArray); sl@0: refArray = new; sl@0: spaceAvl *= 2; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Make a new entry for the new reference. sl@0: */ sl@0: sl@0: refPtr = &refArray[inUse]; sl@0: refPtr->clientData = clientData; sl@0: refPtr->refCount = 1; sl@0: refPtr->mustFree = 0; sl@0: refPtr->freeProc = TCL_STATIC; sl@0: inUse += 1; sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_Release -- sl@0: * sl@0: * This procedure is called to cancel a previous call to sl@0: * Tcl_Preserve, thereby allowing a block of memory to be sl@0: * freed (if no one else cares about it). sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * If Tcl_EventuallyFree has been called for clientData, and if sl@0: * no other call to Tcl_Preserve is still in effect, the block of sl@0: * memory is freed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_Release(clientData) sl@0: ClientData clientData; /* Pointer to malloc'ed block of memory. */ sl@0: { sl@0: Reference *refPtr; sl@0: int mustFree; sl@0: Tcl_FreeProc *freeProc; sl@0: int i; sl@0: sl@0: Tcl_MutexLock(&preserveMutex); sl@0: for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) { sl@0: if (refPtr->clientData != clientData) { sl@0: continue; sl@0: } sl@0: refPtr->refCount--; sl@0: if (refPtr->refCount == 0) { sl@0: sl@0: /* sl@0: * Must remove information from the slot before calling freeProc sl@0: * to avoid reentrancy problems if the freeProc calls Tcl_Preserve sl@0: * on the same clientData. Copy down the last reference in the sl@0: * array to overwrite the current slot. sl@0: */ sl@0: sl@0: freeProc = refPtr->freeProc; sl@0: mustFree = refPtr->mustFree; sl@0: inUse--; sl@0: if (i < inUse) { sl@0: refArray[i] = refArray[inUse]; sl@0: } sl@0: if (mustFree) { sl@0: if (freeProc == TCL_DYNAMIC) { sl@0: ckfree((char *) clientData); sl@0: } else { sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: (*freeProc)((char *) clientData); sl@0: return; sl@0: } sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: return; sl@0: } sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: sl@0: /* sl@0: * Reference not found. This is a bug in the caller. sl@0: */ sl@0: sl@0: panic("Tcl_Release couldn't find reference for 0x%x", clientData); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_EventuallyFree -- sl@0: * sl@0: * Free up a block of memory, unless a call to Tcl_Preserve is in sl@0: * effect for that block. In this case, defer the free until all sl@0: * calls to Tcl_Preserve have been undone by matching calls to sl@0: * Tcl_Release. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Ptr may be released by calling free(). sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_EventuallyFree(clientData, freeProc) sl@0: ClientData clientData; /* Pointer to malloc'ed block of memory. */ sl@0: Tcl_FreeProc *freeProc; /* Procedure to actually do free. */ sl@0: { sl@0: Reference *refPtr; sl@0: int i; sl@0: sl@0: /* sl@0: * See if there is a reference for this pointer. If so, set its sl@0: * "mustFree" flag (the flag had better not be set already!). sl@0: */ sl@0: sl@0: Tcl_MutexLock(&preserveMutex); sl@0: for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) { sl@0: if (refPtr->clientData != clientData) { sl@0: continue; sl@0: } sl@0: if (refPtr->mustFree) { sl@0: panic("Tcl_EventuallyFree called twice for 0x%x\n", clientData); sl@0: } sl@0: refPtr->mustFree = 1; sl@0: refPtr->freeProc = freeProc; sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: return; sl@0: } sl@0: Tcl_MutexUnlock(&preserveMutex); sl@0: sl@0: /* sl@0: * No reference for this block. Free it now. sl@0: */ sl@0: sl@0: if (freeProc == TCL_DYNAMIC) { sl@0: ckfree((char *) clientData); sl@0: } else { sl@0: (*freeProc)((char *)clientData); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *--------------------------------------------------------------------------- sl@0: * sl@0: * TclHandleCreate -- sl@0: * sl@0: * Allocate a handle that contains enough information to determine sl@0: * if an arbitrary malloc'd block has been deleted. This is sl@0: * used to avoid the more time-expensive algorithm of Tcl_Preserve(). sl@0: * sl@0: * Results: sl@0: * The return value is a TclHandle that refers to the given malloc'd sl@0: * block. Doubly dereferencing the returned handle will give sl@0: * back the pointer to the block, or will give NULL if the block has sl@0: * been deleted. sl@0: * sl@0: * Side effects: sl@0: * The caller must keep track of this handle (generally by storing sl@0: * it in a field in the malloc'd block) and call TclHandleFree() sl@0: * on this handle when the block is deleted. Everything else that sl@0: * wishes to keep track of whether the malloc'd block has been deleted sl@0: * should use calls to TclHandlePreserve() and TclHandleRelease() sl@0: * on the associated handle. sl@0: * sl@0: *--------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: TclHandle sl@0: TclHandleCreate(ptr) sl@0: VOID *ptr; /* Pointer to an arbitrary block of memory sl@0: * to be tracked for deletion. Must not be sl@0: * NULL. */ sl@0: { sl@0: HandleStruct *handlePtr; sl@0: sl@0: handlePtr = (HandleStruct *) ckalloc(sizeof(HandleStruct)); sl@0: handlePtr->ptr = ptr; sl@0: #ifdef TCL_MEM_DEBUG sl@0: handlePtr->ptr2 = ptr; sl@0: #endif sl@0: handlePtr->refCount = 0; sl@0: return (TclHandle) handlePtr; sl@0: } sl@0: sl@0: /* sl@0: *--------------------------------------------------------------------------- sl@0: * sl@0: * TclHandleFree -- sl@0: * sl@0: * Called when the arbitrary malloc'd block associated with the sl@0: * handle is being deleted. Modifies the handle so that doubly sl@0: * dereferencing it will give NULL. This informs any user of the sl@0: * handle that the block of memory formerly referenced by the sl@0: * handle has been freed. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * If nothing is referring to the handle, the handle will be reclaimed. sl@0: * sl@0: *--------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclHandleFree(handle) sl@0: TclHandle handle; /* Previously created handle associated sl@0: * with a malloc'd block that is being sl@0: * deleted. The handle is modified so that sl@0: * doubly dereferencing it will give NULL. */ sl@0: { sl@0: HandleStruct *handlePtr; sl@0: sl@0: handlePtr = (HandleStruct *) handle; sl@0: #ifdef TCL_MEM_DEBUG sl@0: if (handlePtr->refCount == 0x61616161) { sl@0: panic("using previously disposed TclHandle %x", handlePtr); sl@0: } sl@0: if (handlePtr->ptr2 != handlePtr->ptr) { sl@0: panic("someone has changed the block referenced by the handle %x\nfrom %x to %x", sl@0: handlePtr, handlePtr->ptr2, handlePtr->ptr); sl@0: } sl@0: #endif sl@0: handlePtr->ptr = NULL; sl@0: if (handlePtr->refCount == 0) { sl@0: ckfree((char *) handlePtr); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *--------------------------------------------------------------------------- sl@0: * sl@0: * TclHandlePreserve -- sl@0: * sl@0: * Declare an interest in the arbitrary malloc'd block associated sl@0: * with the handle. sl@0: * sl@0: * Results: sl@0: * The return value is the handle argument, with its ref count sl@0: * incremented. sl@0: * sl@0: * Side effects: sl@0: * For each call to TclHandlePreserve(), there should be a matching sl@0: * call to TclHandleRelease() when the caller is no longer interested sl@0: * in the malloc'd block associated with the handle. sl@0: * sl@0: *--------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: TclHandle sl@0: TclHandlePreserve(handle) sl@0: TclHandle handle; /* Declare an interest in the block of sl@0: * memory referenced by this handle. */ sl@0: { sl@0: HandleStruct *handlePtr; sl@0: sl@0: handlePtr = (HandleStruct *) handle; sl@0: #ifdef TCL_MEM_DEBUG sl@0: if (handlePtr->refCount == 0x61616161) { sl@0: panic("using previously disposed TclHandle %x", handlePtr); sl@0: } sl@0: if ((handlePtr->ptr != NULL) sl@0: && (handlePtr->ptr != handlePtr->ptr2)) { sl@0: panic("someone has changed the block referenced by the handle %x\nfrom %x to %x", sl@0: handlePtr, handlePtr->ptr2, handlePtr->ptr); sl@0: } sl@0: #endif sl@0: handlePtr->refCount++; sl@0: sl@0: return handle; sl@0: } sl@0: sl@0: /* sl@0: *--------------------------------------------------------------------------- sl@0: * sl@0: * TclHandleRelease -- sl@0: * sl@0: * This procedure is called to release an interest in the malloc'd sl@0: * block associated with the handle. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The ref count of the handle is decremented. If the malloc'd block sl@0: * has been freed and if no one is using the handle any more, the sl@0: * handle will be reclaimed. sl@0: * sl@0: *--------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclHandleRelease(handle) sl@0: TclHandle handle; /* Unregister interest in the block of sl@0: * memory referenced by this handle. */ sl@0: { sl@0: HandleStruct *handlePtr; sl@0: sl@0: handlePtr = (HandleStruct *) handle; sl@0: #ifdef TCL_MEM_DEBUG sl@0: if (handlePtr->refCount == 0x61616161) { sl@0: panic("using previously disposed TclHandle %x", handlePtr); sl@0: } sl@0: if ((handlePtr->ptr != NULL) sl@0: && (handlePtr->ptr != handlePtr->ptr2)) { sl@0: panic("someone has changed the block referenced by the handle %x\nfrom %x to %x", sl@0: handlePtr, handlePtr->ptr2, handlePtr->ptr); sl@0: } sl@0: #endif sl@0: handlePtr->refCount--; sl@0: if ((handlePtr->refCount == 0) && (handlePtr->ptr == NULL)) { sl@0: ckfree((char *) handlePtr); sl@0: } sl@0: } sl@0: