Merge pull request #2429 from alexanderkyte/nunit_lite_integration
[mono.git] / mono / utils / checked-build.c
1 /*
2  * checked-build.c: Expensive asserts used when mono is built with --with-checked-build=yes
3  *
4  * Author:
5  *      Rodrigo Kumpera (kumpera@gmail.com)
6  *
7  * (C) 2015 Xamarin
8  */
9 #include <config.h>
10
11 #ifdef CHECKED_BUILD
12
13 #include <mono/utils/checked-build.h>
14 #include <mono/utils/mono-threads.h>
15 #include <mono/utils/mono-tls.h>
16 #include <mono/metadata/mempool.h>
17 #include <mono/metadata/metadata-internals.h>
18 #include <mono/metadata/image-internals.h>
19 #include <mono/metadata/class-internals.h>
20 #include <mono/metadata/reflection-internals.h>
21 #include <glib.h>
22
23 #ifdef HAVE_BACKTRACE_SYMBOLS
24 #include <execinfo.h>
25 #endif
26
27 typedef struct {
28         GPtrArray *transitions;
29         guint32 in_gc_critical_region;
30 } CheckState;
31
32 static MonoNativeTlsKey thread_status;
33
34 void
35 checked_build_init (void)
36 {
37         mono_native_tls_alloc (&thread_status, NULL);
38 }
39
40 static CheckState*
41 get_state (void)
42 {
43         CheckState *state = mono_native_tls_get_value (thread_status);
44         if (!state) {
45                 state = g_new0 (CheckState, 1);
46                 state->transitions = g_ptr_array_new ();
47                 mono_native_tls_set_value (thread_status, state);
48         }
49
50         return state;
51 }
52
53 #if !defined(DISABLE_CHECKED_BUILD_THREAD)
54
55 #define MAX_NATIVE_BT 6
56 #define MAX_NATIVE_BT_PROBE (MAX_NATIVE_BT + 5)
57 #define MAX_TRANSITIONS 3
58
59 #ifdef HAVE_BACKTRACE_SYMBOLS
60
61 //XXX We should collect just the IPs and lazily symbolificate them.
62 static int
63 collect_backtrace (gpointer out_data[])
64 {
65         return backtrace (out_data, MAX_NATIVE_BT_PROBE);
66 }
67
68 static char*
69 translate_backtrace (gpointer native_trace[], int size)
70 {
71         char **names = backtrace_symbols (native_trace, size);
72         GString* bt = g_string_sized_new (100);
73
74         int i, j = -1;
75
76         //Figure out the cut point of useless backtraces
77         //We'll skip up to the caller of checked_build_thread_transition
78         for (i = 0; i < size; ++i) {
79                 if (strstr (names [i], "checked_build_thread_transition")) {
80                         j = i + 1;
81                         break;
82                 }
83         }
84
85         if (j == -1)
86                 j = 0;
87         for (i = j; i < size; ++i) {
88                 if (i - j <= MAX_NATIVE_BT)
89                         g_string_append_printf (bt, "\tat %s\n", names [i]);
90         }
91
92         free (names);
93         return g_string_free (bt, FALSE);
94 }
95
96 #else
97
98 static int
99 collect_backtrace (gpointer out_data[])
100 {
101         return 0;
102 }
103
104 static char*
105 translate_backtrace (gpointer native_trace[], int size)
106 {
107         return g_strdup ("\tno backtrace available\n");
108 }
109
110 #endif
111
112 typedef struct {
113         const char *name;
114         int from_state, next_state, suspend_count, suspend_count_delta, size;
115         gpointer backtrace [MAX_NATIVE_BT_PROBE];
116 } ThreadTransition;
117
118 static void
119 free_transition (ThreadTransition *t)
120 {
121         g_free (t);
122 }
123
124 void
125 checked_build_thread_transition (const char *transition, void *info, int from_state, int suspend_count, int next_state, int suspend_count_delta)
126 {
127         MonoThreadInfo *cur = mono_thread_info_current_unchecked ();
128         CheckState *state = get_state ();
129         /* We currently don't record external changes as those are hard to reason about. */
130         if (cur != info)
131                 return;
132
133         if (state->transitions->len >= MAX_TRANSITIONS)
134                 free_transition (g_ptr_array_remove_index (state->transitions, 0));
135
136         ThreadTransition *t = g_new0 (ThreadTransition, 1);
137         t->name = transition;
138         t->from_state = from_state;
139         t->next_state = next_state;
140         t->suspend_count = suspend_count;
141         t->suspend_count_delta = suspend_count_delta;
142         t->size = collect_backtrace (t->backtrace);
143         g_ptr_array_add (state->transitions, t);
144 }
145
146 #endif /* !defined(DISABLE_CHECKED_BUILD_THREAD) */
147
148 #if !defined(DISABLE_CHECKED_BUILD_GC)
149
150 static void
151 assertion_fail (const char *msg, ...)
152 {
153         int i;
154         GString* err = g_string_sized_new (100);
155         CheckState *state = get_state ();
156
157         g_string_append_printf (err, "Assertion failure in thread %p due to: ", mono_native_thread_id_get ());
158
159         va_list args;
160         va_start (args, msg);
161         g_string_append_vprintf (err, msg, args);
162         va_end (args);
163
164         g_string_append_printf (err, "\nLast %d state transitions: (most recent first)\n", state->transitions->len);
165
166         for (i = state->transitions->len - 1; i >= 0; --i) {
167                 ThreadTransition *t = state->transitions->pdata [i];
168                 char *bt = translate_backtrace (t->backtrace, t->size);
169                 g_string_append_printf (err, "[%s] %s -> %s (%d) %s%d at:\n%s",
170                         t->name,
171                         mono_thread_state_name (t->from_state),
172                         mono_thread_state_name (t->next_state),
173                         t->suspend_count,
174                         t->suspend_count_delta > 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
175                         t->suspend_count_delta,
176                         bt);
177                 g_free (bt);
178         }
179
180         g_error (err->str);
181         g_string_free (err, TRUE);
182 }
183
184 void
185 assert_gc_safe_mode (void)
186 {
187         MonoThreadInfo *cur = mono_thread_info_current ();
188         int state;
189
190         if (!cur)
191                 assertion_fail ("Expected GC Safe mode but thread is not attached");
192
193         switch (state = mono_thread_info_current_state (cur)) {
194         case STATE_BLOCKING:
195         case STATE_BLOCKING_AND_SUSPENDED:
196                 break;
197         default:
198                 assertion_fail ("Expected GC Safe mode but was in %s state", mono_thread_state_name (state));
199         }
200 }
201
202 void
203 assert_gc_unsafe_mode (void)
204 {
205         MonoThreadInfo *cur = mono_thread_info_current ();
206         int state;
207
208         if (!cur)
209                 assertion_fail ("Expected GC Unsafe mode but thread is not attached");
210
211         switch (state = mono_thread_info_current_state (cur)) {
212         case STATE_RUNNING:
213         case STATE_ASYNC_SUSPEND_REQUESTED:
214         case STATE_SELF_SUSPEND_REQUESTED:
215                 break;
216         default:
217                 assertion_fail ("Expected GC Unsafe mode but was in %s state", mono_thread_state_name (state));
218         }
219 }
220
221 void
222 assert_gc_neutral_mode (void)
223 {
224         MonoThreadInfo *cur = mono_thread_info_current ();
225         int state;
226
227         if (!cur)
228                 assertion_fail ("Expected GC Neutral mode but thread is not attached");
229
230         switch (state = mono_thread_info_current_state (cur)) {
231         case STATE_RUNNING:
232         case STATE_ASYNC_SUSPEND_REQUESTED:
233         case STATE_SELF_SUSPEND_REQUESTED:
234         case STATE_BLOCKING:
235         case STATE_BLOCKING_AND_SUSPENDED:
236                 break;
237         default:
238                 assertion_fail ("Expected GC Neutral mode but was in %s state", mono_thread_state_name (state));
239         }
240 }
241
242 void *
243 critical_gc_region_begin(void)
244 {
245         CheckState *state = get_state ();
246         state->in_gc_critical_region++;
247         return state;
248 }
249
250
251 void
252 critical_gc_region_end(void* token)
253 {
254         CheckState *state = get_state();
255         g_assert (state == token);
256         state->in_gc_critical_region--;
257 }
258
259 void
260 assert_not_in_gc_critical_region(void)
261 {
262         CheckState *state = get_state();
263         if (state->in_gc_critical_region > 0) {
264                 assertion_fail("Expected GC Unsafe mode, but was in %s state", mono_thread_state_name (mono_thread_info_current_state (mono_thread_info_current ())));
265         }
266 }
267
268 void
269 assert_in_gc_critical_region (void)
270 {
271         CheckState *state = get_state();
272         if (state->in_gc_critical_region == 0)
273                 assertion_fail("Expected GC critical region");
274 }
275
276 #endif /* !defined(DISABLE_CHECKED_BUILD_GC) */
277
278 #if !defined(DISABLE_CHECKED_BUILD_METADATA)
279
280 // check_metadata_store et al: The goal of these functions is to verify that if there is a pointer from one mempool into
281 // another, that the pointed-to memory is protected by the reference count mechanism for MonoImages.
282 //
283 // Note: The code below catches only some kinds of failures. Failures outside its scope notably incode:
284 // * Code below absolutely assumes that no mempool is ever held as "mempool" member by more than one Image or ImageSet at once
285 // * Code below assumes reference counts never underflow (ie: if we have a pointer to something, it won't be deallocated while we're looking at it)
286 // Locking strategy is a little slapdash overall.
287
288 // Reference audit support
289 #define check_mempool_assert_message(...) \
290         g_assertion_message("Mempool reference violation: " __VA_ARGS__)
291
292 typedef struct
293 {
294         MonoImage *image;
295         MonoImageSet *image_set;
296 } MonoMemPoolOwner;
297
298 static MonoMemPoolOwner mono_mempool_no_owner = {NULL,NULL};
299
300 static gboolean
301 check_mempool_owner_eq (MonoMemPoolOwner a, MonoMemPoolOwner b)
302 {
303         return a.image == b.image && a.image_set == b.image_set;
304 }
305
306 // Say image X "references" image Y if X either contains Y in its modules field, or X’s "references" field contains an
307 // assembly whose image is Y.
308 // Say image X transitively references image Y if there is any chain of images-referencing-images which leads from X to Y.
309 // Once the mempools for two pointers have been looked up, there are four possibilities:
310
311 // Case 1. Image FROM points to Image TO: Legal if FROM transitively references TO
312
313 // We'll do a simple BFS graph search on images. For each image we visit:
314 static void
315 check_image_search (GHashTable *visited, GPtrArray *next, MonoImage *candidate, MonoImage *goal, gboolean *success)
316 {
317         // Image hasn't even been loaded-- ignore it
318         if (!candidate)
319                 return;
320
321         // Image has already been visited-- ignore it
322         if (g_hash_table_lookup_extended (visited, candidate, NULL, NULL))
323                 return;
324
325         // Image is the target-- mark success
326         if (candidate == goal)
327         {
328                 *success = TRUE;
329                 return;
330         }
331
332         // Unvisited image, queue it to have its children visited
333         g_hash_table_insert (visited, candidate, NULL);
334         g_ptr_array_add (next, candidate);
335         return;
336 }
337
338 static gboolean
339 check_image_may_reference_image(MonoImage *from, MonoImage *to)
340 {
341         if (to == from) // Shortcut
342                 return TRUE;
343
344         // Corlib is never unloaded, and all images implicitly reference it.
345         // Some images avoid explicitly referencing it as an optimization, so special-case it here.
346         if (to == mono_defaults.corlib)
347                 return TRUE;
348
349         // Non-dynamic images may NEVER reference dynamic images
350         if (to->dynamic && !from->dynamic)
351                 return FALSE;
352
353         // FIXME: We currently give a dynamic images a pass on the reference rules.
354         // Dynamic images may ALWAYS reference non-dynamic images.
355         // We allow this because the dynamic image code is known "messy", and in theory it is already
356         // protected because dynamic images can only reference classes their assembly has retained.
357         // However, long term, we should make this rigorous.
358         if (from->dynamic && !to->dynamic)
359                 return TRUE;
360
361         gboolean success = FALSE;
362
363         // Images to inspect on this pass, images to inspect on the next pass
364         GPtrArray *current = g_ptr_array_sized_new (1), *next = g_ptr_array_new ();
365
366         // Because in practice the image graph contains cycles, we must track which images we've visited
367         GHashTable *visited = g_hash_table_new (NULL, NULL);
368
369         #define CHECK_IMAGE_VISIT(i) check_image_search (visited, next, (i), to, &success)
370
371         CHECK_IMAGE_VISIT (from); // Initially "next" contains only from node
372
373         // For each pass exhaust the "to check" queue while filling up the "check next" queue
374         while (!success && next->len > 0) // Halt on success or when out of nodes to process
375         {
376                 // Swap "current" and "next" and clear next
377                 GPtrArray *temp = current;
378                 current = next;
379                 next = temp;
380                 g_ptr_array_set_size (next, 0);
381
382                 int current_idx;
383                 for(current_idx = 0; current_idx < current->len; current_idx++)
384                 {
385                         MonoImage *checking = g_ptr_array_index (current, current_idx); // CAST?
386
387                         mono_image_lock (checking);
388
389                         // For each queued image visit all directly referenced images
390                         int inner_idx;
391
392                         for (inner_idx = 0; !success && inner_idx < checking->module_count; inner_idx++)
393                         {
394                                 CHECK_IMAGE_VISIT (checking->modules[inner_idx]);
395                         }
396
397                         for (inner_idx = 0; !success && inner_idx < checking->nreferences; inner_idx++)
398                         {
399                                 // References are lazy-loaded and thus allowed to be NULL.
400                                 // If they are NULL, we don't care about them for this search, because they haven't impacted ref_count yet.
401                                 if (checking->references[inner_idx])
402                                 {
403                                         CHECK_IMAGE_VISIT (checking->references[inner_idx]->image);
404                                 }
405                         }
406
407                         mono_image_unlock (checking);
408                 }
409         }
410
411         g_ptr_array_free (current, TRUE); g_ptr_array_free (next, TRUE); g_hash_table_destroy (visited);
412
413         return success;
414 }
415
416 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
417 static gboolean
418 check_image_set_may_reference_image (MonoImageSet *from, MonoImage *to)
419 {
420         // See above-- All images implicitly reference corlib
421         if (to == mono_defaults.corlib)
422                 return TRUE;
423
424         int idx;
425         gboolean success = FALSE;
426         mono_image_set_lock (from);
427         for (idx = 0; !success && idx < from->nimages; idx++)
428         {
429                 if (check_image_may_reference_image (from->images[idx], to))
430                         success = TRUE;
431         }
432         mono_image_set_unlock (from);
433
434         return success; // No satisfying image found in from->images
435 }
436
437 // Case 3. ImageSet FROM points to ImageSet TO: The images in TO are a strict subset of FROM (no transitive relationship is important here)
438 static gboolean
439 check_image_set_may_reference_image_set (MonoImageSet *from, MonoImageSet *to)
440 {
441         if (to == from)
442                 return TRUE;
443
444         gboolean valid = TRUE; // Until proven otherwise
445
446         mono_image_set_lock (from); mono_image_set_lock (to);
447
448         int to_idx, from_idx;
449         for (to_idx = 0; valid && to_idx < to->nimages; to_idx++)
450         {
451                 gboolean seen = FALSE;
452
453                 // If TO set includes corlib, the FROM set may
454                 // implicitly reference corlib, even if it's not
455                 // present in the set explicitly.
456                 if (to->images[to_idx] == mono_defaults.corlib)
457                         seen = TRUE;
458
459                 // For each item in to->images, scan over from->images looking for it.
460                 for (from_idx = 0; !seen && from_idx < from->nimages; from_idx++)
461                 {
462                         if (to->images[to_idx] == from->images[from_idx])
463                                 seen = TRUE;
464                 }
465
466                 // If the to->images item is not found in from->images, the subset check has failed
467                 if (!seen)
468                         valid = FALSE;
469         }
470
471         mono_image_set_unlock (from); mono_image_set_unlock (to);
472
473         return valid; // All items in "to" were found in "from"
474 }
475
476 // Case 4. Image FROM points to ImageSet TO: FROM transitively references *ALL* of the “images” listed in TO
477 static gboolean
478 check_image_may_reference_image_set (MonoImage *from, MonoImageSet *to)
479 {
480         if (to->nimages == 0) // Malformed image_set
481                 return FALSE;
482
483         gboolean valid = TRUE;
484
485         mono_image_set_lock (to);
486         int idx;
487         for (idx = 0; valid && idx < to->nimages; idx++)
488         {
489                 if (!check_image_may_reference_image (from, to->images[idx]))
490                         valid = FALSE;
491         }
492         mono_image_set_unlock (to);
493
494         return valid; // All images in to->images checked out
495 }
496
497 // Small helper-- get a descriptive string for a MonoMemPoolOwner
498 // Callers are obligated to free buffer with g_free after use
499 static const char *
500 check_mempool_owner_name (MonoMemPoolOwner owner)
501 {
502         GString *result = g_string_new (NULL);
503         if (owner.image)
504         {
505                 if (owner.image->dynamic)
506                         g_string_append (result, "(Dynamic)");
507                 g_string_append (result, owner.image->name);
508         }
509         else if (owner.image_set)
510         {
511                 char *temp = mono_image_set_description (owner.image_set);
512                 g_string_append (result, "(Image set)");
513                 g_string_append (result, temp);
514                 g_free (temp);
515         }
516         else
517         {
518                 g_string_append (result, "(Non-image memory)");
519         }
520         return g_string_free (result, FALSE);
521 }
522
523 // Helper -- surf various image-locating functions looking for the owner of this pointer
524 static MonoMemPoolOwner
525 mono_find_mempool_owner (void *ptr)
526 {
527         MonoMemPoolOwner owner = mono_mempool_no_owner;
528
529         owner.image = mono_find_image_owner (ptr);
530         if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
531                 return owner;
532
533         owner.image_set = mono_find_image_set_owner (ptr);
534         if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
535                 return owner;
536
537         owner.image = mono_find_dynamic_image_owner (ptr);
538
539         return owner;
540 }
541
542 // Actually perform reference audit
543 static void
544 check_mempool_may_reference_mempool (void *from_ptr, void *to_ptr, gboolean require_local)
545 {
546         // Null pointers are OK
547         if (!to_ptr)
548                 return;
549
550         MonoMemPoolOwner from = mono_find_mempool_owner (from_ptr), to = mono_find_mempool_owner (to_ptr);
551
552         if (require_local)
553         {
554                 if (!check_mempool_owner_eq (from,to))
555                         check_mempool_assert_message ("Pointer in image %s should have been internal, but instead pointed to image %s", check_mempool_owner_name (from), check_mempool_owner_name (to));
556         }
557
558         // Writing into unknown mempool
559         else if (check_mempool_owner_eq (from, mono_mempool_no_owner))
560         {
561                 check_mempool_assert_message ("Non-image memory attempting to write pointer to image %s", check_mempool_owner_name (to));
562         }
563
564         // Reading from unknown mempool
565         else if (check_mempool_owner_eq (to, mono_mempool_no_owner))
566         {
567                 check_mempool_assert_message ("Attempting to write pointer from image %s to non-image memory", check_mempool_owner_name (from));
568         }
569
570         // Split out the four cases described above:
571         else if (from.image && to.image)
572         {
573                 if (!check_image_may_reference_image (from.image, to.image))
574                         check_mempool_assert_message ("Image %s tried to point to image %s, but does not retain a reference", check_mempool_owner_name (from), check_mempool_owner_name (to));
575         }
576
577         else if (from.image && to.image_set)
578         {
579                 if (!check_image_may_reference_image_set (from.image, to.image_set))
580                         check_mempool_assert_message ("Image %s tried to point to image set %s, but does not retain a reference", check_mempool_owner_name (from), check_mempool_owner_name (to));
581         }
582
583         else if (from.image_set && to.image_set)
584         {
585                 if (!check_image_set_may_reference_image_set (from.image_set, to.image_set))
586                         check_mempool_assert_message ("Image set %s tried to point to image set %s, but does not retain a reference", check_mempool_owner_name (from), check_mempool_owner_name (to));
587         }
588
589         else if (from.image_set && to.image)
590         {
591                 if (!check_image_set_may_reference_image (from.image_set, to.image))
592                         check_mempool_assert_message ("Image set %s tried to point to image %s, but does not retain a reference", check_mempool_owner_name (from), check_mempool_owner_name (to));
593         }
594
595         else
596         {
597                 check_mempool_assert_message ("Internal logic error: Unreachable code");
598         }
599 }
600
601 void
602 check_metadata_store (void *from, void *to)
603 {
604     check_mempool_may_reference_mempool (from, to, FALSE);
605 }
606
607 void
608 check_metadata_store_local (void *from, void *to)
609 {
610     check_mempool_may_reference_mempool (from, to, TRUE);
611 }
612
613 #endif /* !defined(DISABLE_CHECKED_BUILD_METADATA) */
614
615 #endif /* CHECKED_BUILD */