Skip to content

Commit 8e9be33

Browse files
lambdageekUnityAlex
authored andcommitted
[metadata] Fields whose types are gparams with a reference type constraint aren't blittlable. (mono#15761)
* [metadata] Fields whose types are gparams with a reference type constraint aren't blittlable. Don't try to layout the field to find out if it's blittable. For gshared gparams, follow the blittability of the constraint. Fixes certain recursive examples. ``` using System; namespace TestRecursiveType { class Program { static void Main(string[] args) { SubClass subC = new SubClass(); Console.WriteLine(subC.GetTest()); } } public struct ValueTest<U> { // When U is instantiated with T, from BaseClass, we know it'll be a // reference field, so we know the instantiation ValueTest<T> won't // be blittable. public readonly U value; } public abstract class BaseClass<T> where T : BaseClass<T> { public ValueTest<T> valueTest = default(ValueTest<T>); } public class SubClass : BaseClass<SubClass> { private String test = "test"; public string GetTest() { return test; } } } ``` Fixes mono#15760 --- The failure is happening when we are doing mono_class_setup_fields ("BaseClass<T>") which needs to decide for each field whether it is blittable or not. So therefore we are trying to decide if ValueTest<T> (that is: the ValueTest<U> field inside BaseClass<T>) is blittable or not. So we instantiate U with T. Now to decide whether VaueTest<T> is blittable or not, we look at every field. So then we look at T value. To decide if T is blittable we first check if it's a reference type. That check is currently inadequate for generic parameters - what the PR adds is the ability to see if theres a T : class constraint or a T : C constraint - where C is some class. As soon as we know that T's constraint will force it to be a reference type we can definitely say that T won't be blittable without having to initialize C, at all. Previously, Mono would see that T is some kind of type for which it couldn't definitively decide that it's a reference type and it would call: mono_class_setup_fields (field_class) which would then try to setup the fields of the parent class BaseClass<T>. And that would hit the recursion check. Unity cherry-pick note: Needed to bring MONO_CLASS_IS_INTERFACE_INTERNAL and mono_class_get_valuetype_class forward
1 parent 7a3ac72 commit 8e9be33

File tree

6 files changed

+169
-0
lines changed

6 files changed

+169
-0
lines changed

mono/metadata/class-internals.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,7 @@ GENERATE_GET_CLASS_WITH_CACHE_DECL (variant)
11861186
#endif
11871187

11881188
GENERATE_GET_CLASS_WITH_CACHE_DECL (appdomain_unloaded_exception)
1189+
GENERATE_GET_CLASS_WITH_CACHE_DECL (valuetype)
11891190

11901191
extern MonoDefaults mono_defaults;
11911192

@@ -1371,6 +1372,9 @@ mono_class_vtable_full (MonoDomain *domain, MonoClass *klass, MonoError *error);
13711372
gboolean
13721373
mono_class_is_assignable_from_slow (MonoClass *target, MonoClass *candidate);
13731374

1375+
MonoClass*
1376+
mono_generic_param_get_base_type (MonoClass *klass);
1377+
13741378
gboolean
13751379
mono_class_has_variant_generic_params (MonoClass *klass);
13761380

