1/* 2 * tclPreserve.c -- 3 * 4 * This file contains a collection of functions that are used to make 5 * sure that widget records and other data structures aren't reallocated 6 * when there are nested functions that depend on their existence. 7 * 8 * Copyright (c) 1991-1994 The Regents of the University of California. 9 * Copyright (c) 1994-1998 Sun Microsystems, Inc. 10 * 11 * See the file "license.terms" for information on usage and redistribution of 12 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id: tclPreserve.c,v 1.10 2007/03/21 18:02:51 dgp Exp $ 15 */ 16 17#include "tclInt.h" 18 19/* 20 * The following data structure is used to keep track of all the Tcl_Preserve 21 * calls that are still in effect. It grows as needed to accommodate any 22 * number of calls in effect. 23 */ 24 25typedef struct { 26 ClientData clientData; /* Address of preserved block. */ 27 int refCount; /* Number of Tcl_Preserve calls in effect for 28 * block. */ 29 int mustFree; /* Non-zero means Tcl_EventuallyFree was 30 * called while a Tcl_Preserve call was in 31 * effect, so the structure must be freed when 32 * refCount becomes zero. */ 33 Tcl_FreeProc *freeProc; /* Function to call to free. */ 34} Reference; 35 36/* 37 * Global data structures used to hold the list of preserved data references. 38 * These variables are protected by "preserveMutex". 39 */ 40 41static Reference *refArray = NULL; /* First in array of references. */ 42static int spaceAvl = 0; /* Total number of structures available at 43 * *firstRefPtr. */ 44static int inUse = 0; /* Count of structures currently in use in 45 * refArray. */ 46TCL_DECLARE_MUTEX(preserveMutex)/* To protect the above statics */ 47 48#define INITIAL_SIZE 2 /* Initial number of reference slots to make */ 49 50/* 51 * The following data structure is used to keep track of whether an arbitrary 52 * block of memory has been deleted. This is used by the TclHandle code to 53 * avoid the more time-expensive algorithm of Tcl_Preserve(). This mechanism 54 * is mainly used when we have lots of references to a few big, expensive 55 * objects that we don't want to live any longer than necessary. 56 */ 57 58typedef struct HandleStruct { 59 void *ptr; /* Pointer to the memory block being tracked. 60 * This field will become NULL when the memory 61 * block is deleted. This field must be the 62 * first in the structure. */ 63#ifdef TCL_MEM_DEBUG 64 void *ptr2; /* Backup copy of the above pointer used to 65 * ensure that the contents of the handle are 66 * not changed by anyone else. */ 67#endif 68 int refCount; /* Number of TclHandlePreserve() calls in 69 * effect on this handle. */ 70} HandleStruct; 71 72/* 73 *---------------------------------------------------------------------- 74 * 75 * TclFinalizePreserve -- 76 * 77 * Called during exit processing to clean up the reference array. 78 * 79 * Results: 80 * None. 81 * 82 * Side effects: 83 * Frees the storage of the reference array. 84 * 85 *---------------------------------------------------------------------- 86 */ 87 88 /* ARGSUSED */ 89void 90TclFinalizePreserve(void) 91{ 92 Tcl_MutexLock(&preserveMutex); 93 if (spaceAvl != 0) { 94 ckfree((char *) refArray); 95 refArray = NULL; 96 inUse = 0; 97 spaceAvl = 0; 98 } 99 Tcl_MutexUnlock(&preserveMutex); 100} 101 102/* 103 *---------------------------------------------------------------------- 104 * 105 * Tcl_Preserve -- 106 * 107 * This function is used by a function to declare its interest in a 108 * particular block of memory, so that the block will not be reallocated 109 * until a matching call to Tcl_Release has been made. 110 * 111 * Results: 112 * None. 113 * 114 * Side effects: 115 * Information is retained so that the block of memory will not be freed 116 * until at least the matching call to Tcl_Release. 117 * 118 *---------------------------------------------------------------------- 119 */ 120 121void 122Tcl_Preserve( 123 ClientData clientData) /* Pointer to malloc'ed block of memory. */ 124{ 125 Reference *refPtr; 126 int i; 127 128 /* 129 * See if there is already a reference for this pointer. If so, just 130 * increment its reference count. 131 */ 132 133 Tcl_MutexLock(&preserveMutex); 134 for (i=0, refPtr=refArray ; i<inUse ; i++, refPtr++) { 135 if (refPtr->clientData == clientData) { 136 refPtr->refCount++; 137 Tcl_MutexUnlock(&preserveMutex); 138 return; 139 } 140 } 141 142 /* 143 * Make a reference array if it doesn't already exist, or make it bigger 144 * if it is full. 145 */ 146 147 if (inUse == spaceAvl) { 148 spaceAvl = spaceAvl ? 2*spaceAvl : INITIAL_SIZE; 149 refArray = (Reference *) ckrealloc((char *) refArray, 150 spaceAvl * sizeof(Reference)); 151 } 152 153 /* 154 * Make a new entry for the new reference. 155 */ 156 157 refPtr = &refArray[inUse]; 158 refPtr->clientData = clientData; 159 refPtr->refCount = 1; 160 refPtr->mustFree = 0; 161 refPtr->freeProc = TCL_STATIC; 162 inUse += 1; 163 Tcl_MutexUnlock(&preserveMutex); 164} 165 166/* 167 *---------------------------------------------------------------------- 168 * 169 * Tcl_Release -- 170 * 171 * This function is called to cancel a previous call to Tcl_Preserve, 172 * thereby allowing a block of memory to be freed (if no one else cares 173 * about it). 174 * 175 * Results: 176 * None. 177 * 178 * Side effects: 179 * If Tcl_EventuallyFree has been called for clientData, and if no other 180 * call to Tcl_Preserve is still in effect, the block of memory is freed. 181 * 182 *---------------------------------------------------------------------- 183 */ 184 185void 186Tcl_Release( 187 ClientData clientData) /* Pointer to malloc'ed block of memory. */ 188{ 189 Reference *refPtr; 190 int i; 191 192 Tcl_MutexLock(&preserveMutex); 193 for (i=0, refPtr=refArray ; i<inUse ; i++, refPtr++) { 194 int mustFree; 195 Tcl_FreeProc *freeProc; 196 197 if (refPtr->clientData != clientData) { 198 continue; 199 } 200 201 if (--refPtr->refCount != 0) { 202 Tcl_MutexUnlock(&preserveMutex); 203 return; 204 } 205 206 /* 207 * Must remove information from the slot before calling freeProc to 208 * avoid reentrancy problems if the freeProc calls Tcl_Preserve on the 209 * same clientData. Copy down the last reference in the array to 210 * overwrite the current slot. 211 */ 212 213 freeProc = refPtr->freeProc; 214 mustFree = refPtr->mustFree; 215 inUse--; 216 if (i < inUse) { 217 refArray[i] = refArray[inUse]; 218 } 219 220 /* 221 * Now committed to disposing the data. But first, we've patched up 222 * all the global data structures so we should release the mutex now. 223 * Only then should we dabble around with potentially-slow memory 224 * managers... 225 */ 226 227 Tcl_MutexUnlock(&preserveMutex); 228 if (mustFree) { 229 if (freeProc == TCL_DYNAMIC) { 230 ckfree((char *) clientData); 231 } else { 232 (*freeProc)((char *) clientData); 233 } 234 } 235 return; 236 } 237 Tcl_MutexUnlock(&preserveMutex); 238 239 /* 240 * Reference not found. This is a bug in the caller. 241 */ 242 243 Tcl_Panic("Tcl_Release couldn't find reference for 0x%x", clientData); 244} 245 246/* 247 *---------------------------------------------------------------------- 248 * 249 * Tcl_EventuallyFree -- 250 * 251 * Free up a block of memory, unless a call to Tcl_Preserve is in effect 252 * for that block. In this case, defer the free until all calls to 253 * Tcl_Preserve have been undone by matching calls to Tcl_Release. 254 * 255 * Results: 256 * None. 257 * 258 * Side effects: 259 * Ptr may be released by calling free(). 260 * 261 *---------------------------------------------------------------------- 262 */ 263 264void 265Tcl_EventuallyFree( 266 ClientData clientData, /* Pointer to malloc'ed block of memory. */ 267 Tcl_FreeProc *freeProc) /* Function to actually do free. */ 268{ 269 Reference *refPtr; 270 int i; 271 272 /* 273 * See if there is a reference for this pointer. If so, set its "mustFree" 274 * flag (the flag had better not be set already!). 275 */ 276 277 Tcl_MutexLock(&preserveMutex); 278 for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) { 279 if (refPtr->clientData != clientData) { 280 continue; 281 } 282 if (refPtr->mustFree) { 283 Tcl_Panic("Tcl_EventuallyFree called twice for 0x%x", 284 clientData); 285 } 286 refPtr->mustFree = 1; 287 refPtr->freeProc = freeProc; 288 Tcl_MutexUnlock(&preserveMutex); 289 return; 290 } 291 Tcl_MutexUnlock(&preserveMutex); 292 293 /* 294 * No reference for this block. Free it now. 295 */ 296 297 if (freeProc == TCL_DYNAMIC) { 298 ckfree((char *) clientData); 299 } else { 300 (*freeProc)((char *)clientData); 301 } 302} 303 304/* 305 *--------------------------------------------------------------------------- 306 * 307 * TclHandleCreate -- 308 * 309 * Allocate a handle that contains enough information to determine if an 310 * arbitrary malloc'd block has been deleted. This is used to avoid the 311 * more time-expensive algorithm of Tcl_Preserve(). 312 * 313 * Results: 314 * The return value is a TclHandle that refers to the given malloc'd 315 * block. Doubly dereferencing the returned handle will give back the 316 * pointer to the block, or will give NULL if the block has been deleted. 317 * 318 * Side effects: 319 * The caller must keep track of this handle (generally by storing it in 320 * a field in the malloc'd block) and call TclHandleFree() on this handle 321 * when the block is deleted. Everything else that wishes to keep track 322 * of whether the malloc'd block has been deleted should use calls to 323 * TclHandlePreserve() and TclHandleRelease() on the associated handle. 324 * 325 *--------------------------------------------------------------------------- 326 */ 327 328TclHandle 329TclHandleCreate( 330 void *ptr) /* Pointer to an arbitrary block of memory to 331 * be tracked for deletion. Must not be 332 * NULL. */ 333{ 334 HandleStruct *handlePtr; 335 336 handlePtr = (HandleStruct *) ckalloc(sizeof(HandleStruct)); 337 handlePtr->ptr = ptr; 338#ifdef TCL_MEM_DEBUG 339 handlePtr->ptr2 = ptr; 340#endif 341 handlePtr->refCount = 0; 342 return (TclHandle) handlePtr; 343} 344 345/* 346 *--------------------------------------------------------------------------- 347 * 348 * TclHandleFree -- 349 * 350 * Called when the arbitrary malloc'd block associated with the handle is 351 * being deleted. Modifies the handle so that doubly dereferencing it 352 * will give NULL. This informs any user of the handle that the block of 353 * memory formerly referenced by the handle has been freed. 354 * 355 * Results: 356 * None. 357 * 358 * Side effects: 359 * If nothing is referring to the handle, the handle will be reclaimed. 360 * 361 *--------------------------------------------------------------------------- 362 */ 363 364void 365TclHandleFree( 366 TclHandle handle) /* Previously created handle associated with a 367 * malloc'd block that is being deleted. The 368 * handle is modified so that doubly 369 * dereferencing it will give NULL. */ 370{ 371 HandleStruct *handlePtr; 372 373 handlePtr = (HandleStruct *) handle; 374#ifdef TCL_MEM_DEBUG 375 if (handlePtr->refCount == 0x61616161) { 376 Tcl_Panic("using previously disposed TclHandle %x", handlePtr); 377 } 378 if (handlePtr->ptr2 != handlePtr->ptr) { 379 Tcl_Panic("someone has changed the block referenced by the handle %x\nfrom %x to %x", 380 handlePtr, handlePtr->ptr2, handlePtr->ptr); 381 } 382#endif 383 handlePtr->ptr = NULL; 384 if (handlePtr->refCount == 0) { 385 ckfree((char *) handlePtr); 386 } 387} 388 389/* 390 *--------------------------------------------------------------------------- 391 * 392 * TclHandlePreserve -- 393 * 394 * Declare an interest in the arbitrary malloc'd block associated with 395 * the handle. 396 * 397 * Results: 398 * The return value is the handle argument, with its ref count 399 * incremented. 400 * 401 * Side effects: 402 * For each call to TclHandlePreserve(), there should be a matching call 403 * to TclHandleRelease() when the caller is no longer interested in the 404 * malloc'd block associated with the handle. 405 * 406 *--------------------------------------------------------------------------- 407 */ 408 409TclHandle 410TclHandlePreserve( 411 TclHandle handle) /* Declare an interest in the block of memory 412 * referenced by this handle. */ 413{ 414 HandleStruct *handlePtr; 415 416 handlePtr = (HandleStruct *) handle; 417#ifdef TCL_MEM_DEBUG 418 if (handlePtr->refCount == 0x61616161) { 419 Tcl_Panic("using previously disposed TclHandle %x", handlePtr); 420 } 421 if ((handlePtr->ptr != NULL) && (handlePtr->ptr != handlePtr->ptr2)) { 422 Tcl_Panic("someone has changed the block referenced by the handle %x\nfrom %x to %x", 423 handlePtr, handlePtr->ptr2, handlePtr->ptr); 424 } 425#endif 426 handlePtr->refCount++; 427 428 return handle; 429} 430 431/* 432 *--------------------------------------------------------------------------- 433 * 434 * TclHandleRelease -- 435 * 436 * This function is called to release an interest in the malloc'd block 437 * associated with the handle. 438 * 439 * Results: 440 * None. 441 * 442 * Side effects: 443 * The ref count of the handle is decremented. If the malloc'd block has 444 * been freed and if no one is using the handle any more, the handle will 445 * be reclaimed. 446 * 447 *--------------------------------------------------------------------------- 448 */ 449 450void 451TclHandleRelease( 452 TclHandle handle) /* Unregister interest in the block of memory 453 * referenced by this handle. */ 454{ 455 HandleStruct *handlePtr; 456 457 handlePtr = (HandleStruct *) handle; 458#ifdef TCL_MEM_DEBUG 459 if (handlePtr->refCount == 0x61616161) { 460 Tcl_Panic("using previously disposed TclHandle %x", handlePtr); 461 } 462 if ((handlePtr->ptr != NULL) && (handlePtr->ptr != handlePtr->ptr2)) { 463 Tcl_Panic("someone has changed the block referenced by the handle %x\nfrom %x to %x", 464 handlePtr, handlePtr->ptr2, handlePtr->ptr); 465 } 466#endif 467 handlePtr->refCount--; 468 if ((handlePtr->refCount == 0) && (handlePtr->ptr == NULL)) { 469 ckfree((char *) handlePtr); 470 } 471} 472 473/* 474 * Local Variables: 475 * mode: c 476 * c-basic-offset: 4 477 * fill-column: 78 478 * End: 479 */ 480