2 * checked-build.c: Expensive asserts used when mono is built with --with-checked-build=yes
5 * Rodrigo Kumpera (kumpera@gmail.com)
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>
24 #ifdef HAVE_BACKTRACE_SYMBOLS
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.
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");
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
;
51 #ifdef ENABLE_CHECKED_BUILD_METADATA
52 if (check_all
|| g_str_equal (*env_component
, "metadata"))
53 env_check_mode
|= MONO_CHECK_MODE_METADATA
;
55 #ifdef ENABLE_CHECKED_BUILD_THREAD
56 if (check_all
|| g_str_equal (*env_component
, "thread"))
57 env_check_mode
|= MONO_CHECK_MODE_THREAD
;
60 g_strfreev (env_split
);
63 check_mode
= env_check_mode
;
65 return check_mode
& query
;
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");
75 transition_limit
= atoi (env_string
);
79 return transition_limit
;
83 GPtrArray
*transitions
;
84 guint32 in_gc_critical_region
;
87 static MonoNativeTlsKey thread_status
;
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
);
100 CheckState
*state
= mono_native_tls_get_value (thread_status
);
102 state
= g_new0 (CheckState
, 1);
103 state
->transitions
= g_ptr_array_new ();
104 mono_native_tls_set_value (thread_status
, 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.
120 collect_backtrace (gpointer out_data
[])
122 return backtrace (out_data
, MAX_NATIVE_BT_PROBE
);
126 translate_backtrace (gpointer native_trace
[], int size
)
128 char **names
= backtrace_symbols (native_trace
, size
);
129 GString
* bt
= g_string_sized_new (100);
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")) {
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
]);
150 return g_string_free (bt
, FALSE
);
156 collect_backtrace (gpointer out_data
[])
162 translate_backtrace (gpointer native_trace
[], int size
)
164 return g_strdup ("\tno backtrace available\n");
171 int from_state
, next_state
, suspend_count
, suspend_count_delta
, size
;
172 gpointer backtrace
[MAX_NATIVE_BT_PROBE
];
176 free_transition (ThreadTransition
*t
)
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
))
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
))
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
);
206 mono_fatal_with_history (const char *msg
, ...)
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 ());
214 va_start (args
, msg
);
215 g_string_append_vprintf (err
, msg
, 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",
229 mono_thread_state_name (t
->from_state
),
230 mono_thread_state_name (t
->next_state
),
232 t
->suspend_count_delta
> 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
233 t
->suspend_count_delta
,
240 g_string_free (err
, TRUE
);
243 #endif /* defined(ENABLE_CHECKED_BUILD_THREAD) */
245 #ifdef ENABLE_CHECKED_BUILD_GC
248 assert_gc_safe_mode (void)
250 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
253 MonoThreadInfo
*cur
= mono_thread_info_current ();
257 mono_fatal_with_history ("Expected GC Safe mode but thread is not attached");
259 switch (state
= mono_thread_info_current_state (cur
)) {
261 case STATE_BLOCKING_AND_SUSPENDED
:
264 mono_fatal_with_history ("Expected GC Safe mode but was in %s state", mono_thread_state_name (state
));
269 assert_gc_unsafe_mode (void)
271 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
274 MonoThreadInfo
*cur
= mono_thread_info_current ();
278 mono_fatal_with_history ("Expected GC Unsafe mode but thread is not attached");
280 switch (state
= mono_thread_info_current_state (cur
)) {
282 case STATE_ASYNC_SUSPEND_REQUESTED
:
283 case STATE_SELF_SUSPEND_REQUESTED
:
286 mono_fatal_with_history ("Expected GC Unsafe mode but was in %s state", mono_thread_state_name (state
));
291 assert_gc_neutral_mode (void)
293 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
296 MonoThreadInfo
*cur
= mono_thread_info_current ();
300 mono_fatal_with_history ("Expected GC Neutral mode but thread is not attached");
302 switch (state
= mono_thread_info_current_state (cur
)) {
304 case STATE_ASYNC_SUSPEND_REQUESTED
:
305 case STATE_SELF_SUSPEND_REQUESTED
:
307 case STATE_BLOCKING_AND_SUSPENDED
:
310 mono_fatal_with_history ("Expected GC Neutral mode but was in %s state", mono_thread_state_name (state
));
315 critical_gc_region_begin(void)
317 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
320 CheckState
*state
= get_state ();
321 state
->in_gc_critical_region
++;
327 critical_gc_region_end(void* token
)
329 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
332 CheckState
*state
= get_state();
333 g_assert (state
== token
);
334 state
->in_gc_critical_region
--;
338 assert_not_in_gc_critical_region(void)
340 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
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 ())));
350 assert_in_gc_critical_region (void)
352 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
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__)
379 MonoImageSet
*image_set
;
382 static MonoMemPoolOwner mono_mempool_no_owner
= {NULL
,NULL
};
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:
399 check_image_search (GHashTable
*visited
, GPtrArray
*next
, MonoImage
*candidate
, MonoImage
*goal
, gboolean
*success
)
401 // Image hasn't even been loaded-- ignore it
405 // Image has already been visited-- ignore it
406 if (g_hash_table_lookup_extended (visited
, candidate
, NULL
, NULL
))
409 // Image is the target-- mark success
410 if (candidate
== goal
)
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
);
423 check_image_may_reference_image(MonoImage
*from
, MonoImage
*to
)
425 if (to
== from
) // Shortcut
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
)
433 // Non-dynamic images may NEVER reference dynamic images
434 if (to
->dynamic
&& !from
->dynamic
)
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
)
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
;
464 g_ptr_array_set_size (next
, 0);
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
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
);
506 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
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
)
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
))
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)
529 check_image_set_may_reference_image_set (MonoImageSet
*from
, MonoImageSet
*to
)
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
)
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
]))
556 // If the to->images item is not found in from->images, the subset check has failed
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
568 check_image_may_reference_image_set (MonoImage
*from
, MonoImageSet
*to
)
570 if (to
->nimages
== 0) // Malformed image_set
573 gboolean valid
= TRUE
;
575 mono_image_set_lock (to
);
577 for (idx
= 0; valid
&& idx
< to
->nimages
; idx
++)
579 if (!check_image_may_reference_image (from
, to
->images
[idx
]))
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
590 check_mempool_owner_name (MonoMemPoolOwner owner
)
592 GString
*result
= g_string_new (NULL
);
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
);
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
))
623 owner
.image_set
= mono_find_image_set_owner (ptr
);
624 if (!check_mempool_owner_eq (owner
, mono_mempool_no_owner
))
627 owner
.image
= mono_find_dynamic_image_owner (ptr
);
632 // Actually perform reference audit
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
))
639 // Null pointers are OK
643 MonoMemPoolOwner from
= mono_find_mempool_owner (from_ptr
), to
= mono_find_mempool_owner (to_ptr
);
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
));
690 check_mempool_assert_message ("Internal logic error: Unreachable code");
695 check_metadata_store (void *from
, void *to
)
697 check_mempool_may_reference_mempool (from
, to
, FALSE
);
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 */