Skip to content

Commit 0ea0e32

Browse files
authored
[mono] Skip exact interface matches when an interface has variance (#103757)
* When searching for a variant interface, use a special 'variant search table' that contains only variant interfaces in the correct search order, and perform the search by doing two passes per class * Add missing interfaces to set of array special interfaces in mono * Don't dereference random memory if get_virtual_method fails in interp * Add regression test * Update reason why variance test is disabled
1 parent e56ebd1 commit 0ea0e32

File tree

8 files changed

+207
-16
lines changed

8 files changed

+207
-16
lines changed

src/mono/mono/metadata/class-init.c

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3286,7 +3286,14 @@ mono_class_setup_interface_id_nolock (MonoClass *klass)
32863286
* a != b ==> true
32873287
*/
32883288
const char *name = m_class_get_name (klass);
3289-
if (!strcmp (name, "IList`1") || !strcmp (name, "ICollection`1") || !strcmp (name, "IEnumerable`1") || !strcmp (name, "IEnumerator`1"))
3289+
if (
3290+
!strcmp (name, "IList`1") ||
3291+
!strcmp (name, "IReadOnlyList`1") ||
3292+
!strcmp (name, "ICollection`1") ||
3293+
!strcmp (name, "IReadOnlyCollection`1") ||
3294+
!strcmp (name, "IEnumerable`1") ||
3295+
!strcmp (name, "IEnumerator`1")
3296+
)
32903297
klass->is_array_special_interface = 1;
32913298
}
32923299
}
@@ -4189,6 +4196,89 @@ mono_class_set_runtime_vtable (MonoClass *klass, MonoVTable *vtable)
41894196
klass->runtime_vtable = vtable;
41904197
}
41914198

4199+
static int
4200+
index_of_class (MonoClass *needle, MonoClass **haystack, int haystack_size) {
4201+
for (int i = 0; i < haystack_size; i++)
4202+
if (haystack[i] == needle)
4203+
return i;
4204+
4205+
return -1;
4206+
}
4207+
4208+
static void
4209+
build_variance_search_table_inner (MonoClass *klass, MonoClass **buf, int buf_size, int *buf_count) {
4210+
if (!m_class_is_interfaces_inited (klass)) {
4211+
ERROR_DECL (error);
4212+
mono_class_setup_interfaces (klass, error);
4213+
return_if_nok (error);
4214+
}
4215+
guint c = m_class_get_interface_count (klass);
4216+
if (c) {
4217+
MonoClass **ifaces = m_class_get_interfaces (klass);
4218+
for (guint i = 0; i < c; i++) {
4219+
MonoClass *iface = ifaces [i];
4220+
// Avoid adding duplicates or recursing into them.
4221+
if (index_of_class (iface, buf, *buf_count) >= 0)
4222+
continue;
4223+
4224+
if (mono_class_has_variant_generic_params (iface)) {
4225+
g_assert (*buf_count < buf_size);
4226+
buf[*buf_count] = iface;
4227+
(*buf_count) += 1;
4228+
}
4229+
4230+
build_variance_search_table_inner (iface, buf, buf_size, buf_count);
4231+
}
4232+
}
4233+
}
4234+
4235+
// Only call this with the loader lock held
4236+
static void
4237+
build_variance_search_table (MonoClass *klass) {
4238+
// FIXME: Is there a way to deterministically compute the right capacity?
4239+
int buf_size = m_class_get_interface_offsets_count (klass), buf_count = 0;
4240+
MonoClass **buf = g_alloca (buf_size * sizeof(MonoClass *));
4241+
MonoClass **result = NULL;
4242+
memset (buf, 0, buf_size * sizeof(MonoClass *));
4243+
build_variance_search_table_inner (klass, buf, buf_size, &buf_count);
4244+
4245+
if (buf_count) {
4246+
guint bytes = buf_count * sizeof(MonoClass *);
4247+
result = mono_mem_manager_alloc (m_class_get_mem_manager (klass), bytes);
4248+
memcpy (result, buf, bytes);
4249+
}
4250+
klass->variant_search_table_length = buf_count;
4251+
klass->variant_search_table = result;
4252+
// Ensure we do not set the inited flag until we've stored the result pointer
4253+
mono_memory_barrier ();
4254+
klass->variant_search_table_inited = TRUE;
4255+
}
4256+
4257+
void
4258+
mono_class_get_variance_search_table (MonoClass *klass, MonoClass ***table, int *table_size) {
4259+
g_assert (klass);
4260+
g_assert (table);
4261+
g_assert (table_size);
4262+
4263+
// We will never do a variance search to locate a given interface on an interface, only on
4264+
// a fully-defined type or generic instance
4265+
if (m_class_is_interface (klass)) {
4266+
*table = NULL;
4267+
*table_size = 0;
4268+
return;
4269+
}
4270+
4271+
if (!klass->variant_search_table_inited) {
4272+
mono_loader_lock ();
4273+
if (!klass->variant_search_table_inited)
4274+
build_variance_search_table (klass);
4275+
mono_loader_unlock ();
4276+
}
4277+
4278+
*table = klass->variant_search_table;
4279+
*table_size = klass->variant_search_table_length;
4280+
}
4281+
41924282
/**
41934283
* mono_classes_init:
41944284
*

src/mono/mono/metadata/class-internals.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,9 @@ mono_class_has_default_constructor (MonoClass *klass, gboolean public_only);
14651465
gboolean
14661466
mono_method_has_unmanaged_callers_only_attribute (MonoMethod *method);
14671467

1468+
void
1469+
mono_class_get_variance_search_table (MonoClass *klass, MonoClass ***table, int *table_size);
1470+
14681471
// There are many ways to do on-demand initialization.
14691472
// Some allow multiple concurrent initializations. Some do not.
14701473
// Some allow multiple concurrent writes to the global. Some do not.

src/mono/mono/metadata/class-private-definition.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ struct _MonoClass {
8080
guint has_deferred_failure : 1;
8181
/* next byte*/
8282
guint is_exception_class : 1; /* is System.Exception or derived from it */
83+
guint variant_search_table_inited : 1;
8384

