diff --git a/src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs b/src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs index 4df9d827..ee9021fd 100644 --- a/src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs +++ b/src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs @@ -5,26 +5,39 @@ public class ReflectionCriticalAnalyzer : ICriticalAnalyzer { private readonly ObfuscationSettings _obfuscationSettings; private readonly List _cachedMethods; + private readonly List _cachedFields; + private readonly List _cachedProperties; + private readonly List _cachedEvents; + private readonly List _cachedTypes; private static readonly string[] ReflectionMethods = [ nameof(Type.GetMethod), nameof(Type.GetField), nameof(Type.GetProperty), nameof(Type.GetEvent), - nameof(Type.GetMember) + nameof(Type.GetMember), + nameof(Type.GetTypeFromHandle) ]; public ReflectionCriticalAnalyzer(IOptions obfuscation) { _obfuscationSettings = obfuscation.Value; _cachedMethods = []; + _cachedFields = []; + _cachedProperties = []; + _cachedEvents = []; + _cachedTypes = []; } public IReadOnlyList CachedMethods => _cachedMethods.AsReadOnly(); + public IReadOnlyList CachedFields => _cachedFields.AsReadOnly(); + public IReadOnlyList CachedProperties => _cachedProperties.AsReadOnly(); + public IReadOnlyList CachedEvents => _cachedEvents.AsReadOnly(); + public IReadOnlyList CachedTypes => _cachedTypes.AsReadOnly(); public bool NotCriticalToMakeChanges(MethodDefinition method) { - if (_obfuscationSettings.ReflectionMembersObfuscationExclude == false) + if (!_obfuscationSettings.ReflectionMembersObfuscationExclude) { return true; } @@ -32,6 +45,9 @@ public bool NotCriticalToMakeChanges(MethodDefinition method) { return false; } + + bool foundReflection = false; + if (method.CilMethodBody is { } body) { body.ConstructSymbolicFlowGraph(out var dataFlowGraph); @@ -46,24 +62,152 @@ public bool NotCriticalToMakeChanges(MethodDefinition method) { if (IsReflection(calledMethod)) { - var traceArgument = TraceLdstrArgument(body, instruction); - if (traceArgument?.Operand is string traceMethodName) + var traceArgument = TraceStringArgument(body, instruction); + if (traceArgument?.Operand is string memberName) { - foreach (var possibleMethod in method.DeclaringModule - .FindMembers() - .OfType() - .Where(x => x.Name.Equals(traceMethodName))) + var module = method.DeclaringModule; + var allMembers = module.FindMembers(); + + switch (calledMethod.Name.Value) { - _cachedMethods.Add(possibleMethod); - return false; + case nameof(Type.GetMethod): + foreach (var possibleMethod in allMembers.OfType() + .Where(x => x.Name.Equals(memberName))) + { + if (possibleMethod == method && !_cachedMethods.Contains(possibleMethod)) + { + _cachedMethods.Add(possibleMethod); + foundReflection = true; + } + } + break; + + case nameof(Type.GetField): + foreach (var possibleField in allMembers.OfType() + .Where(x => x.Name.Equals(memberName))) + { + _cachedFields.Add(possibleField); + foundReflection = true; + } + break; + + case nameof(Type.GetProperty): + foreach (var possibleProperty in allMembers.OfType() + .Where(x => x.Name.Equals(memberName))) + { + _cachedProperties.Add(possibleProperty); + foundReflection = true; + } + break; + + case nameof(Type.GetEvent): + foreach (var possibleEvent in allMembers.OfType() + .Where(x => x.Name.Equals(memberName))) + { + _cachedEvents.Add(possibleEvent); + foundReflection = true; + } + break; + + case nameof(Type.GetMember): + foreach (var possibleMember in allMembers) + { + string? memberNameToCheck = possibleMember switch + { + MethodDefinition m => m.Name, + FieldDefinition f => f.Name, + PropertyDefinition p => p.Name, + EventDefinition e => e.Name, + TypeDefinition t => t.Name, + _ => null + }; + + if (memberNameToCheck != null && memberNameToCheck.Equals(memberName)) + { + switch (possibleMember) + { + case MethodDefinition m: + _cachedMethods.Add(m); + break; + case FieldDefinition f: + _cachedFields.Add(f); + break; + case PropertyDefinition p: + _cachedProperties.Add(p); + break; + case EventDefinition e: + _cachedEvents.Add(e); + break; + case TypeDefinition t: + _cachedTypes.Add(t); + break; + } + foundReflection = true; + } + } + break; } } } + else if (IsTypeGetTypeFromHandle(calledMethod)) + { + var typeFromHandle = TraceTypeFromHandle(body, instruction); + if (typeFromHandle != null) + { + _cachedTypes.Add(typeFromHandle); + foundReflection = true; + } + } + } + else if (instruction?.OpCode == CilOpCodes.Ldtoken && instruction.Operand is ITypeDefOrRef typeRef) + { + if (typeRef.Resolve() is TypeDefinition typeDef) + { + _cachedTypes.Add(typeDef); + foundReflection = true; + } } } } } - return true; + + return !foundReflection; + } + + public bool NotCriticalToMakeChanges(FieldDefinition field) + { + if (!_obfuscationSettings.ReflectionMembersObfuscationExclude) + { + return true; + } + return _cachedFields.FirstOrDefault(x => x.Name.Equals(field.Name)) != null; + } + + public bool NotCriticalToMakeChanges(PropertyDefinition property) + { + if (!_obfuscationSettings.ReflectionMembersObfuscationExclude) + { + return true; + } + return _cachedProperties.FirstOrDefault(x => x.Name.Equals(property.Name)) != null; + } + + public bool NotCriticalToMakeChanges(EventDefinition eventDef) + { + if (!_obfuscationSettings.ReflectionMembersObfuscationExclude) + { + return true; + } + return _cachedEvents.FirstOrDefault(x => x.Name.Equals(eventDef.Name)) != null; + } + + public bool NotCriticalToMakeChanges(TypeDefinition type) + { + if (!_obfuscationSettings.ReflectionMembersObfuscationExclude) + { + return true; + } + return _cachedTypes.FirstOrDefault(x => x.Name.Equals(type.Name)) != null; } private static bool IsReflection(IMethodDefOrRef calledMethod) @@ -71,15 +215,104 @@ private static bool IsReflection(IMethodDefOrRef calledMethod) return calledMethod.DeclaringType.IsSystemType() && ReflectionMethods.Contains(calledMethod.Name.Value); } - private static CilInstruction? TraceLdstrArgument(CilMethodBody body, CilInstruction instruction) + + private static bool IsTypeGetTypeFromHandle(IMethodDefOrRef calledMethod) { - for (var i = body.Instructions.IndexOf(instruction); i > 0 && body.Instructions.Count.IsLess(i) == false; i--) + return calledMethod.DeclaringType.IsSystemType() && + calledMethod.Name.Value == nameof(Type.GetTypeFromHandle); + } + + private static TypeDefinition? TraceTypeFromHandle(CilMethodBody body, CilInstruction instruction) + { + var callIndex = body.Instructions.IndexOf(instruction); + if (callIndex <= 0) return null; + + for (var i = callIndex - 1; i >= 0; i--) + { + var prevInstruction = body.Instructions[i]; + if (prevInstruction.OpCode == CilOpCodes.Ldtoken && prevInstruction.Operand is ITypeDefOrRef typeRef) + { + return typeRef.Resolve(); + } + } + return null; + } + private static CilInstruction? TraceStringArgument(CilMethodBody body, CilInstruction instruction) + { + return TraceStringArgumentSimple(body, instruction); + } + + private static CilInstruction? TraceStringArgumentSimple(CilMethodBody body, CilInstruction instruction) + { + var callIndex = body.Instructions.IndexOf(instruction); + if (callIndex <= 0) return null; + + for (var i = callIndex - 1; i >= 0; i--) { var previousInstruction = body.Instructions[i]; + if (previousInstruction.OpCode == CilOpCodes.Ldstr) { return previousInstruction; } + + if (previousInstruction.OpCode == CilOpCodes.Ldloc_0 || + previousInstruction.OpCode == CilOpCodes.Ldloc_1 || + previousInstruction.OpCode == CilOpCodes.Ldloc_2 || + previousInstruction.OpCode == CilOpCodes.Ldloc_3 || + previousInstruction.OpCode == CilOpCodes.Ldloc_S || + previousInstruction.OpCode == CilOpCodes.Ldloc) + { + var variableIndex = GetVariableIndex(previousInstruction); + if (variableIndex >= 0) + { + var stringInstruction = FindStringAssignmentToVariable(body, variableIndex, i); + if (stringInstruction != null) + { + return stringInstruction; + } + } + } + } + return null; + } + + private static int GetVariableIndex(CilInstruction instruction) + { + return instruction.OpCode.Code switch + { + CilCode.Ldloc_0 => 0, + CilCode.Ldloc_1 => 1, + CilCode.Ldloc_2 => 2, + CilCode.Ldloc_3 => 3, + CilCode.Ldloc_S => ((CilLocalVariable)instruction.Operand!).Index, + CilCode.Ldloc => ((CilLocalVariable)instruction.Operand!).Index, + _ => -1 + }; + } + + private static CilInstruction? FindStringAssignmentToVariable(CilMethodBody body, int variableIndex, int maxIndex) + { + for (var i = 0; i < maxIndex; i++) + { + var instruction = body.Instructions[i]; + + if ((instruction.OpCode == CilOpCodes.Stloc_0 && variableIndex == 0) || + (instruction.OpCode == CilOpCodes.Stloc_1 && variableIndex == 1) || + (instruction.OpCode == CilOpCodes.Stloc_2 && variableIndex == 2) || + (instruction.OpCode == CilOpCodes.Stloc_3 && variableIndex == 3) || + (instruction.OpCode == CilOpCodes.Stloc_S && ((CilLocalVariable)instruction.Operand!).Index == variableIndex) || + (instruction.OpCode == CilOpCodes.Stloc && ((CilLocalVariable)instruction.Operand!).Index == variableIndex)) + { + for (var j = i - 1; j >= 0; j--) + { + var prevInstruction = body.Instructions[j]; + if (prevInstruction.OpCode == CilOpCodes.Ldstr) + { + return prevInstruction; + } + } + } } return null; } diff --git a/src/BitMono.Core/Attributes/MemberInclusionFlags.cs b/src/BitMono.Core/Attributes/MemberInclusionFlags.cs index bcd8ecea..0c69c8b7 100644 --- a/src/BitMono.Core/Attributes/MemberInclusionFlags.cs +++ b/src/BitMono.Core/Attributes/MemberInclusionFlags.cs @@ -1,9 +1,24 @@ namespace BitMono.Core.Attributes; +/// +/// Flags that specify which types of members should be excluded from resolution (obfuscation). +/// Used with the to control which members are protected from obfuscation. +/// [Flags] public enum MemberInclusionFlags { + /// + /// Exclude special runtime members from obfuscation (e.g., special methods, properties, or fields used by the runtime). + /// SpecialRuntime = 0x1, + + /// + /// Exclude model members from obfuscation (e.g., data models, DTOs, or entities that should preserve their structure). + /// Model = 0x2, + + /// + /// Exclude members that are used in reflection from obfuscation (e.g., methods, fields, properties accessed via reflection). + /// Reflection = 0x4, } \ No newline at end of file diff --git a/src/BitMono.Core/GlobalUsings.cs b/src/BitMono.Core/GlobalUsings.cs index f60ff857..cae1f8c2 100644 --- a/src/BitMono.Core/GlobalUsings.cs +++ b/src/BitMono.Core/GlobalUsings.cs @@ -30,6 +30,7 @@ global using BitMono.Core.Resolvers; global using BitMono.Core.Services; global using BitMono.Utilities.AsmResolver; +global using Echo.DataFlow; global using Echo.DataFlow.Analysis; global using Echo.Platforms.AsmResolver; global using JetBrains.Annotations; diff --git a/src/BitMono.Core/Resolvers/DoNotResolveMemberResolver.cs b/src/BitMono.Core/Resolvers/DoNotResolveMemberResolver.cs index 5e05d91c..72d86429 100644 --- a/src/BitMono.Core/Resolvers/DoNotResolveMemberResolver.cs +++ b/src/BitMono.Core/Resolvers/DoNotResolveMemberResolver.cs @@ -18,13 +18,13 @@ public DoNotResolveMemberResolver( public bool Resolve(IProtection protection, IMetadataMember member) { - if (protection.TryGetDoNotResolveAttribute(out var doNotResolveAttribute) == false) + if (!protection.TryGetDoNotResolveAttribute(out var doNotResolveAttribute)) { return true; } if (doNotResolveAttribute!.MemberInclusion.HasFlag(MemberInclusionFlags.SpecialRuntime)) { - if (_runtimeCriticalAnalyzer.NotCriticalToMakeChanges(member) == false) + if (!_runtimeCriticalAnalyzer.NotCriticalToMakeChanges(member)) { return false; } @@ -33,20 +33,46 @@ public bool Resolve(IProtection protection, IMetadataMember member) { if (doNotResolveAttribute.MemberInclusion.HasFlag(MemberInclusionFlags.Model)) { - if (_modelAttributeCriticalAnalyzer.NotCriticalToMakeChanges(customAttribute) == false) + if (!_modelAttributeCriticalAnalyzer.NotCriticalToMakeChanges(customAttribute)) { return false; } } } - if (member is MethodDefinition method) + if (doNotResolveAttribute.MemberInclusion.HasFlag(MemberInclusionFlags.Reflection)) { - if (doNotResolveAttribute.MemberInclusion.HasFlag(MemberInclusionFlags.Reflection)) + switch (member) { - if (_reflectionCriticalAnalyzer.NotCriticalToMakeChanges(method) == false) - { - return false; - } + case MethodDefinition method: + if (!_reflectionCriticalAnalyzer.NotCriticalToMakeChanges(method)) + { + return false; + } + break; + case FieldDefinition field: + if (!_reflectionCriticalAnalyzer.NotCriticalToMakeChanges(field)) + { + return false; + } + break; + case PropertyDefinition property: + if (!_reflectionCriticalAnalyzer.NotCriticalToMakeChanges(property)) + { + return false; + } + break; + case EventDefinition eventDef: + if (!_reflectionCriticalAnalyzer.NotCriticalToMakeChanges(eventDef)) + { + return false; + } + break; + case TypeDefinition type: + if (!_reflectionCriticalAnalyzer.NotCriticalToMakeChanges(type)) + { + return false; + } + break; } } return true; diff --git a/test/BitMono.Core.Tests/Analyzing/ReflectionCriticalAnalyzerTest.cs b/test/BitMono.Core.Tests/Analyzing/ReflectionCriticalAnalyzerTest.cs index afc656cb..4a8e1e43 100644 --- a/test/BitMono.Core.Tests/Analyzing/ReflectionCriticalAnalyzerTest.cs +++ b/test/BitMono.Core.Tests/Analyzing/ReflectionCriticalAnalyzerTest.cs @@ -3,7 +3,7 @@ namespace BitMono.Core.Tests.Analyzing; public class ReflectionCriticalAnalyzerTest { [Fact] - public void WhenReflectionCriticalAnalyzing_AndMethodUsesReflectionOfItSelf_ThenShouldBeFalse() + public void ShouldDetectSelfReflection() { var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); @@ -16,13 +16,11 @@ public void WhenReflectionCriticalAnalyzing_AndMethodUsesReflectionOfItSelf_Then var result = criticalAnalyzer.NotCriticalToMakeChanges(method); - result - .Should() - .BeFalse(); + result.Should().BeFalse(); } + [Fact] - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - public void WhenReflectionCriticalAnalyzing_AndMethodUses2DifferentReflectionAnd1OnItSelf_ThenShouldBeFalseAndCountOfCachedMethodsShouldBe1AndMethodNameShouldBeEqualToSelf() + public void ShouldDetectMultipleReflectionCalls() { var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); @@ -35,14 +33,346 @@ public void WhenReflectionCriticalAnalyzing_AndMethodUses2DifferentReflectionAnd var result = criticalAnalyzer.NotCriticalToMakeChanges(method); - result - .Should() - .BeFalse(); - criticalAnalyzer.CachedMethods.Count - .Should() - .Be(1); - criticalAnalyzer.CachedMethods.First().Name.Value - .Should() - .Be(method.Name); + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectFieldReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesFieldReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectPrivateFieldReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesPrivateFieldReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectPropertyReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesPropertyReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectReadOnlyPropertyReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesReadOnlyPropertyReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectEventReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesEventReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectGetMemberForMethod() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesGetMemberForMethod)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectGetMemberForField() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesGetMemberForField)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectGetMemberForProperty() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesGetMemberForProperty)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectGetMemberForEvent() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesGetMemberForEvent)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectVariableForMethodReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesVariableForMethodReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectVariableForFieldReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesVariableForFieldReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectVariableForPropertyReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesVariableForPropertyReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectVariableForEventReflection() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesVariableForEventReflection)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectTypeGetTypeFromHandle() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesTypeGetTypeFromHandle)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectLdtokenForType() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesLdtokenForType)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeTrue(); + } + + [Fact] + public void ShouldDetectMultipleReflectionTypes() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesAllReflectionTypes)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldDetectReflectionWithBindingFlags() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesReflectionWithBindingFlags)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); + } + + [Fact] + public void ShouldNotDetectReflectionWhenDisabled() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesReflectionOnItSelf)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = false + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeTrue(); + } + + [Fact] + public void ShouldNotDetectReflectionInNonReflectionMethod() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.VoidMethod)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeTrue(); + } + + [Fact] + public void ShouldDetectComplexReflectionPatterns() + { + var module = ModuleDefinition.FromFile(typeof(ReflectionMethods).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(ReflectionMethods)); + var method = type.Methods.First(m => m.Name == nameof(ReflectionMethods.UsesComplexReflectionPatterns)); + var obfuscation = new ObfuscationSettings + { + ReflectionMembersObfuscationExclude = true + }; + var criticalAnalyzer = new ReflectionCriticalAnalyzer(Options.Create(obfuscation)); + + var result = criticalAnalyzer.NotCriticalToMakeChanges(method); + + result.Should().BeFalse(); } } \ No newline at end of file diff --git a/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/GlobalUsings.cs b/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/GlobalUsings.cs index dbea1a41..b8d24636 100644 --- a/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/GlobalUsings.cs +++ b/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/GlobalUsings.cs @@ -1 +1,3 @@ +global using System; +global using System.Collections.Generic; global using System.Reflection; \ No newline at end of file diff --git a/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/ReflectionMethods.cs b/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/ReflectionMethods.cs index 25d579c4..e1f907c1 100644 --- a/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/ReflectionMethods.cs +++ b/test/TestBinaries/DotNet/BitMono.Core.TestCases.Reflection/ReflectionMethods.cs @@ -2,17 +2,201 @@ namespace BitMono.Core.TestCases.Reflection; public class ReflectionMethods { + public string TestField = "test"; + private int _privateField = 42; + public string TestProperty { get; set; } = "test"; + public int ReadOnlyProperty { get; } = 123; + public event EventHandler TestEvent; + public static void VoidMethod(string text) { } + public void UsesReflectionOnItSelf() { - typeof(ReflectionMethods).GetMethod(nameof(UsesReflectionOnItSelf)); + _ = typeof(ReflectionMethods).GetMethod(nameof(UsesReflectionOnItSelf)); } + public void Uses3Reflection() { - typeof(ReflectionMethods).GetMethod(nameof(Uses3Reflection)); - typeof(ReflectionMethods).GetMethod(nameof(UsesReflectionOnItSelf)); - typeof(ReflectionMethods).GetMethod(nameof(VoidMethod), BindingFlags.Public | BindingFlags.Static); + _ = typeof(ReflectionMethods).GetMethod(nameof(Uses3Reflection)); + _ = typeof(ReflectionMethods).GetMethod(nameof(UsesReflectionOnItSelf)); + _ = typeof(ReflectionMethods).GetMethod(nameof(VoidMethod), BindingFlags.Public | BindingFlags.Static); + } + + public void UsesFieldReflection() + { + _ = typeof(ReflectionMethods).GetField(nameof(TestField)); + } + + public void UsesPrivateFieldReflection() + { + _ = typeof(ReflectionMethods).GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance); + } + + public void UsesPropertyReflection() + { + _ = typeof(ReflectionMethods).GetProperty(nameof(TestProperty)); + } + + public void UsesReadOnlyPropertyReflection() + { + _ = typeof(ReflectionMethods).GetProperty(nameof(ReadOnlyProperty)); + } + + public void UsesEventReflection() + { + _ = typeof(ReflectionMethods).GetEvent(nameof(TestEvent)); + } + + public void UsesGetMemberForMethod() + { + _ = typeof(ReflectionMethods).GetMember(nameof(UsesFieldReflection)); + } + + public void UsesGetMemberForField() + { + _ = typeof(ReflectionMethods).GetMember(nameof(TestField)); + } + + public void UsesGetMemberForProperty() + { + _ = typeof(ReflectionMethods).GetMember(nameof(TestProperty)); + } + + public void UsesGetMemberForEvent() + { + _ = typeof(ReflectionMethods).GetMember(nameof(TestEvent)); + } + + public void UsesVariableForMethodReflection() + { + string methodName = nameof(UsesFieldReflection); + _ = typeof(ReflectionMethods).GetMethod(methodName); + } + + public void UsesVariableForFieldReflection() + { + string fieldName = nameof(TestField); + _ = typeof(ReflectionMethods).GetField(fieldName); + } + + public void UsesVariableForPropertyReflection() + { + string propertyName = nameof(TestProperty); + _ = typeof(ReflectionMethods).GetProperty(propertyName); + } + + public void UsesVariableForEventReflection() + { + string eventName = nameof(TestEvent); + _ = typeof(ReflectionMethods).GetEvent(eventName); + } + + public void UsesTypeGetTypeFromHandle() + { + var typeHandle = typeof(ReflectionMethods).TypeHandle; + _ = Type.GetTypeFromHandle(typeHandle); + } + + public void UsesLdtokenForType() + { + _ = typeof(ReflectionMethods); + } + + public void UsesMultipleReflectionTypes() + { + _ = typeof(ReflectionMethods).GetMethod(nameof(UsesFieldReflection)); + _ = typeof(ReflectionMethods).GetField(nameof(TestField)); + _ = typeof(ReflectionMethods).GetProperty(nameof(TestProperty)); + _ = typeof(ReflectionMethods).GetEvent(nameof(TestEvent)); + } + + public void UsesReflectionWithBindingFlags() + { + _ = typeof(ReflectionMethods).GetMethod(nameof(UsesFieldReflection), BindingFlags.Public | BindingFlags.Instance); + _ = typeof(ReflectionMethods).GetField(nameof(TestField), BindingFlags.Public | BindingFlags.Instance); + _ = typeof(ReflectionMethods).GetProperty(nameof(TestProperty), BindingFlags.Public | BindingFlags.Instance); + _ = typeof(ReflectionMethods).GetEvent(nameof(TestEvent), BindingFlags.Public | BindingFlags.Instance); + } + + public void UsesAllReflectionTypes() + { + _ = typeof(ReflectionMethods).GetMethod(nameof(UsesFieldReflection)); + _ = typeof(ReflectionMethods).GetField(nameof(TestField)); + _ = typeof(ReflectionMethods).GetProperty(nameof(TestProperty)); + _ = typeof(ReflectionMethods).GetEvent(nameof(TestEvent)); + _ = typeof(ReflectionMethods).GetMember(nameof(UsesFieldReflection)); + var typeHandle = typeof(ReflectionMethods).TypeHandle; + _ = Type.GetTypeFromHandle(typeHandle); + } + + public void UsesComplexReflectionPatterns() + { + var type = typeof(ReflectionMethods); + + var methodName = nameof(UsesFieldReflection); + var fieldName = nameof(TestField); + var propertyName = nameof(TestProperty); + var eventName = nameof(TestEvent); + + _ = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetMethod(methodName, new[] { typeof(string) }); + _ = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + + _ = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + _ = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Static); + + _ = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance); + _ = type.GetProperty(propertyName, new[] { typeof(string) }); + + _ = type.GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetEvent(eventName, BindingFlags.NonPublic | BindingFlags.Instance); + + _ = type.GetMember(methodName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetMember(fieldName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetMember(propertyName, BindingFlags.Public | BindingFlags.Instance); + _ = type.GetMember(eventName, BindingFlags.Public | BindingFlags.Instance); + + _ = type.GetMembers(BindingFlags.Public | BindingFlags.Instance); + _ = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); + _ = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + _ = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + _ = type.GetEvents(BindingFlags.Public | BindingFlags.Instance); + + var typeHandle = type.TypeHandle; + _ = Type.GetTypeFromHandle(typeHandle); + + var assembly = type.Assembly; + _ = assembly.GetType("BitMono.Core.TestCases.Reflection.ReflectionMethods"); + + var baseType = type.BaseType; + if (baseType != null) + { + _ = baseType.GetMethod("ToString"); + _ = baseType.GetProperty("Name"); + } + + var interfaces = type.GetInterfaces(); + foreach (var iface in interfaces) + { + _ = iface.GetMethod("GetHashCode"); + } + + var genericType = typeof(List<>); + _ = genericType.GetMethod("Add"); + _ = genericType.GetProperty("Count"); + + var constructedType = typeof(List); + _ = constructedType.GetMethod("Add"); + _ = constructedType.GetProperty("Count"); + + var nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); + foreach (var nestedType in nestedTypes) + { + _ = nestedType.GetMethod("ToString"); + } } } \ No newline at end of file