Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/linker/Linker.Steps/MarkStep.cs1107
-rw-r--r--src/linker/Linker/IReflectionPatternRecorder.cs62
-rw-r--r--src/linker/Linker/LinkContext.cs5
-rw-r--r--src/linker/Linker/LoggingReflectionPatternRecorder.cs49
-rw-r--r--test/Mono.Linker.Tests.Cases.Expectations/Assertions/RecognizedReflectionAccessPatternAttribute.cs24
-rw-r--r--test/Mono.Linker.Tests.Cases.Expectations/Assertions/UnrecognizedReflectionAccessPatternAttribute.cs22
-rw-r--r--test/Mono.Linker.Tests.Cases/Reflection/ActivatorCreateInstance.cs3
-rw-r--r--test/Mono.Linker.Tests.Cases/Reflection/MethodUsedViaReflection.cs7
-rw-r--r--test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs5
-rw-r--r--test/Mono.Linker.Tests/Extensions/CecilExtensions.cs30
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/LinkerCustomizations.cs2
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs125
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs9
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/TestReflectionPatternRecorder.cs37
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
+ });
+ }
+ }
+}