From 5a486ac1d79ae8439943183f105e1013d3f0a8bf Mon Sep 17 00:00:00 2001 From: Andi McClure Date: Wed, 25 Nov 2015 17:07:59 -0500 Subject: [PATCH 1/5] Improvements to mempool reference auditing feature - New audit-point macro for atomic writes - Check imageset list directly (instead of indirectly through image list) - Check dynamic images and have special rules for them (for the time being we will allow dynamic images to point to non-dynamic ones without auditing, long-term this is a FIXME) - Print image names out with more detail (image sets get full names) - Remove some #if CHECKED_BUILDs - New g_assert_checked asserts for checked builds only --- mono/metadata/image-internals.h | 23 +------- mono/metadata/image.c | 51 ++++------------ mono/metadata/metadata-internals.h | 6 ++ mono/metadata/metadata.c | 47 +++++++++++++++ mono/metadata/reflection-internals.h | 3 + mono/metadata/reflection.c | 60 ++++++++++++++++++- mono/utils/checked-build.c | 87 ++++++++++++++++++++++++---- mono/utils/checked-build.h | 11 ++++ 8 files changed, 213 insertions(+), 75 deletions(-) diff --git a/mono/metadata/image-internals.h b/mono/metadata/image-internals.h index 3bfd9553dccc..e5211176d8ee 100644 --- a/mono/metadata/image-internals.h +++ b/mono/metadata/image-internals.h @@ -4,28 +4,9 @@ #ifndef __MONO_METADATA_IMAGE_INTERNALS_H__ #define __MONO_METADATA_IMAGE_INTERNALS_H__ -#ifdef CHECKED_BUILD - #include -#include - -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; -} - -MonoMemPoolOwner -mono_find_mempool_owner (void *ptr); -#endif /* CHECKED_BUILD */ +MonoImage * +mono_find_image_owner (void *ptr); #endif /* __MONO_METADATA_IMAGE_INTERNALS_H__ */ diff --git a/mono/metadata/image.c b/mono/metadata/image.c index 158831256dc2..a82df45a5bfd 100644 --- a/mono/metadata/image.c +++ b/mono/metadata/image.c @@ -2538,51 +2538,24 @@ mono_image_append_class_to_reflection_info_set (MonoClass *klass) mono_image_unlock (image); } -#if CHECKED_BUILD - -// These are support for the mempool reference tracking feature in checked-build, but live in image.c due to use of static variables of this file. - -// Given an image and a pointer, return the mempool owner if it is either this image or one of its imagesets. -static MonoMemPoolOwner -check_for_mempool_owner (void *ptr, MonoImage *image) -{ - if (mono_mempool_contains_addr (image->mempool, ptr)) - { - MonoMemPoolOwner owner = {image, NULL}; - return owner; - } - - GSList *l; - for (l = image->image_sets; l; l = l->next) { - MonoImageSet *set = l->data; - - if (mono_mempool_contains_addr (set->mempool, ptr)) - { - MonoMemPoolOwner owner = {NULL, set}; - return owner; - } - } - - return mono_mempool_no_owner; -} +// This is support for the mempool reference tracking feature in checked-build, but lives in image.c due to use of static variables of this file. /** - * mono_find_mempool_owner: + * mono_find_image_owner: * - * Find the image or imageset, if any, which a given pointer is located in the memory of. + * Find the image, if any, which a given pointer is located in the memory of. */ -MonoMemPoolOwner -mono_find_mempool_owner (void *ptr) +MonoImage * +mono_find_image_owner (void *ptr) { mono_images_lock (); - MonoMemPoolOwner owner = mono_mempool_no_owner; - gboolean searching = TRUE; + MonoImage *owner = NULL; // Iterate over both by-path image hashes const int hash_candidates[] = {IMAGES_HASH_PATH, IMAGES_HASH_PATH_REFONLY}; int hash_idx; - for (hash_idx = 0; searching && hash_idx < G_N_ELEMENTS (hash_candidates); hash_idx++) + for (hash_idx = 0; !owner && hash_idx < G_N_ELEMENTS (hash_candidates); hash_idx++) { GHashTable *target = loaded_images_hashes [hash_candidates [hash_idx]]; GHashTableIter iter; @@ -2590,14 +2563,12 @@ mono_find_mempool_owner (void *ptr) // Iterate over images within a hash g_hash_table_iter_init (&iter, target); - while (searching && g_hash_table_iter_next(&iter, NULL, (gpointer *)&image)) + while (!owner && g_hash_table_iter_next(&iter, NULL, (gpointer *)&image)) { mono_image_lock (image); - owner = check_for_mempool_owner (ptr, image); + if (mono_mempool_contains_addr (image->mempool, ptr)) + owner = image; mono_image_unlock (image); - - // Continue searching if null owner returned - searching = check_mempool_owner_eq (owner, mono_mempool_no_owner); } } @@ -2605,5 +2576,3 @@ mono_find_mempool_owner (void *ptr) return owner; } - -#endif diff --git a/mono/metadata/metadata-internals.h b/mono/metadata/metadata-internals.h index 710cdf0b5020..0a72e72db953 100644 --- a/mono/metadata/metadata-internals.h +++ b/mono/metadata/metadata-internals.h @@ -899,5 +899,11 @@ mono_method_get_wrapper_cache (MonoMethod *method); MonoType* mono_metadata_parse_type_checked (MonoImage *m, MonoGenericContainer *container, short opt_attrs, gboolean transient, const char *ptr, const char **rptr, MonoError *error); +char * +mono_image_set_description (MonoImageSet *); + +MonoImageSet * +mono_find_image_set_owner (void *ptr); + #endif /* __MONO_METADATA_INTERNALS_H__ */ diff --git a/mono/metadata/metadata.c b/mono/metadata/metadata.c index 1f74ab9debc2..2ebb8ee00350 100644 --- a/mono/metadata/metadata.c +++ b/mono/metadata/metadata.c @@ -2503,6 +2503,24 @@ mono_image_set_strdup (MonoImageSet *set, const char *s) return res; } +// Get a descriptive string for a MonoImageSet +// Callers are obligated to free buffer with g_free after use +char * +mono_image_set_description (MonoImageSet *set) +{ + GString *result = g_string_new (NULL); + int img; + g_string_append (result, "["); + for (img = 0; img < set->nimages; img++) + { + if (img > 0) + g_string_append (result, ", "); + g_string_append (result, set->images[img]->name); + } + g_string_append (result, "]"); + return g_string_free (result, FALSE); +} + /* * Structure used by the collect_..._images functions to store the image list. */ @@ -6638,3 +6656,32 @@ mono_method_get_wrapper_cache (MonoMethod *method) return &method->klass->image->wrapper_caches; } } + +// This is support for the mempool reference tracking feature in checked-build, but lives in metadata.c due to use of static variables of this file. + +/** + * mono_find_image_set_owner: + * + * Find the imageset, if any, which a given pointer is located in the memory of. + */ +MonoImageSet * +mono_find_image_set_owner (void *ptr) +{ + MonoImageSet *owner = NULL; + int i; + + image_sets_lock (); + + if (image_sets) + { + for (i = 0; !owner && i < image_sets->len; ++i) { + MonoImageSet *set = g_ptr_array_index (image_sets, i); + if (mono_mempool_contains_addr (set->mempool, ptr)) + owner = set; + } + } + + image_sets_unlock (); + + return owner; +} diff --git a/mono/metadata/reflection-internals.h b/mono/metadata/reflection-internals.h index dc8141bbdbfb..54eee9f0c732 100644 --- a/mono/metadata/reflection-internals.h +++ b/mono/metadata/reflection-internals.h @@ -14,4 +14,7 @@ mono_custom_attrs_get_attr_checked (MonoCustomAttrInfo *ainfo, MonoClass *attr_k char* mono_identifier_unescape_type_name_chars (char* identifier); +MonoImage * +mono_find_dynamic_image_owner (void *ptr); + #endif /* __MONO_METADATA_REFLECTION_INTERNALS_H__ */ diff --git a/mono/metadata/reflection.c b/mono/metadata/reflection.c index 3d34adb29f1e..46420c8a175d 100644 --- a/mono/metadata/reflection.c +++ b/mono/metadata/reflection.c @@ -215,6 +215,48 @@ static void init_type_builder_generics (MonoObject *type); #define ADDP_IS_GREATER_OR_OVF(a, b, c) (((a) + (b) > (c)) || CHECK_ADDP_OVERFLOW_UN (a, b)) #define ADD_IS_GREATER_OR_OVF(a, b, c) (((a) + (b) > (c)) || CHECK_ADD4_OVERFLOW_UN (a, b)) +// The dynamic images list is only needed to support the mempool reference tracking feature in checked-build. +static GPtrArray *dynamic_images; +static mono_mutex_t dynamic_images_mutex; + +static inline void +dynamic_images_lock (void) +{ + mono_mutex_lock (&dynamic_images_mutex); +} + +static inline void +dynamic_images_unlock (void) +{ + mono_mutex_unlock (&dynamic_images_mutex); +} + +/** + * mono_find_dynamic_image_owner: + * + * Find the dynamic image, if any, which a given pointer is located in the memory of. + */ +MonoImage * +mono_find_dynamic_image_owner (void *ptr) +{ + MonoImage *owner = NULL; + int i; + + dynamic_images_lock (); + + if (dynamic_images) + { + for (i = 0; !owner && i < dynamic_images->len; ++i) { + MonoImage *image = g_ptr_array_index (dynamic_images, i); + if (mono_mempool_contains_addr (image->mempool, ptr)) + owner = image; + } + } + + dynamic_images_unlock (); + + return owner; +} void mono_reflection_init (void) @@ -5338,6 +5380,15 @@ create_dynamic_mono_image (MonoDynamicAssembly *assembly, char *assembly_name, c mono_profiler_module_loaded (&image->image, MONO_PROFILE_OK); + dynamic_images_lock (); + + if (!dynamic_images) + dynamic_images = g_ptr_array_new (); + + g_ptr_array_add (dynamic_images, image); + + dynamic_images_unlock (); + return image; } #endif @@ -5438,7 +5489,14 @@ mono_dynamic_image_free (MonoDynamicImage *image) for (i = 0; i < MONO_TABLE_NUM; ++i) { g_free (di->tables [i].values); } -} + + dynamic_images_lock (); + + if (dynamic_images) + g_ptr_array_remove (dynamic_images, di); + + dynamic_images_unlock (); +} void mono_dynamic_image_free_image (MonoDynamicImage *image) diff --git a/mono/utils/checked-build.c b/mono/utils/checked-build.c index 1a07001dc042..9690d9d030d7 100644 --- a/mono/utils/checked-build.c +++ b/mono/utils/checked-build.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #define MAX_NATIVE_BT 6 @@ -237,9 +238,24 @@ assert_gc_neutral_mode (void) // * 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. @@ -283,6 +299,18 @@ check_image_may_reference_image(MonoImage *from, MonoImage *to) 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 @@ -351,7 +379,7 @@ check_image_set_may_reference_image (MonoImageSet *from, MonoImage *to) mono_image_set_lock (from); for (idx = 0; !success && idx < from->nimages; idx++) { - if (!check_image_may_reference_image (from->images[idx], to)) + if (check_image_may_reference_image (from->images[idx], to)) success = TRUE; } mono_image_set_unlock (from); @@ -414,16 +442,51 @@ check_image_may_reference_image_set (MonoImage *from, MonoImageSet *to) } // 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) - return owner.image->name; - if (owner.image_set) // TODO: Construct a string containing all included images - return "(Imageset)"; - return "(Non-image memory)"; + { + 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) { @@ -436,44 +499,44 @@ check_mempool_may_reference_mempool (void *from_ptr, void *to_ptr, gboolean requ 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)); + 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)); + 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)); + 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)); + 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, but does not retain a reference", check_mempool_owner_name(from)); + 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 tried to point to image set, but does not retain a reference"); + 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 tried to point to image %s, but does not retain a reference", check_mempool_owner_name(to)); + 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 diff --git a/mono/utils/checked-build.h b/mono/utils/checked-build.h index 17524bdd1b83..8fac45b018fa 100644 --- a/mono/utils/checked-build.h +++ b/mono/utils/checked-build.h @@ -20,6 +20,8 @@ #ifdef CHECKED_BUILD +#define g_assert_checked g_assert + /* GC runtime modes rules: @@ -88,6 +90,12 @@ Functions that can be called from both coop or preept modes. (ptr) = (val); \ } while (0); +// Use when writing a pointer from one image or imageset to another (atomic version). +#define CHECKED_METADATA_WRITE_PTR_ATOMIC(ptr, val) do { \ + check_metadata_store (&(ptr), (val)); \ + mono_atomic_store_release (&(ptr), (val)); \ +} while (0); + /* This can be called by embedders */ @@ -116,6 +124,8 @@ void check_metadata_store_local(void *from, void *to); #else +#define g_assert_checked(...) + #define MONO_REQ_GC_SAFE_MODE #define MONO_REQ_GC_UNSAFE_MODE #define MONO_REQ_GC_NEUTRAL_MODE @@ -127,6 +137,7 @@ void check_metadata_store_local(void *from, void *to); #define CHECKED_METADATA_WRITE_PTR(ptr, val) do { (ptr) = (val); } while (0) #define CHECKED_METADATA_WRITE_PTR_LOCAL(ptr, val) do { (ptr) = (val); } while (0) +#define CHECKED_METADATA_WRITE_PTR_ATOMIC(ptr, val) do { mono_atomic_store_release (&(ptr), (val)); } while (0) #endif /* CHECKED_BUILD */ From 152d6d05611e45730cc55b8050def9cbad1bd5e8 Mon Sep 17 00:00:00 2001 From: Andi McClure Date: Wed, 25 Nov 2015 17:23:15 -0500 Subject: [PATCH 2/5] Add first batch of mempool-reference audit points to class.c --- mono/metadata/class.c | 46 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/mono/metadata/class.c b/mono/metadata/class.c index 548554a19872..fdcce9854a32 100644 --- a/mono/metadata/class.c +++ b/mono/metadata/class.c @@ -43,6 +43,7 @@ #include #include #include +#include MonoStats mono_stats; @@ -5692,13 +5693,16 @@ mono_class_setup_supertypes (MonoClass *klass) supertypes = mono_class_alloc0 (klass, sizeof (MonoClass *) * ms); if (klass->parent) { - supertypes [klass->idepth - 1] = klass; - memcpy (supertypes, klass->parent->supertypes, klass->parent->idepth * sizeof (gpointer)); + CHECKED_METADATA_WRITE_PTR ( supertypes [klass->idepth - 1] , klass ); + + int supertype_idx; + for (supertype_idx = 0; supertype_idx < klass->parent->idepth; supertype_idx++) + CHECKED_METADATA_WRITE_PTR ( supertypes [supertype_idx] , klass->parent->supertypes [supertype_idx] ); } else { - supertypes [0] = klass; + CHECKED_METADATA_WRITE_PTR ( supertypes [0] , klass ); } - mono_atomic_store_release (&klass->supertypes, supertypes); + CHECKED_METADATA_WRITE_PTR_ATOMIC ( klass->supertypes , supertypes ); } static gboolean @@ -6132,23 +6136,23 @@ make_generic_param_class (MonoGenericParam *param, MonoImage *image, gboolean is classes_size += sizeof (MonoClass); if (pinfo) { - klass->name = pinfo->name; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name , pinfo->name ); } else { int n = mono_generic_param_num (param); - klass->name = mono_image_alloc0 (image, 16); + CHECKED_METADATA_WRITE_PTR_LOCAL ( klass->name , mono_image_alloc0 (image, 16) ); sprintf ((char*)klass->name, "%d", n); } if (container) { if (is_mvar) { MonoMethod *omethod = container->owner.method; - klass->name_space = (omethod && omethod->klass) ? omethod->klass->name_space : ""; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , (omethod && omethod->klass) ? omethod->klass->name_space : "" ); } else { MonoClass *oklass = container->owner.klass; - klass->name_space = oklass ? oklass->name_space : ""; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , oklass ? oklass->name_space : "" ); } } else { - klass->name_space = ""; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , "" ); } mono_profiler_class_event (klass, MONO_PROFILE_START_LOAD); @@ -6160,30 +6164,32 @@ make_generic_param_class (MonoGenericParam *param, MonoImage *image, gboolean is pos = 0; if ((count > 0) && !MONO_CLASS_IS_INTERFACE (pinfo->constraints [0])) { - klass->parent = pinfo->constraints [0]; + CHECKED_METADATA_WRITE_PTR ( klass->parent , pinfo->constraints [0] ); pos++; - } else if (pinfo && pinfo->flags & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) - klass->parent = mono_class_from_name (mono_defaults.corlib, "System", "ValueType"); - else - klass->parent = mono_defaults.object_class; - + } else if (pinfo && pinfo->flags & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) { + CHECKED_METADATA_WRITE_PTR ( klass->parent , mono_class_from_name (mono_defaults.corlib, "System", "ValueType") ); + } else { + CHECKED_METADATA_WRITE_PTR ( klass->parent , mono_defaults.object_class ); + } if (count - pos > 0) { klass->interface_count = count - pos; - klass->interfaces = mono_image_alloc0 (image, sizeof (MonoClass *) * (count - pos)); + CHECKED_METADATA_WRITE_PTR_LOCAL ( klass->interfaces , mono_image_alloc0 (image, sizeof (MonoClass *) * (count - pos)) ); klass->interfaces_inited = TRUE; for (i = pos; i < count; i++) - klass->interfaces [i - pos] = pinfo->constraints [i]; + CHECKED_METADATA_WRITE_PTR ( klass->interfaces [i - pos] , pinfo->constraints [i] ); } - klass->image = image; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->image , image ); klass->inited = TRUE; - klass->cast_class = klass->element_class = klass; + CHECKED_METADATA_WRITE_PTR_LOCAL ( klass->cast_class , klass ); + CHECKED_METADATA_WRITE_PTR_LOCAL ( klass->element_class , klass ); klass->flags = TYPE_ATTRIBUTE_PUBLIC; klass->this_arg.type = klass->byval_arg.type = is_mvar ? MONO_TYPE_MVAR : MONO_TYPE_VAR; - klass->this_arg.data.generic_param = klass->byval_arg.data.generic_param = param; + CHECKED_METADATA_WRITE_PTR ( klass->this_arg.data.generic_param , param ); + CHECKED_METADATA_WRITE_PTR ( klass->byval_arg.data.generic_param , param ); klass->this_arg.byref = TRUE; /* We don't use type_token for VAR since only classes can use it (not arrays, pointer, VARs, etc) */ From 4a3498d994579b80c5c9c7be85c7fe8cc2dc1906 Mon Sep 17 00:00:00 2001 From: Andi McClure Date: Wed, 25 Nov 2015 17:38:03 -0500 Subject: [PATCH 3/5] Clarify ownership of generic param objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes rule violations found by the first batch of mempool-reference audit points. The fix is to remove ambiguities in what image owns or should own any particular generic parameter object. Previously, there were four options for specifying a MonoGenericParam’s image: 1. klass/method union in MonoGenericContainer. 2. image field in MonoGenericContainer. 3. image field in MonoGenericParam. 4. No field; “image” passed in to methods in at time of usage. This patch removes 3 and 4; it preserves the image field from (2), but merges it into the union from (1). It adds a new field, “is_anonymous”, to indicate when the image member of the union should be used. The single source of the “owner" union now always points to the generic param’s image, directly or indirectly. A further change is made to support this: Previously, containers were optional, and the presence or absence of container was incidentally used to determine whether an object was a MonoGenericParam or a MonoGenericParamFull. Now, containers are mandatory for all MonoGenericParams, so a new “is_small_size” bit in the container tracks this information. This change opens some sources of risk: ⁃ Code which creates MonoGenericParams now must make sure to allocate a container early. ⁃ Code that accesses the MonoGenericContainer::owner field must all be correctly updated to look for is_anonymous or there will be a crash. (Much of this code would have previously used a NULL container as the flag for “anonymous”). ⁃ Code that casts MonoGenericParams to MonoGenericParamFull must use the is_small_size field. ⁃ Some generic containers will now exist with NULL type_params and 0 type_argc. Code which reads the type param array must either not receive objects from these containers, or must handle it. ⁃ If code by users of the embedding API exists which somehow depends on particular behavior related to the now-unused image argument of mono_class_from_generic_parameter, it will break. I have audited code around, at least, the first three of these cases and fixed what problems I found. --- mono/metadata/class-internals.h | 43 +++++--- mono/metadata/class.c | 159 +++++++++++++++++++---------- mono/metadata/icall.c | 5 +- mono/metadata/loader.c | 1 + mono/metadata/metadata-internals.h | 8 ++ mono/metadata/metadata.c | 140 ++++++++++--------------- mono/metadata/reflection.c | 6 +- mono/mini/aot-compiler.c | 5 +- mono/mini/aot-runtime.c | 18 ++-- mono/mini/debugger-agent.c | 4 +- mono/mini/mini-generic-sharing.c | 54 +++++----- 11 files changed, 239 insertions(+), 204 deletions(-) diff --git a/mono/metadata/class-internals.h b/mono/metadata/class-internals.h index a7f745474987..14c72b41fd54 100644 --- a/mono/metadata/class-internals.h +++ b/mono/metadata/class-internals.h @@ -570,13 +570,6 @@ struct _MonoGenericParam { * sharing. */ MonoType *gshared_constraint; - /* - * If owner is NULL, or owner is 'owned' by this gparam, - * then this is the image whose mempool this struct was allocated from. - * The second case happens for gparams created in - * mono_reflection_initialize_generic_parameter (). - */ - MonoImage *image; }; /* Additional details about a MonoGenericParam */ @@ -609,22 +602,28 @@ struct _MonoGenericContainer { the generic container of the containing class. */ MonoGenericContainer *parent; /* the generic type definition or the generic method definition corresponding to this container */ + /* Union rules: If is_anonymous, image field is valid; else if is_method, method field is valid; else klass is valid. */ union { MonoClass *klass; MonoMethod *method; + MonoImage *image; } owner; - int type_argc : 31; + int type_argc : 29; // Per the ECMA spec, this value is capped at 16 bits /* If true, we're a generic method, otherwise a generic type definition. */ /* Invariant: parent != NULL => is_method */ - int is_method : 1; + int is_method : 1; + /* If true, this container has no associated class/method and only the image is known. This can happen: + 1. For the special anonymous containers kept by MonoImage. + 2. During container creation via the mono_metadata_load_generic_params path-- in this case the caller + sets the owner, so temporarily while load_generic_params is completing the container is anonymous. + 3. When user code creates a generic parameter via SRE, but has not yet set an owner. */ + int is_anonymous : 1; + /* If false, all params in this container are full-size. If true, all params are just param structs. */ + /* This field is always == to the is_anonymous field, except in "temporary" cases (2) and (3) above. */ + /* TODO: Merge GenericParam and GenericParamFull, remove this field. Benefit is marginal. */ + int is_small_param : 1; /* Our type parameters. */ MonoGenericParamFull *type_params; - - /* - * For owner-less containers created by SRE, the image the container was - * allocated from. - */ - MonoImage *image; }; #define mono_generic_container_get_param(gc, i) ((MonoGenericParam *) ((gc)->type_params + (i))) @@ -632,8 +631,9 @@ struct _MonoGenericContainer { #define mono_generic_param_owner(p) ((p)->owner) #define mono_generic_param_num(p) ((p)->num) -#define mono_generic_param_info(p) (mono_generic_param_owner (p) ? &((MonoGenericParamFull *) p)->info : NULL) -#define mono_generic_param_name(p) (((mono_generic_param_owner (p) || (p)->gshared_constraint)) ? ((MonoGenericParamFull *) p)->info.name : NULL) +#define mono_generic_param_is_fullsize(p) ( !mono_generic_param_owner (p)->is_small_param ) +#define mono_generic_param_info(p) (mono_generic_param_is_fullsize (p) ? &((MonoGenericParamFull *) p)->info : NULL) +#define mono_generic_param_name(p) (((mono_generic_param_is_fullsize (p) || (p)->gshared_constraint)) ? ((MonoGenericParamFull *) p)->info.name : NULL) #define mono_type_get_generic_param_owner(t) (mono_generic_param_owner ((t)->data.generic_param)) #define mono_type_get_generic_param_num(t) (mono_generic_param_num ((t)->data.generic_param)) @@ -1416,4 +1416,13 @@ mono_field_from_token_checked (MonoImage *image, uint32_t token, MonoClass **ret gpointer mono_ldtoken_checked (MonoImage *image, guint32 token, MonoClass **handle_class, MonoGenericContext *context, MonoError *error); +MonoClass * +mono_class_from_generic_parameter_internal (MonoGenericParam *param); + +MonoImage * +get_image_for_generic_param (MonoGenericParam *param); + +char * +make_generic_name_string (MonoImage *image, int num); + #endif /* __MONO_METADATA_CLASS_INTERNALS_H__ */ diff --git a/mono/metadata/class.c b/mono/metadata/class.c index fdcce9854a32..315f60776a4e 100644 --- a/mono/metadata/class.c +++ b/mono/metadata/class.c @@ -5812,6 +5812,7 @@ mono_class_create_from_typedef (MonoImage *image, guint32 type_token, MonoError if (klass->generic_container) { klass->is_generic = 1; klass->generic_container->owner.klass = klass; + klass->generic_container->is_anonymous = FALSE; // Owner class is now known, container is no longer anonymous context = &klass->generic_container->context; } @@ -6121,16 +6122,59 @@ mono_generic_class_get_class (MonoGenericClass *gclass) return klass; } +static MonoImage * +get_image_for_container (MonoGenericContainer *container) +{ + MonoImage *result; + if (container->is_anonymous) { + result = container->owner.image; + } else { + MonoClass *klass; + if (container->is_method) { + MonoMethod *method = container->owner.method; + g_assert_checked (method); + klass = method->klass; + } else { + klass = container->owner.klass; + } + g_assert_checked (klass); + result = klass->image; + } + g_assert (result); + return result; +} + +MonoImage * +get_image_for_generic_param (MonoGenericParam *param) +{ + MonoGenericContainer *container = mono_generic_param_owner (param); + g_assert_checked (container); + return get_image_for_container (container); +} + +// Make a string in the designated image consisting of a single integer. +#define INT_STRING_SIZE 16 +char * +make_generic_name_string (MonoImage *image, int num) +{ + char *name = mono_image_alloc0 (image, INT_STRING_SIZE); + snprintf (name, INT_STRING_SIZE, "%d", num); + return name; +} + +// This is called by mono_class_from_generic_parameter_internal when a new class must be created. +// pinfo is derived from param by the caller for us. static MonoClass* -make_generic_param_class (MonoGenericParam *param, MonoImage *image, gboolean is_mvar, MonoGenericParamInfo *pinfo) +make_generic_param_class (MonoGenericParam *param, MonoGenericParamInfo *pinfo) { MonoClass *klass, **ptr; int count, pos, i; MonoGenericContainer *container = mono_generic_param_owner (param); + g_assert_checked (container); - if (!image) - /* FIXME: */ - image = mono_defaults.corlib; + MonoImage *image = get_image_for_container (container); + gboolean is_mvar = container->is_method; + gboolean is_anonymous = container->is_anonymous; klass = mono_image_alloc0 (image, sizeof (MonoClass)); classes_size += sizeof (MonoClass); @@ -6139,24 +6183,23 @@ make_generic_param_class (MonoGenericParam *param, MonoImage *image, gboolean is CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name , pinfo->name ); } else { int n = mono_generic_param_num (param); - CHECKED_METADATA_WRITE_PTR_LOCAL ( klass->name , mono_image_alloc0 (image, 16) ); - sprintf ((char*)klass->name, "%d", n); + + CHECKED_METADATA_WRITE_PTR_LOCAL ( klass->name , make_generic_name_string (image, n) ); } - if (container) { - if (is_mvar) { - MonoMethod *omethod = container->owner.method; - CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , (omethod && omethod->klass) ? omethod->klass->name_space : "" ); - } else { - MonoClass *oklass = container->owner.klass; - CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , oklass ? oklass->name_space : "" ); - } + if (is_anonymous) { + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , "" ); + } else if (is_mvar) { + MonoMethod *omethod = container->owner.method; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , (omethod && omethod->klass) ? omethod->klass->name_space : "" ); } else { - CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , "" ); + MonoClass *oklass = container->owner.klass; + CHECKED_METADATA_WRITE_PTR_EXEMPT ( klass->name_space , oklass ? oklass->name_space : "" ); } mono_profiler_class_event (klass, MONO_PROFILE_START_LOAD); + // Count non-NULL items in pinfo->constraints count = 0; if (pinfo) for (ptr = pinfo->constraints; ptr && *ptr; ptr++, count++) @@ -6222,18 +6265,25 @@ make_generic_param_class (MonoGenericParam *param, MonoImage *image, gboolean is #define FAST_CACHE_SIZE 16 /* + * get_anon_gparam_class and set_anon_gparam_class are helpers for mono_class_from_generic_parameter_internal. + * The latter will sometimes create MonoClasses for anonymous generic params. To prevent this being wasteful, + * we cache the MonoClasses. + * FIXME: It would be better to instead cache anonymous MonoGenericParams, and allow anonymous params to point directly to classes using the pklass field. * LOCKING: Takes the image lock depending on @take_lock. */ static MonoClass * -get_anon_gparam_class (MonoGenericParam *param, gboolean is_mvar, gboolean take_lock) +get_anon_gparam_class (MonoGenericParam *param, gboolean take_lock) { int n = mono_generic_param_num (param); - MonoImage *image = param->image; + MonoImage *image = get_image_for_generic_param (param); + gboolean is_mvar = mono_generic_param_owner (param)->is_method; MonoClass *klass = NULL; GHashTable *ht; g_assert (image); + // For params with a small num and no constraints, we use a "fast" cache which does simple num lookup in an array. + // For high numbers or constraints we have to use pointer hashes. if (param->gshared_constraint) { ht = is_mvar ? image->mvar_cache_constrained : image->var_cache_constrained; if (ht) { @@ -6268,10 +6318,11 @@ get_anon_gparam_class (MonoGenericParam *param, gboolean is_mvar, gboolean take_ * LOCKING: Image lock (param->image) must be held */ static void -set_anon_gparam_class (MonoGenericParam *param, gboolean is_mvar, MonoClass *klass) +set_anon_gparam_class (MonoGenericParam *param, MonoClass *klass) { int n = mono_generic_param_num (param); - MonoImage *image = param->image; + MonoImage *image = get_image_for_generic_param (param); + gboolean is_mvar = mono_generic_param_owner (param)->is_method; g_assert (image); @@ -6318,66 +6369,71 @@ set_anon_gparam_class (MonoGenericParam *param, gboolean is_mvar, MonoClass *kla * LOCKING: Acquires the image lock (@image). */ MonoClass * -mono_class_from_generic_parameter (MonoGenericParam *param, MonoImage *image, gboolean is_mvar) +mono_class_from_generic_parameter_internal (MonoGenericParam *param) { - MonoGenericContainer *container = mono_generic_param_owner (param); - MonoGenericParamInfo *pinfo = NULL; + MonoImage *image = get_image_for_generic_param (param); + MonoGenericParamInfo *pinfo = mono_generic_param_info (param); MonoClass *klass, *klass2; - if (container) { - pinfo = mono_generic_param_info (param); + // If a klass already exists for this object and is cached, return it. + if (pinfo) // Non-anonymous klass = pinfo->pklass; - } else { - image = NULL; - klass = get_anon_gparam_class (param, is_mvar, TRUE); - } + else // Anonymous + klass = get_anon_gparam_class (param, TRUE); + if (klass) return klass; - if (!image && container) { - if (is_mvar) { - MonoMethod *method = container->owner.method; - image = (method && method->klass) ? method->klass->image : NULL; - } else { - MonoClass *klass = container->owner.klass; - // FIXME: 'klass' should not be null - // But, monodis creates GenericContainers without associating a owner to it - image = klass ? klass->image : NULL; - } - } - - klass = make_generic_param_class (param, image, is_mvar, pinfo); + // Create a new klass + klass = make_generic_param_class (param, pinfo); + // Now we need to cache the klass we created. + // But since we wait to grab the lock until after creating the klass, we need to check to make sure + // another thread did not get in and cache a klass ahead of us. In that case, return their klass + // and allow our newly-created klass object to just leak. mono_memory_barrier (); - if (!image) //FIXME is this only needed by monodis? Can't we fix monodis instead of having this hack? - image = mono_defaults.corlib; - mono_image_lock (image); - if (container) + + // Here "klass2" refers to the klass potentially created by the other thread. + if (pinfo) // Repeat check from above klass2 = pinfo->pklass; else - klass2 = get_anon_gparam_class (param, is_mvar, FALSE); + klass2 = get_anon_gparam_class (param, FALSE); if (klass2) { klass = klass2; } else { - if (container) + // Cache here + if (pinfo) pinfo->pklass = klass; else - set_anon_gparam_class (param, is_mvar, klass); + set_anon_gparam_class (param, klass); } mono_image_unlock (image); /* FIXME: Should this go inside 'make_generic_param_klass'? */ if (klass2) - mono_profiler_class_loaded (klass2, MONO_PROFILE_FAILED); + mono_profiler_class_loaded (klass2, MONO_PROFILE_FAILED); // Alert profiler about botched class create else mono_profiler_class_loaded (klass, MONO_PROFILE_OK); return klass; } +/** + * mono_class_from_generic_parameter: + * @param: Parameter to find/construct a class for. + * @arg2: Is ignored. + * @arg3: Is ignored. + */ +MonoClass * +mono_class_from_generic_parameter (MonoGenericParam *param, MonoImage *arg2 G_GNUC_UNUSED, gboolean arg3 G_GNUC_UNUSED) +{ + return mono_class_from_generic_parameter_internal (param); +} + + MonoClass * mono_ptr_class_get (MonoType *type) { @@ -6545,10 +6601,9 @@ mono_class_from_mono_type (MonoType *type) return type->data.klass; case MONO_TYPE_GENERICINST: return mono_generic_class_get_class (type->data.generic_class); - case MONO_TYPE_VAR: - return mono_class_from_generic_parameter (type->data.generic_param, NULL, FALSE); case MONO_TYPE_MVAR: - return mono_class_from_generic_parameter (type->data.generic_param, NULL, TRUE); + case MONO_TYPE_VAR: + return mono_class_from_generic_parameter_internal (type->data.generic_param); default: g_warning ("mono_class_from_mono_type: implement me 0x%02x\n", type->type); g_assert_not_reached (); diff --git a/mono/metadata/icall.c b/mono/metadata/icall.c index 1b36b74bc697..a4a6e9202d65 100644 --- a/mono/metadata/icall.c +++ b/mono/metadata/icall.c @@ -2324,7 +2324,7 @@ ves_icall_MonoType_GetGenericArguments (MonoReflectionType *type, MonoBoolean ru MonoGenericContainer *container = klass->generic_container; res = create_type_array (domain, runtimeTypeArray, container->type_argc); for (i = 0; i < container->type_argc; ++i) { - pklass = mono_class_from_generic_parameter (mono_generic_container_get_param (container, i), klass->image, FALSE); + pklass = mono_class_from_generic_parameter_internal (mono_generic_container_get_param (container, i)); mono_array_setref (res, i, mono_type_get_object (domain, &pklass->byval_arg)); } } else if (klass->generic_class) { @@ -2678,8 +2678,7 @@ ves_icall_MonoMethod_GetGenericArguments (MonoReflectionMethod *method) for (i = 0; i < count; i++) { MonoGenericContainer *container = mono_method_get_generic_container (method->method); MonoGenericParam *param = mono_generic_container_get_param (container, i); - MonoClass *pklass = mono_class_from_generic_parameter ( - param, method->method->klass->image, TRUE); + MonoClass *pklass = mono_class_from_generic_parameter_internal (param); mono_array_setref (res, i, mono_type_get_object (domain, &pklass->byval_arg)); } diff --git a/mono/metadata/loader.c b/mono/metadata/loader.c index b99d6a00b116..0cb4cb401d62 100644 --- a/mono/metadata/loader.c +++ b/mono/metadata/loader.c @@ -1942,6 +1942,7 @@ mono_get_method_from_token (MonoImage *image, guint32 token, MonoClass *klass, if (generic_container) { result->is_generic = TRUE; generic_container->owner.method = result; + generic_container->is_anonymous = FALSE; // Method is now known, container is no longer anonymous /*FIXME put this before the image alloc*/ if (!mono_metadata_load_generic_param_constraints_checked (image, token, generic_container, error)) return NULL; diff --git a/mono/metadata/metadata-internals.h b/mono/metadata/metadata-internals.h index 0a72e72db953..30c3fb07a60f 100644 --- a/mono/metadata/metadata-internals.h +++ b/mono/metadata/metadata-internals.h @@ -389,6 +389,11 @@ struct _MonoImage { /* The loader used to load this image */ MonoImageLoader *loader; + // Containers for MonoGenericParams associated with this image but not with any specific class or method. Created on demand. + // This could happen, for example, for MonoTypes associated with TypeSpec table entries. + MonoGenericContainer *anonymous_generic_class_container; + MonoGenericContainer *anonymous_generic_method_container; + /* * No other runtime locks must be taken while holding this lock. * It's meant to be used only to mutate and query structures part of this image. @@ -899,6 +904,9 @@ mono_method_get_wrapper_cache (MonoMethod *method); MonoType* mono_metadata_parse_type_checked (MonoImage *m, MonoGenericContainer *container, short opt_attrs, gboolean transient, const char *ptr, const char **rptr, MonoError *error); +MonoGenericContainer * +get_anonymous_container_for_image (MonoImage *image, gboolean is_mvar); + char * mono_image_set_description (MonoImageSet *); diff --git a/mono/metadata/metadata.c b/mono/metadata/metadata.c index 2ebb8ee00350..9ad1de0f37f1 100644 --- a/mono/metadata/metadata.c +++ b/mono/metadata/metadata.c @@ -28,6 +28,7 @@ #include "abi-details.h" #include #include +#include /* Auxiliary structure used for caching inflated signatures */ typedef struct { @@ -2288,43 +2289,9 @@ type_in_image (MonoType *type, MonoImage *image) goto retry; case MONO_TYPE_FNPTR: return signature_in_image (type->data.method, image); - case MONO_TYPE_VAR: { - MonoGenericContainer *container = mono_type_get_generic_param_owner (type); - if (container) { - g_assert (!container->is_method); - /* - * FIXME: The following check is here solely - * for monodis, which uses the internal - * function - * mono_metadata_load_generic_params(). The - * caller of that function needs to fill in - * owner->klass or owner->method of the - * returned struct, but monodis doesn't do - * that. The image unloading depends on that, - * however, so a crash results without this - * check. - */ - if (!container->owner.klass) - return container->image == image; - return container->owner.klass->image == image; - } else { - return type->data.generic_param->image == image; - } - } - case MONO_TYPE_MVAR: { - MonoGenericContainer *container = mono_type_get_generic_param_owner (type); - if (type->data.generic_param->image == image) - return TRUE; - if (container) { - g_assert (container->is_method); - if (!container->owner.method) - /* RefEmit created generic param whose method is not finished */ - return container->image == image; - return container->owner.method->klass->image == image; - } else { - return type->data.generic_param->image == image; - } - } + case MONO_TYPE_VAR: + case MONO_TYPE_MVAR: + return image == get_image_for_generic_param (type->data.generic_param); default: /* At this point, we should've avoided all potential allocations in mono_class_from_mono_type () */ return image == mono_class_from_mono_type (type)->image; @@ -2655,48 +2622,13 @@ collect_type_images (MonoType *type, CollectData *data) case MONO_TYPE_FNPTR: //return signature_in_image (type->data.method, image); g_assert_not_reached (); - case MONO_TYPE_VAR: { - MonoGenericContainer *container = mono_type_get_generic_param_owner (type); - if (container) { - g_assert (!container->is_method); - /* - * FIXME: The following check is here solely - * for monodis, which uses the internal - * function - * mono_metadata_load_generic_params(). The - * caller of that function needs to fill in - * owner->klass or owner->method of the - * returned struct, but monodis doesn't do - * that. The image unloading depends on that, - * however, so a crash results without this - * check. - */ - if (!container->owner.klass) - add_image (container->image, data); - else - add_image (container->owner.klass->image, data); - } else { - add_image (type->data.generic_param->image, data); - } - } + case MONO_TYPE_VAR: + case MONO_TYPE_MVAR: + { + MonoImage *image = get_image_for_generic_param (type->data.generic_param); + add_image (image, data); break; - case MONO_TYPE_MVAR: { - MonoGenericContainer *container = mono_type_get_generic_param_owner (type); - if (type->data.generic_param->image) - add_image (type->data.generic_param->image, data); - if (container) { - if (!container->owner.method) { - /* RefEmit created generic param whose method is not finished */ - add_image (container->image, data); - } else { - g_assert (container->is_method); - add_image (container->owner.method->klass->image, data); - } - } else { - add_image (type->data.generic_param->image, data); - } } - break; case MONO_TYPE_CLASS: case MONO_TYPE_VALUETYPE: add_image (mono_class_from_mono_type (type)->image, data); @@ -3213,7 +3145,36 @@ select_container (MonoGenericContainer *gc, MonoTypeEnum type) return gc; } -/* +MonoGenericContainer * +get_anonymous_container_for_image (MonoImage *image, gboolean is_mvar) +{ + MonoGenericContainer **container_pointer; + if (is_mvar) + container_pointer = &image->anonymous_generic_method_container; + else + container_pointer = &image->anonymous_generic_class_container; + MonoGenericContainer *result = *container_pointer; + + // This container has never been created; make it now. + if (!result) + { + // Note this is never deallocated anywhere-- it exists for the lifetime of the image it's allocated from + result = mono_image_alloc0 (image, sizeof (MonoGenericContainer)); + result->owner.image = image; + result->is_anonymous = TRUE; + result->is_small_param = TRUE; + result->is_method = is_mvar; + + // If another thread already made a container, use that and leak this new one. + // (Technically it would currently be safe to just assign instead of CASing.) + MonoGenericContainer *exchange = InterlockedCompareExchangePointer ((volatile gpointer *)container_pointer, result, NULL); + if (exchange) + result = exchange; + } + return result; +} + +/* * mono_metadata_parse_generic_param: * @generic_container: Our MonoClass's or MonoMethod's MonoGenericContainer; * see mono_metadata_parse_type_full() for details. @@ -3232,12 +3193,24 @@ mono_metadata_parse_generic_param (MonoImage *m, MonoGenericContainer *generic_c generic_container = select_container (generic_container, type); if (!generic_container) { + gboolean is_mvar = FALSE; + switch (type) + { + case MONO_TYPE_VAR: + break; + case MONO_TYPE_MVAR: + is_mvar = TRUE; + break; + default: + g_error ("Cerating generic param object with invalid MonoType"); // This is not a generic param + } + /* Create dummy MonoGenericParam */ MonoGenericParam *param; param = mono_image_alloc0 (m, sizeof (MonoGenericParam)); param->num = index; - param->image = m; + param->owner = get_anonymous_container_for_image (m, is_mvar); return param; } @@ -4949,12 +4922,8 @@ mono_metadata_generic_param_equal_internal (MonoGenericParam *p1, MonoGenericPar * image B gets that generic inst from the cache, image A is * unloaded, so the inst is deleted, but image B still retains * a pointer to it. - * - * The AOT runtime doesn't set the image when it's decoding - * types, so we only compare it when the owner is NULL. */ - if (mono_generic_param_owner (p1) == mono_generic_param_owner (p2) && - (mono_generic_param_owner (p1) || p1->image == p2->image)) + if (mono_generic_param_owner (p1) == mono_generic_param_owner (p2)) return TRUE; /* @@ -6230,7 +6199,8 @@ mono_metadata_load_generic_params (MonoImage *image, guint32 token, MonoGenericC params = NULL; n = 0; container = mono_image_alloc0 (image, sizeof (MonoGenericContainer)); - container->image = image; + container->owner.image = image; // Temporarily mark as anonymous, but this will be overriden by caller + container->is_anonymous = TRUE; do { n++; params = g_realloc (params, sizeof (MonoGenericParamFull) * n); diff --git a/mono/metadata/reflection.c b/mono/metadata/reflection.c index 46420c8a175d..411351721863 100644 --- a/mono/metadata/reflection.c +++ b/mono/metadata/reflection.c @@ -10746,6 +10746,7 @@ reflection_methodbuilder_to_mono_method (MonoClass *klass, container->type_argc = count; container->type_params = image_g_new0 (image, MonoGenericParamFull, count); container->owner.method = m; + container->is_anonymous = FALSE; // Method is now known, container is no longer anonymous m->is_generic = TRUE; mono_method_set_generic_container (m, container); @@ -11946,7 +11947,8 @@ mono_reflection_initialize_generic_parameter (MonoReflectionGenericParam *gparam * Cannot set owner.method, since the MonoMethod is not created yet. * Set the image field instead, so type_in_image () works. */ - gparam->mbuilder->generic_container->image = klass->image; + gparam->mbuilder->generic_container->is_anonymous = TRUE; + gparam->mbuilder->generic_container->owner.image = klass->image; } param->param.owner = gparam->mbuilder->generic_container; } else if (gparam->tbuilder) { @@ -11958,7 +11960,7 @@ mono_reflection_initialize_generic_parameter (MonoReflectionGenericParam *gparam param->param.owner = gparam->tbuilder->generic_container; } - pklass = mono_class_from_generic_parameter ((MonoGenericParam *) param, image, gparam->mbuilder != NULL); + pklass = mono_class_from_generic_parameter_internal ((MonoGenericParam *) param); gparam->type.type = &pklass->byval_arg; diff --git a/mono/mini/aot-compiler.c b/mono/mini/aot-compiler.c index 49fb87c222d8..e828ba9d3d12 100644 --- a/mono/mini/aot-compiler.c +++ b/mono/mini/aot-compiler.c @@ -2508,10 +2508,9 @@ encode_klass_ref_inner (MonoAotCompile *acfg, MonoClass *klass, guint8 *buf, gui encode_value (klass->byval_arg.type, p, &p); encode_value (mono_type_get_generic_param_num (&klass->byval_arg), p, &p); - encode_value (container ? 1 : 0, p, &p); - if (container) { + encode_value (container->is_anonymous ? 0 : 1, p, &p); + if (!container->is_anonymous) { encode_value (container->is_method, p, &p); - g_assert (!par->gshared_constraint); if (container->is_method) encode_method_ref (acfg, container->owner.method, p, &p); else diff --git a/mono/mini/aot-runtime.c b/mono/mini/aot-runtime.c index bb6d72f607a1..82e78f2a48b2 100644 --- a/mono/mini/aot-runtime.c +++ b/mono/mini/aot-runtime.c @@ -471,16 +471,14 @@ decode_klass_ref (MonoAotModule *module, guint8 *buf, guint8 **endbuf) break; } case MONO_AOT_TYPEREF_VAR: { - MonoType *t; + MonoType *t = NULL; MonoGenericContainer *container = NULL; int type = decode_value (p, &p); int num = decode_value (p, &p); - gboolean has_container = decode_value (p, &p); + gboolean is_not_anonymous = decode_value (p, &p); MonoType *gshared_constraint = NULL; - char *par_name = NULL; - t = NULL; - if (has_container) { + if (is_not_anonymous) { gboolean is_method = decode_value (p, &p); if (is_method) { @@ -515,6 +513,9 @@ decode_klass_ref (MonoAotModule *module, guint8 *buf, guint8 **endbuf) t = mini_get_shared_gparam (&par_klass->byval_arg, gshared_constraint); } + + // We didn't decode is_method, so we have to infer it from type enum. + container = get_anonymous_container_for_image (module->assembly->image, type == MONO_TYPE_MVAR); } if (t) { @@ -522,18 +523,17 @@ decode_klass_ref (MonoAotModule *module, guint8 *buf, guint8 **endbuf) } else { t = g_new0 (MonoType, 1); t->type = type; - if (container) { + if (is_not_anonymous) { t->data.generic_param = mono_generic_container_get_param (container, num); g_assert (gshared_constraint == NULL); } else { /* Anonymous */ MonoGenericParam *par = (MonoGenericParam*)mono_image_alloc0 (module->assembly->image, sizeof (MonoGenericParamFull)); + par->owner = container; par->num = num; par->gshared_constraint = gshared_constraint; - par->image = module->assembly->image; t->data.generic_param = par; - if (par_name) - ((MonoGenericParamFull*)par)->info.name = par_name; + ((MonoGenericParamFull*)par)->info.name = make_generic_name_string (module->assembly->image, num); } // FIXME: Maybe use types directly to avoid // the overhead of creating MonoClass-es diff --git a/mono/mini/debugger-agent.c b/mono/mini/debugger-agent.c index eef2438346a6..87e05f892646 100644 --- a/mono/mini/debugger-agent.c +++ b/mono/mini/debugger-agent.c @@ -7932,7 +7932,7 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint count = container->type_argc; buffer_add_int (buf, count); for (i = 0; i < count; i++) { - pklass = mono_class_from_generic_parameter (mono_generic_container_get_param (container, i), klass->image, FALSE); + pklass = mono_class_from_generic_parameter_internal (mono_generic_container_get_param (container, i)); buffer_add_typeid (buf, domain, pklass); } } else { @@ -8544,7 +8544,7 @@ method_commands_internal (int command, MonoMethod *method, MonoDomain *domain, g buffer_add_int (buf, count); for (i = 0; i < count; i++) { MonoGenericParam *param = mono_generic_container_get_param (container, i); - MonoClass *pklass = mono_class_from_generic_parameter (param, method->klass->image, TRUE); + MonoClass *pklass = mono_class_from_generic_parameter_internal (param); buffer_add_typeid (buf, domain, pklass); } } else { diff --git a/mono/mini/mini-generic-sharing.c b/mono/mini/mini-generic-sharing.c index 6bec18405f58..a0841764ada7 100644 --- a/mono/mini/mini-generic-sharing.c +++ b/mono/mini/mini-generic-sharing.c @@ -2779,38 +2779,30 @@ mini_get_shared_gparam (MonoType *t, MonoType *constraint) key.param.param.gshared_constraint = constraint; g_assert (mono_generic_param_info (par)); - /* image might not be set for sre */ - if (par->owner && par->owner->image) { - image = par->owner->image; + image = get_image_for_generic_param(par); - /* - * Need a cache to ensure the newly created gparam - * is unique wrt T/CONSTRAINT. - */ - mono_image_lock (image); - if (!image->gshared_types) { - image->gshared_types_len = MONO_TYPE_INTERNAL; - image->gshared_types = g_new0 (GHashTable*, image->gshared_types_len); - } - if (!image->gshared_types [constraint->type]) - image->gshared_types [constraint->type] = g_hash_table_new (shared_gparam_hash, shared_gparam_equal); - res = g_hash_table_lookup (image->gshared_types [constraint->type], &key); - mono_image_unlock (image); - if (res) - return res; - copy = mono_image_alloc0 (image, sizeof (MonoGSharedGenericParam)); - memcpy (©->param, par, sizeof (MonoGenericParamFull)); - name = get_shared_gparam_name (constraint->type, ((MonoGenericParamFull*)copy)->info.name); - copy->param.info.name = mono_image_strdup (image, name); - g_free (name); - } else { - /* mono_generic_param_name () expects this to be a MonoGenericParamFull */ - copy = g_new0 (MonoGSharedGenericParam, 1); - memcpy (©->param, par, sizeof (MonoGenericParam)); - } - copy->param.param.owner = NULL; - // FIXME: - copy->param.param.image = image ? image : mono_defaults.corlib; + /* + * Need a cache to ensure the newly created gparam + * is unique wrt T/CONSTRAINT. + */ + mono_image_lock (image); + if (!image->gshared_types) { + image->gshared_types_len = MONO_TYPE_INTERNAL; + image->gshared_types = g_new0 (GHashTable*, image->gshared_types_len); + } + if (!image->gshared_types [constraint->type]) + image->gshared_types [constraint->type] = g_hash_table_new (shared_gparam_hash, shared_gparam_equal); + res = g_hash_table_lookup (image->gshared_types [constraint->type], &key); + mono_image_unlock (image); + if (res) + return res; + copy = mono_image_alloc0 (image, sizeof (MonoGSharedGenericParam)); + memcpy (©->param, par, sizeof (MonoGenericParamFull)); + name = get_shared_gparam_name (constraint->type, ((MonoGenericParamFull*)copy)->info.name); + copy->param.info.name = mono_image_strdup (image, name); + g_free (name); + + copy->param.param.owner = par->owner; copy->param.param.gshared_constraint = constraint; copy->parent = par; From ee43692e03b4cba553b86b4f99089c3b150d2b7a Mon Sep 17 00:00:00 2001 From: Andi McClure Date: Wed, 25 Nov 2015 17:40:10 -0500 Subject: [PATCH 4/5] Minor cleanup inspired by but unrelated to generic param refactor Comments, variable initialization, formatting. No functional changes. --- mono/metadata/class-internals.h | 4 +-- mono/metadata/class.c | 4 +-- mono/metadata/metadata.c | 54 +++++++++++++++++++++------------ mono/metadata/reflection.c | 2 ++ 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/mono/metadata/class-internals.h b/mono/metadata/class-internals.h index 14c72b41fd54..2888c0eaec03 100644 --- a/mono/metadata/class-internals.h +++ b/mono/metadata/class-internals.h @@ -66,7 +66,7 @@ struct _MonoMethod { guint16 flags; /* method flags */ guint16 iflags; /* method implementation flags */ guint32 token; - MonoClass *klass; + MonoClass *klass; /* To what class does this method belong */ MonoMethodSignature *signature; /* name is useful mostly for debugging */ const char *name; @@ -528,7 +528,7 @@ struct _MonoMethodInflated { */ struct _MonoGenericClass { MonoClass *container_class; /* the generic type definition */ - MonoGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */ + MonoGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */ /* FIXME: Only the class_inst member of "context" is ever used, so this field could be replaced with just a monogenericinst */ guint is_dynamic : 1; /* We're a MonoDynamicGenericClass */ guint is_tb_open : 1; /* This is the fully open instantiation for a type_builder. Quite ugly, but it's temporary.*/ MonoClass *cached_class; /* if present, the MonoClass corresponding to the instantiation. */ diff --git a/mono/metadata/class.c b/mono/metadata/class.c index 315f60776a4e..55dfcc1a488f 100644 --- a/mono/metadata/class.c +++ b/mono/metadata/class.c @@ -4269,7 +4269,7 @@ verify_class_overrides (MonoClass *klass, MonoMethod **overrides, int onum) } if (!mono_class_is_assignable_from_slow (decl->klass, klass)) { - mono_class_set_failure (klass, MONO_EXCEPTION_TYPE_LOAD, g_strdup ("Method overrides a class or interface that extended or implemented by this type")); + mono_class_set_failure (klass, MONO_EXCEPTION_TYPE_LOAD, g_strdup ("Method overrides a class or interface that is not extended or implemented by this type")); return FALSE; } @@ -5982,7 +5982,7 @@ mono_class_create_from_typedef (MonoImage *image, guint32 type_token, MonoError return NULL; } -/** is klass Nullable? */ +/** Is klass a Nullable ginst? */ gboolean mono_class_is_nullable (MonoClass *klass) { diff --git a/mono/metadata/metadata.c b/mono/metadata/metadata.c index 9ad1de0f37f1..e8eaed11c9e7 100644 --- a/mono/metadata/metadata.c +++ b/mono/metadata/metadata.c @@ -1493,10 +1493,14 @@ mono_generic_inst_equal_full (const MonoGenericInst *a, const MonoGenericInst *b { int i; -#ifndef MONO_SMALL_CONFIG - if (a->id && b->id) { + // An optimization: if the ids of two insts are the same, we know they are the same inst and don't check contents. + // Furthermore, because we perform early de-duping, if the ids differ, we know the contents differ. +#ifndef MONO_SMALL_CONFIG // Optimization does not work in MONO_SMALL_CONFIG: There are no IDs + if (a->id && b->id) { // "id 0" means "object has no id"-- de-duping hasn't been performed yet, must check contents. if (a->id == b->id) return TRUE; + // In signature-comparison mode id equality implies object equality, but this is not true for inequality. + // Two separate objects could have signature-equavalent contents. if (!signature_only) return FALSE; } @@ -1612,7 +1616,7 @@ mono_metadata_parse_type_internal (MonoImage *m, MonoGenericContainer *container gboolean byref = FALSE; gboolean pinned = FALSE; const char *tmp_ptr; - int count = 0; + int count = 0; // Number of mod arguments gboolean found; /* @@ -1646,7 +1650,7 @@ mono_metadata_parse_type_internal (MonoImage *m, MonoGenericContainer *container } } - if (count) { + if (count) { // There are mods, so the MonoType will be of nonstandard size. int size; size = MONO_SIZEOF_TYPE + ((gint32)count) * sizeof (MonoCustomMod); @@ -1654,12 +1658,12 @@ mono_metadata_parse_type_internal (MonoImage *m, MonoGenericContainer *container type->num_mods = count; if (count > 64) g_warning ("got more than 64 modifiers in type"); - } else { + } else { // The type is of standard size, so we can allocate it on the stack. type = &stype; memset (type, 0, MONO_SIZEOF_TYPE); } - /* Parse pinned, byref and custom modifiers */ + /* Iterate again, but now parse pinned, byref and custom modifiers */ found = TRUE; count = 0; while (found) { @@ -1695,6 +1699,7 @@ mono_metadata_parse_type_internal (MonoImage *m, MonoGenericContainer *container if (rptr) *rptr = ptr; + // Possibly we can return an already-allocated type instead of the one we decoded if (!type->num_mods && !transient) { /* no need to free type here, because it is on the stack */ if ((type->type == MONO_TYPE_CLASS || type->type == MONO_TYPE_VALUETYPE) && !type->pinned && !type->attrs) { @@ -1730,7 +1735,7 @@ mono_metadata_parse_type_internal (MonoImage *m, MonoGenericContainer *container /* printf ("%x %x %c %s\n", type->attrs, type->num_mods, type->pinned ? 'p' : ' ', mono_type_full_name (type)); */ - if (type == &stype) { + if (type == &stype) { // Type was allocated on the stack, so we need to copy it to safety type = transient ? g_malloc (MONO_SIZEOF_TYPE) : mono_image_alloc (m, MONO_SIZEOF_TYPE); memcpy (type, &stype, MONO_SIZEOF_TYPE); } @@ -2322,11 +2327,12 @@ get_image_set (MonoImage **images, int nimages) MonoImageSet *set; GSList *l; - /* Common case */ + /* Common case: Image set contains corlib only. If we've seen that case before, we cached the set. */ if (nimages == 1 && images [0] == mono_defaults.corlib && mscorlib_image_set) return mscorlib_image_set; /* Happens with empty generic instances */ + // FIXME: Is corlib the correct thing to return here? If so, why? This may be an artifact of generic instances previously defaulting to allocating from corlib. if (nimages == 0) return mscorlib_image_set; @@ -2335,32 +2341,40 @@ get_image_set (MonoImage **images, int nimages) if (!image_sets) image_sets = g_ptr_array_new (); + // Before we go on, we should check to see whether a MonoImageSet with these images already exists. + // We can search the referred-by imagesets of any one of our images to do this. Arbitrarily pick one here: if (images [0] == mono_defaults.corlib && nimages > 1) - l = images [1]->image_sets; + l = images [1]->image_sets; // Prefer not to search the imagesets of corlib-- that will be a long list. else l = images [0]->image_sets; set = NULL; - for (; l; l = l->next) { + while (l) // Iterate over selected list, looking for an imageset with members equal to our target one + { set = l->data; - if (set->nimages == nimages) { + if (set->nimages == nimages) { // Member count differs, this can't be it + // Compare all members to all members-- order might be different for (j = 0; j < nimages; ++j) { for (k = 0; k < nimages; ++k) if (set->images [k] == images [j]) - break; + break; // Break on match + + // If we iterated all the way through set->images, images[j] was *not* found. if (k == nimages) - /* Not found */ - break; + break; // Break on "image not found" } + + // If we iterated all the way through images without breaking, all items in images were found in set->images if (j == nimages) - /* Found */ - break; + break; // Break on "found a set with equal members" } + + l = l->next; } + // If we iterated all the way through l without breaking, the imageset does not already exist and we shuold create it if (!l) { - /* Not found */ set = g_new0 (MonoImageSet, 1); set->nimages = nimages; set->images = g_new0 (MonoImage*, nimages); @@ -2371,7 +2385,7 @@ get_image_set (MonoImage **images, int nimages) set->ginst_cache = g_hash_table_new_full (mono_metadata_generic_inst_hash, mono_metadata_generic_inst_equal, NULL, (GDestroyNotify)free_generic_inst); set->gmethod_cache = g_hash_table_new_full (inflated_method_hash, inflated_method_equal, NULL, (GDestroyNotify)free_inflated_method); set->gsignature_cache = g_hash_table_new_full (inflated_signature_hash, inflated_signature_equal, NULL, (GDestroyNotify)free_inflated_signature); - + for (i = 0; i < nimages; ++i) set->images [i]->image_sets = g_slist_prepend (set->images [i]->image_sets, set); @@ -2966,12 +2980,11 @@ mono_metadata_lookup_generic_class (MonoClass *container_class, MonoGenericInst MonoImageSet *set; CollectData data; + memset (&helper, 0, sizeof(helper)); // act like g_new0 helper.container_class = container_class; helper.context.class_inst = inst; - helper.context.method_inst = NULL; helper.is_dynamic = is_dynamic; /* We use this in a hash lookup, which does not attempt to downcast the pointer */ helper.is_tb_open = is_tb_open; - helper.cached_class = NULL; collect_data_init (&data); @@ -5001,6 +5014,7 @@ mono_metadata_fnptr_equal (MonoMethodSignature *s1, MonoMethodSignature *s2, gbo * mono_metadata_type_equal: * @t1: a type * @t2: another type + * @signature_only: If true, treat ginsts as equal which are instantiated separately but have equal positional value * * Determine if @t1 and @t2 represent the same type. * Returns: #TRUE if @t1 and @t2 are equal. diff --git a/mono/metadata/reflection.c b/mono/metadata/reflection.c index 411351721863..30fbd69cf074 100644 --- a/mono/metadata/reflection.c +++ b/mono/metadata/reflection.c @@ -5419,6 +5419,7 @@ mono_dynamic_image_release_gc_roots (MonoDynamicImage *image) release_hashtable (&image->methodspec); } +// Free dynamic image pass one: Free resources but not image itself void mono_dynamic_image_free (MonoDynamicImage *image) { @@ -5498,6 +5499,7 @@ mono_dynamic_image_free (MonoDynamicImage *image) dynamic_images_unlock (); } +// Free dynamic image pass two: Free image itself (might never get called in some debug modes) void mono_dynamic_image_free_image (MonoDynamicImage *image) { From 1251a594c9f4df987d9a6d8651127302c1d6284b Mon Sep 17 00:00:00 2001 From: Andi McClure Date: Mon, 30 Nov 2015 13:39:27 -0500 Subject: [PATCH 5/5] Convert MonoGenericContainer/Param accessor macros to inline functions --- mono/metadata/class-internals.h | 67 ++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/mono/metadata/class-internals.h b/mono/metadata/class-internals.h index 2888c0eaec03..84a4c9090ae3 100644 --- a/mono/metadata/class-internals.h +++ b/mono/metadata/class-internals.h @@ -626,16 +626,63 @@ struct _MonoGenericContainer { MonoGenericParamFull *type_params; }; -#define mono_generic_container_get_param(gc, i) ((MonoGenericParam *) ((gc)->type_params + (i))) -#define mono_generic_container_get_param_info(gc, i) (&((gc)->type_params + (i))->info) - -#define mono_generic_param_owner(p) ((p)->owner) -#define mono_generic_param_num(p) ((p)->num) -#define mono_generic_param_is_fullsize(p) ( !mono_generic_param_owner (p)->is_small_param ) -#define mono_generic_param_info(p) (mono_generic_param_is_fullsize (p) ? &((MonoGenericParamFull *) p)->info : NULL) -#define mono_generic_param_name(p) (((mono_generic_param_is_fullsize (p) || (p)->gshared_constraint)) ? ((MonoGenericParamFull *) p)->info.name : NULL) -#define mono_type_get_generic_param_owner(t) (mono_generic_param_owner ((t)->data.generic_param)) -#define mono_type_get_generic_param_num(t) (mono_generic_param_num ((t)->data.generic_param)) +static inline MonoGenericParam * +mono_generic_container_get_param (MonoGenericContainer *gc, int i) +{ + return (MonoGenericParam *) &gc->type_params [i]; +} + +static inline MonoGenericParamInfo * +mono_generic_container_get_param_info (MonoGenericContainer *gc, int i) +{ + return &gc->type_params [i].info; +} + +static inline MonoGenericContainer * +mono_generic_param_owner (MonoGenericParam *p) +{ + return p->owner; +} + +static inline int +mono_generic_param_num (MonoGenericParam *p) +{ + return p->num; +} + +static inline gboolean +mono_generic_param_is_fullsize (MonoGenericParam *p) +{ + return !mono_generic_param_owner (p)->is_small_param; +} + +static inline MonoGenericParamInfo * +mono_generic_param_info (MonoGenericParam *p) +{ + if (mono_generic_param_is_fullsize (p)) + return &((MonoGenericParamFull *) p)->info; + return NULL; +} + +static inline const char * +mono_generic_param_name (MonoGenericParam *p) +{ + if (mono_generic_param_is_fullsize (p)) + return ((MonoGenericParamFull *) p)->info.name; + return NULL; +} + +static inline MonoGenericContainer * +mono_type_get_generic_param_owner (MonoType *t) +{ + return mono_generic_param_owner (t->data.generic_param); +} + +static inline int +mono_type_get_generic_param_num (MonoType *t) +{ + return mono_generic_param_num (t->data.generic_param); +} /* * Class information which might be cached by the runtime in the AOT file for