8485
MonoClass *parent;
8586
MonoClass *nested_in;
@@ -127,6 +128,9 @@ struct _MonoClass {
127128

128129
/* Infrequently used items. See class-accessors.c: InfrequentDataKind for what goes into here. */
129130
MonoPropertyBag infrequent_data;
131+
132+
MonoClass **variant_search_table;
133+
int variant_search_table_length;
130134
};
131135

132136
struct _MonoClassDef {

src/mono/mono/metadata/class.c

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,21 +1946,25 @@ mono_class_interface_offset (MonoClass *klass, MonoClass *itf)
19461946
int
19471947
mono_class_interface_offset_with_variance (MonoClass *klass, MonoClass *itf, gboolean *non_exact_match)
19481948
{
1949-
int i = mono_class_interface_offset (klass, itf);
1949+
gboolean has_variance = mono_class_has_variant_generic_params (itf);
1950+
int exact_match = mono_class_interface_offset (klass, itf), i = -1;
19501951
*non_exact_match = FALSE;
1951-
if (i >= 0)
1952-
return i;
1952+
1953+
if (exact_match >= 0) {
1954+
if (!has_variance)
1955+
return exact_match;
1956+
}
19531957

19541958
int klass_interface_offsets_count = m_class_get_interface_offsets_count (klass);
19551959

1956-
if (m_class_is_array_special_interface (itf) && m_class_get_rank (klass) < 2) {
1960+
if (m_class_is_array_special_interface (itf) && m_class_get_rank (klass) < 2) {
19571961
MonoClass *gtd = mono_class_get_generic_type_definition (itf);
19581962
int found = -1;
19591963

19601964
for (i = 0; i < klass_interface_offsets_count; i++) {
19611965
if (mono_class_is_variant_compatible (itf, m_class_get_interfaces_packed (klass) [i], FALSE)) {
19621966
found = i;
1963-
*non_exact_match = TRUE;
1967+
*non_exact_match = (i != exact_match);
19641968
break;
19651969
}
19661970

@@ -1971,7 +1975,7 @@ mono_class_interface_offset_with_variance (MonoClass *klass, MonoClass *itf, gbo
19711975
for (i = 0; i < klass_interface_offsets_count; i++) {
19721976
if (mono_class_get_generic_type_definition (m_class_get_interfaces_packed (klass) [i]) == gtd) {
19731977
found = i;
1974-
*non_exact_match = TRUE;
1978+
*non_exact_match = (i != exact_match);
19751979
break;
19761980
}
19771981
}
@@ -1980,16 +1984,45 @@ mono_class_interface_offset_with_variance (MonoClass *klass, MonoClass *itf, gbo
19801984
return -1;
19811985

19821986
return m_class_get_interface_offsets_packed (klass) [found];
1983-
}
1987+
} else if (has_variance) {
1988+
MonoClass **vst;
1989+
int vst_count;
1990+
MonoClass *current = klass;
1991+
1992+
// Perform two passes per class, then check the base class
1993+
while (current) {
1994+
mono_class_get_variance_search_table (current, &vst, &vst_count);
1995+
1996+
// Exact match pass: Is there an exact match at this level of the type hierarchy?
1997+
// If so, we can use the interface_offset we computed earlier, since we're walking from most derived to least.
1998+
for (i = 0; i < vst_count; i++) {
1999+
if (itf != vst [i])
2000+
continue;
2001+
2002+
*non_exact_match = FALSE;
2003+
return exact_match;
2004+
}
19842005

1985-
if (!mono_class_has_variant_generic_params (itf))
1986-
return -1;
2006+
// Inexact match (variance) pass:
2007+
// Is any interface at this level of the type hierarchy variantly compatible with the desired interface?
2008+
// If so, select the first compatible one we find.
2009+
for (i = 0; i < vst_count; i++) {
2010+
if (!mono_class_is_variant_compatible (itf, vst [i], FALSE))
2011+
continue;
19872012

1988-
for (i = 0; i < klass_interface_offsets_count; i++) {
1989-
if (mono_class_is_variant_compatible (itf, m_class_get_interfaces_packed (klass) [i], FALSE)) {
1990-
*non_exact_match = TRUE;
1991-
return m_class_get_interface_offsets_packed (klass) [i];
2013+
int inexact_match = mono_class_interface_offset (klass, vst[i]);
2014+
g_assert (inexact_match != exact_match);
2015+
*non_exact_match = TRUE;
2016+
return inexact_match;
2017+
}
2018+
2019+
// Now check base class if present
2020+
current = m_class_get_parent (current);
19922021
}
2022+
2023+
// If the variance search failed to find a match, return the exact match search result (probably -1).
2024+
*non_exact_match = (exact_match < 0);
2025+
return exact_match;
19932026
}
19942027

19952028
return -1;

src/mono/mono/mini/interp/interp.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,9 @@ get_virtual_method (InterpMethod *imethod, MonoVTable *vtable)
619619
g_assert (vtable->klass != m->klass);
620620
/* TODO: interface offset lookup is slow, go through IMT instead */
621621
gboolean non_exact_match;
622-
slot += mono_class_interface_offset_with_variance (vtable->klass, m->klass, &non_exact_match);
622+
int ioffset = mono_class_interface_offset_with_variance (vtable->klass, m->klass, &non_exact_match);
623+
g_assert (ioffset >= 0);
624+
slot += ioffset;
623625
}
624626

625627
MonoMethod *virtual_method = m_class_get_vtable (vtable->klass) [slot];
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
/*
5+
https://github.com/dotnet/runtime/issues/103365
6+
When using an interface with a generic out type, an explicit implementation, and a derived class, the base classes implementation is called instead of the derived class when running on Mono; CoreCLR has the expected behavior.
7+
*/
8+
9+
using System;
10+
using System.Collections;
11+
using System.Collections.Generic;
12+
using System.Runtime.CompilerServices;
13+
using Xunit;
14+
15+
public interface IBaseInterface<out T>
16+
{
17+
string explicitDeclaration();
18+
}
19+
20+
public class BasicBaseClass : IBaseInterface<BasicBaseClass>
21+
{
22+
string className = "BasicBaseClass";
23+
string IBaseInterface<BasicBaseClass>.explicitDeclaration()
24+
{
25+
return className;
26+
}
27+
}
28+
29+
public class BasicDerivedClass : BasicBaseClass, IBaseInterface<BasicDerivedClass>
30+
{
31+
string className = "BasicDerivedClass";
32+
33+
string IBaseInterface<BasicDerivedClass>.explicitDeclaration()
34+
{
35+
return className;
36+
}
37+
}
38+
39+
public static class Test_Issue103365
40+
{
41+
[Fact]
42+
public static void Main ()
43+
{
44+
var instances = new IBaseInterface<BasicBaseClass>[2];
45+
instances[0] = new BasicBaseClass();
46+
instances[1] = new BasicDerivedClass();
47+
Assert.Equal("BasicBaseClass", instances[0].explicitDeclaration());
48+
Assert.Equal("BasicDerivedClass", instances[1].explicitDeclaration());
49+
}
50+
}
51+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<ItemGroup>
3+
<Compile Include="103365.cs" />
4+
</ItemGroup>
5+
<ItemGroup>
6+
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
7+
</ItemGroup>
8+
</Project>

src/tests/issues.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1756,7 +1756,7 @@
17561756
<Issue>needs triage</Issue>
17571757
</ExcludeList>
17581758
<ExcludeList Include="$(XunitTestBinBase)/Regressions/coreclr/16355/variance/**">
1759-
<Issue>needs triage</Issue>
1759+
<Issue>ldtoken in default interface methods does not appear to work in minijit or interp</Issue>
17601760
</ExcludeList>
17611761
<ExcludeList Include="$(XunitTestBinBase)/Regressions/coreclr/20616/UnicodeBug/**">
17621762
<Issue>needs triage</Issue>

0 commit comments

Comments
 (0)