Skip to content

Commit 6bf3332

Browse files
refactor: replace separate method body scanning methods with single-pass ScanMethodBody
Signed-off-by: Alexander Linne <alexander.linne@tngtech.com>
1 parent 277dd42 commit 6bf3332

2 files changed

Lines changed: 151 additions & 133 deletions

File tree

ArchUnitNET/Loader/LoadTasks/AddMethodDependencies.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,16 @@ MethodMember methodMember
165165
);
166166
}
167167

168-
bodyTypes.AddRange(methodDefinition.GetBodyTypes(_domainResolver).ToList());
168+
var scan = methodDefinition.ScanMethodBody(_domainResolver);
169+
bodyTypes.AddRange(scan.BodyTypes);
169170

170-
var castTypes = methodDefinition.GetCastTypes(_domainResolver).ToList();
171+
var castTypes = scan.CastTypes;
171172

172-
var typeCheckTypes = methodDefinition.GetTypeCheckTypes(_domainResolver).ToList();
173+
var typeCheckTypes = scan.TypeCheckTypes;
173174

174-
var metaDataTypes = methodDefinition.GetMetaDataTypes(_domainResolver).ToList();
175+
var metaDataTypes = scan.MetaDataTypes;
175176

176-
var accessedFieldMembers = methodDefinition
177-
.GetAccessedFieldMembers(_domainResolver)
178-
.ToList();
177+
var accessedFieldMembers = scan.AccessedFieldMembers;
179178

180179
var calledMethodMembers = CreateMethodBodyDependenciesRecursive(
181180
methodBody,
@@ -302,13 +301,12 @@ var calledMethodReference in calledMethodReferences.Except(visitedMethodReferenc
302301
);
303302
}
304303

305-
bodyTypes.AddRange(calledMethodDefinition.GetBodyTypes(_domainResolver));
306-
castTypes.AddRange(calledMethodDefinition.GetCastTypes(_domainResolver));
307-
typeCheckTypes.AddRange(calledMethodDefinition.GetTypeCheckTypes(_domainResolver));
308-
metaDataTypes.AddRange(calledMethodDefinition.GetMetaDataTypes(_domainResolver));
309-
accessedFieldMembers.AddRange(
310-
calledMethodDefinition.GetAccessedFieldMembers(_domainResolver)
311-
);
304+
var calledScan = calledMethodDefinition.ScanMethodBody(_domainResolver);
305+
bodyTypes.AddRange(calledScan.BodyTypes);
306+
castTypes.AddRange(calledScan.CastTypes);
307+
typeCheckTypes.AddRange(calledScan.TypeCheckTypes);
308+
metaDataTypes.AddRange(calledScan.MetaDataTypes);
309+
accessedFieldMembers.AddRange(calledScan.AccessedFieldMembers);
312310

