diff --git a/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 4269aa3b80de..9ecb136725e1 100644 --- a/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -28,6 +28,7 @@ static ImmutableArray GetSupportedDiagnostics () diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor ((DiagnosticId) i)); } diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor ((DiagnosticId) 2110)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor ((DiagnosticId) 2111)); return diagDescriptorsArrayBuilder.ToImmutable (); } @@ -173,6 +174,13 @@ SymbolKind.Field when member is IFieldSymbol privateFieldSymbol && memberTypes.HasFlag (DynamicallyAccessedMemberTypes.NonPublicFields) && privateFieldSymbol.GetDynamicallyAccessedMemberTypes () != DynamicallyAccessedMemberTypes.None => DiagnosticId.DynamicallyAccessedMembersUnsuportedReflectionAccessInField, + + SymbolKind.Method when member is IMethodSymbol publicMethodSymbol && + publicMethodSymbol.DeclaredAccessibility == Accessibility.Public && + memberTypes.HasFlag (DynamicallyAccessedMemberTypes.PublicMethods) && + publicMethodSymbol.GetDynamicallyAccessedMemberTypesOnParameters () != DynamicallyAccessedMemberTypes.None + => DiagnosticId.DynamicallyAccessedMembersUnsuportedReflectionAccessInMethod, + _ => null, }; return diagnosticId != null; diff --git a/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs b/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs index 71bb461414b7..c61e766d26b9 100644 --- a/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs +++ b/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs @@ -42,6 +42,23 @@ internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypes return (DynamicallyAccessedMemberTypes) dynamicallyAccessedMembers!.ConstructorArguments[0].Value!; } + internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesOnParameters (this IMethodSymbol methodSymbol) + { + + AttributeData? dynamicallyAccessedMembers = null; + foreach (var parameter in methodSymbol.Parameters) { + if (parameter.TryGetAttribute (DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out AttributeData? attribute)) { + dynamicallyAccessedMembers = attribute; + break; + } + } + + if (dynamicallyAccessedMembers == null) + return DynamicallyAccessedMemberTypes.None; + + return (DynamicallyAccessedMemberTypes) dynamicallyAccessedMembers.ConstructorArguments[0].Value!; + } + internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypesOnReturnType (this IMethodSymbol methodSymbol) { AttributeData? dynamicallyAccessedMembers = null; diff --git a/src/ILLink.RoslynAnalyzer/ReflectionMethodBodyScanner.cs b/src/ILLink.RoslynAnalyzer/ReflectionMethodBodyScanner.cs index 11e4cd015929..c8e6bb7e4fc8 100644 --- a/src/ILLink.RoslynAnalyzer/ReflectionMethodBodyScanner.cs +++ b/src/ILLink.RoslynAnalyzer/ReflectionMethodBodyScanner.cs @@ -75,6 +75,23 @@ enum IntrinsicId static IntrinsicId GetIntrinsicIdForMethod (IMethodSymbol calledMethod) { return calledMethod.Name switch { + // System.Type.GetMethod (string) + // System.Type.GetMethod (string, BindingFlags) + // System.Type.GetMethod (string, Type[]) + // System.Type.GetMethod (string, Type[], ParameterModifier[]) + // System.Type.GetMethod (string, BindingFlags, Type[]) + // System.Type.GetMethod (string, BindingFlags, Binder, Type[], ParameterModifier[]) + // System.Type.GetMethod (string, BindingFlags, Binder, CallingConventions, Type[], ParameterModifier[]) + // System.Type.GetMethod (string, int, Type[]) + // System.Type.GetMethod (string, int, Type[], ParameterModifier[]?) + // System.Type.GetMethod (string, int, BindingFlags, Binder?, Type[], ParameterModifier[]?) + // System.Type.GetMethod (string, int, BindingFlags, Binder?, CallingConventions, Type[], ParameterModifier[]?) + "GetMethod" when calledMethod.ContainingType.ContainingNamespace.GetDisplayName () == "System" + && calledMethod.ContainingType.Name == "Type" + && calledMethod.Parameters[0].Type.ContainingSymbol.ToDisplayString () == "System" + && calledMethod.Parameters[0].Type.Name == "String" + => IntrinsicId.Type_GetMethod, + // System.Type.GetField (string) // System.Type.GetField (string, BindingFlags) "GetField" when calledMethod.ContainingType.ContainingNamespace.GetDisplayName () == "System" @@ -91,6 +108,40 @@ public static bool HandleCall (OperationAnalysisContext context, IInvocationOper var calledMethod = invocationOperation.TargetMethod; switch (GetIntrinsicIdForMethod (calledMethod)) { + // + // GetMethod (string) + // GetMethod (string, BindingFlags) + // GetMethod (string, Type[]) + // GetMethod (string, Type[], ParameterModifier[]) + // GetMethod (string, BindingFlags, Type[]) + // GetMethod (string, BindingFlags, Binder, Type[], ParameterModifier[]) + // GetMethod (string, BindingFlags, Binder, CallingConventions, Type[], ParameterModifier[]) + // GetMethod (string, int, Type[]) + // GetMethod (string, int, Type[], ParameterModifier[]?) + // GetMethod (string, int, BindingFlags, Binder?, Type[], ParameterModifier[]?) + // GetMethod (string, int, BindingFlags, Binder?, CallingConventions, Type[], ParameterModifier[]?) + // + case IntrinsicId.Type_GetMethod: { + BindingFlags? bindingFlags; + // @TODO - only handling the first 2 cases here + if (calledMethod.Parameters.Length > 1 && calledMethod.Parameters[1].Type.GetDisplayName () == "System.Reflection.BindingFlags") + bindingFlags = GetBindingFlagsFromValue (invocationOperation.Arguments[1]); + else + // Assume a default value for BindingFlags for methods that don't use BindingFlags as a parameter + bindingFlags = BindingFlags.Public; + + DynamicallyAccessedMemberTypes memberTypes = GetDynamicallyAccessedMemberTypesFromBindingFlagsForMethods (bindingFlags); + + var source = DynamicallyAccessedMembersAnalyzer.TryGetSymbolFromOperation (invocationOperation.Instance, context); + if (source != null) { + // Lets generate the + context.ReportDiagnostic (Diagnostic.Create (DiagnosticDescriptors.GetDiagnosticDescriptor ((DiagnosticId) DiagnosticId.DynamicallyAccessedMembersUnsuportedReflectionAccessInMethod), invocationOperation.Syntax.GetLocation (), calledMethod)); + //DynamicallyAccessedMembersAnalyzer.VerifyAnnotations (source.Value, memberTypes, context, invocationOperation.Syntax.GetLocation ()); + } + + break; + } + // // GetField (string) // GetField (string, BindingFlags) diff --git a/src/ILLink.Shared/DiagnosticId.cs b/src/ILLink.Shared/DiagnosticId.cs index 61b0fdc3d08f..91d1beb0ae70 100644 --- a/src/ILLink.Shared/DiagnosticId.cs +++ b/src/ILLink.Shared/DiagnosticId.cs @@ -42,6 +42,7 @@ public enum DiagnosticId DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter = 2091, // Dynamically Accessed Members Unsuported Reflection Access DynamicallyAccessedMembersUnsuportedReflectionAccessInField = 2110, + DynamicallyAccessedMembersUnsuportedReflectionAccessInMethod = 2111, // Single-file diagnostic ids. AvoidAssemblyLocationInSingleFile = 3000, diff --git a/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs b/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs index e87e31ad96d2..112aabe42e01 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs @@ -1218,5 +1218,58 @@ static void Reflection () // (12,3): warning IL2110: Field 'AnnotatedField._annotatedField' with 'DynamicallyAccessedMembersAttribute' is accessed via reflection. Trimmer can't guarantee availability of the requirements of the field. VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersUnsuportedReflectionAccessInField).WithSpan (12, 3, 12, 55).WithArguments ("AnnotatedField._annotatedField")); } + + [Fact] + public Task AnnotatedMethodParametersReflection () + { + var source = @" +using System.Diagnostics.CodeAnalysis; +//using System.Reflection; +using System; + +class AnnotatedMethodParameters +{ + public static void MethodWithSingleAnnotatedParameter ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type type) + { } + + static void Reflection () + { + typeof (AnnotatedMethodParameters).GetMethod (nameof (MethodWithSingleAnnotatedParameter));//, BindingFlags.CreateInstance); + } +}"; + return VerifyDynamicallyAccessedMembersAnalyzer (source, + // (14,3): warning IL2111: + VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersUnsuportedReflectionAccessInMethod) + .WithSpan (14, 3, 14, 93) + .WithArguments ("AnnotatedMethodParameters.MethodWithSingleAnnotatedParameter(System.Type)")); + } + + [Fact] + public Task AnnotatedMethodParametersReflectionWithBindingFlags () + { + var source = @" +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System; + +class AnnotatedMethodParameters +{ + public static void MethodWithSingleAnnotatedParameter ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] Type type) + { } + + static void Reflection () + { + typeof (AnnotatedMethodParameters).GetMethod (nameof (MethodWithSingleAnnotatedParameter), BindingFlags.CreateInstance); + } +}"; + return VerifyDynamicallyAccessedMembersAnalyzer (source, + // (14,3): warning IL2111: + VerifyCS.Diagnostic (DiagnosticId.DynamicallyAccessedMembersUnsuportedReflectionAccessInMethod) + .WithSpan (14, 3, 14, 122) + .WithArguments ("System.Type.GetMethod(string, System.Reflection.BindingFlags)")); + } + } }