Skip to content

Commit c1018b8

Browse files
authored
Merge pull request #252 from sunnamed434/reflection-usage-analysis
Improve reflection usage analysis
2 parents d32e458 + 1627d1b commit c1018b8

7 files changed

Lines changed: 832 additions & 41 deletions

File tree

src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs

Lines changed: 246 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,49 @@ public class ReflectionCriticalAnalyzer : ICriticalAnalyzer<MethodDefinition>
55
{
66
private readonly ObfuscationSettings _obfuscationSettings;
77
private readonly List<MethodDefinition> _cachedMethods;
8+
private readonly List<FieldDefinition> _cachedFields;
9+
private readonly List<PropertyDefinition> _cachedProperties;
10+
private readonly List<EventDefinition> _cachedEvents;
11+
private readonly List<TypeDefinition> _cachedTypes;
812
private static readonly string[] ReflectionMethods =
913
[
1014
nameof(Type.GetMethod),
1115
nameof(Type.GetField),
1216
nameof(Type.GetProperty),
1317
nameof(Type.GetEvent),
14-
nameof(Type.GetMember)
18+
nameof(Type.GetMember),
19+
nameof(Type.GetTypeFromHandle)
1520
];
1621

1722
public ReflectionCriticalAnalyzer(IOptions<ObfuscationSettings> obfuscation)
1823
{
1924
_obfuscationSettings = obfuscation.Value;
2025
_cachedMethods = [];
26+
_cachedFields = [];
27+
_cachedProperties = [];
28+
_cachedEvents = [];
29+
_cachedTypes = [];
2130
}
2231

2332
public IReadOnlyList<MethodDefinition> CachedMethods => _cachedMethods.AsReadOnly();
33+
public IReadOnlyList<FieldDefinition> CachedFields => _cachedFields.AsReadOnly();
34+
public IReadOnlyList<PropertyDefinition> CachedProperties => _cachedProperties.AsReadOnly();
35+
public IReadOnlyList<EventDefinition> CachedEvents => _cachedEvents.AsReadOnly();
36+
public IReadOnlyList<TypeDefinition> CachedTypes => _cachedTypes.AsReadOnly();
2437

2538
public bool NotCriticalToMakeChanges(MethodDefinition method)
2639
{
27-
if (_obfuscationSettings.ReflectionMembersObfuscationExclude == false)
40+
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
2841
{
2942
return true;
3043
}
3144
if (_cachedMethods.FirstOrDefault(x => x.Name.Equals(method.Name)) != null)
3245
{
3346
return false;
3447
}
48+
49+
bool foundReflection = false;
50+
3551
if (method.CilMethodBody is { } body)
3652
{
3753
body.ConstructSymbolicFlowGraph(out var dataFlowGraph);
@@ -46,40 +62,257 @@ public bool NotCriticalToMakeChanges(MethodDefinition method)
4662
{
4763
if (IsReflection(calledMethod))
4864
{
49-
var traceArgument = TraceLdstrArgument(body, instruction);
50-
if (traceArgument?.Operand is string traceMethodName)
65+
var traceArgument = TraceStringArgument(body, instruction);
66+
if (traceArgument?.Operand is string memberName)
5167
{
52-
foreach (var possibleMethod in method.DeclaringModule
53-
.FindMembers()
54-
.OfType<MethodDefinition>()
55-
.Where(x => x.Name.Equals(traceMethodName)))
68+
var module = method.DeclaringModule;
69+
var allMembers = module.FindMembers();
70+
71+
switch (calledMethod.Name.Value)
5672
{
57-
_cachedMethods.Add(possibleMethod);
58-
return false;
73+
case nameof(Type.GetMethod):
74+
foreach (var possibleMethod in allMembers.OfType<MethodDefinition>()
75+
.Where(x => x.Name.Equals(memberName)))
76+
{
77+
if (possibleMethod == method && !_cachedMethods.Contains(possibleMethod))
78+
{
79+
_cachedMethods.Add(possibleMethod);
80+
foundReflection = true;
81+
}
82+
}
83+
break;
84+
85+
case nameof(Type.GetField):
86+
foreach (var possibleField in allMembers.OfType<FieldDefinition>()
87+
.Where(x => x.Name.Equals(memberName)))
88+
{
89+
_cachedFields.Add(possibleField);
90+
foundReflection = true;
91+
}
92+
break;
93+
94+
case nameof(Type.GetProperty):
95+
foreach (var possibleProperty in allMembers.OfType<PropertyDefinition>()
96+
.Where(x => x.Name.Equals(memberName)))
97+
{
98+
_cachedProperties.Add(possibleProperty);
99+
foundReflection = true;
100+
}
101+
break;
102+
103+
case nameof(Type.GetEvent):
104+
foreach (var possibleEvent in allMembers.OfType<EventDefinition>()
105+
.Where(x => x.Name.Equals(memberName)))
106+
{
107+
_cachedEvents.Add(possibleEvent);
108+
foundReflection = true;
109+
}
110+
break;
111+
112+
case nameof(Type.GetMember):
113+
foreach (var possibleMember in allMembers)
114+
{
115+
string? memberNameToCheck = possibleMember switch
116+
{
117+
MethodDefinition m => m.Name,
118+
FieldDefinition f => f.Name,
119+
PropertyDefinition p => p.Name,
120+
EventDefinition e => e.Name,
121+
TypeDefinition t => t.Name,
122+
_ => null
123+
};
124+
125+
if (memberNameToCheck != null && memberNameToCheck.Equals(memberName))
126+
{
127+
switch (possibleMember)
128+
{
129+
case MethodDefinition m:
130+
_cachedMethods.Add(m);
131+
break;
132+
case FieldDefinition f:
133+
_cachedFields.Add(f);
134+
break;
135+
case PropertyDefinition p:
136+
_cachedProperties.Add(p);
137+
break;
138+
case EventDefinition e:
139+
_cachedEvents.Add(e);
140+
break;
141+
case TypeDefinition t:
142+
_cachedTypes.Add(t);
143+
break;
144+
}
145+
foundReflection = true;
146+
}
147+
}
148+
break;
59149
}
60150
}
61151
}
152+
else if (IsTypeGetTypeFromHandle(calledMethod))
153+
{
154+
var typeFromHandle = TraceTypeFromHandle(body, instruction);
155+
if (typeFromHandle != null)
156+
{
157+
_cachedTypes.Add(typeFromHandle);
158+
foundReflection = true;
159+
}
160+
}
161+
}
162+
else if (instruction?.OpCode == CilOpCodes.Ldtoken && instruction.Operand is ITypeDefOrRef typeRef)
163+
{
164+
if (typeRef.Resolve() is TypeDefinition typeDef)
165+
{
166+
_cachedTypes.Add(typeDef);
167+
foundReflection = true;
168+
}
62169
}
63170
}
64171
}
65172
}
66-
return true;
173+
174+
return !foundReflection;
175+
}
176+
177+
public bool NotCriticalToMakeChanges(FieldDefinition field)
178+
{
179+
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
180+
{
181+
return true;
182+
}
183+
return _cachedFields.FirstOrDefault(x => x.Name.Equals(field.Name)) != null;
184+
}
185+
186+
public bool NotCriticalToMakeChanges(PropertyDefinition property)
187+
{
188+
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
189+
{
190+
return true;
191+
}
192+
return _cachedProperties.FirstOrDefault(x => x.Name.Equals(property.Name)) != null;
193+
}
194+
195+
public bool NotCriticalToMakeChanges(EventDefinition eventDef)
196+
{
197+
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
198+
{
199+
return true;
200+
}
201+
return _cachedEvents.FirstOrDefault(x => x.Name.Equals(eventDef.Name)) != null;
202+
}
203+
204+
public bool NotCriticalToMakeChanges(TypeDefinition type)
205+
{
206+
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
207+
{
208+
return true;
209+
}
210+
return _cachedTypes.FirstOrDefault(x => x.Name.Equals(type.Name)) != null;
67211
}
68212

