removed the class hash and all functions identifying classes by name only
[cacao.git] / src / vm / classcache.c
1 /* vm/classcache.c - loaded class cache and loading constraints
2
3    Copyright (C) 1996-2005 R. Grafl, A. Krall, C. Kruegel, C. Oates,
4    R. Obermaisser, M. Platter, M. Probst, S. Ring, E. Steiner,
5    C. Thalinger, D. Thuernbeck, P. Tomsich, C. Ullrich, J. Wenninger,
6    Institut f. Computersprachen - TU Wien
7
8    This file is part of CACAO.
9
10    This program is free software; you can redistribute it and/or
11    modify it under the terms of the GNU General Public License as
12    published by the Free Software Foundation; either version 2, or (at
13    your option) any later version.
14
15    This program is distributed in the hope that it will be useful, but
16    WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
23    02111-1307, USA.
24
25    Contact: cacao@complang.tuwien.ac.at
26
27    Authors: Edwin Steiner
28
29    Changes:
30
31    $Id: classcache.c 2195 2005-04-03 16:53:16Z edwin $
32
33 */
34
35 #include <assert.h>
36 #include "vm/classcache.h"
37 #include "vm/utf8.h"
38 #include "vm/tables.h"
39 #include "vm/exceptions.h"
40 #include "mm/memory.h"
41
42 /* initial number of slots in the classcache hash table */
43 #define CLASSCACHE_INIT_SIZE  2048
44
45 /*============================================================================*/
46 /* DEBUG HELPERS                                                              */
47 /*============================================================================*/
48
49 /*#define CLASSCACHE_VERBOSE*/
50
51 #ifndef NDEBUG
52 #define CLASSCACHE_DEBUG
53 #endif
54
55 #ifdef CLASSCACHE_DEBUG
56 #define CLASSCACHE_ASSERT(cond)  assert(cond)
57 #else
58 #define CLASSCACHE_ASSERT(cond)
59 #endif
60
61 /*============================================================================*/
62 /* THREAD-SAFE LOCKING                                                        */
63 /*============================================================================*/
64
65     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
66     /* CAUTION: The static functions below are */
67     /*          NOT synchronized!              */
68     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
69
70 #if defined(USE_THREADS) && defined(NATIVE_THREADS)
71 #  define CLASSCACHE_LOCK()    tables_lock()
72 #  define CLASSCACHE_UNLOCK()  tables_unlock()
73 #else
74 #  define CLASSCACHE_LOCK()
75 #  define CLASSCACHE_UNLOCK()
76 #endif
77
78 /*============================================================================*/
79 /* GLOBAL VARIABLES                                                           */
80 /*============================================================================*/
81
82 hashtable classcache_hash;
83
84 /*============================================================================*/
85 /*                                                                            */
86 /*============================================================================*/
87
88 /* classcache_init *************************************************************
89  
90    Initialize the loaded class cache
91
92    Note: NOT synchronized!
93   
94 *******************************************************************************/
95
96 void 
97 classcache_init()
98 {
99         init_hashtable(&classcache_hash,CLASSCACHE_INIT_SIZE);
100 }
101
102 /* classcache_new_loader_entry *************************************************
103  
104    Create a new classcache_loader_entry struct
105    (internally used helper function)
106   
107    IN:
108        loader...........the ClassLoader object
109            next.............the next classcache_loader_entry
110
111    RETURN VALUE:
112        the new classcache_loader_entry
113   
114 *******************************************************************************/
115
116 static classcache_loader_entry *
117 classcache_new_loader_entry(classloader *loader,classcache_loader_entry *next)
118 {
119         classcache_loader_entry *lden;
120
121         lden = NEW(classcache_loader_entry);
122         lden->loader = loader;
123         lden->next = next;
124
125         return lden;
126 }
127
128 /* classcache_merge_loaders ****************************************************
129  
130    Merge two lists of loaders into one
131    (internally used helper function)
132   
133    IN:
134        lista............first list (may be NULL)
135            listb............second list (may be NULL)
136
137    RETURN VALUE:
138        the merged list (may be NULL)
139
140    NOTE:
141        The lists given as arguments are destroyed!
142   
143 *******************************************************************************/
144
145 static classcache_loader_entry *
146 classcache_merge_loaders(classcache_loader_entry *lista,
147                                  classcache_loader_entry *listb)
148 {
149         classcache_loader_entry *result;
150         classcache_loader_entry *ldenA;
151         classcache_loader_entry *ldenB;
152         classcache_loader_entry **chain;
153         
154         /* XXX This is a quadratic algorithm. If this ever
155          * becomes a problem, the loader lists should be
156          * stored as sorted lists and merged in linear time. */
157
158         result = NULL;
159         chain = &result;
160
161         for (ldenA=lista; ldenA; ldenA=ldenA->next) {
162                 
163                 for (ldenB=listb; ldenB; ldenB=ldenB->next) {
164                         if (ldenB->loader == ldenA->loader)
165                                 goto common_element;
166                 }
167
168                 /* this loader is only in lista */
169                 *chain = ldenA;
170                 chain = &(ldenA->next);
171
172 common_element:
173                 /* XXX free the duplicated element */
174                 ;
175         }
176
177         /* concat listb to the result */
178         *chain = listb;
179
180         return result;
181 }
182
183 /* classcache_lookup_name ******************************************************
184  
185    Lookup a name in the first level of the cache
186    (internally used helper function)
187    
188    IN:
189        name.............the name to look up
190   
191    RETURN VALUE:
192        a pointer to the classcache_name_entry for this name, or
193        null if no entry was found.
194            
195 *******************************************************************************/
196  
197 static classcache_name_entry *
198 classcache_lookup_name(utf *name)
199 {
200         classcache_name_entry * c;     /* hash table element */
201         u4 key;           /* hashkey computed from classname */
202         u4 slot;          /* slot in hashtable               */
203         u2 i;
204
205         key  = utf_hashkey(name->text, name->blength);
206         slot = key & (classcache_hash.size - 1);
207         c    = classcache_hash.ptr[slot];
208
209         /* search external hash chain for the entry */
210         while (c) {
211                 if (c->name->blength == name->blength) {
212                         for (i = 0; i < name->blength; i++)
213                                 if (name->text[i] != c->name->text[i]) goto nomatch;
214                                                 
215                         /* entry found in hashtable */
216                         return c;
217                 }
218                         
219         nomatch:
220                 c = c->hashlink; /* next element in external chain */
221         }
222
223         /* not found */
224         return NULL;
225 }
226
227 /* classcache_new_name *********************************************************
228  
229    Return a classcache_name_entry for the given name. The entry is created
230    if it is not already in the cache.
231    (internally used helper function)
232    
233    IN:
234        name.............the name to look up / create an entry for
235   
236    RETURN VALUE:
237        a pointer to the classcache_name_entry for this name
238            
239 *******************************************************************************/
240  
241 static classcache_name_entry *
242 classcache_new_name(utf *name)
243 {
244         classcache_name_entry * c;     /* hash table element */
245         u4 key;           /* hashkey computed from classname */
246         u4 slot;          /* slot in hashtable               */
247         u2 i;
248
249         key  = utf_hashkey(name->text, name->blength);
250         slot = key & (classcache_hash.size - 1);
251         c    = classcache_hash.ptr[slot];
252
253         /* search external hash chain for the entry */
254         while (c) {
255                 if (c->name->blength == name->blength) {
256                         for (i = 0; i < name->blength; i++)
257                                 if (name->text[i] != c->name->text[i]) goto nomatch;
258                                                 
259                         /* entry found in hashtable */
260                         return c;
261                 }
262                         
263         nomatch:
264                 c = c->hashlink; /* next element in external chain */
265         }
266
267         /* location in hashtable found, create new entry */
268
269         c = NEW(classcache_name_entry);
270
271         c->name = name;
272         c->classes = NULL;
273
274         /* insert entry into hashtable */
275         c->hashlink = classcache_hash.ptr[slot];
276         classcache_hash.ptr[slot] = c;
277
278         /* update number of hashtable-entries */
279         classcache_hash.entries++;
280
281         if (classcache_hash.entries > (classcache_hash.size * 2)) {
282
283                 /* reorganization of hashtable, average length of 
284                    the external chains is approx. 2                */  
285
286                 u4 i;
287                 classcache_name_entry *c;
288                 hashtable newhash;  /* the new hashtable */
289
290                 /* create new hashtable, double the size */
291                 init_hashtable(&newhash, classcache_hash.size * 2);
292                 newhash.entries = classcache_hash.entries;
293
294                 /* transfer elements to new hashtable */
295                 for (i = 0; i < classcache_hash.size; i++) {
296                         c = (classcache_name_entry *) classcache_hash.ptr[i];
297                         while (c) {
298                                 classcache_name_entry *nextc = c->hashlink;
299                                 u4 slot = (utf_hashkey(c->name->text, c->name->blength)) & (newhash.size - 1);
300                                                 
301                                 c->hashlink = newhash.ptr[slot];
302                                 newhash.ptr[slot] = c;
303
304                                 c = nextc;
305                         }
306                 }
307         
308                 /* dispose old table */ 
309                 MFREE(classcache_hash.ptr, void*, classcache_hash.size);
310                 classcache_hash = newhash;
311         }
312
313         return c;
314 }
315
316 /* classcache_lookup ***********************************************************
317  
318    Lookup a possibly loaded class
319   
320    IN:
321        initloader.......initiating loader for resolving the class name
322        classname........class name to look up
323   
324    RETURN VALUE:
325        The return value is a pointer to the cached class object,
326        or NULL, if the class is not in the cache.
327
328    Note: synchronized with global tablelock
329    
330 *******************************************************************************/
331
332 classinfo *
333 classcache_lookup(classloader *initloader,utf *classname)
334 {
335         classcache_name_entry *en;
336         classcache_class_entry *clsen;
337         classcache_loader_entry *lden;
338         classinfo *cls = NULL;
339
340         CLASSCACHE_LOCK();
341
342         en = classcache_lookup_name(classname);
343         
344         if (en) {
345                 /* iterate over all class entries */
346                 for (clsen=en->classes; clsen; clsen=clsen->next) {
347                         /* check if this entry has been loaded by initloader */
348                         for (lden=clsen->loaders; lden; lden=lden->next) {
349                                 if (lden->loader == initloader) {
350                                         /* found the loaded class entry */
351                                         CLASSCACHE_ASSERT(clsen->classobj);
352                                         cls = clsen->classobj;
353                                         goto found;
354                                 }
355                         }
356                 }
357         }
358 found:
359         CLASSCACHE_UNLOCK();
360         return cls;
361 }
362
363 /* classcache_lookup_defined ***************************************************
364  
365    Lookup a class with the given name and defining loader
366   
367    IN:
368        defloader........defining loader
369        classname........class name
370   
371    RETURN VALUE:
372        The return value is a pointer to the cached class object,
373        or NULL, if the class is not in the cache.
374    
375 *******************************************************************************/
376
377 classinfo * 
378 classcache_lookup_defined(classloader *defloader,utf *classname)
379 {
380         classcache_name_entry *en;
381         classcache_class_entry *clsen;
382         classcache_loader_entry *lden;
383         classinfo *cls = NULL;
384
385         CLASSCACHE_LOCK();
386
387         en = classcache_lookup_name(classname);
388         
389         if (en) {
390                 /* iterate over all class entries */
391                 for (clsen=en->classes; clsen; clsen=clsen->next) {
392                         if (!clsen->classobj)
393                                 continue;
394                         
395                         /* check if this entry has been defined by defloader */
396                         if (clsen->classobj->classloader == defloader) {
397                                 cls = clsen->classobj;
398                                 goto found;
399                         }
400                 }
401         }
402 found:
403         CLASSCACHE_UNLOCK();
404         return cls;
405 }
406
407 /* classcache_store ************************************************************
408    
409    Store a loaded class
410   
411    IN:
412        initloader.......initiating loader used to load the class
413        cls..............class object to cache
414   
415    RETURN VALUE:
416        true.............everything ok, the class was stored in
417                         the cache if necessary,
418        false............an exception has been thrown.
419    
420    Note: synchronized with global tablelock
421    
422 *******************************************************************************/
423
424 bool
425 classcache_store(classloader *initloader,classinfo *cls)
426 {
427         classcache_name_entry *en;
428         classcache_class_entry *clsen;
429         classcache_loader_entry *lden;
430
431         CLASSCACHE_ASSERT(cls);
432         CLASSCACHE_ASSERT(cls->loaded);
433
434 #ifdef CLASSCACHE_VERBOSE
435         fprintf(stderr,"classcache_store(%p,",initloader);
436         utf_fprint_classname(stderr,cls->name);
437         fprintf(stderr,")\n");
438 #endif
439
440         CLASSCACHE_LOCK();
441
442         en = classcache_new_name(cls->name);
443
444         CLASSCACHE_ASSERT(en);
445         
446         /* iterate over all class entries */
447         for (clsen=en->classes; clsen; clsen=clsen->next) {
448                 
449 #ifdef CLASSCACHE_DEBUG
450                 /* check if this entry has already been loaded by initloader */
451                 /* It never should have been loaded before! */
452                 for (lden=clsen->loaders; lden; lden=lden->next) {
453                         if (lden->loader == initloader)
454                                 CLASSCACHE_ASSERT(false);
455                 }
456 #endif
457
458                 /* check if initloader is constrained to this entry */
459                 for (lden=clsen->constraints; lden; lden=lden->next) {
460                         if (lden->loader == initloader) {
461                                 /* we have to use this entry */
462                                 /* check if is has already been resolved to another class */
463                                 if (clsen->classobj && clsen->classobj != cls) {
464                                         /* a loading constraint is violated */
465                                         *exceptionptr = new_exception_message(string_java_lang_LinkageError,
466                                                         "loading constraint violated XXX add message");
467                                         goto return_exception;
468                                 }
469
470                                 /* record initloader as initiating loader */
471                                 clsen->loaders = classcache_new_loader_entry(initloader,clsen->loaders);
472
473                                 /* record the loaded class object */
474                                 clsen->classobj = cls;
475
476                                 /* done */
477                                 goto return_success;
478                         }
479                 }
480                 
481         }
482
483         /* create a new class entry for this class object with */
484         /* initiating loader initloader                        */
485
486         clsen = NEW(classcache_class_entry);
487         clsen->classobj = cls;
488         clsen->loaders = classcache_new_loader_entry(initloader,NULL);
489         clsen->constraints = NULL;
490
491         clsen->next = en->classes;
492         en->classes = clsen;
493
494 return_success:
495         CLASSCACHE_UNLOCK();
496         return true;
497
498 return_exception:
499         CLASSCACHE_UNLOCK();
500         return false; /* exception */
501 }
502
503 /* classcache_find_loader ******************************************************
504  
505    Find the class entry loaded by or constrained to a given loader
506    (internally used helper function)
507   
508    IN:
509        entry............the classcache_name_entry
510        loader...........the loader to look for
511   
512    RETURN VALUE:
513        the classcache_class_entry for the given loader, or
514            NULL if no entry was found
515    
516 *******************************************************************************/
517
518 static classcache_class_entry *
519 classcache_find_loader(classcache_name_entry *entry,classloader *loader)
520 {
521         classcache_class_entry *clsen;
522         classcache_loader_entry *lden;
523         
524         CLASSCACHE_ASSERT(entry);
525
526         /* iterate over all class entries */
527         for (clsen=entry->classes; clsen; clsen=clsen->next) {
528                 
529                 /* check if this entry has already been loaded by initloader */
530                 for (lden=clsen->loaders; lden; lden=lden->next) {
531                         if (lden->loader == loader)
532                                 return clsen; /* found */
533                 }
534                 
535                 /* check if loader is constrained to this entry */
536                 for (lden=clsen->constraints; lden; lden=lden->next) {
537                         if (lden->loader == loader)
538                                 return clsen; /* found */
539                 }
540         }
541
542         /* not found */
543         return NULL;
544 }
545
546 /* classcache_free_class_entry *************************************************
547  
548    Free the memory used by a class entry
549   
550    IN:
551        clsen............the classcache_class_entry to free  
552            
553 *******************************************************************************/
554
555 static void
556 classcache_free_class_entry(classcache_class_entry *clsen)
557 {
558         classcache_loader_entry *lden;
559         classcache_loader_entry *next;
560         
561         CLASSCACHE_ASSERT(clsen);
562
563         for (lden=clsen->loaders; lden; lden=next) {
564                 next = lden->next;
565                 FREE(lden,classcache_loader_entry);
566         }
567         for (lden=clsen->constraints; lden; lden=next) {
568                 next = lden->next;
569                 FREE(lden,classcache_loader_entry);
570         }
571
572         FREE(clsen,classcache_class_entry);
573 }
574
575 /* classcache_remove_class_entry ***********************************************
576  
577    Remove a classcache_class_entry from the list of possible resolution of
578    a name entry
579    (internally used helper function)
580   
581    IN:
582        entry............the classcache_name_entry
583        clsen............the classcache_class_entry to remove
584   
585 *******************************************************************************/
586
587 static void
588 classcache_remove_class_entry(classcache_name_entry *entry,
589                                                           classcache_class_entry *clsen)
590 {
591         classcache_class_entry **chain;
592
593         CLASSCACHE_ASSERT(entry);
594         CLASSCACHE_ASSERT(clsen);
595
596         chain = &(entry->classes);
597         while (*chain) {
598                 if (*chain == clsen) {
599                         *chain = clsen->next;
600                         classcache_free_class_entry(clsen);
601                         return;
602                 }
603                 chain = &((*chain)->next);
604         }
605 }
606
607 /* classcache_free_name_entry **************************************************
608  
609    Free the memory used by a name entry
610   
611    IN:
612        entry............the classcache_name_entry to free  
613            
614 *******************************************************************************/
615
616 static void
617 classcache_free_name_entry(classcache_name_entry *entry)
618 {
619         classcache_class_entry *clsen;
620         classcache_class_entry *next;
621         
622         CLASSCACHE_ASSERT(entry);
623
624         for (clsen=entry->classes; clsen; clsen=next) {
625                 next = clsen->next;
626                 classcache_free_class_entry(clsen);
627         }
628
629         FREE(entry,classcache_name_entry);
630 }
631
632 /* classcache_free *************************************************************
633  
634    Free the memory used by the class cache
635
636    NOTE:
637        The class cache may not be used any more after this call, except
638            when it is reinitialized with classcache_init.
639   
640    Note: NOT synchronized!
641   
642 *******************************************************************************/
643
644 void 
645 classcache_free()
646 {
647         u4 slot;
648         classcache_name_entry *entry;
649         classcache_name_entry *next;
650
651         for (slot=0; slot<classcache_hash.size; ++slot) {
652                 for (entry=(classcache_name_entry *)classcache_hash.ptr[slot];
653                          entry;
654                          entry = next)
655                 {
656                         next = entry->hashlink;
657                         classcache_free_name_entry(entry);
658                 }
659         }
660
661         MFREE(classcache_hash.ptr,voidptr,classcache_hash.size);
662         classcache_hash.size = 0;
663         classcache_hash.entries = 0;
664         classcache_hash.ptr = NULL;
665 }
666
667 /* classcache_add_constraint ***************************************************
668  
669    Add a loading constraint
670   
671    IN:
672        a................first initiating loader
673        b................second initiating loader
674        classname........class name
675   
676    RETURN VALUE:
677        true.............everything ok, the constraint has been added,
678        false............an exception has been thrown.
679    
680    Note: synchronized with global tablelock
681    
682 *******************************************************************************/
683
684 bool
685 classcache_add_constraint(classloader *a,classloader *b,utf *classname)
686 {
687         classcache_name_entry *en;
688         classcache_class_entry *clsenA;
689         classcache_class_entry *clsenB;
690
691         CLASSCACHE_ASSERT(classname);
692
693 #ifdef CLASSCACHE_VERBOSE
694         fprintf(stderr,"classcache_add_constraint(%p,%p,",(void*)a,(void*)b);
695         utf_fprint_classname(stderr,classname);
696         fprintf(stderr,")\n");
697 #endif
698
699         /* a constraint with a == b is trivially satisfied */
700         if (a == b)
701                 return true;
702
703         CLASSCACHE_LOCK();
704
705         en = classcache_new_name(classname);
706
707         CLASSCACHE_ASSERT(en);
708         
709         /* find the entry loaded by / constrained to each loader */
710         clsenA = classcache_find_loader(en,a);
711         clsenB = classcache_find_loader(en,b);
712
713         if (clsenA && clsenB) {
714                 /* { both loaders have corresponding entries } */
715
716                 /* if the entries are the same, the constraint is already recorded */
717                 if (clsenA == clsenB)
718                         goto return_success;
719
720                 /* check if the entries can be merged */
721                 if (clsenA->classobj && clsenB->classobj && clsenA->classobj != clsenB->classobj) {
722                         /* no, the constraint is violated */
723                         *exceptionptr = new_exception_message(string_java_lang_LinkageError,
724                                         "loading constraint violated XXX add message");
725                         goto return_exception;
726                 }
727
728                 /* yes, merge the entries */
729                 /* clsenB will be merged into clsenA */
730                 clsenA->loaders = classcache_merge_loaders(clsenA->loaders,
731                                                                                                    clsenB->loaders);
732                 
733                 clsenA->constraints = classcache_merge_loaders(clsenA->constraints,
734                                                                                                            clsenB->constraints);
735
736                 if (!clsenA->classobj)
737                         clsenA->classobj = clsenB->classobj;
738                 
739                 /* remove clsenB from the list of class entries */
740                 classcache_remove_class_entry(en,clsenB);
741         }
742         else {
743                 /* { at most one of the loaders has a corresponding entry } */
744
745                 /* set clsenA to the single class entry we have */
746                 if (!clsenA)
747                         clsenA = clsenB;
748
749                 if (!clsenA) {
750                         /* { no loader has a corresponding entry } */
751
752                         /* create a new class entry with the constraint (a,b,en->name) */
753                         clsenA = NEW(classcache_class_entry);
754                         clsenA->classobj = NULL;
755                         clsenA->loaders = NULL;
756                         clsenA->constraints = classcache_new_loader_entry(b,NULL);
757                         clsenA->constraints = classcache_new_loader_entry(a,clsenA->constraints);
758
759                         clsenA->next = en->classes;
760                         en->classes = clsenA;
761                 }
762                 else {
763                         /* make b the loader that has no corresponding entry */
764                         if (clsenB)
765                                 b = a;
766                         
767                         /* loader b must be added to entry clsenA */
768                         clsenA->constraints = classcache_new_loader_entry(b,clsenA->constraints);
769                 }
770         }
771
772 return_success:
773         CLASSCACHE_UNLOCK();
774         return true;
775
776 return_exception:
777         CLASSCACHE_UNLOCK();
778         return false; /* exception */
779 }
780
781 /*============================================================================*/
782 /* DEBUG DUMPS                                                                */
783 /*============================================================================*/
784
785 /* classcache_debug_dump *******************************************************
786  
787    Print the contents of the loaded class cache to a stream
788   
789    IN:
790        file.............output stream
791   
792    Note: synchronized with global tablelock
793    
794 *******************************************************************************/
795
796 void
797 classcache_debug_dump(FILE *file)
798 {
799         classcache_name_entry *c;
800         classcache_class_entry *clsen;
801         classcache_loader_entry *lden;
802         u4 slot;
803
804         CLASSCACHE_LOCK();
805
806         fprintf(file,"\n=== [loaded class cache] =====================================\n\n");
807         fprintf(file,"hash size   : %d\n",classcache_hash.size);
808         fprintf(file,"hash entries: %d\n",classcache_hash.entries);
809         fprintf(file,"\n");
810
811         for (slot=0; slot<classcache_hash.size; ++slot) {
812                 c = (classcache_name_entry *) classcache_hash.ptr[slot];
813
814                 for (; c; c=c->hashlink) {
815                         utf_fprint_classname(file,c->name);
816                         fprintf(file,"\n");
817
818                         /* iterate over all class entries */
819                         for (clsen=c->classes; clsen; clsen=clsen->next) {
820                                 fprintf(file,"    %s\n",(clsen->classobj) ? "loaded" : "unresolved");
821                                 fprintf(file,"    loaders:");
822                                 for (lden=clsen->loaders; lden; lden=lden->next) {
823                                         fprintf(file," %p",(void *)lden->loader);
824                                 }
825                                 fprintf(file,"\n    constraints:");
826                                 for (lden=clsen->constraints; lden; lden=lden->next) {
827                                         fprintf(file," %p",(void *)lden->loader);
828                                 }
829                                 fprintf(file,"\n");
830                         }
831                 }
832         }
833         fprintf(file,"\n==============================================================\n\n");
834
835         CLASSCACHE_UNLOCK();
836 }
837
838 /*
839  * These are local overrides for various environment variables in Emacs.
840  * Please do not remove this and leave it at the end of the file, where
841  * Emacs will automagically detect them.
842  * ---------------------------------------------------------------------
843  * Local variables:
844  * mode: c
845  * indent-tabs-mode: t
846  * c-basic-offset: 4
847  * tab-width: 4
848  * End:
849  * vim:noexpandtab:sw=4:ts=4:
850  */
851