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