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
72 #include <mono/metadata/assembly.h>
73 #include <mono/metadata/debug-helpers.h>
74 #include <mono/metadata/profiler.h>
75 #include <mono/metadata/tabledefs.h>
76 #include <mono/metadata/metadata-internals.h>
77
78 #include <mono/mini/jit.h>
79
80 #include <mono/utils/atomic.h>
81 #include <mono/utils/hazard-pointer.h>
82 #include <mono/utils/lock-free-queue.h>
83 #include <mono/utils/mono-conc-hashtable.h>
84 #include <mono/utils/mono-os-mutex.h>
85 #include <mono/utils/mono-logger-internals.h>
86 #include <mono/utils/mono-counters.h>
87
88 // Statistics for profiler events.
89 static gint32 coverage_methods_ctr,
90 coverage_statements_ctr,
91 coverage_classes_ctr,
92 coverage_assemblies_ctr;
93
94 struct _MonoProfiler {
95 MonoProfilerHandle handle;
96
97 FILE* file;
98
99 char *args;
100
101 mono_mutex_t mutex;
102 GPtrArray *data;
103
104 GPtrArray *filters;
105 MonoConcurrentHashTable *filtered_classes;
106 MonoConcurrentHashTable *suppressed_assemblies;
107
108 MonoConcurrentHashTable *methods;
109 MonoConcurrentHashTable *assemblies;
110 MonoConcurrentHashTable *classes;
111
112 MonoConcurrentHashTable *image_to_methods;
113
114 GHashTable *uncovered_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 *
escape_string_for_xml(const char * string)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, "<");
156 break;
157
158 case '>':
159 g_string_append (string_builder, ">");
160 break;
161
162 case '&':
163 g_string_append (string_builder, "&");
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
free_coverage_entry(gpointer data,gpointer userdata)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
obtain_coverage_for_method(MonoProfiler * prof,const MonoProfilerCoverageData * entry)199 obtain_coverage_for_method (MonoProfiler *prof, const MonoProfilerCoverageData *entry)
200 {
201 g_assert (prof == &coverage_profiler);
202
203 CoverageEntry *e = g_new (CoverageEntry, 1);
204
205 coverage_profiler.previous_offset = entry->il_offset;
206
207 e->offset = entry->il_offset;
208 e->counter = entry->counter;
209 e->filename = g_strdup(entry->file_name ? entry->file_name : "");
210 e->line = entry->line;
211 e->column = entry->column;
212
213 g_ptr_array_add (coverage_profiler.data, e);
214 }
215
216 static char *
parse_generic_type_names(char * name)217 parse_generic_type_names(char *name)
218 {
219 char *new_name, *ret;
220 int within_generic_declaration = 0, generic_members = 1;
221
222 if (name == NULL || *name == '\0')
223 return g_strdup ("");
224
225 if (!(ret = new_name = (char *) g_calloc (strlen (name) * 4 + 1, sizeof (char))))
226 return NULL;
227
228 do {
229 switch (*name) {
230 case '<':
231 within_generic_declaration ++;
232 break;
233
234 case '>':
235 within_generic_declaration --;
236
237 if (within_generic_declaration)
238 break;
239
240 if (*(name - 1) != '<') {
241 *new_name++ = '`';
242 *new_name++ = '0' + generic_members;
243 } else {
244 memcpy (new_name, "<>", 8);
245 new_name += 8;
246 }
247
248 generic_members = 0;
249 break;
250
251 case ',':
252 generic_members++;
253 break;
254
255 default:
256 if (!within_generic_declaration)
257 *new_name++ = *name;
258
259 break;
260 }
261 } while (*name++);
262
263 return ret;
264 }
265
266 static void
dump_method(gpointer key,gpointer value,gpointer userdata)267 dump_method (gpointer key, gpointer value, gpointer userdata)
268 {
269 MonoMethod *method = (MonoMethod *)value;
270 MonoClass *klass;
271 MonoImage *image;
272 char *class_name, *escaped_image_name, *escaped_class_name, *escaped_method_name, *escaped_method_signature, *escaped_method_filename;
273 const char *image_name, *method_name, *method_signature, *method_filename;
274 guint i;
275
276 coverage_profiler.previous_offset = 0;
277 coverage_profiler.data = g_ptr_array_new ();
278
279 mono_profiler_get_coverage_data (coverage_profiler.handle, method, obtain_coverage_for_method);
280
281 klass = mono_method_get_class (method);
282 image = mono_class_get_image (klass);
283 image_name = mono_image_get_name (image);
284
285 method_signature = mono_signature_get_desc (mono_method_signature (method), TRUE);
286 class_name = parse_generic_type_names (mono_type_get_name (mono_class_get_type (klass)));
287 method_name = mono_method_get_name (method);
288
289 if (coverage_profiler.data->len != 0) {
290 CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[0];
291 method_filename = entry->filename ? entry->filename : "";
292 } else
293 method_filename = "";
294
295 image_name = image_name ? image_name : "";
296 method_signature = method_signature ? method_signature : "";
297 method_name = method_name ? method_name : "";
298
299 escaped_image_name = escape_string_for_xml (image_name);
300 escaped_class_name = escape_string_for_xml (class_name);
301 escaped_method_name = escape_string_for_xml (method_name);
302 escaped_method_signature = escape_string_for_xml (method_signature);
303 escaped_method_filename = escape_string_for_xml (method_filename);
304
305 fprintf (coverage_profiler.file, "\t<method assembly=\"%s\" class=\"%s\" name=\"%s (%s)\" filename=\"%s\" token=\"%d\">\n",
306 escaped_image_name, escaped_class_name, escaped_method_name, escaped_method_signature, escaped_method_filename, mono_method_get_token (method));
307
308 g_free (escaped_image_name);
309 g_free (escaped_class_name);
310 g_free (escaped_method_name);
311 g_free (escaped_method_signature);
312 g_free (escaped_method_filename);
313
314 for (i = 0; i < coverage_profiler.data->len; i++) {
315 CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[i];
316
317 fprintf (coverage_profiler.file, "\t\t<statement offset=\"%d\" counter=\"%d\" line=\"%d\" column=\"%d\"/>\n",
318 entry->offset, entry->counter, entry->line, entry->column);
319 }
320
321 fprintf (coverage_profiler.file, "\t</method>\n");
322
323 g_free (class_name);
324
325 g_ptr_array_foreach (coverage_profiler.data, free_coverage_entry, NULL);
326 g_ptr_array_free (coverage_profiler.data, TRUE);
327 }
328
329 /* This empties the queue */
330 static guint
count_queue(MonoLockFreeQueue * queue)331 count_queue (MonoLockFreeQueue *queue)
332 {
333 MonoLockFreeQueueNode *node;
334 guint count = 0;
335
336 while ((node = mono_lock_free_queue_dequeue (queue))) {
337 count++;
338 mono_thread_hazardous_try_free (node, g_free);
339 }
340
341 return count;
342 }
343
344 static void
dump_classes_for_image(gpointer key,gpointer value,gpointer userdata)345 dump_classes_for_image (gpointer key, gpointer value, gpointer userdata)
346 {
347 MonoClass *klass = (MonoClass *)key;
348 MonoLockFreeQueue *class_methods = (MonoLockFreeQueue *)value;
349 MonoImage *image;
350 char *class_name, *escaped_class_name;
351 const char *image_name;
352 int number_of_methods, partially_covered;
353 guint fully_covered;
354
355 image = mono_class_get_image (klass);
356 image_name = mono_image_get_name (image);
357
358 if (!image_name || strcmp (image_name, mono_image_get_name (((MonoImage*) userdata))) != 0)
359 return;
360
361 class_name = mono_type_get_name (mono_class_get_type (klass));
362
363 number_of_methods = mono_class_num_methods (klass);
364
365 GHashTable *covered_methods = g_hash_table_new (NULL, NULL);
366 int count = 0;
367 {
368 MonoLockFreeQueueNode *node;
369 guint count = 0;
370
371 while ((node = mono_lock_free_queue_dequeue (class_methods))) {
372 MethodNode *mnode = (MethodNode*)node;
373 g_hash_table_insert (covered_methods, mnode->method, mnode->method);
374 count++;
375 mono_thread_hazardous_try_free (node, g_free);
376 }
377 }
378 fully_covered = count;
379
380 gpointer iter = NULL;
381 MonoMethod *method;
382 while ((method = mono_class_get_methods (klass, &iter))) {
383 if (!g_hash_table_lookup (covered_methods, method))
384 g_hash_table_insert (coverage_profiler.uncovered_methods, method, method);
385 }
386 g_hash_table_destroy (covered_methods);
387
388 /* We don't handle partial covered yet */
389 partially_covered = 0;
390
391 escaped_class_name = escape_string_for_xml (class_name);
392
393 fprintf (coverage_profiler.file, "\t<class name=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n",
394 escaped_class_name, number_of_methods, fully_covered, partially_covered);
395
396 g_free (escaped_class_name);
397
398 g_free (class_name);
399
400 }
401
402 static void
get_coverage_for_image(MonoImage * image,int * number_of_methods,guint * fully_covered,int * partially_covered)403 get_coverage_for_image (MonoImage *image, int *number_of_methods, guint *fully_covered, int *partially_covered)
404 {
405 MonoLockFreeQueue *image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image);
406
407 *number_of_methods = mono_image_get_table_rows (image, MONO_TABLE_METHOD);
408 if (image_methods)
409 *fully_covered = count_queue (image_methods);
410 else
411 *fully_covered = 0;
412
413 // FIXME: We don't handle partially covered yet.
414 *partially_covered = 0;
415 }
416
417 static void
dump_assembly(gpointer key,gpointer value,gpointer userdata)418 dump_assembly (gpointer key, gpointer value, gpointer userdata)
419 {
420 MonoAssembly *assembly = (MonoAssembly *)value;
421 MonoImage *image = mono_assembly_get_image (assembly);
422 const char *image_name, *image_guid, *image_filename;
423 char *escaped_image_name, *escaped_image_filename;
424 int number_of_methods = 0, partially_covered = 0;
425 guint fully_covered = 0;
426
427 image_name = mono_image_get_name (image);
428 image_guid = mono_image_get_guid (image);
429 image_filename = mono_image_get_filename (image);
430
431 image_name = image_name ? image_name : "";
432 image_guid = image_guid ? image_guid : "";
433 image_filename = image_filename ? image_filename : "";
434
435 get_coverage_for_image (image, &number_of_methods, &fully_covered, &partially_covered);
436
437 escaped_image_name = escape_string_for_xml (image_name);
438 escaped_image_filename = escape_string_for_xml (image_filename);
439
440 fprintf (coverage_profiler.file, "\t<assembly name=\"%s\" guid=\"%s\" filename=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n",
441 escaped_image_name, image_guid, escaped_image_filename, number_of_methods, fully_covered, partially_covered);
442
443 g_free (escaped_image_name);
444 g_free (escaped_image_filename);
445
446 mono_conc_hashtable_foreach (coverage_profiler.classes, dump_classes_for_image, image);
447 }
448
449 static void
dump_coverage(void)450 dump_coverage (void)
451 {
452 fprintf (coverage_profiler.file, "<?xml version=\"1.0\"?>\n");
453 fprintf (coverage_profiler.file, "<coverage version=\"0.3\">\n");
454
455 mono_os_mutex_lock (&coverage_profiler.mutex);
456 mono_conc_hashtable_foreach (coverage_profiler.assemblies, dump_assembly, NULL);
457 mono_conc_hashtable_foreach (coverage_profiler.methods, dump_method, NULL);
458 g_hash_table_foreach (coverage_profiler.uncovered_methods, dump_method, NULL);
459 mono_os_mutex_unlock (&coverage_profiler.mutex);
460
461 fprintf (coverage_profiler.file, "</coverage>\n");
462 }
463
464 static MonoLockFreeQueueNode *
create_method_node(MonoMethod * method)465 create_method_node (MonoMethod *method)
466 {
467 MethodNode *node = (MethodNode *) g_malloc (sizeof (MethodNode));
468 mono_lock_free_queue_node_init ((MonoLockFreeQueueNode *) node, FALSE);
469 node->method = method;
470
471 return (MonoLockFreeQueueNode *) node;
472 }
473
474 static gboolean
coverage_filter(MonoProfiler * prof,MonoMethod * method)475 coverage_filter (MonoProfiler *prof, MonoMethod *method)
476 {
477 MonoError error;
478 MonoClass *klass;
479 MonoImage *image;
480 MonoAssembly *assembly;
481 MonoMethodHeader *header;
482 guint32 iflags, flags, code_size;
483 char *fqn, *classname;
484 gboolean has_positive, found;
485 MonoLockFreeQueue *image_methods, *class_methods;
486 MonoLockFreeQueueNode *node;
487
488 g_assert (prof == &coverage_profiler);
489
490 flags = mono_method_get_flags (method, &iflags);
491 if ((iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) ||
492 (flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
493 return FALSE;
494
495 // Don't need to do anything else if we're already tracking this method
496 if (mono_conc_hashtable_lookup (coverage_profiler.methods, method))
497 return TRUE;
498
499 klass = mono_method_get_class (method);
500 image = mono_class_get_image (klass);
501
502 // Don't handle coverage for the core assemblies
503 if (mono_conc_hashtable_lookup (coverage_profiler.suppressed_assemblies, (gpointer) mono_image_get_name (image)) != NULL)
504 return FALSE;
505
506 if (coverage_profiler.filters) {
507 /* Check already filtered classes first */
508 if (mono_conc_hashtable_lookup (coverage_profiler.filtered_classes, klass))
509 return FALSE;
510
511 classname = mono_type_get_name (mono_class_get_type (klass));
512
513 fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname);
514
515 // Check positive filters first
516 has_positive = FALSE;
517 found = FALSE;
518 for (guint i = 0; i < coverage_profiler.filters->len; ++i) {
519 char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i);
520
521 if (filter [0] == '+') {
522 filter = &filter [1];
523
524 if (strstr (fqn, filter) != NULL)
525 found = TRUE;
526
527 has_positive = TRUE;
528 }
529 }
530
531 if (has_positive && !found) {
532 mono_os_mutex_lock (&coverage_profiler.mutex);
533 mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass);
534 mono_os_mutex_unlock (&coverage_profiler.mutex);
535 g_free (fqn);
536 g_free (classname);
537
538 return FALSE;
539 }
540
541 for (guint i = 0; i < coverage_profiler.filters->len; ++i) {
542 // FIXME: Is substring search sufficient?
543 char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i);
544 if (filter [0] == '+' || filter [0] != '-')
545 continue;
546
547 // Skip '-'
548 filter = &filter [1];
549
550 if (strstr (fqn, filter) != NULL) {
551 mono_os_mutex_lock (&coverage_profiler.mutex);
552 mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass);
553 mono_os_mutex_unlock (&coverage_profiler.mutex);
554 g_free (fqn);
555 g_free (classname);
556
557 return FALSE;
558 }
559 }
560
561 g_free (fqn);
562 g_free (classname);
563 }
564
565 header = mono_method_get_header_checked (method, &error);
566 mono_error_cleanup (&error);
567
568 mono_method_header_get_code (header, &code_size, NULL);
569
570 assembly = mono_image_get_assembly (image);
571
572 // Need to keep the assemblies around for as long as they are kept in the hashtable
573 // Nunit, for example, has a habit of unloading them before the coverage statistics are
574 // generated causing a crash. See https://bugzilla.xamarin.com/show_bug.cgi?id=39325
575 mono_assembly_addref (assembly);
576
577 mono_os_mutex_lock (&coverage_profiler.mutex);
578 mono_conc_hashtable_insert (coverage_profiler.methods, method, method);
579 mono_conc_hashtable_insert (coverage_profiler.assemblies, assembly, assembly);
580 mono_os_mutex_unlock (&coverage_profiler.mutex);
581
582 image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image);
583
584 if (image_methods == NULL) {
585 image_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue));
586 mono_lock_free_queue_init (image_methods);
587 mono_os_mutex_lock (&coverage_profiler.mutex);
588 mono_conc_hashtable_insert (coverage_profiler.image_to_methods, image, image_methods);
589 mono_os_mutex_unlock (&coverage_profiler.mutex);
590 }
591
592 node = create_method_node (method);
593 mono_lock_free_queue_enqueue (image_methods, node);
594
595 class_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.classes, klass);
596
597 if (class_methods == NULL) {
598 class_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue));
599 mono_lock_free_queue_init (class_methods);
600 mono_os_mutex_lock (&coverage_profiler.mutex);
601 mono_conc_hashtable_insert (coverage_profiler.classes, klass, class_methods);
602 mono_os_mutex_unlock (&coverage_profiler.mutex);
603 }
604
605 node = create_method_node (method);
606 mono_lock_free_queue_enqueue (class_methods, node);
607
608 return TRUE;
609 }
610
611 #define LINE_BUFFER_SIZE 4096
612 /* Max file limit of 128KB */
613 #define MAX_FILE_SIZE 128 * 1024
614 static char *
get_file_content(const gchar * filename)615 get_file_content (const gchar *filename)
616 {
617 char *buffer;
618 ssize_t bytes_read;
619 long filesize;
620 int res, offset = 0;
621 FILE *stream;
622
623 stream = fopen (filename, "r");
624 if (stream == NULL)
625 return NULL;
626
627 res = fseek (stream, 0, SEEK_END);
628 if (res < 0) {
629 fclose (stream);
630 return NULL;
631 }
632
633 filesize = ftell (stream);
634 if (filesize < 0) {
635 fclose (stream);
636 return NULL;
637 }
638
639 res = fseek (stream, 0, SEEK_SET);
640 if (res < 0) {
641 fclose (stream);
642 return NULL;
643 }
644
645 if (filesize > MAX_FILE_SIZE) {
646 fclose (stream);
647 return NULL;
648 }
649
650 buffer = (char *) g_malloc ((filesize + 1) * sizeof (char));
651 while ((bytes_read = fread (buffer + offset, 1, LINE_BUFFER_SIZE, stream)) > 0)
652 offset += bytes_read;
653
654 /* NULL terminate our buffer */
655 buffer[filesize] = '\0';
656
657 fclose (stream);
658 return buffer;
659 }
660
661 static char *
get_next_line(char * contents,char ** next_start)662 get_next_line (char *contents, char **next_start)
663 {
664 char *p = contents;
665
666 if (p == NULL || *p == '\0') {
667 *next_start = NULL;
668 return NULL;
669 }
670
671 while (*p != '\n' && *p != '\0')
672 p++;
673
674 if (*p == '\n') {
675 *p = '\0';
676 *next_start = p + 1;
677 } else
678 *next_start = NULL;
679
680 return contents;
681 }
682
683 static void
init_suppressed_assemblies(void)684 init_suppressed_assemblies (void)
685 {
686 char *content;
687 char *line;
688
689 coverage_profiler.suppressed_assemblies = mono_conc_hashtable_new (g_str_hash, g_str_equal);
690
691 /* Don't need to free content as it is referred to by the lines stored in @filters */
692 content = get_file_content (SUPPRESSION_DIR "/mono-profiler-coverage.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
parse_cov_filter_file(GPtrArray * filters,const char * file)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
unref_coverage_assemblies(gpointer key,gpointer value,gpointer userdata)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
cov_shutdown(MonoProfiler * prof)728 cov_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
runtime_initialized(MonoProfiler * profiler)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
match_option(const char * arg,const char * opt_name,const char ** rval)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
parse_arg(const char * arg)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
parse_args(const char * desc)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
usage(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 exit (0);
885 }
886
887 MONO_API void
888 mono_profiler_init_coverage (const char *desc);
889
890 void
mono_profiler_init_coverage(const char * desc)891 mono_profiler_init_coverage (const char *desc)
892 {
893 if (mono_jit_aot_compiling ()) {
894 mono_profiler_printf_err ("The coverage profiler does not currently support instrumenting AOT code.");
895 exit (1);
896 }
897
898 GPtrArray *filters = NULL;
899
900 parse_args (desc [strlen("coverage")] == ':' ? desc + strlen ("coverage") + 1 : "");
901
902 if (coverage_config.cov_filter_files) {
903 filters = g_ptr_array_new ();
904 int i;
905 for (i = 0; i < coverage_config.cov_filter_files->len; ++i) {
906 const char *name = coverage_config.cov_filter_files->pdata [i];
907 parse_cov_filter_file (filters, name);
908 }
909 }
910
911 coverage_profiler.args = g_strdup (desc);
912
913 //If coverage_config.output_filename begin with +, append the pid at the end
914 if (!coverage_config.output_filename)
915 coverage_config.output_filename = "coverage.xml";
916 else if (*coverage_config.output_filename == '+')
917 coverage_config.output_filename = g_strdup_printf ("%s.%d", coverage_config.output_filename + 1, getpid ());
918
919 if (*coverage_config.output_filename == '|')
920 coverage_profiler.file = popen (coverage_config.output_filename + 1, "w");
921 else if (*coverage_config.output_filename == '#')
922 coverage_profiler.file = fdopen (strtol (coverage_config.output_filename + 1, NULL, 10), "a");
923 else
924 coverage_profiler.file = fopen (coverage_config.output_filename, "w");
925
926 if (!coverage_profiler.file) {
927 mono_profiler_printf_err ("Could not create coverage profiler output file '%s': %s", coverage_config.output_filename, g_strerror (errno));
928 exit (1);
929 }
930
931 mono_os_mutex_init (&coverage_profiler.mutex);
932 coverage_profiler.methods = mono_conc_hashtable_new (NULL, NULL);
933 coverage_profiler.assemblies = mono_conc_hashtable_new (NULL, NULL);
934 coverage_profiler.classes = mono_conc_hashtable_new (NULL, NULL);
935 coverage_profiler.filtered_classes = mono_conc_hashtable_new (NULL, NULL);
936 coverage_profiler.image_to_methods = mono_conc_hashtable_new (NULL, NULL);
937 coverage_profiler.uncovered_methods = g_hash_table_new (NULL, NULL);
938 init_suppressed_assemblies ();
939
940 coverage_profiler.filters = filters;
941
942 MonoProfilerHandle handle = coverage_profiler.handle = mono_profiler_create (&coverage_profiler);
943
944 mono_profiler_set_runtime_shutdown_end_callback (handle, cov_shutdown);
945 mono_profiler_set_runtime_initialized_callback (handle, runtime_initialized);
946
947 mono_profiler_enable_coverage ();
948 mono_profiler_set_coverage_filter_callback (handle, coverage_filter);
949 }
950