#include <mono/utils/checked-build.h>
#include <mono/utils/mono-threads.h>
#include <mono/utils/mono-tls.h>
+#include <mono/metadata/mempool.h>
+#include <mono/metadata/metadata-internals.h>
+#include <mono/metadata/image-internals.h>
+#include <mono/metadata/class-internals.h>
+#include <mono/metadata/reflection-internals.h>
#include <glib.h>
#define MAX_NATIVE_BT 6
typedef struct {
GPtrArray *transitions;
+ gboolean in_gc_critical_region;
} CheckState;
typedef struct {
}
}
+void *
+critical_gc_region_begin(void)
+{
+ CheckState *state = get_state ();
+ state->in_gc_critical_region = TRUE;
+ return state;
+}
+
+
+void
+critical_gc_region_end(void* token)
+{
+ CheckState *state = get_state();
+ g_assert (state == token);
+ state->in_gc_critical_region = FALSE;
+}
+
+void
+assert_not_in_gc_critical_region(void)
+{
+ CheckState *state = get_state();
+ if (state->in_gc_critical_region) {
+ MonoThreadInfo *cur = mono_thread_info_current();
+ state = mono_thread_info_current_state(cur);
+ assertion_fail("Expected GC Unsafe mode, but was in %s state", mono_thread_state_name(state));
+ }
+}
+
+// check_metadata_store et al: The goal of these functions is to verify that if there is a pointer from one mempool into
+// another, that the pointed-to memory is protected by the reference count mechanism for MonoImages.
+//
+// Note: The code below catches only some kinds of failures. Failures outside its scope notably incode:
+// * Code below absolutely assumes that no mempool is ever held as "mempool" member by more than one Image or ImageSet at once
+// * 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)
+// Locking strategy is a little slapdash overall.
+
+// Reference audit support
+#define check_mempool_assert_message(...) \
+ g_assertion_message("Mempool reference violation: " __VA_ARGS__)
+
+typedef struct
+{
+ MonoImage *image;
+ MonoImageSet *image_set;
+} MonoMemPoolOwner;
+
+static MonoMemPoolOwner mono_mempool_no_owner = {NULL,NULL};
+
+static gboolean
+check_mempool_owner_eq (MonoMemPoolOwner a, MonoMemPoolOwner b)
+{
+ return a.image == b.image && a.image_set == b.image_set;
+}
+
+// Say image X "references" image Y if X either contains Y in its modules field, or X’s "references" field contains an
+// assembly whose image is Y.
+// Say image X transitively references image Y if there is any chain of images-referencing-images which leads from X to Y.
+// Once the mempools for two pointers have been looked up, there are four possibilities:
+
+// Case 1. Image FROM points to Image TO: Legal if FROM transitively references TO
+
+// We'll do a simple BFS graph search on images. For each image we visit:
+static void
+check_image_search (GHashTable *visited, GPtrArray *next, MonoImage *candidate, MonoImage *goal, gboolean *success)
+{
+ // Image hasn't even been loaded-- ignore it
+ if (!candidate)
+ return;
+
+ // Image has already been visited-- ignore it
+ if (g_hash_table_lookup_extended (visited, candidate, NULL, NULL))
+ return;
+
+ // Image is the target-- mark success
+ if (candidate == goal)
+ {
+ *success = TRUE;
+ return;
+ }
+
+ // Unvisited image, queue it to have its children visited
+ g_hash_table_insert (visited, candidate, NULL);
+ g_ptr_array_add (next, candidate);
+ return;
+}
+
+static gboolean
+check_image_may_reference_image(MonoImage *from, MonoImage *to)
+{
+ if (to == from) // Shortcut
+ return TRUE;
+
+ // Corlib is never unloaded, and all images implicitly reference it.
+ // Some images avoid explicitly referencing it as an optimization, so special-case it here.
+ if (to == mono_defaults.corlib)
+ return TRUE;
+
+ // Non-dynamic images may NEVER reference dynamic images
+ if (to->dynamic && !from->dynamic)
+ return FALSE;
+
+ // FIXME: We currently give a dynamic images a pass on the reference rules.
+ // Dynamic images may ALWAYS reference non-dynamic images.
+ // We allow this because the dynamic image code is known "messy", and in theory it is already
+ // protected because dynamic images can only reference classes their assembly has retained.
+ // However, long term, we should make this rigorous.
+ if (from->dynamic && !to->dynamic)
+ return TRUE;
+
+ gboolean success = FALSE;
+
+ // Images to inspect on this pass, images to inspect on the next pass
+ GPtrArray *current = g_ptr_array_sized_new (1), *next = g_ptr_array_new ();
+
+ // Because in practice the image graph contains cycles, we must track which images we've visited
+ GHashTable *visited = g_hash_table_new (NULL, NULL);
+
+ #define CHECK_IMAGE_VISIT(i) check_image_search (visited, next, (i), to, &success)
+
+ CHECK_IMAGE_VISIT (from); // Initially "next" contains only from node
+
+ // For each pass exhaust the "to check" queue while filling up the "check next" queue
+ while (!success && next->len > 0) // Halt on success or when out of nodes to process
+ {
+ // Swap "current" and "next" and clear next
+ GPtrArray *temp = current;
+ current = next;
+ next = temp;
+ g_ptr_array_set_size (next, 0);
+
+ int current_idx;
+ for(current_idx = 0; current_idx < current->len; current_idx++)
+ {
+ MonoImage *checking = g_ptr_array_index (current, current_idx); // CAST?
+
+ mono_image_lock (checking);
+
+ // For each queued image visit all directly referenced images
+ int inner_idx;
+
+ for (inner_idx = 0; !success && inner_idx < checking->module_count; inner_idx++)
+ {
+ CHECK_IMAGE_VISIT (checking->modules[inner_idx]);
+ }
+
+ for (inner_idx = 0; !success && inner_idx < checking->nreferences; inner_idx++)
+ {
+ // References are lazy-loaded and thus allowed to be NULL.
+ // If they are NULL, we don't care about them for this search, because they haven't impacted ref_count yet.
+ if (checking->references[inner_idx])
+ {
+ CHECK_IMAGE_VISIT (checking->references[inner_idx]->image);
+ }
+ }
+
+ mono_image_unlock (checking);
+ }
+ }
+
+ g_ptr_array_free (current, TRUE); g_ptr_array_free (next, TRUE); g_hash_table_destroy (visited);
+
+ return success;
+}
+
+// Case 2. ImageSet FROM points to Image TO: One of FROM's "images" either is, or transitively references, TO.
+static gboolean
+check_image_set_may_reference_image (MonoImageSet *from, MonoImage *to)
+{
+ // See above-- All images implicitly reference corlib
+ if (to == mono_defaults.corlib)
+ return TRUE;
+
+ int idx;
+ gboolean success = FALSE;
+ mono_image_set_lock (from);
+ for (idx = 0; !success && idx < from->nimages; idx++)
+ {
+ if (check_image_may_reference_image (from->images[idx], to))
+ success = TRUE;
+ }
+ mono_image_set_unlock (from);
+
+ return success; // No satisfying image found in from->images
+}
+
+// Case 3. ImageSet FROM points to ImageSet TO: The images in TO are a strict subset of FROM (no transitive relationship is important here)
+static gboolean
+check_image_set_may_reference_image_set (MonoImageSet *from, MonoImageSet *to)
+{
+ if (to == from)
+ return TRUE;
+
+ gboolean valid = TRUE; // Until proven otherwise
+
+ mono_image_set_lock (from); mono_image_set_lock (to);
+
+ int to_idx, from_idx;
+ for (to_idx = 0; valid && to_idx < to->nimages; to_idx++)
+ {
+ gboolean seen = FALSE;
+
+ // If TO set includes corlib, the FROM set may
+ // implicitly reference corlib, even if it's not
+ // present in the set explicitly.
+ if (to->images[to_idx] == mono_defaults.corlib)
+ seen = TRUE;
+
+ // For each item in to->images, scan over from->images looking for it.
+ for (from_idx = 0; !seen && from_idx < from->nimages; from_idx++)
+ {
+ if (to->images[to_idx] == from->images[from_idx])
+ seen = TRUE;
+ }
+
+ // If the to->images item is not found in from->images, the subset check has failed
+ if (!seen)
+ valid = FALSE;
+ }
+
+ mono_image_set_unlock (from); mono_image_set_unlock (to);
+
+ return valid; // All items in "to" were found in "from"
+}
+
+// Case 4. Image FROM points to ImageSet TO: FROM transitively references *ALL* of the “images” listed in TO
+static gboolean
+check_image_may_reference_image_set (MonoImage *from, MonoImageSet *to)
+{
+ if (to->nimages == 0) // Malformed image_set
+ return FALSE;
+
+ gboolean valid = TRUE;
+
+ mono_image_set_lock (to);
+ int idx;
+ for (idx = 0; valid && idx < to->nimages; idx++)
+ {
+ if (!check_image_may_reference_image (from, to->images[idx]))
+ valid = FALSE;
+ }
+ mono_image_set_unlock (to);
+
+ return valid; // All images in to->images checked out
+}
+
+// Small helper-- get a descriptive string for a MonoMemPoolOwner
+// Callers are obligated to free buffer with g_free after use
+static const char *
+check_mempool_owner_name (MonoMemPoolOwner owner)
+{
+ GString *result = g_string_new (NULL);
+ if (owner.image)
+ {
+ if (owner.image->dynamic)
+ g_string_append (result, "(Dynamic)");
+ g_string_append (result, owner.image->name);
+ }
+ else if (owner.image_set)
+ {
+ char *temp = mono_image_set_description (owner.image_set);
+ g_string_append (result, "(Image set)");
+ g_string_append (result, temp);
+ g_free (temp);
+ }
+ else
+ {
+ g_string_append (result, "(Non-image memory)");
+ }
+ return g_string_free (result, FALSE);
+}
+
+// Helper -- surf various image-locating functions looking for the owner of this pointer
+static MonoMemPoolOwner
+mono_find_mempool_owner (void *ptr)
+{
+ MonoMemPoolOwner owner = mono_mempool_no_owner;
+
+ owner.image = mono_find_image_owner (ptr);
+ if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
+ return owner;
+
+ owner.image_set = mono_find_image_set_owner (ptr);
+ if (!check_mempool_owner_eq (owner, mono_mempool_no_owner))
+ return owner;
+
+ owner.image = mono_find_dynamic_image_owner (ptr);
+
+ return owner;
+}
+
+// Actually perform reference audit
+static void
+check_mempool_may_reference_mempool (void *from_ptr, void *to_ptr, gboolean require_local)
+{
+ // Null pointers are OK
+ if (!to_ptr)
+ return;
+
+ MonoMemPoolOwner from = mono_find_mempool_owner (from_ptr), to = mono_find_mempool_owner (to_ptr);
+
+ if (require_local)
+ {
+ if (!check_mempool_owner_eq (from,to))
+ 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));
+ }
+
+ // Writing into unknown mempool
+ else if (check_mempool_owner_eq (from, mono_mempool_no_owner))
+ {
+ check_mempool_assert_message ("Non-image memory attempting to write pointer to image %s", check_mempool_owner_name (to));
+ }
+
+ // Reading from unknown mempool
+ else if (check_mempool_owner_eq (to, mono_mempool_no_owner))
+ {
+ check_mempool_assert_message ("Attempting to write pointer from image %s to non-image memory", check_mempool_owner_name (from));
+ }
+
+ // Split out the four cases described above:
+ else if (from.image && to.image)
+ {
+ if (!check_image_may_reference_image (from.image, to.image))
+ 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));
+ }
+
+ else if (from.image && to.image_set)
+ {
+ if (!check_image_may_reference_image_set (from.image, to.image_set))
+ 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));
+ }
+
+ else if (from.image_set && to.image_set)
+ {
+ if (!check_image_set_may_reference_image_set (from.image_set, to.image_set))
+ 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));
+ }
+
+ else if (from.image_set && to.image)
+ {
+ if (!check_image_set_may_reference_image (from.image_set, to.image))
+ 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));
+ }
+
+ else
+ {
+ check_mempool_assert_message ("Internal logic error: Unreachable code");
+ }
+}
+
+void
+check_metadata_store (void *from, void *to)
+{
+ check_mempool_may_reference_mempool (from, to, FALSE);
+}
+
+void
+check_metadata_store_local (void *from, void *to)
+{
+ check_mempool_may_reference_mempool (from, to, TRUE);
+}
+
#endif /* CHECKED_BUILD */