[coop] Checked build GC critical regions.
[mono-project.git] / mono / utils / checked-build.c
blob5c4fd4249e9acd79cd6635d5a0bd82504be451f2
1 /*
2 * checked-build.c: Expensive asserts used when mono is built with --with-checked-build=yes
4 * Author:
5 * Rodrigo Kumpera (kumpera@gmail.com)
7 * (C) 2015 Xamarin
8 */
9 #include <config.h>
10 #ifdef CHECKED_BUILD
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>
22 #define MAX_NATIVE_BT 6
23 #define MAX_NATIVE_BT_PROBE (MAX_NATIVE_BT + 5)
24 #define MAX_TRANSITIONS 3
27 #ifdef HAVE_BACKTRACE_SYMBOLS
28 #include <execinfo.h>
30 //XXX We should collect just the IPs and lazily symbolificate them.
31 static int
32 collect_backtrace (gpointer out_data[])
34 return backtrace (out_data, MAX_NATIVE_BT_PROBE);
37 static char*
38 translate_backtrace (gpointer native_trace[], int size)
40 char **names = backtrace_symbols (native_trace, size);
41 GString* bt = g_string_sized_new (100);
43 int i, j = -1;
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;
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]);
61 free (names);
62 return g_string_free (bt, FALSE);
65 #else
67 static int
68 collect_backtrace (gpointer out_data[])
70 return 0;
73 static char*
74 translate_backtrace (gpointer native_trace[], int size)
76 return g_strdup ("\tno backtrace available\n");
79 #endif
82 typedef struct {
83 GPtrArray *transitions;
84 gboolean in_gc_critical_region;
85 } CheckState;
87 typedef struct {
88 const char *name;
89 int from_state, next_state, suspend_count, suspend_count_delta, size;
90 gpointer backtrace [MAX_NATIVE_BT_PROBE];
91 } ThreadTransition;
93 static MonoNativeTlsKey thread_status;
95 void
96 checked_build_init (void)
98 mono_native_tls_alloc (&thread_status, NULL);
101 static CheckState*
102 get_state (void)
104 CheckState *state = mono_native_tls_get_value (thread_status);
105 if (!state) {
106 state = g_new0 (CheckState, 1);
107 state->transitions = g_ptr_array_new ();
108 mono_native_tls_set_value (thread_status, state);
111 return state;
114 static void
115 free_transition (ThreadTransition *t)
117 g_free (t);
120 void
121 checked_build_thread_transition (const char *transition, void *info, int from_state, int suspend_count, int next_state, int suspend_count_delta)
123 MonoThreadInfo *cur = mono_thread_info_current_unchecked ();
124 CheckState *state = get_state ();
125 /* We currently don't record external changes as those are hard to reason about. */
126 if (cur != info)
127 return;
129 if (state->transitions->len >= MAX_TRANSITIONS)
130 free_transition (g_ptr_array_remove_index (state->transitions, 0));
132 ThreadTransition *t = g_new0 (ThreadTransition, 1);
133 t->name = transition;
134 t->from_state = from_state;
135 t->next_state = next_state;
136 t->suspend_count = suspend_count;
137 t->suspend_count_delta = suspend_count_delta;
138 t->size = collect_backtrace (t->backtrace);
139 g_ptr_array_add (state->transitions, t);
142 static void
143 assertion_fail (const char *msg, ...)
145 int i;
146 GString* err = g_string_sized_new (100);
147 CheckState *state = get_state ();
149 g_string_append_printf (err, "Assertion failure in thread %p due to: ", mono_native_thread_id_get ());
151 va_list args;
152 va_start (args, msg);
153 g_string_append_vprintf (err, msg, args);
154 va_end (args);
156 g_string_append_printf (err, "\nLast %d state transitions: (most recent first)\n", state->transitions->len);
158 for (i = state->transitions->len - 1; i >= 0; --i) {
159 ThreadTransition *t = state->transitions->pdata [i];
160 char *bt = translate_backtrace (t->backtrace, t->size);
161 g_string_append_printf (err, "[%s] %s -> %s (%d) %s%d at:\n%s",
162 t->name,
163 mono_thread_state_name (t->from_state),
164 mono_thread_state_name (t->next_state),
165 t->suspend_count,
166 t->suspend_count_delta > 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
167 t->suspend_count_delta,
168 bt);
169 g_free (bt);
172 g_error (err->str);
173 g_string_free (err, TRUE);
176 void
177 assert_gc_safe_mode (void)
179 MonoThreadInfo *cur = mono_thread_info_current ();
180 int state;
182 if (!cur)
183 assertion_fail ("Expected GC Safe mode but thread is not attached");
185 switch (state = mono_thread_info_current_state (cur)) {
186 case STATE_BLOCKING:
187 case STATE_BLOCKING_AND_SUSPENDED:
188 break;
189 default:
190 assertion_fail ("Expected GC Safe mode but was in %s state", mono_thread_state_name (state));
194 void
195 assert_gc_unsafe_mode (void)
197 MonoThreadInfo *cur = mono_thread_info_current ();
198 int state;
200 if (!cur)
201 assertion_fail ("Expected GC Unsafe mode but thread is not attached");
203 switch (state = mono_thread_info_current_state (cur)) {
204 case STATE_RUNNING:
205 case STATE_ASYNC_SUSPEND_REQUESTED:
206 case STATE_SELF_SUSPEND_REQUESTED:
207 break;
208 default:
209 assertion_fail ("Expected GC Unsafe mode but was in %s state", mono_thread_state_name (state));
213 void
214 assert_gc_neutral_mode (void)
216 MonoThreadInfo *cur = mono_thread_info_current ();
217 int state;
219 if (!cur)
220 assertion_fail ("Expected GC Neutral mode but thread is not attached");
222 switch (state = mono_thread_info_current_state (cur)) {
223 case STATE_RUNNING:
224 case STATE_ASYNC_SUSPEND_REQUESTED:
225 case STATE_SELF_SUSPEND_REQUESTED:
226 case STATE_BLOCKING:
227 case STATE_BLOCKING_AND_SUSPENDED:
228 break;
229 default:
230 assertion_fail ("Expected GC Neutral mode but was in %s state", mono_thread_state_name (state));
234 void *
235 critical_gc_region_begin(void)
237 CheckState *state = get_state ();
238 state->in_gc_critical_region = TRUE;
239 return state;
243 void
244 critical_gc_region_end(void* token)
246 CheckState *state = get_state();
247 g_assert (state == token);
248 state->in_gc_critical_region = FALSE;
251 void
252 assert_not_in_gc_critical_region(void)
254 CheckState *state = get_state();
255 if (state->in_gc_critical_region) {
256 MonoThreadInfo *cur = mono_thread_info_current();
257 state = mono_thread_info_current_state(cur);
258 assertion_fail("Expected GC Unsafe mode, but was in %s state", mono_thread_state_name(state));
262 // check_metadata_store et al: The goal of these functions is to verify that if there is a pointer from one mempool into
263 // another, that the pointed-to memory is protected by the reference count mechanism for MonoImages.
265 // Note: The code below catches only some kinds of failures. Failures outside its scope notably incode:
266 // * Code below absolutely assumes that no mempool is ever held as "mempool" member by more than one Image or ImageSet at once
267 // * 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)
268 // Locking strategy is a little slapdash overall.
270 // Reference audit support
271 #define check_mempool_assert_message(...) \
272 g_assertion_message("Mempool reference violation: " __VA_ARGS__)
274 typedef struct
276 MonoImage *image;
277 MonoImageSet *image_set;
278 } MonoMemPoolOwner;
280 static MonoMemPoolOwner mono_mempool_no_owner = {NULL,NULL};
282 static gboolean
283 check_mempool_owner_eq (MonoMemPoolOwner a, MonoMemPoolOwner b)
285 return a.image == b.image && a.image_set == b.image_set;
288 // Say image X "references" image Y if X either contains Y in its modules field, or X’s "references" field contains an
289 // assembly whose image is Y.
290 // Say image X transitively references image Y if there is any chain of images-referencing-images which leads from X to Y.
291 // Once the mempools for two pointers have been looked up, there are four possibilities:
293 // Case 1. Image FROM points to Image TO: Legal if FROM transitively references TO
295 // We'll do a simple BFS graph search on images. For each image we visit:
296 static void
297 check_image_search (GHashTable *visited, GPtrArray *next, MonoImage *candidate, MonoImage *goal, gboolean *success)
299 // Image hasn't even been loaded-- ignore it
300 if (!candidate)
301 return;
303 // Image has already been visited-- ignore it
304 if (g_hash_table_lookup_extended (visited, candidate, NULL, NULL))
305 return;
307 // Image is the target-- mark success
308 if (candidate == goal)
310 *success = TRUE;
311 return;
314 // Unvisited image, queue it to have its children visited
315 g_hash_table_insert (visited, candidate, NULL);
316 g_ptr_array_add (next, candidate);
317 return;
320 static gboolean
321 check_image_may_reference_image(MonoImage *from, MonoImage *to)
323 if (to == from) // Shortcut
324 return TRUE;
326 // Corlib is never unloaded, and all images implicitly reference it.
327 // Some images avoid explicitly referencing it as an optimization, so special-case it here.
328 if (to == mono_defaults.corlib)
329 return TRUE;
331 // Non-dynamic images may NEVER reference dynamic images
332 if (to->dynamic && !from->dynamic)
333 return FALSE;
335 // FIXME: We currently give a dynamic images a pass on the reference rules.
336 // Dynamic images may ALWAYS reference non-dynamic images.
337 // We allow this because the dynamic image code is known "messy", and in theory it is already
338 // protected because dynamic images can only reference classes their assembly has retained.
339 // However, long term, we should make this rigorous.
340 if (from->dynamic && !to->dynamic)
341 return TRUE;
343 gboolean success = FALSE;
345 // Images to inspect on this pass, images to inspect on the next pass
346 GPtrArray *current = g_ptr_array_sized_new (1), *next = g_ptr_array_new ();
348 // Because in practice the image graph contains cycles, we must track which images we've visited
349 GHashTable *visited = g_hash_table_new (NULL, NULL);
351 #define CHECK_IMAGE_VISIT(i) check_image_search (visited, next, (i), to, &success)
353 CHECK_IMAGE_VISIT (from); // Initially "next" contains only from node
355 // For each pass exhaust the "to check" queue while filling up the "check next" queue
356 while (!success && next->len > 0) // Halt on success or when out of nodes to process
358 // Swap "current" and "next" and clear next
359 GPtrArray *temp = current;
360 current = next;
361 next = temp;
362 g_ptr_array_set_size (next, 0);
364 int current_idx;
365 for(current_idx = 0; current_idx < current->len; current_idx++)
367 MonoImage *checking = g_ptr_array_index (current, current_idx); // CAST?
369 mono_image_lock (checking);
371 // For each queued image visit all directly referenced images
372 int inner_idx;
374 for (inner_idx = 0; !success && inner_idx < checking->module_count; inner_idx++)
376 CHECK_IMAGE_VISIT (checking->modules[inner_idx]);
379 for (inner_idx = 0; !success && inner_idx < checking->nreferences; inner_idx++)
381 // References are lazy-loaded and thus allowed to be NULL.
382 // If they are NULL, we don't care about them for this search, because they haven't impacted ref_count yet.
383 if (checking->references[inner_idx])
385 CHECK_IMAGE_VISIT (checking->references[inner_idx]->image);
389 mono_image_unlock (checking);
393 g_ptr_array_free (current, TRUE); g_ptr_array_free (next, TRUE); g_hash_table_destroy (visited);
395 return success;
398 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
399 static gboolean
400 check_image_set_may_reference_image (MonoImageSet *from, MonoImage *to)
402 // See above-- All images implicitly reference corlib
403 if (to == mono_defaults.corlib)
404 return TRUE;
406 int idx;
407 gboolean success = FALSE;
408 mono_image_set_lock (from);
409 for (idx = 0; !success && idx < from->nimages; idx++)
411 if (check_image_may_reference_image (from->images[idx], to))
412 success = TRUE;
414 mono_image_set_unlock (from);
416 return success; // No satisfying image found in from->images
419 // Case 3. ImageSet FROM points to ImageSet TO: The images in TO are a strict subset of FROM (no transitive relationship is important here)
420 static gboolean
421 check_image_set_may_reference_image_set (MonoImageSet *from, MonoImageSet *to)
423 if (to == from)
424 return TRUE;
426 gboolean valid = TRUE; // Until proven otherwise
428 mono_image_set_lock (from); mono_image_set_lock (to);
430 int to_idx, from_idx;
431 for (to_idx = 0; valid && to_idx < to->nimages; to_idx++)
433 gboolean seen = FALSE;
435 // For each item in to->images, scan over from->images looking for it.
436 for (from_idx = 0; !seen && from_idx < from->nimages; from_idx++)
438 if (to->images[to_idx] == from->images[from_idx])
439 seen = TRUE;
442 // If the to->images item is not found in from->images, the subset check has failed
443 if (!seen)
444 valid = FALSE;
447 mono_image_set_unlock (from); mono_image_set_unlock (to);
449 return valid; // All items in "to" were found in "from"
452 // Case 4. Image FROM points to ImageSet TO: FROM transitively references *ALL* of the “images” listed in TO
453 static gboolean
454 check_image_may_reference_image_set (MonoImage *from, MonoImageSet *to)
456 if (to->nimages == 0) // Malformed image_set
457 return FALSE;
459 gboolean valid = TRUE;
461 mono_image_set_lock (to);
462 int idx;
463 for (idx = 0; valid && idx < to->nimages; idx++)
465 if (!check_image_may_reference_image (from, to->images[idx]))
466 valid = FALSE;
468 mono_image_set_unlock (to);
470 return valid; // All images in to->images checked out
473 // Small helper-- get a descriptive string for a MonoMemPoolOwner
474 // Callers are obligated to free buffer with g_free after use
475 static const char *
476 check_mempool_owner_name (MonoMemPoolOwner owner)
478 GString *result = g_string_new (NULL);
479 if (owner.image)
481 if (owner.image->dynamic)
482 g_string_append (result, "(Dynamic)");
483 g_string_append (result, owner.image->name);
485 else if (owner.image_set)
487 char *temp = mono_image_set_description (owner.image_set);
488 g_string_append (result, "(Image set)");
489 g_string_append (result, temp);
490 g_free (temp);
492 else
494 g_string_append (result, "(Non-image memory)");
496 return g_string_free (result, FALSE);
499 // Helper -- surf various image-locating functions looking for the owner of this pointer
500 static MonoMemPoolOwner
501 mono_find_mempool_owner (void *ptr)
503 MonoMemPoolOwner owner = mono_mempool_no_owner;
505 owner.image = mono_find_image_owner (ptr);
506 if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
507 return owner;
509 owner.image_set = mono_find_image_set_owner (ptr);
510 if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
511 return owner;
513 owner.image = mono_find_dynamic_image_owner (ptr);
515 return owner;
518 // Actually perform reference audit
519 static void
520 check_mempool_may_reference_mempool (void *from_ptr, void *to_ptr, gboolean require_local)
522 // Null pointers are OK
523 if (!to_ptr)
524 return;
526 MonoMemPoolOwner from = mono_find_mempool_owner (from_ptr), to = mono_find_mempool_owner (to_ptr);
528 if (require_local)
530 if (!check_mempool_owner_eq (from,to))
531 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));
534 // Writing into unknown mempool
535 else if (check_mempool_owner_eq (from, mono_mempool_no_owner))
537 check_mempool_assert_message ("Non-image memory attempting to write pointer to image %s", check_mempool_owner_name (to));
540 // Reading from unknown mempool
541 else if (check_mempool_owner_eq (to, mono_mempool_no_owner))
543 check_mempool_assert_message ("Attempting to write pointer from image %s to non-image memory", check_mempool_owner_name (from));
546 // Split out the four cases described above:
547 else if (from.image && to.image)
549 if (!check_image_may_reference_image (from.image, to.image))
550 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));
553 else if (from.image && to.image_set)
555 if (!check_image_may_reference_image_set (from.image, to.image_set))
556 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));
559 else if (from.image_set && to.image_set)
561 if (!check_image_set_may_reference_image_set (from.image_set, to.image_set))
562 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));
565 else if (from.image_set && to.image)
567 if (!check_image_set_may_reference_image (from.image_set, to.image))
568 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));
571 else
573 check_mempool_assert_message ("Internal logic error: Unreachable code");
577 void
578 check_metadata_store (void *from, void *to)
580 check_mempool_may_reference_mempool (from, to, FALSE);
583 void
584 check_metadata_store_local (void *from, void *to)
586 check_mempool_may_reference_mempool (from, to, TRUE);
589 #endif /* CHECKED_BUILD */