de8f5207b0afde32a72ae0666d796726ad1ce4fa
[mono.git] / mono / profiler / coverage.c
1 /*
2  * coverage.c: mono coverage profiler
3  *
4  * Authors:
5  *   Paolo Molaro (lupus@ximian.com)
6  *   Alex Rønne Petersen (alexrp@xamarin.com)
7  *   Ludovic Henry (ludovic@xamarin.com)
8  *
9  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
10  */
11
12 /*
13  * The Coverage XML output schema
14  * <coverage>
15  *   <assembly/>
16  *   <class/>
17  *   <method>
18  *     <statement/>
19  *   </method>
20  * </coverage>
21  *
22  * Elements:
23  *   <coverage> - The root element of the documentation. It can contain any number of
24  *                <assembly>, <class> or <method> elements.
25  *                Attributes:
26  *                   - version: The version number for the file format - (eg: "0.3")
27  *   <assembly> - Contains data about assemblies. Has no child elements
28  *                Attributes:
29  *                   - name: The name of the assembly - (eg: "System.Xml")
30  *                   - guid: The GUID of the assembly
31  *                   - filename: The filename of the assembly
32  *                   - method-count: The number of methods in the assembly
33  *                   - full: The number of fully covered methods
34  *                   - partial: The number of partially covered methods
35  *   <class> - Contains data about classes. Has no child elements
36  *             Attributes:
37  *                - name: The name of the class
38  *                - method-count: The number of methods in the class
39  *                - full: The number of fully covered methods
40  *                - partial: The number of partially covered methods
41  *   <method> - Contains data about methods. Can contain any number of <statement> elements
42  *              Attributes:
43  *                 - assembly: The name of the parent assembly
44  *                 - class: The name of the parent class
45  *                 - name: The name of the method, with all it's parameters
46  *                 - filename: The name of the source file containing this method
47  *                 - token
48  *   <statement> - Contains data about IL statements. Has no child elements
49  *                 Attributes:
50  *                    - offset: The offset of the statement in the IL code
51  *                    - counter: 1 if the line was covered, 0 if it was not
52  *                    - line: The line number in the parent method's file
53  *                    - column: The column on the line
54  */
55
56 #include <config.h>
57 #include <glib.h>
58
59 #include <stdio.h>
60
61 #ifdef HAVE_DLFCN_H
62 #include <dlfcn.h>
63 #endif
64 #include <fcntl.h>
65 #ifdef HAVE_UNISTD_H
66 #include <unistd.h>
67 #endif
68 #ifdef HAVE_SYS_MMAN_H
69 #include <sys/mman.h>
70 #endif
71 #if defined (HAVE_SYS_ZLIB)
72 #include <zlib.h>
73 #endif
74
75 #include <mono/metadata/assembly.h>
76 #include <mono/metadata/debug-helpers.h>
77 #include <mono/metadata/profiler.h>
78 #include <mono/metadata/tabledefs.h>
79 #include <mono/metadata/metadata-internals.h>
80
81 #include <mono/utils/atomic.h>
82 #include <mono/utils/hazard-pointer.h>
83 #include <mono/utils/lock-free-queue.h>
84 #include <mono/utils/mono-conc-hashtable.h>
85 #include <mono/utils/mono-os-mutex.h>
86 #include <mono/utils/mono-logger-internals.h>
87 #include <mono/utils/mono-counters.h>
88
89 // Statistics for profiler events.
90 static gint32 coverage_methods_ctr,
91               coverage_statements_ctr,
92               coverage_classes_ctr,
93               coverage_assemblies_ctr;
94
95 struct _MonoProfiler {
96         MonoProfilerHandle handle;
97
98         FILE* file;
99
100         char *args;
101
102         mono_mutex_t mutex;
103         GPtrArray *data;
104
105         GPtrArray *filters;
106         MonoConcurrentHashTable *filtered_classes;
107         MonoConcurrentHashTable *suppressed_assemblies;
108
109         MonoConcurrentHashTable *methods;
110         MonoConcurrentHashTable *assemblies;
111         MonoConcurrentHashTable *classes;
112
113         MonoConcurrentHashTable *image_to_methods;
114
115         guint32 previous_offset;
116 };
117
118 typedef struct {
119         //Where to compress the output file
120         gboolean use_zip;
121
122         //Name of the generated xml file
123         const char *output_filename;
124
125         //Filter files used by the code coverage mode
126         GPtrArray *cov_filter_files;
127 } ProfilerConfig;
128
129 static ProfilerConfig coverage_config;
130 static struct _MonoProfiler coverage_profiler;
131
132 /* This is a very basic escape function that escapes < > and &
133    Ideally we'd use g_markup_escape_string but that function isn't
134          available in Mono's eglib. This was written without looking at the
135          source of that function in glib. */
136 static char *
137 escape_string_for_xml (const char *string)
138 {
139         GString *string_builder = g_string_new (NULL);
140         const char *start, *p;
141
142         start = p = string;
143         while (*p) {
144                 while (*p && *p != '&' && *p != '<' && *p != '>')
145                         p++;
146
147                 g_string_append_len (string_builder, start, p - start);
148
149                 if (*p == '\0')
150                         break;
151
152                 switch (*p) {
153                 case '<':
154                         g_string_append (string_builder, "&lt;");
155                         break;
156
157                 case '>':
158                         g_string_append (string_builder, "&gt;");
159                         break;
160
161                 case '&':
162                         g_string_append (string_builder, "&amp;");
163                         break;
164
165                 default:
166                         break;
167                 }
168
169                 p++;
170                 start = p;
171         }
172
173         return g_string_free (string_builder, FALSE);
174 }
175
176 typedef struct {
177         MonoLockFreeQueueNode node;
178         MonoMethod *method;
179 } MethodNode;
180
181 typedef struct {
182         int offset;
183         int counter;
184         char *filename;
185         int line;
186         int column;
187 } CoverageEntry;
188
189 static void
190 free_coverage_entry (gpointer data, gpointer userdata)
191 {
192         CoverageEntry *entry = (CoverageEntry *)data;
193         g_free (entry->filename);
194         g_free (entry);
195 }
196
197 static void
198 obtain_coverage_for_method (MonoProfiler *prof, const MonoProfilerCoverageData *entry)
199 {
200         g_assert (prof == &coverage_profiler);
201
202         CoverageEntry *e = g_new (CoverageEntry, 1);
203
204         coverage_profiler.previous_offset = entry->il_offset;
205
206         e->offset = entry->il_offset;
207         e->counter = entry->counter;
208         e->filename = g_strdup(entry->file_name ? entry->file_name : "");
209         e->line = entry->line;
210         e->column = entry->column;
211
212         g_ptr_array_add (coverage_profiler.data, e);
213 }
214
215 static char *
216 parse_generic_type_names(char *name)
217 {
218         char *new_name, *ret;
219         int within_generic_declaration = 0, generic_members = 1;
220
221         if (name == NULL || *name == '\0')
222                 return g_strdup ("");
223
224         if (!(ret = new_name = (char *) g_calloc (strlen (name) * 4 + 1, sizeof (char))))
225                 return NULL;
226
227         do {
228                 switch (*name) {
229                         case '<':
230                                 within_generic_declaration = 1;
231                                 break;
232
233                         case '>':
234                                 within_generic_declaration = 0;
235
236                                 if (*(name - 1) != '<') {
237                                         *new_name++ = '`';
238                                         *new_name++ = '0' + generic_members;
239                                 } else {
240                                         memcpy (new_name, "&lt;&gt;", 8);
241                                         new_name += 8;
242                                 }
243
244                                 generic_members = 0;
245                                 break;
246
247                         case ',':
248                                 generic_members++;
249                                 break;
250
251                         default:
252                                 if (!within_generic_declaration)
253                                         *new_name++ = *name;
254
255                                 break;
256                 }
257         } while (*name++);
258
259         return ret;
260 }
261
262 static void
263 dump_method (gpointer key, gpointer value, gpointer userdata)
264 {
265         MonoMethod *method = (MonoMethod *)value;
266         MonoClass *klass;
267         MonoImage *image;
268         char *class_name, *escaped_image_name, *escaped_class_name, *escaped_method_name, *escaped_method_signature, *escaped_method_filename;
269         const char *image_name, *method_name, *method_signature, *method_filename;
270         guint i;
271
272         coverage_profiler.previous_offset = 0;
273         coverage_profiler.data = g_ptr_array_new ();
274
275         mono_profiler_get_coverage_data (coverage_profiler.handle, method, obtain_coverage_for_method);
276
277         klass = mono_method_get_class (method);
278         image = mono_class_get_image (klass);
279         image_name = mono_image_get_name (image);
280
281         method_signature = mono_signature_get_desc (mono_method_signature (method), TRUE);
282         class_name = parse_generic_type_names (mono_type_get_name (mono_class_get_type (klass)));
283         method_name = mono_method_get_name (method);
284
285         if (coverage_profiler.data->len != 0) {
286                 CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[0];
287                 method_filename = entry->filename ? entry->filename : "";
288         } else
289                 method_filename = "";
290
291         image_name = image_name ? image_name : "";
292         method_signature = method_signature ? method_signature : "";
293         method_name = method_name ? method_name : "";
294
295         escaped_image_name = escape_string_for_xml (image_name);
296         escaped_class_name = escape_string_for_xml (class_name);
297         escaped_method_name = escape_string_for_xml (method_name);
298         escaped_method_signature = escape_string_for_xml (method_signature);
299         escaped_method_filename = escape_string_for_xml (method_filename);
300
301         fprintf (coverage_profiler.file, "\t<method assembly=\"%s\" class=\"%s\" name=\"%s (%s)\" filename=\"%s\" token=\"%d\">\n",
302                 escaped_image_name, escaped_class_name, escaped_method_name, escaped_method_signature, escaped_method_filename, mono_method_get_token (method));
303
304         g_free (escaped_image_name);
305         g_free (escaped_class_name);
306         g_free (escaped_method_name);
307         g_free (escaped_method_signature);
308         g_free (escaped_method_filename);
309
310         for (i = 0; i < coverage_profiler.data->len; i++) {
311                 CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[i];
312
313                 fprintf (coverage_profiler.file, "\t\t<statement offset=\"%d\" counter=\"%d\" line=\"%d\" column=\"%d\"/>\n",
314                         entry->offset, entry->counter, entry->line, entry->column);
315         }
316
317         fprintf (coverage_profiler.file, "\t</method>\n");
318
319         g_free (class_name);
320
321         g_ptr_array_foreach (coverage_profiler.data, free_coverage_entry, NULL);
322         g_ptr_array_free (coverage_profiler.data, TRUE);
323 }
324
325 /* This empties the queue */
326 static guint
327 count_queue (MonoLockFreeQueue *queue)
328 {
329         MonoLockFreeQueueNode *node;
330         guint count = 0;
331
332         while ((node = mono_lock_free_queue_dequeue (queue))) {
333                 count++;
334                 mono_thread_hazardous_try_free (node, g_free);
335         }
336
337         return count;
338 }
339
340 static void
341 dump_classes_for_image (gpointer key, gpointer value, gpointer userdata)
342 {
343         MonoClass *klass = (MonoClass *)key;
344         MonoLockFreeQueue *class_methods = (MonoLockFreeQueue *)value;
345         MonoImage *image;
346         char *class_name, *escaped_class_name;
347         const char *image_name;
348         int number_of_methods, partially_covered;
349         guint fully_covered;
350
351         image = mono_class_get_image (klass);
352         image_name = mono_image_get_name (image);
353
354         if (!image_name || strcmp (image_name, mono_image_get_name (((MonoImage*) userdata))) != 0)
355                 return;
356
357         class_name = mono_type_get_name (mono_class_get_type (klass));
358
359         number_of_methods = mono_class_num_methods (klass);
360         fully_covered = count_queue (class_methods);
361         /* We don't handle partial covered yet */
362         partially_covered = 0;
363
364         escaped_class_name = escape_string_for_xml (class_name);
365
366         fprintf (coverage_profiler.file, "\t<class name=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n",
367                 escaped_class_name, number_of_methods, fully_covered, partially_covered);
368
369         g_free (escaped_class_name);
370
371         g_free (class_name);
372
373 }
374
375 static void
376 get_coverage_for_image (MonoImage *image, int *number_of_methods, guint *fully_covered, int *partially_covered)
377 {
378         MonoLockFreeQueue *image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image);
379
380         *number_of_methods = mono_image_get_table_rows (image, MONO_TABLE_METHOD);
381         if (image_methods)
382                 *fully_covered = count_queue (image_methods);
383         else
384                 *fully_covered = 0;
385
386         // FIXME: We don't handle partially covered yet.
387         *partially_covered = 0;
388 }
389
390 static void
391 dump_assembly (gpointer key, gpointer value, gpointer userdata)
392 {
393         MonoAssembly *assembly = (MonoAssembly *)value;
394         MonoImage *image = mono_assembly_get_image (assembly);
395         const char *image_name, *image_guid, *image_filename;
396         char *escaped_image_name, *escaped_image_filename;
397         int number_of_methods = 0, partially_covered = 0;
398         guint fully_covered = 0;
399
400         image_name = mono_image_get_name (image);
401         image_guid = mono_image_get_guid (image);
402         image_filename = mono_image_get_filename (image);
403
404         image_name = image_name ? image_name : "";
405         image_guid = image_guid ? image_guid : "";
406         image_filename = image_filename ? image_filename : "";
407
408         get_coverage_for_image (image, &number_of_methods, &fully_covered, &partially_covered);
409
410         escaped_image_name = escape_string_for_xml (image_name);
411         escaped_image_filename = escape_string_for_xml (image_filename);
412
413         fprintf (coverage_profiler.file, "\t<assembly name=\"%s\" guid=\"%s\" filename=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n",
414                 escaped_image_name, image_guid, escaped_image_filename, number_of_methods, fully_covered, partially_covered);
415
416         g_free (escaped_image_name);
417         g_free (escaped_image_filename);
418
419         mono_conc_hashtable_foreach (coverage_profiler.classes, dump_classes_for_image, image);
420 }
421
422 static void
423 dump_coverage (void)
424 {
425         fprintf (coverage_profiler.file, "<?xml version=\"1.0\"?>\n");
426         fprintf (coverage_profiler.file, "<coverage version=\"0.3\">\n");
427
428         mono_os_mutex_lock (&coverage_profiler.mutex);
429         mono_conc_hashtable_foreach (coverage_profiler.assemblies, dump_assembly, NULL);
430         mono_conc_hashtable_foreach (coverage_profiler.methods, dump_method, NULL);
431         mono_os_mutex_unlock (&coverage_profiler.mutex);
432
433         fprintf (coverage_profiler.file, "</coverage>\n");
434 }
435
436 static MonoLockFreeQueueNode *
437 create_method_node (MonoMethod *method)
438 {
439         MethodNode *node = (MethodNode *) g_malloc (sizeof (MethodNode));
440         mono_lock_free_queue_node_init ((MonoLockFreeQueueNode *) node, FALSE);
441         node->method = method;
442
443         return (MonoLockFreeQueueNode *) node;
444 }
445
446 static gboolean
447 coverage_filter (MonoProfiler *prof, MonoMethod *method)
448 {
449         MonoError error;
450         MonoClass *klass;
451         MonoImage *image;
452         MonoAssembly *assembly;
453         MonoMethodHeader *header;
454         guint32 iflags, flags, code_size;
455         char *fqn, *classname;
456         gboolean has_positive, found;
457         MonoLockFreeQueue *image_methods, *class_methods;
458         MonoLockFreeQueueNode *node;
459
460         g_assert (prof == &coverage_profiler);
461
462         flags = mono_method_get_flags (method, &iflags);
463         if ((iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) ||
464             (flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
465                 return FALSE;
466
467         // Don't need to do anything else if we're already tracking this method
468         if (mono_conc_hashtable_lookup (coverage_profiler.methods, method))
469                 return TRUE;
470
471         klass = mono_method_get_class (method);
472         image = mono_class_get_image (klass);
473
474         // Don't handle coverage for the core assemblies
475         if (mono_conc_hashtable_lookup (coverage_profiler.suppressed_assemblies, (gpointer) mono_image_get_name (image)) != NULL)
476                 return FALSE;
477
478         if (coverage_profiler.filters) {
479                 /* Check already filtered classes first */
480                 if (mono_conc_hashtable_lookup (coverage_profiler.filtered_classes, klass))
481                         return FALSE;
482
483                 classname = mono_type_get_name (mono_class_get_type (klass));
484
485                 fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname);
486
487                 // Check positive filters first
488                 has_positive = FALSE;
489                 found = FALSE;
490                 for (guint i = 0; i < coverage_profiler.filters->len; ++i) {
491                         char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i);
492
493                         if (filter [0] == '+') {
494                                 filter = &filter [1];
495
496                                 if (strstr (fqn, filter) != NULL)
497                                         found = TRUE;
498
499                                 has_positive = TRUE;
500                         }
501                 }
502
503                 if (has_positive && !found) {
504                         mono_os_mutex_lock (&coverage_profiler.mutex);
505                         mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass);
506                         mono_os_mutex_unlock (&coverage_profiler.mutex);
507                         g_free (fqn);
508                         g_free (classname);
509
510                         return FALSE;
511                 }
512
513                 for (guint i = 0; i < coverage_profiler.filters->len; ++i) {
514                         // FIXME: Is substring search sufficient?
515                         char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i);
516                         if (filter [0] == '+')
517                                 continue;
518
519                         // Skip '-'
520                         filter = &filter [1];
521
522                         if (strstr (fqn, filter) != NULL) {
523                                 mono_os_mutex_lock (&coverage_profiler.mutex);
524                                 mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass);
525                                 mono_os_mutex_unlock (&coverage_profiler.mutex);
526                                 g_free (fqn);
527                                 g_free (classname);
528
529                                 return FALSE;
530                         }
531                 }
532
533                 g_free (fqn);
534                 g_free (classname);
535         }
536
537         header = mono_method_get_header_checked (method, &error);
538         mono_error_cleanup (&error);
539
540         mono_method_header_get_code (header, &code_size, NULL);
541
542         assembly = mono_image_get_assembly (image);
543
544         // Need to keep the assemblies around for as long as they are kept in the hashtable
545         // Nunit, for example, has a habit of unloading them before the coverage statistics are
546         // generated causing a crash. See https://bugzilla.xamarin.com/show_bug.cgi?id=39325
547         mono_assembly_addref (assembly);
548
549         mono_os_mutex_lock (&coverage_profiler.mutex);
550         mono_conc_hashtable_insert (coverage_profiler.methods, method, method);
551         mono_conc_hashtable_insert (coverage_profiler.assemblies, assembly, assembly);
552         mono_os_mutex_unlock (&coverage_profiler.mutex);
553
554         image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image);
555
556         if (image_methods == NULL) {
557                 image_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue));
558                 mono_lock_free_queue_init (image_methods);
559                 mono_os_mutex_lock (&coverage_profiler.mutex);
560                 mono_conc_hashtable_insert (coverage_profiler.image_to_methods, image, image_methods);
561                 mono_os_mutex_unlock (&coverage_profiler.mutex);
562         }
563
564         node = create_method_node (method);
565         mono_lock_free_queue_enqueue (image_methods, node);
566
567         class_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.classes, klass);
568
569         if (class_methods == NULL) {
570                 class_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue));
571                 mono_lock_free_queue_init (class_methods);
572                 mono_os_mutex_lock (&coverage_profiler.mutex);
573                 mono_conc_hashtable_insert (coverage_profiler.classes, klass, class_methods);
574                 mono_os_mutex_unlock (&coverage_profiler.mutex);
575         }
576
577         node = create_method_node (method);
578         mono_lock_free_queue_enqueue (class_methods, node);
579
580         return TRUE;
581 }
582
583 #define LINE_BUFFER_SIZE 4096
584 /* Max file limit of 128KB */
585 #define MAX_FILE_SIZE 128 * 1024
586 static char *
587 get_file_content (const gchar *filename)
588 {
589         char *buffer;
590         ssize_t bytes_read;
591         long filesize;
592         int res, offset = 0;
593         FILE *stream;
594
595         stream = fopen (filename, "r");
596         if (stream == NULL)
597                 return NULL;
598
599         res = fseek (stream, 0, SEEK_END);
600         if (res < 0) {
601                 fclose (stream);
602                 return NULL;
603         }
604
605         filesize = ftell (stream);
606         if (filesize < 0) {
607                 fclose (stream);
608                 return NULL;
609         }
610
611         res = fseek (stream, 0, SEEK_SET);
612         if (res < 0) {
613                 fclose (stream);
614                 return NULL;
615         }
616
617         if (filesize > MAX_FILE_SIZE) {
618                 fclose (stream);
619                 return NULL;
620         }
621
622         buffer = (char *) g_malloc ((filesize + 1) * sizeof (char));
623         while ((bytes_read = fread (buffer + offset, 1, LINE_BUFFER_SIZE, stream)) > 0)
624                 offset += bytes_read;
625
626         /* NULL terminate our buffer */
627         buffer[filesize] = '\0';
628
629         fclose (stream);
630         return buffer;
631 }
632
633 static char *
634 get_next_line (char *contents, char **next_start)
635 {
636         char *p = contents;
637
638         if (p == NULL || *p == '\0') {
639                 *next_start = NULL;
640                 return NULL;
641         }
642
643         while (*p != '\n' && *p != '\0')
644                 p++;
645
646         if (*p == '\n') {
647                 *p = '\0';
648                 *next_start = p + 1;
649         } else
650                 *next_start = NULL;
651
652         return contents;
653 }
654
655 static void
656 init_suppressed_assemblies (void)
657 {
658         char *content;
659         char *line;
660
661         coverage_profiler.suppressed_assemblies = mono_conc_hashtable_new (g_str_hash, g_str_equal);
662
663         /* Don't need to free content as it is referred to by the lines stored in @filters */
664         content = get_file_content (SUPPRESSION_DIR "/mono-profiler-coverage.suppression");
665         if (content == NULL)
666                 content = get_file_content (SUPPRESSION_DIR "/mono-profiler-log.suppression");
667         if (content == NULL)
668                 return;
669
670         while ((line = get_next_line (content, &content))) {
671                 line = g_strchomp (g_strchug (line));
672                 /* No locking needed as we're doing initialization */
673                 mono_conc_hashtable_insert (coverage_profiler.suppressed_assemblies, line, line);
674         }
675 }
676
677 static void
678 parse_cov_filter_file (GPtrArray *filters, const char *file)
679 {
680         char *content;
681         char *line;
682
683         /* Don't need to free content as it is referred to by the lines stored in @filters */
684         content = get_file_content (file);
685         if (content == NULL) {
686                 mono_profiler_printf_err ("Could not open coverage filter file '%s'.", file);
687                 return;
688         }
689
690         while ((line = get_next_line (content, &content)))
691                 g_ptr_array_add (filters, g_strchug (g_strchomp (line)));
692 }
693
694 static void
695 unref_coverage_assemblies (gpointer key, gpointer value, gpointer userdata)
696 {
697         MonoAssembly *assembly = (MonoAssembly *)value;
698         mono_assembly_close (assembly);
699 }
700
701 static void
702 log_shutdown (MonoProfiler *prof)
703 {
704         g_assert (prof == &coverage_profiler);
705
706         dump_coverage ();
707
708         mono_os_mutex_lock (&coverage_profiler.mutex);
709         mono_conc_hashtable_foreach (coverage_profiler.assemblies, unref_coverage_assemblies, NULL);
710         mono_os_mutex_unlock (&coverage_profiler.mutex);
711
712         mono_conc_hashtable_destroy (coverage_profiler.methods);
713         mono_conc_hashtable_destroy (coverage_profiler.assemblies);
714         mono_conc_hashtable_destroy (coverage_profiler.classes);
715         mono_conc_hashtable_destroy (coverage_profiler.filtered_classes);
716
717         mono_conc_hashtable_destroy (coverage_profiler.image_to_methods);
718         mono_conc_hashtable_destroy (coverage_profiler.suppressed_assemblies);
719         mono_os_mutex_destroy (&coverage_profiler.mutex);
720
721         if (*coverage_config.output_filename == '|') {
722                 pclose (coverage_profiler.file);
723         } else if (*coverage_config.output_filename == '#') {
724                 // do nothing
725         } else {
726                 fclose (coverage_profiler.file);
727         }
728
729         g_free (coverage_profiler.args);
730 }
731
732 static void
733 runtime_initialized (MonoProfiler *profiler)
734 {
735         mono_counters_register ("Event: Coverage methods", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_methods_ctr);
736         mono_counters_register ("Event: Coverage statements", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_statements_ctr);
737         mono_counters_register ("Event: Coverage classes", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_classes_ctr);
738         mono_counters_register ("Event: Coverage assemblies", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_assemblies_ctr);
739 }
740
741 static void usage (void);
742
743 static gboolean
744 match_option (const char *arg, const char *opt_name, const char **rval)
745 {
746         if (rval) {
747                 const char *end = strchr (arg, '=');
748
749                 *rval = NULL;
750                 if (!end)
751                         return !strcmp (arg, opt_name);
752
753                 if (strncmp (arg, opt_name, strlen (opt_name)) || (end - arg) > strlen (opt_name) + 1)
754                         return FALSE;
755                 *rval = end + 1;
756                 return TRUE;
757         } else {
758                 //FIXME how should we handle passing a value to an arg that doesn't expect it?
759                 return !strcmp (arg, opt_name);
760         }
761 }
762
763 static void
764 parse_arg (const char *arg)
765 {
766         const char *val;
767
768         if (match_option (arg, "help", NULL)) {
769                 usage ();
770         // } else if (match_option (arg, "zip", NULL)) {
771         //      coverage_config.use_zip = TRUE;
772         } else if (match_option (arg, "output", &val)) {
773                 coverage_config.output_filename = g_strdup (val);
774         // } else if (match_option (arg, "covfilter", &val)) {
775         //      g_error ("not supported");
776         } else if (match_option (arg, "covfilter-file", &val)) {
777                 if (coverage_config.cov_filter_files == NULL)
778                         coverage_config.cov_filter_files = g_ptr_array_new ();
779                 g_ptr_array_add (coverage_config.cov_filter_files, g_strdup (val));
780         } else {
781                 mono_profiler_printf_err ("Could not parse argument: %s", arg);
782         }
783 }
784
785 static void
786 parse_args (const char *desc)
787 {
788         const char *p;
789         gboolean in_quotes = FALSE;
790         char quote_char = '\0';
791         char *buffer = malloc (strlen (desc));
792         int buffer_pos = 0;
793
794         for (p = desc; *p; p++){
795                 switch (*p){
796                 case ',':
797                         if (!in_quotes) {
798                                 if (buffer_pos != 0){
799                                         buffer [buffer_pos] = 0;
800                                         parse_arg (buffer);
801                                         buffer_pos = 0;
802                                 }
803                         } else {
804                                 buffer [buffer_pos++] = *p;
805                         }
806                         break;
807
808                 case '\\':
809                         if (p [1]) {
810                                 buffer [buffer_pos++] = p[1];
811                                 p++;
812                         }
813                         break;
814                 case '\'':
815                 case '"':
816                         if (in_quotes) {
817                                 if (quote_char == *p)
818                                         in_quotes = FALSE;
819                                 else
820                                         buffer [buffer_pos++] = *p;
821                         } else {
822                                 in_quotes = TRUE;
823                                 quote_char = *p;
824                         }
825                         break;
826                 default:
827                         buffer [buffer_pos++] = *p;
828                         break;
829                 }
830         }
831
832         if (buffer_pos != 0) {
833                 buffer [buffer_pos] = 0;
834                 parse_arg (buffer);
835         }
836
837         g_free (buffer);
838 }
839
840 static void
841 usage (void)
842 {
843         mono_profiler_printf ("Mono coverage profiler");
844         mono_profiler_printf ("Usage: mono --profile=coverage[:OPTION1[,OPTION2...]] program.exe\n");
845         mono_profiler_printf ("Options:");
846         mono_profiler_printf ("\thelp                 show this usage info");
847
848         // mono_profiler_printf ("\tcovfilter=ASSEMBLY   add ASSEMBLY to the code coverage filters");
849         // mono_profiler_printf ("\t                     prefix a + to include the assembly or a - to exclude it");
850         // mono_profiler_printf ("\t                     e.g. covfilter=-mscorlib");
851         mono_profiler_printf ("\tcovfilter-file=FILE  use FILE to generate the list of assemblies to be filtered");
852         mono_profiler_printf ("\toutput=FILENAME      write the data to file FILENAME (the file is always overwritten)");
853         mono_profiler_printf ("\toutput=+FILENAME     write the data to file FILENAME.pid (the file is always overwritten)");
854         mono_profiler_printf ("\toutput=|PROGRAM      write the data to the stdin of PROGRAM");
855         mono_profiler_printf ("\toutput=|PROGRAM      write the data to the stdin of PROGRAM");
856         // mono_profiler_printf ("\tzip                  compress the output data");
857 }
858
859 MONO_API void
860 mono_profiler_init_coverage (const char *desc);
861
862 void
863 mono_profiler_init_coverage (const char *desc)
864 {
865         GPtrArray *filters = NULL;
866
867         parse_args (desc [strlen("coverage")] == ':' ? desc + strlen ("coverage") + 1 : "");
868
869         if (coverage_config.cov_filter_files) {
870                 filters = g_ptr_array_new ();
871                 int i;
872                 for (i = 0; i < coverage_config.cov_filter_files->len; ++i) {
873                         const char *name = coverage_config.cov_filter_files->pdata [i];
874                         parse_cov_filter_file (filters, name);
875                 }
876         }
877
878         coverage_profiler.args = g_strdup (desc);
879
880         //If coverage_config.output_filename begin with +, append the pid at the end
881         if (!coverage_config.output_filename)
882                 coverage_config.output_filename = "coverage.xml";
883         else if (*coverage_config.output_filename == '+')
884                 coverage_config.output_filename = g_strdup_printf ("%s.%d", coverage_config.output_filename + 1, getpid ());
885
886         if (*coverage_config.output_filename == '|')
887                 coverage_profiler.file = popen (coverage_config.output_filename + 1, "w");
888         else if (*coverage_config.output_filename == '#')
889                 coverage_profiler.file = fdopen (strtol (coverage_config.output_filename + 1, NULL, 10), "a");
890         else
891                 coverage_profiler.file = fopen (coverage_config.output_filename, "w");
892
893         if (!coverage_profiler.file) {
894                 mono_profiler_printf_err ("Could not create coverage profiler output file '%s'.", coverage_config.output_filename);
895                 exit (1);
896         }
897
898         mono_os_mutex_init (&coverage_profiler.mutex);
899         coverage_profiler.methods = mono_conc_hashtable_new (NULL, NULL);
900         coverage_profiler.assemblies = mono_conc_hashtable_new (NULL, NULL);
901         coverage_profiler.classes = mono_conc_hashtable_new (NULL, NULL);
902         coverage_profiler.filtered_classes = mono_conc_hashtable_new (NULL, NULL);
903         coverage_profiler.image_to_methods = mono_conc_hashtable_new (NULL, NULL);
904         init_suppressed_assemblies ();
905
906         coverage_profiler.filters = filters;
907
908         MonoProfilerHandle handle = coverage_profiler.handle = mono_profiler_create (&coverage_profiler);
909
910         /*
911          * Required callbacks. These are either necessary for the profiler itself
912          * to function, or provide metadata that's needed if other events (e.g.
913          * allocations, exceptions) are dynamically enabled/disabled.
914          */
915
916         mono_profiler_set_runtime_shutdown_end_callback (handle, log_shutdown);
917         mono_profiler_set_runtime_initialized_callback (handle, runtime_initialized);
918
919         mono_profiler_enable_coverage ();
920         mono_profiler_set_coverage_filter_callback (handle, coverage_filter);
921 }