Wed Feb 20 17:53:59 CET 2002 Paolo Molaro <lupus@ximian.com>
[mono.git] / mono / monograph / monograph.c
1 #include <glib.h>
2 #include <string.h>
3 #include "mono/metadata/class.h"
4 #include "mono/metadata/assembly.h"
5 #include "mono/metadata/tokentype.h"
6 #include "mono/metadata/opcodes.h"
7 #include "mono/metadata/tabledefs.h"
8 #include "mono/metadata/cil-coff.h" /* MonoCLIImageInfo */
9 #include "mono/metadata/mono-endian.h"
10 #include "mono/metadata/appdomain.h" /* mono_init */
11
12 static FILE *output;
13 static int include_namespace = 0;
14 static int max_depth = 6;
15 static int verbose = 0;
16 static char *graph_properties = "\tnode [fontsize=8.0]\n\tedge [len=2,color=red]\n";
17
18 static void
19 output_type_edge (MonoClass *first, MonoClass *second) {
20         if (include_namespace)
21                 fprintf (output, "\t\"%s.%s\" -> \"%s.%s\"\n", first->name_space, first->name, second->name_space, second->name);
22         else
23                 fprintf (output, "\t\"%s\" -> \"%s\"\n", first->name, second->name);
24 }
25
26 static void
27 print_subtypes (MonoImage *image, MonoClass *class, int depth) {
28         int i, index;
29         MonoTableInfo *t;
30         MonoClass *child;
31
32         if (depth++ > max_depth)
33                 return;
34
35         t = &image->tables [MONO_TABLE_TYPEDEF];
36         
37         index = mono_metadata_token_index (class->type_token);
38         index <<= TYPEDEFORREF_BITS;
39         index |= TYPEDEFORREF_TYPEDEF;
40
41         /* use a subgraph? */
42         for (i = 0; i < t->rows; ++i) {
43                 if (index == mono_metadata_decode_row_col (t, i, MONO_TYPEDEF_EXTENDS)) {
44                         child = mono_class_get (image, MONO_TOKEN_TYPE_DEF | (i + 1));
45                         output_type_edge (class, child);
46                         print_subtypes (image, child, depth);
47                 }
48         }
49 }
50
51 static void
52 type_graph (MonoImage *image, char* cname) {
53         MonoClass *class;
54         MonoClass *parent;
55         MonoClass *child;
56         char *name_space;
57         char *p;
58         int depth = 0;
59
60         cname = g_strdup (cname);
61         p = strrchr (cname, '.');
62         if (p) {
63                 name_space = cname;
64                 *p++ = 0;
65                 cname = p;
66         } else {
67                 name_space = "";
68         }
69         class = mono_class_from_name (image, name_space, cname);
70         if (!class)
71                 g_error ("class %s.%s not found", name_space, cname);
72         fprintf (output, "digraph blah {\n");
73         fprintf (output, "%s", graph_properties);
74         child = class;
75         /* go back and print the parents for the node as well: not sure it's a good idea */
76         for (parent = class->parent; parent; parent = parent->parent) {
77                 output_type_edge (parent, child);
78                 child = parent;
79         }
80         print_subtypes (image, class, depth);
81         fprintf (output, "}\n");
82 }
83
84 static void
85 interface_graph (MonoImage *image, char* cname) {
86         MonoClass *class;
87         MonoClass *child;
88         char *name_space;
89         char *p;
90         guint32 cols [MONO_INTERFACEIMPL_SIZE];
91         guint32 index, i, count = 0;
92         MonoTableInfo *intf = &image->tables [MONO_TABLE_INTERFACEIMPL];
93
94         cname = g_strdup (cname);
95         p = strrchr (cname, '.');
96         if (p) {
97                 name_space = cname;
98                 *p++ = 0;
99                 cname = p;
100         } else {
101                 name_space = "";
102         }
103         class = mono_class_from_name (image, name_space, cname);
104         if (!class)
105                 g_error ("interface %s.%s not found", name_space, cname);
106         /* chek if it's really an interface... */
107         fprintf (output, "digraph interface {\n");
108         fprintf (output, "%s", graph_properties);
109         /* TODO: handle inetrface defined in one image and class defined in another. */
110         index = mono_metadata_token_index (class->type_token);
111         index <<= TYPEDEFORREF_BITS;
112         index |= TYPEDEFORREF_TYPEDEF;
113         for (i = 0; i < intf->rows; ++i) {
114                 mono_metadata_decode_row (intf, i, cols, MONO_INTERFACEIMPL_SIZE);
115                 /*g_print ("index: %d [%d implements %d]\n", index, cols [MONO_INTERFACEIMPL_CLASS], cols [MONO_INTERFACEIMPL_INTERFACE]);*/
116                 if (index == cols [MONO_INTERFACEIMPL_INTERFACE]) {
117                         child = mono_class_get (image, MONO_TOKEN_TYPE_DEF | cols [MONO_INTERFACEIMPL_CLASS]);
118                         output_type_edge (class, child);
119                         count++;
120                 }
121         }
122         fprintf (output, "}\n");
123         if (verbose && !count)
124                 g_print ("No class implements %s.%s\n", class->name_space, class->name);
125
126 }
127
128 static void
129 get_type (GString *res, MonoType *type) {
130         switch (type->type) {
131         case MONO_TYPE_VOID:
132                 g_string_append (res, "void"); break;
133         case MONO_TYPE_CHAR:
134                 g_string_append (res, "char"); break;
135         case MONO_TYPE_BOOLEAN:
136                 g_string_append (res, "bool"); break;
137         case MONO_TYPE_U1:
138                 g_string_append (res, "byte"); break;
139         case MONO_TYPE_I1:
140                 g_string_append (res, "sbyte"); break;
141         case MONO_TYPE_U2:
142                 g_string_append (res, "uint16"); break;
143         case MONO_TYPE_I2:
144                 g_string_append (res, "int16"); break;
145         case MONO_TYPE_U4:
146                 g_string_append (res, "int"); break;
147         case MONO_TYPE_I4:
148                 g_string_append (res, "uint"); break;
149         case MONO_TYPE_U8:
150                 g_string_append (res, "ulong"); break;
151         case MONO_TYPE_I8:
152                 g_string_append (res, "long"); break;
153         case MONO_TYPE_FNPTR: /* who cares for the exact signature? */
154                 g_string_append (res, "*()"); break;
155         case MONO_TYPE_U:
156                 g_string_append (res, "intptr"); break;
157         case MONO_TYPE_I:
158                 g_string_append (res, "uintptr"); break;
159         case MONO_TYPE_R4:
160                 g_string_append (res, "single"); break;
161         case MONO_TYPE_R8:
162                 g_string_append (res, "double"); break;
163         case MONO_TYPE_STRING:
164                 g_string_append (res, "string"); break;
165         case MONO_TYPE_OBJECT:
166                 g_string_append (res, "object"); break;
167         case MONO_TYPE_PTR:
168                 get_type (res, type->data.type);
169                 g_string_append_c (res, '*');
170                 break;
171         case MONO_TYPE_ARRAY:
172                 get_type (res, type->data.array->type);
173                 g_string_append (res, "[,]"); /* not the full array info.. */
174                 break;
175         case MONO_TYPE_SZARRAY:
176                 get_type (res, type->data.type);
177                 g_string_append (res, "[]");
178                 break;
179         case MONO_TYPE_CLASS:
180         case MONO_TYPE_VALUETYPE: {
181                 MonoClass *class = type->data.klass;
182                 if (!class) {
183                         g_string_append (res, "Unknown");
184                         break;
185                 }
186                 if (include_namespace && *(class->name_space))
187                         g_string_sprintfa (res, "%s.", class->name_space);
188                 g_string_sprintfa (res, "%s", class->name);
189                 break;
190         }
191         default:
192                 break;
193         }
194         if (type->byref)
195                 g_string_append_c (res, '&');
196 }
197
198 static char *
199 get_signature (MonoMethod *method) {
200         GString *res;
201         static GHashTable *hash = NULL;
202         char *result;
203         int i;
204
205         if (!hash)
206                 hash = g_hash_table_new (g_direct_hash, g_direct_equal);
207         if ((result = g_hash_table_lookup (hash, method)))
208                 return result;
209
210         res = g_string_new ("");
211         if (include_namespace && *(method->klass->name_space))
212                 g_string_sprintfa (res, "%s.", method->klass->name_space);
213         g_string_sprintfa (res, "%s:%s(", method->klass->name, method->name);
214         for (i = 0; i < method->signature->param_count; ++i) {
215                 if (i > 0)
216                         g_string_append_c (res, ',');
217                 get_type (res, method->signature->params [i]);
218         }
219         g_string_sprintfa (res, ")");
220         g_hash_table_insert (hash, method, res->str);
221
222         result = res->str;
223         g_string_free (res, FALSE);
224         return result;
225                 
226 }
227
228 static void
229 output_method_edge (MonoMethod *first, MonoMethod *second) {
230         char * f = get_signature (first);
231         char * s = get_signature (second);
232         
233         fprintf (output, "\t\"%s\" -> \"%s\"\n", f, s);
234 }
235
236 /*
237  * We need to handle virtual calls is derived types.
238  * We could check what types implement the method and
239  * disassemble all of them: this can make the graph to explode.
240  * We could try and keep track of the 'this' pointer type and
241  * consider only the methods in the classes derived from that:
242  * this can reduce the graph complexity somewhat (and it would 
243  * be the more correct approach).
244  */
245 static void
246 print_method (MonoMethod *method, int depth) {
247         const MonoOpcode *opcode;
248         MonoMethodHeader *header;
249         GHashTable *hash;
250         const unsigned char *ip;
251         int i;
252
253         if (depth++ > max_depth)
254                 return;
255         if (method->info) /* avoid recursion */
256                 return;
257         method->info = method;
258
259         if (method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME))
260                 return;
261         if (method->flags & (METHOD_ATTRIBUTE_PINVOKE_IMPL | METHOD_ATTRIBUTE_ABSTRACT))
262                 return;
263
264         header = ((MonoMethodNormal *)method)->header;
265         ip = header->code;
266
267         hash = g_hash_table_new (g_direct_hash, g_direct_equal);
268         
269         while (ip < (header->code + header->code_size)) {
270                 if (*ip == 0xfe) {
271                         ++ip;
272                         i = *ip + 256;
273                 } else {
274                         i = *ip;
275                 }
276
277                 opcode = &mono_opcodes [i];
278
279                 switch (opcode->argument) {
280                 case MonoInlineNone:
281                         ++ip;
282                         break;
283                 case MonoInlineType:
284                 case MonoInlineField:
285                 case MonoInlineTok:
286                 case MonoInlineString:
287                 case MonoInlineSig:
288                 case MonoShortInlineR:
289                 case MonoInlineI:
290                 case MonoInlineBrTarget:
291                         ip += 5;
292                         break;
293                 case MonoInlineVar:
294                         ip += 3;
295                         break;
296                 case MonoShortInlineVar:
297                 case MonoShortInlineI:
298                 case MonoShortInlineBrTarget:
299                         ip += 2;
300                         break;
301                 case MonoInlineSwitch: {
302                         gint32 n;
303                         ++ip;
304                         n = read32 (ip);
305                         ip += 4;
306                         ip += 4 * n;
307                         break;
308                 }
309                 case MonoInlineR:
310                 case MonoInlineI8:
311                         ip += 9;
312                         break;
313                 case MonoInlineMethod: {
314                         guint32 token;
315                         MonoMethod *called;
316                         ip++;
317                         token = read32 (ip);
318                         ip += 4;
319                         called = mono_get_method (method->klass->image, token, NULL);
320                         if (!called)
321                                 break; /* warn? */
322                         if (g_hash_table_lookup (hash, called))
323                                 break;
324                         g_hash_table_insert (hash, called, called);
325                         output_method_edge (method, called);
326                         print_method (called, depth);
327                         break;
328                 }
329                 default:
330                         g_assert_not_reached ();
331                 }
332         }
333         g_hash_table_destroy (hash);
334 }
335
336 static void
337 method_graph (MonoImage *image, char *name) {
338         int depth = 0;
339         MonoMethod *method = NULL;
340         
341         if (!name) {
342                 method = mono_get_method (image, ((MonoCLIImageInfo*)image->image_info)->cli_cli_header.ch_entry_point, NULL);
343                 if (!method)
344                         g_error ("Cannot find entry point");
345         } else {
346                 /* search the method */
347                 MonoTableInfo *tdef = &image->tables [MONO_TABLE_TYPEDEF];
348                 MonoTableInfo *methods = &image->tables [MONO_TABLE_METHOD];
349                 char *class_name, *class_nspace, *method_name, *use_args;
350                 int use_namespace, i;
351                 
352                 class_nspace = g_strdup (name);
353                 use_args = strchr (class_nspace, '(');
354                 if (use_args)
355                         *use_args++ = 0;
356                 method_name = strrchr (class_nspace, ':');
357                 if (!method_name)
358                         g_error ("Invalid method name %s", name);
359                 *method_name++ = 0;
360                 class_name = strrchr (class_nspace, '.');
361                 if (class_name) {
362                         *class_name++ = 0;
363                         use_namespace = 1;
364                 } else {
365                         class_name = class_nspace;
366                         use_namespace = 0;
367                 }
368                 for (i = 0; i < methods->rows; ++i) {
369                         guint32 index = mono_metadata_decode_row_col (methods, i, MONO_METHOD_NAME);
370                         guint32 idx;
371                         const char *n = mono_metadata_string_heap (image, index);
372
373                         if (strcmp (n, method_name))
374                                 continue;
375                         index = mono_metadata_typedef_from_method (image, i + 1);
376                         idx = mono_metadata_decode_row_col (tdef, index - 1, MONO_TYPEDEF_NAME);
377                         n = mono_metadata_string_heap (image, idx);
378                         if (strcmp (n, class_name))
379                                 continue;
380                         if (use_namespace) {
381                                 idx = mono_metadata_decode_row_col (tdef, index - 1, MONO_TYPEDEF_NAMESPACE);
382                                 n = mono_metadata_string_heap (image, idx);
383                                 if (strcmp (n, class_nspace))
384                                         continue;
385                         }
386                         method = mono_get_method (image, MONO_TOKEN_METHOD_DEF | (i + 1), NULL);
387                         if (use_args) {
388                                 /* check the signature */
389                                 n = get_signature (method);
390                                 if (strcmp (n, name) == 0)
391                                         break;
392                                 if (verbose)
393                                         g_print ("signature check failed: '%s' != '%s'.\n", n, name);
394                                 method = NULL;
395                         }
396                 }
397                 if (!method)
398                         g_error ("Cannot find method %s", name);
399                 g_free (class_nspace);
400         }
401         fprintf (output, "digraph blah {\n");
402         fprintf (output, "%s", graph_properties);
403
404         print_method (method, depth);
405         
406         fprintf (output, "}\n");
407 }
408
409 static void
410 usage () {
411         printf ("monograph 0.1 Copyright (c) 2002 Ximian, Inc\n");
412         printf ("Create call graph or type hierarchy information from CIL assemblies.\n");
413         printf ("Usage: monograph [options] [assembly [typename|methodname]]\n");
414         printf ("Valid options are:\n");
415         printf ("\t-c|--call             output call graph instead of type hierarchy\n");
416         printf ("\t-d|--depth num        max depth recursion (default: 6)\n");
417         printf ("\t-o|--output filename  write graph to file filename (default: stdout)\n");
418         printf ("\t-f|--fullname         include namespace in type and method names\n");
419         printf ("\t-n|--neato            invoke neato directly\n");
420         printf ("\t-v|--verbose          verbose operation\n");
421         printf ("The default assembly is corlib.dll. The default method for\n");
422         printf ("the --call option is the entrypoint.\n");
423         printf ("When the --neato option is used the output type info is taken\n");
424         printf ("from the output filename extension.\n");
425         printf ("Sample runs:\n");
426         printf ("\tmonograph -n -o vt.png corlib.dll System.ValueType\n");
427         printf ("\tmonograph -n -o expr.png mcs.exe Mono.CSharp.Expression\n");
428         printf ("\tmonograph -d 3 -n -o callgraph.png -c mis.exe\n");
429         exit (1);
430 }
431
432 enum {
433         GRAPH_TYPES,
434         GRAPH_CALL,
435         GRAPH_INTERFACE
436 };
437
438 /*
439  * TODO:
440  * * virtual method calls as explained above
441  * * maybe track field references
442  * * track what exceptions a method could throw?
443  * * for some inputs neato appears to hang or take a long time: kill it?
444  * * allow passing additional command-line arguments to neato
445  * * allow setting different graph/node/edge options directly
446  * * option to avoid specialname methods
447  * * make --neato option the default?
448  * * use multiple classes/method roots?
449  * * write a manpage
450  * * reverse call graph: given a method what methods call it?
451  */
452 int
453 main (int argc, char *argv[]) {
454         MonoAssembly *assembly;
455         char *cname = NULL;
456         char *aname = NULL;
457         char *outputfile = NULL;
458         int graphtype = GRAPH_TYPES;
459         int callneato = 0;
460         int i;
461         
462         mono_init (argv [0]);
463         output = stdout;
464
465         for (i = 1; i < argc; ++i) {
466                 if (argv [i] [0] != '-')
467                         break;
468                 if (strcmp (argv [i], "--call") == 0 || strcmp (argv [i], "-c") == 0) {
469                         graphtype = GRAPH_CALL;
470                 } else if (strcmp (argv [i], "--interface") == 0 || strcmp (argv [i], "-i") == 0) {
471                         graphtype = GRAPH_INTERFACE;
472                 } else if (strcmp (argv [i], "--fullname") == 0 || strcmp (argv [i], "-f") == 0) {
473                         include_namespace = 1;
474                 } else if (strcmp (argv [i], "--neato") == 0 || strcmp (argv [i], "-n") == 0) {
475                         callneato = 1;
476                 } else if (strcmp (argv [i], "--verbose") == 0 || strcmp (argv [i], "-v") == 0) {
477                         verbose++;
478                 } else if (strcmp (argv [i], "--output") == 0 || strcmp (argv [i], "-o") == 0) {
479                         if (i + 1 >= argc)
480                                 usage ();
481                         outputfile = argv [++i];
482                 } else if (strcmp (argv [i], "--depth") == 0 || strcmp (argv [i], "-d") == 0) {
483                         if (i + 1 >= argc)
484                                 usage ();
485                         max_depth = atoi (argv [++i]);
486                 } else {
487                         usage ();
488                 }
489                 
490         }
491         if (argc > i)
492                 aname = argv [i];
493         if (argc > i + 1)
494                 cname = argv [i + 1];
495         if (!aname)
496                 aname = "corlib.dll";
497         if (!cname && (graphtype != GRAPH_CALL))
498                 cname = "System.Object";
499
500         assembly = mono_assembly_open (aname, NULL, NULL);
501         if (!assembly)
502                 g_error ("cannot open assembly %s", aname);
503
504         if (callneato) {
505                 GString *command = g_string_new ("neato");
506                 char *type = NULL;
507
508                 if (outputfile) {
509                         type = strrchr (outputfile, '.');
510                         g_string_sprintfa (command, " -o %s", outputfile);
511                 }
512                 if (type)
513                         g_string_sprintfa (command, " -T%s", type + 1);
514                 output = popen (command->str, "w");
515                 if (!output)
516                         g_error ("Cannot run neato");
517         } else if (outputfile) {
518                 output = fopen (outputfile, "w");
519                 if (!output)
520                         g_error ("Cannot open file: %s", outputfile);
521         }
522         /* if it looks like a method name, we want a callgraph. */
523         if (cname && strchr (cname, ':'))
524                 graphtype = GRAPH_CALL;
525
526         switch (graphtype) {
527         case GRAPH_TYPES:
528                 type_graph (assembly->image, cname);
529                 break;
530         case GRAPH_CALL:
531                 method_graph (assembly->image, cname);
532                 break;
533         case GRAPH_INTERFACE:
534                 interface_graph (assembly->image, cname);
535                 break;
536         default:
537                 g_error ("wrong graph type");
538         }
539         
540         if (callneato) {
541                 if (verbose)
542                         g_print ("waiting for neato.\n");
543                 pclose (output);
544         } else if (outputfile)
545                 fclose (output);
546         return 0;
547 }
548
549