69213
private static bool IsReflection(IMethodDefOrRef calledMethod)
70214
{
71215
return calledMethod.DeclaringType.IsSystemType() &&
72216
ReflectionMethods.Contains(calledMethod.Name.Value);
73217
}
74-
private static CilInstruction? TraceLdstrArgument(CilMethodBody body, CilInstruction instruction)
218+
219+
private static bool IsTypeGetTypeFromHandle(IMethodDefOrRef calledMethod)
75220
{
76-
for (var i = body.Instructions.IndexOf(instruction); i > 0 && body.Instructions.Count.IsLess(i) == false; i--)
221+
return calledMethod.DeclaringType.IsSystemType() &&
222+
calledMethod.Name.Value == nameof(Type.GetTypeFromHandle);
223+
}
224+
225+
private static TypeDefinition? TraceTypeFromHandle(CilMethodBody body, CilInstruction instruction)
226+
{
227+
var callIndex = body.Instructions.IndexOf(instruction);
228+
if (callIndex <= 0) return null;
229+
230+
for (var i = callIndex - 1; i >= 0; i--)
231+
{
232+
var prevInstruction = body.Instructions[i];
233+
if (prevInstruction.OpCode == CilOpCodes.Ldtoken && prevInstruction.Operand is ITypeDefOrRef typeRef)
234+
{
235+
return typeRef.Resolve();
236+
}
237+
}
238+
return null;
239+
}
240+
private static CilInstruction? TraceStringArgument(CilMethodBody body, CilInstruction instruction)
241+
{
242+
return TraceStringArgumentSimple(body, instruction);
243+
}
244+
245+
private static CilInstruction? TraceStringArgumentSimple(CilMethodBody body, CilInstruction instruction)
246+
{
247+
var callIndex = body.Instructions.IndexOf(instruction);
248+
if (callIndex <= 0) return null;
249+
250+
for (var i = callIndex - 1; i >= 0; i--)
77251
{
78252
var previousInstruction = body.Instructions[i];
253+
79254
if (previousInstruction.OpCode == CilOpCodes.Ldstr)
80255
{
81256
return previousInstruction;
82257
}
258+
259+
if (previousInstruction.OpCode == CilOpCodes.Ldloc_0 ||
260+
previousInstruction.OpCode == CilOpCodes.Ldloc_1 ||
261+
previousInstruction.OpCode == CilOpCodes.Ldloc_2 ||
262+
previousInstruction.OpCode == CilOpCodes.Ldloc_3 ||
263+
previousInstruction.OpCode == CilOpCodes.Ldloc_S ||
264+
previousInstruction.OpCode == CilOpCodes.Ldloc)
265+
{
266+
var variableIndex = GetVariableIndex(previousInstruction);
267+
if (variableIndex >= 0)
268+
{
269+
var stringInstruction = FindStringAssignmentToVariable(body, variableIndex, i);
270+
if (stringInstruction != null)
271+
{
272+
return stringInstruction;
273+
}
274+
}
275+
}
276+
}
277+
return null;
278+
}
279+
280+
private static int GetVariableIndex(CilInstruction instruction)
281+
{
282+
return instruction.OpCode.Code switch
283+
{
284+
CilCode.Ldloc_0 => 0,
285+
CilCode.Ldloc_1 => 1,
286+
CilCode.Ldloc_2 => 2,
287+
CilCode.Ldloc_3 => 3,
288+
CilCode.Ldloc_S => ((CilLocalVariable)instruction.Operand!).Index,
289+
CilCode.Ldloc => ((CilLocalVariable)instruction.Operand!).Index,
290+
_ => -1
291+
};
292+
}
293+
294+
private static CilInstruction? FindStringAssignmentToVariable(CilMethodBody body, int variableIndex, int maxIndex)
295+
{
296+
for (var i = 0; i < maxIndex; i++)
297+
{
298+
var instruction = body.Instructions[i];
299+
300+
if ((instruction.OpCode == CilOpCodes.Stloc_0 && variableIndex == 0) ||
301+
(instruction.OpCode == CilOpCodes.Stloc_1 && variableIndex == 1) ||
302+
(instruction.OpCode == CilOpCodes.Stloc_2 && variableIndex == 2) ||
303+
(instruction.OpCode == CilOpCodes.Stloc_3 && variableIndex == 3) ||
304+
(instruction.OpCode == CilOpCodes.Stloc_S && ((CilLocalVariable)instruction.Operand!).Index == variableIndex) ||
305+
(instruction.OpCode == CilOpCodes.Stloc && ((CilLocalVariable)instruction.Operand!).Index == variableIndex))
306+
{
307+
for (var j = i - 1; j >= 0; j--)
308+
{
309+
var prevInstruction = body.Instructions[j];
310+
if (prevInstruction.OpCode == CilOpCodes.Ldstr)
311+
{
312+
return prevInstruction;
313+
}
314+
}
315+
}
83316
}
84317
return null;
85318
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
namespace BitMono.Core.Attributes;
22

3+
/// <summary>
4+
/// Flags that specify which types of members should be excluded from resolution (obfuscation).
5+
/// Used with the <see cref="DoNotResolveAttribute"/> to control which members are protected from obfuscation.
6+
/// </summary>
37
[Flags]
48
public enum MemberInclusionFlags
59
{
10+
/// <summary>
11+
/// Exclude special runtime members from obfuscation (e.g., special methods, properties, or fields used by the runtime).
12+
/// </summary>
613
SpecialRuntime = 0x1,
14+
15+
/// <summary>
16+
/// Exclude model members from obfuscation (e.g., data models, DTOs, or entities that should preserve their structure).
17+
/// </summary>
718
Model = 0x2,
19+
20+
/// <summary>
21+
/// Exclude members that are used in reflection from obfuscation (e.g., methods, fields, properties accessed via reflection).
22+
/// </summary>
823
Reflection = 0x4,
924
}

src/BitMono.Core/GlobalUsings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
global using BitMono.Core.Resolvers;
3131
global using BitMono.Core.Services;
3232
global using BitMono.Utilities.AsmResolver;
33+
global using Echo.DataFlow;
3334
global using Echo.DataFlow.Analysis;
3435
global using Echo.Platforms.AsmResolver;
3536
global using JetBrains.Annotations;

0 commit comments

Comments
 (0)