Skip to content

Commit f7b64f7

Browse files
authored
Fix custom attribute with enum on generic type (#827)
* Fix custom attribute with enum on generic type Fixes both the reader and the write to correctly handle values of type enum on a generic type. Cecil represents generic instantiations as typeref which has etype GenericInst, so the exising check for etype doesn't work. Also since attributes only allow simple values and enums (and types), there's technically no other way to get a GenericInst then the enum case. Added several test for various combinations of boxed an unboxed enums on generic type. Added a test case provided by @mrvoorhe with array of such enums. * Disable the new tests on .NET 4 The CodeDom compiler doesn't support parsing enums on generic types in attributes (uses the "old" csc.exe from framework).
1 parent 79b43e8 commit f7b64f7

File tree

5 files changed

+156
-2
lines changed

5 files changed

+156
-2
lines changed

Mono.Cecil/AssemblyReader.cs

+6
Original file line numberDiff line numberDiff line change
@@ -3598,6 +3598,12 @@ CustomAttributeArgument ReadCustomAttributeElement (TypeReference type)
35983598
object ReadCustomAttributeElementValue (TypeReference type)
35993599
{
36003600
var etype = type.etype;
3601+
if (etype == ElementType.GenericInst) {
3602+
// The only way to get a generic here is that it's an enum on a generic type
3603+
// so for enum we don't need to know the generic arguments (they have no effect)
3604+
type = type.GetElementType ();
3605+
etype = type.etype;
3606+
}
36013607

36023608
switch (etype) {
36033609
case ElementType.String:

Mono.Cecil/AssemblyWriter.cs

+12
Original file line numberDiff line numberDiff line change
@@ -2993,6 +2993,10 @@ void WriteCustomAttributeValue (TypeReference type, object value)
29932993
else
29942994
WriteCustomAttributeEnumValue (type, value);
29952995
break;
2996+
case ElementType.GenericInst:
2997+
// Generic instantiation can only happen for an enum (no other generic like types can appear in attribute value)
2998+
WriteCustomAttributeEnumValue (type, value);
2999+
break;
29963000
default:
29973001
WritePrimitiveValue (value);
29983002
break;
@@ -3099,6 +3103,14 @@ void WriteCustomAttributeFieldOrPropType (TypeReference type)
30993103
WriteTypeReference (type);
31003104
}
31013105
return;
3106+
case ElementType.GenericInst:
3107+
// Generic instantiation can really only happen if it's an enum type since no other
3108+
// types are allowed in the attribute value.
3109+
// Enums are special in attribute data, they're encoded as ElementType.Enum followed by a typeref
3110+
// followed by the value.
3111+
WriteElementType (ElementType.Enum);
3112+
WriteTypeReference (type);
3113+
return;
31023114
default:
31033115
WriteElementType (etype);
31043116
return;

Test/Mono.Cecil.Tests/CompilationService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ static Compilation GetCompilation (string name)
186186
case ".cs":
187187
return CS.CSharpCompilation.Create (
188188
assemblyName,
189-
new [] { CS.SyntaxFactory.ParseSyntaxTree (source) },
189+
new [] { CS.SyntaxFactory.ParseSyntaxTree (source, new CS.CSharpParseOptions (preprocessorSymbols: new string [] { "NET_CORE" })) },
190190
references,
191191
new CS.CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));
192192
default:

Test/Mono.Cecil.Tests/CustomAttributesTests.cs

+100-1
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,105 @@ public void DefineCustomAttributeFromBlob ()
546546
module.Dispose ();
547547
}
548548

