1 |
dashley |
71 |
/* $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 */ |