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