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