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