313311
foreach (
314312
var dep in CreateMethodBodyDependenciesRecursive(

ArchUnitNET/Loader/MonoCecilMemberExtensions.cs

Lines changed: 139 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Linq;
44
using System.Text;
55
using ArchUnitNET.Domain;
6-
using ArchUnitNET.Domain.Exceptions;
76
using ArchUnitNET.Domain.Extensions;
87
using JetBrains.Annotations;
98
using Mono.Cecil;
@@ -117,94 +116,161 @@ DomainResolver domainResolver
117116
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference
118117
);
119118

119+
/// <summary>
120+
/// Result of a single-pass scan of a method body.
121+
/// </summary>
122+
internal struct MethodBodyScanResult
123+
{
124+
internal readonly List<ITypeInstance<IType>> BodyTypes;
125+
internal readonly List<ITypeInstance<IType>> CastTypes;
126+
internal readonly List<ITypeInstance<IType>> MetaDataTypes;
127+
internal readonly List<ITypeInstance<IType>> TypeCheckTypes;
128+
internal readonly List<FieldMember> AccessedFieldMembers;
129+
130+
internal MethodBodyScanResult(
131+
List<ITypeInstance<IType>> bodyTypes,
132+
List<ITypeInstance<IType>> castTypes,
133+
List<ITypeInstance<IType>> metaDataTypes,
134+
List<ITypeInstance<IType>> typeCheckTypes,
135+
List<FieldMember> accessedFieldMembers
136+
)
137+
{
138+
BodyTypes = bodyTypes;
139+
CastTypes = castTypes;
140+
MetaDataTypes = metaDataTypes;
141+
TypeCheckTypes = typeCheckTypes;
142+
AccessedFieldMembers = accessedFieldMembers;
143+
}
144+
}
145+
146+
/// <summary>
147+
/// Scans the method body in a single pass, collecting body types, cast types,
148+
/// metadata types, type-check types, accessed field members, and local variable types.
149+
/// </summary>
120150
[NotNull]
121-
internal static IEnumerable<ITypeInstance<IType>> GetBodyTypes(
151+
internal static MethodBodyScanResult ScanMethodBody(
122152
this MethodDefinition methodDefinition,
123153
DomainResolver domainResolver
124154
)
125155
{
126-
var instructions =
127-
methodDefinition.Body?.Instructions ?? Enumerable.Empty<Instruction>();
156+
var bodyTypes = new List<ITypeInstance<IType>>();
157+
var castTypes = new List<ITypeInstance<IType>>();
158+
var metaDataTypes = new List<ITypeInstance<IType>>();
159+
var typeCheckTypes = new List<ITypeInstance<IType>>();
160+
var accessedFieldMembers = new List<FieldMember>();
128161

129-
var bodyTypes = instructions
130-
.Where(inst =>
131-
BodyTypeOpCodes.Contains(inst.OpCode) && inst.Operand is TypeReference
132-
)
133-
.Select(inst =>
134-
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
135-
(TypeReference)inst.Operand
136-
)
162+
var body = methodDefinition.Body;
163+
if (body != null)
164+
{
165+
// Collect local variable types
166+
var seenBodyTypes = new HashSet<ITypeInstance<IType>>();
167+
bodyTypes.AddRange(
168+
body.Variables.Select(variableDefinition =>
169+
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
170+
variableDefinition.VariableType
171+
)
172+
)
173+
.Where(typeInstance => seenBodyTypes.Add(typeInstance))
137174
);
138175

139-
//OpCodes.Ldstr should create a dependency to string, but it does not have a TypeReference as Operand so no Type can be created
176+
// Single pass over instructions
177+
var seenFieldRefs = new HashSet<FieldReference>(
178+
FieldReferenceNameComparer.Instance
179+
);
180+
foreach (var instruction in body.Instructions)
181+
{
182+
var opCode = instruction.OpCode;
183+
var operand = instruction.Operand;
140184

141-
bodyTypes = bodyTypes
142-
.Union(
143-
methodDefinition.Body?.Variables.Select(variableDefinition =>
185+
switch (operand)
144186
{
145-
var variableTypeReference = variableDefinition.VariableType;
146-
return domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
147-
variableTypeReference
148-
);
149-
}) ?? Enumerable.Empty<TypeInstance<IType>>()
150-
)
151-
.Distinct();
187+
case TypeReference typeReference when opCode == OpCodes.Castclass:
188+
castTypes.Add(
189+
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
190+
typeReference
191+
)
192+
);
193+
break;
194+
case TypeReference typeReference when opCode == OpCodes.Ldtoken:
195+
metaDataTypes.Add(
196+
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
197+
typeReference
198+
)
199+
);
200+
break;
201+
case TypeReference typeReference when opCode == OpCodes.Isinst:
202+
typeCheckTypes.Add(
203+
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
204+
typeReference
205+
)
206+
);
207+
break;
208+
case TypeReference typeReference:
209+
{
210+
if (BodyTypeOpCodes.Contains(opCode))
211+
{
212+
var bodyTypeInstance =
213+
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
214+
typeReference
215+
);
216+
if (seenBodyTypes.Add(bodyTypeInstance))
217+
{
218+
bodyTypes.Add(bodyTypeInstance);
219+
}
220+
}
221+
222+
break;
223+
}
224+
case FieldReference fieldReference when !seenFieldRefs.Add(fieldReference):
225+
continue;
226+
case FieldReference fieldReference:
227+
{
228+
var declaringType =
229+
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
230+
fieldReference.DeclaringType
231+
);
232+
accessedFieldMembers.Add(
233+
domainResolver.GetOrCreateFieldMember(
234+
declaringType.Type,
235+
fieldReference
236+
)
237+
);
238+
break;
239+
}
240+
}
241+
}
242+
}
152243

153-
return bodyTypes;
244+
return new MethodBodyScanResult(
245+
bodyTypes,
246+
castTypes,
247+
metaDataTypes,
248+
typeCheckTypes,
249+
accessedFieldMembers
250+
);
154251
}
155252

