1+ // Copyright (c) 2026 Siegfried Pammer
2+ //
3+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+ // software and associated documentation files (the "Software"), to deal in the Software
5+ // without restriction, including without limitation the rights to use, copy, modify, merge,
6+ // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+ // to whom the Software is furnished to do so, subject to the following conditions:
8+ //
9+ // The above copyright notice and this permission notice shall be included in all copies or
10+ // substantial portions of the Software.
11+ //
12+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+ // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+ // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+ // DEALINGS IN THE SOFTWARE.
18+
19+ using System . Collections . Generic ;
20+ using System . Linq ;
21+ using System . Reflection . Metadata ;
22+
23+ using ICSharpCode . Decompiler . Metadata ;
24+
25+ using NUnit . Framework ;
26+
27+ namespace ICSharpCode . Decompiler . Tests . Metadata
28+ {
29+ [ TestFixture ]
30+ public class SignatureBlobComparerTests
31+ {
32+ static MetadataReader Metadata => TypeSystem . TypeSystemLoaderTests . TestAssembly . Metadata ;
33+
34+ #region Helper methods
35+
36+ /// <summary>
37+ /// Finds all MethodDefinitionHandles for methods with the given name in the given type.
38+ /// </summary>
39+ static List < MethodDefinitionHandle > FindMethods ( string typeName , string methodName )
40+ {
41+ var results = new List < MethodDefinitionHandle > ( ) ;
42+ foreach ( var typeHandle in Metadata . TypeDefinitions )
43+ {
44+ var typeDef = Metadata . GetTypeDefinition ( typeHandle ) ;
45+ if ( ! Metadata . StringComparer . Equals ( typeDef . Name , typeName ) )
46+ continue ;
47+
48+ foreach ( var methodHandle in typeDef . GetMethods ( ) )
49+ {
50+ var methodDef = Metadata . GetMethodDefinition ( methodHandle ) ;
51+ if ( Metadata . StringComparer . Equals ( methodDef . Name , methodName ) )
52+ results . Add ( methodHandle ) ;
53+ }
54+ }
55+ return results ;
56+ }
57+
58+ /// <summary>
59+ /// Finds a single MethodDefinitionHandle for a method with the given name in the given type.
60+ /// </summary>
61+ static MethodDefinitionHandle FindMethod ( string typeName , string methodName )
62+ {
63+ var results = FindMethods ( typeName , methodName ) ;
64+ Assert . That ( results . Count , Is . EqualTo ( 1 ) ,
65+ $ "Expected exactly one method '{ methodName } ' in '{ typeName } ', found { results . Count } ") ;
66+ return results [ 0 ] ;
67+ }
68+
69+ /// <summary>
70+ /// Finds a MethodDefinitionHandle matching on name and parameter count.
71+ /// </summary>
72+ static MethodDefinitionHandle FindMethod ( string typeName , string methodName , int parameterCount )
73+ {
74+ var results = FindMethods ( typeName , methodName ) ;
75+ var filtered = results . Where ( h => {
76+ var m = Metadata . GetMethodDefinition ( h ) ;
77+ return m . GetParameters ( ) . Count == parameterCount ;
78+ } ) . ToList ( ) ;
79+ Assert . That ( filtered . Count , Is . EqualTo ( 1 ) ,
80+ $ "Expected exactly one method '{ methodName } ' with { parameterCount } params in '{ typeName } ', found { filtered . Count } ") ;
81+ return filtered [ 0 ] ;
82+ }
83+
84+ static BlobReader GetSignatureBlob ( MethodDefinitionHandle handle )
85+ {
86+ var method = Metadata . GetMethodDefinition ( handle ) ;
87+ return Metadata . GetBlobReader ( method . Signature ) ;
88+ }
89+
90+ static bool CompareSignatures ( MethodDefinitionHandle a , MethodDefinitionHandle b )
91+ {
92+ return SignatureBlobComparer . EqualsMethodSignature (
93+ GetSignatureBlob ( a ) , GetSignatureBlob ( b ) ,
94+ Metadata , Metadata ) ;
95+ }
96+
97+ #endregion
98+
99+ #region Identity / reflexive tests
100+
101+ [ Test ]
102+ public void SameMethod_IsEqual ( )
103+ {
104+ // SimplePublicClass.Method() compared to itself
105+ var method = FindMethod ( "SimplePublicClass" , "Method" ) ;
106+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
107+ }
108+
109+ [ Test ]
110+ public void Constructor_ComparedToItself_IsEqual ( )
111+ {
112+ var ctor = FindMethod ( "SimplePublicClass" , ".ctor" ) ;
113+ Assert . That ( CompareSignatures ( ctor , ctor ) , Is . True ) ;
114+ }
115+
116+ [ Test ]
117+ public void StaticMethod_ComparedToItself_IsEqual ( )
118+ {
119+ // StaticClass.Extension(this object) — it's static at the IL level
120+ var method = FindMethod ( "StaticClass" , "Extension" ) ;
121+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
122+ }
123+
124+ #endregion
125+
126+ #region Same name, different signatures
127+
128+ [ Test ]
129+ public void DifferentParameterTypes_NotEqual ( )
130+ {
131+ // MethodWithOptionalParameter(int) vs MethodWithParamsArray(object[])
132+ var intParam = FindMethod ( "ParameterTests" , "MethodWithOptionalParameter" ) ;
133+ var arrayParam = FindMethod ( "ParameterTests" , "MethodWithParamsArray" ) ;
134+ Assert . That ( CompareSignatures ( intParam , arrayParam ) , Is . False ) ;
135+ }
136+
137+ [ Test ]
138+ public void RefVsOut_SameAtSignatureLevel ( )
139+ {
140+ // ref and out are both ELEMENT_TYPE_BYREF at the blob level;
141+ // the out modifier is only represented via attributes.
142+ var methodRef = FindMethod ( "ParameterTests" , "MethodWithRefParameter" ) ;
143+ var methodOut = FindMethod ( "ParameterTests" , "MethodWithOutParameter" ) ;
144+ Assert . That ( CompareSignatures ( methodRef , methodOut ) , Is . True ) ;
145+ }
146+
147+ [ Test ]
148+ public void RefVsIn_SameAtSignatureLevel ( )
149+ {
150+ // ref and in are both ELEMENT_TYPE_BYREF at the blob level;
151+ // the in modifier is only represented via IsReadOnlyAttribute.
152+ var methodRef = FindMethod ( "ParameterTests" , "MethodWithRefParameter" ) ;
153+ var methodIn = FindMethod ( "ParameterTests" , "MethodWithInParameter" ) ;
154+ Assert . That ( CompareSignatures ( methodRef , methodIn ) , Is . True ) ;
155+ }
156+
157+ [ Test ]
158+ public void ByRefVsNonByRef_NotEqual ( )
159+ {
160+ // MethodWithRefParameter(ref int) vs MethodWithOptionalParameter(int)
161+ // byref int vs plain int should differ at the blob level
162+ var byRef = FindMethod ( "ParameterTests" , "MethodWithRefParameter" ) ;
163+ var plain = FindMethod ( "ParameterTests" , "MethodWithOptionalParameter" ) ;
164+ Assert . That ( CompareSignatures ( byRef , plain ) , Is . False ) ;
165+ }
166+
167+ [ Test ]
168+ public void DifferentParameterCount_NotEqual ( )
169+ {
170+ // Compare a method with 0 params to one with 1 param
171+ var method0 = FindMethod ( "SimplePublicClass" , "Method" ) ; // 0 params
172+ var method1 = FindMethod ( "ParameterTests" , "MethodWithOutParameter" ) ; // 1 param
173+ Assert . That ( CompareSignatures ( method0 , method1 ) , Is . False ) ;
174+ }
175+
176+ [ Test ]
177+ public void DifferentReturnType_NotEqual ( )
178+ {
179+ // SimplePublicClass.Method() returns void
180+ // PropertyTest has a getter that returns string (get_Item)
181+ var voidMethod = FindMethod ( "SimplePublicClass" , "Method" ) ;
182+ var stringGetter = FindMethod ( "PropertyTest" , "get_Item" ) ;
183+ Assert . That ( CompareSignatures ( voidMethod , stringGetter ) , Is . False ) ;
184+ }
185+
186+ #endregion
187+
188+ #region Generic methods
189+
190+ [ Test ]
191+ public void GenericMethod_ComparedToItself_IsEqual ( )
192+ {
193+ // GenericClass<A,B>.TestMethod<K,V>(string)
194+ var method = FindMethod ( "GenericClass`2" , "TestMethod" ) ;
195+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
196+ }
197+
198+ [ Test ]
199+ public void GenericMethod_DifferentArity_NotEqual ( )
200+ {
201+ // TestMethod<K,V>(string) has 2 type params
202+ // GetIndex<T>(T) has 1 type param
203+ var testMethod = FindMethod ( "GenericClass`2" , "TestMethod" ) ;
204+ var getIndex = FindMethod ( "GenericClass`2" , "GetIndex" ) ;
205+ Assert . That ( CompareSignatures ( testMethod , getIndex ) , Is . False ) ;
206+ }
207+
208+ [ Test ]
209+ public void GenericMethodWithOneTypeParam_ComparedToItself_IsEqual ( )
210+ {
211+ var method = FindMethod ( "GenericClass`2" , "GetIndex" ) ;
212+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
213+ }
214+
215+ [ Test ]
216+ public void NonGenericVsGenericMethod_NotEqual ( )
217+ {
218+ // SimplePublicClass.Method() — non-generic
219+ // GenericClass<A,B>.GetIndex<T>(T) — generic
220+ var nonGeneric = FindMethod ( "SimplePublicClass" , "Method" ) ;
221+ var generic = FindMethod ( "GenericClass`2" , "GetIndex" ) ;
222+ Assert . That ( CompareSignatures ( nonGeneric , generic ) , Is . False ) ;
223+ }
224+
225+ #endregion
226+
227+ #region Instance vs static
228+
229+ [ Test ]
230+ public void InstanceVsStatic_DifferentCallingConvention_NotEqual ( )
231+ {
232+ // An instance method vs a static method — calling convention differs in the signature header
233+ // StaticClass.Extension is static; SimplePublicClass.Method is instance
234+ var staticMethod = FindMethod ( "StaticClass" , "Extension" ) ;
235+ var instanceMethod = FindMethod ( "SimplePublicClass" , "Method" ) ;
236+ Assert . That ( CompareSignatures ( staticMethod , instanceMethod ) , Is . False ) ;
237+ }
238+
239+ #endregion
240+
241+ #region Methods with same signature across different types
242+
243+ [ Test ]
244+ public void SameSignatureInDifferentTypes_IsEqual ( )
245+ {
246+ // Both SimplePublicClass and ParameterTests have parameterless instance constructors
247+ var ctor1 = FindMethod ( "SimplePublicClass" , ".ctor" ) ;
248+ var ctor2 = FindMethod ( "ParameterTests" , ".ctor" ) ;
249+ Assert . That ( CompareSignatures ( ctor1 , ctor2 ) , Is . True ) ;
250+ }
251+
252+ #endregion
253+
254+ #region Methods involving complex types
255+
256+ [ Test ]
257+ public void MethodWithArrayParam_ComparedToItself_IsEqual ( )
258+ {
259+ // ParameterTests.MethodWithParamsArray(params object[])
260+ var method = FindMethod ( "ParameterTests" , "MethodWithParamsArray" ) ;
261+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
262+ }
263+
264+ [ Test ]
265+ public void MethodWithOptionalParam_ComparedToNonOptional_IsEqual ( )
266+ {
267+ // At the signature blob level, optional vs required doesn't matter —
268+ // the signatures are the same if the types match.
269+ // MethodWithOptionalParameter(int) vs MethodWithEnumOptionalParameter(StringComparison)
270+ // These have different parameter types, so they should NOT be equal.
271+ var optionalInt = FindMethod ( "ParameterTests" , "MethodWithOptionalParameter" ) ;
272+ var optionalEnum = FindMethod ( "ParameterTests" , "MethodWithEnumOptionalParameter" ) ;
273+ Assert . That ( CompareSignatures ( optionalInt , optionalEnum ) , Is . False ) ;
274+ }
275+
276+ [ Test ]
277+ public void VarArgsMethod_ComparedToItself_IsEqual ( )
278+ {
279+ var method = FindMethod ( "ParameterTests" , "VarArgsMethod" ) ;
280+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
281+ }
282+
283+ [ Test ]
284+ public void VarArgsMethod_VsNormalMethod_NotEqual ( )
285+ {
286+ // VarArgsMethod uses __arglist calling convention
287+ var varArgs = FindMethod ( "ParameterTests" , "VarArgsMethod" ) ;
288+ var normal = FindMethod ( "SimplePublicClass" , "Method" ) ;
289+ Assert . That ( CompareSignatures ( varArgs , normal ) , Is . False ) ;
290+ }
291+
292+ #endregion
293+
294+ #region Indexer accessors
295+
296+ [ Test ]
297+ public void IndexerGetter_ComparedToItself_IsEqual ( )
298+ {
299+ var getter = FindMethod ( "PropertyTest" , "get_Item" ) ;
300+ Assert . That ( CompareSignatures ( getter , getter ) , Is . True ) ;
301+ }
302+
303+ [ Test ]
304+ public void IndexerGetter_VsSetter_NotEqual ( )
305+ {
306+ var getter = FindMethod ( "PropertyTest" , "get_Item" ) ;
307+ var setter = FindMethod ( "PropertyTest" , "set_Item" ) ;
308+ Assert . That ( CompareSignatures ( getter , setter ) , Is . False ) ;
309+ }
310+
311+ #endregion
312+
313+ #region DllImport / extern methods
314+
315+ [ Test ]
316+ public void DllImportMethod_ComparedToItself_IsEqual ( )
317+ {
318+ var method = FindMethod ( "NonCustomAttributes" , "DllMethod" ) ;
319+ Assert . That ( CompareSignatures ( method , method ) , Is . True ) ;
320+ }
321+
322+ #endregion
323+
324+ #region Explicit interface implementation
325+
326+ [ Test ]
327+ public void ExplicitImpl_VsRegularMethod_SameSignature ( )
328+ {
329+ // ExplicitImplementationTests has both M(int) and IExplicitImplementationTests.M(int)
330+ // Their signatures should be identical at the blob level
331+ var methods = FindMethods ( "ExplicitImplementationTests" , "M" ) ;
332+ Assert . That ( methods . Count , Is . EqualTo ( 1 ) ,
333+ "Explicit impl has a mangled name, so only one 'M' should be found" ) ;
334+ }
335+
336+ #endregion
337+
338+ #region Symmetric property
339+
340+ [ Test ]
341+ public void Comparison_IsSymmetric ( )
342+ {
343+ var method1 = FindMethod ( "SimplePublicClass" , "Method" ) ;
344+ var method2 = FindMethod ( "ParameterTests" , "MethodWithOutParameter" ) ;
345+
346+ bool forward = CompareSignatures ( method1 , method2 ) ;
347+ bool backward = CompareSignatures ( method2 , method1 ) ;
348+ Assert . That ( forward , Is . EqualTo ( backward ) , "Comparison should be symmetric" ) ;
349+ }
350+
351+ [ Test ]
352+ public void Comparison_IsSymmetric_WhenEqual ( )
353+ {
354+ var ctor1 = FindMethod ( "SimplePublicClass" , ".ctor" ) ;
355+ var ctor2 = FindMethod ( "ParameterTests" , ".ctor" ) ;
356+
357+ bool forward = CompareSignatures ( ctor1 , ctor2 ) ;
358+ bool backward = CompareSignatures ( ctor2 , ctor1 ) ;
359+ Assert . That ( forward , Is . True ) ;
360+ Assert . That ( backward , Is . True ) ;
361+ }
362+
363+ #endregion
364+ }
365+ }
0 commit comments