Skip to content

Commit

Permalink
[metadata] Implement PreserveBaseOverridesAttribute and covariant ret…
Browse files Browse the repository at this point in the history
…urns support (#19927)

Covariant return implementation for Mono.

---

There are also a couple of general Mono fixes in here, too:

1. Interpreter support for `MONO_TYPE_FNPTR`  in `mint_type()`
2. a "signature assignment" mode for `mono_class_is_assignable_from` that doesn't treat `IntPtr[]` and `long[]` (or `int[]`) as interchangeable
3. support for unmanaged pointer comparisons in `mono_class_is_assignable_from`
4. Factor `mono_byref_type_is_assignable_from` out of `ves_icall_RuntimeTypeHandle_type_is_assignable_from` and make it generally usable.

---

For covariant returns, there are a couple of pieces here:
1. Method overrides come in two flavors: implicit (matching by name and sig) and explicit (using a `.override` directive).
   * for explicit overrides, we have to check that the override has a signature that is subsumed by (its return type is a subtype of the return type of) the previous override, if any, and the declared method.
   * for implicit overrides, we have to check that the override has a signature that is subsumed by the previous most derived override (not just the one that we happened to find with a precisely matching signature).
2. If any override is marked by `[PreserveBaseOverridesAttribute]` then we need to find all the other slots that may have methods from the override chain and update them.  (The attribute is expected to be applied to a `newslot virtual` method that is an explicit `.override` of some other method).

There's an interesting test case where a class has _both_ an implicit and and explicit override for the same slot, arrived via different methods - in this case, the implicit one is supposed to be ignored.  We implement this by doing the implicit slot filling first, then the explicit, and saving the covariant return checking until after both are done.

---

Because the method (`MethodImpl`) is both an override of another virtual method,
and is itself a `newslot`, it will occupy (at least) 2 slots in the vtable -
its own newslot and the `MethodDecl` that it is overriding.

If a third class subclasses the class with the `MethodImpl` or one of its
descendents and itself overrides either the `MethodDecl` or (one of the)
`MethodImpl` (either implicitly or with another explicit .override)  the
`PreserveBaseOverridesAttribute` is a signal to apply the new override that it
should apply to all the slots.

The way this works is when processing the overrides, we walk up the class
hierarchy looking at the contents of the impl slot and if any of them have the
attribute, we make sure to add a mapping to the "`override_map`" to replace any
occurrences of a previous impl with the newest (most derived) impl.

An (existing) later pass goes through the vtable and applies the override_map.

Contributes to dotnet/runtime#37509


* [metadata] Implement support for PreserveBaseOverridesAttribute

The attribute is expected to be applied to a `newslot virtual` method that
is an explicit .override of some other method.

Becuase the method (MethodImpl) is both an override of another virtual method,
and is itself a `newslot`, it will occupy (at least) 2 slots in the vtable -
its own newslot and the MethodDecl that it is overriding.

If a third class subclasses the class with the MethodImpl or one of its
descendents and itself overrides either the MethodDecl or (one of the)
MethodImpl (either implicitly or with another explicit .override)  the
PreserveBaseOverridesAttribute is a signal to apply the new override that it
should apply to all the slots.

The way this works is when processing the overrides, we walk up the class
hierarchy looking at the contents of the impl slot and if any of them have the
attribute, we make sure to add a mapping to the "override_map" to replace any
occurrences of a previous impl with the newest (most derived) impl.

An (existing) later pass goes through the vtable and applies the override_map.

* add FIXME for dynamic images

* scan entire chain of inheritance, don't stop at the class with the attribute

   Makes OverrideMoreDerivedReturn.il pass

* UnitTestDelegates is also passing

* [metadata] Add mono_metadata_signature_equal_no_ret

* inflate signature->ret when doing covariant checking

* [class] Add mono_class_signature_is_assignable_from_checked

   In ECMA I.8.7.1 "assignment compatability for signature types" we need to
   distinguish IntPtr[] from int32[] and int64[].  The ordinary cast_class for an
   array type in Mono implements the "intermediate type" relation from I.8.7.3
   "general assignment compatability" - IntPtr[] gets treated as inter-castable
   with int32[] or int64[], depending on platform pointer size.

   The new mono_class_signature_is_assignable_from_checked function does the finer
   relation for signatures, while the old mono_class_signature_is_assignable_from_checked
   preserves the existing behavior used elsewhere in the runtime.

* Check every method previously in the slot against the new impl

* UnitTest_GVM passes

* [metadata] Don't infinite loop in mono_class_get_flags for a FNPTR

   When a MonoClass is a MONO_TYPE_FNPTR, the element_class is itself.  We should
   probably not do that, but meanwhile, make `mono_class_get_flags` return
   something else for function pointer types.

   Makes mono_class_is_interface not overflow the stack

* [metadata] Set MonoClass:cast_class for pointer types to the intermediate type

   Ignore signedness differences and set cast_class to the storage type, same as
   for arrays.  This is used by mono_class_is_assignable_from when comparing
   IntPtr* vs ulong*, for example.

* [metadata] Make mono_class_is_assignable_from work for unmanaged pointer types

   The issue is that we need to compare intermediate type or the reduced type of
   the element type, depending on whether we're doing compatability or general
   assignment compatability (upshot: ulong* and long* are always inter-assignable,
   but IntPtr* is not in signatures, but is for general storage)

* [metadata] Pull mono_type_byref_is_assignable_from out of ves_icall_RuntimeTypeHandle_type_is_assignable_from

   Make it a separate internal function

* [metadata] mono_type_byref_is_assignable_from for valuetypes and class types should check for identity

   X& and Y& where both are either a reference types or valuetypes are only
   assignable if X and Y are identical.  The old code was checking for identity
   for valuetypes, but for reference types it would allow any two reference types
   at all, which is incorrect.

* [class-init] Check for byref types in covariant return signatures

Lets us pass Loader/classloader/MethodImpl/CovariantReturns/UnitTest/CompatibleWithTest.il from the CoreCLR testsuite

* [interp] handle MONO_TYPE_FNPTR in mint_type

   It's just a pointer-sized integer, as far as the interpreter's type discipline is concerned

* [class-init] Use a bit for covariant return method impls

   Allows us to check implicit overrides against previous impl's sig when the previous impl potentially had a covariant return type.

   Also use the bit to decide if we need to check signatures of the entire impl chain for explicit instantiations

* ReturnTypeValidation unit tests work on Mono now

* [class-init] check covariant vtable slots after overloads are applied

   The check has to be done after both implicit and explicit overrides have been
   applied because some implicit overrides must be ignored.  If the check is done
   as soon as we detect that a covariant return method override is needed, we will
   incorrectly check implicit overrides that are to be ignored because a later
   explicit override applied to the same slot.

* [metadata] Fix byref IsAssignableFrom

   The reflection API has an interesting behavior, whereas the ECMA definition is
   stricter.

   ```
   public interface I {}
   
   public class B {}
   public class D : B, I {}
   
   public struct S : I { }
   
   class Program {
           static void Main(string[] args)
           {
   	    var tb = typeof (B).MakeByRefType ();
   	    var td = typeof (D).MakeByRefType ();
   	    var ti = typeof (I).MakeByRefType ();
   	    var ts = typeof (S).MakeByRefType ();
   
   	    Console.WriteLine ($"{tb.FullName} is assignable from {td.FullName} ? {tb.IsAssignableFrom (td) }");
   	    Console.WriteLine ($"{td.FullName} is assignable from {tb.FullName} ? {td.IsAssignableFrom (tb) }");
   
   	    Console.WriteLine ($"{ti.FullName} is assignable from {td.FullName} ? {ti.IsAssignableFrom (td) }");
   	    Console.WriteLine ($"{td.FullName} is assignable from {ti.FullName} ? {td.IsAssignableFrom (ti) }");
   
   	    Console.WriteLine ($"{ti.FullName} is assignable from {ts.FullName} ? {ti.IsAssignableFrom (ts) }");
   	    Console.WriteLine ($"{ts.FullName} is assignable from {ti.FullName} ? {ts.IsAssignableFrom (ti) }");
   
           }
   }
   
   // Expected Output:
   // B& is assignable from D& ? True
   // D& is assignable from B& ? False
   // I& is assignable from D& ? True
   // D& is assignable from I& ? False
   // I& is assignable from S& ? False
   // S& is assignable from I& ? False
   ```

* rename mono_class_signature_is_assignable

   No need for _checked suffix

* rename mono_byref_type_is_assignable_from

* [class-init] Move vtable setup to a separate file

   The code is getting quite long, move it to a separate file
  • Loading branch information
monojenkins authored Jun 26, 2020
1 parent f562eb8 commit c511814
Show file tree
Hide file tree
Showing 13 changed files with 2,504 additions and 1,896 deletions.
2 changes: 2 additions & 0 deletions mono/metadata/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,11 @@ common_sources = \
class-getters.h \
class-init.h \
class-init.c \
class-init-internals.h \
class-internals.h \
class-inlines.h \
class-private-definition.h \
class-setup-vtable.c \
class-accessors.c \
cominterop.c \
cominterop.h \
Expand Down
2 changes: 2 additions & 0 deletions mono/metadata/class-accessors.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ mono_class_get_flags (MonoClass *klass)
/* all arrays are marked serializable and sealed, bug #42779 */
return TYPE_ATTRIBUTE_CLASS | TYPE_ATTRIBUTE_SERIALIZABLE | TYPE_ATTRIBUTE_SEALED | TYPE_ATTRIBUTE_PUBLIC;
case MONO_CLASS_POINTER:
if (m_class_get_byval_arg (klass)->type == MONO_TYPE_FNPTR)
return TYPE_ATTRIBUTE_SEALED | TYPE_ATTRIBUTE_PUBLIC;
return TYPE_ATTRIBUTE_CLASS | (mono_class_get_flags (m_class_get_element_class (klass)) & TYPE_ATTRIBUTE_VISIBILITY_MASK);
}
g_assert_not_reached ();
Expand Down
26 changes: 26 additions & 0 deletions mono/metadata/class-init-internals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* \file Implementation details for class initialization
*/

#ifndef __MONO_METADATA_CLASS_INIT_INTERNALS_H__
#define __MONO_METADATA_CLASS_INIT_INTERNALS_H__

#include <mono/metadata/class.h>

void
mono_class_setup_interface_id_nolock (MonoClass *klass);

int
mono_class_setup_interface_offsets_internal (MonoClass *klass, int cur_slot, gboolean overwrite);

int
mono_class_setup_count_virtual_methods (MonoClass *klass);

gboolean
mono_class_setup_need_stelemref_method (MonoClass *klass);

gboolean
mono_class_setup_method_has_preserve_base_overrides_attribute (MonoMethod *method);


#endif /* __MONO_METADATA_CLASS_INIT_INTERNALS_H__ */
Loading

0 comments on commit c511814

Please sign in to comment.