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