From 87eb0544f13a0cfff5fac0028e1ede94f82902ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 16 Aug 2021 12:32:33 +0900 Subject: [PATCH 01/14] Add handling of generic attributes to CustomAttributeDecoder If the attribute constructor refers to the generic T in its signature, we would throw `BadImageFormatException`. This adds handling by capturing the generic context and interpreting the signature variables when needed. --- .../Ecma335/CustomAttributeDecoder.cs | 144 +++++++++++++++++- 1 file changed, 139 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs index a81396cbdef16..802b6ba303d2a 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs @@ -22,6 +22,7 @@ public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, Meta public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHandle value) { BlobHandle signature; + BlobHandle attributeOwningTypeSpec = default; switch (constructor.Kind) { case HandleKind.MethodDefinition: @@ -32,6 +33,13 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan case HandleKind.MemberReference: MemberReference reference = _reader.GetMemberReference((MemberReferenceHandle)constructor); signature = reference.Signature; + + // If this is a generic attribute, we'll need its instantiation to decode the signatures + if (reference.Parent.Kind == HandleKind.TypeSpecification) + { + TypeSpecification genericOwner = _reader.GetTypeSpecification((TypeSpecificationHandle)reference.Parent); + attributeOwningTypeSpec = genericOwner.Signature; + } break; default: @@ -60,12 +68,32 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan throw new BadImageFormatException(); } - ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount); + BlobReader genericContextReader = default; + if (!attributeOwningTypeSpec.IsNil) + { + // If this is a generic attribute, grab the instantiation arguments so that we can + // interpret the constructor signature, should it refer to the generic context. + genericContextReader = _reader.GetBlobReader(attributeOwningTypeSpec); + if (genericContextReader.ReadSignatureTypeCode() == SignatureTypeCode.GenericTypeInstance) + { + int kind = genericContextReader.ReadCompressedInteger(); + if (kind != (int)SignatureTypeKind.Class && kind != (int)SignatureTypeKind.ValueType) + { + throw new BadImageFormatException(); + } + + genericContextReader.ReadTypeHandle(); + + // At this point, the reader points to the "GenArgCount Type Type*" part of the signature. + } + } + + ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount, genericContextReader); ImmutableArray> namedArguments = DecodeNamedArguments(ref valueReader); return new CustomAttributeValue(fixedArguments, namedArguments); } - private ImmutableArray> DecodeFixedArguments(ref BlobReader signatureReader, ref BlobReader valueReader, int count) + private ImmutableArray> DecodeFixedArguments(ref BlobReader signatureReader, ref BlobReader valueReader, int count, BlobReader genericContextReader) { if (count == 0) { @@ -76,7 +104,7 @@ private ImmutableArray> DecodeFixedArguments for (int i = 0; i < count; i++) { - ArgumentTypeInfo info = DecodeFixedArgumentType(ref signatureReader); + ArgumentTypeInfo info = DecodeFixedArgumentType(ref signatureReader, genericContextReader); arguments.Add(DecodeArgument(ref valueReader, info)); } @@ -124,7 +152,7 @@ private struct ArgumentTypeInfo // better perf-wise, but even more important is that we can't actually reason about // a method signature with opaque TType values without adding some unnecessary chatter // with the provider. - private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, bool isElementType = false) + private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, BlobReader genericContextReader, bool isElementType = false) { SignatureTypeCode signatureTypeCode = signatureReader.ReadSignatureTypeCode(); @@ -170,12 +198,28 @@ private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, throw new BadImageFormatException(); } - var elementInfo = DecodeFixedArgumentType(ref signatureReader, isElementType: true); + var elementInfo = DecodeFixedArgumentType(ref signatureReader, genericContextReader, isElementType: true); info.ElementType = elementInfo.Type; info.ElementTypeCode = elementInfo.TypeCode; info.Type = _provider.GetSZArrayType(info.ElementType); break; + case SignatureTypeCode.GenericTypeParameter: + int parameterIndex = signatureReader.ReadCompressedInteger(); + int numGenericParameters = genericContextReader.ReadCompressedInteger(); + if (parameterIndex >= numGenericParameters) + { + throw new BadImageFormatException(); + } + + while (parameterIndex > 0) + { + SkipType(ref genericContextReader); + parameterIndex--; + } + + return DecodeFixedArgumentType(ref genericContextReader, default, isElementType); + default: throw new BadImageFormatException(); } @@ -363,5 +407,95 @@ private TType GetTypeFromHandle(EntityHandle handle) => HandleKind.TypeReference => _provider.GetTypeFromReference(_reader, (TypeReferenceHandle)handle, 0), _ => throw new BadImageFormatException(SR.NotTypeDefOrRefHandle), }; + + private static void SkipType(ref BlobReader blobReader) + { + int typeCode = blobReader.ReadCompressedInteger(); + + switch (typeCode) + { + case (int)SignatureTypeCode.Boolean: + case (int)SignatureTypeCode.Char: + case (int)SignatureTypeCode.SByte: + case (int)SignatureTypeCode.Byte: + case (int)SignatureTypeCode.Int16: + case (int)SignatureTypeCode.UInt16: + case (int)SignatureTypeCode.Int32: + case (int)SignatureTypeCode.UInt32: + case (int)SignatureTypeCode.Int64: + case (int)SignatureTypeCode.UInt64: + case (int)SignatureTypeCode.Single: + case (int)SignatureTypeCode.Double: + case (int)SignatureTypeCode.IntPtr: + case (int)SignatureTypeCode.UIntPtr: + case (int)SignatureTypeCode.Object: + case (int)SignatureTypeCode.String: + case (int)SignatureTypeCode.Void: + case (int)SignatureTypeCode.TypedReference: + return; + + case (int)SignatureTypeCode.Pointer: + case (int)SignatureTypeCode.ByReference: + case (int)SignatureTypeCode.Pinned: + case (int)SignatureTypeCode.SZArray: + SkipType(ref blobReader); + return; + + case (int)SignatureTypeCode.FunctionPointer: + SignatureHeader header = blobReader.ReadSignatureHeader(); + if (header.IsGeneric) + { + blobReader.ReadCompressedInteger(); // arity + } + + int paramCount = blobReader.ReadCompressedInteger(); + SkipType(ref blobReader); + for (int i = 0; i < paramCount; i++) + SkipType(ref blobReader); + return; + + case (int)SignatureTypeCode.Array: + SkipType(ref blobReader); + blobReader.ReadCompressedInteger(); // rank + int boundsCount = blobReader.ReadCompressedInteger(); + for (int i = 0; i < boundsCount; i++) + { + blobReader.ReadCompressedInteger(); + } + int lowerBoundsCount = blobReader.ReadCompressedInteger(); + for (int i = 0; i < lowerBoundsCount; i++) + { + blobReader.ReadCompressedSignedInteger(); + } + return; + + case (int)SignatureTypeCode.RequiredModifier: + case (int)SignatureTypeCode.OptionalModifier: + blobReader.ReadTypeHandle(); + SkipType(ref blobReader); + return; + + case (int)SignatureTypeCode.GenericTypeInstance: + SkipType(ref blobReader); + int count = blobReader.ReadCompressedInteger(); + for (int i = 0; i < count; i++) + { + SkipType(ref blobReader); + } + return; + + case (int)SignatureTypeCode.GenericTypeParameter: + blobReader.ReadCompressedInteger(); + return; + + case (int)SignatureTypeKind.Class: + case (int)SignatureTypeKind.ValueType: + SkipType(ref blobReader); + break; + + default: + throw new BadImageFormatException(); + } + } } } From c4b28bd39d8af8b8f5d7067007f94f20dc7d444e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 17 Aug 2021 07:01:23 +0900 Subject: [PATCH 02/14] Update CustomAttributeDecoder.cs --- .../Metadata/Ecma335/CustomAttributeDecoder.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs index 802b6ba303d2a..b4777bcbbeb2c 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs @@ -86,6 +86,12 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan // At this point, the reader points to the "GenArgCount Type Type*" part of the signature. } + else + { + // Some other invalid TypeSpec. Don't accidentally allow resolving generic parameters + // from the constructor signature into a broken blob. + genericContextReader = default; + } } ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount, genericContextReader); @@ -205,6 +211,11 @@ private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, break; case SignatureTypeCode.GenericTypeParameter: + if (genericContextReader.IsNil) + { + throw new BadImageFormatException(); + } + int parameterIndex = signatureReader.ReadCompressedInteger(); int numGenericParameters = genericContextReader.ReadCompressedInteger(); if (parameterIndex >= numGenericParameters) From b8462c51f6f466665fb1ae84523f6105964ef0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 17 Aug 2021 09:03:52 +0900 Subject: [PATCH 03/14] Update CustomAttributeDecoder.cs --- .../Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs index b4777bcbbeb2c..de7a3150d702f 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs @@ -211,7 +211,7 @@ private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, break; case SignatureTypeCode.GenericTypeParameter: - if (genericContextReader.IsNil) + if (genericContextReader.Length == 0) { throw new BadImageFormatException(); } From ae5977ad3917b96b3d3b3654a16448565f9e7bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 17 Aug 2021 11:07:59 +0900 Subject: [PATCH 04/14] Update CustomAttributeDecoder.cs --- .../Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs index de7a3150d702f..45deeee785304 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs @@ -215,7 +215,7 @@ private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, { throw new BadImageFormatException(); } - + int parameterIndex = signatureReader.ReadCompressedInteger(); int numGenericParameters = genericContextReader.ReadCompressedInteger(); if (parameterIndex >= numGenericParameters) From 39f4b86aeff843dcb1596c521bac6459dab8cfd4 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 20 Jul 2022 16:18:19 -0700 Subject: [PATCH 05/14] Add tests --- .../Decoding/CustomAttributeDecoderTests.cs | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index aed29838d61cb..538f4ce563bbf 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Reflection.Metadata.Tests; using System.Reflection.PortableExecutable; @@ -87,6 +89,258 @@ public void TestCustomAttributeDecoder() } } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] + public void TestCustomAttributeDecoderGeneric() + { + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(typeof(HasGenericAttributes).GetTypeInfo().Assembly))) + using (var peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + var provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasGenericAttributes)); + + + int i = 0; + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + + switch (i++) + { + case 0: + Assert.Empty(value.FixedArguments); + Assert.Empty(value.NamedArguments); + break; + + case 1: + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("string", value.FixedArguments[0].Type); + Assert.Equal("Hello", value.FixedArguments[0].Value); + + Assert.Empty(value.NamedArguments); + break; + + case 2: + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("int32", value.FixedArguments[0].Type); + Assert.Equal(12, value.FixedArguments[0].Value); + + Assert.Empty(value.NamedArguments); + break; + + case 3: + Assert.Equal(2, value.FixedArguments.Length); + + Assert.Equal("string", value.FixedArguments[0].Type); + Assert.Equal("Hello", value.FixedArguments[0].Value); + Assert.Equal("int32", value.FixedArguments[1].Type); + Assert.Equal(12, value.FixedArguments[1].Value); + + Assert.Equal(1, value.NamedArguments.Length); + + Assert.Equal("string", value.NamedArguments[0].Type); + Assert.Equal("Bye", value.NamedArguments[0].Value); + break; + + case 4: + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("uint8", value.FixedArguments[0].Type); + Assert.Equal((byte)1, value.FixedArguments[0].Value); + + Assert.Equal(1, value.NamedArguments.Length); + + Assert.Equal("uint8", value.NamedArguments[0].Type); + Assert.Equal((byte)2, value.NamedArguments[0].Value); + break; + + case 5: + Assert.Equal(2, value.FixedArguments.Length); + + Assert.Equal("bool", value.FixedArguments[0].Type); + Assert.Equal(true, value.FixedArguments[0].Value); + Assert.Equal("int32", value.FixedArguments[1].Type); + Assert.Equal(13, value.FixedArguments[1].Value); + + Assert.Empty(value.NamedArguments); + break; + + case 6: + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests/MyEnum", value.FixedArguments[0].Type); + Assert.Equal(MyEnum.Property, Enum.ToObject(typeof(MyEnum), value.FixedArguments[0].Value)); + + Assert.Empty(value.NamedArguments); + break; + + case 7: + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("[System.Runtime]System.Type", value.FixedArguments[0].Type); + Assert.Equal(typeof(HasAttributes).FullName, value.FixedArguments[0].Value); + + Assert.Empty(value.NamedArguments); + break; + + default: + break; + } + } + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] + public void TestCustomAttributeDecoderGenericUsingReflection() + { + Type type = typeof(HasGenericAttributes); + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) + using (PEReader peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + + IList attributes= type.GetCustomAttributesData(); + + int i = 0; + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + CustomAttributeData reflectionAttribute = attributes[i++]; + + Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); + Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); + + int j = 0; + foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) + { + Assert.Equal(TypeToString(reflectionAttribute.ConstructorArguments[j].ArgumentType), arguments.Type); + if (reflectionAttribute.ConstructorArguments[j].Value.ToString() != arguments.Value.ToString()) + { + Assert.NotStrictEqual(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); + } + j++; + } + j = 0; + foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) + { + Assert.Equal(TypeToString(reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType), arguments.Type); + if (reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString() != arguments.Value.ToString()) + { + Assert.NotStrictEqual(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); + } + j++; + } + } + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] + public void TestCustomAttributeDecoderGenericArray() + { + Type type = typeof(HasGenericArrayAttributes); + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) + using (PEReader peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + + IList attributes = type.GetCustomAttributesData(); + + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + + if (value.FixedArguments.Length == 2) + { + Assert.Equal(2, value.FixedArguments.Length); + var array1 = (ImmutableArray>)(value.FixedArguments[0].Value); + Assert.Equal("int32[]", value.FixedArguments[0].Type); + Assert.Equal(1, array1[0].Value); + Assert.Equal(3, array1[2].Value); + var array2 = (ImmutableArray>)(value.FixedArguments[1].Value); + Assert.Equal("uint8[]", value.FixedArguments[1].Type); + Assert.Equal((byte)4, array2[0].Value); + Assert.Equal((byte)5, array2[1].Value); + + Assert.Empty(value.NamedArguments); + } + else + { + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("uint8", value.FixedArguments[0].Type); + Assert.Equal((byte)1, value.FixedArguments[0].Value); + + Assert.Equal(2, value.NamedArguments.Length); + + Assert.Equal("uint8", value.NamedArguments[0].Type); + Assert.Equal((byte)2, value.NamedArguments[0].Value); + + var array = (ImmutableArray>)(value.NamedArguments[1].Value); + Assert.Equal("uint8[]", value.NamedArguments[1].Type); + Assert.Equal((byte)3, array[0].Value); + } + } + } + } + + [GenericAttribute] + [GenericAttribute("Hello")] + [GenericAttribute(12)] + [GenericAttribute("Hello", 12, TProperty = "Bye")] + [GenericAttribute(1, TProperty = 2)] + [GenericAttribute2(true, 13)] + [GenericAttribute(MyEnum.Property)] + [GenericAttribute(typeof(HasAttributes))] + [GenericAttribute(TProperty = typeof(HasAttributes))] + public class HasGenericAttributes { } + + [GenericAttribute2(new int[] { 1, 2, 3 }, new byte[] { 4, 5 })] + [GenericAttribute(1, TProperty = 2, TArrayProperty = new byte[] { 3, 4 })] + public class HasGenericArrayAttributes { } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + internal class GenericAttribute : Attribute + { + public GenericAttribute() { } + public GenericAttribute(T value) + { + Field = value; + } + public GenericAttribute(T value, int count) + { + Field = value; + } + public T TProperty { get; set; } + public T[] TArrayProperty { get; set; } + public T Field; + } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + internal class GenericAttribute2 : Attribute + { + public GenericAttribute2() { } + public GenericAttribute2(K key) { } + public GenericAttribute2(K key, V value) { } + public K Key { get; set; } + public V Value { get; set; } + public K[] ArrayProperty { get; set; } + } + // no arguments [Test] @@ -335,6 +589,69 @@ public TestAttribute(UInt64Enum[] value) { } public UInt64Enum[] UInt64EnumArrayProperty { get; set; } } + private string TypeToString(Type type) + { + if (type == typeof(Type)) + return "[System.Runtime]System.Type"; + + if (type == typeof(MyEnum)) + return "System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests/MyEnum"; + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return "bool"; + + case TypeCode.Byte: + return "uint8"; + + case TypeCode.Char: + return "char"; + + case TypeCode.Double: + return "float64"; + + case TypeCode.Int16: + return "int16"; + + case TypeCode.Int32: + return "int32"; + + case TypeCode.Int64: + return "int64"; + + case TypeCode.Object: + return "object"; + + case TypeCode.SByte: + return "int8"; + + case TypeCode.Single: + return "float32"; + + case TypeCode.String: + return "string"; + + case TypeCode.UInt16: + return "uint16"; + + case TypeCode.UInt32: + return "uint32"; + + case TypeCode.UInt64: + return "uint64"; + + default: + Debug.Assert(false); + throw new ArgumentOutOfRangeException(nameof(type)); + } + } + + public enum MyEnum + { + Ctor, + Property + } private class CustomAttributeTypeProvider : DisassemblingTypeProvider, ICustomAttributeTypeProvider { @@ -382,6 +699,9 @@ public PrimitiveTypeCode GetUnderlyingEnumType(string type) if (runtimeType == typeof(UInt64Enum)) return PrimitiveTypeCode.UInt64; + if (runtimeType == typeof(MyEnum)) + return PrimitiveTypeCode.Byte; + throw new ArgumentOutOfRangeException(); } } From bf813f39a5a0386b74a4cb69599596367d80a47a Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 3 Aug 2022 16:31:19 -0700 Subject: [PATCH 06/14] Update tests to use reflection for testing all attributes --- .../Decoding/CustomAttributeDecoderTests.cs | 299 ++++++++---------- 1 file changed, 124 insertions(+), 175 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 538f4ce563bbf..3f65c6df91027 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Reflection.Metadata.Tests; @@ -19,177 +20,110 @@ public class CustomAttributeDecoderTests [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] public void TestCustomAttributeDecoder() { - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(typeof(HasAttributes).GetTypeInfo().Assembly))) - using (var peReader = new PEReader(stream)) + Type type = typeof(HasAttributes); + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) + using (PEReader peReader = new PEReader(stream)) { MetadataReader reader = peReader.GetMetadataReader(); - var provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasAttributes)); + CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + IList attributes = type.GetCustomAttributesData(); int i = 0; foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) { CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); CustomAttributeValue value = attribute.DecodeValue(provider); + CustomAttributeData reflectionAttribute = attributes[i++]; + + Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); + Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); - switch (i++) + int j = 0; + foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) { - case 0: - Assert.Empty(value.FixedArguments); - Assert.Empty(value.NamedArguments); - break; - - case 1: - Assert.Equal(3, value.FixedArguments.Length); - - Assert.Equal("string", value.FixedArguments[0].Type); - Assert.Equal("0", value.FixedArguments[0].Value); - - Assert.Equal("int32", value.FixedArguments[1].Type); - Assert.Equal(1, value.FixedArguments[1].Value); - - Assert.Equal("float64", value.FixedArguments[2].Type); - Assert.Equal(2.0, value.FixedArguments[2].Value); - - Assert.Empty(value.NamedArguments); - break; - - case 2: - Assert.Equal(3, value.NamedArguments.Length); - - Assert.Equal(CustomAttributeNamedArgumentKind.Field, value.NamedArguments[0].Kind); - Assert.Equal("StringField", value.NamedArguments[0].Name); - Assert.Equal("string", value.NamedArguments[0].Type); - Assert.Equal("0", value.NamedArguments[0].Value); - - Assert.Equal(CustomAttributeNamedArgumentKind.Field, value.NamedArguments[1].Kind); - Assert.Equal("Int32Field", value.NamedArguments[1].Name); - Assert.Equal("int32", value.NamedArguments[1].Type); - Assert.Equal(1, value.NamedArguments[1].Value); - - Assert.Equal(CustomAttributeNamedArgumentKind.Property, value.NamedArguments[2].Kind); - Assert.Equal("SByteEnumArrayProperty", value.NamedArguments[2].Name); - Assert.Equal(typeof(SByteEnum).FullName + "[]", value.NamedArguments[2].Type); - - var array = (ImmutableArray>)(value.NamedArguments[2].Value); - Assert.Equal(1, array.Length); - Assert.Equal(typeof(SByteEnum).FullName, array[0].Type); - Assert.Equal((sbyte)SByteEnum.Value, array[0].Value); - break; - - default: - // TODO: https://github.com/dotnet/runtime/issues/16552 - // The other cases are missing corresponding assertions. This needs some refactoring to - // be data-driven. A better approach would probably be to generically compare reflection - // CustomAttributeData to S.R.M CustomAttributeValue for every test attribute applied. - break; + Type t = reflectionAttribute.ConstructorArguments[j].ArgumentType; + Assert.True(TypeToString(t).Equals(arguments.Type), $"{i} {j} {arguments.Type} : {t}"); + if (t.IsArray && arguments.Value is not null) + { + ImmutableArray> array = (ImmutableArray>)(arguments.Value); + IList refArray = (IList)reflectionAttribute.ConstructorArguments[j].Value; + int k = 0; + foreach (CustomAttributeTypedArgument element in array) + { + if (refArray[k].ArgumentType.IsArray) + { + ImmutableArray> innerArray = (ImmutableArray>)(element.Value); + IList refInnerArray = (IList)refArray[k].Value; + int a = 0; + foreach (CustomAttributeTypedArgument el in innerArray) + { + if (refInnerArray[a].Value?.ToString() != el.Value?.ToString()) + { + Assert.True(refInnerArray[a].Value.Equals(el.Value), $"{i} {j} {a} {refInnerArray[a].Value} : {el.Value}"); + } + a++; + } + } + else if (refArray[k].Value?.ToString() != element.Value?.ToString()) + { + if (refArray[k].ArgumentType == typeof(Type)) // TODO: check if it is expected + { + Assert.Contains(refArray[k].Value.ToString(), element.Value.ToString()); + } + else + { + Assert.True(refArray[k].Value.Equals(element.Value), $"{i} {j} {k} {refArray[k].Value} : {element.Value}"); + } + } + k++; + } + } + else if (reflectionAttribute.ConstructorArguments[j].Value?.ToString() != arguments.Value?.ToString()) + { + if (reflectionAttribute.ConstructorArguments[j].ArgumentType == typeof(Type)) + { + Assert.Contains(reflectionAttribute.ConstructorArguments[j].Value.ToString(), arguments.Value.ToString()); + } + else + { + Assert.True(reflectionAttribute.ConstructorArguments[j].Value.Equals(arguments.Value), $"{i} {j} {reflectionAttribute.ConstructorArguments[j].Value} : {arguments.Value}"); + } + } + j++; } - } - } - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] - public void TestCustomAttributeDecoderGeneric() - { - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(typeof(HasGenericAttributes).GetTypeInfo().Assembly))) - using (var peReader = new PEReader(stream)) - { - MetadataReader reader = peReader.GetMetadataReader(); - var provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasGenericAttributes)); - - - int i = 0; - foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) - { - CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); - CustomAttributeValue value = attribute.DecodeValue(provider); - - switch (i++) + j = 0; + foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) { - case 0: - Assert.Empty(value.FixedArguments); - Assert.Empty(value.NamedArguments); - break; - - case 1: - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("string", value.FixedArguments[0].Type); - Assert.Equal("Hello", value.FixedArguments[0].Value); - - Assert.Empty(value.NamedArguments); - break; - - case 2: - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("int32", value.FixedArguments[0].Type); - Assert.Equal(12, value.FixedArguments[0].Value); - - Assert.Empty(value.NamedArguments); - break; - - case 3: - Assert.Equal(2, value.FixedArguments.Length); - - Assert.Equal("string", value.FixedArguments[0].Type); - Assert.Equal("Hello", value.FixedArguments[0].Value); - Assert.Equal("int32", value.FixedArguments[1].Type); - Assert.Equal(12, value.FixedArguments[1].Value); - - Assert.Equal(1, value.NamedArguments.Length); - - Assert.Equal("string", value.NamedArguments[0].Type); - Assert.Equal("Bye", value.NamedArguments[0].Value); - break; - - case 4: - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("uint8", value.FixedArguments[0].Type); - Assert.Equal((byte)1, value.FixedArguments[0].Value); - - Assert.Equal(1, value.NamedArguments.Length); - - Assert.Equal("uint8", value.NamedArguments[0].Type); - Assert.Equal((byte)2, value.NamedArguments[0].Value); - break; - - case 5: - Assert.Equal(2, value.FixedArguments.Length); - - Assert.Equal("bool", value.FixedArguments[0].Type); - Assert.Equal(true, value.FixedArguments[0].Value); - Assert.Equal("int32", value.FixedArguments[1].Type); - Assert.Equal(13, value.FixedArguments[1].Value); - - Assert.Empty(value.NamedArguments); - break; - - case 6: - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests/MyEnum", value.FixedArguments[0].Type); - Assert.Equal(MyEnum.Property, Enum.ToObject(typeof(MyEnum), value.FixedArguments[0].Value)); - - Assert.Empty(value.NamedArguments); - break; - - case 7: - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("[System.Runtime]System.Type", value.FixedArguments[0].Type); - Assert.Equal(typeof(HasAttributes).FullName, value.FixedArguments[0].Value); - - Assert.Empty(value.NamedArguments); - break; - - default: - break; + Type t = reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType; + Assert.True(TypeToString(t).Equals(arguments.Type), $"{i} {j} {t} : {arguments.Type}"); + if (t.IsArray && arguments.Value is not null) + { + ImmutableArray> array = (ImmutableArray>)(arguments.Value); + IList refArray = (IList)reflectionAttribute.NamedArguments[j].TypedValue.Value; + int k = 0; + foreach (CustomAttributeTypedArgument element in array) + { + if (refArray[k].Value?.ToString() != element.Value?.ToString()) + { + Assert.True(refArray[k].Value.Equals(element.Value), $"{i} {j} {k} {element}"); + } + k++; + } + } + else if (reflectionAttribute.NamedArguments[j].TypedValue.Value?.ToString() != arguments.Value?.ToString()) + { + if (reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType == typeof(Type)) // typeof operator used for named parameter, like [Test(TypeField = typeof(string))] + { + Assert.Contains(reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString(), arguments.Value.ToString()); + } + else + { + Assert.True(reflectionAttribute.NamedArguments[j].TypedValue.Value.Equals(arguments.Value), $"{i} {j} {reflectionAttribute.NamedArguments[j].TypedValue.Value} : {arguments.Value}"); + } + } + j++; } } } @@ -226,7 +160,7 @@ public void TestCustomAttributeDecoderGenericUsingReflection() Assert.Equal(TypeToString(reflectionAttribute.ConstructorArguments[j].ArgumentType), arguments.Type); if (reflectionAttribute.ConstructorArguments[j].Value.ToString() != arguments.Value.ToString()) { - Assert.NotStrictEqual(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); + Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); } j++; } @@ -236,7 +170,7 @@ public void TestCustomAttributeDecoderGenericUsingReflection() Assert.Equal(TypeToString(reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType), arguments.Type); if (reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString() != arguments.Value.ToString()) { - Assert.NotStrictEqual(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); + Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); } j++; } @@ -267,11 +201,11 @@ public void TestCustomAttributeDecoderGenericArray() if (value.FixedArguments.Length == 2) { Assert.Equal(2, value.FixedArguments.Length); - var array1 = (ImmutableArray>)(value.FixedArguments[0].Value); + ImmutableArray> array1 = (ImmutableArray>)(value.FixedArguments[0].Value); Assert.Equal("int32[]", value.FixedArguments[0].Type); Assert.Equal(1, array1[0].Value); Assert.Equal(3, array1[2].Value); - var array2 = (ImmutableArray>)(value.FixedArguments[1].Value); + ImmutableArray> array2 = (ImmutableArray>)(value.FixedArguments[1].Value); Assert.Equal("uint8[]", value.FixedArguments[1].Type); Assert.Equal((byte)4, array2[0].Value); Assert.Equal((byte)5, array2[1].Value); @@ -290,7 +224,7 @@ public void TestCustomAttributeDecoderGenericArray() Assert.Equal("uint8", value.NamedArguments[0].Type); Assert.Equal((byte)2, value.NamedArguments[0].Value); - var array = (ImmutableArray>)(value.NamedArguments[1].Value); + ImmutableArray> array = (ImmutableArray>)(value.NamedArguments[1].Value); Assert.Equal("uint8[]", value.NamedArguments[1].Type); Assert.Equal((byte)3, array[0].Value); } @@ -304,7 +238,7 @@ public void TestCustomAttributeDecoderGenericArray() [GenericAttribute("Hello", 12, TProperty = "Bye")] [GenericAttribute(1, TProperty = 2)] [GenericAttribute2(true, 13)] - [GenericAttribute(MyEnum.Property)] + // [GenericAttribute(MyEnum.Property)] TODO: https://github.com/dotnet/runtime/issues/16552 [GenericAttribute(typeof(HasAttributes))] [GenericAttribute(TProperty = typeof(HasAttributes))] public class HasGenericAttributes { } @@ -376,17 +310,17 @@ public GenericAttribute2(K key, V value) { } [Test(true)] [Test(false)] [Test(typeof(string))] - [Test(SByteEnum.Value)] - [Test(Int16Enum.Value)] - [Test(Int32Enum.Value)] + /* [Test(SByteEnum.Value)] // The FullName is (System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests+SByteEnum) + [Test(Int16Enum.Value)] // but some enums '+' is replaced with '/' and causing inconsistency + [Test(Int32Enum.Value)] // Updaated https://github.com/dotnet/runtime/issues/16552 to resolve this scenario later [Test(Int64Enum.Value)] [Test(ByteEnum.Value)] [Test(UInt16Enum.Value)] [Test(UInt32Enum.Value)] - [Test(UInt64Enum.Value)] + [Test(UInt64Enum.Value)]*/ [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - [Test(new Int32Enum[] { Int32Enum.Value })] + //[Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 // same single fixed arguments as above, typed as object [Test((object)("string"))] @@ -404,8 +338,8 @@ public GenericAttribute2(K key, V value) { } [Test((object)(true))] [Test((object)(false))] [Test((object)(typeof(string)))] - [Test((object)(SByteEnum.Value))] - [Test((object)(Int16Enum.Value))] + [Test((object)(SByteEnum.Value))] + [Test((object)(Int16Enum.Value))] [Test((object)(Int32Enum.Value))] [Test((object)(Int64Enum.Value))] [Test((object)(ByteEnum.Value))] @@ -432,7 +366,7 @@ public GenericAttribute2(K key, V value) { } (uint)4, true, false, - typeof(string), + typeof(string), // check if the produced value is expected SByteEnum.Value, Int16Enum.Value, Int32Enum.Value, @@ -473,7 +407,7 @@ public GenericAttribute2(K key, V value) { } [Test(UInt64EnumField = UInt64Enum.Value)] [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - [Test(new Int32Enum[] { Int32Enum.Value })] + // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 // null named arguments [Test(ObjectField = null)] @@ -594,9 +528,24 @@ private string TypeToString(Type type) if (type == typeof(Type)) return "[System.Runtime]System.Type"; - if (type == typeof(MyEnum)) - return "System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests/MyEnum"; + if (type.IsArray) + { + if (type.GetElementType().IsEnum) + { + Type el = type.GetElementType(); + return type.FullName; + } + return GetPrimitiveType(type.GetElementType()) + "[]"; + } + if (type.IsEnum) + return type.FullName; + + return GetPrimitiveType(type); + } + + private static string GetPrimitiveType(Type type) + { switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: From 1d8f656b48ac430e250f46e7eb3dd937e9684666 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 3 Aug 2022 16:51:22 -0700 Subject: [PATCH 07/14] Remove attribute row printing logic --- .../Decoding/CustomAttributeDecoderTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 466496a590593..0dc35212d9506 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -43,7 +43,7 @@ public void TestCustomAttributeDecoder() foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) { Type t = reflectionAttribute.ConstructorArguments[j].ArgumentType; - Assert.True(TypeToString(t).Equals(arguments.Type), $"{i} {j} {arguments.Type} : {t}"); + Assert.Equal(TypeToString(t), arguments.Type); if (t.IsArray && arguments.Value is not null) { ImmutableArray> array = (ImmutableArray>)(arguments.Value); @@ -60,7 +60,7 @@ public void TestCustomAttributeDecoder() { if (refInnerArray[a].Value?.ToString() != el.Value?.ToString()) { - Assert.True(refInnerArray[a].Value.Equals(el.Value), $"{i} {j} {a} {refInnerArray[a].Value} : {el.Value}"); + Assert.Equal(refInnerArray[a].Value, el.Value); } a++; } @@ -73,7 +73,7 @@ public void TestCustomAttributeDecoder() } else { - Assert.True(refArray[k].Value.Equals(element.Value), $"{i} {j} {k} {refArray[k].Value} : {element.Value}"); + Assert.Equal(refArray[k].Value, element.Value); } } k++; @@ -87,7 +87,7 @@ public void TestCustomAttributeDecoder() } else { - Assert.True(reflectionAttribute.ConstructorArguments[j].Value.Equals(arguments.Value), $"{i} {j} {reflectionAttribute.ConstructorArguments[j].Value} : {arguments.Value}"); + Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); } } j++; @@ -96,7 +96,7 @@ public void TestCustomAttributeDecoder() foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) { Type t = reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType; - Assert.True(TypeToString(t).Equals(arguments.Type), $"{i} {j} {t} : {arguments.Type}"); + Assert.Equal(TypeToString(t), arguments.Type); if (t.IsArray && arguments.Value is not null) { ImmutableArray> array = (ImmutableArray>)(arguments.Value); @@ -106,20 +106,20 @@ public void TestCustomAttributeDecoder() { if (refArray[k].Value?.ToString() != element.Value?.ToString()) { - Assert.True(refArray[k].Value.Equals(element.Value), $"{i} {j} {k} {element}"); + Assert.Equal(refArray[k].Value, element.Value); } k++; } } else if (reflectionAttribute.NamedArguments[j].TypedValue.Value?.ToString() != arguments.Value?.ToString()) { - if (reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType == typeof(Type)) // typeof operator used for named parameter, like [Test(TypeField = typeof(string))] + if (reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType == typeof(Type)) // typeof operator used for named parameter, like [Test(TypeField = typeof(string))], check if it is expected { Assert.Contains(reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString(), arguments.Value.ToString()); } else { - Assert.True(reflectionAttribute.NamedArguments[j].TypedValue.Value.Equals(arguments.Value), $"{i} {j} {reflectionAttribute.NamedArguments[j].TypedValue.Value} : {arguments.Value}"); + Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); } } j++; From 807459ce6e8e8c5a5593ae42af147f36435d46b3 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 8 Aug 2022 14:35:00 -0700 Subject: [PATCH 08/14] Disable test on mono --- .../Decoding/CustomAttributeDecoderTests.cs | 90 ++++++++++++++++--- 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 0dc35212d9506..883d7b71d04d3 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -1,11 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.IO; using System.Reflection.Metadata.Tests; using System.Reflection.PortableExecutable; @@ -15,9 +12,81 @@ namespace System.Reflection.Metadata.Decoding.Tests { public class CustomAttributeDecoderTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoder() + { + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(typeof(HasAttributes).GetTypeInfo().Assembly))) + using (var peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + var provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasAttributes)); + + int i = 0; + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + + switch (i++) + { + case 0: + Assert.Empty(value.FixedArguments); + Assert.Empty(value.NamedArguments); + break; + + case 1: + Assert.Equal(3, value.FixedArguments.Length); + + Assert.Equal("string", value.FixedArguments[0].Type); + Assert.Equal("0", value.FixedArguments[0].Value); + + Assert.Equal("int32", value.FixedArguments[1].Type); + Assert.Equal(1, value.FixedArguments[1].Value); + + Assert.Equal("float64", value.FixedArguments[2].Type); + Assert.Equal(2.0, value.FixedArguments[2].Value); + + Assert.Empty(value.NamedArguments); + break; + + case 2: + Assert.Equal(3, value.NamedArguments.Length); + + Assert.Equal(CustomAttributeNamedArgumentKind.Field, value.NamedArguments[0].Kind); + Assert.Equal("StringField", value.NamedArguments[0].Name); + Assert.Equal("string", value.NamedArguments[0].Type); + Assert.Equal("0", value.NamedArguments[0].Value); + + Assert.Equal(CustomAttributeNamedArgumentKind.Field, value.NamedArguments[1].Kind); + Assert.Equal("Int32Field", value.NamedArguments[1].Name); + Assert.Equal("int32", value.NamedArguments[1].Type); + Assert.Equal(1, value.NamedArguments[1].Value); + + Assert.Equal(CustomAttributeNamedArgumentKind.Property, value.NamedArguments[2].Kind); + Assert.Equal("SByteEnumArrayProperty", value.NamedArguments[2].Name); + Assert.Equal(typeof(SByteEnum).FullName + "[]", value.NamedArguments[2].Type); + + var array = (ImmutableArray>)(value.NamedArguments[2].Value); + Assert.Equal(1, array.Length); + Assert.Equal(typeof(SByteEnum).FullName, array[0].Type); + Assert.Equal((sbyte)SByteEnum.Value, array[0].Value); + break; + + default: + // TODO: https://github.com/dotnet/runtime/issues/73593 + // This method only tests first 3 attriubtes because the complete test 'TestCustomAttributeDecoderUsingReflection' fails on mono + // Leaving this hard coded test only for mono, until the issue fixed on mono + break; + } + } + } + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73593", TestRuntimes.Mono)] + public void TestCustomAttributeDecoderUsingReflection() { Type type = typeof(HasAttributes); using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) @@ -128,9 +197,8 @@ public void TestCustomAttributeDecoder() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] public void TestCustomAttributeDecoderGenericUsingReflection() { Type type = typeof(HasGenericAttributes); @@ -177,9 +245,8 @@ public void TestCustomAttributeDecoderGenericUsingReflection() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Type assembly name is different on .NET Framework.")] public void TestCustomAttributeDecoderGenericArray() { Type type = typeof(HasGenericArrayAttributes); @@ -319,7 +386,7 @@ public GenericAttribute2(K key, V value) { } [Test(UInt64Enum.Value)]*/ [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - //[Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 + // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 // same single fixed arguments as above, typed as object [Test((object)("string"))] @@ -337,8 +404,8 @@ public GenericAttribute2(K key, V value) { } [Test((object)(true))] [Test((object)(false))] [Test((object)(typeof(string)))] - [Test((object)(SByteEnum.Value))] - [Test((object)(Int16Enum.Value))] + [Test((object)(SByteEnum.Value))] + [Test((object)(Int16Enum.Value))] [Test((object)(Int32Enum.Value))] [Test((object)(Int64Enum.Value))] [Test((object)(ByteEnum.Value))] @@ -590,7 +657,6 @@ private static string GetPrimitiveType(Type type) return "uint64"; default: - Debug.Assert(false); throw new ArgumentOutOfRangeException(nameof(type)); } } From 613d6b37c0ee9ab47804035b5fafa6861c70b9bb Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 8 Aug 2022 23:47:34 -0700 Subject: [PATCH 09/14] Add back [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] --- .../Metadata/Decoding/CustomAttributeDecoderTests.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 883d7b71d04d3..a1f6355201053 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -12,7 +12,7 @@ namespace System.Reflection.Metadata.Decoding.Tests { public class CustomAttributeDecoderTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles), nameof(PlatformDetection.IsMonoRuntime))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoder() { @@ -84,7 +84,7 @@ public void TestCustomAttributeDecoder() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/73593", TestRuntimes.Mono)] public void TestCustomAttributeDecoderUsingReflection() { @@ -197,7 +197,8 @@ public void TestCustomAttributeDecoderUsingReflection() } } - [Fact] +#if NETCOREAPP // Generic attribute is not supported on .NET Framework. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoderGenericUsingReflection() { @@ -245,7 +246,7 @@ public void TestCustomAttributeDecoderGenericUsingReflection() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoderGenericArray() { @@ -340,6 +341,7 @@ public GenericAttribute2(K key, V value) { } public V Value { get; set; } public K[] ArrayProperty { get; set; } } +#endif // no arguments [Test] @@ -592,7 +594,7 @@ public TestAttribute(UInt64Enum[] value) { } private string TypeToString(Type type) { if (type == typeof(Type)) - return "[System.Runtime]System.Type"; + return $"[{MetadataReaderTestHelpers.RuntimeAssemblyName}]System.Type"; if (type.IsArray) { From 881e3b972fcfcbbfec1ac698a3410ec42d0b21af Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 10 Aug 2022 09:59:06 -0700 Subject: [PATCH 10/14] If def out mobile targets --- .../tests/Metadata/Decoding/CustomAttributeDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index a1f6355201053..9fa7052d83d14 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -197,7 +197,7 @@ public void TestCustomAttributeDecoderUsingReflection() } } -#if NETCOREAPP // Generic attribute is not supported on .NET Framework. +#if NETCOREAPP && !TargetsMobile // Generic attribute is not supported on .NET Framework. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoderGenericUsingReflection() From 47a77cdf10d02af41ee71f0902572d9a8c11b3f4 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 10 Aug 2022 11:44:09 -0700 Subject: [PATCH 11/14] Update const --- .../tests/Metadata/Decoding/CustomAttributeDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 9fa7052d83d14..32b07d3211926 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -197,7 +197,7 @@ public void TestCustomAttributeDecoderUsingReflection() } } -#if NETCOREAPP && !TargetsMobile // Generic attribute is not supported on .NET Framework. +#if NETCOREAPP && !TARGET_WASM // Generic attribute is not supported on .NET Framework. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoderGenericUsingReflection() From 04081570170ebbbd17891b56a5fbdbad896a1e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Aug 2022 06:19:06 +0900 Subject: [PATCH 12/14] Use TARGET_BROWSER --- .../tests/Metadata/Decoding/CustomAttributeDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index 32b07d3211926..62944bef46d20 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -197,7 +197,7 @@ public void TestCustomAttributeDecoderUsingReflection() } } -#if NETCOREAPP && !TARGET_WASM // Generic attribute is not supported on .NET Framework. +#if NETCOREAPP && !TARGET_BROWSER // Generic attribute is not supported on .NET Framework. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoderGenericUsingReflection() From 9c21e8a8b4ae8f4d1022436c30bf7d26d6c6a7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Aug 2022 09:21:15 +0900 Subject: [PATCH 13/14] Define TARGET_BROWSER --- .../tests/System.Reflection.Metadata.Tests.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj index d611b766fc215..d13d2e1fc76d5 100644 --- a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj +++ b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj @@ -7,6 +7,7 @@ $(NoWarn);436;SYSLIB0037 $(NetCoreAppCurrent);$(NetFrameworkMinimum) true + $(DefineConstants);TARGET_BROWSER - \ No newline at end of file + From 3a6a78127a11aa55391eb648f21dd9a8e472ede0 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Wed, 10 Aug 2022 17:37:21 -0700 Subject: [PATCH 14/14] Update src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj --- .../tests/System.Reflection.Metadata.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj index d13d2e1fc76d5..465eca5fba4be 100644 --- a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj +++ b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj @@ -7,7 +7,7 @@ $(NoWarn);436;SYSLIB0037 $(NetCoreAppCurrent);$(NetFrameworkMinimum) true - $(DefineConstants);TARGET_BROWSER + $(DefineConstants);TARGET_BROWSER