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