This is due to the structure of the blob structure, where user attribute e is specified .
The values โโof the array begin with an integer indicating the number of elements in the array, then the values โโof the element are combined together.
A null array is represented using a length of -1.
The enumeration argument is represented using byte 0x55 , followed by a string specifying the name and assembly of the enumeration type.
Unfortunately, what happens if you pass the enum array as null, this rename name will be lost.
In terms of native debugging, this is the corresponding source code.
else if (encodedType == CustomAttributeEncoding.Array) { encodedType = encodedArg.CustomAttributeType.EncodedArrayType; Type elementType; if (encodedType == CustomAttributeEncoding.Enum) { elementType = ResolveType(scope, encodedArg.CustomAttributeType.EnumName); }
And so the c.tor instance is created
for (int i = 0; i < parameters.Length; i++) m_ctorParams[i] = new CustomAttributeCtorParameter(InitCustomAttributeType((RuntimeType)parameters[i].ParameterType));
The problem is that the enum value is simply represented using the base value (basically int): the CLR implementation ( RuntimeType ) needs to look at the signature of the attribute constructor to interpret it, but custom attribute signatures are slightly different from other types of signatures encoded in the assembly. NET
In particular, without a specific encodedArrayType (from GetElementType ), the following if it becomes false (and enumName remains zero)
if (encodedType == CustomAttributeEncoding.Array) { parameterType = (RuntimeType)parameterType.GetElementType(); encodedArrayType = CustomAttributeData.TypeToCustomAttributeEncoding(parameterType); } if (encodedType == CustomAttributeEncoding.Enum || encodedArrayType == CustomAttributeEncoding.Enum) { encodedEnumType = TypeToCustomAttributeEncoding((RuntimeType)Enum.GetUnderlyingType(parameterType)); enumName = parameterType.AssemblyQualifiedName; }
ILDASM
You can find a .custom instance of Main from ildasm
when
[Test(new[] { Test.Bar }, null)] static void Main(string[] args)
this (note that FF FF FF FF means array size -1 )
.custom instance void TestAttribute::.ctor(valuetype Test[], valuetype Test[]) = ( 01 00 01 00 00 00 01 00 00 00 FF FF FF FF 00 00 )
and for
[Test(new[] { Test.Bar }, new Test[] { })] static void Main(string[] args)
you see
.custom instance void TestAttribute::.ctor(valuetype Test[], valuetype Test[]) = ( 01 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 )
CLR virtual machine
Finally, you have confirmation that the CLR virtual machine reads the blob of a user attribute into an array only when the size is other than -1
case SERIALIZATION_TYPE_SZARRAY: typeArray: { // read size BOOL isObject = FALSE; int size = (int)GetDataFromBlob(pCtorAssembly, SERIALIZATION_TYPE_I4, nullTH, pBlob, endBlob, pModule, &isObject); _ASSERTE(!isObject); if (size != -1) { CorSerializationType arrayType; if (th.IsEnum()) arrayType = SERIALIZATION_TYPE_ENUM; else arrayType = (CorSerializationType)th.GetInternalCorElementType(); BASEARRAYREF array = NULL; GCPROTECT_BEGIN(array); ReadArray(pCtorAssembly, arrayType, size, th, pBlob, endBlob, pModule, &array); retValue = ObjToArgSlot(array); GCPROTECT_END(); } *bObjectCreated = TRUE; break; }
In conclusion, in this case, constructor arguments are not created inside C #, therefore, they can only be obtained from the constructor itself: in fact, the user attribute is created (via CreateCaObject ) in the CLR virtual machine, calling its contructor with unsafe pointers (directly to the blob)
[MethodImplAttribute(MethodImplOptions.InternalCall)] private static unsafe extern Object _CreateCaObject(RuntimeModule pModule, IRuntimeMethodInfo pCtor, byte** ppBlob, byte* pEndBlob, int* pcNamedArgs); [System.Security.SecurityCritical] // auto-generated private static unsafe Object CreateCaObject(RuntimeModule module, IRuntimeMethodInfo ctor, ref IntPtr blob, IntPtr blobEnd, out int namedArgs) { byte* pBlob = (byte*)blob; byte* pBlobEnd = (byte*)blobEnd; int cNamedArgs; object ca = _CreateCaObject(module, ctor, &pBlob, pBlobEnd, &cNamedArgs); blob = (IntPtr)pBlob; namedArgs = cNamedArgs; return ca; }
Possible error
The critical point for a possible error is
unsafe { ParseAttributeArguments( attributeBlob.Signature, (int)attributeBlob.Length, ref customAttributeCtorParameters, ref customAttributeNamedParameters, (RuntimeAssembly)customAttributeModule.Assembly); }
implemented in
FCIMPL5(VOID, Attribute::ParseAttributeArguments, void* pCa, INT32 cCa, CaArgArrayREF* ppCustomAttributeArguments, CaNamedArgArrayREF* ppCustomAttributeNamedArguments, AssemblyBaseObject* pAssemblyUNSAFE)
The following may be considered:
cArgs = (*ppCustomAttributeArguments)->GetNumComponents(); if (cArgs) { gc.pArgs = (*ppCustomAttributeArguments)->GetDirectPointerToNonObjectElements();
Proposed Fix
You can find this problem repeatedly in CoreCLR with the proposed FIX from github.