33using System . Linq ;
44using System . Text ;
55using ArchUnitNET . Domain ;
6- using ArchUnitNET . Domain . Exceptions ;
76using ArchUnitNET . Domain . Extensions ;
87using JetBrains . Annotations ;
98using 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