From 043677607f6cc3bc29824662e8344622f2f334fe Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 8 Oct 2026 19:59:39 +0200 Subject: [PATCH] [mono][aot] Fix recursive struct wrapper normalization get_wrapper_shared_vtype() normalizes each value type field while get_wrapper_shared_type_full() normalizes generic type arguments. A struct containing a generic struct instantiated over itself makes the two functions recurse until mono-aot-cross overflows its native stack. Track the value types currently being normalized. If a type is reached again, or an ever-growing generic shape reaches a bounded depth, keep the original type using the existing non-shared fallback. The regression test uses only the valid self-recursive shape. In particular, it does not reintroduce the mutually recursive generic value types from #135029 that caused TypeLoadException during test assembly loading. Fixes https://github.com/dotnet/runtime/issues/132071 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System/ValueTypeTests.cs | 23 +++++++++ src/mono/mono/mini/mini-generic-sharing.c | 49 +++++++++++++++---- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs index 92c7000ed414d5..69579feda11d88 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs @@ -330,6 +330,19 @@ public static void StructWithNestedOverriddenNotBitwiseComparableTest() Assert.Equal(obj1.GetHashCode(), obj2.GetHashCode()); } + [Fact] + public static void StructWithSelfReferencingGenericFieldPassedToGenericMethodTest() + { + var value = new StructWithSelfReferencingGenericField + { + value = new GenericFieldBox { value = "self" } + }; + + Assert.Equal("self", Identity(value).value.value); + } + + private static T Identity(T value) => value; + public struct S { public int x; @@ -443,5 +456,15 @@ public struct StructWithNestedOverriddenNotBitwiseComparable public StructOverriddenNotBitwiseComparable value1; public StructOverriddenNotBitwiseComparable value2; } + + private struct GenericFieldBox + { + public object value; + } + + private struct StructWithSelfReferencingGenericField + { + public GenericFieldBox value; + } } } diff --git a/src/mono/mono/mini/mini-generic-sharing.c b/src/mono/mono/mini/mini-generic-sharing.c index 8ae3d2cc42006e..bce4eec3569ea8 100644 --- a/src/mono/mono/mini/mini-generic-sharing.c +++ b/src/mono/mono/mini/mini-generic-sharing.c @@ -1157,19 +1157,35 @@ static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_5, "Mono", "ValueTuple`5"); static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_6, "Mono", "ValueTuple`6"); static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_7, "Mono", "ValueTuple`7"); +/* + * Tracks the valuetypes whose layout is currently being normalized by get_wrapper_shared_vtype (). + * A struct can reference itself through a type argument of a field type, e.g. + * struct S { Box f; }, which would otherwise recurse without bound. + */ +typedef struct _WrapperSharedVtypeFrame WrapperSharedVtypeFrame; +struct _WrapperSharedVtypeFrame { + MonoClass *klass; + WrapperSharedVtypeFrame *parent; + int depth; +}; + +/* Bounds expansion for types which grow on every level, e.g. struct S { Box>> f; } */ +#define WRAPPER_SHARED_VTYPE_MAX_DEPTH 16 + static MonoType* get_wrapper_shared_type (MonoType *t); static MonoType* -get_wrapper_shared_type_full (MonoType *t, gboolean field); +get_wrapper_shared_type_full (MonoType *t, gboolean field, WrapperSharedVtypeFrame *frame); /* * get_wrapper_shared_vtype: * * Return an instantiation of one of the Mono.ValueTuple types with the same - * layout as the valuetype KLASS. + * layout as the valuetype KLASS. Return NULL if the type should not be shared, + * including when its layout is already being normalized further up the stack. */ static MonoType* -get_wrapper_shared_vtype (MonoType *t) +get_wrapper_shared_vtype (MonoType *t, WrapperSharedVtypeFrame *parent) { ERROR_DECL (error); MonoGenericContext ctx; @@ -1184,6 +1200,19 @@ get_wrapper_shared_vtype (MonoType *t) // FIXME: Map 1 member structs to primitive types on platforms where its supported klass = mono_class_from_mono_type_internal (t); + + if (parent && parent->depth >= WRAPPER_SHARED_VTYPE_MAX_DEPTH) + return NULL; + for (WrapperSharedVtypeFrame *f = parent; f; f = f->parent) { + if (f->klass == klass) + return NULL; + } + + WrapperSharedVtypeFrame frame; + frame.klass = klass; + frame.parent = parent; + frame.depth = parent ? parent->depth + 1 : 1; + /* Under mono, auto and sequential layout are the same for valuetypes, see mono_class_layout_fields () */ if (((mono_class_get_flags (klass) & TYPE_ATTRIBUTE_LAYOUT_MASK) != TYPE_ATTRIBUTE_SEQUENTIAL_LAYOUT) && ((mono_class_get_flags (klass) & TYPE_ATTRIBUTE_LAYOUT_MASK) != TYPE_ATTRIBUTE_AUTO_LAYOUT)) @@ -1233,7 +1262,7 @@ get_wrapper_shared_vtype (MonoType *t) while ((field = mono_class_get_fields_internal (klass, &iter))) { if (field->type->attrs & (FIELD_ATTRIBUTE_STATIC | FIELD_ATTRIBUTE_HAS_FIELD_RVA)) continue; - MonoType *ftype = get_wrapper_shared_type_full (field->type, TRUE); + MonoType *ftype = get_wrapper_shared_type_full (field->type, TRUE, &frame); if (m_class_is_byreflike (mono_class_from_mono_type_internal (ftype))) /* Cannot inflate generic params with byreflike types */ return NULL; @@ -1326,7 +1355,7 @@ get_wrapper_shared_vtype (MonoType *t) * Return a type which is handled identically wrt to calling conventions as T. */ static MonoType* -get_wrapper_shared_type_full (MonoType *t, gboolean is_field) +get_wrapper_shared_type_full (MonoType *t, gboolean is_field, WrapperSharedVtypeFrame *frame) { if (m_type_is_byref (t)) return mono_class_get_byref_type (mono_defaults.int_class); @@ -1401,14 +1430,14 @@ get_wrapper_shared_type_full (MonoType *t, gboolean is_field) if (inst) { g_assert (inst->type_argc < 16); for (guint i = 0; i < inst->type_argc; ++i) - args [i] = get_wrapper_shared_type_full (inst->type_argv [i], TRUE); + args [i] = get_wrapper_shared_type_full (inst->type_argv [i], TRUE, frame); ctx.class_inst = mono_metadata_get_generic_inst (inst->type_argc, args); } inst = orig_ctx->method_inst; if (inst) { g_assert (inst->type_argc < 16); for (guint i = 0; i < inst->type_argc; ++i) - args [i] = get_wrapper_shared_type_full (inst->type_argv [i], TRUE); + args [i] = get_wrapper_shared_type_full (inst->type_argv [i], TRUE, frame); ctx.method_inst = mono_metadata_get_generic_inst (inst->type_argc, args); } klass = mono_class_inflate_generic_class_checked (mono_class_get_generic_class (klass)->container_class, &ctx, error); @@ -1418,13 +1447,13 @@ get_wrapper_shared_type_full (MonoType *t, gboolean is_field) mono_class_set_skip_generic_constraints (klass); t = m_class_get_byval_arg (klass); - MonoType *shared_type = get_wrapper_shared_vtype (t); + MonoType *shared_type = get_wrapper_shared_vtype (t, frame); if (shared_type) t = shared_type; return t; } case MONO_TYPE_VALUETYPE: { - MonoType *shared_type = get_wrapper_shared_vtype (t); + MonoType *shared_type = get_wrapper_shared_vtype (t, frame); if (shared_type) t = shared_type; return t; @@ -1440,7 +1469,7 @@ get_wrapper_shared_type_full (MonoType *t, gboolean is_field) static MonoType* get_wrapper_shared_type (MonoType *t) { - return get_wrapper_shared_type_full (t, FALSE); + return get_wrapper_shared_type_full (t, FALSE, NULL); } /* Returns the intptr type for types that are passed in a single register */