[Facades] Add netstandard
[mono-project.git] / mono / utils / checked-build.c
blob09926ffcd5f2266ce632b71bf837ec8dc73c9c5e
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>
11 #ifdef ENABLE_CHECKED_BUILD
13 #include <mono/utils/checked-build.h>
14 #include <mono/utils/mono-threads.h>
15 #include <mono/utils/mono-threads-coop.h>
16 #include <mono/utils/mono-tls.h>
17 #include <mono/metadata/mempool.h>
18 #include <mono/metadata/metadata-internals.h>
19 #include <mono/metadata/image-internals.h>
20 #include <mono/metadata/class-internals.h>
21 #include <mono/metadata/reflection-internals.h>
22 #include <glib.h>
24 #ifdef HAVE_BACKTRACE_SYMBOLS
25 #include <execinfo.h>
26 #endif
28 // Selective-enable support
30 // Returns true for check modes which are allowed by both the current DISABLE_ macros and the MONO_CHECK_MODE env var.
31 // Argument may be a bitmask; if so, result is true if at least one specified mode is enabled.
32 mono_bool
33 mono_check_mode_enabled (MonoCheckMode query)
35 static MonoCheckMode check_mode = MONO_CHECK_MODE_UNKNOWN;
36 if (G_UNLIKELY (check_mode == MONO_CHECK_MODE_UNKNOWN))
38 MonoCheckMode env_check_mode = MONO_CHECK_MODE_NONE;
39 const gchar *env_string = g_getenv ("MONO_CHECK_MODE");
41 if (env_string)
43 gchar **env_split = g_strsplit (env_string, ",", 0);
44 for (gchar **env_component = env_split; *env_component; env_component++)
46 mono_bool check_all = g_str_equal (*env_component, "all");
47 #ifdef ENABLE_CHECKED_BUILD_GC
48 if (check_all || g_str_equal (*env_component, "gc"))
49 env_check_mode |= MONO_CHECK_MODE_GC;
50 #endif
51 #ifdef ENABLE_CHECKED_BUILD_METADATA
52 if (check_all || g_str_equal (*env_component, "metadata"))
53 env_check_mode |= MONO_CHECK_MODE_METADATA;
54 #endif
55 #ifdef ENABLE_CHECKED_BUILD_THREAD
56 if (check_all || g_str_equal (*env_component, "thread"))
57 env_check_mode |= MONO_CHECK_MODE_THREAD;
58 #endif
60 g_strfreev (env_split);
63 check_mode = env_check_mode;
65 return check_mode & query;
68 static int
69 mono_check_transition_limit (void)
71 static int transition_limit = -1;
72 if (transition_limit < 0) {
73 const gchar *env_string = g_getenv ("MONO_CHECK_THREAD_TRANSITION_HISTORY");
74 if (env_string)
75 transition_limit = atoi (env_string);
76 else
77 transition_limit = 3;
79 return transition_limit;
82 typedef struct {
83 GPtrArray *transitions;
84 guint32 in_gc_critical_region;
85 } CheckState;
87 static MonoNativeTlsKey thread_status;
89 void
90 checked_build_init (void)
92 // Init state for get_state, which can be called either by gc or thread mode
93 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC | MONO_CHECK_MODE_THREAD))
94 mono_native_tls_alloc (&thread_status, NULL);
97 static CheckState*
98 get_state (void)
100 CheckState *state = mono_native_tls_get_value (thread_status);
101 if (!state) {
102 state = g_new0 (CheckState, 1);
103 state->transitions = g_ptr_array_new ();
104 mono_native_tls_set_value (thread_status, state);
107 return state;
110 #ifdef ENABLE_CHECKED_BUILD_THREAD
112 #define MAX_NATIVE_BT 6
113 #define MAX_NATIVE_BT_PROBE (MAX_NATIVE_BT + 5)
114 #define MAX_TRANSITIONS (mono_check_transition_limit ())
116 #ifdef HAVE_BACKTRACE_SYMBOLS
118 //XXX We should collect just the IPs and lazily symbolificate them.
119 static int
120 collect_backtrace (gpointer out_data[])
122 return backtrace (out_data, MAX_NATIVE_BT_PROBE);
125 static char*
126 translate_backtrace (gpointer native_trace[], int size)
128 char **names = backtrace_symbols (native_trace, size);
129 GString* bt = g_string_sized_new (100);
131 int i, j = -1;
133 //Figure out the cut point of useless backtraces
134 //We'll skip up to the caller of checked_build_thread_transition
135 for (i = 0; i < size; ++i) {
136 if (strstr (names [i], "checked_build_thread_transition")) {
137 j = i + 1;
138 break;
142 if (j == -1)
143 j = 0;
144 for (i = j; i < size; ++i) {
145 if (i - j <= MAX_NATIVE_BT)
146 g_string_append_printf (bt, "\tat %s\n", names [i]);
149 g_free (names);
150 return g_string_free (bt, FALSE);
153 #else
155 static int
156 collect_backtrace (gpointer out_data[])
158 return 0;
161 static char*
162 translate_backtrace (gpointer native_trace[], int size)
164 return g_strdup ("\tno backtrace available\n");
167 #endif
169 typedef struct {
170 const char *name;
171 int from_state, next_state, suspend_count, suspend_count_delta, size;
172 gpointer backtrace [MAX_NATIVE_BT_PROBE];
173 } ThreadTransition;
175 static void
176 free_transition (ThreadTransition *t)
178 g_free (t);
181 void
182 checked_build_thread_transition (const char *transition, void *info, int from_state, int suspend_count, int next_state, int suspend_count_delta)
184 if (!mono_check_mode_enabled (MONO_CHECK_MODE_THREAD))
185 return;
187 CheckState *state = get_state ();
188 /* We currently don't record external changes as those are hard to reason about. */
189 if (!mono_thread_info_is_current (info))
190 return;
192 if (state->transitions->len >= MAX_TRANSITIONS)
193 free_transition (g_ptr_array_remove_index (state->transitions, 0));
195 ThreadTransition *t = g_new0 (ThreadTransition, 1);
196 t->name = transition;
197 t->from_state = from_state;
198 t->next_state = next_state;
199 t->suspend_count = suspend_count;
200 t->suspend_count_delta = suspend_count_delta;
201 t->size = collect_backtrace (t->backtrace);
202 g_ptr_array_add (state->transitions, t);
205 void
206 mono_fatal_with_history (const char *msg, ...)
208 int i;
209 GString* err = g_string_sized_new (100);
211 g_string_append_printf (err, "Assertion failure in thread %p due to: ", mono_native_thread_id_get ());
213 va_list args;
214 va_start (args, msg);
215 g_string_append_vprintf (err, msg, args);
216 va_end (args);
218 if (mono_check_mode_enabled (MONO_CHECK_MODE_THREAD))
220 CheckState *state = get_state ();
222 g_string_append_printf (err, "\nLast %d state transitions: (most recent first)\n", state->transitions->len);
224 for (i = state->transitions->len - 1; i >= 0; --i) {
225 ThreadTransition *t = state->transitions->pdata [i];
226 char *bt = translate_backtrace (t->backtrace, t->size);
227 g_string_append_printf (err, "[%s] %s -> %s (%d) %s%d at:\n%s",
228 t->name,
229 mono_thread_state_name (t->from_state),
230 mono_thread_state_name (t->next_state),
231 t->suspend_count,
232 t->suspend_count_delta > 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
233 t->suspend_count_delta,
234 bt);
235 g_free (bt);
239 g_error (err->str);
240 g_string_free (err, TRUE);
243 #endif /* defined(ENABLE_CHECKED_BUILD_THREAD) */
245 #ifdef ENABLE_CHECKED_BUILD_GC
247 void
248 assert_gc_safe_mode (void)
250 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
251 return;
253 MonoThreadInfo *cur = mono_thread_info_current ();
254 int state;
256 if (!cur)
257 mono_fatal_with_history ("Expected GC Safe mode but thread is not attached");
259 switch (state = mono_thread_info_current_state (cur)) {
260 case STATE_BLOCKING:
261 case STATE_BLOCKING_AND_SUSPENDED:
262 break;
263 default:
264 mono_fatal_with_history ("Expected GC Safe mode but was in %s state", mono_thread_state_name (state));
268 void
269 assert_gc_unsafe_mode (void)
271 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
272 return;
274 MonoThreadInfo *cur = mono_thread_info_current ();
275 int state;
277 if (!cur)
278 mono_fatal_with_history ("Expected GC Unsafe mode but thread is not attached");
280 switch (state = mono_thread_info_current_state (cur)) {
281 case STATE_RUNNING:
282 case STATE_ASYNC_SUSPEND_REQUESTED:
283 case STATE_SELF_SUSPEND_REQUESTED:
284 break;
285 default:
286 mono_fatal_with_history ("Expected GC Unsafe mode but was in %s state", mono_thread_state_name (state));
290 void
291 assert_gc_neutral_mode (void)
293 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
294 return;
296 MonoThreadInfo *cur = mono_thread_info_current ();
297 int state;
299 if (!cur)
300 mono_fatal_with_history ("Expected GC Neutral mode but thread is not attached");
302 switch (state = mono_thread_info_current_state (cur)) {
303 case STATE_RUNNING:
304 case STATE_ASYNC_SUSPEND_REQUESTED:
305 case STATE_SELF_SUSPEND_REQUESTED:
306 case STATE_BLOCKING:
307 case STATE_BLOCKING_AND_SUSPENDED:
308 break;
309 default:
310 mono_fatal_with_history ("Expected GC Neutral mode but was in %s state", mono_thread_state_name (state));
314 void *
315 critical_gc_region_begin(void)
317 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
318 return NULL;
320 CheckState *state = get_state ();
321 state->in_gc_critical_region++;
322 return state;
326 void
327 critical_gc_region_end(void* token)
329 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
330 return;
332 CheckState *state = get_state();
333 g_assert (state == token);
334 state->in_gc_critical_region--;
337 void
338 assert_not_in_gc_critical_region(void)
340 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
341 return;
343 CheckState *state = get_state();
344 if (state->in_gc_critical_region > 0) {
345 mono_fatal_with_history("Expected GC Unsafe mode, but was in %s state", mono_thread_state_name (mono_thread_info_current_state (mono_thread_info_current ())));
349 void
350 assert_in_gc_critical_region (void)
352 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
353 return;
355 CheckState *state = get_state();
356 if (state->in_gc_critical_region == 0)
357 mono_fatal_with_history("Expected GC critical region");
360 #endif /* defined(ENABLE_CHECKED_BUILD_GC) */
362 #ifdef ENABLE_CHECKED_BUILD_METADATA
364 // check_metadata_store et al: The goal of these functions is to verify that if there is a pointer from one mempool into
365 // another, that the pointed-to memory is protected by the reference count mechanism for MonoImages.
367 // Note: The code below catches only some kinds of failures. Failures outside its scope notably incode:
368 // * Code below absolutely assumes that no mempool is ever held as "mempool" member by more than one Image or ImageSet at once
369 // * 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)
370 // Locking strategy is a little slapdash overall.
372 // Reference audit support
373 #define check_mempool_assert_message(...) \
374 g_assertion_message("Mempool reference violation: " __VA_ARGS__)
376 typedef struct
378 MonoImage *image;
379 MonoImageSet *image_set;
380 } MonoMemPoolOwner;
382 static MonoMemPoolOwner mono_mempool_no_owner = {NULL,NULL};
384 static gboolean
385 check_mempool_owner_eq (MonoMemPoolOwner a, MonoMemPoolOwner b)
387 return a.image == b.image && a.image_set == b.image_set;
390 // Say image X "references" image Y if X either contains Y in its modules field, or X’s "references" field contains an
391 // assembly whose image is Y.
392 // Say image X transitively references image Y if there is any chain of images-referencing-images which leads from X to Y.
393 // Once the mempools for two pointers have been looked up, there are four possibilities:
395 // Case 1. Image FROM points to Image TO: Legal if FROM transitively references TO
397 // We'll do a simple BFS graph search on images. For each image we visit:
398 static void
399 check_image_search (GHashTable *visited, GPtrArray *next, MonoImage *candidate, MonoImage *goal, gboolean *success)
401 // Image hasn't even been loaded-- ignore it
402 if (!candidate)
403 return;
405 // Image has already been visited-- ignore it
406 if (g_hash_table_lookup_extended (visited, candidate, NULL, NULL))
407 return;
409 // Image is the target-- mark success
410 if (candidate == goal)
412 *success = TRUE;
413 return;
416 // Unvisited image, queue it to have its children visited
417 g_hash_table_insert (visited, candidate, NULL);
418 g_ptr_array_add (next, candidate);
419 return;
422 static gboolean
423 check_image_may_reference_image(MonoImage *from, MonoImage *to)
425 if (to == from) // Shortcut
426 return TRUE;
428 // Corlib is never unloaded, and all images implicitly reference it.
429 // Some images avoid explicitly referencing it as an optimization, so special-case it here.
430 if (to == mono_defaults.corlib)
431 return TRUE;
433 // Non-dynamic images may NEVER reference dynamic images
434 if (to->dynamic && !from->dynamic)
435 return FALSE;
437 // FIXME: We currently give a dynamic images a pass on the reference rules.
438 // Dynamic images may ALWAYS reference non-dynamic images.
439 // We allow this because the dynamic image code is known "messy", and in theory it is already
440 // protected because dynamic images can only reference classes their assembly has retained.
441 // However, long term, we should make this rigorous.
442 if (from->dynamic && !to->dynamic)
443 return TRUE;
445 gboolean success = FALSE;
447 // Images to inspect on this pass, images to inspect on the next pass
448 GPtrArray *current = g_ptr_array_sized_new (1), *next = g_ptr_array_new ();
450 // Because in practice the image graph contains cycles, we must track which images we've visited
451 GHashTable *visited = g_hash_table_new (NULL, NULL);
453 #define CHECK_IMAGE_VISIT(i) check_image_search (visited, next, (i), to, &success)
455 CHECK_IMAGE_VISIT (from); // Initially "next" contains only from node
457 // For each pass exhaust the "to check" queue while filling up the "check next" queue
458 while (!success && next->len > 0) // Halt on success or when out of nodes to process
460 // Swap "current" and "next" and clear next
461 GPtrArray *temp = current;
462 current = next;
463 next = temp;
464 g_ptr_array_set_size (next, 0);
466 int current_idx;
467 for(current_idx = 0; current_idx < current->len; current_idx++)
469 MonoImage *checking = g_ptr_array_index (current, current_idx); // CAST?
471 mono_image_lock (checking);
473 // For each queued image visit all directly referenced images
474 int inner_idx;
476 // 'files' and 'modules' semantically contain the same items but because of lazy loading we must check both
477 for (inner_idx = 0; !success && inner_idx < checking->file_count; inner_idx++)
479 CHECK_IMAGE_VISIT (checking->files[inner_idx]);
482 for (inner_idx = 0; !success && inner_idx < checking->module_count; inner_idx++)
484 CHECK_IMAGE_VISIT (checking->modules[inner_idx]);
487 for (inner_idx = 0; !success && inner_idx < checking->nreferences; inner_idx++)
489 // Assembly references are lazy-loaded and thus allowed to be NULL.
490 // If they are NULL, we don't care about them for this search, because their images haven't impacted ref_count yet.
491 if (checking->references[inner_idx])
493 CHECK_IMAGE_VISIT (checking->references[inner_idx]->image);
497 mono_image_unlock (checking);
501 g_ptr_array_free (current, TRUE); g_ptr_array_free (next, TRUE); g_hash_table_destroy (visited);
503 return success;
506 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
507 static gboolean
508 check_image_set_may_reference_image (MonoImageSet *from, MonoImage *to)
510 // See above-- All images implicitly reference corlib
511 if (to == mono_defaults.corlib)
512 return TRUE;
514 int idx;
515 gboolean success = FALSE;
516 mono_image_set_lock (from);
517 for (idx = 0; !success && idx < from->nimages; idx++)
519 if (check_image_may_reference_image (from->images[idx], to))
520 success = TRUE;
522 mono_image_set_unlock (from);
524 return success; // No satisfying image found in from->images
527 // Case 3. ImageSet FROM points to ImageSet TO: The images in TO are a strict subset of FROM (no transitive relationship is important here)
528 static gboolean
529 check_image_set_may_reference_image_set (MonoImageSet *from, MonoImageSet *to)
531 if (to == from)
532 return TRUE;
534 gboolean valid = TRUE; // Until proven otherwise
536 mono_image_set_lock (from); mono_image_set_lock (to);
538 int to_idx, from_idx;
539 for (to_idx = 0; valid && to_idx < to->nimages; to_idx++)
541 gboolean seen = FALSE;
543 // If TO set includes corlib, the FROM set may
544 // implicitly reference corlib, even if it's not
545 // present in the set explicitly.
546 if (to->images[to_idx] == mono_defaults.corlib)
547 seen = TRUE;
549 // For each item in to->images, scan over from->images seeking a path to it.
550 for (from_idx = 0; !seen && from_idx < from->nimages; from_idx++)
552 if (check_image_may_reference_image (from->images[from_idx], to->images[to_idx]))
553 seen = TRUE;
556 // If the to->images item is not found in from->images, the subset check has failed
557 if (!seen)
558 valid = FALSE;
561 mono_image_set_unlock (from); mono_image_set_unlock (to);
563 return valid; // All items in "to" were found in "from"
566 // Case 4. Image FROM points to ImageSet TO: FROM transitively references *ALL* of the “images” listed in TO
567 static gboolean
568 check_image_may_reference_image_set (MonoImage *from, MonoImageSet *to)
570 if (to->nimages == 0) // Malformed image_set
571 return FALSE;
573 gboolean valid = TRUE;
575 mono_image_set_lock (to);
576 int idx;
577 for (idx = 0; valid && idx < to->nimages; idx++)
579 if (!check_image_may_reference_image (from, to->images[idx]))
580 valid = FALSE;
582 mono_image_set_unlock (to);
584 return valid; // All images in to->images checked out
587 // Small helper-- get a descriptive string for a MonoMemPoolOwner
588 // Callers are obligated to free buffer with g_free after use
589 static const char *
590 check_mempool_owner_name (MonoMemPoolOwner owner)
592 GString *result = g_string_new (NULL);
593 if (owner.image)
595 if (owner.image->dynamic)
596 g_string_append (result, "(Dynamic)");
597 g_string_append (result, owner.image->name);
599 else if (owner.image_set)
601 char *temp = mono_image_set_description (owner.image_set);
602 g_string_append (result, "(Image set)");
603 g_string_append (result, temp);
604 g_free (temp);
606 else
608 g_string_append (result, "(Non-image memory)");
610 return g_string_free (result, FALSE);
613 // Helper -- surf various image-locating functions looking for the owner of this pointer
614 static MonoMemPoolOwner
615 mono_find_mempool_owner (void *ptr)
617 MonoMemPoolOwner owner = mono_mempool_no_owner;
619 owner.image = mono_find_image_owner (ptr);
620 if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
621 return owner;
623 owner.image_set = mono_find_image_set_owner (ptr);
624 if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
625 return owner;
627 owner.image = mono_find_dynamic_image_owner (ptr);
629 return owner;
632 // Actually perform reference audit
633 static void
634 check_mempool_may_reference_mempool (void *from_ptr, void *to_ptr, gboolean require_local)
636 if (!mono_check_mode_enabled (MONO_CHECK_MODE_METADATA))
637 return;
639 // Null pointers are OK
640 if (!to_ptr)
641 return;
643 MonoMemPoolOwner from = mono_find_mempool_owner (from_ptr), to = mono_find_mempool_owner (to_ptr);
645 if (require_local)
647 if (!check_mempool_owner_eq (from,to))
648 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));
651 // Writing into unknown mempool
652 else if (check_mempool_owner_eq (from, mono_mempool_no_owner))
654 check_mempool_assert_message ("Non-image memory attempting to write pointer to image %s", check_mempool_owner_name (to));
657 // Reading from unknown mempool
658 else if (check_mempool_owner_eq (to, mono_mempool_no_owner))
660 check_mempool_assert_message ("Attempting to write pointer from image %s to non-image memory", check_mempool_owner_name (from));
663 // Split out the four cases described above:
664 else if (from.image && to.image)
666 if (!check_image_may_reference_image (from.image, to.image))
667 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));
670 else if (from.image && to.image_set)
672 if (!check_image_may_reference_image_set (from.image, to.image_set))
673 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));
676 else if (from.image_set && to.image_set)
678 if (!check_image_set_may_reference_image_set (from.image_set, to.image_set))
679 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));
682 else if (from.image_set && to.image)
684 if (!check_image_set_may_reference_image (from.image_set, to.image))
685 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));
688 else
690 check_mempool_assert_message ("Internal logic error: Unreachable code");
694 void
695 check_metadata_store (void *from, void *to)
697 check_mempool_may_reference_mempool (from, to, FALSE);
700 void
701 check_metadata_store_local (void *from, void *to)
703 check_mempool_may_reference_mempool (from, to, TRUE);
706 #endif /* defined(ENABLE_CHECKED_BUILD_METADATA) */
708 #endif /* ENABLE_CHECKED_BUILD */