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