Mon Dec 13 18:10:50 CET 2004 Paolo Molaro <lupus@ximian.com>
[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 (method->signature);
110         for (i = 0; i < method->signature->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         
227         if (g_hash_table_lookup_extended (type_table, klass, &oldkey, &val)) {
228                 missing = flags & ~(GPOINTER_TO_UINT (val));
229         } else {
230                 missing = flags;
231         }
232         if (!missing)
233                 return;
234         g_hash_table_insert (type_table, klass, GUINT_TO_POINTER (missing));
235         if (verbose)
236                 g_print ("#processing klass: %s.%s\n", klass->name_space, klass->name);
237         mono_class_init (klass);
238         if (klass->parent)
239                 add_type (klass->parent);
240         if (klass->nested_in)
241                 add_type (klass->nested_in);
242         for (i = 0; i < klass->method.count; ++i) {
243                 if ((missing & TYPE_METHODS) || strcmp (klass->methods [i]->name, ".cctor") == 0)
244                         add_types_from_method (klass->methods [i]);
245         }
246         if (klass->enumtype) {
247                 add_field (mono_class_get_field_from_name (klass, "value__"));
248         }
249         if (force_enums || (missing & TYPE_FIELDS)) {
250                 for (i = 0; i < klass->field.count; ++i) {
251                         add_field (&klass->fields [i]);
252                 }
253         }
254         for (i = 0; i < klass->property.count; ++i) {
255                 cattrs = mono_custom_attrs_from_property (klass, &klass->properties [i]);
256                 handle_cattrs (cattrs);
257         }
258         for (i = 0; i < klass->event.count; ++i) {
259                 cattrs = mono_custom_attrs_from_event (klass, &klass->events [i]);
260                 handle_cattrs (cattrs);
261         }
262         for (i = 0; i < klass->interface_count; ++i)
263                 add_type (klass->interfaces [i]);
264         cattrs = mono_custom_attrs_from_class (klass);
265         handle_cattrs (cattrs);
266 }
267
268 static void
269 process_image (MonoImage *image, gboolean all) {
270         int i;
271         const MonoTableInfo *t;
272         MonoClass *klass;
273         MonoMethod *entry;
274         guint32 eptoken;
275
276         if (verbose)
277                 g_print ("#processing image: %s\n", mono_image_get_name (image));
278         eptoken =  mono_image_get_entry_point (image);
279         if (eptoken) {
280                 entry = mono_get_method (image, eptoken, NULL);
281                 add_types_from_method (entry);
282         }
283         /* we always add the <Module> type */
284         klass = mono_class_get (image, MONO_TOKEN_TYPE_DEF | 1);
285         handle_type (klass, all? TYPE_ALL: TYPE_BASIC);
286         if (all) {
287                 t = mono_image_get_table_info (image, MONO_TABLE_TYPEDEF);
288                 for (i = 1; i < mono_table_info_get_rows (t); ++i) {
289                         klass = mono_class_get (image, MONO_TOKEN_TYPE_DEF | (i + 1));
290                         handle_type (klass, all? TYPE_ALL: TYPE_BASIC);
291                 }
292         }
293 }
294
295 static void
296 process_assembly (MonoAssembly *assembly, gboolean all) {
297         MonoCustomAttrInfo* cattrs;
298         process_image (mono_assembly_get_image (assembly), all);
299         cattrs = mono_custom_attrs_from_assembly (assembly);
300         handle_cattrs (cattrs);
301 }
302
303 static GList *worklist = NULL;
304
305 static void
306 collect_type (const gpointer key, const gpointer val, gpointer user_data)
307 {
308         MonoClass *klass = key;
309         if (klass->rank || klass->byval_arg.type == MONO_TYPE_PTR)
310                 return;
311         worklist = g_list_prepend (worklist, key);
312 }
313
314 static void
315 check_vmethods (MonoClass *klass, MonoMethod *method)
316 {
317         MonoMethod **vtable;
318         if (method->klass == klass)
319                 return;
320         mono_class_init (klass);
321         mono_class_init (method->klass);
322         vtable = klass->vtable;
323         /* iface */
324         if (!vtable)
325                 return;
326         if (method->klass->flags & TYPE_ATTRIBUTE_INTERFACE) {
327                 if (method->klass->interface_id <= klass->max_interface_id &&
328                                 (klass->interface_offsets [method->klass->interface_id] >= 0)) {
329                         int iface_offset = klass->interface_offsets [method->klass->interface_id];
330                         g_assert (method->slot != -1);
331                         if (vtable [iface_offset + method->slot])
332                                 add_types_from_method (vtable [iface_offset + method->slot]);
333                 }
334         } else {
335                 if (mono_class_has_parent (klass, method->klass)) {
336                         g_assert (method->slot != -1);
337                         if (vtable [method->slot])
338                                 add_types_from_method (vtable [method->slot]);
339                 }
340         }
341 }
342
343 static void
344 process_images (void) {
345         int old_count, new_count;
346         GList *item, *vmethod;
347         new_count = g_hash_table_size (type_table);
348         new_count += g_hash_table_size (method_table);
349         new_count += g_hash_table_size (field_table);
350         do {
351                 old_count = new_count;
352                 if (verbose)
353                         g_print ("#processing type table: %d\n", old_count);
354                 g_list_free (worklist);
355                 worklist = NULL;
356                 g_hash_table_foreach (type_table, collect_type, NULL);
357                 for (item = worklist; item; item = item->next) {
358                         for (vmethod = virtual_methods; vmethod; vmethod = vmethod->next) {
359                                 check_vmethods (item->data, vmethod->data);
360                         }
361                 }
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                         handle_type (item->data, TYPE_BASIC);
367                 }
368                 new_count = g_hash_table_size (type_table);
369                 new_count += g_hash_table_size (method_table);
370                 new_count += g_hash_table_size (field_table);
371         } while (old_count != new_count);
372 }
373
374 static void
375 foreach_method (const gpointer key, const gpointer val, gpointer user_data)
376 {
377         MonoMethod *method = key;
378         MonoClass *klass = user_data;
379         if (method->klass != klass)
380                 return;
381         /* FIXME: ensure it's the correct token */
382         fprintf (outf, "M:0x%x\n", mono_metadata_token_index (method->token));
383 }
384
385 static void
386 foreach_field (const gpointer key, const gpointer val, gpointer user_data)
387 {
388         MonoClassField *field = key;
389         MonoClass *klass = user_data;
390         int idx;
391         if (field->parent != klass)
392                 return;
393         idx = mono_metadata_token_index (mono_class_get_field_token (field));
394         fprintf (outf, "F:0x%x\n", idx);
395 }
396
397 static void
398 foreach_type (const gpointer key, const gpointer val, gpointer user_data)
399 {
400         MonoClass *klass = key;
401         MonoImage *image = user_data;
402         if (klass->image != image)
403                 return;
404         if (klass->rank || klass->byval_arg.type == MONO_TYPE_PTR)
405                 return;
406         fprintf (outf, "T:0x%x\n", mono_metadata_token_index (klass->type_token));
407         g_hash_table_foreach (method_table, foreach_method, klass);
408         g_hash_table_foreach (field_table, foreach_field, klass);
409 }
410
411 static void
412 foreach_image (const gpointer key, const gpointer val, gpointer user_data)
413 {
414         MonoImage *image = key;
415         const char* aname;
416         aname = mono_image_get_name (image);
417         /* later print the guid as well to prevent mismatches */
418         fprintf (outf, "[%s]\n", aname);
419         g_hash_table_foreach (type_table, foreach_type, image);
420 }
421
422 static void
423 dump_images (const char *filename)
424 {
425         if (filename) {
426                 FILE* f = fopen (filename, "w");
427                 if (!f)
428                         g_error ("cannot write to file '%s'\n", filename);
429                 else
430                         outf = f;
431         } else {
432                 outf = stdout;
433         }
434         g_hash_table_foreach (image_table, foreach_image, NULL);
435         if (filename)
436                 fclose (outf);
437 }
438
439 static void
440 usage (int code) {
441         printf ("monodiet 0.1 Copyright (c) 2004 Novell, Inc\n");
442         printf ("List the metadata elements used by the named assemblies.\n");
443         printf ("Usage: monodiet [options] assembly [assembly2 ...]\n\n");
444         printf ("Options:\n");
445         printf ("\t-v           increase verbose level\n");
446         printf ("\t-h           show help screen\n");
447         printf ("\t-e           force inclusion of enum members\n");
448         printf ("\t-o FILENAME  output the result to filename\n");
449         printf ("\t-a FILENAME  add metadata elements from description in filename\n");
450         printf ("\t-F ASSEMBLY  force add all metadata elements from assembly\n");
451         exit (code);
452 }
453
454 static MonoImage*
455 find_image (char *name)
456 {
457         return mono_image_loaded (name);
458 }
459
460 static MonoClass*
461 find_class (MonoImage *image, char *name)
462 {
463         char *p = strrchr (name, '.');
464         if (p) {
465                 *p = 0;
466                 return mono_class_from_name (image, name, p + 1);
467         }
468         return mono_class_from_name (image, "", name);
469 }
470
471 static void
472 load_roots (const char* filename)
473 {
474         FILE *file;
475         char buf [2048];
476         char *p, *s;
477         int line = 0;
478         MonoImage *image = NULL;
479         MonoClass *klass = NULL;
480         MonoClassField *field;
481         MonoMethodDesc *mdesc;
482         MonoMethod *method;
483
484         if (!(file = fopen (filename, "r")))
485                 return;
486         
487         while (fgets (buf, sizeof (buf), file)) {
488                 /* FIXME:
489                  * decide on the format to use to express types, fields, methods,
490                  * maybe the same used on output from the tool, but with explicit
491                  * names and signatures instead of token indexes
492                  * add wildcard support
493                  */
494                 ++line;
495                 s = buf;
496                 while (*s && g_ascii_isspace (*s)) ++s;
497                 switch (*s) {
498                 case 0:
499                 case '#':
500                         continue; /* comment */
501                 case '[':
502                         p = strchr (s, ']');
503                         if (!p)
504                                 g_error ("invalid assembly format at line %d\n", line);
505                         *p = 0;
506                         p = s + 1;
507                         image = find_image (p);
508                         if (!image)
509                                 g_error ("image not loaded: %s\n", p);
510                         klass = NULL;
511                         break;
512                 case 'T':
513                         if (s [1] != ':')
514                                 g_error ("invalid type format at line %d\n", line);
515                         if (!image)
516                                 break;
517                         klass = find_class (image, s + 2);
518                         break;
519                 case 'F':
520                         if (s [1] != ':')
521                                 g_error ("invalid field format at line %d\n", line);
522                         if (!image || !klass)
523                                 break;
524                         p = s + 2;
525                         if (*p == '*') {
526                                 handle_type (klass, TYPE_FIELDS);
527                                 break;
528                         }
529                         field = mono_class_get_field_from_name (klass, p);
530                         if (!field)
531                                 g_warning ("no field '%s' at line %d\n", p, line);
532                         else
533                                 add_field (field);
534                         break;
535                 case 'M':
536                         if (s [1] != ':')
537                                 g_error ("invalid method format at line %d\n", line);
538                         if (!image || !klass)
539                                 break;
540                         p = s + 2;
541                         if (*p == '*') {
542                                 handle_type (klass, TYPE_METHODS);
543                                 break;
544                         }
545                         mdesc = mono_method_desc_new (p, FALSE);
546                         if (!mdesc) {
547                                 g_error ("invalid method desc at line %d\n", line);
548                         }
549                         method = mono_method_desc_search_in_class (mdesc, klass);
550                         if (!method)
551                                 g_warning ("no method '%s' at line %d\n", p, line);
552                         else
553                                 add_types_from_method (method);
554                         mono_method_desc_free (mdesc);
555                         break;
556                 default:
557                         g_error ("invalid format at line %d\n", line);
558                 }
559         }
560         fclose (file);
561 }
562
563 int
564 main (int argc, char *argv[]) {
565         MonoAssembly *assembly = NULL;
566         const char *aname = NULL;
567         const char *outfile = NULL;
568         const char *rootfile = NULL;
569         int i;
570         gboolean all_meta = FALSE;
571
572         mono_init (argv [0]);
573
574         type_table = g_hash_table_new (NULL, NULL);
575         method_table = g_hash_table_new (NULL, NULL);
576         field_table = g_hash_table_new (NULL, NULL);
577         image_table = g_hash_table_new (NULL, NULL);
578
579         for (i = 1; i < argc; ++i) {
580                 all_meta = FALSE;
581                 aname = argv [i];
582                 if (strcmp (aname, "-v") == 0) {
583                         verbose++;
584                         continue;
585                 } else if (strcmp (aname, "-e") == 0) {
586                         force_enums = 1;
587                         continue;
588                 } else if (strcmp (aname, "-h") == 0) {
589                         usage (0);
590                 } else if (strcmp (aname, "-o") == 0) {
591                         i++;
592                         if (i >= argc)
593                                 usage (1);
594                         outfile = argv [i];
595                         continue;
596                 } else if (strcmp (aname, "-F") == 0) {
597                         i++;
598                         if (i >= argc)
599                                 usage (1);
600                         all_meta = TRUE;
601                         aname = argv [i];
602                 } else if (strcmp (aname, "-a") == 0) {
603                         i++;
604                         if (i >= argc)
605                                 usage (1);
606                         rootfile = argv [i];
607                         continue;
608                 }
609                 assembly = mono_assembly_open (aname, NULL);
610                 if (!assembly) {
611                         g_print ("cannot open assembly %s\n", aname);
612                         exit (1);
613                 }
614                 process_assembly (assembly, all_meta);
615         }
616         if (!assembly)
617                 usage (1);
618         if (rootfile)
619                 load_roots (rootfile);
620         process_images ();
621         dump_images (outfile);
622
623         return 0;
624 }
625
626