diff options
14 files changed, 962 insertions, 525 deletions
diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index dbc4559dd..c4230df0b 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -537,7 +537,7 @@ namespace Mono.Linker.Steps { } else { name = name.Substring (0, arity_marker); } - + foreach (var m in type.Methods) { if (m.Name != name) continue; @@ -1789,7 +1789,7 @@ namespace Mono.Linker.Steps { // For example: // Field Name = <IFoo<int>.Bar>k__BackingField // Property Name = IFoo<System.Int32>.Bar - // + // // instead we will search the properties and find the one that makes use of the current backing field var propertyDefinition = SearchPropertiesForMatchingFieldDefinition (field); if (propertyDefinition != null && !Annotations.IsMarked (propertyDefinition)) @@ -2345,8 +2345,8 @@ namespace Mono.Linker.Steps { if (HasManuallyTrackedDependency (body)) return; - var methodCalling = body.Method; var instructions = body.Instructions; + ReflectionPatternDetector detector = new ReflectionPatternDetector (this, body.Method); // // Starting at 1 because all patterns require at least 1 instruction backward lookup @@ -2367,477 +2367,561 @@ namespace Mono.Linker.Steps { if (methodCalledDefinition == null) continue; - var methodCalledType = methodCalled.DeclaringType; - - int first_arg_instr, second_arg_instr; - Instruction first_arg; - - switch (methodCalledType.Name) { - // - // System.Type - // - case "Type" when methodCalledType.Namespace == "System": - - // Some of the overloads are implemented by calling another overload of the same name. - // These "internal" calls are not interesting to analyze, the outermost call is the one - // which needs to be analyzed. The assumption is that all overloads have the same semantics. - // (for example that all overload of GetConstructor if used require the specified type to have a .ctor). - if (methodCalling.DeclaringType == methodCalling.DeclaringType && methodCalling.Name == methodCalled.Name) - break; + ReflectionPatternContext reflectionContext = new ReflectionPatternContext (_context, body.Method, methodCalledDefinition); + try { + detector.Process (ref reflectionContext, methodCalledDefinition, i); + } + finally { + reflectionContext.Dispose (); + } + } + } - switch (methodCalled.Name) { - // - // GetConstructor (Type []) - // GetConstructor (BindingFlags, Binder, Type [], ParameterModifier []) - // GetConstructor (BindingFlags, Binder, CallingConventions, Type [], ParameterModifier []) - // - case "GetConstructor": - if (!methodCalledDefinition.IsStatic) - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Constructor, methodCalledDefinition, i - 1); + struct ReflectionPatternContext : IDisposable + { + readonly LinkContext _context; + bool _patternReported; - break; + public MethodDefinition MethodCalling { get; private set; } + public MethodDefinition MethodCalled { get; private set; } - // - // GetMethod (string) - // GetMethod (string, BindingFlags) - // GetMethod (string, Type[]) - // GetMethod (string, Type[], ParameterModifier[]) - // GetMethod (string, BindingFlags, Binder, Type[], ParameterModifier[]) - // GetMethod (string, BindingFlags, Binder, CallingConventions, Type[], ParameterModifier[]) - // - // TODO: .NET Core extensions - // 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 "GetMethod": - if (!methodCalledDefinition.IsStatic) - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Method, methodCalledDefinition, i - 1); + public ReflectionPatternContext (LinkContext context, MethodDefinition methodCalling, MethodDefinition methodCalled) + { + _context = context; + MethodCalling = methodCalling; + MethodCalled = methodCalled; - break; + _patternReported = false; + } - // - // GetField (string) - // GetField (string, BindingFlags) - // - case "GetField": - if (!methodCalledDefinition.IsStatic) - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Field, methodCalledDefinition, i - 1); + public void RecordRecognizedPattern<T> (T accessedItem, Action mark) + where T : IMemberDefinition + { + _context.Tracer.Push ($"Reflection-{accessedItem}"); + try { + _patternReported = true; + mark (); + _context.ReflectionPatternRecorder.RecognizedReflectionAccessPattern (MethodCalling, MethodCalled, accessedItem); + } finally { + _context.Tracer.Pop (); + } + } - break; + public void RecordUnrecognizedPattern (string message) + { + _patternReported = true; + _context.ReflectionPatternRecorder.UnrecognizedReflectionAccessPattern (MethodCalling, MethodCalled, message); + } - // - // GetEvent (string) - // GetEvent (string, BindingFlags) - // - case "GetEvent": - if (!methodCalledDefinition.IsStatic) - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Event, methodCalledDefinition, i - 1); + public void Dispose () + { + if (!_patternReported) { + _context.ReflectionPatternRecorder.UnrecognizedReflectionAccessPattern (MethodCalling, MethodCalled, $"Call to'`{MethodCalled.FullName}' inside '{MethodCalling.FullName}' could not be recognized as a known pattern"); + } + } + } - break; + class ReflectionPatternDetector + { + readonly MarkStep _markStep; + readonly MethodDefinition _methodCalling; + readonly Collection<Instruction> _instructions; - // - // GetProperty (string) - // GetProperty (string, BindingFlags) - // GetProperty (string, Type) - // GetProperty (string, Type[]) - // GetProperty (string, Type, Type[]) - // GetProperty (string, Type, Type[], ParameterModifier[]) - // GetProperty (string, BindingFlags, Binder, Type, Type[], ParameterModifier[]) - // - case "GetProperty": - if (!methodCalledDefinition.IsStatic) - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Property, methodCalledDefinition, i - 1); + public ReflectionPatternDetector (MarkStep markStep, MethodDefinition callingMethod) + { + _markStep = markStep; + _methodCalling = callingMethod; + _instructions = _methodCalling.Body.Instructions; + } - break; + public void Process (ref ReflectionPatternContext reflectionContext, MethodDefinition methodCalled, int instructionIndex) + { + var methodCalledType = methodCalled.DeclaringType; + switch (methodCalledType.Name) { // - // GetType (string) - // GetType (string, Boolean) - // GetType (string, Boolean, Boolean) - // GetType (string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>) - // GetType (string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>, Boolean) - // GetType (string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>, Boolean, Boolean) + // System.Type // - case "GetType": - if (!methodCalledDefinition.IsStatic) - continue; - - first_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, methodCalledDefinition.Parameters.Count); - if (first_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Reflection call '{methodCalled.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); - continue; - } - - // - // The next value must be string constant (we don't handle anything else) - // - first_arg = instructions [first_arg_instr]; - if (first_arg.OpCode != OpCodes.Ldstr) { - _context.LogMessage (MessageImportance.Low, $"Reflection call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with argument which cannot be analyzed"); - continue; - } - - TypeDefinition foundType = ResolveFullyQualifiedTypeName ((string)first_arg.Operand); - if (foundType == null) - continue; + case "Type" when methodCalledType.Namespace == "System": + + // Some of the overloads are implemented by calling another overload of the same name. + // These "internal" calls are not interesting to analyze, the outermost call is the one + // which needs to be analyzed. The assumption is that all overloads have the same semantics. + // (for example that all overload of GetConstructor if used require the specified type to have a .ctor). + if (_methodCalling.DeclaringType == methodCalled.DeclaringType && _methodCalling.Name == methodCalled.Name) + return; + + switch (methodCalled.Name) { + // + // GetConstructor (Type []) + // GetConstructor (BindingFlags, Binder, Type [], ParameterModifier []) + // GetConstructor (BindingFlags, Binder, CallingConventions, Type [], ParameterModifier []) + // + case "GetConstructor": + if (!methodCalled.IsStatic) + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Constructor, instructionIndex - 1); + + break; + + // + // GetMethod (string) + // GetMethod (string, BindingFlags) + // GetMethod (string, Type[]) + // GetMethod (string, Type[], ParameterModifier[]) + // GetMethod (string, BindingFlags, Binder, Type[], ParameterModifier[]) + // GetMethod (string, BindingFlags, Binder, CallingConventions, Type[], ParameterModifier[]) + // + // TODO: .NET Core extensions + // 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 "GetMethod": + if (!methodCalled.IsStatic) + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Method, instructionIndex - 1); + + break; + + // + // GetField (string) + // GetField (string, BindingFlags) + // + case "GetField": + if (!methodCalled.IsStatic) + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Field, instructionIndex - 1); + + break; + + // + // GetEvent (string) + // GetEvent (string, BindingFlags) + // + case "GetEvent": + if (!methodCalled.IsStatic) + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Event, instructionIndex - 1); + + break; + + // + // GetProperty (string) + // GetProperty (string, BindingFlags) + // GetProperty (string, Type) + // GetProperty (string, Type[]) + // GetProperty (string, Type, Type[]) + // GetProperty (string, Type, Type[], ParameterModifier[]) + // GetProperty (string, BindingFlags, Binder, Type, Type[], ParameterModifier[]) + // + case "GetProperty": + if (!methodCalled.IsStatic) + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Property, instructionIndex - 1); + + break; + + // + // GetType (string) + // GetType (string, Boolean) + // GetType (string, Boolean, Boolean) + // GetType (string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>) + // GetType (string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>, Boolean) + // GetType (string, Func<AssemblyName, Assembly>, Func<Assembly, String, Boolean, Type>, Boolean, Boolean) + // + case "GetType": + if (!methodCalled.IsStatic) { + return; + } else { + var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, methodCalled.Parameters.Count); + if (first_arg_instr < 0) { + reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); + break; + } + + // + // The next value must be string constant (we don't handle anything else) + // + var first_arg = _instructions [first_arg_instr]; + if (first_arg.OpCode != OpCodes.Ldstr) { + reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with argument which cannot be analyzed"); + break; + } + + TypeDefinition foundType = _markStep.ResolveFullyQualifiedTypeName ((string)first_arg.Operand); + if (foundType == null) + break; + + reflectionContext.RecordRecognizedPattern (foundType, () => _markStep.MarkType (foundType)); + } + break; - _context.Tracer.Push ($"Reflection-{foundType}"); - try { - MarkType (foundType); - } finally { - _context.Tracer.Pop (); + default: + return; } break; - } - - break; - - // - // System.Linq.Expressions.Expression - // - case "Expression" when methodCalledType.Namespace == "System.Linq.Expressions": - Instruction second_argument; - TypeDefinition declaringType; - - if (!methodCalledDefinition.IsStatic) - break; - - switch (methodCalled.Name) { // - // static Call (Type, String, Type[], Expression[]) + // System.Linq.Expressions.Expression // - case "Call": - first_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, 4); - if (first_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); - continue; - } + case "Expression" when methodCalledType.Namespace == "System.Linq.Expressions": + Instruction second_argument; + TypeDefinition declaringType; + + if (!methodCalled.IsStatic) + break; + + switch (methodCalled.Name) { + + // + // static Call (Type, String, Type[], Expression[]) + // + case "Call": { + var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 4); + if (first_arg_instr < 0) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); + break; + } + + var first_arg = _instructions [first_arg_instr]; + if (first_arg.OpCode == OpCodes.Ldtoken) + first_arg_instr++; + + declaringType = FindReflectionTypeForLookup (_instructions, first_arg_instr); + if (declaringType == null) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 1st argument which cannot be analyzed"); + break; + } + + var second_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 3); + second_argument = _instructions [second_arg_instr]; + if (second_argument.OpCode != OpCodes.Ldstr) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 2nd argument which cannot be analyzed"); + break; + } + + var name = (string)second_argument.Operand; + + MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, name, null, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } - first_arg = instructions [first_arg_instr]; - if (first_arg.OpCode == OpCodes.Ldtoken) - first_arg_instr++; + break; + + // + // static Field (Expression, Type, String) + // static Property(Expression, Type, String) + // + case "Property": + case "Field": { + var second_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 2); + if (second_arg_instr < 0) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); + break; + } + + var second_arg = _instructions [second_arg_instr]; + if (second_arg.OpCode == OpCodes.Ldtoken) + second_arg_instr++; + + declaringType = FindReflectionTypeForLookup (_instructions, second_arg_instr); + if (declaringType == null) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 2nd argument which cannot be analyzed"); + break; + } + + var third_arg_inst = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 1); + var third_argument = _instructions [third_arg_inst]; + if (third_argument.OpCode != OpCodes.Ldstr) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with the 3rd argument which cannot be analyzed"); + break; + } + + var name = (string)third_argument.Operand; + + // + // The first argument can be any expression but we are looking only for simple null + // which we can convert to static only field lookup + // + var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 3); + bool staticOnly = false; + + if (first_arg_instr >= 0) { + var first_arg = _instructions [first_arg_instr]; + if (first_arg.OpCode == OpCodes.Ldnull) + staticOnly = true; + } + + if (methodCalled.Name [0] == 'P') + MarkPropertiesFromReflectionCall (ref reflectionContext, declaringType, name, staticOnly); + else + MarkFieldsFromReflectionCall (ref reflectionContext, declaringType, name, staticOnly); + } - declaringType = FindReflectionTypeForLookup (instructions, first_arg_instr); - if (declaringType == null) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with 1st argument which cannot be analyzed"); - continue; - } + break; + + // + // static New (Type) + // + case "New": { + var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 1); + if (first_arg_instr < 0) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); + break; + } + + var first_arg = _instructions [first_arg_instr]; + if (first_arg.OpCode == OpCodes.Ldtoken) + first_arg_instr++; + + declaringType = FindReflectionTypeForLookup (_instructions, first_arg_instr); + if (declaringType == null) { + reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 1st argument which cannot be analyzed"); + break; + } + + MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, ".ctor", 0, BindingFlags.Instance, parametersCount: 0); + } + break; - second_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, 3); - second_argument = instructions [second_arg_instr]; - if (second_argument.OpCode != OpCodes.Ldstr) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with 2nd argument which cannot be analyzed"); - continue; + default: + return; } - var name = (string)second_argument.Operand; - - MarkMethodsFromReflectionCall (declaringType, name, null, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - break; // - // static Field (Expression, Type, String) - // static Property(Expression, Type, String) + // System.Reflection.RuntimeReflectionExtensions // - case "Property": - case "Field": - - second_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, 2); - if (second_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); - continue; - } - - var second_arg = instructions [second_arg_instr]; - if (second_arg.OpCode == OpCodes.Ldtoken) - second_arg_instr++; - - declaringType = FindReflectionTypeForLookup (instructions, second_arg_instr); - if (declaringType == null) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with 2nd argument which cannot be analyzed"); - continue; - } - - var third_arg_inst = GetInstructionAtStackDepth (instructions, i - 1, 1); - var third_argument = instructions [third_arg_inst]; - if (third_argument.OpCode != OpCodes.Ldstr) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with the 3rd argument which cannot be analyzed"); - continue; - } - - name = (string)third_argument.Operand; - - // - // The first argument can be any expression but we are looking only for simple null - // which we can convert to static only field lookup - // - first_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, 3); - bool staticOnly = false; - - if (first_arg_instr >= 0) { - first_arg = instructions [first_arg_instr]; - if (first_arg.OpCode == OpCodes.Ldnull) - staticOnly = true; + case "RuntimeReflectionExtensions" when methodCalledType.Namespace == "System.Reflection": + switch (methodCalled.Name) { + // + // static GetRuntimeField (this Type type, string name) + // + case "GetRuntimeField": + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Field, instructionIndex - 1, thisExtension: true); + break; + + // + // static GetRuntimeMethod (this Type type, string name, Type[] parameters) + // + case "GetRuntimeMethod": + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Method, instructionIndex - 1, thisExtension: true); + break; + + // + // static GetRuntimeProperty (this Type type, string name) + // + case "GetRuntimeProperty": + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Property, instructionIndex - 1, thisExtension: true); + break; + + // + // static GetRuntimeEvent (this Type type, string name) + // + case "GetRuntimeEvent": + ProcessSystemTypeGetMemberLikeCall (ref reflectionContext, System.Reflection.MemberTypes.Event, instructionIndex - 1, thisExtension: true); + break; + + default: + return; } - if (methodCalled.Name [0] == 'P') - MarkPropertiesFromReflectionCall (declaringType, name, staticOnly); - else - MarkFieldsFromReflectionCall (declaringType, name, staticOnly); - break; // - // static New (Type) + // System.AppDomain // - case "New": - first_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, 1); - if (first_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); - continue; - } - - first_arg = instructions [first_arg_instr]; - if (first_arg.OpCode == OpCodes.Ldtoken) - first_arg_instr++; - - declaringType = FindReflectionTypeForLookup (instructions, first_arg_instr); - if (declaringType == null) { - _context.LogMessage (MessageImportance.Low, $"Expression call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with 1st argument which cannot be analyzed"); - continue; + case "AppDomain" when methodCalledType.Namespace == "System": + // + // CreateInstance (string assemblyName, string typeName) + // CreateInstance (string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) + // CreateInstance (string assemblyName, string typeName, object? []? activationAttributes) + // + // CreateInstanceAndUnwrap (string assemblyName, string typeName) + // CreateInstanceAndUnwrap (string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) + // CreateInstanceAndUnwrap (string assemblyName, string typeName, object? []? activationAttributes) + // + // CreateInstanceFrom (string assemblyFile, string typeName) + // CreateInstanceFrom (string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) + // CreateInstanceFrom (string assemblyFile, string typeName, object? []? activationAttributes) + // + // CreateInstanceFromAndUnwrap (string assemblyFile, string typeName) + // CreateInstanceFromAndUnwrap (string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) + // CreateInstanceFromAndUnwrap (string assemblyFile, string typeName, object? []? activationAttributes) + // + switch (methodCalled.Name) { + case "CreateInstance": + case "CreateInstanceAndUnwrap": + case "CreateInstanceFrom": + case "CreateInstanceFromAndUnwrap": + ProcessActivatorCallWithStrings (ref reflectionContext, instructionIndex - 1, methodCalled.Parameters.Count < 4); + break; + + default: + return; } - MarkMethodsFromReflectionCall (declaringType, ".ctor", 0, BindingFlags.Instance, parametersCount: 0); - break; - } - - break; - - // - // System.Reflection.RuntimeReflectionExtensions - // - case "RuntimeReflectionExtensions" when methodCalledType.Namespace == "System.Reflection": - switch (methodCalled.Name) { - // - // static GetRuntimeField (this Type type, string name) - // - case "GetRuntimeField": - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Field, methodCalledDefinition, i - 1, thisExtension: true); break; // - // static GetRuntimeMethod (this Type type, string name, Type[] parameters) + // System.Reflection.Assembly // - case "GetRuntimeMethod": - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Method, methodCalledDefinition, i - 1, thisExtension: true); - break; - - // - // static GetRuntimeProperty (this Type type, string name) - // - case "GetRuntimeProperty": - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Property, methodCalledDefinition, i - 1, thisExtension: true); - break; - - // - // static GetRuntimeEvent (this Type type, string name) - // - case "GetRuntimeEvent": - ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes.Event, methodCalledDefinition, i - 1, thisExtension: true); - break; - } - - break; - - // - // System.AppDomain - // - case "AppDomain" when methodCalledType.Namespace == "System": - // - // CreateInstance (string assemblyName, string typeName) - // CreateInstance (string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) - // CreateInstance (string assemblyName, string typeName, object? []? activationAttributes) - // - // CreateInstanceAndUnwrap (string assemblyName, string typeName) - // CreateInstanceAndUnwrap (string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) - // CreateInstanceAndUnwrap (string assemblyName, string typeName, object? []? activationAttributes) - // - // CreateInstanceFrom (string assemblyFile, string typeName) - // CreateInstanceFrom (string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) - // CreateInstanceFrom (string assemblyFile, string typeName, object? []? activationAttributes) - // - // CreateInstanceFromAndUnwrap (string assemblyFile, string typeName) - // CreateInstanceFromAndUnwrap (string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) - // CreateInstanceFromAndUnwrap (string assemblyFile, string typeName, object? []? activationAttributes) - // - switch (methodCalled.Name) { - case "CreateInstance": - case "CreateInstanceAndUnwrap": - case "CreateInstanceFrom": - case "CreateInstanceFromAndUnwrap": - ProcessActivatorCallWithStrings (methodCalledDefinition, i - 1, methodCalled.Parameters.Count < 4); - break; - } - - break; - - // - // System.Reflection.Assembly - // - case "Assembly" when methodCalledType.Namespace == "System.Reflection": - // - // CreateInstance (string typeName) - // CreateInstance (string typeName, bool ignoreCase) - // CreateInstance (string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder? binder, object []? args, CultureInfo? culture, object []? activationAttributes) - // - if (methodCalled.Name == "CreateInstance") { + case "Assembly" when methodCalledType.Namespace == "System.Reflection": // - // TODO: This could be supported for `this` only calls + // CreateInstance (string typeName) + // CreateInstance (string typeName, bool ignoreCase) + // CreateInstance (string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder? binder, object []? args, CultureInfo? culture, object []? activationAttributes) // - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalled.FullName}' inside '{body.Method.FullName}' is not yet supported"); - break; - } - - break; - - // - // System.Activator - // - case "Activator" when methodCalledType.Namespace == "System": - if (!methodCalledDefinition.IsStatic) - break; + if (methodCalled.Name == "CreateInstance") { + // + // TODO: This could be supported for `this` only calls + // + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' is not yet supported"); + break; + } else { + return; + } - switch (methodCalled.Name) { - // - // static CreateInstance (string assemblyName, string typeName) - // static CreateInstance (string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) - // static CreateInstance (string assemblyName, string typeName, object?[]? activationAttributes) // - // static CreateInstance (System.Type type) - // static CreateInstance (System.Type type, bool nonPublic) - // static CreateInstance (System.Type type, params object?[]? args) - // static CreateInstance (System.Type type, object?[]? args, object?[]? activationAttributes) - // static CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture) - // static CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) { throw null; } + // System.Activator // - case "CreateInstance": - var parameters = methodCalled.Parameters; - if (parameters.Count < 1) - continue; - - if (parameters [0].ParameterType.MetadataType == MetadataType.String) { - ProcessActivatorCallWithStrings (methodCalledDefinition, i - 1, parameters.Count < 4); - continue; - } - - first_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, methodCalledDefinition.Parameters.Count); - if (first_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalled.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); - continue; - } + case "Activator" when methodCalledType.Namespace == "System": + if (!methodCalled.IsStatic) + return; + + switch (methodCalled.Name) { + // + // static T CreateInstance<T> () + // + case "CreateInstance" when methodCalled.ContainsGenericParameter: + // Not sure it's worth implementing as we cannot expant T and simple cases can be rewritten + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' is not supported"); + break; + + // + // static CreateInstance (string assemblyName, string typeName) + // static CreateInstance (string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) + // static CreateInstance (string assemblyName, string typeName, object?[]? activationAttributes) + // + // static CreateInstance (System.Type type) + // static CreateInstance (System.Type type, bool nonPublic) + // static CreateInstance (System.Type type, params object?[]? args) + // static CreateInstance (System.Type type, object?[]? args, object?[]? activationAttributes) + // static CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture) + // static CreateInstance (System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) { throw null; } + // + case "CreateInstance": { + var parameters = methodCalled.Parameters; + if (parameters.Count < 1) + return; + + if (parameters [0].ParameterType.MetadataType == MetadataType.String) { + ProcessActivatorCallWithStrings (ref reflectionContext, instructionIndex - 1, parameters.Count < 4); + break; + } + + var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, methodCalled.Parameters.Count); + if (first_arg_instr < 0) { + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); + break; + } + + if (parameters [0].ParameterType.IsTypeOf ("System", "Type")) { + declaringType = FindReflectionTypeForLookup (_instructions, first_arg_instr + 1); + if (declaringType == null) { + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 1st argument expression which cannot be analyzed"); + break; + } + + BindingFlags bindingFlags = BindingFlags.Instance; + int? parametersCount = null; + + if (methodCalled.Parameters.Count == 1) { + parametersCount = 0; + } else { + var second_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, methodCalled.Parameters.Count - 1); + second_argument = _instructions [second_arg_instr]; + switch (second_argument.OpCode.Code) { + case Code.Ldc_I4_0 when parameters [1].ParameterType.MetadataType == MetadataType.Boolean: + parametersCount = 0; + bindingFlags |= BindingFlags.Public; + break; + case Code.Ldc_I4_1 when parameters [1].ParameterType.MetadataType == MetadataType.Boolean: + parametersCount = 0; + break; + case Code.Ldc_I4_S when parameters [1].ParameterType.IsTypeOf ("System.Reflection", "BindingFlags"): + bindingFlags = (BindingFlags)(sbyte)second_argument.Operand; + break; + } + } + + MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, ".ctor", 0, bindingFlags, parametersCount); + break; + } + } - if (parameters [0].ParameterType.IsTypeOf ("System", "Type")) { - declaringType = FindReflectionTypeForLookup (instructions, first_arg_instr + 1); - if (declaringType == null) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalled.FullName}' inside '{body.Method.FullName}' was detected with 1st argument expression which cannot be analyzed"); - continue; - } + break; - BindingFlags bindingFlags = BindingFlags.Instance; - int? parametersCount = null; - - if (methodCalledDefinition.Parameters.Count == 1) { - parametersCount = 0; - } else { - second_arg_instr = GetInstructionAtStackDepth (instructions, i - 1, methodCalledDefinition.Parameters.Count - 1); - second_argument = instructions [second_arg_instr]; - switch (second_argument.OpCode.Code) { - case Code.Ldc_I4_0 when parameters [1].ParameterType.MetadataType == MetadataType.Boolean: - parametersCount = 0; - bindingFlags |= BindingFlags.Public; - break; - case Code.Ldc_I4_1 when parameters [1].ParameterType.MetadataType == MetadataType.Boolean: - parametersCount = 0; - break; - case Code.Ldc_I4_S when parameters [1].ParameterType.IsTypeOf ("System.Reflection", "BindingFlags"): - bindingFlags = (BindingFlags)(sbyte)second_argument.Operand; - break; - } - } + // + // static CreateInstanceFrom (string assemblyFile, string typeName) + // static CreateInstanceFrom (string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) + // static CreateInstanceFrom (string assemblyFile, string typeName, object? []? activationAttributes) + // + case "CreateInstanceFrom": + ProcessActivatorCallWithStrings (ref reflectionContext, instructionIndex - 1, methodCalled.Parameters.Count < 4); + break; - MarkMethodsFromReflectionCall (declaringType, ".ctor", 0, bindingFlags, parametersCount); - continue; + default: + return; } break; - // - // static T CreateInstance<T> () - // - case "CreateInstance`1": - // Not sure it's worth implementing as we cannot expant T and simple cases can be rewritten - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalled.FullName}' inside '{body.Method.FullName}' is not supported"); - break; - - // - // static CreateInstanceFrom (string assemblyFile, string typeName) - // static CreateInstanceFrom (string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object? []? args, System.Globalization.CultureInfo? culture, object? []? activationAttributes) - // static CreateInstanceFrom (string assemblyFile, string typeName, object? []? activationAttributes) - // - case "CreateInstanceFrom": - ProcessActivatorCallWithStrings (methodCalledDefinition, i - 1, methodCalled.Parameters.Count < 4); - break; - } - break; + default: + return; } + } // // Handles static method calls in form of Create (string assemblyFile, string typeName, ......) // - void ProcessActivatorCallWithStrings (MethodDefinition methodCalledDefinition, int startIndex, bool defaultCtorOnly) + void ProcessActivatorCallWithStrings (ref ReflectionPatternContext reflectionContext, int startIndex, bool defaultCtorOnly) { - var parameters = methodCalledDefinition.Parameters; + var parameters = reflectionContext.MethodCalled.Parameters; if (parameters.Count < 2) return; if (parameters [0].ParameterType.MetadataType != MetadataType.String && parameters [1].ParameterType.MetadataType != MetadataType.String) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' is not supported"); + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' is not supported"); return; } - var first_arg_instr = GetInstructionAtStackDepth (instructions, startIndex, methodCalledDefinition.Parameters.Count); + var first_arg_instr = GetInstructionAtStackDepth (_instructions, startIndex, reflectionContext.MethodCalled.Parameters.Count); if (first_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); return; } - var first_arg = instructions [first_arg_instr]; + var first_arg = _instructions [first_arg_instr]; if (first_arg.OpCode != OpCodes.Ldstr) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' was detected with the 1st argument which cannot be analyzed"); + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with the 1st argument which cannot be analyzed"); return; } - var second_arg_instr = GetInstructionAtStackDepth (instructions, startIndex, methodCalledDefinition.Parameters.Count - 1); + var second_arg_instr = GetInstructionAtStackDepth (_instructions, startIndex, reflectionContext.MethodCalled.Parameters.Count - 1); if (second_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); return; } - var second_arg = instructions [second_arg_instr]; + var second_arg = _instructions [second_arg_instr]; if (second_arg.OpCode != OpCodes.Ldstr) { - _context.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' was detected with the 2nd argument which cannot be analyzed"); + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with the 2nd argument which cannot be analyzed"); return; } string assembly_name = (string)first_arg.Operand; - if (!_context.Resolver.AssemblyCache.TryGetValue (assembly_name, out var assembly)) { - _context.Logger.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' references assembly '{assembly_name}' which could not be found"); + if (!_markStep._context.Resolver.AssemblyCache.TryGetValue (assembly_name, out var assembly)) { + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' references assembly '{assembly_name}' which could not be found"); return; } @@ -2845,34 +2929,34 @@ namespace Mono.Linker.Steps { var declaringType = FindType (assembly, type_name); if (declaringType == null) { - _context.Logger.LogMessage (MessageImportance.Low, $"Activator call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' references type '{type_name}' which could not be found"); + reflectionContext.RecordUnrecognizedPattern ($"Activator call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' references type '{type_name}' which could not be found"); return; } - MarkMethodsFromReflectionCall (declaringType, ".ctor", 0, null, defaultCtorOnly ? 0 : (int?) null); + MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, ".ctor", 0, null, defaultCtorOnly ? 0 : (int?)null); } // // Handles instance methods called over typeof (Foo) with string name as the first argument // - void ProcessSystemTypeGetMemberLikeCall (System.Reflection.MemberTypes memberTypes, MethodDefinition methodCalledDefinition, int startIndex, bool thisExtension = false) + void ProcessSystemTypeGetMemberLikeCall (ref ReflectionPatternContext reflectionContext, System.Reflection.MemberTypes memberTypes, int startIndex, bool thisExtension = false) { - int first_instance_arg = methodCalledDefinition.Parameters.Count; + int first_instance_arg = reflectionContext.MethodCalled.Parameters.Count; if (thisExtension) --first_instance_arg; - var first_arg_instr = GetInstructionAtStackDepth (instructions, startIndex, first_instance_arg); + var first_arg_instr = GetInstructionAtStackDepth (_instructions, startIndex, first_instance_arg); if (first_arg_instr < 0) { - _context.LogMessage (MessageImportance.Low, $"Reflection call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' couldn't be decomposed"); + reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed"); return; } - var first_arg = instructions [first_arg_instr]; + var first_arg = _instructions [first_arg_instr]; BindingFlags? bindingFlags = default; string name = default; if (memberTypes == System.Reflection.MemberTypes.Constructor) { - if (first_arg.OpCode == OpCodes.Ldc_I4_S && methodCalledDefinition.Parameters.Count > 0 && methodCalledDefinition.Parameters [0].ParameterType.IsTypeOf ("System.Reflection", "BindingFlags")) { + if (first_arg.OpCode == OpCodes.Ldc_I4_S && reflectionContext.MethodCalled.Parameters.Count > 0 && reflectionContext.MethodCalled.Parameters [0].ParameterType.IsTypeOf ("System.Reflection", "BindingFlags")) { bindingFlags = (BindingFlags)(sbyte)first_arg.Operand; } } else { @@ -2880,42 +2964,126 @@ namespace Mono.Linker.Steps { // The next value must be string constant (we don't handle anything else) // if (first_arg.OpCode != OpCodes.Ldstr) { - _context.LogMessage (MessageImportance.Low, $"Reflection call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' was detected with argument which cannot be analyzed"); + reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with argument which cannot be analyzed"); return; } name = (string)first_arg.Operand; - var pos_arg = instructions [first_arg_instr + 1]; - if (pos_arg.OpCode == OpCodes.Ldc_I4_S && methodCalledDefinition.Parameters.Count > 1 && methodCalledDefinition.Parameters [1].ParameterType.IsTypeOf ("System.Reflection", "BindingFlags")) { + var pos_arg = _instructions [first_arg_instr + 1]; + if (pos_arg.OpCode == OpCodes.Ldc_I4_S && reflectionContext.MethodCalled.Parameters.Count > 1 && reflectionContext.MethodCalled.Parameters [1].ParameterType.IsTypeOf ("System.Reflection", "BindingFlags")) { bindingFlags = (BindingFlags)(sbyte)pos_arg.Operand; } } - var declaringType = FindReflectionTypeForLookup (instructions, first_arg_instr - 1); + var declaringType = FindReflectionTypeForLookup (_instructions, first_arg_instr - 1); if (declaringType == null) { - _context.LogMessage (MessageImportance.Low, $"Reflection call '{methodCalledDefinition.FullName}' inside '{body.Method.FullName}' does not use detectable instance type extraction"); + reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{reflectionContext.MethodCalled.FullName}' inside '{_methodCalling.FullName}' does not use detectable instance type extraction"); return; } switch (memberTypes) { - case System.Reflection.MemberTypes.Constructor: - MarkMethodsFromReflectionCall (declaringType, ".ctor", 0, bindingFlags); - break; - case System.Reflection.MemberTypes.Method: - MarkMethodsFromReflectionCall (declaringType, name, 0, bindingFlags); - break; - case System.Reflection.MemberTypes.Field: - MarkFieldsFromReflectionCall (declaringType, name); - break; - case System.Reflection.MemberTypes.Property: - MarkPropertiesFromReflectionCall (declaringType, name); - break; - case System.Reflection.MemberTypes.Event: - MarkEventsFromReflectionCall (declaringType, name); + case System.Reflection.MemberTypes.Constructor: + MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, ".ctor", 0, bindingFlags); + break; + case System.Reflection.MemberTypes.Method: + MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, name, 0, bindingFlags); + break; + case System.Reflection.MemberTypes.Field: + MarkFieldsFromReflectionCall (ref reflectionContext, declaringType, name); + break; + case System.Reflection.MemberTypes.Property: + MarkPropertiesFromReflectionCall (ref reflectionContext, declaringType, name); + break; + case System.Reflection.MemberTypes.Event: + MarkEventsFromReflectionCall (ref reflectionContext, declaringType, name); + break; + } + } + + // + // arity == null for name match regardless of arity + // + void MarkMethodsFromReflectionCall (ref ReflectionPatternContext reflectionContext, TypeDefinition declaringType, string name, int? arity, BindingFlags? bindingFlags, int? parametersCount = null) + { + foreach (var method in declaringType.Methods) { + var mname = method.Name; + + // Either exact match or generic method with any arity when unspecified + if (mname != name && !(arity == null && mname.StartsWith (name, StringComparison.Ordinal) && mname.Length > name.Length + 2 && mname [name.Length + 1] == '`')) { + continue; + } + + if ((bindingFlags & (BindingFlags.Instance | BindingFlags.Static)) == BindingFlags.Static && !method.IsStatic) + continue; + + if ((bindingFlags & (BindingFlags.Instance | BindingFlags.Static)) == BindingFlags.Instance && method.IsStatic) + continue; + + if ((bindingFlags & (BindingFlags.Public | BindingFlags.NonPublic)) == BindingFlags.Public && !method.IsPublic) + continue; + + if ((bindingFlags & (BindingFlags.Public | BindingFlags.NonPublic)) == BindingFlags.NonPublic && method.IsPublic) + continue; + + if (parametersCount != null && parametersCount != method.Parameters.Count) + continue; + + reflectionContext.RecordRecognizedPattern (method, () => _markStep.MarkIndirectlyCalledMethod (method)); + } + } + + void MarkPropertiesFromReflectionCall (ref ReflectionPatternContext reflectionContext, TypeDefinition declaringType, string name, bool staticOnly = false) + { + foreach (var property in declaringType.Properties) { + if (property.Name != name) + continue; + + bool markedAny = false; + + // It is not easy to reliably detect in the IL code whether the getter or setter (or both) are used. + // Be conservative and mark everything for the property. + var getter = property.GetMethod; + if (getter != null && (!staticOnly || staticOnly && getter.IsStatic)) { + reflectionContext.RecordRecognizedPattern (getter, () => _markStep.MarkIndirectlyCalledMethod (getter)); + markedAny = true; + } + + var setter = property.SetMethod; + if (setter != null && (!staticOnly || staticOnly && setter.IsStatic)) { + reflectionContext.RecordRecognizedPattern (setter, () => _markStep.MarkIndirectlyCalledMethod (setter)); + markedAny = true; + } + + if (markedAny) { + reflectionContext.RecordRecognizedPattern (property, () => _markStep.MarkProperty (property)); + } + } + } + + void MarkFieldsFromReflectionCall (ref ReflectionPatternContext reflectionContext, TypeDefinition declaringType, string name, bool staticOnly = false) + { + foreach (var field in declaringType.Fields) { + if (field.Name != name) + continue; + + if (staticOnly && !field.IsStatic) + continue; + + reflectionContext.RecordRecognizedPattern (field, () => _markStep.MarkField (field)); break; } } + + void MarkEventsFromReflectionCall (ref ReflectionPatternContext reflectionContext, TypeDefinition declaringType, string name) + { + foreach (var eventInfo in declaringType.Events) { + if (eventInfo.Name != name) + continue; + + reflectionContext.RecordRecognizedPattern (eventInfo, () => _markStep.MarkEvent (eventInfo)); + } + } } static int GetInstructionAtStackDepth (Collection<Instruction> instructions, int startIndex, int stackSizeToBacktrace) @@ -3086,111 +3254,6 @@ namespace Mono.Linker.Steps { return -1; } - // - // arity == null for name match regardless of arity - // - void MarkMethodsFromReflectionCall (TypeDefinition declaringType, string name, int? arity, BindingFlags? bindingFlags, int? parametersCount = null) - { - foreach (var method in declaringType.Methods) { - var mname = method.Name; - - // Either exact match or generic method with any arity when unspecified - if (mname != name && !(arity == null && mname.StartsWith (name, StringComparison.Ordinal) && mname.Length > name.Length + 2 && mname [name.Length + 1] == '`')) { - continue; - } - - if ((bindingFlags & (BindingFlags.Instance | BindingFlags.Static)) == BindingFlags.Static && !method.IsStatic) - continue; - - if ((bindingFlags & (BindingFlags.Instance | BindingFlags.Static)) == BindingFlags.Instance && method.IsStatic) - continue; - - if ((bindingFlags & (BindingFlags.Public | BindingFlags.NonPublic)) == BindingFlags.Public && !method.IsPublic) - continue; - - if ((bindingFlags & (BindingFlags.Public | BindingFlags.NonPublic)) == BindingFlags.NonPublic && method.IsPublic) - continue; - - if (parametersCount != null && parametersCount != method.Parameters.Count) - continue; - - Tracer.Push ($"Reflection-{method}"); - try { - MarkIndirectlyCalledMethod (method); - } finally { - Tracer.Pop (); - } - } - } - - void MarkPropertiesFromReflectionCall (TypeDefinition declaringType, string name, bool staticOnly = false) - { - foreach (var property in declaringType.Properties) { - if (property.Name != name) - continue; - - Tracer.Push ($"Reflection-{property}"); - try { - bool markedAny = false; - - // It is not easy to reliably detect in the IL code whether the getter or setter (or both) are used. - // Be conservative and mark everything for the property. - var getter = property.GetMethod; - if (getter != null && (!staticOnly || staticOnly && getter.IsStatic)) { - MarkIndirectlyCalledMethod (getter); - markedAny = true; - } - - var setter = property.SetMethod; - if (setter != null && (!staticOnly || staticOnly && setter.IsStatic)) { - MarkIndirectlyCalledMethod (setter); - markedAny = true; - } - - if (markedAny) - MarkProperty (property); - - } finally { - Tracer.Pop (); - } - } - } - - void MarkFieldsFromReflectionCall (TypeDefinition declaringType, string name, bool staticOnly = false) - { - foreach (var field in declaringType.Fields) { - if (field.Name != name) - continue; - - if (staticOnly && !field.IsStatic) - continue; - - Tracer.Push ($"Reflection-{field}"); - try { - MarkField (field); - } finally { - Tracer.Pop (); - } - - break; - } - } - - void MarkEventsFromReflectionCall (TypeDefinition declaringType, string name) - { - foreach (var eventInfo in declaringType.Events) { - if (eventInfo.Name != name) - continue; - - Tracer.Push ($"Reflection-{eventInfo}"); - try { - MarkEvent (eventInfo); - } finally { - Tracer.Pop (); - } - } - } - protected class AttributeProviderPair { public AttributeProviderPair (CustomAttribute attribute, ICustomAttributeProvider provider) { diff --git a/src/linker/Linker/IReflectionPatternRecorder.cs b/src/linker/Linker/IReflectionPatternRecorder.cs new file mode 100644 index 000000000..b3aae2b2e --- /dev/null +++ b/src/linker/Linker/IReflectionPatternRecorder.cs @@ -0,0 +1,62 @@ +// +// IReflectionPatternRecorder.cs +// +// Copyright (C) 2017 Microsoft Corporation (http://www.microsoft.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using Mono.Cecil; + +namespace Mono.Linker +{ + /// <summary> + /// Interface which is called every time the linker inspects a pattern of code involving reflection to determine a more complex + /// dependency. + /// </summary> + /// <remarks> + /// The rules are such that if a given callsite of a "reflectionMethod" gets examined + /// linker will always report it one way or another: + /// - it will either call RecognizedReflectionAccessPattern method when it can figure out exactly the dependency. + /// - or it will call UnrecognizedReflectionAccessPattern with an optional message describing why it could not recognize + /// the pattern. + /// </remarks> + public interface IReflectionPatternRecorder + { + /// <summary> + /// Called when the linker recognized a reflection access pattern (and thus was able to correctly apply marking to the accessed item). + /// </summary> + /// <param name="sourceMethod">The method which contains the reflection access pattern.</param> + /// <param name="reflectionMethod">The reflection method which is at the heart of the access pattern.</param> + /// <param name="accessedItem">The item accessed through reflection. This can be one of: + /// TypeDefinition, MethodDefinition, PropertyDefinition, FieldDefinition, EventDefinition.</param> + void RecognizedReflectionAccessPattern (MethodDefinition sourceMethod, MethodDefinition reflectionMethod, IMemberDefinition accessedItem); + + /// <summary> + /// Called when the linker detected a reflection access but was not able to recognize the entire pattern. + /// </summary> + /// <param name="sourceMethod">The method which contains the reflection access code.</param> + /// <param name="reflectionMethod">The reflection method which is at the heart of the access code.</param> + /// <param name="message">Humanly readable message describing what failed during the pattern recognition.</param> + /// <remarks>This effectively means that there's a potential hole in the linker marking - some items which are accessed only through + /// reflection may not be marked correctly and thus may fail at runtime.</remarks> + void UnrecognizedReflectionAccessPattern (MethodDefinition sourceMethod, MethodDefinition reflectionMethod, string message); + } +} diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 4c8c5613c..949038dde 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -151,7 +151,9 @@ namespace Mono.Linker { public Tracer Tracer { get; private set; } - public string[] ExcludedFeatures { get; set; } + public IReflectionPatternRecorder ReflectionPatternRecorder { get; set; } + + public string [] ExcludedFeatures { get; set; } public CodeOptimizations DisabledOptimizations { get; set; } @@ -189,6 +191,7 @@ namespace Mono.Linker { _annotations = factory.CreateAnnotationStore (this); MarkingHelpers = factory.CreateMarkingHelpers (this); Tracer = factory.CreateTracer (this); + ReflectionPatternRecorder = new LoggingReflectionPatternRecorder (this); MarkedKnownMembers = new KnownMembers (); StripResources = true; diff --git a/src/linker/Linker/LoggingReflectionPatternRecorder.cs b/src/linker/Linker/LoggingReflectionPatternRecorder.cs new file mode 100644 index 000000000..f9c0d89e9 --- /dev/null +++ b/src/linker/Linker/LoggingReflectionPatternRecorder.cs @@ -0,0 +1,49 @@ +// +// LoggingReflectionPatternRecorder.cs +// +// Copyright (C) 2017 Microsoft Corporation (http://www.microsoft.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using Mono.Cecil; + +namespace Mono.Linker +{ + class LoggingReflectionPatternRecorder : IReflectionPatternRecorder + { + private readonly LinkContext _context; + + public LoggingReflectionPatternRecorder (LinkContext context) + { + _context = context; + } + + public void RecognizedReflectionAccessPattern (MethodDefinition sourceMethod, MethodDefinition reflectionMethod, IMemberDefinition accessedItem) + { + // Do nothing - there's no logging for successfully recognized patterns + } + + public void UnrecognizedReflectionAccessPattern (MethodDefinition sourceMethod, MethodDefinition reflectionMethod, string message) + { + _context.LogMessage (MessageImportance.Low, message); + } + } +} 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 + }); + } + } +} |