monocov/coverage.c

392 строки
9.6 KiB
C

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <glib.h>
#include <mono/metadata/class.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/profiler.h>
struct _MonoProfiler {
/* Contains the methods for which we have coverage data */
GHashTable *methods;
/* A list of classes for which we are collecting coverage data */
GHashTable *classes;
/* A list of assemblies for which we are collecting coverage data */
GHashTable *assemblies;
char *outfile_name;
GPtrArray *filters;
GPtrArray *filters_as_str;
GHashTable *filtered_classes;
FILE *outfile;
};
static char
*parse_generic_type_names(char *string);
static void
add_filter (MonoProfiler *prof, const char *filter);
static void
assembly_load (MonoProfiler *prof, MonoAssembly *assembly, int result);
static void
coverage_shutdown (MonoProfiler *prof);
static gboolean
collect_coverage_for (MonoProfiler *prof, MonoMethod *method);
void
mono_profiler_startup (char *arg)
{
gchar **ptr;
char *filterfile_name = NULL;
gchar **args;
/* Why does the runtime passes the module name to us ? */
if (strstr (arg, ":"))
arg = strstr (arg, ":") + 1;
else
arg = NULL;
args = g_strsplit (arg ? arg : "", ",", -1);
MonoProfiler *prof = g_new0 (MonoProfiler, 1);
prof->methods = g_hash_table_new (NULL, NULL);
prof->classes = g_hash_table_new (NULL, NULL);
prof->assemblies = g_hash_table_new (NULL, NULL);
for (ptr = args; ptr && *ptr; ptr++) {
const char *arg = *ptr;
gchar *message;
if (strncmp (arg, "filterfile=", 11) == 0)
filterfile_name = g_strdup (arg + 11);
else
if (strncmp (arg, "outfile=", 8) == 0)
prof->outfile_name = g_strdup (arg + 8);
else
if (strncmp (arg, "-", 1) == 0) {
add_filter (prof, arg);
}
else if (strncmp (arg, "+", 1) == 0) {
add_filter (prof, arg);
}
else {
message = g_strdup_printf ("Unknown argument '%s'.", arg);
fprintf (stderr, "monocov | Error while processing arguments: %s\n", message);
g_free (message);
}
}
g_strfreev (args);
if (filterfile_name) {
FILE *filterfile;
filterfile = fopen (filterfile_name, "r");
if (!filterfile) {
fprintf (stderr, "coverage.c: Unable to open filter file '%s'.\n", filterfile_name);
exit (1);
}
char buf [2048];
while (fgets (buf, 2048, filterfile) != NULL) {
buf [sizeof (buf) - 1] = '\0';
if ((buf [0] == '#') || (buf [0] == '\0'))
continue;
if (buf [strlen (buf) - 1] == '\n')
buf [strlen (buf) - 1] = '\0';
add_filter (prof, buf);
}
fclose (filterfile);
}
mono_profiler_install (prof, coverage_shutdown);
mono_profiler_set_events (MONO_PROFILE_INS_COVERAGE | MONO_PROFILE_ASSEMBLY_EVENTS);
mono_profiler_install_coverage_filter (collect_coverage_for);
mono_profiler_install_assembly (NULL, assembly_load, NULL, NULL);
/* we don't deal with unloading, so disable it for now */
setenv ("MONO_NO_UNLOAD", "1", 1);
}
static void
assembly_load (MonoProfiler *prof, MonoAssembly *assembly, int result)
{
/* Unfortunately, this doesn't get called... */
}
static gboolean
collect_coverage_for (MonoProfiler *prof, MonoMethod *method)
{
int i;
char *classname;
char *fqn;
MonoMethodHeader *header;
gboolean has_positive, found;
guint32 iflags, flags, code_size;
MonoClass *klass;
MonoImage *image;
flags = mono_method_get_flags (method, &iflags);
if ((iflags & 0x1000 /*METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL*/) ||
(flags & 0x2000 /*METHOD_ATTRIBUTE_PINVOKE_IMPL*/))
return FALSE;
//if (method->wrapper_type != MONO_WRAPPER_NONE)
// return FALSE;
klass = mono_method_get_class (method);
image = mono_class_get_image (klass);
/* Hacky way of determining the executing assembly */
if (! prof->outfile_name && (strcmp (mono_method_get_name (method), "Main") == 0)) {
prof->outfile_name = g_strdup_printf ("%s.cov", mono_image_get_filename (image));
}
/* Check filters */
if (prof->filters) {
/* Check already filtered classes first */
if (g_hash_table_lookup (prof->filtered_classes, klass))
return FALSE;
classname = mono_type_get_name (mono_class_get_type (klass));
fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname);
// Check positive filters first
has_positive = FALSE;
found = FALSE;
for (i = 0; i < prof->filters->len; ++i) {
char *filter = g_ptr_array_index (prof->filters_as_str, i);
if (filter [0] == '+') {
filter = &filter [1];
if (strstr (fqn, filter) != NULL)
found = TRUE;
has_positive = TRUE;
}
}
if (has_positive && !found)
return FALSE;
for (i = 0; i < prof->filters->len; ++i) {
// Is substring search suffices ???
// GPatternSpec *spec = g_ptr_array_index (filters, i);
// if (g_pattern_match_string (spec, classname)) {
char *filter = g_ptr_array_index (prof->filters_as_str, i);
if (filter [0] == '+')
continue;
// Skip '-'
filter = &filter [1];
if (strstr (fqn, filter) != NULL) {
g_hash_table_insert (prof->filtered_classes, klass, klass);
return FALSE;
}
}
g_free (fqn);
g_free (classname);
}
header = mono_method_get_header (method);
mono_method_header_get_code (header, &code_size, NULL);
if (code_size > 20000) {
exit (1);
g_warning ("Unable to instrument method %s:%s since it is too complex.", mono_class_get_name (klass), mono_method_get_name (method));
return FALSE;
}
g_hash_table_insert (prof->methods, method, method);
g_hash_table_insert (prof->classes, klass, klass);
g_hash_table_insert (prof->assemblies, mono_image_get_assembly (image), mono_image_get_assembly (image));
return TRUE;
}
static void
add_filter (MonoProfiler *prof, const char *filter)
{
GPatternSpec *spec;
if (prof->filters == NULL) {
prof->filters = g_ptr_array_new ();
prof->filters_as_str = g_ptr_array_new ();
prof->filtered_classes = g_hash_table_new (NULL, NULL);
}
spec = NULL; /* compile a pattern later */
g_ptr_array_add (prof->filters, spec);
g_ptr_array_add (prof->filters_as_str, g_strdup (filter));
}
static void
output_filters (MonoProfiler *prof, FILE *outfile)
{
int i;
if (prof->filters) {
for (i = 0; i < prof->filters_as_str->len; ++i) {
char *str = g_ptr_array_index (prof->filters_as_str, i);
fprintf (outfile, "\t<filter pattern=\"%s\"/>\n",
g_markup_escape_text (str, strlen (str)));
}
}
}
static void
output_assembly (MonoAssembly *assembly, MonoAssembly *assembly2, FILE *outfile)
{
MonoImage *image = mono_assembly_get_image (assembly);
fprintf (outfile, "\t<assembly name=\"%s\" guid=\"%s\" filename=\"%s\"/>\n",
mono_image_get_name (image), mono_image_get_guid (image), mono_image_get_filename (image));
}
static int count;
static int prev_offset;
static void
output_entry (MonoProfiler *prof, const MonoProfileCoverageEntry *entry)
{
count ++;
if ((count % 8) == 0)
fprintf (prof->outfile, "\n\t\t");
fprintf (prof->outfile, "%d %d\t", entry->iloffset - prev_offset, entry->counter);
prev_offset = entry->iloffset;
}
static char
*parse_generic_type_names(char *name)
{
char *new_name,*ret;
int within_generic_declaration=0, generic_members=1;
if( !(ret = new_name = calloc(strlen(name) * 4 + 1, sizeof(char))) )
return NULL;
do
{
switch(*name)
{
case '<':
within_generic_declaration = 1;
break;
case '>':
within_generic_declaration = 0;
if( *(name-1) != '<')
{
*new_name++ = '`';
*new_name++ = '0' + generic_members;
}
else
{
memcpy(new_name,"&lt;&gt;",8);
new_name+=8;
}
generic_members = 0;
break;
case ',':
generic_members++;
break;
default:
if(!within_generic_declaration)
*new_name++ = *name;
break;
}
}while(*name++);
return ret;
}
static void
output_method (MonoMethod *method, gpointer dummy, MonoProfiler *prof)
{
MonoMethodHeader *header;
char *classname;
char *tmpsig;
char *tmpname;
FILE *outfile;
MonoClass *klass;
MonoImage *image;
outfile = prof->outfile;
header = mono_method_get_header (method);
tmpsig = mono_signature_get_desc (mono_method_signature (method), TRUE);
tmpsig = g_markup_escape_text (tmpsig, strlen (tmpsig));
klass = mono_method_get_class (method);
classname = parse_generic_type_names (mono_type_get_name (mono_class_get_type (klass)));
image = mono_class_get_image (klass);
tmpname = (char*)mono_method_get_name (method);
tmpname = g_markup_escape_text (tmpname, strlen (tmpname));
fprintf (outfile, "\t<method assembly=\"%s\" class=\"%s\" name=\"%s (%s)\" token=\"%d\">\n",
mono_image_get_name (image),
classname, tmpname,
tmpsig, mono_method_get_token (method));
g_free (tmpsig);
g_free (tmpname);
fprintf (outfile, "\t\t");
count = 0;
prev_offset = 0;
mono_profiler_coverage_get (prof, method, output_entry);
fprintf (outfile, "\n");
fprintf (outfile, "\t</method>\n");
}
static void
coverage_shutdown (MonoProfiler *prof)
{
FILE *outfile;
if (!prof->outfile_name)
prof->outfile_name = g_strdup ("/dev/stdout");
printf ("Dumping coverage data to %s ...\n", prof->outfile_name);
outfile = fopen (prof->outfile_name, "w");
if (!outfile) {
fprintf (stderr, "coverage: unable to create result file %s: %s.\n",
prof->outfile_name, strerror (errno));
return;
}
prof->outfile = outfile;
fprintf (outfile, "<?xml version=\"1.0\"?>\n");
fprintf (outfile, "<coverage version=\"%s\">\n", VERSION);
/*
* The UI doesn't deal well with this enabled.
* output_filters (prof, outfile);
*/
g_hash_table_foreach (prof->assemblies, (GHFunc)output_assembly, outfile);
g_hash_table_foreach (prof->methods, (GHFunc)output_method, prof);
fprintf (outfile, "</coverage>\n");
fclose (outfile);
printf ("Done.\n");
}