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