Fix sealing of types that were not concretely instantiated (#73218)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Tue, 2 Aug 2022 23:17:42 +0000 (08:17 +0900)
committerGitHub <noreply@github.com>
Tue, 2 Aug 2022 23:17:42 +0000 (08:17 +0900)
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs [new file with mode: 0644]
src/tests/nativeaot/SmokeTests/UnitTests/Main.cs
src/tests/nativeaot/SmokeTests/UnitTests/UnitTests.csproj

index 675ba55..c2caeb2 100644 (file)
@@ -372,10 +372,15 @@ namespace ILCompiler
             {
                 foreach (var node in markedNodes)
                 {
-                    if (node is ConstructedEETypeNode eetypeNode)
+                    TypeDesc type = node switch
                     {
-                        TypeDesc type = eetypeNode.Type;
+                        ConstructedEETypeNode eetypeNode => eetypeNode.Type,
+                        CanonicalEETypeNode canoneetypeNode => canoneetypeNode.Type,
+                        _ => null,
+                    };
 
+                    if (type != null)
+                    {
                         if (!type.IsInterface)
                         {
                             //
@@ -391,7 +396,8 @@ namespace ILCompiler
                             //    didn't scan the virtual methods on them.
                             //
 
-                            _constructedTypes.Add(type);
+                            if (!type.IsCanonicalSubtype(CanonicalFormKind.Any))
+                                _constructedTypes.Add(type);
 
                             TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
 
diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs
new file mode 100644 (file)
index 0000000..73e3ab4
--- /dev/null
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+class Devirtualization
+{
+    internal static int Run()
+    {
+        RegressionBug73076.Run();
+        DevirtualizationCornerCaseTests.Run();
+
+        return 100;
+    }
+
+    class RegressionBug73076
+    {
+        interface IFactory
+        {
+            BaseFtnn<T> Make<T>();
+        }
+
+        class Factory : IFactory
+        {
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            public BaseFtnn<T> Make<T>() => new DerivedFtnn<T>();
+        }
+
+        class BaseFtnn<T>
+        {
+            public virtual string GetId() => "Base";
+        }
+
+        class DerivedFtnn<T> : BaseFtnn<T>
+        {
+            public override string GetId() => "Derived";
+        }
+
+        public static void Run()
+        {
+            IFactory factory = new Factory();
+
+            // This is a generic virtual method call so we'll only ever see BaseFtnn and DerivedFtnn instantiated
+            // over __Canon at compile time.
+            var made = factory.Make<object>();
+            if (made.GetId() != "Derived")
+                throw new Exception();
+        }
+    }
+
+    class DevirtualizationCornerCaseTests
+    {
+        interface IIntf1
+        {
+            int GetValue();
+        }
+
+        class Intf1Impl : IIntf1
+        {
+            public virtual int GetValue() => 123;
+        }
+
+        [DynamicInterfaceCastableImplementation]
+        interface IIntf1Impl : IIntf1
+        {
+            int IIntf1.GetValue() => 456;
+        }
+
+        class Intf1CastableImpl : IDynamicInterfaceCastable
+        {
+            public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) => typeof(IIntf1Impl).TypeHandle;
+            public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) => true;
+        }
+
+        interface IIntf2
+        {
+            int GetValue();
+        }
+
+        class Intf2Impl1 : IIntf2
+        {
+            public virtual int GetValue() => 123;
+        }
+
+        class Intf2Impl2<T> : IIntf2
+        {
+            public virtual int GetValue() => 456;
+        }
+
+        static void AssertEqual<T>(T expected, T actual)
+        {
+            if (!object.Equals(expected, actual))
+                throw new Exception();
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static void TestIntf1(IIntf1 o, int expected) => AssertEqual(expected, o.GetValue());
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        static void TestIntf2(IIntf2 o, int expected) => AssertEqual(expected, o.GetValue());
+
+        public static void Run()
+        {
+            TestIntf1(new Intf1Impl(), 123);
+            TestIntf1((IIntf1)new Intf1CastableImpl(), 456);
+
+            TestIntf2(new Intf2Impl1(), 123);
+            TestIntf2((IIntf2)Activator.CreateInstance(typeof(Intf2Impl2<>).MakeGenericType(typeof(object))), 456);
+        }
+    }
+}
index 1fd7841..e71884a 100644 (file)
@@ -9,6 +9,7 @@ success &= RunTest(Delegates.Run);
 success &= RunTest(Generics.Run);
 success &= RunTest(Interfaces.Run);
 success &= RunTest(Threading.Run);
+success &= RunTest(Devirtualization.Run);
 
 return success ? 100 : 1;
 
index 4e4e34b..6eac9a8 100644 (file)
@@ -13,6 +13,7 @@
   <ItemGroup>
     <Compile Include="BasicThreading.cs" />
     <Compile Include="Delegates.cs" />
+    <Compile Include="Devirtualization.cs" />
     <Compile Include="Generics.cs" />
     <Compile Include="Interfaces.cs" />
     <Compile Include="Threading.cs" />