2 * checked-build.c: Expensive asserts used when mono is built with --with-checked-build=yes
5 * Rodrigo Kumpera (kumpera@gmail.com)
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>
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
30 //XXX We should collect just the IPs and lazily symbolificate them.
32 collect_backtrace (gpointer out_data
[])
34 return backtrace (out_data
, MAX_NATIVE_BT_PROBE
);
38 translate_backtrace (gpointer native_trace
[], int size
)
40 char **names
= backtrace_symbols (native_trace
, size
);
41 GString
* bt
= g_string_sized_new (100);
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")) {
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
]);
62 return g_string_free (bt
, FALSE
);
68 collect_backtrace (gpointer out_data
[])
74 translate_backtrace (gpointer native_trace
[], int size
)
76 return g_strdup ("\tno backtrace available\n");
83 GPtrArray
*transitions
;
84 gboolean in_gc_critical_region
;
89 int from_state
, next_state
, suspend_count
, suspend_count_delta
, size
;
90 gpointer backtrace
[MAX_NATIVE_BT_PROBE
];
93 static MonoNativeTlsKey thread_status
;
96 checked_build_init (void)
98 mono_native_tls_alloc (&thread_status
, NULL
);
104 CheckState
*state
= mono_native_tls_get_value (thread_status
);
106 state
= g_new0 (CheckState
, 1);
107 state
->transitions
= g_ptr_array_new ();
108 mono_native_tls_set_value (thread_status
, state
);
115 free_transition (ThreadTransition
*t
)
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. */
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
);
143 assertion_fail (const char *msg
, ...)
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 ());
152 va_start (args
, msg
);
153 g_string_append_vprintf (err
, msg
, 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",
163 mono_thread_state_name (t
->from_state
),
164 mono_thread_state_name (t
->next_state
),
166 t
->suspend_count_delta
> 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
167 t
->suspend_count_delta
,
173 g_string_free (err
, TRUE
);
177 assert_gc_safe_mode (void)
179 MonoThreadInfo
*cur
= mono_thread_info_current ();
183 assertion_fail ("Expected GC Safe mode but thread is not attached");
185 switch (state
= mono_thread_info_current_state (cur
)) {
187 case STATE_BLOCKING_AND_SUSPENDED
:
190 assertion_fail ("Expected GC Safe mode but was in %s state", mono_thread_state_name (state
));
195 assert_gc_unsafe_mode (void)
197 MonoThreadInfo
*cur
= mono_thread_info_current ();
201 assertion_fail ("Expected GC Unsafe mode but thread is not attached");
203 switch (state
= mono_thread_info_current_state (cur
)) {
205 case STATE_ASYNC_SUSPEND_REQUESTED
:
206 case STATE_SELF_SUSPEND_REQUESTED
:
209 assertion_fail ("Expected GC Unsafe mode but was in %s state", mono_thread_state_name (state
));
214 assert_gc_neutral_mode (void)
216 MonoThreadInfo
*cur
= mono_thread_info_current ();
220 assertion_fail ("Expected GC Neutral mode but thread is not attached");
222 switch (state
= mono_thread_info_current_state (cur
)) {
224 case STATE_ASYNC_SUSPEND_REQUESTED
:
225 case STATE_SELF_SUSPEND_REQUESTED
:
227 case STATE_BLOCKING_AND_SUSPENDED
:
230 assertion_fail ("Expected GC Neutral mode but was in %s state", mono_thread_state_name (state
));
235 critical_gc_region_begin(void)
237 CheckState
*state
= get_state ();
238 state
->in_gc_critical_region
= TRUE
;
244 critical_gc_region_end(void* token
)
246 CheckState
*state
= get_state();
247 g_assert (state
== token
);
248 state
->in_gc_critical_region
= FALSE
;
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__)
277 MonoImageSet
*image_set
;
280 static MonoMemPoolOwner mono_mempool_no_owner
= {NULL
,NULL
};
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:
297 check_image_search (GHashTable
*visited
, GPtrArray
*next
, MonoImage
*candidate
, MonoImage
*goal
, gboolean
*success
)
299 // Image hasn't even been loaded-- ignore it
303 // Image has already been visited-- ignore it
304 if (g_hash_table_lookup_extended (visited
, candidate
, NULL
, NULL
))
307 // Image is the target-- mark success
308 if (candidate
== goal
)
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
);
321 check_image_may_reference_image(MonoImage
*from
, MonoImage
*to
)
323 if (to
== from
) // Shortcut
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
)
331 // Non-dynamic images may NEVER reference dynamic images
332 if (to
->dynamic
&& !from
->dynamic
)
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
)
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
;
362 g_ptr_array_set_size (next
, 0);
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
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
);
398 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
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
)
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
))
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)
421 check_image_set_may_reference_image_set (MonoImageSet
*from
, MonoImageSet
*to
)
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
])
442 // If the to->images item is not found in from->images, the subset check has failed
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
454 check_image_may_reference_image_set (MonoImage
*from
, MonoImageSet
*to
)
456 if (to
->nimages
== 0) // Malformed image_set
459 gboolean valid
= TRUE
;
461 mono_image_set_lock (to
);
463 for (idx
= 0; valid
&& idx
< to
->nimages
; idx
++)
465 if (!check_image_may_reference_image (from
, to
->images
[idx
]))
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
476 check_mempool_owner_name (MonoMemPoolOwner owner
)
478 GString
*result
= g_string_new (NULL
);
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
);
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
))
509 owner
.image_set
= mono_find_image_set_owner (ptr
);
510 if (!check_mempool_owner_eq (owner
, mono_mempool_no_owner
))
513 owner
.image
= mono_find_dynamic_image_owner (ptr
);
518 // Actually perform reference audit
520 check_mempool_may_reference_mempool (void *from_ptr
, void *to_ptr
, gboolean require_local
)
522 // Null pointers are OK
526 MonoMemPoolOwner from
= mono_find_mempool_owner (from_ptr
), to
= mono_find_mempool_owner (to_ptr
);
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
));
573 check_mempool_assert_message ("Internal logic error: Unreachable code");
578 check_metadata_store (void *from
, void *to
)
580 check_mempool_may_reference_mempool (from
, to
, FALSE
);
584 check_metadata_store_local (void *from
, void *to
)
586 check_mempool_may_reference_mempool (from
, to
, TRUE
);
589 #endif /* CHECKED_BUILD */