[runtime] Coverage profiler fixes (#5698)
[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         GHashTable *uncovered_methods;
116
117         guint32 previous_offset;
118 };
119
120 typedef struct {
121         //Where to compress the output file
122         gboolean use_zip;
123
124         //Name of the generated xml file
125         const char *output_filename;
126
127         //Filter files used by the code coverage mode
128         GPtrArray *cov_filter_files;
129 } ProfilerConfig;
130
131 static ProfilerConfig coverage_config;
132 static struct _MonoProfiler coverage_profiler;
133
134 /* This is a very basic escape function that escapes < > and &
135    Ideally we'd use g_markup_escape_string but that function isn't
136          available in Mono's eglib. This was written without looking at the
137          source of that function in glib. */
138 static char *
139 escape_string_for_xml (const char *string)
140 {
141         GString *string_builder = g_string_new (NULL);
142         const char *start, *p;
143
144         start = p = string;
145         while (*p) {
146                 while (*p && *p != '&' && *p != '<' && *p != '>')
147                         p++;
148
149                 g_string_append_len (string_builder, start, p - start);
150
151                 if (*p == '\0')
152                         break;
153
154                 switch (*p) {
155                 case '<':
156                         g_string_append (string_builder, "&lt;");
157                         break;
158
159                 case '>':
160                         g_string_append (string_builder, "&gt;");
161                         break;
162
163                 case '&':
164                         g_string_append (string_builder, "&amp;");
165                         break;
166
167                 default:
168                         break;
169                 }
170
171                 p++;
172                 start = p;
173         }
174
175         return g_string_free (string_builder, FALSE);
176 }
177
178 typedef struct {
179         MonoLockFreeQueueNode node;
180         MonoMethod *method;
181 } MethodNode;
182
183 typedef struct {
184         int offset;
185         int counter;
186         char *filename;
187         int line;
188         int column;
189 } CoverageEntry;
190
191 static void
192 free_coverage_entry (gpointer data, gpointer userdata)
193 {
194         CoverageEntry *entry = (CoverageEntry *)data;
195         g_free (entry->filename);
196         g_free (entry);
197 }
198
199 static void
200 obtain_coverage_for_method (MonoProfiler *prof, const MonoProfilerCoverageData *entry)
201 {
202         g_assert (prof == &coverage_profiler);
203
204         CoverageEntry *e = g_new (CoverageEntry, 1);
205
206         coverage_profiler.previous_offset = entry->il_offset;
207
208         e->offset = entry->il_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
363         GHashTable *covered_methods = g_hash_table_new (NULL, NULL);
364         int count = 0;
365         {
366                 MonoLockFreeQueueNode *node;
367                 guint count = 0;
368
369                 while ((node = mono_lock_free_queue_dequeue (class_methods))) {
370                         MethodNode *mnode = (MethodNode*)node;
371                         g_hash_table_insert (covered_methods, mnode->method, mnode->method);
372                         count++;
373                         mono_thread_hazardous_try_free (node, g_free);
374                 }
375         }
376         fully_covered = count;
377
378         gpointer iter = NULL;
379         MonoMethod *method;
380         while ((method = mono_class_get_methods (klass, &iter))) {
381                 if (!g_hash_table_lookup (covered_methods, method))
382                         g_hash_table_insert (coverage_profiler.uncovered_methods, method, method);
383         }
384         g_hash_table_destroy (covered_methods);
385
386         /* We don't handle partial covered yet */
387         partially_covered = 0;
388
389         escaped_class_name = escape_string_for_xml (class_name);
390
391         fprintf (coverage_profiler.file, "\t<class name=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n",
392                 escaped_class_name, number_of_methods, fully_covered, partially_covered);
393
394         g_free (escaped_class_name);
395
396         g_free (class_name);
397
398 }
399
400 static void
401 get_coverage_for_image (MonoImage *image, int *number_of_methods, guint *fully_covered, int *partially_covered)
402 {
403         MonoLockFreeQueue *image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image);
404
405         *number_of_methods = mono_image_get_table_rows (image, MONO_TABLE_METHOD);
406         if (image_methods)
407                 *fully_covered = count_queue (image_methods);
408         else
409                 *fully_covered = 0;
410
411         // FIXME: We don't handle partially covered yet.
412         *partially_covered = 0;
413 }
414
415 static void
416 dump_assembly (gpointer key, gpointer value, gpointer userdata)
417 {
418         MonoAssembly *assembly = (MonoAssembly *)value;
419         MonoImage *image = mono_assembly_get_image (assembly);
420         const char *image_name, *image_guid, *image_filename;
421         char *escaped_image_name, *escaped_image_filename;
422         int number_of_methods = 0, partially_covered = 0;
423         guint fully_covered = 0;
424
425         image_name = mono_image_get_name (image);
426         image_guid = mono_image_get_guid (image);
427         image_filename = mono_image_get_filename (image);
428
429         image_name = image_name ? image_name : "";
430         image_guid = image_guid ? image_guid : "";
431         image_filename = image_filename ? image_filename : "";
432
433         get_coverage_for_image (image, &number_of_methods, &fully_covered, &partially_covered);
434
435         escaped_image_name = escape_string_for_xml (image_name);
436         escaped_image_filename = escape_string_for_xml (image_filename);
437
438         fprintf (coverage_profiler.file, "\t<assembly name=\"%s\" guid=\"%s\" filename=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n",
439                 escaped_image_name, image_guid, escaped_image_filename, number_of_methods, fully_covered, partially_covered);
440
441         g_free (escaped_image_name);
442         g_free (escaped_image_filename);
443
444         mono_conc_hashtable_foreach (coverage_profiler.classes, dump_classes_for_image, image);
445 }
446
447 static void
448 dump_coverage (void)
449 {
450         fprintf (coverage_profiler.file, "<?xml version=\"1.0\"?>\n");
451         fprintf (coverage_profiler.file, "<coverage version=\"0.3\">\n");
452
453         mono_os_mutex_lock (&coverage_profiler.mutex);
454         mono_conc_hashtable_foreach (coverage_profiler.assemblies, dump_assembly, NULL);
455         mono_conc_hashtable_foreach (coverage_profiler.methods, dump_method, NULL);
456         g_hash_table_foreach (coverage_profiler.uncovered_methods, dump_method, NULL);
457         mono_os_mutex_unlock (&coverage_profiler.mutex);
458
459         fprintf (coverage_profiler.file, "</coverage>\n");
460 }
461
462 static MonoLockFreeQueueNode *
463 create_method_node (MonoMethod *method)
464 {
465         MethodNode *node = (MethodNode *) g_malloc (sizeof (MethodNode));
466         mono_lock_free_queue_node_init ((MonoLockFreeQueueNode *) node, FALSE);
467         node->method = method;
468
469         return (MonoLockFreeQueueNode *) node;
470 }
471
472 static gboolean
473 coverage_filter (MonoProfiler *prof, MonoMethod *method)
474 {
475         MonoError error;
476         MonoClass *klass;
477         MonoImage *image;
478         MonoAssembly *assembly;
479         MonoMethodHeader *header;
480         guint32 iflags, flags, code_size;
481         char *fqn, *classname;
482         gboolean has_positive, found;
483         MonoLockFreeQueue *image_methods, *class_methods;
484         MonoLockFreeQueueNode *node;
485
486         g_assert (prof == &coverage_profiler);
487
488         flags = mono_method_get_flags (method, &iflags);
489         if ((iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) ||
490             (flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
491                 return FALSE;
492
493         // Don't need to do anything else if we're already tracking this method
494         if (mono_conc_hashtable_lookup (coverage_profiler.methods, method))
495                 return TRUE;
496
497         klass = mono_method_get_class (method);
498         image = mono_class_get_image (klass);
499
500         // Don't handle coverage for the core assemblies
501         if (mono_conc_hashtable_lookup (coverage_profiler.suppressed_assemblies, (gpointer) mono_image_get_name (image)) != NULL)
502                 return FALSE;
503
504         if (coverage_profiler.filters) {
505                 /* Check already filtered classes first */
506                 if (mono_conc_hashtable_lookup (coverage_profiler.filtered_classes, klass))
507                         return FALSE;
508
509                 classname = mono_type_get_name (mono_class_get_type (klass));
510
511                 fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname);
512
513                 // Check positive filters first
514                 has_positive = FALSE;
515                 found = FALSE;
516                 for (guint i = 0; i < coverage_profiler.filters->len; ++i) {
517                         char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i);
518
519                         if (filter [0] == '+') {
520                                 filter = &filter [1];
521
522                                 if (strstr (fqn, filter) != NULL)
523                                         found = TRUE;
524
525                                 has_positive = TRUE;
526                         }
527                 }
528
529                 if (has_positive && !found) {
530                         mono_os_mutex_lock (&coverage_profiler.mutex);
531                         mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass);
532                         mono_os_mutex_unlock (&coverage_profiler.mutex);
533                         g_free (fqn);
534                         g_free (classname);
535
536                         return FALSE;
537                 }
538
539                 for (guint i = 0; i < coverage_profiler.filters->len; ++i) {
540                         // FIXME: Is substring search sufficient?
541                         char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i);
542                         if (filter [0] == '+')
543                                 continue;
544
545                         // Skip '-'
546                         filter = &filter [1];
547
548                         if (strstr (fqn, filter) != NULL) {
549                                 mono_os_mutex_lock (&coverage_profiler.mutex);
550                                 mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass);
551                                 mono_os_mutex_unlock (&coverage_profiler.mutex);
552                                 g_free (fqn);
553                                 g_free (classname);
554
555                                 return FALSE;
556                         }
557                 }
558
559                 g_free (fqn);
560                 g_free (classname);
561         }
562
563         header = mono_method_get_header_checked (method, &error);
564         mono_error_cleanup (&error);
565
566         mono_method_header_get_code (header, &code_size, NULL);
567
568         assembly = mono_image_get_assembly (image);
569
570         // Need to keep the assemblies around for as long as they are kept in the hashtable
571         // Nunit, for example, has a habit of unloading them before the coverage statistics are
572         // generated causing a crash. See https://bugzilla.xamarin.com/show_bug.cgi?id=39325
573         mono_assembly_addref (assembly);
574
575         mono_os_mutex_lock (&coverage_profiler.mutex);
576         mono_conc_hashtable_insert (coverage_profiler.methods, method, method);
577         mono_conc_hashtable_insert (coverage_profiler.assemblies, assembly, assembly);
578         mono_os_mutex_unlock (&coverage_profiler.mutex);
579
580         image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image);
581
582         if (image_methods == NULL) {
583                 image_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue));
584                 mono_lock_free_queue_init (image_methods);
585                 mono_os_mutex_lock (&coverage_profiler.mutex);
586                 mono_conc_hashtable_insert (coverage_profiler.image_to_methods, image, image_methods);
587                 mono_os_mutex_unlock (&coverage_profiler.mutex);
588         }
589
590         node = create_method_node (method);
591         mono_lock_free_queue_enqueue (image_methods, node);
592
593         class_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.classes, klass);
594
595         if (class_methods == NULL) {
596                 class_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue));
597                 mono_lock_free_queue_init (class_methods);
598                 mono_os_mutex_lock (&coverage_profiler.mutex);
599                 mono_conc_hashtable_insert (coverage_profiler.classes, klass, class_methods);
600                 mono_os_mutex_unlock (&coverage_profiler.mutex);
601         }
602
603         node = create_method_node (method);
604         mono_lock_free_queue_enqueue (class_methods, node);
605
606         return TRUE;
607 }
608
609 #define LINE_BUFFER_SIZE 4096
610 /* Max file limit of 128KB */
611 #define MAX_FILE_SIZE 128 * 1024
612 static char *
613 get_file_content (const gchar *filename)
614 {
615         char *buffer;
616         ssize_t bytes_read;
617         long filesize;
618         int res, offset = 0;
619         FILE *stream;
620
621         stream = fopen (filename, "r");
622         if (stream == NULL)
623                 return NULL;
624
625         res = fseek (stream, 0, SEEK_END);
626         if (res < 0) {
627                 fclose (stream);
628                 return NULL;
629         }
630
631         filesize = ftell (stream);
632         if (filesize < 0) {
633                 fclose (stream);
634                 return NULL;
635         }
636
637         res = fseek (stream, 0, SEEK_SET);
638         if (res < 0) {
639                 fclose (stream);
640                 return NULL;
641         }
642
643         if (filesize > MAX_FILE_SIZE) {
644                 fclose (stream);
645                 return NULL;
646         }
647
648         buffer = (char *) g_malloc ((filesize + 1) * sizeof (char));
649         while ((bytes_read = fread (buffer + offset, 1, LINE_BUFFER_SIZE, stream)) > 0)
650                 offset += bytes_read;
651
652         /* NULL terminate our buffer */
653         buffer[filesize] = '\0';
654
655         fclose (stream);
656         return buffer;
657 }
658
659 static char *
660 get_next_line (char *contents, char **next_start)
661 {
662         char *p = contents;
663
664         if (p == NULL || *p == '\0') {
665                 *next_start = NULL;
666                 return NULL;
667         }
668
669         while (*p != '\n' && *p != '\0')
670                 p++;
671
672         if (*p == '\n') {
673                 *p = '\0';
674                 *next_start = p + 1;
675         } else
676                 *next_start = NULL;
677
678         return contents;
679 }
680
681 static void
682 init_suppressed_assemblies (void)
683 {
684         char *content;
685         char *line;
686
687         coverage_profiler.suppressed_assemblies = mono_conc_hashtable_new (g_str_hash, g_str_equal);
688
689         /* Don't need to free content as it is referred to by the lines stored in @filters */
690         content = get_file_content (SUPPRESSION_DIR "/mono-profiler-coverage.suppression");
691         if (content == NULL)
692                 content = get_file_content (SUPPRESSION_DIR "/mono-profiler-log.suppression");
693         if (content == NULL)
694                 return;
695
696         while ((line = get_next_line (content, &content))) {
697                 line = g_strchomp (g_strchug (line));
698                 /* No locking needed as we're doing initialization */
699                 mono_conc_hashtable_insert (coverage_profiler.suppressed_assemblies, line, line);
700         }
701 }
702
703 static void
704 parse_cov_filter_file (GPtrArray *filters, const char *file)
705 {
706         char *content;
707         char *line;
708
709         /* Don't need to free content as it is referred to by the lines stored in @filters */
710         content = get_file_content (file);
711         if (content == NULL) {
712                 mono_profiler_printf_err ("Could not open coverage filter file '%s'.", file);
713                 return;
714         }
715
716         while ((line = get_next_line (content, &content)))
717                 g_ptr_array_add (filters, g_strchug (g_strchomp (line)));
718 }
719
720 static void
721 unref_coverage_assemblies (gpointer key, gpointer value, gpointer userdata)
722 {
723         MonoAssembly *assembly = (MonoAssembly *)value;
724         mono_assembly_close (assembly);
725 }
726
727 static void
728 log_shutdown (MonoProfiler *prof)
729 {
730         g_assert (prof == &coverage_profiler);
731
732         dump_coverage ();
733
734         mono_os_mutex_lock (&coverage_profiler.mutex);
735         mono_conc_hashtable_foreach (coverage_profiler.assemblies, unref_coverage_assemblies, NULL);
736         mono_os_mutex_unlock (&coverage_profiler.mutex);
737
738         mono_conc_hashtable_destroy (coverage_profiler.methods);
739         mono_conc_hashtable_destroy (coverage_profiler.assemblies);
740         mono_conc_hashtable_destroy (coverage_profiler.classes);
741         mono_conc_hashtable_destroy (coverage_profiler.filtered_classes);
742
743         mono_conc_hashtable_destroy (coverage_profiler.image_to_methods);
744         mono_conc_hashtable_destroy (coverage_profiler.suppressed_assemblies);
745         mono_os_mutex_destroy (&coverage_profiler.mutex);
746
747         if (*coverage_config.output_filename == '|') {
748                 pclose (coverage_profiler.file);
749         } else if (*coverage_config.output_filename == '#') {
750                 // do nothing
751         } else {
752                 fclose (coverage_profiler.file);
753         }
754
755         g_free (coverage_profiler.args);
756 }
757
758 static void
759 runtime_initialized (MonoProfiler *profiler)
760 {
761         mono_counters_register ("Event: Coverage methods", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_methods_ctr);
762         mono_counters_register ("Event: Coverage statements", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_statements_ctr);
763         mono_counters_register ("Event: Coverage classes", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_classes_ctr);
764         mono_counters_register ("Event: Coverage assemblies", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_assemblies_ctr);
765 }
766
767 static void usage (void);
768
769 static gboolean
770 match_option (const char *arg, const char *opt_name, const char **rval)
771 {
772         if (rval) {
773                 const char *end = strchr (arg, '=');
774
775                 *rval = NULL;
776                 if (!end)
777                         return !strcmp (arg, opt_name);
778
779                 if (strncmp (arg, opt_name, strlen (opt_name)) || (end - arg) > strlen (opt_name) + 1)
780                         return FALSE;
781                 *rval = end + 1;
782                 return TRUE;
783         } else {
784                 //FIXME how should we handle passing a value to an arg that doesn't expect it?
785                 return !strcmp (arg, opt_name);
786         }
787 }
788
789 static void
790 parse_arg (const char *arg)
791 {
792         const char *val;
793
794         if (match_option (arg, "help", NULL)) {
795                 usage ();
796         // } else if (match_option (arg, "zip", NULL)) {
797         //      coverage_config.use_zip = TRUE;
798         } else if (match_option (arg, "output", &val)) {
799                 coverage_config.output_filename = g_strdup (val);
800         // } else if (match_option (arg, "covfilter", &val)) {
801         //      g_error ("not supported");
802         } else if (match_option (arg, "covfilter-file", &val)) {
803                 if (coverage_config.cov_filter_files == NULL)
804                         coverage_config.cov_filter_files = g_ptr_array_new ();
805                 g_ptr_array_add (coverage_config.cov_filter_files, g_strdup (val));
806         } else {
807                 mono_profiler_printf_err ("Could not parse argument: %s", arg);
808         }
809 }
810
811 static void
812 parse_args (const char *desc)
813 {
814         const char *p;
815         gboolean in_quotes = FALSE;
816         char quote_char = '\0';
817         char *buffer = malloc (strlen (desc));
818         int buffer_pos = 0;
819
820         for (p = desc; *p; p++){
821                 switch (*p){
822                 case ',':
823                         if (!in_quotes) {
824                                 if (buffer_pos != 0){
825                                         buffer [buffer_pos] = 0;
826                                         parse_arg (buffer);
827                                         buffer_pos = 0;
828                                 }
829                         } else {
830                                 buffer [buffer_pos++] = *p;
831                         }
832                         break;
833
834                 case '\\':
835                         if (p [1]) {
836                                 buffer [buffer_pos++] = p[1];
837                                 p++;
838                         }
839                         break;
840                 case '\'':
841                 case '"':
842                         if (in_quotes) {
843                                 if (quote_char == *p)
844                                         in_quotes = FALSE;
845                                 else
846                                         buffer [buffer_pos++] = *p;
847                         } else {
848                                 in_quotes = TRUE;
849                                 quote_char = *p;
850                         }
851                         break;
852                 default:
853                         buffer [buffer_pos++] = *p;
854                         break;
855                 }
856         }
857
858         if (buffer_pos != 0) {
859                 buffer [buffer_pos] = 0;
860                 parse_arg (buffer);
861         }
862
863         g_free (buffer);
864 }
865
866 static void
867 usage (void)
868 {
869         mono_profiler_printf ("Mono coverage profiler");
870         mono_profiler_printf ("Usage: mono --profile=coverage[:OPTION1[,OPTION2...]] program.exe\n");
871         mono_profiler_printf ("Options:");
872         mono_profiler_printf ("\thelp                 show this usage info");
873
874         // mono_profiler_printf ("\tcovfilter=ASSEMBLY   add ASSEMBLY to the code coverage filters");
875         // mono_profiler_printf ("\t                     prefix a + to include the assembly or a - to exclude it");
876         // mono_profiler_printf ("\t                     e.g. covfilter=-mscorlib");
877         mono_profiler_printf ("\tcovfilter-file=FILE  use FILE to generate the list of assemblies to be filtered");
878         mono_profiler_printf ("\toutput=FILENAME      write the data to file FILENAME (the file is always overwritten)");
879         mono_profiler_printf ("\toutput=+FILENAME     write the data to file FILENAME.pid (the file is always overwritten)");
880         mono_profiler_printf ("\toutput=|PROGRAM      write the data to the stdin of PROGRAM");
881         mono_profiler_printf ("\toutput=|PROGRAM      write the data to the stdin of PROGRAM");
882         // mono_profiler_printf ("\tzip                  compress the output data");
883 }
884
885 MONO_API void
886 mono_profiler_init_coverage (const char *desc);
887
888 void
889 mono_profiler_init_coverage (const char *desc)
890 {
891         GPtrArray *filters = NULL;
892
893         parse_args (desc [strlen("coverage")] == ':' ? desc + strlen ("coverage") + 1 : "");
894
895         if (coverage_config.cov_filter_files) {
896                 filters = g_ptr_array_new ();
897                 int i;
898                 for (i = 0; i < coverage_config.cov_filter_files->len; ++i) {
899                         const char *name = coverage_config.cov_filter_files->pdata [i];
900                         parse_cov_filter_file (filters, name);
901                 }
902         }
903
904         coverage_profiler.args = g_strdup (desc);
905
906         //If coverage_config.output_filename begin with +, append the pid at the end
907         if (!coverage_config.output_filename)
908                 coverage_config.output_filename = "coverage.xml";
909         else if (*coverage_config.output_filename == '+')
910                 coverage_config.output_filename = g_strdup_printf ("%s.%d", coverage_config.output_filename + 1, getpid ());
911
912         if (*coverage_config.output_filename == '|')
913                 coverage_profiler.file = popen (coverage_config.output_filename + 1, "w");
914         else if (*coverage_config.output_filename == '#')
915                 coverage_profiler.file = fdopen (strtol (coverage_config.output_filename + 1, NULL, 10), "a");
916         else
917                 coverage_profiler.file = fopen (coverage_config.output_filename, "w");
918
919         if (!coverage_profiler.file) {
920                 mono_profiler_printf_err ("Could not create coverage profiler output file '%s'.", coverage_config.output_filename);
921                 exit (1);
922         }
923
924         mono_os_mutex_init (&coverage_profiler.mutex);
925         coverage_profiler.methods = mono_conc_hashtable_new (NULL, NULL);
926         coverage_profiler.assemblies = mono_conc_hashtable_new (NULL, NULL);
927         coverage_profiler.classes = mono_conc_hashtable_new (NULL, NULL);
928         coverage_profiler.filtered_classes = mono_conc_hashtable_new (NULL, NULL);
929         coverage_profiler.image_to_methods = mono_conc_hashtable_new (NULL, NULL);
930         coverage_profiler.uncovered_methods = g_hash_table_new (NULL, NULL);
931         init_suppressed_assemblies ();
932
933         coverage_profiler.filters = filters;
934
935         MonoProfilerHandle handle = coverage_profiler.handle = mono_profiler_create (&coverage_profiler);
936
937         /*
938          * Required callbacks. These are either necessary for the profiler itself
939          * to function, or provide metadata that's needed if other events (e.g.
940          * allocations, exceptions) are dynamically enabled/disabled.
941          */
942
943         mono_profiler_set_runtime_shutdown_end_callback (handle, log_shutdown);
944         mono_profiler_set_runtime_initialized_callback (handle, runtime_initialized);
945
946         mono_profiler_enable_coverage ();
947         mono_profiler_set_coverage_filter_callback (handle, coverage_filter);
948 }