2005-03-08 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mono / metadata / monodiet.c
1 /*
2  * monodiet.c: an IL code garbage collector
3  *
4  * Author:
5  *        Paolo Molaro (lupus@ximian.com)
6  *
7  * (C) 2004 Novell, Inc.
8  */
9
10 #include <glib.h>
11 #include <string.h>
12 #include "mono/metadata/class-internals.h"
13 #include "mono/metadata/assembly.h"
14 #include "mono/metadata/tokentype.h"
15 #include "mono/metadata/opcodes.h"
16 #include "mono/metadata/tabledefs.h"
17 #include "mono/metadata/mono-endian.h"
18 #include "mono/metadata/appdomain.h" /* mono_init */
19 #include "mono/metadata/debug-helpers.h"
20
21 /*
22 TODO:
23 *) handle proprties, events in a smart way.
24 *) add option that takes a directory and outputs the il files and recompiles automatically
25 */
26 static GHashTable *type_table;
27 static GHashTable *method_table;
28 static GHashTable *field_table;
29 static GHashTable *image_table;
30 static GList *virtual_methods;
31 static int verbose = 0;
32 static int force_enums = 0;
33 static FILE *outf = NULL;
34
35 enum {
36         TYPE_BASIC = 1 << 0,
37         TYPE_FIELDS = 1 << 1,
38         TYPE_METHODS = 1 << 2,
39         TYPE_PROPERTIES = 1 << 3,
40         TYPE_EVENTS = 1 << 4,
41         TYPE_ALL = TYPE_BASIC | TYPE_FIELDS | TYPE_METHODS | TYPE_PROPERTIES | TYPE_EVENTS
42 };
43
44 static void handle_cattrs (MonoCustomAttrInfo* cattrs);
45
46 static void
47 add_type (MonoClass* klass)
48 {
49         gpointer val = NULL, oldkey = NULL;
50         if (g_hash_table_lookup_extended (type_table, klass, &oldkey, &val))
51                 return;
52         g_hash_table_insert (type_table, klass, NULL);
53         g_hash_table_insert (image_table, klass->image, NULL);
54 }
55
56 static void
57 add_types_from_signature (MonoMethodSignature *sig)
58 {
59         MonoClass *klass;
60         int i;
61         for (i = 0; i < sig->param_count; ++i) {
62                 klass = mono_class_from_mono_type (sig->params [i]);
63                 add_type (klass);
64         }
65         klass = mono_class_from_mono_type (sig->ret);
66         add_type (klass);
67 }
68
69 static void
70 add_field (MonoClassField *field) {
71         MonoClass *k;
72         MonoCustomAttrInfo* cattrs;
73         gpointer val = NULL, oldkey = NULL;
74
75         if (g_hash_table_lookup_extended (field_table, field, &oldkey, &val))
76                 return;
77         g_hash_table_insert (field_table, field, NULL);
78         add_type (field->parent);
79         k = mono_class_from_mono_type (field->type);
80         add_type (k);
81         cattrs = mono_custom_attrs_from_field (field->parent, field);
82         handle_cattrs (cattrs);
83 }
84
85 static void
86 add_types_from_method (MonoMethod *method) {
87         const MonoOpcode *opcode;
88         MonoMethodHeader *header;
89         const unsigned char *ip;
90         gpointer val = NULL, oldkey = NULL;
91         int i, n;
92         guint32 token;
93         MonoClass *klass;
94         MonoClassField *field;
95         MonoCustomAttrInfo* cattrs;
96
97         if (g_hash_table_lookup_extended (method_table, method, &oldkey, &val))
98                 return;
99         g_hash_table_insert (method_table, method, NULL);
100
101         g_assert (method->klass);
102
103         if (verbose > 1)
104                 g_print ("#processing method: %s\n", mono_method_full_name (method, TRUE));
105         mono_class_init (method->klass);
106         cattrs = mono_custom_attrs_from_method (method);
107         handle_cattrs (cattrs);
108         add_type (method->klass);
109         add_types_from_signature (mono_method_signature (method));
110         for (i = 0; i < mono_method_signature (method)->param_count + 1; ++i) {
111                 cattrs = mono_custom_attrs_from_param (method, i);
112                 handle_cattrs (cattrs);
113         }
114
115         if (method->flags & METHOD_ATTRIBUTE_VIRTUAL)
116                 virtual_methods = g_list_prepend (virtual_methods, method);
117
118         /* if no IL code to parse, return */
119         if (method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME))
120                 return;
121         if (method->flags & (METHOD_ATTRIBUTE_PINVOKE_IMPL | METHOD_ATTRIBUTE_ABSTRACT))
122                 return;
123
124         header = mono_method_get_header (method);
125
126         for (i = 0; i < header->num_locals; ++i) {
127                 klass = mono_class_from_mono_type (header->locals [i]);
128                 add_type (klass);
129         }
130         for (i = 0; i < header->num_clauses; ++i) {
131                 MonoExceptionClause *clause = &header->clauses [i];
132                 if (clause->flags == MONO_EXCEPTION_CLAUSE_NONE)
133                         add_type (clause->data.catch_class);
134         }
135
136         ip = header->code;
137
138         while (ip < (header->code + header->code_size)) {
139                 if (verbose > 2)
140                         g_print ("#%s", mono_disasm_code_one (NULL, method, ip, NULL));
141                 if (*ip == 0xfe) {
142                         ++ip;
143                         i = *ip + 256;
144                 } else {
145                         i = *ip;
146                 }
147
148                 opcode = &mono_opcodes [i];
149
150                 switch (opcode->argument) {
151                 case MonoInlineNone:
152                         ip++;
153                         break;
154                 case MonoInlineType:
155                         token = read32 (ip + 1);
156                         add_type (mono_class_get (method->klass->image, token));
157                         ip += 5;
158                         break;
159                 case MonoInlineField: {
160                         token = read32 (ip + 1);
161                         field = mono_field_from_token (method->klass->image, token, &klass, NULL);
162                         add_field (field);
163                         add_type (klass);
164                         ip += 5;
165                         break;
166                 }
167                 case MonoInlineTok:
168                 case MonoInlineSig:
169                         /* FIXME */
170                 case MonoInlineString:
171                 case MonoShortInlineR:
172                 case MonoInlineBrTarget:
173                 case MonoInlineI:
174                         ip += 5;
175                         break;
176                 case MonoInlineVar:
177                         ip += 3;
178                         break;
179                 case MonoShortInlineVar:
180                 case MonoShortInlineI:
181                 case MonoShortInlineBrTarget:
182                         ip += 2;
183                         break;
184                 case MonoInlineSwitch:
185                         ++ip;
186                         n = read32 (ip);
187                         ip += 4;
188                         ip += 4 * n;
189                         break;
190                 case MonoInlineI8:
191                 case MonoInlineR:
192                         ip += 9;
193                         break;
194                 case MonoInlineMethod:
195                         {
196                                 MonoMethod *cm = mono_get_method (method->klass->image, read32 (ip + 1), NULL);
197                                 add_type (cm->klass);
198                                 add_types_from_method (cm);
199                         }
200                         ip += 5;
201                         break;
202                 default:
203                         g_assert_not_reached ();
204                 }
205         }
206 }
207
208 static void
209 handle_cattrs (MonoCustomAttrInfo* cattrs)
210 {
211         int i;
212         if (!cattrs)
213                 return;
214         for (i = 0; i < cattrs->num_attrs; ++i) {
215                 add_types_from_method (cattrs->attrs [i].ctor);
216         }
217 }
218
219 static void
220 handle_type (MonoClass *klass, guint32 flags)
221 {
222         int i;
223         guint32 missing;
224         MonoCustomAttrInfo* cattrs;
225         gpointer val = NULL, oldkey = NULL;
226         MonoProperty* prop;
227         MonoEvent* event;
228         MonoMethod* method;
229         MonoClassField* field;
230         gpointer iter;
231         
232         if (g_hash_table_lookup_extended (type_table, klass, &oldkey, &val)) {
233                 missing = flags & ~(GPOINTER_TO_UINT (val));
234         } else {
235                 missing = flags;
236         }
237         if (!missing)
238                 return;
239         g_hash_table_insert (type_table, klass, GUINT_TO_POINTER (missing));
240         if (verbose)
241                 g_print ("#processing klass: %s.%s\n", klass->name_space, klass->name);
242         mono_class_init (klass);
243         if (klass->parent)
244                 add_type (klass->parent);
245         if (klass->nested_in)
246                 add_type (klass->nested_in);
247         iter = NULL;
248         while ((method = mono_class_get_methods (klass, &iter))) {
249                 if ((missing & TYPE_METHODS) || strcmp (method->name, ".cctor") == 0)
250                         add_types_from_method (method);
251         }
252         if (klass->enumtype) {
253                 add_field (mono_class_get_field_from_name (klass, "value__"));
254         }
255         if (force_enums || (missing & TYPE_FIELDS)) {
256                 iter = NULL;
257                 while ((field = mono_class_get_fields (klass, &iter)))
258                         add_field (field);
259         }
260         iter = NULL;
261         while ((prop = mono_class_get_properties (klass, &iter))) {
262                 cattrs = mono_custom_attrs_from_property (klass, prop);
263                 handle_cattrs (cattrs);
264         }
265         iter = NULL;
266         while ((event = mono_class_get_events (klass, &iter))) {
267                 cattrs = mono_custom_attrs_from_event (klass, event);
268                 handle_cattrs (cattrs);
269         }
270         for (i = 0; i < klass->interface_count; ++i)
271                 add_type (klass->interfaces [i]);
272         cattrs = mono_custom_attrs_from_class (klass);
273         handle_cattrs (cattrs);
274 }
275
276 static void
277 process_image (MonoImage *image, gboolean all) {
278         int i;
279         const MonoTableInfo *t;
280         MonoClass *klass;
281         MonoMethod *entry;
282         guint32 eptoken;
283
284         if (verbose)
285                 g_print ("#processing image: %s\n", mono_image_get_name (image));
286         eptoken =  mono_image_get_entry_point (image);
287         if (eptoken) {
288                 entry = mono_get_method (image, eptoken, NULL);
289                 add_types_from_method (entry);
290         }
291         /* we always add the <Module> type */
292         klass = mono_class_get (image, MONO_TOKEN_TYPE_DEF | 1);
293         handle_type (klass, all? TYPE_ALL: TYPE_BASIC);
294         if (all) {
295                 t = mono_image_get_table_info (image, MONO_TABLE_TYPEDEF);
296                 for (i = 1; i < mono_table_info_get_rows (t); ++i) {
297                         klass = mono_class_get (image, MONO_TOKEN_TYPE_DEF | (i + 1));
298                         handle_type (klass, all? TYPE_ALL: TYPE_BASIC);
299                 }
300         }
301 }
302
303 static void
304 process_assembly (MonoAssembly *assembly, gboolean all) {
305         MonoCustomAttrInfo* cattrs;
306         process_image (mono_assembly_get_image (assembly), all);
307         cattrs = mono_custom_attrs_from_assembly (assembly);
308         handle_cattrs (cattrs);
309 }
310
311 static GList *worklist = NULL;
312
313 static void
314 collect_type (const gpointer key, const gpointer val, gpointer user_data)
315 {
316         MonoClass *klass = key;
317         if (klass->rank || klass->byval_arg.type == MONO_TYPE_PTR)
318                 return;
319         worklist = g_list_prepend (worklist, key);
320 }
321
322 static void
323 check_vmethods (MonoClass *klass, MonoMethod *method)
324 {
325         MonoMethod **vtable;
326         if (method->klass == klass)
327                 return;
328         mono_class_init (klass);
329         mono_class_init (method->klass);
330         vtable = klass->vtable;
331         /* iface */
332         if (!vtable)
333                 return;
334         if (method->klass->flags & TYPE_ATTRIBUTE_INTERFACE) {
335                 if (method->klass->interface_id <= klass->max_interface_id &&
336                                 (klass->interface_offsets [method->klass->interface_id] >= 0)) {
337                         int iface_offset = klass->interface_offsets [method->klass->interface_id];
338                         g_assert (method->slot != -1);
339                         if (vtable [iface_offset + method->slot])
340                                 add_types_from_method (vtable [iface_offset + method->slot]);
341                 }
342         } else {
343                 if (mono_class_has_parent (klass, method->klass)) {
344                         g_assert (method->slot != -1);
345                         if (vtable [method->slot])
346                                 add_types_from_method (vtable [method->slot]);
347                 }
348         }
349 }
350
351 static void
352 process_images (void) {
353         int old_count, new_count;
354         GList *item, *vmethod;
355         new_count = g_hash_table_size (type_table);
356         new_count += g_hash_table_size (method_table);
357         new_count += g_hash_table_size (field_table);
358         do {
359                 old_count = new_count;
360                 if (verbose)
361                         g_print ("#processing type table: %d\n", old_count);
362                 g_list_free (worklist);
363                 worklist = NULL;
364                 g_hash_table_foreach (type_table, collect_type, NULL);
365                 for (item = worklist; item; item = item->next) {
366                         for (vmethod = virtual_methods; vmethod; vmethod = vmethod->next) {
367                                 check_vmethods (item->data, vmethod->data);
368                         }
369                 }
370                 g_list_free (worklist);
371                 worklist = NULL;
372                 g_hash_table_foreach (type_table, collect_type, NULL);
373                 for (item = worklist; item; item = item->next) {
374                         handle_type (item->data, TYPE_BASIC);
375                 }
376                 new_count = g_hash_table_size (type_table);
377                 new_count += g_hash_table_size (method_table);
378                 new_count += g_hash_table_size (field_table);
379         } while (old_count != new_count);
380 }
381
382 static void
383 foreach_method (const gpointer key, const gpointer val, gpointer user_data)
384 {
385         MonoMethod *method = key;
386         MonoClass *klass = user_data;
387         if (method->klass != klass)
388                 return;
389         /* FIXME: ensure it's the correct token */
390         fprintf (outf, "M:0x%x\n", mono_metadata_token_index (method->token));
391 }
392
393 static void
394 foreach_field (const gpointer key, const gpointer val, gpointer user_data)
395 {
396         MonoClassField *field = key;
397         MonoClass *klass = user_data;
398         int idx;
399         if (field->parent != klass)
400                 return;
401         idx = mono_metadata_token_index (mono_class_get_field_token (field));
402         fprintf (outf, "F:0x%x\n", idx);
403 }
404
405 static void
406 foreach_type (const gpointer key, const gpointer val, gpointer user_data)
407 {
408         MonoClass *klass = key;
409         MonoImage *image = user_data;
410         if (klass->image != image)
411                 return;
412         if (klass->rank || klass->byval_arg.type == MONO_TYPE_PTR)
413                 return;
414         fprintf (outf, "T:0x%x\n", mono_metadata_token_index (klass->type_token));
415         g_hash_table_foreach (method_table, foreach_method, klass);
416         g_hash_table_foreach (field_table, foreach_field, klass);
417 }
418
419 static void
420 foreach_image (const gpointer key, const gpointer val, gpointer user_data)
421 {
422         MonoImage *image = key;
423         const char* aname;
424         aname = mono_image_get_name (image);
425         /* later print the guid as well to prevent mismatches */
426         fprintf (outf, "[%s]\n", aname);
427         g_hash_table_foreach (type_table, foreach_type, image);
428 }
429
430 static void
431 dump_images (const char *filename)
432 {
433         if (filename) {
434                 FILE* f = fopen (filename, "w");
435                 if (!f)
436                         g_error ("cannot write to file '%s'\n", filename);
437                 else
438                         outf = f;
439         } else {
440                 outf = stdout;
441         }
442         g_hash_table_foreach (image_table, foreach_image, NULL);
443         if (filename)
444                 fclose (outf);
445 }
446
447 static void
448 usage (int code) {
449         printf ("monodiet 0.1 Copyright (c) 2004 Novell, Inc\n");
450         printf ("List the metadata elements used by the named assemblies.\n");
451         printf ("Usage: monodiet [options] assembly [assembly2 ...]\n\n");
452         printf ("Options:\n");
453         printf ("\t-v           increase verbose level\n");
454         printf ("\t-h           show help screen\n");
455         printf ("\t-e           force inclusion of enum members\n");
456         printf ("\t-o FILENAME  output the result to filename\n");
457         printf ("\t-a FILENAME  add metadata elements from description in filename\n");
458         printf ("\t-F ASSEMBLY  force add all metadata elements from assembly\n");
459         exit (code);
460 }
461
462 static MonoImage*
463 find_image (char *name)
464 {
465         return mono_image_loaded (name);
466 }
467
468 static MonoClass*
469 find_class (MonoImage *image, char *name)
470 {
471         char *p = strrchr (name, '.');
472         if (p) {
473                 *p = 0;
474                 return mono_class_from_name (image, name, p + 1);
475         }
476         return mono_class_from_name (image, "", name);
477 }
478
479 static void
480 load_roots (const char* filename)
481 {
482         FILE *file;
483         char buf [2048];
484         char *p, *s;
485         int line = 0;
486         MonoImage *image = NULL;
487         MonoClass *klass = NULL;
488         MonoClassField *field;
489         MonoMethodDesc *mdesc;
490         MonoMethod *method;
491
492         if (!(file = fopen (filename, "r")))
493                 return;
494         
495         while (fgets (buf, sizeof (buf), file)) {
496                 /* FIXME:
497                  * decide on the format to use to express types, fields, methods,
498                  * maybe the same used on output from the tool, but with explicit
499                  * names and signatures instead of token indexes
500                  * add wildcard support
501                  */
502                 ++line;
503                 s = buf;
504                 while (*s && g_ascii_isspace (*s)) ++s;
505                 switch (*s) {
506                 case 0:
507                 case '#':
508                         continue; /* comment */
509                 case '[':
510                         p = strchr (s, ']');
511                         if (!p)
512                                 g_error ("invalid assembly format at line %d\n", line);
513                         *p = 0;
514                         p = s + 1;
515                         image = find_image (p);
516                         if (!image)
517                                 g_error ("image not loaded: %s\n", p);
518                         klass = NULL;
519                         break;
520                 case 'T':
521                         if (s [1] != ':')
522                                 g_error ("invalid type format at line %d\n", line);
523                         if (!image)
524                                 break;
525                         klass = find_class (image, s + 2);
526                         break;
527                 case 'F':
528                         if (s [1] != ':')
529                                 g_error ("invalid field format at line %d\n", line);
530                         if (!image || !klass)
531                                 break;
532                         p = s + 2;
533                         if (*p == '*') {
534                                 handle_type (klass, TYPE_FIELDS);
535                                 break;
536                         }
537                         field = mono_class_get_field_from_name (klass, p);
538                         if (!field)
539                                 g_warning ("no field '%s' at line %d\n", p, line);
540                         else
541                                 add_field (field);
542                         break;
543                 case 'M':
544                         if (s [1] != ':')
545                                 g_error ("invalid method format at line %d\n", line);
546                         if (!image || !klass)
547                                 break;
548                         p = s + 2;
549                         if (*p == '*') {
550                                 handle_type (klass, TYPE_METHODS);
551                                 break;
552                         }
553                         mdesc = mono_method_desc_new (p, FALSE);
554                         if (!mdesc) {
555                                 g_error ("invalid method desc at line %d\n", line);
556                         }
557                         method = mono_method_desc_search_in_class (mdesc, klass);
558                         if (!method)
559                                 g_warning ("no method '%s' at line %d\n", p, line);
560                         else
561                                 add_types_from_method (method);
562                         mono_method_desc_free (mdesc);
563                         break;
564                 default:
565                         g_error ("invalid format at line %d\n", line);
566                 }
567         }
568         fclose (file);
569 }
570
571 int
572 main (int argc, char *argv[]) {
573         MonoAssembly *assembly = NULL;
574         const char *aname = NULL;
575         const char *outfile = NULL;
576         const char *rootfile = NULL;
577         int i;
578         gboolean all_meta = FALSE;
579
580         mono_init (argv [0]);
581
582         type_table = g_hash_table_new (NULL, NULL);
583         method_table = g_hash_table_new (NULL, NULL);
584         field_table = g_hash_table_new (NULL, NULL);
585         image_table = g_hash_table_new (NULL, NULL);
586
587         for (i = 1; i < argc; ++i) {
588                 all_meta = FALSE;
589                 aname = argv [i];
590                 if (strcmp (aname, "-v") == 0) {
591                         verbose++;
592                         continue;
593                 } else if (strcmp (aname, "-e") == 0) {
594                         force_enums = 1;
595                         continue;
596                 } else if (strcmp (aname, "-h") == 0) {
597                         usage (0);
598                 } else if (strcmp (aname, "-o") == 0) {
599                         i++;
600                         if (i >= argc)
601                                 usage (1);
602                         outfile = argv [i];
603                         continue;
604                 } else if (strcmp (aname, "-F") == 0) {
605                         i++;
606                         if (i >= argc)
607                                 usage (1);
608                         all_meta = TRUE;
609                         aname = argv [i];
610                 } else if (strcmp (aname, "-a") == 0) {
611                         i++;
612                         if (i >= argc)
613                                 usage (1);
614                         rootfile = argv [i];
615                         continue;
616                 }
617                 assembly = mono_assembly_open (aname, NULL);
618                 if (!assembly) {
619                         g_print ("cannot open assembly %s\n", aname);
620                         exit (1);
621                 }
622                 process_assembly (assembly, all_meta);
623         }
624         if (!assembly)
625                 usage (1);
626         if (rootfile)
627                 load_roots (rootfile);
628         process_images ();
629         dump_images (outfile);
630
631         return 0;
632 }
633
634