diff options
author | Jackson Schuster <36744439+jtschuster@users.noreply.github.com> | 2022-03-12 01:26:00 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-12 01:26:00 +0300 |
commit | ac6cfb356d9ec4a279511aa0bd667fd1143e9eb6 (patch) | |
tree | c5a2674d5963ab5c35b49c164e9ac3f5cfc56924 | |
parent | 98c81d59e153a5bf4329470b9d0fbf6d58fdd6e8 (diff) |
Warn on DAM mismatch between overrides (#2656)
* Warn on DAM mismatch between overrides
8 files changed, 208 insertions, 46 deletions
diff --git a/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index fa7a8ca8a..bac91626c 100644 --- a/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -37,8 +37,12 @@ namespace ILLink.RoslynAnalyzer diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedTypeInRuntimeHelpersRunClassConstructor)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnGenericParameterBetweenOverrides)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnImplicitThisBetweenOverrides)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersConflictsBetweenPropertyAndAccessor)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.PropertyAccessorParameterInLinqExpressionsCannotBeStaticallyDetermined)); - return diagDescriptorsArrayBuilder.ToImmutable (); void AddRange (DiagnosticId first, DiagnosticId last) @@ -103,8 +107,13 @@ namespace ILLink.RoslynAnalyzer }, SyntaxKind.GenericName); context.RegisterSymbolAction (context => { VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol); + VerifyDamOnPropertyAndAccessorMatch (context, (IMethodSymbol) context.Symbol); + VerifyDamOnDerivedAndBaseMethodsMatch (context, (IMethodSymbol) context.Symbol); }, SymbolKind.Method); context.RegisterSymbolAction (context => { + VerifyDamOnInterfaceAndImplementationMethodsMatch (context, (INamedTypeSymbol) context.Symbol); + }, SymbolKind.NamedType); + context.RegisterSymbolAction (context => { VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol); }, SymbolKind.Property); context.RegisterSymbolAction (context => { @@ -209,5 +218,75 @@ namespace ILLink.RoslynAnalyzer context.ReportDiagnostic (Diagnostic.Create (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings), member.Locations[0], member.GetDisplayName ())); } } + + static void VerifyDamOnDerivedAndBaseMethodsMatch (SymbolAnalysisContext context, IMethodSymbol methodSymbol) + { + if (methodSymbol.TryGetOverriddenMember (out var overriddenSymbol) && overriddenSymbol is IMethodSymbol overriddenMethod + && context.Symbol is IMethodSymbol method) { + VerifyDamOnMethodsMatch (context, method, overriddenMethod); + } + } + + static void VerifyDamOnMethodsMatch (SymbolAnalysisContext context, IMethodSymbol method, IMethodSymbol overriddenMethod) + { + if (FlowAnnotations.GetMethodReturnValueAnnotation (method) != FlowAnnotations.GetMethodReturnValueAnnotation (overriddenMethod)) + context.ReportDiagnostic (Diagnostic.Create ( + DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides), + method.Locations[0], method.GetDisplayName (), overriddenMethod.GetDisplayName ())); + + for (int i = 0; i < method.Parameters.Length; i++) { + if (FlowAnnotations.GetMethodParameterAnnotation (method.Parameters[i]) != FlowAnnotations.GetMethodParameterAnnotation (overriddenMethod.Parameters[i])) + context.ReportDiagnostic (Diagnostic.Create ( + DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides), + method.Parameters[i].Locations[0], + method.Parameters[i].GetDisplayName (), method.GetDisplayName (), overriddenMethod.Parameters[i].GetDisplayName (), overriddenMethod.GetDisplayName ())); + } + + for (int i = 0; i < method.TypeParameters.Length; i++) { + if (method.TypeParameters[i].GetDynamicallyAccessedMemberTypes () != overriddenMethod.TypeParameters[i].GetDynamicallyAccessedMemberTypes ()) + context.ReportDiagnostic (Diagnostic.Create ( + DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnGenericParameterBetweenOverrides), + method.TypeParameters[i].Locations[0], + method.TypeParameters[i].GetDisplayName (), method.GetDisplayName (), + overriddenMethod.TypeParameters[i].GetDisplayName (), overriddenMethod.GetDisplayName ())); + } + + if (!method.IsStatic && method.GetDynamicallyAccessedMemberTypes () != overriddenMethod.GetDynamicallyAccessedMemberTypes ()) + context.ReportDiagnostic (Diagnostic.Create ( + DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnImplicitThisBetweenOverrides), + method.Locations[0], + method.GetDisplayName (), overriddenMethod.GetDisplayName ())); + } + + static void VerifyDamOnInterfaceAndImplementationMethodsMatch (SymbolAnalysisContext context, INamedTypeSymbol type) + { + foreach (var (interfaceMember, implementationMember) in type.GetMemberInterfaceImplementationPairs ()) { + if (implementationMember is IMethodSymbol implementationMethod + && interfaceMember is IMethodSymbol interfaceMethod) + VerifyDamOnMethodsMatch (context, implementationMethod, interfaceMethod); + } + } + + static void VerifyDamOnPropertyAndAccessorMatch (SymbolAnalysisContext context, IMethodSymbol methodSymbol) + { + if ((methodSymbol.MethodKind != MethodKind.PropertyGet && methodSymbol.MethodKind != MethodKind.PropertySet) + || (methodSymbol.AssociatedSymbol?.GetDynamicallyAccessedMemberTypes () == DynamicallyAccessedMemberTypes.None)) + return; + + // None on the return type of 'get' matches unannotated + if (methodSymbol.MethodKind == MethodKind.PropertyGet + && methodSymbol.GetDynamicallyAccessedMemberTypesOnReturnType () != DynamicallyAccessedMemberTypes.None + // None on parameter of 'set' matches unannotated + || methodSymbol.MethodKind == MethodKind.PropertySet + && methodSymbol.Parameters[0].GetDynamicallyAccessedMemberTypes () != DynamicallyAccessedMemberTypes.None) { + context.ReportDiagnostic (Diagnostic.Create ( + DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersConflictsBetweenPropertyAndAccessor), + methodSymbol.AssociatedSymbol!.Locations[0], + methodSymbol.AssociatedSymbol!.GetDisplayName (), + methodSymbol.GetDisplayName () + )); + return; + } + } } } diff --git a/src/ILLink.RoslynAnalyzer/INamedTypeSymbolExtensions.cs b/src/ILLink.RoslynAnalyzer/INamedTypeSymbolExtensions.cs index b15dfebb8..577e47c2d 100644 --- a/src/ILLink.RoslynAnalyzer/INamedTypeSymbolExtensions.cs +++ b/src/ILLink.RoslynAnalyzer/INamedTypeSymbolExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using Microsoft.CodeAnalysis; namespace ILLink.RoslynAnalyzer @@ -28,5 +29,30 @@ namespace ILLink.RoslynAnalyzer return true; } + + internal static IEnumerable<(ISymbol InterfaceMember, ISymbol ImplementationMember)> GetMemberInterfaceImplementationPairs (this INamedTypeSymbol namedType) + { + var interfaces = namedType.Interfaces; + foreach (INamedTypeSymbol iface in interfaces) { + foreach (var pair in GetMatchingMembers (namedType, iface)) { + yield return pair; + } + } + } + + private static IEnumerable<(ISymbol InterfaceMember, ISymbol ImplementationMember)> GetMatchingMembers (INamedTypeSymbol implementationSymbol, INamedTypeSymbol interfaceSymbol) + { + var members = interfaceSymbol.GetMembers (); + foreach (ISymbol interfaceMember in members) { + if (implementationSymbol.FindImplementationForInterfaceMember (interfaceMember) is ISymbol implementationMember) { + yield return (InterfaceMember: interfaceMember, ImplementationMember: implementationMember); + } + } + foreach (var iface in interfaceSymbol.Interfaces) { + foreach (var pair in GetMatchingMembers (implementationSymbol, iface)) { + yield return pair; + } + } + } } } diff --git a/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index e659a10c1..23319e0fb 100644 --- a/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -263,15 +263,9 @@ namespace ILLink.RoslynAnalyzer SymbolAnalysisContext symbolAnalysisContext, INamedTypeSymbol type) { - ImmutableArray<INamedTypeSymbol> interfaces = type.Interfaces; - foreach (INamespaceOrTypeSymbol iface in interfaces) { - var members = iface.GetMembers (); - foreach (var member in members) { - var implementation = type.FindImplementationForInterfaceMember (member); - // In case the implementation is null because the user code is missing an implementation, we dont provide diagnostics. - // The compiler will provide an error - if (implementation != null && HasMismatchingAttributes (member, implementation)) - ReportMismatchInAttributesDiagnostic (symbolAnalysisContext, implementation, member, isInterface: true); + foreach (var memberpair in type.GetMemberInterfaceImplementationPairs ()) { + if (HasMismatchingAttributes (memberpair.InterfaceMember, memberpair.ImplementationMember)) { + ReportMismatchInAttributesDiagnostic (symbolAnalysisContext, memberpair.ImplementationMember, memberpair.InterfaceMember, isInterface: true); } } } diff --git a/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index 8ada1cff3..cd90ee2b7 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -211,7 +211,7 @@ namespace ILLink.RoslynAnalyzer.Tests return RunTest (allowMissingWarnings: true); } - [Fact (Skip = "https://github.com/dotnet/linker/issues/2273")] + [Fact] public Task VirtualMethodHierarchyDataflowAnnotationValidation () { return RunTest (nameof (VirtualMethodHierarchyDataflowAnnotationValidation)); diff --git a/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs b/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs index 9e250aabc..bd8723731 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs @@ -511,6 +511,7 @@ class C using System.Globalization; using System.Reflection; +using System.Diagnostics.CodeAnalysis; namespace System { @@ -534,6 +535,8 @@ namespace System public override string Name => throw new NotImplementedException (); + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors)] public override ConstructorInfo[] GetConstructors (BindingFlags bindingAttr) { throw new NotImplementedException (); @@ -554,61 +557,76 @@ namespace System throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents + | DynamicallyAccessedMemberTypes.NonPublicEvents)] public override EventInfo GetEvent (string name, BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] public override EventInfo[] GetEvents (BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public override FieldInfo GetField (string name, BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields)] public override FieldInfo[] GetFields (BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] public override Type GetInterface (string name, bool ignoreCase) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] public override Type[] GetInterfaces () { throw new NotImplementedException (); } + [DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes)0x1FFF)] public override MemberInfo[] GetMembers (BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] public override MethodInfo[] GetMethods (BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] public override Type GetNestedType (string name, BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] public override Type[] GetNestedTypes (BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] public override PropertyInfo[] GetProperties (BindingFlags bindingAttr) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] public override object InvokeMember (string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) { throw new NotImplementedException (); @@ -624,16 +642,20 @@ namespace System throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors)] protected override ConstructorInfo GetConstructorImpl (BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] protected override MethodInfo GetMethodImpl (string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) { throw new NotImplementedException (); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] protected override PropertyInfo GetPropertyImpl (string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) { throw new NotImplementedException (); @@ -698,12 +720,12 @@ namespace System } }"; - // (178,16): warning IL2082: 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.C.M2(Type)'. + // (200,16): warning IL2082: 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.C.M2(Type)'. // The implicit 'this' argument of method 'System.C.M1()' does not have matching annotations. // The source value must declare at least the same requirements as those declared on the target location it is assigned to. return VerifyDynamicallyAccessedMembersAnalyzer (string.Concat (GetSystemTypeBase (), TargetParameterWithAnnotations), VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsParameter) - .WithSpan (178, 13, 178, 21) + .WithSpan (200, 13, 200, 21) .WithArguments ("type", "System.C.M2(Type)", "System.C.M1()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -740,7 +762,7 @@ namespace System return VerifyDynamicallyAccessedMembersAnalyzer (string.Concat (GetSystemTypeBase (), ConversionOperation), VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) - .WithSpan (183, 13, 183, 37) + .WithSpan (205, 13, 205, 37) .WithArguments ("type", "System.C.M2(Type)", "System.ConvertsToType.implicit operator Type(ConvertsToType)", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -780,7 +802,7 @@ namespace System return VerifyDynamicallyAccessedMembersAnalyzer (string.Concat (GetSystemTypeBase (), AnnotatedConversionOperation), VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) - .WithSpan (185, 13, 185, 37) + .WithSpan (207, 13, 207, 37) .WithArguments ("type", "System.C.M2(Type)", "System.ConvertsToType.implicit operator Type(ConvertsToType)", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -843,12 +865,12 @@ namespace System } }"; - // (180,13): warning IL2083: 'System.C.M()' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. + // (202,13): warning IL2083: 'System.C.M()' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. // The implicit 'this' argument of method 'System.C.M()' does not have matching annotations. // The source value must declare at least the same requirements as those declared on the target location it is assigned to. return VerifyDynamicallyAccessedMembersAnalyzer (string.Concat (GetSystemTypeBase (), TargetMethodReturnTypeWithAnnotations), VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsMethodReturnType) - .WithSpan (180, 20, 180, 24) + .WithSpan (202, 20, 202, 24) .WithArguments ("System.C.M()", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -876,12 +898,12 @@ namespace System } }"; - // (178,13): warning IL2084: value stored in field 'System.C.f' does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. + // (200,13): warning IL2084: value stored in field 'System.C.f' does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. // The implicit 'this' argument of method 'System.C.M()' does not have matching annotations. // The source value must declare at least the same requirements as those declared on the target location it is assigned to. return VerifyDynamicallyAccessedMembersAnalyzer (string.Concat (GetSystemTypeBase (), TargetFieldWithAnnotations), VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsField) - .WithSpan (178, 13, 178, 21) + .WithSpan (200, 13, 200, 21) .WithArguments ("System.C.f", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); @@ -907,12 +929,12 @@ namespace System } }"; - // (178,13): warning IL2085: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. + // (200,13): warning IL2085: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. // The implicit 'this' argument of method 'System.C.M()' does not have matching annotations. // The source value must declare at least the same requirements as those declared on the target location it is assigned to. return VerifyDynamicallyAccessedMembersAnalyzer (string.Concat (GetSystemTypeBase (), TargetMethodWithAnnotations), VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsThisParameter) - .WithSpan (178, 13, 178, 30) + .WithSpan (200, 13, 200, 30) .WithArguments ("System.Type.GetMethods()", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } #endregion diff --git a/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs b/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs index 7be81d99e..e060c68ab 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -283,43 +284,68 @@ namespace ILLink.RoslynAnalyzer.Tests private bool TryValidateLogContainsAttribute (AttributeSyntax attribute, List<Diagnostic> diagnostics, out int? matchIndex, out string? missingDiagnosticMessage) { - missingDiagnosticMessage = null; - matchIndex = null; + if (!LogContains (attribute, diagnostics, out matchIndex, out string text)) { + missingDiagnosticMessage = $"Could not find text:\n{text}\nIn diagnostics:\n{string.Join (Environment.NewLine, _diagnostics)}"; + return false; + } else { + missingDiagnosticMessage = null; + return true; + } + } + + private void ValidateLogDoesNotContainAttribute (AttributeSyntax attribute, IReadOnlyList<Diagnostic> diagnosticMessages) + { var args = LinkerTestBase.GetAttributeArguments (attribute); - var text = LinkerTestBase.GetStringFromExpression (args["#0"], _semanticModel); + var arg = args["#0"]; + Assert.False (args.ContainsKey ("#1")); + _ = LinkerTestBase.GetStringFromExpression (arg, _semanticModel); + if (LogContains (attribute, diagnosticMessages, out var matchIndex, out var findText)) { + Assert.True (false, $"LogDoesNotContain failure: Text\n\"{findText}\"\nfound in diagnostic:\n {diagnosticMessages[(int) matchIndex]}"); + } + } + + private bool LogContains (AttributeSyntax attribute, IReadOnlyList<Diagnostic> diagnostics, [NotNullWhen (true)] out int? matchIndex, out string findText) + { + + var args = LinkerTestBase.GetAttributeArguments (attribute); + findText = LinkerTestBase.GetStringFromExpression (args["#0"], _semanticModel); // If the text starts with `warning IL...` then it probably follows the pattern // 'warning <diagId>: <location>:' // We don't want to repeat the location in the error message for the analyzer, so // it's better to just trim here. We've already filtered by diagnostic location so // the text location shouldn't matter - if (text.StartsWith ("warning IL")) { - var firstColon = text.IndexOf (": "); + if (findText.StartsWith ("warning IL")) { + var firstColon = findText.IndexOf (": "); if (firstColon > 0) { - var secondColon = text.IndexOf (": ", firstColon + 1); + var secondColon = findText.IndexOf (": ", firstColon + 1); if (secondColon > 0) { - text = text.Substring (secondColon + 2); + findText = findText.Substring (secondColon + 2); } } } - for (int i = 0; i < diagnostics.Count; i++) { - if (diagnostics[i].GetMessage ().Contains (text)) { - matchIndex = i; - return true; + bool isRegex = args.TryGetValue ("regexMatch", out var regexMatchExpr) + && regexMatchExpr.GetLastToken ().Value is bool regexMatch + && regexMatch; + if (isRegex) { + var regex = new Regex (findText); + for (int i = 0; i < diagnostics.Count; i++) { + if (regex.IsMatch (diagnostics[i].GetMessage ())) { + matchIndex = i; + return true; + } + } + } else { + for (int i = 0; i < diagnostics.Count; i++) { + if (diagnostics[i].GetMessage ().Contains (findText)) { + matchIndex = i; + return true; + } } } - - missingDiagnosticMessage = $"Could not find text:\n{text}\nIn diagnostics:\n{string.Join (Environment.NewLine, _diagnostics)}"; + matchIndex = null; return false; } - - private void ValidateLogDoesNotContainAttribute (AttributeSyntax attribute, IReadOnlyList<Diagnostic> diagnosticMessages) - { - var arg = Assert.Single (LinkerTestBase.GetAttributeArguments (attribute)); - var text = LinkerTestBase.GetStringFromExpression (arg.Value, _semanticModel); - foreach (var diagnostic in diagnosticMessages) - Assert.DoesNotContain (text, diagnostic.GetMessage ()); - } } } diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs index 9506cf0db..e8aa8a654 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs @@ -68,16 +68,27 @@ namespace Mono.Linker.Tests.Cases.DataFlow class MyReflect : IReflect { public Type UnderlyingSystemType => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public FieldInfo GetField (string name, BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public FieldInfo[] GetFields (BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers ((DynamicallyAccessedMemberTypes) 8191)] public MemberInfo[] GetMember (string name, BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers ((DynamicallyAccessedMemberTypes) 8191)] public MemberInfo[] GetMembers (BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] public MethodInfo GetMethod (string name, BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] public MethodInfo GetMethod (string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] public MethodInfo[] GetMethods (BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] public PropertyInfo[] GetProperties (BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] public PropertyInfo GetProperty (string name, BindingFlags bindingAttr) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] public PropertyInfo GetProperty (string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) => throw new NotImplementedException (); + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] public object InvokeMember (string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) => throw new NotImplementedException (); } diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs index ed83c7bea..8ff523a92 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs @@ -292,15 +292,17 @@ namespace Mono.Linker.Tests.Cases.DataFlow [CompilerGenerated] Type PropertyWithExistingAttributes_Field; + [ExpectedWarning ("IL2043", "PropertyWithExistingAttributes", "PropertyWithExistingAttributes.get", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2043", "PropertyWithExistingAttributes", "PropertyWithExistingAttributes.set", ProducedBy = ProducedBy.Analyzer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] Type PropertyWithExistingAttributes { - // Analyzer doesn't try to detect backing fields of properties: https://github.com/dotnet/linker/issues/2273 + // On property/accessor mismatch, linker warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning ("IL2043", "PropertyWithExistingAttributes", "PropertyWithExistingAttributes.get", ProducedBy = ProducedBy.Trimmer)] [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] get { return PropertyWithExistingAttributes_Field; } - // Analyzer doesn't try to detect backing fields of properties: https://github.com/dotnet/linker/issues/2273 + // On property/accessor mismatch, linker warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning ("IL2043", "PropertyWithExistingAttributes", "PropertyWithExistingAttributes.set", ProducedBy = ProducedBy.Trimmer)] [param: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] @@ -328,15 +330,17 @@ namespace Mono.Linker.Tests.Cases.DataFlow [CompilerGenerated] Type PropertyWithConflictingAttributes_Field; + [ExpectedWarning ("IL2043", "PropertyWithConflictingAttributes", "PropertyWithConflictingAttributes.get", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2043", "PropertyWithConflictingAttributes", "PropertyWithConflictingAttributes.set", ProducedBy = ProducedBy.Analyzer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] Type PropertyWithConflictingAttributes { - // Analyzer doesn't try to detect backing fields of properties: https://github.com/dotnet/linker/issues/2273 + // On property/accessor mismatch, linker warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning ("IL2043", "PropertyWithConflictingAttributes", "PropertyWithConflictingAttributes.get", ProducedBy = ProducedBy.Trimmer)] [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.NonPublicConstructors)] get { return PropertyWithConflictingAttributes_Field; } - // Analyzer doesn't try to detect backing fields of properties: https://github.com/dotnet/linker/issues/2273 + // On property/accessor mismatch, linker warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning ("IL2043", "PropertyWithConflictingAttributes", "PropertyWithConflictingAttributes.set", ProducedBy = ProducedBy.Trimmer)] [param: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.NonPublicConstructors)] |