Skip to content

Commit ae8160b

Browse files
jtschustersbomervitek-karas
authored
[release/7.0] Check for marking virtual method due to base only when state changes (#3094)
* Check for marking virtual method due to base only when state changes (#3073) Instead of checking every virtual method to see if it should be kept due to a base method every iteration of the MarkStep pipeline, check each method only when its relevant state has changed. Co-authored-by: Sven Boemer <sbomer@gmail.com> * Don't mark override of abstract base if the override's declaring type is not marked (#3098) * Don't mark an override every time the base is abstract, only if the declaring type is also marked Adds a condition to ShouldMarkOverrideForBase to exit early if the declaring type of the method is not marked. * Add test case for #3112 with pseudo-circular reference with ifaces * Link issue to TODO * Adds a test for recursive generics on interfaces This is a copy of the test added in #3156 Co-authored-by: Sven Boemer <sbomer@gmail.com> Co-authored-by: vitek-karas <10670590+vitek-karas@users.noreply.github.com>
1 parent 19fa656 commit ae8160b

File tree

15 files changed

+363
-151
lines changed

15 files changed

+363
-151
lines changed

src/linker/Linker.Steps/MarkStep.cs

Lines changed: 103 additions & 117 deletions
Large diffs are not rendered by default.

src/linker/Linker.Steps/SealerStep.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ void ProcessType (TypeDefinition type)
9696
//
9797
// cannot de-virtualize nor seal methods if something overrides them
9898
//
99-
if (IsAnyMarked (overrides))
99+
if (IsAnyOverrideMarked (overrides))
100100
continue;
101101

102102
SealMethod (method);
@@ -108,7 +108,7 @@ void ProcessType (TypeDefinition type)
108108

109109
var bases = Annotations.GetBaseMethods (method);
110110
// Devirtualize if a method is not override to existing marked methods
111-
if (!IsAnyMarked (bases))
111+
if (!IsAnyBaseMarked (bases))
112112
method.IsVirtual = method.IsFinal = method.IsNewSlot = false;
113113
}
114114
}
@@ -123,7 +123,7 @@ protected virtual void SealMethod (MethodDefinition method)
123123
method.IsFinal = true;
124124
}
125125

126-
bool IsAnyMarked (IEnumerable<OverrideInformation>? list)
126+
bool IsAnyOverrideMarked (IEnumerable<OverrideInformation>? list)
127127
{
128128
if (list == null)
129129
return false;
@@ -135,12 +135,13 @@ bool IsAnyMarked (IEnumerable<OverrideInformation>? list)
135135
return false;
136136
}
137137

