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, "&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
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, "&lt;&gt;", 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