1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.IO;
8 using System.Linq;
9 using System.Reflection.Metadata.Ecma335;
10 using System.Reflection.Metadata.Tests;
11 using System.Reflection.PortableExecutable;
12 using Xunit;
13 
14 namespace System.Reflection.Metadata.Decoding.Tests
15 {
16     public partial class SignatureDecoderTests
17     {
18         [Fact]
VerifyMultipleOptionalModifiers()19         public unsafe void VerifyMultipleOptionalModifiers()
20         {
21             // Type 1: int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong) modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
22             // Type 2: char*
23             // Type 3: uint32
24             // Type 4: char modopt([mscorlib]System.Runtime.CompilerServices.IsConst)*
25             var testSignature = new byte[] { 0x20, 0x45, 0x20, 0x69, 0x08, 0x0F, 0x03, 0x09, 0x0F, 0x20, 0x55, 0x03 };
26             var types = new string[]
27             {
28                 "int32 modopt(100001A) modopt(1000011)",
29                 "char*",
30                 "uint32",
31                 "char modopt(1000015)*"
32             };
33 
34             fixed (byte* testSignaturePtr = &testSignature[0])
35             {
36                 var signatureBlob = new BlobReader(testSignaturePtr, testSignature.Length);
37                 var provider = new OpaqueTokenTypeProvider();
38                 var decoder = new SignatureDecoder<string, DisassemblingGenericContext>(provider, metadataReader: null, genericContext: null);
39 
40                 foreach (string typeString in types)
41                 {
42                     // Verify that each type is decoded as expected
43                     Assert.Equal(typeString, decoder.DecodeType(ref signatureBlob));
44                 }
45                 // And that nothing is left over to decode
46                 Assert.True(signatureBlob.RemainingBytes == 0);
47                 Assert.Throws<BadImageFormatException>(() => decoder.DecodeType(ref signatureBlob));
48             }
49         }
50 
51         [Theory]
52         [InlineData(new string[] { "int32", "string" }, new byte[] { 0x0A /*GENERICINST*/, 2  /*count*/, 0x8 /*I4*/, 0xE /*STRING*/ })]
DecodeValidMethodSpecificationSignature(string[] expectedTypes, byte[] testSignature)53         public unsafe void DecodeValidMethodSpecificationSignature(string[] expectedTypes, byte[] testSignature)
54         {
55             fixed (byte* testSignaturePtr = &testSignature[0])
56             {
57                 var signatureBlob = new BlobReader(testSignaturePtr, testSignature.Length);
58                 var provider = new OpaqueTokenTypeProvider();
59                 var decoder = new SignatureDecoder<string, DisassemblingGenericContext>(provider, metadataReader: null, genericContext: null);
60 
61                 IEnumerable<string> actualTypes = decoder.DecodeMethodSpecificationSignature(ref signatureBlob);
62                 Assert.Equal(expectedTypes, actualTypes);
63                 Assert.True(signatureBlob.RemainingBytes == 0);
64                 Assert.Throws<BadImageFormatException>(() => decoder.DecodeType(ref signatureBlob));
65             }
66         }
67 
68         [Theory]
69         [InlineData(new byte[] { 0 })]  // bad header
70         [InlineData(new byte[] { 0x0A /*GENERICINST*/, 0 /*count*/ })] // no type parameters
DecodeInvalidMethodSpecificationSignature(byte[] testSignature)71         public unsafe void DecodeInvalidMethodSpecificationSignature(byte[] testSignature)
72         {
73             fixed (byte* testSignaturePtr = &testSignature[0])
74             {
75                 var signatureBlob = new BlobReader(testSignaturePtr, testSignature.Length);
76                 var provider = new OpaqueTokenTypeProvider();
77                 var decoder = new SignatureDecoder<string, DisassemblingGenericContext>(provider, metadataReader: null, genericContext: null);
78             }
79         }
80 
81         [Fact]
DecodeVarArgsDefAndRef()82         public void DecodeVarArgsDefAndRef()
83         {
84             using (FileStream stream = File.OpenRead(typeof(VarArgsToDecode).GetTypeInfo().Assembly.Location))
85             using (var peReader = new PEReader(stream))
86             {
87                 MetadataReader metadataReader = peReader.GetMetadataReader();
88                 TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(metadataReader, typeof(VarArgsToDecode));
89                 TypeDefinition typeDef = metadataReader.GetTypeDefinition(typeDefHandle);
90                 MethodDefinition methodDef = metadataReader.GetMethodDefinition(typeDef.GetMethods().First());
91 
92                 Assert.Equal("VarArgsCallee", metadataReader.GetString(methodDef.Name));
93                 var provider = new OpaqueTokenTypeProvider();
94 
95                 MethodSignature<string> defSignature = methodDef.DecodeSignature(provider, null);
96                 Assert.Equal(SignatureCallingConvention.VarArgs, defSignature.Header.CallingConvention);
97                 Assert.Equal(1, defSignature.RequiredParameterCount);
98                 Assert.Equal(new[] { "int32" }, defSignature.ParameterTypes);
99 
100                 int refCount = 0;
101                 foreach (MemberReferenceHandle memberRefHandle in metadataReader.MemberReferences)
102                 {
103                     MemberReference memberRef = metadataReader.GetMemberReference(memberRefHandle);
104 
105                     if (metadataReader.StringComparer.Equals(memberRef.Name, "VarArgsCallee"))
106                     {
107                         Assert.Equal(MemberReferenceKind.Method, memberRef.GetKind());
108                         MethodSignature<string> refSignature = memberRef.DecodeMethodSignature(provider, null);
109                         Assert.Equal(SignatureCallingConvention.VarArgs, refSignature.Header.CallingConvention);
110                         Assert.Equal(1, refSignature.RequiredParameterCount);
111                         Assert.Equal(new[] { "int32", "bool", "string", "float64" }, refSignature.ParameterTypes);
112                         refCount++;
113                     }
114                 }
115 
116                 Assert.Equal(1, refCount);
117             }
118         }
119 
120         private static class VarArgsToDecode
121         {
VarArgsCallee(int i, __arglist)122             public static void VarArgsCallee(int i, __arglist)
123             {
124             }
125 
VarArgsCaller()126             public static void VarArgsCaller()
127             {
128                 VarArgsCallee(1, __arglist(true, "hello", 0.42));
129             }
130         }
131 
132         // Test as much as we can with simple C# examples inline below.
133         [Fact]
SimpleSignatureProviderCoverage()134         public void SimpleSignatureProviderCoverage()
135         {
136             using (FileStream stream = File.OpenRead(typeof(SignaturesToDecode<>).GetTypeInfo().Assembly.Location))
137             using (var peReader = new PEReader(stream))
138             {
139 
140                 MetadataReader reader = peReader.GetMetadataReader();
141                 var provider = new DisassemblingTypeProvider();
142                 TypeDefinitionHandle typeHandle = TestMetadataResolver.FindTestType(reader, typeof(SignaturesToDecode<>));
143                 Assert.Equal("System.Reflection.Metadata.Decoding.Tests.SignatureDecoderTests/SignaturesToDecode`1", provider.GetTypeFromHandle(reader, genericContext: null, handle: typeHandle));
144 
145                 TypeDefinition type = reader.GetTypeDefinition(typeHandle);
146                 Dictionary<string, string> expectedFields = GetExpectedFieldSignatures();
147                 ImmutableArray<string> genericTypeParameters = type.GetGenericParameters().Select(h => reader.GetString(reader.GetGenericParameter(h).Name)).ToImmutableArray();
148 
149                 var genericTypeContext = new DisassemblingGenericContext(genericTypeParameters, ImmutableArray<string>.Empty);
150 
151                 foreach (var fieldHandle in type.GetFields())
152                 {
153                     FieldDefinition field = reader.GetFieldDefinition(fieldHandle);
154                     string fieldName = reader.GetString(field.Name);
155                     string expected;
156                     Assert.True(expectedFields.TryGetValue(fieldName, out expected), "Unexpected field: " + fieldName);
157                     Assert.Equal(expected, field.DecodeSignature(provider, genericTypeContext));
158                 }
159 
160                 Dictionary<string, string> expectedMethods = GetExpectedMethodSignatures();
161                 foreach (var methodHandle in type.GetMethods())
162                 {
163                     MethodDefinition method = reader.GetMethodDefinition(methodHandle);
164 
165                     ImmutableArray<string> genericMethodParameters = method.GetGenericParameters().Select(h => reader.GetString(reader.GetGenericParameter(h).Name)).ToImmutableArray();
166                     var genericMethodContext = new DisassemblingGenericContext(genericTypeParameters, genericMethodParameters);
167 
168                     string methodName = reader.GetString(method.Name);
169                     string expected;
170                     Assert.True(expectedMethods.TryGetValue(methodName, out expected), "Unexpected method: " + methodName);
171                     MethodSignature<string> signature = method.DecodeSignature(provider, genericMethodContext);
172                     Assert.True(signature.Header.Kind == SignatureKind.Method);
173 
174                     if (methodName.StartsWith("Generic"))
175                     {
176                         Assert.Equal(1, signature.GenericParameterCount);
177                     }
178                     else
179                     {
180                         Assert.Equal(0, signature.GenericParameterCount);
181                     }
182 
183                     Assert.True(signature.GenericParameterCount <= 1 && (methodName != "GenericMethodParameter" || signature.GenericParameterCount == 1));
184                     Assert.Equal(expected, provider.GetFunctionPointerType(signature));
185                 }
186 
187                 Dictionary<string, string> expectedProperties = GetExpectedPropertySignatures();
188                 foreach (var propertyHandle in type.GetProperties())
189                 {
190                     PropertyDefinition property = reader.GetPropertyDefinition(propertyHandle);
191                     string propertyName = reader.GetString(property.Name);
192                     string expected;
193                     Assert.True(expectedProperties.TryGetValue(propertyName, out expected), "Unexpected property: " + propertyName);
194                     MethodSignature<string> signature = property.DecodeSignature(provider, genericTypeContext);
195                     Assert.True(signature.Header.Kind == SignatureKind.Property);
196                     Assert.Equal(expected, provider.GetFunctionPointerType(signature));
197                 }
198 
199                 Dictionary<string, string> expectedEvents = GetExpectedEventSignatures();
200                 foreach (var eventHandle in type.GetEvents())
201                 {
202                     EventDefinition @event = reader.GetEventDefinition(eventHandle);
203                     string eventName = reader.GetString(@event.Name);
204                     string expected;
205                     Assert.True(expectedEvents.TryGetValue(eventName, out expected), "Unexpected event: " + eventName);
206 
207                     Assert.Equal(expected, provider.GetTypeFromHandle(reader, genericTypeContext, @event.Type));
208                 }
209 
210                 Assert.Equal("[System.Collections]System.Collections.Generic.List`1<!T>", provider.GetTypeFromHandle(reader, genericTypeContext, handle: type.BaseType));
211             }
212         }
213 
214         public unsafe class SignaturesToDecode<T> : List<T>
215         {
216             public sbyte SByte;
217             public byte Byte;
218             public short Int16;
219             public ushort UInt16;
220             public int Int32;
221             public uint UInt32;
222             public long Int64;
223             public ulong UInt64;
224             public string String;
225             public object Object;
226             public float Single;
227             public double Double;
228             public IntPtr IntPtr;
229             public UIntPtr UIntPtr;
230             public bool Boolean;
231             public char Char;
232             public volatile int ModifiedType;
233             public int* Pointer;
234             public int[] SZArray;
235             public int[,] Array;
ByReference(ref int i)236             public void ByReference(ref int i) { }
237             public T GenericTypeParameter;
GenericMethodParameter()238             public U GenericMethodParameter<U>() { throw null; }
239             public List<int> GenericInstantiation;
240             public struct Nested { }
241             public Nested Property { get { throw null; } }
242             public event EventHandler<EventArgs> Event { add { } remove { } }
243         }
244 
245         [Fact]
PinnedAndUnpinnedLocals()246         public void PinnedAndUnpinnedLocals()
247         {
248             using (FileStream stream = File.OpenRead(typeof(PinnedAndUnpinnedLocalsToDecode).GetTypeInfo().Assembly.Location))
249             using (var peReader = new PEReader(stream))
250             {
251                 MetadataReader reader = peReader.GetMetadataReader();
252                 var provider = new DisassemblingTypeProvider();
253 
254                 TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(PinnedAndUnpinnedLocalsToDecode));
255                 TypeDefinition typeDef = reader.GetTypeDefinition(typeDefHandle);
256                 MethodDefinition methodDef = reader.GetMethodDefinition(typeDef.GetMethods().First());
257 
258                 Assert.Equal("DoSomething", reader.GetString(methodDef.Name));
259 
260                 MethodBodyBlock body = peReader.GetMethodBody(methodDef.RelativeVirtualAddress);
261                 StandaloneSignature localSignature = reader.GetStandaloneSignature(body.LocalSignature);
262 
263                 ImmutableArray<string> localTypes = localSignature.DecodeLocalSignature(provider, genericContext: null);
264 
265                 // Compiler can generate temporaries or re-order so just check the ones we expect are there.
266                 // (They could get optimized away too. If that happens in practice, change this test to use hard-coded signatures.)
267                 Assert.Contains("uint8[] pinned", localTypes);
268                 Assert.Contains("uint8[]", localTypes);
269             }
270         }
271 
272         public static class PinnedAndUnpinnedLocalsToDecode
273         {
DoSomething()274             public static unsafe int DoSomething()
275             {
276                 byte[] bytes = new byte[] { 1, 2, 3 };
277                 fixed (byte* bytePtr = bytes)
278                 {
279                     return *bytePtr;
280                 }
281             }
282         }
283 
284         [Fact]
WrongSignatureType()285         public void WrongSignatureType()
286         {
287             using (FileStream stream = File.OpenRead(typeof(VarArgsToDecode).GetTypeInfo().Assembly.Location))
288             using (var peReader = new PEReader(stream))
289             {
290                 MetadataReader reader = peReader.GetMetadataReader();
291                 var provider = new DisassemblingTypeProvider();
292                 var decoder = new SignatureDecoder<string, DisassemblingGenericContext>(provider, reader, genericContext: null);
293 
294                 BlobReader fieldSignature = reader.GetBlobReader(reader.GetFieldDefinition(MetadataTokens.FieldDefinitionHandle(1)).Signature);
295                 BlobReader methodSignature = reader.GetBlobReader(reader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle(1)).Signature);
296                 BlobReader propertySignature = reader.GetBlobReader(reader.GetPropertyDefinition(MetadataTokens.PropertyDefinitionHandle(1)).Signature);
297 
298                 Assert.Throws<BadImageFormatException>(() => decoder.DecodeMethodSignature(ref fieldSignature));
299                 Assert.Throws<BadImageFormatException>(() => decoder.DecodeFieldSignature(ref methodSignature));
300                 Assert.Throws<BadImageFormatException>(() => decoder.DecodeLocalSignature(ref propertySignature));
301             }
302         }
303 
GetExpectedFieldSignatures()304         private static Dictionary<string, string> GetExpectedFieldSignatures()
305         {
306             // Field name -> signature
307             return new Dictionary<string, string>()
308             {
309                 { "SByte", "int8" },
310                 { "Byte", "uint8" },
311                 { "Int16", "int16" },
312                 { "UInt16", "uint16" },
313                 { "Int32", "int32" },
314                 { "UInt32", "uint32" },
315                 { "Int64", "int64" },
316                 { "UInt64", "uint64" },
317                 { "String", "string" },
318                 { "Object", "object" },
319                 { "Single", "float32" },
320                 { "Double", "float64" },
321                 { "IntPtr", "native int" },
322                 { "UIntPtr", "native uint" },
323                 { "Boolean", "bool" },
324                 { "Char", "char" },
325                 { "ModifiedType", "int32 modreq([System.Runtime]System.Runtime.CompilerServices.IsVolatile)" },
326                 { "Pointer", "int32*"  },
327                 { "SZArray", "int32[]" },
328                 { "Array", "int32[0...,0...]" },
329                 { "GenericTypeParameter", "!T" },
330                 { "GenericInstantiation", "[System.Collections]System.Collections.Generic.List`1<int32>" },
331             };
332         }
333 
GetExpectedMethodSignatures()334         private static Dictionary<string, string> GetExpectedMethodSignatures()
335         {
336             // method name -> signature
337             return new Dictionary<string, string>()
338             {
339                 { "ByReference", "method void *(int32&)" },
340                 { "GenericMethodParameter", "method !!U *()" },
341                 { ".ctor", "method void *()" },
342                 { "get_Property", "method System.Reflection.Metadata.Decoding.Tests.SignatureDecoderTests/SignaturesToDecode`1/Nested<!T> *()"  },
343                 { "add_Event",  "method void *([System.Runtime]System.EventHandler`1<[System.Runtime]System.EventArgs>)" },
344                 { "remove_Event", "method void *([System.Runtime]System.EventHandler`1<[System.Runtime]System.EventArgs>)" },
345             };
346         }
347 
GetExpectedPropertySignatures()348         private static Dictionary<string, string> GetExpectedPropertySignatures()
349         {
350             // field name -> signature
351             return new Dictionary<string, string>()
352             {
353                 { "Property", "method System.Reflection.Metadata.Decoding.Tests.SignatureDecoderTests/SignaturesToDecode`1/Nested<!T> *()" },
354             };
355         }
356 
GetExpectedEventSignatures()357         private static Dictionary<string, string> GetExpectedEventSignatures()
358         {
359             // event name -> signature
360             return new Dictionary<string, string>()
361             {
362                 { "Event", "[System.Runtime]System.EventHandler`1<[System.Runtime]System.EventArgs>" },
363             };
364         }
365 
366         [Theory]
367         [InlineData(new byte[] { 0x12 /*CLASS*/, 0x06 /*encoded type spec*/ })] // not def or ref
368         [InlineData(new byte[] { 0x11 /*VALUETYPE*/, 0x06 /*encoded type spec*/})] // not def or ref
369         [InlineData(new byte[] { 0x60 })] // Bad type code
BadTypeSignature(byte[] signature)370         public unsafe void BadTypeSignature(byte[] signature)
371         {
372             fixed (byte* bytes = signature)
373             {
374                 BlobReader reader = new BlobReader(bytes, signature.Length);
375                 Assert.Throws<BadImageFormatException>(() => new SignatureDecoder<string, DisassemblingGenericContext>(new OpaqueTokenTypeProvider(), metadataReader: null, genericContext: null).DecodeType(ref reader));
376             }
377         }
378 
379         [Theory]
380         [InlineData("method void *()", new byte[] { 0x1B /*FNPTR*/, 0 /*default calling convention*/, 0 /*parameters count*/, 0x1 /* return type (VOID)*/ })]
381         [InlineData("int32[...]", new byte[] { 0x14 /*ARRAY*/, 0x8 /*I4*/, 1 /*rank*/, 0 /*sizes*/, 0 /*lower bounds*/ })]
382         [InlineData("int32[...,...,...]", new byte[] { 0x14 /*ARRAY*/, 0x8 /*I4*/, 3 /*rank*/, 0 /*sizes*/, 0/*lower bounds*/  })]
383         [InlineData("int32[-1...1]", new byte[] { 0x14 /*ARRAY*/, 0x8 /*I4*/, 1 /*rank*/, 1 /*sizes*/, 3 /*size*/, 1 /*lower bounds*/, 0x7F /*lower bound (compressed -1)*/ })]
384         [InlineData("int32[1...]", new byte[] { 0x14 /*ARRAY*/, 0x8 /*I4*/, 1 /*rank*/, 0 /*sizes*/, 1 /*lower bounds*/, 2 /*lower bound (compressed +1)*/ })]
ExoticTypeSignature(string expected, byte[] signature)385         public unsafe void ExoticTypeSignature(string expected, byte[] signature)
386         {
387             fixed (byte* bytes = signature)
388             {
389                 BlobReader reader = new BlobReader(bytes, signature.Length);
390                 Assert.Equal(expected, new SignatureDecoder<string, DisassemblingGenericContext>(new OpaqueTokenTypeProvider(), metadataReader: null, genericContext: null).DecodeType(ref reader));
391             }
392         }
393 
394         [Fact]
ProviderCannotBeNull()395         public void ProviderCannotBeNull()
396         {
397             AssertExtensions.Throws<ArgumentNullException>("provider", () => new SignatureDecoder<int, object>(provider: null, metadataReader: null, genericContext: null));
398         }
399     }
400 }
401