138-
bool IsAnyMarked (List<MethodDefinition>? list)
138+
bool IsAnyBaseMarked (IEnumerable<OverrideInformation>? list)
139139
{
140140
if (list == null)
141141
return false;
142+
142143
foreach (var m in list) {
143-
if (Annotations.IsMarked (m))
144+
if (Annotations.IsMarked (m.Base))
144145
return true;
145146
}
146147
return false;

src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ protected override void Process ()
1313
{
1414
var annotations = Context.Annotations;
1515
foreach (var method in annotations.VirtualMethodsWithAnnotationsToValidate) {
16-
var baseMethods = annotations.GetBaseMethods (method);
17-
if (baseMethods != null) {
18-
foreach (var baseMethod in baseMethods) {
19-
annotations.FlowAnnotations.ValidateMethodAnnotationsAreSame (method, baseMethod);
20-
ValidateMethodRequiresUnreferencedCodeAreSame (method, baseMethod);
16+
var baseOverrideInformations = annotations.GetBaseMethods (method);
17+
if (baseOverrideInformations != null) {
18+
foreach (var baseOv in baseOverrideInformations) {
19+
annotations.FlowAnnotations.ValidateMethodAnnotationsAreSame (method, baseOv.Base);
20+
ValidateMethodRequiresUnreferencedCodeAreSame (method, baseOv.Base);
2121
}
2222
}
2323

src/linker/Linker/Annotations.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ public bool IsPublic (IMetadataTokenProvider provider)
436436
return public_api.Contains (provider);
437437
}
438438

439+
/// <summary>
440+
/// Returns a list of all known methods that override <paramref name="method"/>. The list may be incomplete if other overrides exist in assemblies that haven't been processed by TypeMapInfo yet
441+
/// </summary>
439442
public IEnumerable<OverrideInformation>? GetOverrides (MethodDefinition method)
440443
{
441444
return TypeMapInfo.GetOverrides (method);
@@ -446,7 +449,14 @@ public bool IsPublic (IMetadataTokenProvider provider)
446449
return TypeMapInfo.GetDefaultInterfaceImplementations (method);
447450
}
448451

449-
public List<MethodDefinition>? GetBaseMethods (MethodDefinition method)
452+
/// <summary>
453+
/// Returns all base methods that <paramref name="method"/> overrides.
454+
/// This includes methods on <paramref name="method"/>'s declaring type's base type (but not methods higher up in the type hierarchy),
455+
/// methods on an interface that <paramref name="method"/>'s delcaring type implements,
456+
/// and methods an interface implemented by a derived type of <paramref name="method"/>'s declaring type if the derived type uses <paramref name="method"/> as the implementing method.
457+
/// The list may be incomplete if there are derived types in assemblies that havent been processed yet that use <paramref name="method"/> to implement an interface.
458+
/// </summary>
459+
public List<OverrideInformation>? GetBaseMethods (MethodDefinition method)
450460
{
451461
return TypeMapInfo.GetBaseMethods (method);
452462
}

src/linker/Linker/TypeMapInfo.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class TypeMapInfo
3939
{
4040
readonly HashSet<AssemblyDefinition> assemblies = new HashSet<AssemblyDefinition> ();
4141
readonly LinkContext context;
42-
protected readonly Dictionary<MethodDefinition, List<MethodDefinition>> base_methods = new Dictionary<MethodDefinition, List<MethodDefinition>> ();
42+
protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> base_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
4343
protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> override_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
4444
protected readonly Dictionary<MethodDefinition, List<(TypeDefinition InstanceType, InterfaceImplementation ImplementationProvider)>> default_interface_implementations = new Dictionary<MethodDefinition, List<(TypeDefinition, InterfaceImplementation)>> ();
4545

@@ -57,17 +57,27 @@ void EnsureProcessed (AssemblyDefinition assembly)
5757
MapType (type);
5858
}
5959

60+
/// <summary>
61+
/// Returns a list of all known methods that override <paramref name="method"/>. The list may be incomplete if other overrides exist in assemblies that haven't been processed by TypeMapInfo yet
62+
/// </summary>
6063
public IEnumerable<OverrideInformation>? GetOverrides (MethodDefinition method)
6164
{
6265
EnsureProcessed (method.Module.Assembly);
6366
override_methods.TryGetValue (method, out List<OverrideInformation>? overrides);
6467
return overrides;
6568
}
6669

67-
public List<MethodDefinition>? GetBaseMethods (MethodDefinition method)
70+
/// <summary>
71+
/// Returns all base methods that <paramref name="method"/> overrides.
72+
/// This includes the closest overridden virtual method on <paramref name="method"/>'s base types
73+
/// methods on an interface that <paramref name="method"/>'s declaring type implements,
74+
/// and methods an interface implemented by a derived type of <paramref name="method"/>'s declaring type if the derived type uses <paramref name="method"/> as the implementing method.
75+
/// The list may be incomplete if there are derived types in assemblies that havent been processed yet that use <paramref name="method"/> to implement an interface.
76+
/// </summary>
77+
public List<OverrideInformation>? GetBaseMethods (MethodDefinition method)
6878
{
6979
EnsureProcessed (method.Module.Assembly);
70-
base_methods.TryGetValue (method, out List<MethodDefinition>? bases);
80+
base_methods.TryGetValue (method, out List<OverrideInformation>? bases);
7181
return bases;
7282
}
7383

@@ -77,14 +87,14 @@ void EnsureProcessed (AssemblyDefinition assembly)
7787
return ret;
7888
}
7989

80-
public void AddBaseMethod (MethodDefinition method, MethodDefinition @base)
90+
public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, InterfaceImplementation? matchingInterfaceImplementation)
8191
{
82-
if (!base_methods.TryGetValue (method, out List<MethodDefinition>? methods)) {
83-
methods = new List<MethodDefinition> ();
92+
if (!base_methods.TryGetValue (method, out List<OverrideInformation>? methods)) {
93+
methods = new List<OverrideInformation> ();
8494
base_methods[method] = methods;
8595
}
8696

87-
methods.Add (@base);
97+
methods.Add (new OverrideInformation (@base, method, context, matchingInterfaceImplementation));
8898
}
8999

90100
public void AddOverride (MethodDefinition @base, MethodDefinition @override, InterfaceImplementation? matchingInterfaceImplementation = null)
@@ -204,7 +214,7 @@ void MapOverrides (MethodDefinition method)
204214

205215
void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, InterfaceImplementation? matchingInterfaceImplementation = null)
206216
{
207-
AddBaseMethod (@override, @base);
217+
AddBaseMethod (@override, @base, matchingInterfaceImplementation);
208218
AddOverride (@base, @override, matchingInterfaceImplementation);
209219
}
210220

test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.VirtualMethodsTests.g.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public Task NeverInstantiatedTypeWithBaseInCopiedAssembly ()
2727
return RunTest (allowMissingWarnings: true);
2828
}
2929

30+
[Fact]
31+
public Task OverrideInUnmarkedClassIsRemoved ()
32+
{
33+
return RunTest (allowMissingWarnings: true);
34+
}
35+
3036
[Fact]
3137
public Task UnusedTypeWithOverrideOfVirtualMethodIsRemoved ()
3238
{

test/Mono.Linker.Tests.Cases/Attributes/TypeWithDynamicInterfaceCastableImplementationAttributeIsKept.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ namespace Mono.Linker.Tests.Cases.Attributes
1616
[KeptMemberInAssembly ("impl", "Mono.Linker.Tests.Cases.Attributes.Dependencies.IReferencedAssemblyImpl", "Foo()")]
1717
[KeptInterfaceOnTypeInAssembly ("impl", "Mono.Linker.Tests.Cases.Attributes.Dependencies.IReferencedAssemblyImpl",
1818
"interface", "Mono.Linker.Tests.Cases.Attributes.Dependencies.IReferencedAssembly")]
19+
[SetupLinkerTrimMode ("link")]
20+
[IgnoreDescriptors (false)]
1921
public class TypeWithDynamicInterfaceCastableImplementationAttributeIsKept
2022
{
2123
public static void Main ()
@@ -54,6 +56,7 @@ static IReferencedAssembly GetReferencedInterface (object obj)
5456
#if NETCOREAPP
5557
[Kept]
5658
[KeptMember (".ctor()")]
59+
[KeptInterface (typeof (IDynamicInterfaceCastable))]
5760
class Foo : IDynamicInterfaceCastable
5861
{
5962
[Kept]
@@ -74,6 +77,7 @@ public bool IsInterfaceImplemented (RuntimeTypeHandle interfaceType, bool throwI
7477

7578
[Kept]
7679
[KeptMember (".ctor()")]
80+
[KeptInterface (typeof (IDynamicInterfaceCastable))]
7781
class DynamicCastableImplementedInOtherAssembly : IDynamicInterfaceCastable
7882
{
7983
[Kept]

test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ static void TestInterfaceTypeGenericRequirements ()
318318
new InterfaceImplementationTypeWithInstantiationOverSelfOnBase ();
319319
new InterfaceImplementationTypeWithOpenGenericOnBase<TestType> ();
320320
new InterfaceImplementationTypeWithOpenGenericOnBaseWithRequirements<TestType> ();
321+
322+
RecursiveGenericWithInterfacesRequirement.Test ();
321323
}
322324

323325
interface IGenericInterfaceTypeWithRequirements<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T>
@@ -345,6 +347,23 @@ class InterfaceImplementationTypeWithOpenGenericOnBaseWithRequirements<[Dynamica
345347
{
346348
}
347349

350+
class RecursiveGenericWithInterfacesRequirement
351+
{
352+
interface IFace<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.Interfaces)] T>
353+
{
354+
}
355+
356+
class TestType : IFace<TestType>
357+
{
358+
}
359+
360+
public static void Test ()
361+
{
362+
var a = typeof (IFace<string>);
363+
var t = new TestType ();
364+
}
365+
}
366+
348367
static void TestTypeGenericRequirementsOnMembers ()
349368
{
350369
// Basically just root everything we need to test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Mono.Linker.Tests.Cases.Expectations.Assertions;
5+
using Mono.Linker.Tests.Cases.Expectations.Metadata;
6+
using Mono.Linker.Tests.Cases.Inheritance.Interfaces.BaseProvidesInterfaceEdgeCase.Dependencies;
7+
8+
namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.BaseProvidesInterfaceEdgeCase
9+
{
10+
/// <summary>
11+
/// Reproduces the issue found in https://github.com/dotnet/linker/issues/3112.
12+
/// <see cref="Derived1"/> derives from <see cref="Base"/> and uses <see cref="Base"/>'s method to implement <see cref="IFoo"/>,
13+
/// creating a psuedo-circular assembly reference (but not quite since <see cref="Base"/> doesn't implement IFoo itself).
14+
/// In the linker, IsMethodNeededByInstantiatedTypeDueToPreservedScope would iterate through <see cref="Base"/>'s method's base methods,
15+
/// and in the process would trigger the assembly of <see cref="IFoo"/> to be processed. Since that assembly also has <see cref="Derived2"/> that
16+
/// inherits from <see cref="Base"/> and implements <see cref="IBar"/> using <see cref="Base"/>'s methods, the linker adds
17+
/// <see cref="IBar"/>'s method as a base to <see cref="Base"/>'s method, which modifies the collection as it's being iterated, causing an exception.
18+
/// </summary>
19+
[SetupCompileBefore ("base.dll", new[] { "Dependencies/Base.cs" })] // Base Implements IFoo.Method (psuedo-reference to ifoo.dll)
20+
[SetupCompileBefore ("ifoo.dll", new[] { "Dependencies/IFoo.cs" }, references: new[] { "base.dll" })] // Derived2 references base.dll (circular reference)
21+
[SetupCompileBefore ("derived1.dll", new[] { "Dependencies/Derived1.cs" }, references: new[] { "ifoo.dll", "base.dll" })]
22+
public class BaseProvidesInterfaceMethodCircularReference
23+
{
24+
[Kept]
25+
public static void Main ()
26+
{
27+
_ = new Derived1 ();
28+
Foo ();
29+
}
30+
31+
[Kept]
32+
public static void Foo ()
33+
{
34+
((IFoo) null).Method ();
35+
object x = null;
36+
}
37+
}
38+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
6+
namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.BaseProvidesInterfaceEdgeCase.Dependencies
7+
{
8+
public class Base
9+
{
10+
public virtual void Method()
11+
{
12+
throw new NotImplementedException();
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.BaseProvidesInterfaceEdgeCase.Dependencies
5+
{
6+
public class Derived1 : Base, IFoo
7+
{
8+
}
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.BaseProvidesInterfaceEdgeCase.Dependencies
5+
{
6+
public interface IFoo
7+
{
8+
void Method();
9+
}
10+
public interface IBar
11+
{
12+
void Method();
13+
}
14+
public class Derived2 : Base, IBar
15+
{
16+
}
17+
}

test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ public static void Main ()
2121
t = typeof (UninstantiatedPublicClassWithPrivateInterface);
2222
t = typeof (ImplementsUsedStaticInterface.InterfaceMethodUnused);
2323

24-
ImplementsUnusedStaticInterface.Test (); ;
24+
ImplementsUnusedStaticInterface.Test ();
2525
GenericMethodThatCallsInternalStaticInterfaceMethod
2626
<ImplementsUsedStaticInterface.InterfaceMethodUsedThroughInterface> ();
2727
// Use all public interfaces - they're marked as public only to denote them as "used"
2828
typeof (IPublicInterface).RequiresPublicMethods ();
2929
typeof (IPublicStaticInterface).RequiresPublicMethods ();
30-
var ___ = new InstantiatedClassWithInterfaces ();
30+
_ = new InstantiatedClassWithInterfaces ();
31+
MarkIFormattable (null);
3132
}
3233

34+
[Kept]
35+
static void MarkIFormattable (IFormattable x)
36+
{ }
37+
3338
[Kept]
3439
internal static void GenericMethodThatCallsInternalStaticInterfaceMethod<T> () where T : IStaticInterfaceUsed
3540
{
@@ -113,8 +118,8 @@ public static void Test ()
113118
}
114119
}
115120

121+
// Interfaces are kept despite being uninstantiated because it is relevant to variant casting
116122
[Kept]
117-
[KeptInterface (typeof (IEnumerator))]
118123
[KeptInterface (typeof (IPublicInterface))]
119124
[KeptInterface (typeof (IPublicStaticInterface))]
120125
[KeptInterface (typeof (ICopyLibraryInterface))]
@@ -151,18 +156,12 @@ public static void InternalStaticInterfaceMethod () { }
151156
static void IInternalStaticInterface.ExplicitImplementationInternalStaticInterfaceMethod () { }
152157

153158

154-
[Kept]
155-
[ExpectBodyModified]
156159
bool IEnumerator.MoveNext () { throw new PlatformNotSupportedException (); }
157160

158-
[Kept]
159161
object IEnumerator.Current {
160-
[Kept]
161-
[ExpectBodyModified]
162162
get { throw new PlatformNotSupportedException (); }
163163
}
164164

165-
[Kept]
166165
void IEnumerator.Reset () { }
167166

168167
[Kept]
@@ -198,7 +197,6 @@ public string ToString (string format, IFormatProvider formatProvider)
198197
}
199198

200199
[Kept]
201-
[KeptInterface (typeof (IEnumerator))]
202200
[KeptInterface (typeof (IPublicInterface))]
203201
[KeptInterface (typeof (IPublicStaticInterface))]
204202
[KeptInterface (typeof (ICopyLibraryInterface))]
@@ -235,13 +233,10 @@ public static void InternalStaticInterfaceMethod () { }
235233

236234
static void IInternalStaticInterface.ExplicitImplementationInternalStaticInterfaceMethod () { }
237235

238-
[Kept]
239236
bool IEnumerator.MoveNext () { throw new PlatformNotSupportedException (); }
240237

241-
[Kept]
242-
object IEnumerator.Current { [Kept] get { throw new PlatformNotSupportedException (); } }
238+
object IEnumerator.Current { get { throw new PlatformNotSupportedException (); } }
243239

244-
[Kept]
245240
void IEnumerator.Reset () { }
246241

247242
[Kept]

test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/UnusedInterfacesInPreservedScope.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@ class MyType : IStaticInterfaceWithDefaultImpls
3737
public int InstanceMethod () => 0;
3838
}
3939

40+
// Keep MyType without marking it relevant to variant casting
41+
[Kept]
42+
static void KeepMyType (MyType x)
43+
{ }
44+
4045
[Kept]
4146
static void Test ()
4247
{
43-
var x = typeof (MyType); // The only use of MyType
48+
KeepMyType (null);
4449
}
4550
}
4651
}

0 commit comments

Comments
 (0)