1 /**
2  * \file
3  * Expensive asserts used when mono is built with --with-checked-build=yes
4  *
5  * Author:
6  *	Rodrigo Kumpera (kumpera@gmail.com)
7  *
8  * (C) 2015 Xamarin
9  */
10 #include <config.h>
11 #include <mono/utils/mono-compiler.h>
12 
13 #ifdef ENABLE_CHECKED_BUILD
14 
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/class-internals.h>
23 #include <mono/metadata/reflection-internals.h>
24 #include <glib.h>
25 
26 #ifdef HAVE_BACKTRACE_SYMBOLS
27 #include <execinfo.h>
28 #endif
29 
30 // Selective-enable support
31 
32 // Returns true for check modes which are allowed by both the current DISABLE_ macros and the MONO_CHECK_MODE env var.
33 // Argument may be a bitmask; if so, result is true if at least one specified mode is enabled.
34 mono_bool
mono_check_mode_enabled(MonoCheckMode query)35 mono_check_mode_enabled (MonoCheckMode query)
36 {
37 	static MonoCheckMode check_mode = MONO_CHECK_MODE_UNKNOWN;
38 	if (G_UNLIKELY (check_mode == MONO_CHECK_MODE_UNKNOWN))
39 	{
40 		MonoCheckMode env_check_mode = MONO_CHECK_MODE_NONE;
41 		const gchar *env_string = g_getenv ("MONO_CHECK_MODE");
42 
43 		if (env_string)
44 		{
45 			gchar **env_split = g_strsplit (env_string, ",", 0);
46 			for (gchar **env_component = env_split; *env_component; env_component++)
47 			{
48 				mono_bool check_all = g_str_equal (*env_component, "all");
49 #ifdef ENABLE_CHECKED_BUILD_GC
50 				if (check_all || g_str_equal (*env_component, "gc"))
51 					env_check_mode |= MONO_CHECK_MODE_GC;
52 #endif
53 #ifdef ENABLE_CHECKED_BUILD_METADATA
54 				if (check_all || g_str_equal (*env_component, "metadata"))
55 					env_check_mode |= MONO_CHECK_MODE_METADATA;
56 #endif
57 #ifdef ENABLE_CHECKED_BUILD_THREAD
58 				if (check_all || g_str_equal (*env_component, "thread"))
59 					env_check_mode |= MONO_CHECK_MODE_THREAD;
60 #endif
61 			}
62 			g_strfreev (env_split);
63 			g_free (env_string);
64 		}
65 
66 		check_mode = env_check_mode;
67 	}
68 	return check_mode & query;
69 }
70 
71 static int
mono_check_transition_limit(void)72 mono_check_transition_limit (void)
73 {
74 	static int transition_limit = -1;
75 	if (transition_limit < 0) {
76 		const gchar *env_string = g_getenv ("MONO_CHECK_THREAD_TRANSITION_HISTORY");
77 		if (env_string) {
78 			transition_limit = atoi (env_string);
79 			g_free (env_string);
80 		} else {
81 			transition_limit = 3;
82 		}
83 	}
84 	return transition_limit;
85 }
86 
87 typedef struct {
88 	GPtrArray *transitions;
89 	guint32 in_gc_critical_region;
90 } CheckState;
91 
92 static MonoNativeTlsKey thread_status;
93 
94 void
checked_build_init(void)95 checked_build_init (void)
96 {
97 	// Init state for get_state, which can be called either by gc or thread mode
98 	if (mono_check_mode_enabled (MONO_CHECK_MODE_GC | MONO_CHECK_MODE_THREAD))
99 		mono_native_tls_alloc (&thread_status, NULL);
100 }
101 
102 static CheckState*
get_state(void)103 get_state (void)
104 {
105 	CheckState *state = mono_native_tls_get_value (thread_status);
106 	if (!state) {
107 		state = g_new0 (CheckState, 1);
108 		state->transitions = g_ptr_array_new ();
109 		mono_native_tls_set_value (thread_status, state);
110 	}
111 
112 	return state;
113 }
114 
115 #ifdef ENABLE_CHECKED_BUILD_THREAD
116 
117 #define MAX_NATIVE_BT 6
118 #define MAX_NATIVE_BT_PROBE (MAX_NATIVE_BT + 5)
119 #define MAX_TRANSITIONS (mono_check_transition_limit ())
120 
121 #ifdef HAVE_BACKTRACE_SYMBOLS
122 
123 //XXX We should collect just the IPs and lazily symbolificate them.
124 static int
collect_backtrace(gpointer out_data[])125 collect_backtrace (gpointer out_data[])
126 {
127 	return backtrace (out_data, MAX_NATIVE_BT_PROBE);
128 }
129 
130 static char*
translate_backtrace(gpointer native_trace[],int size)131 translate_backtrace (gpointer native_trace[], int size)
132 {
133 	char **names = backtrace_symbols (native_trace, size);
134 	GString* bt = g_string_sized_new (100);
135 
136 	int i, j = -1;
137 
138 	//Figure out the cut point of useless backtraces
139 	//We'll skip up to the caller of checked_build_thread_transition
140 	for (i = 0; i < size; ++i) {
141 		if (strstr (names [i], "checked_build_thread_transition")) {
142 			j = i + 1;
143 			break;
144 		}
145 	}
146 
147 	if (j == -1)
148 		j = 0;
149 	for (i = j; i < size; ++i) {
150 		if (i - j <= MAX_NATIVE_BT)
151 			g_string_append_printf (bt, "\tat %s\n", names [i]);
152 	}
153 
154 	g_free (names);
155 	return g_string_free (bt, FALSE);
156 }
157 
158 #else
159 
160 static int
collect_backtrace(gpointer out_data[])161 collect_backtrace (gpointer out_data[])
162 {
163 	return 0;
164 }
165 
166 static char*
translate_backtrace(gpointer native_trace[],int size)167 translate_backtrace (gpointer native_trace[], int size)
168 {
169 	return g_strdup ("\tno backtrace available\n");
170 }
171 
172 #endif
173 
174 typedef struct {
175 	const char *name;
176 	int from_state, next_state, suspend_count, suspend_count_delta, size;
177 	gpointer backtrace [MAX_NATIVE_BT_PROBE];
178 } ThreadTransition;
179 
180 static void
free_transition(ThreadTransition * t)181 free_transition (ThreadTransition *t)
182 {
183 	g_free (t);
184 }
185 
186 void
checked_build_thread_transition(const char * transition,void * info,int from_state,int suspend_count,int next_state,int suspend_count_delta)187 checked_build_thread_transition (const char *transition, void *info, int from_state, int suspend_count, int next_state, int suspend_count_delta)
188 {
189 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_THREAD))
190 		return;
191 
192 	CheckState *state = get_state ();
193 	/* We currently don't record external changes as those are hard to reason about. */
194 	if (!mono_thread_info_is_current (info))
195 		return;
196 
197 	if (state->transitions->len >= MAX_TRANSITIONS)
198 		free_transition (g_ptr_array_remove_index (state->transitions, 0));
199 
200 	ThreadTransition *t = g_new0 (ThreadTransition, 1);
201 	t->name = transition;
202 	t->from_state = from_state;
203 	t->next_state = next_state;
204 	t->suspend_count = suspend_count;
205 	t->suspend_count_delta = suspend_count_delta;
206 	t->size = collect_backtrace (t->backtrace);
207 	g_ptr_array_add (state->transitions, t);
208 }
209 
210 void
mono_fatal_with_history(const char * msg,...)211 mono_fatal_with_history (const char *msg, ...)
212 {
213 	int i;
214 	GString* err = g_string_sized_new (100);
215 
216 	g_string_append_printf (err, "Assertion failure in thread %p due to: ", mono_native_thread_id_get ());
217 
218 	va_list args;
219 	va_start (args, msg);
220 	g_string_append_vprintf (err, msg, args);
221 	va_end (args);
222 
223 	if (mono_check_mode_enabled (MONO_CHECK_MODE_THREAD))
224 	{
225 		CheckState *state = get_state ();
226 
227 		g_string_append_printf (err, "\nLast %d state transitions: (most recent first)\n", state->transitions->len);
228 
229 		for (i = state->transitions->len - 1; i >= 0; --i) {
230 			ThreadTransition *t = state->transitions->pdata [i];
231 			char *bt = translate_backtrace (t->backtrace, t->size);
232 			g_string_append_printf (err, "[%s] %s -> %s (%d) %s%d at:\n%s",
233 				t->name,
234 				mono_thread_state_name (t->from_state),
235 				mono_thread_state_name (t->next_state),
236 				t->suspend_count,
237 				t->suspend_count_delta > 0 ? "+" : "", //I'd like to see this sort of values: -1, 0, +1
238 				t->suspend_count_delta,
239 				bt);
240 			g_free (bt);
241 		}
242 	}
243 
244 	g_error (err->str);
245 	g_string_free (err, TRUE);
246 }
247 
248 #endif /* defined(ENABLE_CHECKED_BUILD_THREAD) */
249 
250 #ifdef ENABLE_CHECKED_BUILD_GC
251 
252 void
assert_gc_safe_mode(const char * file,int lineno)253 assert_gc_safe_mode (const char *file, int lineno)
254 {
255 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
256 		return;
257 
258 	MonoThreadInfo *cur = mono_thread_info_current ();
259 	int state;
260 
261 	if (!cur)
262 		mono_fatal_with_history ("%s:%d: Expected GC Safe mode but thread is not attached", file, lineno);
263 
264 	switch (state = mono_thread_info_current_state (cur)) {
265 	case STATE_BLOCKING:
266 	case STATE_BLOCKING_AND_SUSPENDED:
267 		break;
268 	default:
269 		mono_fatal_with_history ("%s:%d: Expected GC Safe mode but was in %s state", file, lineno, mono_thread_state_name (state));
270 	}
271 }
272 
273 void
assert_gc_unsafe_mode(const char * file,int lineno)274 assert_gc_unsafe_mode (const char *file, int lineno)
275 {
276 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
277 		return;
278 
279 	MonoThreadInfo *cur = mono_thread_info_current ();
280 	int state;
281 
282 	if (!cur)
283 		mono_fatal_with_history ("%s:%d: Expected GC Unsafe mode but thread is not attached", file, lineno);
284 
285 	switch (state = mono_thread_info_current_state (cur)) {
286 	case STATE_RUNNING:
287 	case STATE_ASYNC_SUSPEND_REQUESTED:
288 	case STATE_SELF_SUSPEND_REQUESTED:
289 		break;
290 	default:
291 		mono_fatal_with_history ("%s:%d: Expected GC Unsafe mode but was in %s state", file, lineno, mono_thread_state_name (state));
292 	}
293 }
294 
295 void
assert_gc_neutral_mode(const char * file,int lineno)296 assert_gc_neutral_mode (const char *file, int lineno)
297 {
298 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
299 		return;
300 
301 	MonoThreadInfo *cur = mono_thread_info_current ();
302 	int state;
303 
304 	if (!cur)
305 		mono_fatal_with_history ("%s:%d: Expected GC Neutral mode but thread is not attached", file, lineno);
306 
307 	switch (state = mono_thread_info_current_state (cur)) {
308 	case STATE_RUNNING:
309 	case STATE_ASYNC_SUSPEND_REQUESTED:
310 	case STATE_SELF_SUSPEND_REQUESTED:
311 	case STATE_BLOCKING:
312 	case STATE_BLOCKING_AND_SUSPENDED:
313 		break;
314 	default:
315 		mono_fatal_with_history ("%s:%d: Expected GC Neutral mode but was in %s state", file, lineno, mono_thread_state_name (state));
316 	}
317 }
318 
319 void *
critical_gc_region_begin(void)320 critical_gc_region_begin(void)
321 {
322 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
323 		return NULL;
324 
325 	CheckState *state = get_state ();
326 	state->in_gc_critical_region++;
327 	return state;
328 }
329 
330 
331 void
critical_gc_region_end(void * token)332 critical_gc_region_end(void* token)
333 {
334 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
335 		return;
336 
337 	CheckState *state = get_state();
338 	g_assert (state == token);
339 	state->in_gc_critical_region--;
340 }
341 
342 void
assert_not_in_gc_critical_region(void)343 assert_not_in_gc_critical_region(void)
344 {
345 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
346 		return;
347 
348 	CheckState *state = get_state();
349 	if (state->in_gc_critical_region > 0) {
350 		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 ())));
351 	}
352 }
353 
354 void
assert_in_gc_critical_region(void)355 assert_in_gc_critical_region (void)
356 {
357 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
358 		return;
359 
360 	CheckState *state = get_state();
361 	if (state->in_gc_critical_region == 0)
362 		mono_fatal_with_history("Expected GC critical region");
363 }
364 
365 #endif /* defined(ENABLE_CHECKED_BUILD_GC) */
366 
367 #ifdef ENABLE_CHECKED_BUILD_METADATA
368 
369 // check_metadata_store et al: The goal of these functions is to verify that if there is a pointer from one mempool into
370 // another, that the pointed-to memory is protected by the reference count mechanism for MonoImages.
371 //
372 // Note: The code below catches only some kinds of failures. Failures outside its scope notably incode:
373 // * Code below absolutely assumes that no mempool is ever held as "mempool" member by more than one Image or ImageSet at once
374 // * 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)
375 // Locking strategy is a little slapdash overall.
376 
377 // Reference audit support
378 #define check_mempool_assert_message(...) \
379 	g_assertion_message("Mempool reference violation: " __VA_ARGS__)
380 
381 typedef struct
382 {
383 	MonoImage *image;
384 	MonoImageSet *image_set;
385 } MonoMemPoolOwner;
386 
387 static MonoMemPoolOwner mono_mempool_no_owner = {NULL,NULL};
388 
389 static gboolean
check_mempool_owner_eq(MonoMemPoolOwner a,MonoMemPoolOwner b)390 check_mempool_owner_eq (MonoMemPoolOwner a, MonoMemPoolOwner b)
391 {
392 	return a.image == b.image && a.image_set == b.image_set;
393 }
394 
395 // Say image X "references" image Y if X either contains Y in its modules field, or X’s "references" field contains an
396 // assembly whose image is Y.
397 // Say image X transitively references image Y if there is any chain of images-referencing-images which leads from X to Y.
398 // Once the mempools for two pointers have been looked up, there are four possibilities:
399 
400 // Case 1. Image FROM points to Image TO: Legal if FROM transitively references TO
401 
402 // We'll do a simple BFS graph search on images. For each image we visit:
403 static void
check_image_search(GHashTable * visited,GPtrArray * next,MonoImage * candidate,MonoImage * goal,gboolean * success)404 check_image_search (GHashTable *visited, GPtrArray *next, MonoImage *candidate, MonoImage *goal, gboolean *success)
405 {
406 	// Image hasn't even been loaded-- ignore it
407 	if (!candidate)
408 		return;
409 
410 	// Image has already been visited-- ignore it
411 	if (g_hash_table_lookup_extended (visited, candidate, NULL, NULL))
412 		return;
413 
414 	// Image is the target-- mark success
415 	if (candidate == goal)
416 	{
417 		*success = TRUE;
418 		return;
419 	}
420 
421 	// Unvisited image, queue it to have its children visited
422 	g_hash_table_insert (visited, candidate, NULL);
423 	g_ptr_array_add (next, candidate);
424 	return;
425 }
426 
427 static gboolean
check_image_may_reference_image(MonoImage * from,MonoImage * to)428 check_image_may_reference_image(MonoImage *from, MonoImage *to)
429 {
430 	if (to == from) // Shortcut
431 		return TRUE;
432 
433 	// Corlib is never unloaded, and all images implicitly reference it.
434 	// Some images avoid explicitly referencing it as an optimization, so special-case it here.
435 	if (to == mono_defaults.corlib)
436 		return TRUE;
437 
438 	// Non-dynamic images may NEVER reference dynamic images
439 	if (to->dynamic && !from->dynamic)
440 		return FALSE;
441 
442 	// FIXME: We currently give a dynamic images a pass on the reference rules.
443 	// Dynamic images may ALWAYS reference non-dynamic images.
444 	// We allow this because the dynamic image code is known "messy", and in theory it is already
445 	// protected because dynamic images can only reference classes their assembly has retained.
446 	// However, long term, we should make this rigorous.
447 	if (from->dynamic && !to->dynamic)
448 		return TRUE;
449 
450 	gboolean success = FALSE;
451 
452 	// Images to inspect on this pass, images to inspect on the next pass
453 	GPtrArray *current = g_ptr_array_sized_new (1), *next = g_ptr_array_new ();
454 
455 	// Because in practice the image graph contains cycles, we must track which images we've visited
456 	GHashTable *visited = g_hash_table_new (NULL, NULL);
457 
458 	#define CHECK_IMAGE_VISIT(i) check_image_search (visited, next, (i), to, &success)
459 
460 	CHECK_IMAGE_VISIT (from); // Initially "next" contains only from node
461 
462 	// For each pass exhaust the "to check" queue while filling up the "check next" queue
463 	while (!success && next->len > 0) // Halt on success or when out of nodes to process
464 	{
465 		// Swap "current" and "next" and clear next
466 		GPtrArray *temp = current;
467 		current = next;
468 		next = temp;
469 		g_ptr_array_set_size (next, 0);
470 
471 		int current_idx;
472 		for(current_idx = 0; current_idx < current->len; current_idx++)
473 		{
474 			MonoImage *checking = g_ptr_array_index (current, current_idx); // CAST?
475 
476 			mono_image_lock (checking);
477 
478 			// For each queued image visit all directly referenced images
479 			int inner_idx;
480 
481 			// 'files' and 'modules' semantically contain the same items but because of lazy loading we must check both
482 			for (inner_idx = 0; !success && inner_idx < checking->file_count; inner_idx++)
483 			{
484 				CHECK_IMAGE_VISIT (checking->files[inner_idx]);
485 			}
486 
487 			for (inner_idx = 0; !success && inner_idx < checking->module_count; inner_idx++)
488 			{
489 				CHECK_IMAGE_VISIT (checking->modules[inner_idx]);
490 			}
491 
492 			for (inner_idx = 0; !success && inner_idx < checking->nreferences; inner_idx++)
493 			{
494 				// Assembly references are lazy-loaded and thus allowed to be NULL.
495 				// If they are NULL, we don't care about them for this search, because their images haven't impacted ref_count yet.
496 				if (checking->references[inner_idx])
497 				{
498 					CHECK_IMAGE_VISIT (checking->references[inner_idx]->image);
499 				}
500 			}
501 
502 			mono_image_unlock (checking);
503 		}
504 	}
505 
506 	g_ptr_array_free (current, TRUE); g_ptr_array_free (next, TRUE); g_hash_table_destroy (visited);
507 
508 	return success;
509 }
510 
511 // Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
512 static gboolean
check_image_set_may_reference_image(MonoImageSet * from,MonoImage * to)513 check_image_set_may_reference_image (MonoImageSet *from, MonoImage *to)
514 {
515 	// See above-- All images implicitly reference corlib
516 	if (to == mono_defaults.corlib)
517 		return TRUE;
518 
519 	int idx;
520 	gboolean success = FALSE;
521 	mono_image_set_lock (from);
522 	for (idx = 0; !success && idx < from->nimages; idx++)
523 	{
524 		if (check_image_may_reference_image (from->images[idx], to))
525 			success = TRUE;
526 	}
527 	mono_image_set_unlock (from);
528 
529 	return success; // No satisfying image found in from->images
530 }
531 
532 // Case 3. ImageSet FROM points to ImageSet TO: The images in TO are a strict subset of FROM (no transitive relationship is important here)
533 static gboolean
check_image_set_may_reference_image_set(MonoImageSet * from,MonoImageSet * to)534 check_image_set_may_reference_image_set (MonoImageSet *from, MonoImageSet *to)
535 {
536 	if (to == from)
537 		return TRUE;
538 
539 	gboolean valid = TRUE; // Until proven otherwise
540 
541 	mono_image_set_lock (from); mono_image_set_lock (to);
542 
543 	int to_idx, from_idx;
544 	for (to_idx = 0; valid && to_idx < to->nimages; to_idx++)
545 	{
546 		gboolean seen = FALSE;
547 
548 		// If TO set includes corlib, the FROM set may
549 		// implicitly reference corlib, even if it's not
550 		// present in the set explicitly.
551 		if (to->images[to_idx] == mono_defaults.corlib)
552 			seen = TRUE;
553 
554 		// For each item in to->images, scan over from->images seeking a path to it.
555 		for (from_idx = 0; !seen && from_idx < from->nimages; from_idx++)
556 		{
557 			if (check_image_may_reference_image (from->images[from_idx], to->images[to_idx]))
558 				seen = TRUE;
559 		}
560 
561 		// If the to->images item is not found in from->images, the subset check has failed
562 		if (!seen)
563 			valid = FALSE;
564 	}
565 
566 	mono_image_set_unlock (from); mono_image_set_unlock (to);
567 
568 	return valid; // All items in "to" were found in "from"
569 }
570 
571 // Case 4. Image FROM points to ImageSet TO: FROM transitively references *ALL* of the “images” listed in TO
572 static gboolean
check_image_may_reference_image_set(MonoImage * from,MonoImageSet * to)573 check_image_may_reference_image_set (MonoImage *from, MonoImageSet *to)
574 {
575 	if (to->nimages == 0) // Malformed image_set
576 		return FALSE;
577 
578 	gboolean valid = TRUE;
579 
580 	mono_image_set_lock (to);
581 	int idx;
582 	for (idx = 0; valid && idx < to->nimages; idx++)
583 	{
584 		if (!check_image_may_reference_image (from, to->images[idx]))
585 			valid = FALSE;
586 	}
587 	mono_image_set_unlock (to);
588 
589 	return valid; // All images in to->images checked out
590 }
591 
592 // Small helper-- get a descriptive string for a MonoMemPoolOwner
593 // Callers are obligated to free buffer with g_free after use
594 static const char *
check_mempool_owner_name(MonoMemPoolOwner owner)595 check_mempool_owner_name (MonoMemPoolOwner owner)
596 {
597 	GString *result = g_string_new (NULL);
598 	if (owner.image)
599 	{
600 		if (owner.image->dynamic)
601 			g_string_append (result, "(Dynamic)");
602 		g_string_append (result, owner.image->name);
603 	}
604 	else if (owner.image_set)
605 	{
606 		char *temp = mono_image_set_description (owner.image_set);
607 		g_string_append (result, "(Image set)");
608 		g_string_append (result, temp);
609 		g_free (temp);
610 	}
611 	else
612 	{
613 		g_string_append (result, "(Non-image memory)");
614 	}
615 	return g_string_free (result, FALSE);
616 }
617 
618 // Helper -- surf various image-locating functions looking for the owner of this pointer
619 static MonoMemPoolOwner
mono_find_mempool_owner(void * ptr)620 mono_find_mempool_owner (void *ptr)
621 {
622 	MonoMemPoolOwner owner = mono_mempool_no_owner;
623 
624 	owner.image = mono_find_image_owner (ptr);
625 	if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
626 		return owner;
627 
628 	owner.image_set = mono_find_image_set_owner (ptr);
629 	if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
630 		return owner;
631 
632 	owner.image = mono_find_dynamic_image_owner (ptr);
633 
634 	return owner;
635 }
636 
637 // Actually perform reference audit
638 static void
check_mempool_may_reference_mempool(void * from_ptr,void * to_ptr,gboolean require_local)639 check_mempool_may_reference_mempool (void *from_ptr, void *to_ptr, gboolean require_local)
640 {
641 	if (!mono_check_mode_enabled (MONO_CHECK_MODE_METADATA))
642 		return;
643 
644 	// Null pointers are OK
645 	if (!to_ptr)
646 		return;
647 
648 	MonoMemPoolOwner from = mono_find_mempool_owner (from_ptr), to = mono_find_mempool_owner (to_ptr);
649 
650 	if (require_local)
651 	{
652 		if (!check_mempool_owner_eq (from,to))
653 			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));
654 	}
655 
656 	// Writing into unknown mempool
657 	else if (check_mempool_owner_eq (from, mono_mempool_no_owner))
658 	{
659 		check_mempool_assert_message ("Non-image memory attempting to write pointer to image %s", check_mempool_owner_name (to));
660 	}
661 
662 	// Reading from unknown mempool
663 	else if (check_mempool_owner_eq (to, mono_mempool_no_owner))
664 	{
665 		check_mempool_assert_message ("Attempting to write pointer from image %s to non-image memory", check_mempool_owner_name (from));
666 	}
667 
668 	// Split out the four cases described above:
669 	else if (from.image && to.image)
670 	{
671 		if (!check_image_may_reference_image (from.image, to.image))
672 			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));
673 	}
674 
675 	else if (from.image && to.image_set)
676 	{
677 		if (!check_image_may_reference_image_set (from.image, to.image_set))
678 			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));
679 	}
680 
681 	else if (from.image_set && to.image_set)
682 	{
683 		if (!check_image_set_may_reference_image_set (from.image_set, to.image_set))
684 			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));
685 	}
686 
687 	else if (from.image_set && to.image)
688 	{
689 		if (!check_image_set_may_reference_image (from.image_set, to.image))
690 			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));
691 	}
692 
693 	else
694 	{
695 		check_mempool_assert_message ("Internal logic error: Unreachable code");
696 	}
697 }
698 
699 void
check_metadata_store(void * from,void * to)700 check_metadata_store (void *from, void *to)
701 {
702     check_mempool_may_reference_mempool (from, to, FALSE);
703 }
704 
705 void
check_metadata_store_local(void * from,void * to)706 check_metadata_store_local (void *from, void *to)
707 {
708     check_mempool_may_reference_mempool (from, to, TRUE);
709 }
710 
711 #endif /* defined(ENABLE_CHECKED_BUILD_METADATA) */
712 #else /* ENABLE_CHECKED_BUILD */
713 
714 MONO_EMPTY_SOURCE_FILE (checked_build);
715 #endif /* ENABLE_CHECKED_BUILD */
716