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