3 * Expensive asserts used when mono is built with --with-checked-build=yes
6 * Rodrigo Kumpera (kumpera@gmail.com)
11 #include <mono/utils/mono-compiler.h>
13 #ifdef ENABLE_CHECKED_BUILD
15 #include <mono/utils/checked-build.h>
16 #include <mono/utils/mono-threads.h>
17 #include <mono/utils/mono-threads-coop.h>
18 #include <mono/utils/mono-tls.h>
19 #include <mono/metadata/mempool.h>
20 #include <mono/metadata/metadata-internals.h>
21 #include <mono/metadata/image-internals.h>
22 #include <mono/metadata/loaded-images-internals.h>
23 #include <mono/metadata/class-internals.h>
24 #include <mono/metadata/reflection-internals.h>
27 #ifdef HAVE_BACKTRACE_SYMBOLS
31 // Selective-enable support
33 // Returns true for check modes which are allowed by both the current DISABLE_ macros and the MONO_CHECK_MODE env var.
34 // Argument may be a bitmask; if so, result is true if at least one specified mode is enabled.
36 mono_check_mode_enabled (MonoCheckMode query
)
38 static MonoCheckMode check_mode
= MONO_CHECK_MODE_UNKNOWN
;
39 if (G_UNLIKELY (check_mode
== MONO_CHECK_MODE_UNKNOWN
))
41 MonoCheckMode env_check_mode
= MONO_CHECK_MODE_NONE
;
42 gchar
*env_string
= g_getenv ("MONO_CHECK_MODE");
46 gchar
**env_split
= g_strsplit (env_string
, ",", 0);
47 for (gchar
**env_component
= env_split
; *env_component
; env_component
++)
49 mono_bool check_all
= g_str_equal (*env_component
, "all");
50 #ifdef ENABLE_CHECKED_BUILD_GC
51 if (check_all
|| g_str_equal (*env_component
, "gc"))
52 env_check_mode
|= MONO_CHECK_MODE_GC
;
54 #ifdef ENABLE_CHECKED_BUILD_METADATA
55 if (check_all
|| g_str_equal (*env_component
, "metadata"))
56 env_check_mode
|= MONO_CHECK_MODE_METADATA
;
58 #ifdef ENABLE_CHECKED_BUILD_THREAD
59 if (check_all
|| g_str_equal (*env_component
, "thread"))
60 env_check_mode
|= MONO_CHECK_MODE_THREAD
;
63 g_strfreev (env_split
);
67 check_mode
= env_check_mode
;
69 return check_mode
& query
;
73 mono_check_transition_limit (void)
75 static int transition_limit
= -1;
76 if (transition_limit
< 0) {
77 gchar
*env_string
= g_getenv ("MONO_CHECK_THREAD_TRANSITION_HISTORY");
79 transition_limit
= atoi (env_string
);
85 return transition_limit
;
88 #define MAX_NATIVE_BT 6
89 #define MAX_NATIVE_BT_PROBE (MAX_NATIVE_BT + 5)
90 #define MAX_TRANSITIONS (mono_check_transition_limit ())
94 int from_state
, next_state
, suspend_count
, suspend_count_delta
, size
;
95 gpointer backtrace
[MAX_NATIVE_BT_PROBE
];
99 guint32 in_gc_critical_region
;
100 // ring buffer of transitions, indexed by two guint16 indices:
101 // push at buf_end, iterate from buf_start. valid range is
102 // buf_start ... buf_end - 1 mod MAX_TRANSITIONS
104 ThreadTransition transitions
[MONO_ZERO_LEN_ARRAY
];
107 static MonoNativeTlsKey thread_status
;
109 static mono_mutex_t backtrace_mutex
;
113 checked_build_init (void)
115 // Init state for get_state, which can be called either by gc or thread mode
116 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC
| MONO_CHECK_MODE_THREAD
))
117 mono_native_tls_alloc (&thread_status
, NULL
);
118 #if HAVE_BACKTRACE_SYMBOLS
119 mono_os_mutex_init (&backtrace_mutex
);
124 backtrace_mutex_trylock (void)
126 return mono_os_mutex_trylock (&backtrace_mutex
) == 0;
130 backtrace_mutex_unlock (void)
132 return mono_os_mutex_unlock (&backtrace_mutex
);
138 CheckState
*state
= (CheckState
*)mono_native_tls_get_value (thread_status
);
140 state
= (CheckState
*) g_malloc0 (sizeof (CheckState
) + sizeof(ThreadTransition
) * MAX_TRANSITIONS
);
141 mono_native_tls_set_value (thread_status
, state
);
148 ringbuf_unpack (gint32 ringbuf
, guint16
*buf_start
, guint16
*buf_end
)
150 *buf_start
= (guint16
) (ringbuf
>> 16);
151 *buf_end
= (guint16
) (ringbuf
& 0x00FF);
155 ringbuf_pack (guint16 buf_start
, guint16 buf_end
)
157 return ((((gint32
)buf_start
) << 16) | ((gint32
)buf_end
));
161 ringbuf_size (guint32 ringbuf
, int n
)
163 guint16 buf_start
, buf_end
;
164 ringbuf_unpack (ringbuf
, &buf_start
, &buf_end
);
165 if (buf_end
> buf_start
)
166 return buf_end
- buf_start
;
168 return n
- (buf_start
- buf_end
);
172 ringbuf_push (gint32
*ringbuf
, int n
)
174 gint32 ringbuf_old
, ringbuf_new
;
175 guint16 buf_start
, buf_end
;
178 ringbuf_old
= *ringbuf
;
179 ringbuf_unpack (ringbuf_old
, &buf_start
, &buf_end
);
183 if (buf_end
== buf_start
) {
184 if (++buf_start
== n
)
187 ringbuf_new
= ringbuf_pack (buf_start
, buf_end
);
188 if (mono_atomic_cas_i32 (ringbuf
, ringbuf_new
, ringbuf_old
) != ringbuf_old
)
193 #ifdef ENABLE_CHECKED_BUILD_THREAD
195 #ifdef HAVE_BACKTRACE_SYMBOLS
197 //XXX We should collect just the IPs and lazily symbolificate them.
199 collect_backtrace (gpointer out_data
[])
201 #if defined (__GNUC__) && !defined (__clang__)
202 /* GNU libc backtrace calls _Unwind_Backtrace in libgcc, which internally may take a lock. */
203 /* Suppose we're using hybrid suspend and T1 is in GC Unsafe and T2 is
204 * GC Safe. T1 will be coop suspended, and T2 will be async suspended.
205 * Suppose T1 is in RUNNING, and T2 just changed from RUNNING to
206 * BLOCKING and it is in trace_state_change to record this fact.
208 * suspend initiator: switches T1 to ASYNC_SUSPEND_REQUESTED
209 * suspend initiator: switches T2 to BLOCKING_SUSPEND_REQUESTED and sends a suspend signal
210 * T1: calls mono_threads_transition_state_poll (),
211 * T1: switches to SELF_SUSPENDED and starts trace_state_change ()
212 * T2: is still in checked_build_thread_transition for the RUNNING->BLOCKING transition and calls backtrace ()
213 * T2: suspend signal lands while T2 is in backtrace() holding a lock; T2 switches to BLOCKING_ASYNC_SUSPENDED () and waits for resume
214 * T1: calls backtrace (), waits for the lock ()
215 * suspend initiator: waiting for T1 to suspend.
217 * At this point we're deadlocked.
219 * So what we'll do is try to take a lock before calling backtrace and
220 * only collect a backtrace if there is no contention.
223 for (i
= 0; i
< 2; i
++ ) {
224 if (backtrace_mutex_trylock ()) {
225 int sz
= backtrace (out_data
, MAX_NATIVE_BT_PROBE
);
226 backtrace_mutex_unlock ();
229 mono_thread_info_yield ();
232 /* didn't get a backtrace, oh well. */
235 return backtrace (out_data
, MAX_NATIVE_BT_PROBE
);
240 translate_backtrace (gpointer native_trace
[], int size
)
243 return g_strdup ("");
244 char **names
= backtrace_symbols (native_trace
, size
);
245 GString
* bt
= g_string_sized_new (100);
249 //Figure out the cut point of useless backtraces
250 //We'll skip up to the caller of checked_build_thread_transition
251 for (i
= 0; i
< size
; ++i
) {
252 if (strstr (names
[i
], "checked_build_thread_transition")) {
260 for (i
= j
; i
< size
; ++i
) {
261 if (i
- j
<= MAX_NATIVE_BT
)
262 g_string_append_printf (bt
, "\tat %s\n", names
[i
]);
266 return g_string_free (bt
, FALSE
);
272 collect_backtrace (gpointer out_data
[])
278 translate_backtrace (gpointer native_trace
[], int size
)
280 return g_strdup ("\tno backtrace available\n");
286 checked_build_thread_transition (const char *transition
, void *info
, int from_state
, int suspend_count
, int next_state
, int suspend_count_delta
, gboolean capture_backtrace
)
288 if (!mono_check_mode_enabled (MONO_CHECK_MODE_THREAD
))
291 /* We currently don't record external changes as those are hard to reason about. */
292 if (!mono_thread_info_is_current ((THREAD_INFO_TYPE
*)info
))
295 CheckState
*state
= get_state ();
297 guint16 cur
= ringbuf_push (&state
->ringbuf
, MAX_TRANSITIONS
);
299 ThreadTransition
*t
= &state
->transitions
[cur
];
300 t
->name
= transition
;
301 t
->from_state
= from_state
;
302 t
->next_state
= next_state
;
303 t
->suspend_count
= suspend_count
;
304 t
->suspend_count_delta
= suspend_count_delta
;
305 if (capture_backtrace
)
306 t
->size
= collect_backtrace (t
->backtrace
);
312 mono_fatal_with_history (const char * volatile msg
, ...)
314 GString
* err
= g_string_sized_new (100);
316 g_string_append_printf (err
, "Assertion failure in thread %p due to: ", mono_native_thread_id_get ());
319 va_start (args
, msg
);
320 g_string_append_vprintf (err
, msg
, args
);
323 if (mono_check_mode_enabled (MONO_CHECK_MODE_THREAD
))
325 CheckState
*state
= get_state ();
327 int len
= ringbuf_size (state
->ringbuf
, MAX_TRANSITIONS
);
329 g_string_append_printf (err
, "\nLast %d state transitions: (most recent first)\n", len
);
331 ringbuf_unpack (state
->ringbuf
, &cur
, &end
);
333 ThreadTransition
*t
= &state
->transitions
[cur
];
334 char *bt
= translate_backtrace (t
->backtrace
, t
->size
);
335 g_string_append_printf (err
, "[%s] %s -> %s (%d) %s%d at:\n%s",
337 mono_thread_state_name (t
->from_state
),
338 mono_thread_state_name (t
->next_state
),
340 t
->suspend_count_delta
> 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
341 t
->suspend_count_delta
,
344 if (++cur
== MAX_TRANSITIONS
)
350 g_string_free (err
, TRUE
);
353 #endif /* defined(ENABLE_CHECKED_BUILD_THREAD) */
355 #ifdef ENABLE_CHECKED_BUILD_GC
358 assert_gc_safe_mode (const char *file
, int lineno
)
360 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
363 MonoThreadInfo
*cur
= mono_thread_info_current ();
366 mono_fatal_with_history ("%s:%d: Expected GC Safe mode but thread is not attached", file
, lineno
);
368 int state
= mono_thread_info_current_state (cur
);
371 case STATE_BLOCKING_SELF_SUSPENDED
:
372 case STATE_BLOCKING_SUSPEND_REQUESTED
:
375 mono_fatal_with_history ("%s:%d: Expected GC Safe mode but was in %s state", file
, lineno
, mono_thread_state_name (state
));
380 assert_gc_unsafe_mode (const char *file
, int lineno
)
382 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
385 MonoThreadInfo
*cur
= mono_thread_info_current ();
388 mono_fatal_with_history ("%s:%d: Expected GC Unsafe mode but thread is not attached", file
, lineno
);
390 int state
= mono_thread_info_current_state (cur
);
393 case STATE_ASYNC_SUSPEND_REQUESTED
:
396 mono_fatal_with_history ("%s:%d: Expected GC Unsafe mode but was in %s state", file
, lineno
, mono_thread_state_name (state
));
401 assert_gc_neutral_mode (const char *file
, int lineno
)
403 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
406 MonoThreadInfo
*cur
= mono_thread_info_current ();
409 mono_fatal_with_history ("%s:%d: Expected GC Neutral mode but thread is not attached", file
, lineno
);
411 int state
= mono_thread_info_current_state (cur
);
414 case STATE_ASYNC_SUSPEND_REQUESTED
:
416 case STATE_BLOCKING_SELF_SUSPENDED
:
417 case STATE_BLOCKING_SUSPEND_REQUESTED
:
420 mono_fatal_with_history ("%s:%d: Expected GC Neutral mode but was in %s state", file
, lineno
, mono_thread_state_name (state
));
425 critical_gc_region_begin(void)
427 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
430 CheckState
*state
= get_state ();
431 state
->in_gc_critical_region
++;
437 critical_gc_region_end(void* token
)
439 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
442 CheckState
*state
= get_state();
443 g_assert (state
== token
);
444 state
->in_gc_critical_region
--;
448 assert_not_in_gc_critical_region(void)
450 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
453 CheckState
*state
= get_state();
454 if (state
->in_gc_critical_region
> 0) {
455 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 ())));
460 assert_in_gc_critical_region (void)
462 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
465 CheckState
*state
= get_state();
466 if (state
->in_gc_critical_region
== 0)
467 mono_fatal_with_history("Expected GC critical region");
470 #endif /* defined(ENABLE_CHECKED_BUILD_GC) */
472 #ifdef ENABLE_CHECKED_BUILD_METADATA
474 // check_metadata_store et al: The goal of these functions is to verify that if there is a pointer from one mempool into
475 // another, that the pointed-to memory is protected by the reference count mechanism for MonoImages.
477 // Note: The code below catches only some kinds of failures. Failures outside its scope notably incode:
478 // * Code below absolutely assumes that no mempool is ever held as "mempool" member by more than one Image or ImageSet at once
479 // * 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)
480 // Locking strategy is a little slapdash overall.
482 // Reference audit support
483 #define check_mempool_assert_message(...) \
484 g_assertion_message("Mempool reference violation: " __VA_ARGS__)
489 MonoImageSet
*image_set
;
492 static MonoMemPoolOwner mono_mempool_no_owner
= {NULL
,NULL
};
495 check_mempool_owner_eq (MonoMemPoolOwner a
, MonoMemPoolOwner b
)
497 return a
.image
== b
.image
&& a
.image_set
== b
.image_set
;
500 // Say image X "references" image Y if X either contains Y in its modules field, or X’s "references" field contains an
501 // assembly whose image is Y.
502 // Say image X transitively references image Y if there is any chain of images-referencing-images which leads from X to Y.
503 // Once the mempools for two pointers have been looked up, there are four possibilities:
505 // Case 1. Image FROM points to Image TO: Legal if FROM transitively references TO
507 // We'll do a simple BFS graph search on images. For each image we visit:
509 check_image_search (GHashTable
*visited
, GPtrArray
*next
, MonoImage
*candidate
, MonoImage
*goal
, gboolean
*success
)
511 // Image hasn't even been loaded-- ignore it
515 // Image has already been visited-- ignore it
516 if (g_hash_table_lookup_extended (visited
, candidate
, NULL
, NULL
))
519 // Image is the target-- mark success
520 if (candidate
== goal
)
526 // Unvisited image, queue it to have its children visited
527 g_hash_table_insert (visited
, candidate
, NULL
);
528 g_ptr_array_add (next
, candidate
);
533 check_image_may_reference_image(MonoImage
*from
, MonoImage
*to
)
535 if (to
== from
) // Shortcut
538 // Corlib is never unloaded, and all images implicitly reference it.
539 // Some images avoid explicitly referencing it as an optimization, so special-case it here.
540 if (to
== mono_defaults
.corlib
)
543 // Non-dynamic images may NEVER reference dynamic images
544 if (to
->dynamic
&& !from
->dynamic
)
547 // FIXME: We currently give a dynamic images a pass on the reference rules.
548 // Dynamic images may ALWAYS reference non-dynamic images.
549 // We allow this because the dynamic image code is known "messy", and in theory it is already
550 // protected because dynamic images can only reference classes their assembly has retained.
551 // However, long term, we should make this rigorous.
552 if (from
->dynamic
&& !to
->dynamic
)
555 gboolean success
= FALSE
;
557 // Images to inspect on this pass, images to inspect on the next pass
558 GPtrArray
*current
= g_ptr_array_sized_new (1), *next
= g_ptr_array_new ();
560 // Because in practice the image graph contains cycles, we must track which images we've visited
561 GHashTable
*visited
= g_hash_table_new (NULL
, NULL
);
563 #define CHECK_IMAGE_VISIT(i) check_image_search (visited, next, (i), to, &success)
565 CHECK_IMAGE_VISIT (from
); // Initially "next" contains only from node
567 // For each pass exhaust the "to check" queue while filling up the "check next" queue
568 while (!success
&& next
->len
> 0) // Halt on success or when out of nodes to process
570 // Swap "current" and "next" and clear next
571 GPtrArray
*temp
= current
;
574 g_ptr_array_set_size (next
, 0);
577 for(current_idx
= 0; current_idx
< current
->len
; current_idx
++)
579 MonoImage
*checking
= (MonoImage
*)g_ptr_array_index (current
, current_idx
);
581 mono_image_lock (checking
);
583 // For each queued image visit all directly referenced images
586 // 'files' and 'modules' semantically contain the same items but because of lazy loading we must check both
587 for (inner_idx
= 0; !success
&& inner_idx
< checking
->file_count
; inner_idx
++)
589 CHECK_IMAGE_VISIT (checking
->files
[inner_idx
]);
592 for (inner_idx
= 0; !success
&& inner_idx
< checking
->module_count
; inner_idx
++)
594 CHECK_IMAGE_VISIT (checking
->modules
[inner_idx
]);
597 for (inner_idx
= 0; !success
&& inner_idx
< checking
->nreferences
; inner_idx
++)
599 // Assembly references are lazy-loaded and thus allowed to be NULL.
600 // If they are NULL, we don't care about them for this search, because their images haven't impacted ref_count yet.
601 if (checking
->references
[inner_idx
])
603 CHECK_IMAGE_VISIT (checking
->references
[inner_idx
]->image
);
607 mono_image_unlock (checking
);
611 g_ptr_array_free (current
, TRUE
); g_ptr_array_free (next
, TRUE
); g_hash_table_destroy (visited
);
616 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
618 check_image_set_may_reference_image (MonoImageSet
*from
, MonoImage
*to
)
620 // See above-- All images implicitly reference corlib
621 if (to
== mono_defaults
.corlib
)
625 gboolean success
= FALSE
;
626 mono_image_set_lock (from
);
627 for (idx
= 0; !success
&& idx
< from
->nimages
; idx
++)
629 if (check_image_may_reference_image (from
->images
[idx
], to
))
632 mono_image_set_unlock (from
);
634 return success
; // No satisfying image found in from->images
637 // Case 3. ImageSet FROM points to ImageSet TO: The images in TO are a strict subset of FROM (no transitive relationship is important here)
639 check_image_set_may_reference_image_set (MonoImageSet
*from
, MonoImageSet
*to
)
644 gboolean valid
= TRUE
; // Until proven otherwise
646 mono_image_set_lock (from
); mono_image_set_lock (to
);
648 int to_idx
, from_idx
;
649 for (to_idx
= 0; valid
&& to_idx
< to
->nimages
; to_idx
++)
651 gboolean seen
= FALSE
;
653 // If TO set includes corlib, the FROM set may
654 // implicitly reference corlib, even if it's not
655 // present in the set explicitly.
656 if (to
->images
[to_idx
] == mono_defaults
.corlib
)
659 // For each item in to->images, scan over from->images seeking a path to it.
660 for (from_idx
= 0; !seen
&& from_idx
< from
->nimages
; from_idx
++)
662 if (check_image_may_reference_image (from
->images
[from_idx
], to
->images
[to_idx
]))
666 // If the to->images item is not found in from->images, the subset check has failed
671 mono_image_set_unlock (from
); mono_image_set_unlock (to
);
673 return valid
; // All items in "to" were found in "from"
676 // Case 4. Image FROM points to ImageSet TO: FROM transitively references *ALL* of the “images” listed in TO
678 check_image_may_reference_image_set (MonoImage
*from
, MonoImageSet
*to
)
680 if (to
->nimages
== 0) // Malformed image_set
683 gboolean valid
= TRUE
;
685 mono_image_set_lock (to
);
687 for (idx
= 0; valid
&& idx
< to
->nimages
; idx
++)
689 if (!check_image_may_reference_image (from
, to
->images
[idx
]))
692 mono_image_set_unlock (to
);
694 return valid
; // All images in to->images checked out
697 // Small helper-- get a descriptive string for a MonoMemPoolOwner
698 // Callers are obligated to free buffer with g_free after use
700 check_mempool_owner_name (MonoMemPoolOwner owner
)
702 GString
*result
= g_string_new (NULL
);
705 if (owner
.image
->dynamic
)
706 g_string_append (result
, "(Dynamic)");
707 g_string_append (result
, owner
.image
->name
);
709 else if (owner
.image_set
)
711 char *temp
= mono_image_set_description (owner
.image_set
);
712 g_string_append (result
, "(Image set)");
713 g_string_append (result
, temp
);
718 g_string_append (result
, "(Non-image memory)");
720 return g_string_free (result
, FALSE
);
723 // Helper -- surf various image-locating functions looking for the owner of this pointer
724 static MonoMemPoolOwner
725 mono_find_mempool_owner (void *ptr
)
727 MonoMemPoolOwner owner
= mono_mempool_no_owner
;
729 owner
.image
= mono_find_image_owner (ptr
);
730 if (!check_mempool_owner_eq (owner
, mono_mempool_no_owner
))
733 owner
.image_set
= mono_find_image_set_owner (ptr
);
734 if (!check_mempool_owner_eq (owner
, mono_mempool_no_owner
))
737 owner
.image
= mono_find_dynamic_image_owner (ptr
);
742 // Actually perform reference audit
744 check_mempool_may_reference_mempool (void *from_ptr
, void *to_ptr
, gboolean require_local
)
746 if (!mono_check_mode_enabled (MONO_CHECK_MODE_METADATA
))
749 // Null pointers are OK
753 MonoMemPoolOwner from
= mono_find_mempool_owner (from_ptr
), to
= mono_find_mempool_owner (to_ptr
);
757 if (!check_mempool_owner_eq (from
,to
))
758 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
));
761 // Writing into unknown mempool
762 else if (check_mempool_owner_eq (from
, mono_mempool_no_owner
))
764 check_mempool_assert_message ("Non-image memory attempting to write pointer to image %s", check_mempool_owner_name (to
));
767 // Reading from unknown mempool
768 else if (check_mempool_owner_eq (to
, mono_mempool_no_owner
))
770 check_mempool_assert_message ("Attempting to write pointer from image %s to non-image memory", check_mempool_owner_name (from
));
773 // Split out the four cases described above:
774 else if (from
.image
&& to
.image
)
776 if (!check_image_may_reference_image (from
.image
, to
.image
))
777 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
));
780 else if (from
.image
&& to
.image_set
)
782 if (!check_image_may_reference_image_set (from
.image
, to
.image_set
))
783 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
));
786 else if (from
.image_set
&& to
.image_set
)
788 if (!check_image_set_may_reference_image_set (from
.image_set
, to
.image_set
))
789 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
));
792 else if (from
.image_set
&& to
.image
)
794 if (!check_image_set_may_reference_image (from
.image_set
, to
.image
))
795 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
));
800 check_mempool_assert_message ("Internal logic error: Unreachable code");
805 check_metadata_store (void *from
, void *to
)
807 check_mempool_may_reference_mempool (from
, to
, FALSE
);
811 check_metadata_store_local (void *from
, void *to
)
813 check_mempool_may_reference_mempool (from
, to
, TRUE
);
816 #endif /* defined(ENABLE_CHECKED_BUILD_METADATA) */
817 #else /* ENABLE_CHECKED_BUILD */
819 MONO_EMPTY_SOURCE_FILE (checked_build
);
820 #endif /* ENABLE_CHECKED_BUILD */