Skip to content

Commit e45a935

Browse files
authored
Process static interface methods as virtual methods (#2926)
Static methods implementing static interface methods are not marked as virtual and were not checked for matching RUC and DAM annotations. This change adds all base methods to the _virtual_methods list to be checked for matching DAM and RUC annotations. This may cause extra/unnecessary warnings for mismatched annotations even a method is removed by the linker.
1 parent 6e8e139 commit e45a935

File tree

7 files changed

+433
-14
lines changed

7 files changed

+433
-14
lines changed

src/linker/Linker.Steps/MarkStep.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3166,6 +3166,7 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo
31663166
}
31673167

31683168
MarkMethodSpecialCustomAttributes (method);
3169+
31693170
if (method.IsVirtual)
31703171
_virtual_methods.Add ((method, ScopeStack.CurrentScope));
31713172

@@ -3345,8 +3346,15 @@ void MarkBaseMethods (MethodDefinition method)
33453346
return;
33463347

33473348
foreach (MethodDefinition base_method in base_methods) {
3348-
if (base_method.DeclaringType.IsInterface && !method.DeclaringType.IsInterface)
3349+
// We should add all interface base methods to _virtual_methods for virtual override annotation validation
3350+
// Interfaces from preserved scope will be missed if we don't add them here
3351+
// This will produce warnings for all interface methods and virtual methods regardless of whether the interface, interface implementation, or interface method is kept or not.
3352+
if (base_method.DeclaringType.IsInterface && !method.DeclaringType.IsInterface) {
3353+
// These are all virtual, no need to check IsVirtual before adding to list
3354+
_virtual_methods.Add ((base_method, ScopeStack.CurrentScope));
3355+
// _virtual_methods is a list and might have duplicates, but it's mostly just used for override validation, so it shouldn't matter
33493356
continue;
3357+
}
33503358

33513359
MarkMethod (base_method, new DependencyInfo (DependencyKind.BaseMethod, method), ScopeStack.CurrentScope.Origin);
33523360
MarkBaseMethods (base_method);

src/linker/Linker/Annotations.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ internal bool IsInRequiresUnreferencedCodeScope (MethodDefinition method)
608608
/// <summary>
609609
/// Determines if a member requires unreferenced code (and thus any usage of such method should be warned about).
610610
/// </summary>
611-
/// <remarks>Unlike <see cref="IsInRequiresUnreferencedCodeScope(MethodDefinition)"/> only static methods
611+
/// <remarks>Unlike <see cref="IsInRequiresUnreferencedCodeScope(MethodDefinition)"/> only static methods
612612
/// and .ctors are reported as requiring unreferenced code when the declaring type has RUC on it.</remarks>
613613
internal bool DoesMemberRequireUnreferencedCode (IMemberDefinition member, [NotNullWhen (returnValue: true)] out RequiresUnreferencedCodeAttribute? attribute)
614614
{
@@ -673,11 +673,27 @@ internal bool DoesFieldRequireUnreferencedCode (FieldDefinition field, [NotNullW
673673
return TryGetLinkerAttribute (field.DeclaringType, out attribute);
674674
}
675675

676+
/// <Summary>
677+
/// Adds a virtual method to the queue if it is annotated and must have matching annotations on its bases and overrides. It does not check if the method is marked before producing a warning about mismatched annotations.
678+
/// </summary>
676679
public void EnqueueVirtualMethod (MethodDefinition method)
677680
{
678681
if (!method.IsVirtual)
679682
return;
680683

684+
// Implementations of static interface methods are not virtual and won't reach here
685+
// We'll search through the implementations of static interface methods to find if any need to be enqueued
686+
if (method.IsStatic) {
687+
Debug.Assert (method.DeclaringType.IsInterface);
688+
var overrides = GetOverrides (method);
689+
if (overrides is not null) {
690+
foreach (var @override in overrides) {
691+
if (FlowAnnotations.RequiresVirtualMethodDataFlowAnalysis (@override.Override) || HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (@override.Override))
692+
VirtualMethodsWithAnnotationsToValidate.Add (@override.Override);
693+
}
694+
}
695+
}
696+
681697
if (FlowAnnotations.RequiresVirtualMethodDataFlowAnalysis (method) || HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (method))
682698
VirtualMethodsWithAnnotationsToValidate.Add (method);
683699
}

test/ILLink.RoslynAnalyzer.Tests/StaticInterfaceMethods.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ public sealed partial class StaticInterfaceMethodsTests : LinkerTestBase
1010
{
1111
protected override string TestSuiteName => "Inheritance.Interfaces.StaticInterfaceMethods";
1212

13+
[Fact]
14+
public Task BaseProvidesInterfaceMethod ()
15+
{
16+
return RunTest (allowMissingWarnings: false);
17+
}
18+
1319
[Fact]
1420
public Task StaticAbstractInterfaceMethods ()
1521
{
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Mono.Linker.Tests.Cases.DataFlow.Dependencies
12+
{
13+
public class Library
14+
{
15+
public interface IAnnotatedMethods
16+
{
17+
static abstract void GenericWithMethodsStatic<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] T> ();
18+
19+
static abstract void ParamWithMethodsStatic ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type t);
20+
21+
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
22+
static abstract Type ReturnWithMethodsStatic ();
23+
24+
void GenericWithMethods<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] T> ();
25+
26+
void ParamWithMethods ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type t);
27+
28+
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
29+
Type ReturnWithMethods ();
30+
}
31+
32+
public interface IUnannotatedMethods
33+
{
34+
static abstract void GenericStatic<T> ();
35+
36+
static abstract void ParamStatic (Type t);
37+
38+
static abstract Type ReturnStatic ();
39+
40+
void Generic<T> ();
41+
42+
void Param (Type t);
43+
44+
Type Return ();
45+
}
46+
47+
public abstract class AnnotatedMethods
48+
{
49+
public abstract void GenericWithMethods<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] T> ();
50+
51+
public abstract void ParamWithMethods ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type t);
52+
53+
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
54+
public abstract Type ReturnWithMethods ();
55+
}
56+
57+
public abstract class UnannotatedMethods
58+
{
59+
public abstract void Generic<T> ();
60+
61+
public abstract void Param (Type t);
62+
63+
public abstract Type Return ();
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)