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