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