mono/metadata/class.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ static gboolean mono_class_set_failure (MonoClass *klass, MonoErrorBoxed *boxed_
8686

8787
static gboolean class_kind_may_contain_generic_instances (MonoTypeKind kind);
8888

89+
GENERATE_GET_CLASS_WITH_CACHE (valuetype, "System", "ValueType");
90+
8991

9092
/*
9193
We use gclass recording to allow recursive system f types to be referenced by a parent.
@@ -1760,6 +1762,59 @@ type_has_references (MonoClass *klass, MonoType *ftype)
17601762
return FALSE;
17611763
}
17621764

1765+
/**
1766+
* mono_class_is_gparam_with_nonblittable_parent:
1767+
* \param klass a generic parameter
1768+
*
1769+
* \returns TRUE if \p klass is definitely not blittable.
1770+
*
1771+
* A parameter is definitely not blittable if it has the IL 'reference'
1772+
* constraint, or if it has a class specified as a parent. If it has an IL
1773+
* 'valuetype' constraint or no constraint at all or only interfaces as
1774+
* constraints, we return FALSE because the parameter may be instantiated both
1775+
* with blittable and non-blittable types.
1776+
*
1777+
* If the paramter is a generic sharing parameter, we look at its gshared_constraint->blittable bit.
1778+
*/
1779+
static gboolean
1780+
mono_class_is_gparam_with_nonblittable_parent (MonoClass *klass)
1781+
{
1782+
MonoType *type = &klass->byval_arg;
1783+
g_assert (mono_type_is_generic_parameter (type));
1784+
MonoGenericParam *gparam = type->data.generic_param;
1785+
if (mono_generic_param_info (gparam) != NULL)
1786+
{
1787+
if ((mono_generic_param_info (gparam)->flags & GENERIC_PARAMETER_ATTRIBUTE_REFERENCE_TYPE_CONSTRAINT) != 0)
1788+
return TRUE;
1789+
if ((mono_generic_param_info (gparam)->flags & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) != 0)
1790+
return FALSE;
1791+
}
1792+
1793+
if (gparam->gshared_constraint) {
1794+
MonoClass *constraint_class = mono_class_from_mono_type (gparam->gshared_constraint);
1795+
return !constraint_class->blittable;
1796+
}
1797+
1798+
if (mono_generic_param_owner (gparam)->is_anonymous)
1799+
return FALSE;
1800+
1801+
/* We could have: T : U, U : Base. So have to follow the constraints. */
1802+
MonoClass *parent_class = mono_generic_param_get_base_type (klass);
1803+
g_assert (!MONO_CLASS_IS_INTERFACE_INTERNAL (parent_class));
1804+
/* Parent can only be: System.Object, System.ValueType or some specific base class.
1805+
*
1806+
* If the parent_class is ValueType, the valuetype constraint would be set, above, so
1807+
* we wouldn't get here.
1808+
*
1809+
* If there was a reference constraint, the parent_class would be System.Object,
1810+
* but we would have returned early above.
1811+
*
1812+
* So if we get here, there is either no base class constraint at all,
1813+
* in which case parent_class would be set to System.Object, or there is none at all.
1814+
*/
1815+
return parent_class != mono_defaults.object_class;
1816+
}
1817+
17631818
/*
17641819
* mono_class_layout_fields:
17651820
* @class: a class
@@ -1877,6 +1932,9 @@ mono_class_layout_fields (MonoClass *klass, int base_instance_size, int packing_
18771932
if (blittable) {
18781933
if (field->type->byref || MONO_TYPE_IS_REFERENCE (field->type)) {
18791934
blittable = FALSE;
1935+
} else if (mono_type_is_generic_parameter (field->type) &&
1936+
mono_class_is_gparam_with_nonblittable_parent (mono_class_from_mono_type (field->type))) {
1937+
blittable = FALSE;
18801938
} else {
18811939
MonoClass *field_class = mono_class_from_mono_type (field->type);
18821940
if (field_class) {
@@ -8645,6 +8703,60 @@ mono_class_is_assignable_from_slow (MonoClass *target, MonoClass *candidate)
86458703
return FALSE;
86468704
}
86478705

8706+
/**
8707+
* mono_generic_param_get_base_type:
8708+
*
8709+
* Return the base type of the given generic parameter from its constraints.
8710+
*
8711+
* Could be another generic parameter, or it could be Object or ValueType.
8712+
*/
8713+
MonoClass*
8714+
mono_generic_param_get_base_type (MonoClass *klass)
8715+
{
8716+
MonoType *type = &klass->byval_arg;
8717+
g_assert (mono_type_is_generic_argument (type));
8718+
8719+
MonoGenericParam *gparam = type->data.generic_param;
8720+
8721+
g_assert (gparam->owner && !gparam->owner->is_anonymous);
8722+
8723+
MonoClass **constraints = mono_generic_container_get_param_info (gparam->owner, gparam->num)->constraints;
8724+
8725+
MonoClass *base_class = mono_defaults.object_class;
8726+
8727+
if (constraints) {
8728+
int i;
8729+
for (i = 0; constraints [i]; ++i) {
8730+
MonoClass *constraint = constraints[i];
8731+
8732+
if (MONO_CLASS_IS_INTERFACE_INTERNAL (constraint))
8733+
continue;
8734+
8735+
MonoType *constraint_type = &constraint->byval_arg;
8736+
if (mono_type_is_generic_argument (constraint_type)) {
8737+
MonoGenericParam *constraint_param = constraint_type->data.generic_param;
8738+
MonoGenericParamInfo *constraint_info = mono_generic_param_info (constraint_param);
8739+
if ((constraint_info->flags & GENERIC_PARAMETER_ATTRIBUTE_REFERENCE_TYPE_CONSTRAINT) == 0 &&
8740+
(constraint_info->flags & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) == 0)
8741+
continue;
8742+
}
8743+
8744+
base_class = constraint;
8745+
}
8746+
8747+
}
8748+
8749+
if (base_class == mono_defaults.object_class)
8750+
{
8751+
MonoGenericParamInfo *gparam_info = mono_generic_param_info (gparam);
8752+
if ((gparam_info->flags & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) != 0) {
8753+
base_class = mono_class_get_valuetype_class ();
8754+
}
8755+
}
8756+
8757+
return base_class;
8758+
}
8759+
86488760
/**
86498761
* mono_class_get_cctor:
86508762
* \param klass A MonoClass pointer

mono/metadata/metadata-internals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,5 +974,7 @@ mono_signature_get_managed_fmt_string (MonoMethodSignature *sig);
974974
gboolean
975975
mono_type_in_image (MonoType *type, MonoImage *image);
976976

977+
#define MONO_CLASS_IS_INTERFACE_INTERNAL(c) ((mono_class_get_flags (c) & TYPE_ATTRIBUTE_INTERFACE) || mono_type_is_generic_parameter (&c->byval_arg))
978+
977979
#endif /* __MONO_METADATA_INTERNALS_H__ */
978980

mono/metadata/metadata.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6815,6 +6815,11 @@ mono_type_is_pointer (MonoType *type)
68156815
mono_bool
68166816
mono_type_is_reference (MonoType *type)
68176817
{
6818+
/* NOTE: changing this function to return TRUE more often may have
6819+
* consequences for generic sharing in the AOT compiler. In
6820+
* particular, returning TRUE for generic parameters with a 'class'
6821+
* constraint may cause crashes.
6822+
*/
68186823
return (type && (((type->type == MONO_TYPE_STRING) ||
68196824
(type->type == MONO_TYPE_SZARRAY) || (type->type == MONO_TYPE_CLASS) ||
68206825
(type->type == MONO_TYPE_OBJECT) || (type->type == MONO_TYPE_ARRAY)) ||

mono/tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ TESTS_CS_SRC= \
374374
generic-stack-traces2.2.cs \
375375
bug-472600.2.cs \
376376
recursive-generics.2.cs \
377+
recursive-generics.3.cs \
377378
bug-473482.2.cs \
378379
bug-473999.2.cs \
379380
bug-479763.2.cs \

mono/tests/recursive-generics.3.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
3+
class Program {
4+
static void Main (string[] args)
5+
{
6+
// If this runs without a TLE, the test passed. A
7+
// TypeLoadException due to recursion during type
8+
// initialization is a failure.
9+
var subC = new SubClass ();
10+
Console.WriteLine (subC.GetTest ());
11+
// same as above, but try to land in generic sharing code.
12+
var genSubC = new GenericSubClass<object> ();
13+
Console.WriteLine (genSubC.GetTest ());
14+
}
15+
}
16+
17+
public struct ValueTest<U> {
18+
// When U is instantiated with T, from BaseClass, we know it'll be a
19+
// reference field without having to fully initialize its parent
20+
// (namely BaseClass<T> itself), so we know the instantiation
21+
// ValueTest<T> won't be blittable.
22+
public readonly U value;
23+
}
24+
25+
public abstract class BaseClass<T> where T : BaseClass<T> {
26+
public ValueTest<T> valueTest = default (ValueTest<T>);
27+
}
28+
29+
public class SubClass : BaseClass<SubClass> {
30+
private string test = "test";
31+
32+
public string GetTest()
33+
{
34+
return test;
35+
}
36+
}
37+
38+
public class GenericSubClass<T> : BaseClass<GenericSubClass<T>> {
39+
private string test = "test";
40+
41+
public string GetTest()
42+
{
43+
return test;
44+
}
45+
}

0 commit comments

Comments
 (0)