[mono][interp] Fix GetType called on ptr constrained to Nullable` (#61020)
authorVlad Brezae <brezaevlad@gmail.com>
Thu, 4 Nov 2021 21:24:26 +0000 (23:24 +0200)
committerGitHub <noreply@github.com>
Thu, 4 Nov 2021 21:24:26 +0000 (16:24 -0500)
* [interp] Fix GetType called on ptr constrained to Nullable`

We were statically optimizing this call to return the actual constrained class type, which is incorrect for nullables, because boxing of a nullable (as part of the constrained call) actually creates an object with the type of the nullable's value (or null if there is no value).

* Add test for GetType call on ptr constrained to nullable

src/mono/mono/mini/interp/transform.c
src/tests/JIT/Directed/nullabletypes/gettype.cs [new file with mode: 0644]
src/tests/JIT/Directed/nullabletypes/gettype_d.csproj [new file with mode: 0644]
src/tests/JIT/Directed/nullabletypes/gettype_do.csproj [new file with mode: 0644]
src/tests/JIT/Directed/nullabletypes/gettype_r.csproj [new file with mode: 0644]
src/tests/JIT/Directed/nullabletypes/gettype_ro.csproj [new file with mode: 0644]

index 1e9dcb3..511f89d 100644 (file)
@@ -2440,7 +2440,7 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
                if (!strcmp (tm, "InternalGetHashCode")) {
                        *op = MINT_INTRINS_GET_HASHCODE;
                } else if (!strcmp (tm, "GetType")) {
-                       if (constrained_class && m_class_is_valuetype (constrained_class)) {
+                       if (constrained_class && m_class_is_valuetype (constrained_class) && !mono_class_is_nullable (constrained_class)) {
                                // If constrained_class is valuetype we already know its type.
                                // Resolve GetType to a constant so we can fold type comparisons
                                ERROR_DECL(error);
@@ -2457,8 +2457,16 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
                                return TRUE;
                        } else {
                                if (constrained_class) {
-                                       // deref the managed pointer to get the object
-                                       interp_add_ins (td, MINT_LDIND_I);
+                                       if (mono_class_is_nullable (constrained_class)) {
+                                               // We can't determine the behavior here statically because we don't know if the
+                                               // nullable vt has a value or not. If it has a value, the result type is
+                                               // m_class_get_cast_class (constrained_class), otherwise GetType should throw NRE.
+                                               interp_add_ins (td, MINT_BOX_NULLABLE_PTR);
+                                               td->last_ins->data [0] = get_data_item_index (td, constrained_class);
+                                       } else {
+                                               // deref the managed pointer to get the object
+                                               interp_add_ins (td, MINT_LDIND_I);
+                                       }
                                        td->sp--;
                                        interp_ins_set_sreg (td->last_ins, td->sp [0].local);
                                        push_simple_type (td, STACK_TYPE_O);
diff --git a/src/tests/JIT/Directed/nullabletypes/gettype.cs b/src/tests/JIT/Directed/nullabletypes/gettype.cs
new file mode 100644 (file)
index 0000000..15bbef7
--- /dev/null
@@ -0,0 +1,48 @@
+// 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.Linq;
+using System.Collections.Generic;
+
+class C<T>
+{
+    public IEnumerable<T> Data { get; set; }
+
+    public C() { }
+
+    public bool Check()
+    {
+       return Data.ElementAt(0).GetType() == typeof(bool);
+    }
+}
+
+public class P
+{
+    public static int Main()
+    {
+        C<bool?> c = new();
+
+        // Try a nullable with value
+        c.Data = new List<bool?> { true };
+        if(!c.Check())
+            return 666;
+
+        // Try a nullable without value. Should throw NRE
+        c.Data = new List<bool?> { new Nullable<bool>() };
+
+        bool thrown = false;
+        try
+        {
+            c.Check();
+        }
+        catch(NullReferenceException)
+        {
+            thrown = true;
+        }
+        if(!thrown)
+            return 667;
+        return 100;
+    }
+}
+
diff --git a/src/tests/JIT/Directed/nullabletypes/gettype_d.csproj b/src/tests/JIT/Directed/nullabletypes/gettype_d.csproj
new file mode 100644 (file)
index 0000000..749b1f2
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>Full</DebugType>
+    <Optimize>False</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="gettype.cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/Directed/nullabletypes/gettype_do.csproj b/src/tests/JIT/Directed/nullabletypes/gettype_do.csproj
new file mode 100644 (file)
index 0000000..3474481
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>Full</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="gettype.cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/Directed/nullabletypes/gettype_r.csproj b/src/tests/JIT/Directed/nullabletypes/gettype_r.csproj
new file mode 100644 (file)
index 0000000..fea75d0
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>False</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="gettype.cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/Directed/nullabletypes/gettype_ro.csproj b/src/tests/JIT/Directed/nullabletypes/gettype_ro.csproj
new file mode 100644 (file)
index 0000000..f51b429
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="gettype.cs" />
+  </ItemGroup>
+</Project>