156-
[NotNull]
157-
internal static IEnumerable<ITypeInstance<IType>> GetCastTypes(
158-
this MethodDefinition methodDefinition,
159-
DomainResolver domainResolver
160-
)
253+
/// <summary>
254+
/// Comparer that deduplicates FieldReference by full name, avoiding repeated field lookups.
255+
/// </summary>
256+
private sealed class FieldReferenceNameComparer : IEqualityComparer<FieldReference>
161257
{
162-
var instructions =
163-
methodDefinition.Body?.Instructions ?? Enumerable.Empty<Instruction>();
164-
165-
return instructions
166-
.Where(inst => inst.OpCode == OpCodes.Castclass && inst.Operand is TypeReference)
167-
.Select(inst =>
168-
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
169-
(TypeReference)inst.Operand
170-
)
171-
);
172-
}
258+
internal static readonly FieldReferenceNameComparer Instance =
259+
new FieldReferenceNameComparer();
173260

174-
[NotNull]
175-
internal static IEnumerable<ITypeInstance<IType>> GetMetaDataTypes(
176-
this MethodDefinition methodDefinition,
177-
DomainResolver domainResolver
178-
)
179-
{
180-
var instructions =
181-
methodDefinition.Body?.Instructions ?? Enumerable.Empty<Instruction>();
182-
183-
return instructions
184-
.Where(inst => inst.OpCode == OpCodes.Ldtoken && inst.Operand is TypeReference)
185-
.Select(inst =>
186-
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
187-
(TypeReference)inst.Operand
188-
)
189-
);
190-
}
261+
public bool Equals(FieldReference x, FieldReference y)
262+
{
263+
if (x == null && y == null)
264+
return true;
265+
if (x == null || y == null)
266+
return false;
267+
return x.FullName == y.FullName;
268+
}
191269

192-
[NotNull]
193-
internal static IEnumerable<ITypeInstance<IType>> GetTypeCheckTypes(
194-
this MethodDefinition methodDefinition,
195-
DomainResolver domainResolver
196-
)
197-
{
198-
var instructions =
199-
methodDefinition.Body?.Instructions ?? Enumerable.Empty<Instruction>();
200-
201-
return instructions
202-
.Where(inst => inst.OpCode == OpCodes.Isinst && inst.Operand is TypeReference)
203-
.Select(inst =>
204-
domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
205-
(TypeReference)inst.Operand
206-
)
207-
);
270+
public int GetHashCode(FieldReference obj)
271+
{
272+
return obj?.FullName?.GetHashCode() ?? 0;
273+
}
208274
}
209275

210276
internal static bool IsIterator(this MethodDefinition methodDefinition)
@@ -223,52 +289,6 @@ internal static bool IsAsync(this MethodDefinition methodDefinition)
223289
);
224290
}
225291

226-
[NotNull]
227-
internal static IEnumerable<FieldMember> GetAccessedFieldMembers(
228-
this MethodDefinition methodDefinition,
229-
DomainResolver domainResolver
230-
)
231-
{
232-
var accessedFieldMembers = new List<FieldMember>();
233-
var instructions =
234-
methodDefinition.Body?.Instructions.ToList() ?? new List<Instruction>();
235-
var accessedFieldReferences = instructions
236-
.Select(inst => inst.Operand)
237-
.OfType<FieldReference>()
238-
.Distinct();
239-
240-
foreach (var fieldReference in accessedFieldReferences)
241-
{
242-
var declaringType = domainResolver.GetOrCreateStubTypeInstanceFromTypeReference(
243-
fieldReference.DeclaringType
244-
);
245-
var matchingFieldMembers = declaringType
246-
.Type.GetFieldMembers()
247-
.Where(member => member.Name == fieldReference.Name)
248-
.ToList();
249-
250-
switch (matchingFieldMembers.Count)
251-
{
252-
case 0:
253-
var stubFieldMember = domainResolver.GetOrCreateFieldMember(
254-
declaringType.Type,
255-
fieldReference
256-
);
257-
accessedFieldMembers.Add(stubFieldMember);
258-
break;
259-
case 1:
260-
accessedFieldMembers.Add(matchingFieldMembers.First());
261-
break;
262-
default:
263-
throw new MultipleOccurrencesInSequenceException(
264-
$"Multiple Fields matching {fieldReference.FullName} found in provided type."
265-
);
266-
}
267-
}
268-
269-
return accessedFieldMembers.Distinct();
270-
}
271-
272292
internal static bool IsCompilerGenerated(this MemberReference memberReference)
273293
{
274294
if (memberReference.Name.HasCompilerGeneratedName())

0 commit comments

Comments
 (0)