Fix generic non-virtual method call in Ready-to-Run images
authorJohnChen0 <jochen@microsoft.com>
Thu, 26 May 2016 22:07:00 +0000 (15:07 -0700)
committerJohn Chen <jochen@microsoft.com>
Fri, 27 May 2016 05:28:27 +0000 (22:28 -0700)
Issue dotnet/coreclr#5201 revealed a bug in the Ready-to-Run implementation.
This bug can cause the runtime to dispatch a method call to the
wrong target when all the following conditions are met:
* A shared generic method in a Ready-to-Run module calls a method
  in a shared generic class.
* The target is a non-virtual instance method, but is called through
  callvirt instruction (as C# compiler normally does).
* The target is in a different module.
* The target method is defined in a base class, while the actual
  object instance is of a derived class.

This commit fixes this bug by changing a virtual call to a regular
call when the target is non-virtual.

Commit migrated from https://github.com/dotnet/coreclr/commit/4fd217d564b743f995426de7bd642166e0a84849

src/coreclr/src/vm/genericdict.cpp
src/coreclr/src/vm/jitinterface.cpp
src/coreclr/tests/src/readytorun/main.cs
src/coreclr/tests/src/readytorun/test.cs

index 87eef87..04e2550 100644 (file)
@@ -880,6 +880,14 @@ Dictionary::PopulateEntry(
                 result = (CORINFO_GENERIC_HANDLE)pMethod->GetMultiCallableAddrOfCode();
             }
             else
+            if (kind == DispatchStubAddrSlot)
+            {
+                _ASSERTE((methodFlags & ENCODE_METHOD_SIG_SlotInsteadOfToken) == 0);
+                PCODE *ppCode = (PCODE*)(void*)pMethod->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)));
+                *ppCode = pMethod->GetMultiCallableAddrOfCode();
+                result = (CORINFO_GENERIC_HANDLE)ppCode;
+            }
+            else
             {
                 _ASSERTE(kind == MethodDescSlot);
                 result = (CORINFO_GENERIC_HANDLE)pMethod;
index 5a120bd..3bdb245 100644 (file)
@@ -3468,7 +3468,7 @@ NoSpecialCase:
                 methodFlags |= ENCODE_METHOD_SIG_SlotInsteadOfToken;
             }
             else
-            if (entryKind == DispatchStubAddrSlot)
+            if (entryKind == DispatchStubAddrSlot && pTemplateMD->IsVtableMethod())
             {
                 // Encode the method for dispatch stub using slot to avoid touching the interface method MethodDesc at runtime
 
index 70157bc..9b947fd 100644 (file)
@@ -203,6 +203,19 @@ class Program
         }
     }
 
+    [MethodImplAttribute(MethodImplOptions.NoInlining)]
+    static void TestGenericNonVirtualMethod()
+    {
+        var c = new MyChildGeneric<string>();
+        Assert.AreEqual(CallGeneric(c), "MyGeneric.NonVirtualMethod");
+    }
+
+    [MethodImplAttribute(MethodImplOptions.NoInlining)]
+    static string CallGeneric<T>(MyGeneric<T, T> g)
+    {
+        return g.NonVirtualMethod();
+    }
+
     static void TestInstanceFields()
     {
         var t = new InstanceFieldTest2();
@@ -375,6 +388,7 @@ class Program
 
         TestGenericVirtualMethod();
         TestMovedGenericVirtualMethod();
+        TestGenericNonVirtualMethod();
 
         TestInstanceFields();
 
index 7e14398..39afbd2 100644 (file)
@@ -255,6 +255,11 @@ public class MyGeneric<T,U>
         return typeof(List<W>).ToString();
     }
 #endif
+
+    public string NonVirtualMethod()
+    {
+        return "MyGeneric.NonVirtualMethod";
+    }
 }
 
 public class MyChildGeneric<T> : MyGeneric<T,T>