[checked] Imageset may reference corlib implicitly in another imageset.
[mono.git] / mono / utils / checked-build.c
index 7dd659918745bfc7419451f9cc3ab93d117fba25..65bff4d26fba514ac65af3529cc1042adb631e43 100644 (file)
 #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
@@ -76,6 +81,7 @@ translate_backtrace (gpointer native_trace[], int size)
 
 typedef struct {
        GPtrArray *transitions;
+       gboolean in_gc_critical_region;
 } CheckState;
 
 typedef struct {
@@ -225,4 +231,365 @@ assert_gc_neutral_mode (void)
        }
 }
 
+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 */