Skip to content

Commit af0e701

Browse files
committed
Deep reflection usage analysis and fix ObfuscationAttribute
1 parent 7a13867 commit af0e701

File tree

15 files changed

+2552
-291
lines changed

15 files changed

+2552
-291
lines changed

src/BitMono.Core/Analyzing/ArgumentTracer.cs

Lines changed: 564 additions & 0 deletions
Large diffs are not rendered by default.

src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs

Lines changed: 566 additions & 114 deletions
Large diffs are not rendered by default.

src/BitMono.Core/Resolvers/MembersResolver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ public static class MembersResolver
55
public static IEnumerable<IMetadataMember> Resolve(IProtection protection, IEnumerable<IMetadataMember> members,
66
IEnumerable<IMemberResolver> resolvers)
77
{
8-
return members.Where(member => CanBeResolved(protection, member, resolvers));
8+
return members.Where(x => CanBeResolved(protection, x, resolvers));
99
}
1010
private static bool CanBeResolved(IProtection protection, IMetadataMember member,
1111
IEnumerable<IMemberResolver> resolvers)
1212
{
13-
return resolvers.All(m => m.Resolve(protection, member));
13+
return resolvers.All(x => x.Resolve(protection, member));
1414
}
1515
}

src/BitMono.Core/Resolvers/ObfuscationAttributeResolver.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ public ObfuscationAttributeResolver(IOptions<ObfuscationSettings> configuration)
1616
public override bool Resolve(string? featureName, IHasCustomAttribute from, [NotNullWhen(true)] out IReadOnlyList<ObfuscationAttributeData>? model)
1717
{
1818
model = null;
19-
if (_obfuscationSettings.ObfuscationAttributeObfuscationExclude == false)
19+
if (!_obfuscationSettings.ObfuscationAttributeObfuscationExclude)
2020
{
2121
return false;
2222
}
23-
if (AttemptAttributeResolver.TryResolve(from, _attributeNamespace, _attributeName,
24-
out var attributesResolve) == false)
23+
if (!AttemptAttributeResolver.TryResolve(from, _attributeNamespace, _attributeName,
24+
out var attributesResolve))
2525
{
2626
return false;
2727
}
@@ -34,20 +34,36 @@ public override bool Resolve(string? featureName, IHasCustomAttribute from, [Not
3434
{
3535
continue;
3636
}
37-
if (namedValues.TryGetTypedValue(nameof(ObfuscationAttribute.Feature), out string? feature) == false)
37+
38+
var hasFeature = namedValues.TryGetTypedValue(nameof(ObfuscationAttribute.Feature), out string? featureValue);
39+
var feature = hasFeature ? featureValue : "all";
40+
41+
if (hasFeature)
3842
{
39-
continue;
43+
if (!string.Equals(feature, featureName, StringComparison.OrdinalIgnoreCase))
44+
{
45+
continue;
46+
}
4047
}
41-
if (feature.Equals(featureName, StringComparison.OrdinalIgnoreCase) == false)
48+
else
4249
{
43-
continue;
50+
if (string.IsNullOrEmpty(featureName))
51+
{
52+
continue;
53+
}
4454
}
4555

4656
var exclude =
4757
namedValues.GetValueOrDefault(nameof(ObfuscationAttribute.Exclude),
4858
defaultValue: true);
59+
60+
if (!exclude)
61+
{
62+
continue;
63+
}
64+
4965
var applyToMembers =
50-
namedValues.GetValueOrDefault(nameof(ObfuscationAttribute.Exclude),
66+
namedValues.GetValueOrDefault(nameof(ObfuscationAttribute.ApplyToMembers),
5167
defaultValue: true);
5268
var stripAfterObfuscation =
5369
namedValues.GetValueOrDefault(nameof(ObfuscationAttribute.StripAfterObfuscation),
@@ -62,6 +78,12 @@ public override bool Resolve(string? featureName, IHasCustomAttribute from, [Not
6278
CustomAttribute = attribute.Attribute
6379
});
6480
}
81+
82+
if (attributes.Count == 0)
83+
{
84+
return false;
85+
}
86+
6587
model = attributes;
6688
return true;
6789
}

src/BitMono.Utilities/AsmResolver/TypeDefinitionExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,19 @@ public static bool HasNamespace(this TypeDefinition source)
1010
{
1111
return Utf8String.IsNullOrEmpty(source.Namespace) == false;
1212
}
13+
14+
/// <summary>
15+
/// Gets the type and all its base types in the inheritance hierarchy.
16+
/// </summary>
17+
/// <param name="source">The type to get the hierarchy for.</param>
18+
/// <returns>An enumerable of the type and all its base types.</returns>
19+
public static IEnumerable<TypeDefinition> GetTypeAndBaseTypes(this TypeDefinition source)
20+
{
21+
var current = source;
22+
while (current != null)
23+
{
24+
yield return current;
25+
current = current.BaseType?.Resolve();
26+
}
27+
}
1328
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
namespace BitMono.Core.Tests.Analyzing;
2+
3+
public class ArgumentTracerTest
4+
{
5+
private static ArgumentTracer CreateTracer()
6+
{
7+
return new ArgumentTracer();
8+
}
9+
10+
private static (ModuleDefinition module, TypeDefinition type) GetTestData()
11+
{
12+
var module = ModuleDefinition.FromFile(typeof(ArgumentTracerTestCases).Assembly.Location);
13+
var type = module.TopLevelTypes.First(t => t.Name == nameof(ArgumentTracerTestCases));
14+
return (module, type);
15+
}
16+
17+
[Fact]
18+
public void ShouldTraceDirectStringArgument()
19+
{
20+
var (_, type) = GetTestData();
21+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.DirectStringArgument));
22+
var tracer = CreateTracer();
23+
24+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
25+
i.OpCode.Code == CilCode.Call &&
26+
i.Operand is IMethodDescriptor md &&
27+
md.Name == "GetMethod");
28+
callInstruction.Should().NotBeNull();
29+
30+
var result = tracer.TraceStringArgument(method.CilMethodBody!, callInstruction!, 0);
31+
32+
result.Should().Be("TestMethod");
33+
}
34+
35+
[Fact]
36+
public void ShouldTraceVariableStringArgument()
37+
{
38+
var (_, type) = GetTestData();
39+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.VariableStringArgument));
40+
var tracer = CreateTracer();
41+
42+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
43+
i.OpCode.Code == CilCode.Call &&
44+
i.Operand is IMethodDescriptor md &&
45+
md.Name == "GetMethod");
46+
callInstruction.Should().NotBeNull();
47+
48+
var result = tracer.TraceStringArgument(method.CilMethodBody!, callInstruction!, 0);
49+
50+
result.Should().Be("TestMethod");
51+
}
52+
53+
[Fact]
54+
public void ShouldTraceMultipleArguments()
55+
{
56+
var (_, type) = GetTestData();
57+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.MultipleArguments));
58+
var tracer = CreateTracer();
59+
60+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
61+
i.OpCode.Code == CilCode.Call &&
62+
i.Operand is IMethodDescriptor md &&
63+
md.Name == "GetMethod");
64+
callInstruction.Should().NotBeNull();
65+
66+
var argumentIndices = tracer.TraceArguments(method.CilMethodBody!, callInstruction!);
67+
68+
argumentIndices.Should().NotBeNull();
69+
argumentIndices.Should().HaveCount(2);
70+
}
71+
72+
[Fact]
73+
public void ShouldTraceTypeArgument()
74+
{
75+
var (_, type) = GetTestData();
76+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.TypeArgument));
77+
var tracer = CreateTracer();
78+
79+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
80+
i.OpCode.Code == CilCode.Call &&
81+
i.Operand is IMethodDescriptor md &&
82+
md.Name == "GetTypeFromHandle");
83+
callInstruction.Should().NotBeNull();
84+
85+
var result = tracer.TraceTypeArgument(method.CilMethodBody!, callInstruction!, 0);
86+
87+
result.Should().NotBeNull();
88+
result!.Name.Value.Should().Be(nameof(ArgumentTracerTestCases));
89+
}
90+
91+
[Fact]
92+
public void ShouldTraceComplexVariableChain()
93+
{
94+
var (_, type) = GetTestData();
95+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.ComplexVariableChain));
96+
var tracer = CreateTracer();
97+
98+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
99+
i.OpCode.Code == CilCode.Call &&
100+
i.Operand is IMethodDescriptor md &&
101+
md.Name == "GetMethod");
102+
callInstruction.Should().NotBeNull();
103+
104+
var result = tracer.TraceStringArgument(method.CilMethodBody!, callInstruction!, 0);
105+
106+
result.Should().Be("ComplexTest");
107+
}
108+
109+
[Fact]
110+
public void ShouldReturnNullForInvalidCallInstruction()
111+
{
112+
var (_, type) = GetTestData();
113+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.DirectStringArgument));
114+
var tracer = CreateTracer();
115+
116+
var nonCallInstruction = method.CilMethodBody!.Instructions.First(i => i.OpCode != CilOpCodes.Call);
117+
var result = tracer.TraceArguments(method.CilMethodBody!, nonCallInstruction);
118+
119+
result.Should().BeNull();
120+
}
121+
122+
[Fact]
123+
public void ShouldReturnNullForInvalidArgumentIndex()
124+
{
125+
var (_, type) = GetTestData();
126+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.DirectStringArgument));
127+
var tracer = CreateTracer();
128+
129+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
130+
i.OpCode.Code == CilCode.Call &&
131+
i.Operand is IMethodDescriptor md &&
132+
md.Name == "GetMethod");
133+
callInstruction.Should().NotBeNull();
134+
135+
var result = tracer.TraceStringArgument(method.CilMethodBody!, callInstruction!, 10);
136+
137+
result.Should().BeNull();
138+
}
139+
140+
[Fact]
141+
public void ShouldHandleNoArguments()
142+
{
143+
var (_, type) = GetTestData();
144+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.NoArguments));
145+
var tracer = CreateTracer();
146+
147+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
148+
i.OpCode.Code == CilCode.Call &&
149+
i.Operand is IMethodDescriptor md &&
150+
md.Name == "GetType");
151+
callInstruction.Should().NotBeNull();
152+
153+
var result = tracer.TraceArguments(method.CilMethodBody!, callInstruction!);
154+
155+
result.Should().NotBeNull();
156+
result.Should().BeEmpty();
157+
}
158+
159+
[Fact]
160+
public void ShouldTraceArgumentsThroughMethodCall()
161+
{
162+
var (_, type) = GetTestData();
163+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.ArgumentsThroughMethodCall));
164+
var tracer = CreateTracer();
165+
166+
var callInstruction = method.CilMethodBody!.Instructions.FirstOrDefault(i =>
167+
i.OpCode.Code == CilCode.Call &&
168+
i.Operand is IMethodDescriptor md &&
169+
md.Name == "GetMethod");
170+
callInstruction.Should().NotBeNull();
171+
172+
var result = tracer.TraceStringArgument(method.CilMethodBody!, callInstruction!, 0);
173+
174+
result.Should().Be("MethodCallTest");
175+
}
176+
177+
[Fact]
178+
public void ShouldHandleNullMethodBody()
179+
{
180+
var tracer = CreateTracer();
181+
var dummyInstruction = new CilInstruction(CilOpCodes.Call, null);
182+
183+
var result = tracer.TraceArguments(null!, dummyInstruction);
184+
185+
result.Should().BeNull();
186+
}
187+
188+
[Fact]
189+
public void ShouldHandleNullCallInstruction()
190+
{
191+
var (_, type) = GetTestData();
192+
var method = type.Methods.First(m => m.Name == nameof(ArgumentTracerTestCases.DirectStringArgument));
193+
var tracer = CreateTracer();
194+
195+
var result = tracer.TraceArguments(method.CilMethodBody!, null!);
196+
197+
result.Should().BeNull();
198+
}
199+
}
200+
201+
/// <summary>
202+
/// Test cases for ArgumentTracer functionality
203+
/// </summary>
204+
public class ArgumentTracerTestCases
205+
{
206+
public void DirectStringArgument()
207+
{
208+
_ = typeof(ArgumentTracerTestCases).GetMethod("TestMethod");
209+
}
210+
211+
public void VariableStringArgument()
212+
{
213+
string methodName = "TestMethod";
214+
_ = typeof(ArgumentTracerTestCases).GetMethod(methodName);
215+
}
216+
217+
public void MultipleArguments()
218+
{
219+
_ = typeof(ArgumentTracerTestCases).GetMethod("TestMethod", BindingFlags.Public | BindingFlags.Instance);
220+
}
221+
222+
public void TypeArgument()
223+
{
224+
_ = Type.GetTypeFromHandle(typeof(ArgumentTracerTestCases).TypeHandle);
225+
}
226+
227+
public void ComplexVariableChain()
228+
{
229+
string baseName = "Complex";
230+
string suffix = "Test";
231+
string methodName = baseName + suffix;
232+
_ = typeof(ArgumentTracerTestCases).GetMethod(methodName);
233+
}
234+
235+
public void NoArguments()
236+
{
237+
_ = typeof(ArgumentTracerTestCases).GetType();
238+
}
239+
240+
public void ArgumentsThroughMethodCall()
241+
{
242+
string methodName = GetMethodName();
243+
_ = typeof(ArgumentTracerTestCases).GetMethod(methodName);
244+
}
245+
246+
private static string GetMethodName()
247+
{
248+
return "MethodCallTest";
249+
}
250+
}

0 commit comments

Comments
 (0)