diff options
Diffstat (limited to 'test')
10 files changed, 262 insertions, 2 deletions
diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RecognizedReflectionAccessPatternAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RecognizedReflectionAccessPatternAttribute.cs new file mode 100644 index 000000000..09d83aa65 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RecognizedReflectionAccessPatternAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class RecognizedReflectionAccessPatternAttribute : BaseExpectedLinkedBehaviorAttribute + { + public RecognizedReflectionAccessPatternAttribute (Type reflectionMethodType, string reflectionMethodName, Type[] reflectionMethodParameters, + Type accessedItemType, string accessedItemName, Type[] accessedItemParameters) + { + if (reflectionMethodType == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodType)); + if (reflectionMethodName == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodName)); + if (reflectionMethodParameters == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodParameters)); + + if (accessedItemType == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (accessedItemType)); + if (accessedItemName == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (accessedItemName)); + } + } +} diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/UnrecognizedReflectionAccessPatternAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/UnrecognizedReflectionAccessPatternAttribute.cs new file mode 100644 index 000000000..666c2da63 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/UnrecognizedReflectionAccessPatternAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class UnrecognizedReflectionAccessPatternAttribute : BaseExpectedLinkedBehaviorAttribute + { + public UnrecognizedReflectionAccessPatternAttribute (Type reflectionMethodType, string reflectionMethodName, Type [] reflectionMethodParameters, + string message = null) + { + if (reflectionMethodType == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodType)); + if (reflectionMethodName == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodName)); + if (reflectionMethodParameters == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodParameters)); + + if (message == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (message)); + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/Reflection/ActivatorCreateInstance.cs b/test/Mono.Linker.Tests.Cases/Reflection/ActivatorCreateInstance.cs index 47c40dd6f..d1d0291d4 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/ActivatorCreateInstance.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/ActivatorCreateInstance.cs @@ -6,6 +6,8 @@ namespace Mono.Linker.Tests.Cases.Reflection { public class ActivatorCreateInstance { + [UnrecognizedReflectionAccessPattern( + typeof(Activator), nameof(Activator.CreateInstance) + "<T>", new Type[0])] public static void Main () { Activator.CreateInstance (typeof (Test1)); @@ -13,6 +15,7 @@ namespace Mono.Linker.Tests.Cases.Reflection Activator.CreateInstance (typeof (Test3), BindingFlags.NonPublic | BindingFlags.Instance, null, null, null); Activator.CreateInstance (typeof (Test4), new object [] { 1, "ss" }); Activator.CreateInstance ("test", "Mono.Linker.Tests.Cases.Reflection.ActivatorCreateInstance+Test5"); + Activator.CreateInstance<Test1> (); } [Kept] diff --git a/test/Mono.Linker.Tests.Cases/Reflection/MethodUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/MethodUsedViaReflection.cs index 39a7e12f4..01beb908b 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/MethodUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/MethodUsedViaReflection.cs @@ -1,8 +1,13 @@ -using System.Reflection; +using System; +using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Reflection { + public class MethodUsedViaReflection { + [RecognizedReflectionAccessPattern ( + typeof (Type), nameof (Type.GetMethod), new Type [] { typeof(string), typeof(BindingFlags) }, + typeof (MethodUsedViaReflection), nameof (MethodUsedViaReflection.OnlyCalledViaReflection), new Type [0])] public static void Main () { var method = typeof (MethodUsedViaReflection).GetMethod ("OnlyCalledViaReflection", BindingFlags.Static | BindingFlags.NonPublic); diff --git a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs index 1937b971a..26d2bb92e 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs @@ -24,6 +24,8 @@ namespace Mono.Linker.Tests.Cases.Reflection { } [Kept] + [UnrecognizedReflectionAccessPattern ( + typeof (Type), nameof (Type.GetType), new Type [] { typeof (string), typeof (bool) })] public static void TestNull () { const string reflectionTypeKeptString = null; @@ -81,6 +83,9 @@ namespace Mono.Linker.Tests.Cases.Reflection { public class AType { } [Kept] + [RecognizedReflectionAccessPattern ( + typeof (Type), nameof (Type.GetType), new Type[] { typeof (string), typeof (bool) }, + typeof (AType), null, null)] public static void TestType () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AType"; diff --git a/test/Mono.Linker.Tests/Extensions/CecilExtensions.cs b/test/Mono.Linker.Tests/Extensions/CecilExtensions.cs index 747abf570..60781468d 100644 --- a/test/Mono.Linker.Tests/Extensions/CecilExtensions.cs +++ b/test/Mono.Linker.Tests/Extensions/CecilExtensions.cs @@ -56,6 +56,36 @@ namespace Mono.Linker.Tests.Extensions { yield return @event; } + public static IEnumerable<MethodDefinition> AllMethods (this TypeDefinition type) + { + foreach (var m in type.AllMembers()) { + switch (m) { + case MethodDefinition method: + yield return method; + break; + case PropertyDefinition @property: + if (@property.GetMethod != null) + yield return @property.GetMethod; + + if (@property.SetMethod != null) + yield return @property.SetMethod; + + break; + case EventDefinition @event: + if (@event.AddMethod != null) + yield return @event.AddMethod; + + if (@event.RemoveMethod != null) + yield return @event.RemoveMethod; + + break; + + default: + break; + } + } + } + public static bool HasAttribute (this ICustomAttributeProvider provider, string name) { return provider.CustomAttributes.Any (ca => ca.AttributeType.Name == name); diff --git a/test/Mono.Linker.Tests/TestCasesRunner/LinkerCustomizations.cs b/test/Mono.Linker.Tests/TestCasesRunner/LinkerCustomizations.cs index d0b65edec..bb599df7f 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/LinkerCustomizations.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/LinkerCustomizations.cs @@ -10,6 +10,8 @@ namespace Mono.Linker.Tests.TestCasesRunner { public TestDependencyRecorder DependencyRecorder { get; set; } + public TestReflectionPatternRecorder ReflectionPatternRecorder { get; set; } + public event Action<LinkContext> CustomizeContext; public void CustomizeLinkContext(LinkContext context) diff --git a/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index f2cf4efd1..bb1de9289 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -168,6 +168,7 @@ namespace Mono.Linker.Tests.TestCasesRunner { { VerifyLoggedMessages(original, linkResult.Logger); VerifyRecordedDependencies (original, linkResult.Customizations.DependencyRecorder); + VerifyRecordedReflectionPatterns (original, linkResult.Customizations.ReflectionPatternRecorder); } protected virtual void InitialChecking (LinkedTestCaseResult linkResult, AssemblyDefinition original, AssemblyDefinition linked) @@ -644,7 +645,7 @@ namespace Mono.Linker.Tests.TestCasesRunner { $"Expected to find recorded dependency '{expectedSource} -> {expectedTarget} {expectedMarked ?? string.Empty}'{Environment.NewLine}" + $"Potential dependencies matching the target: {Environment.NewLine}{targetCandidates}{Environment.NewLine}" + $"Potential dependencies matching the source: {Environment.NewLine}{sourceCandidates}{Environment.NewLine}" + - $"If there's no matches, try to specify just a part of the source/target name and rerun the test."); + $"If there's no matches, try to specify just a part of the source/target name and rerun the test to get potential matches."); } } } @@ -656,6 +657,128 @@ namespace Mono.Linker.Tests.TestCasesRunner { } } + void VerifyRecordedReflectionPatterns (AssemblyDefinition original, TestReflectionPatternRecorder reflectionPatternRecorder) + { + foreach (var expectedSourceMethodDefinition in original.MainModule.AllDefinedTypes ().SelectMany (t => t.AllMethods ())) { + foreach (var attr in expectedSourceMethodDefinition.CustomAttributes) { + if (attr.AttributeType.Resolve ().Name == nameof (RecognizedReflectionAccessPatternAttribute)) { + string expectedSourceMethod = GetFullMemberNameFromDefinition (expectedSourceMethodDefinition); + string expectedReflectionMethod = GetFullMemberNameFromReflectionAccessPatternAttribute (attr, constructorArgumentsOffset: 0); + string expectedAccessedItem = GetFullMemberNameFromReflectionAccessPatternAttribute (attr, constructorArgumentsOffset: 3); + + if (!reflectionPatternRecorder.RecognizedPatterns.Any (pattern => { + if (GetFullMemberNameFromDefinition (pattern.SourceMethod) != expectedSourceMethod) + return false; + + if (GetFullMemberNameFromDefinition (pattern.ReflectionMethod) != expectedReflectionMethod) + return false; + + if (GetFullMemberNameFromDefinition (pattern.AccessedItem) != expectedAccessedItem) + return false; + + return true; + })) { + string sourceMethodCandidates = string.Join (Environment.NewLine, reflectionPatternRecorder.RecognizedPatterns + .Where (p => GetFullMemberNameFromDefinition (p.SourceMethod).ToLowerInvariant ().Contains (expectedSourceMethod.ToLowerInvariant ())) + .Select (p => "\t" + RecognizedReflectionAccessPatternToString (p))); + string reflectionMethodCandidates = string.Join (Environment.NewLine, reflectionPatternRecorder.RecognizedPatterns + .Where (p => GetFullMemberNameFromDefinition (p.ReflectionMethod).ToLowerInvariant ().Contains (expectedReflectionMethod.ToLowerInvariant ())) + .Select (p => "\t" + RecognizedReflectionAccessPatternToString (p))); + + Assert.Fail ( + $"Expected to find recognized reflection access pattern '{expectedSourceMethod.ToString()}: Call to {expectedReflectionMethod} accessed {expectedAccessedItem}'{Environment.NewLine}" + + $"Potential patterns matching the source method: {Environment.NewLine}{sourceMethodCandidates}{Environment.NewLine}" + + $"Potential patterns matching the reflection method: {Environment.NewLine}{reflectionMethodCandidates}{Environment.NewLine}" + + $"If there's no matches, try to specify just a part of the source method or reflection method name and rerun the test to get potential matches."); + } + } else if (attr.AttributeType.Resolve ().Name == nameof (UnrecognizedReflectionAccessPatternAttribute)) { + string expectedSourceMethod = GetFullMemberNameFromDefinition (expectedSourceMethodDefinition); + string expectedReflectionMethod = GetFullMemberNameFromReflectionAccessPatternAttribute (attr, constructorArgumentsOffset: 0); + string expectedMessage = (string)attr.ConstructorArguments [3].Value; + + if (!reflectionPatternRecorder.UnrecognizedPatterns.Any (pattern => { + if (GetFullMemberNameFromDefinition (pattern.SourceMethod) != expectedSourceMethod) + return false; + + if (GetFullMemberNameFromDefinition (pattern.ReflectionMethod) != expectedReflectionMethod) + return false; + + if (expectedMessage != null && pattern.Message != expectedMessage) + return false; + + return true; + })) { + string sourceMethodCandidates = string.Join (Environment.NewLine, reflectionPatternRecorder.UnrecognizedPatterns + .Where (p => GetFullMemberNameFromDefinition (p.SourceMethod).ToLowerInvariant ().Contains (expectedSourceMethod.ToLowerInvariant ())) + .Select (p => "\t" + UnrecognizedReflectionAccessPatternToString (p))); + string reflectionMethodCandidates = string.Join (Environment.NewLine, reflectionPatternRecorder.UnrecognizedPatterns + .Where (p => GetFullMemberNameFromDefinition (p.ReflectionMethod).ToLowerInvariant ().Contains (expectedReflectionMethod.ToLowerInvariant ())) + .Select (p => "\t" + UnrecognizedReflectionAccessPatternToString (p))); + + Assert.Fail ( + $"Expected to find unrecognized reflection access pattern '{expectedSourceMethod}: Call to {expectedReflectionMethod} unrecognized {expectedMessage ?? string.Empty}'{Environment.NewLine}" + + $"Potential patterns matching the source method: {Environment.NewLine}{sourceMethodCandidates}{Environment.NewLine}" + + $"Potential patterns matching the reflection method: {Environment.NewLine}{reflectionMethodCandidates}{Environment.NewLine}" + + $"If there's no matches, try to specify just a part of the source method or reflection method name and rerun the test to get potential matches."); + } + } + } + } + } + + static string GetFullMemberNameFromReflectionAccessPatternAttribute (CustomAttribute attr, int constructorArgumentsOffset) + { + var type = attr.ConstructorArguments [constructorArgumentsOffset].Value; + var memberName = (string)attr.ConstructorArguments [constructorArgumentsOffset + 1].Value; + var parameterTypes = (CustomAttributeArgument[])attr.ConstructorArguments [constructorArgumentsOffset + 2].Value; + + string fullName = type.ToString (); + if (memberName == null) { + return fullName; + } + + fullName += "::" + memberName; + if (parameterTypes != null) { + fullName += "(" + string.Join (",", parameterTypes.Select (t => t.Value.ToString ())) + ")"; + } + + return fullName; + } + + static string GetFullMemberNameFromDefinition (IMemberDefinition member) + { + // Method which basically returns the same as member.ToString() but without the return type + // of a method (if it's a method). + // We need this as the GetFullMemberNameFromReflectionAccessPatternAttribute can't guess the return type + // as it would have to actually resolve the referenced method, which is very expensive and no necessary + // for the tests to work (the return types are redundant piece of information anyway). + + if (member is TypeDefinition) { + return member.FullName; + } + + string fullName = member.DeclaringType.FullName + "::"; + if (member is MethodDefinition method) { + fullName += method.GetSignature (); + } + else { + fullName += member.Name; + } + + return fullName; + } + + static string RecognizedReflectionAccessPatternToString (TestReflectionPatternRecorder.ReflectionAccessPattern pattern) + { + return $"{GetFullMemberNameFromDefinition (pattern.SourceMethod)}: Call to {GetFullMemberNameFromDefinition (pattern.ReflectionMethod)} accessed {GetFullMemberNameFromDefinition (pattern.AccessedItem)}"; + } + + static string UnrecognizedReflectionAccessPatternToString (TestReflectionPatternRecorder.ReflectionAccessPattern pattern) + { + return $"{GetFullMemberNameFromDefinition (pattern.SourceMethod)}: Call to {GetFullMemberNameFromDefinition (pattern.ReflectionMethod)} unrecognized '{pattern.Message}'"; + } + + protected TypeDefinition GetOriginalTypeFromInAssemblyAttribute (CustomAttribute inAssemblyAttribute) { return GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute.ConstructorArguments [0].Value.ToString (), inAssemblyAttribute.ConstructorArguments [1].Value); diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs index d7a1d983d..7d0f22da1 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs @@ -75,6 +75,15 @@ namespace Mono.Linker.Tests.TestCasesRunner { context.Tracer.AddRecorder (customizations.DependencyRecorder); }; } + + if (_testCaseTypeDefinition.AllMethods().Any(method => method.CustomAttributes.Any (attr => + attr.AttributeType.Name == nameof (RecognizedReflectionAccessPatternAttribute) || + attr.AttributeType.Name == nameof (UnrecognizedReflectionAccessPatternAttribute)))) { + customizations.ReflectionPatternRecorder = new TestReflectionPatternRecorder (); + customizations.CustomizeContext += context => { + context.ReflectionPatternRecorder = customizations.ReflectionPatternRecorder; + }; + }; } #if NETCOREAPP diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestReflectionPatternRecorder.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestReflectionPatternRecorder.cs new file mode 100644 index 000000000..88b78ec3b --- /dev/null +++ b/test/Mono.Linker.Tests/TestCasesRunner/TestReflectionPatternRecorder.cs @@ -0,0 +1,37 @@ +using Mono.Cecil; +using System.Collections.Generic; + +namespace Mono.Linker.Tests.TestCasesRunner +{ + public class TestReflectionPatternRecorder : IReflectionPatternRecorder + { + public struct ReflectionAccessPattern + { + public MethodDefinition SourceMethod; + public MethodDefinition ReflectionMethod; + public IMemberDefinition AccessedItem; + public string Message; + } + + public List<ReflectionAccessPattern> RecognizedPatterns = new List<ReflectionAccessPattern> (); + public List<ReflectionAccessPattern> UnrecognizedPatterns = new List<ReflectionAccessPattern> (); + + public void RecognizedReflectionAccessPattern (MethodDefinition sourceMethod, MethodDefinition reflectionMethod, IMemberDefinition accessedItem) + { + RecognizedPatterns.Add (new ReflectionAccessPattern { + SourceMethod = sourceMethod, + ReflectionMethod = reflectionMethod, + AccessedItem = accessedItem + }); + } + + public void UnrecognizedReflectionAccessPattern (MethodDefinition sourceMethod, MethodDefinition reflectionMethod, string message) + { + UnrecognizedPatterns.Add (new ReflectionAccessPattern { + SourceMethod = sourceMethod, + ReflectionMethod = reflectionMethod, + Message = message + }); + } + } +} |