549+
#if NET_CORE
550+
[Test]
551+
public void BoxedEnumOnGenericArgumentOnType ()
552+
{
553+
TestCSharp ("CustomAttributes.cs", module => {
554+
var valueEnumGenericType = module.GetType ("BoxedValueEnumOnGenericType");
555+
556+
Assert.IsTrue (valueEnumGenericType.HasCustomAttributes);
557+
Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count);
558+
559+
var attribute = valueEnumGenericType.CustomAttributes [0];
560+
Assert.AreEqual ("System.Void FooAttribute::.ctor(System.Object,System.Object)",
561+
attribute.Constructor.FullName);
562+
563+
Assert.IsTrue (attribute.HasConstructorArguments);
564+
Assert.AreEqual (2, attribute.ConstructorArguments.Count);
565+
566+
AssertCustomAttributeArgument ("(Object:(GenericWithEnum`1/OnGenericNumber<System.Int32>:0))", attribute.ConstructorArguments [0]);
567+
AssertCustomAttributeArgument ("(Object:(GenericWithEnum`1/OnGenericNumber<System.String>:1))", attribute.ConstructorArguments [1]);
568+
});
569+
}
570+
571+
[Test]
572+
public void EnumOnGenericArgumentOnType ()
573+
{
574+
TestCSharp ("CustomAttributes.cs", module => {
575+
var valueEnumGenericType = module.GetType ("ValueEnumOnGenericType");
576+
577+
Assert.IsTrue (valueEnumGenericType.HasCustomAttributes);
578+
Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count);
579+
580+
var attribute = valueEnumGenericType.CustomAttributes [0];
581+
Assert.AreEqual ("System.Void FooAttribute::.ctor(GenericWithEnum`1/OnGenericNumber<Bingo>)",
582+
attribute.Constructor.FullName);
583+
584+
Assert.IsTrue (attribute.HasConstructorArguments);
585+
Assert.AreEqual (1, attribute.ConstructorArguments.Count);
586+
587+
AssertCustomAttributeArgument ("(GenericWithEnum`1/OnGenericNumber<Bingo>:1)", attribute.ConstructorArguments [0]);
588+
});
589+
}
590+
591+
[Test]
592+
public void EnumOnGenericFieldOnType ()
593+
{
594+
TestCSharp ("CustomAttributes.cs", module => {
595+
var valueEnumGenericType = module.GetType ("FieldEnumOnGenericType");
596+
597+
Assert.IsTrue (valueEnumGenericType.HasCustomAttributes);
598+
Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count);
599+
600+
var attribute = valueEnumGenericType.CustomAttributes [0];
601+
var argument = attribute.Fields.Where (a => a.Name == "NumberEnumField").First ().Argument;
602+
603+
AssertCustomAttributeArgument ("(GenericWithEnum`1/OnGenericNumber<System.Byte>:0)", argument);
604+
});
605+
}
606+
607+
[Test]
608+
public void EnumOnGenericPropertyOnType ()
609+
{
610+
TestCSharp ("CustomAttributes.cs", module => {
611+
var valueEnumGenericType = module.GetType ("PropertyEnumOnGenericType");
612+
613+
Assert.IsTrue (valueEnumGenericType.HasCustomAttributes);
614+
Assert.AreEqual (1, valueEnumGenericType.CustomAttributes.Count);
615+
616+
var attribute = valueEnumGenericType.CustomAttributes [0];
617+
var argument = attribute.Properties.Where (a => a.Name == "NumberEnumProperty").First ().Argument;
618+
619+
AssertCustomAttributeArgument ("(GenericWithEnum`1/OnGenericNumber<System.Byte>:1)", argument);
620+
});
621+
}
622+
623+
[Test]
624+
public void EnumDeclaredInGenericTypeArray ()
625+
{
626+
TestCSharp ("CustomAttributes.cs", module => {
627+
var type = module.GetType ("WithAttributeUsingNestedEnumArray");
628+
var attributes = type.CustomAttributes;
629+
Assert.AreEqual (1, attributes.Count);
630+
var attribute = attributes [0];
631+
Assert.AreEqual (1, attribute.Fields.Count);
632+
var arg = attribute.Fields [0].Argument;
633+
Assert.AreEqual ("System.Object", arg.Type.FullName);
634+
635+
var argumentValue = (CustomAttributeArgument)arg.Value;
636+
Assert.AreEqual ("GenericWithEnum`1/OnGenericNumber<System.String>[]", argumentValue.Type.FullName);
637+
var argumentValues = (CustomAttributeArgument [])argumentValue.Value;
638+
639+
Assert.AreEqual ("GenericWithEnum`1/OnGenericNumber<System.String>", argumentValues [0].Type.FullName);
640+
Assert.AreEqual (0, (int)argumentValues [0].Value);
641+
642+
Assert.AreEqual ("GenericWithEnum`1/OnGenericNumber<System.String>", argumentValues [1].Type.FullName);
643+
Assert.AreEqual (1, (int)argumentValues [1].Value);
644+
});
645+
}
646+
#endif
647+
549648
static void AssertCustomAttribute (string expected, CustomAttribute attribute)
550649
{
551650
Assert.AreEqual (expected, PrettyPrint (attribute));
@@ -643,7 +742,7 @@ static void PrettyPrint (TypeReference type, StringBuilder signature)
643742
if (type.IsArray) {
644743
ArrayType array = (ArrayType) type;
645744
signature.AppendFormat ("{0}[]", array.ElementType.etype.ToString ());
646-
} else if (type.etype == ElementType.None) {
745+
} else if (type.etype == ElementType.None || type.etype == ElementType.GenericInst) {
647746
signature.Append (type.FullName);
648747
} else
649748
signature.Append (type.etype.ToString ());

Test/Resources/cs/CustomAttributes.cs

+37
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ enum Bingo : short {
1111
Binga = 4,
1212
}
1313

14+
class GenericWithEnum<T> {
15+
public enum OnGenericNumber
16+
{
17+
One,
18+
Two
19+
}
20+
}
21+
1422
/*
1523
in System.Security.AccessControl
1624
@@ -70,13 +78,20 @@ public FooAttribute (Type type)
7078
{
7179
}
7280

81+
public FooAttribute (GenericWithEnum<Bingo>.OnGenericNumber number)
82+
{
83+
}
84+
7385
public int Bang { get { return 0; } set {} }
7486
public string Fiou { get { return "fiou"; } set {} }
7587

7688
public object Pan;
7789
public string [] PanPan;
7890

7991
public Type Chose;
92+
93+
public GenericWithEnum<byte>.OnGenericNumber NumberEnumField;
94+
public GenericWithEnum<byte>.OnGenericNumber NumberEnumProperty { get; set; }
8095
}
8196

8297
[Foo ("bar")]
@@ -160,3 +175,25 @@ public class Child {
160175
[Foo ("Foo\0Bar\0")]
161176
class NullCharInString {
162177
}
178+
179+
#if NET_CORE
180+
[Foo (GenericWithEnum<int>.OnGenericNumber.One, GenericWithEnum<string>.OnGenericNumber.Two)]
181+
class BoxedValueEnumOnGenericType {
182+
}
183+
184+
[Foo (GenericWithEnum<Bingo>.OnGenericNumber.Two)]
185+
class ValueEnumOnGenericType {
186+
}
187+
188+
[Foo (NumberEnumField = GenericWithEnum<byte>.OnGenericNumber.One)]
189+
class FieldEnumOnGenericType {
190+
}
191+
192+
[Foo(NumberEnumProperty = GenericWithEnum<byte>.OnGenericNumber.Two)]
193+
class PropertyEnumOnGenericType {
194+
}
195+
196+
[Foo(Pan = new[] { GenericWithEnum<string>.OnGenericNumber.One, GenericWithEnum<string>.OnGenericNumber.Two })]
197+
class WithAttributeUsingNestedEnumArray {
198+
}
199+
#endif

0 commit comments

Comments
 (0)