// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ILAssembler;
internal static partial class PseudoCustomAttributes
{
private static bool Apply(LoweringContext context, KnownAttribute known)
{
if ((known.Targets & GetTarget(context.Owner)) == 0)
{
return context.InvalidTarget();
}
if (!TryParseArguments(context, known, out CustomAttributeValue<SerializationTypeCode> arguments))
{
return false;
}
return known.Kind switch
{
KnownAttributeKind.DllImport => ApplyDllImport(context, arguments),
KnownAttributeKind.ComImport => AddTypeFlags(context, TypeAttributes.Import),
#pragma warning disable SYSLIB0050 // Formatter-based serialization APIs are obsolete.
KnownAttributeKind.Serializable => AddTypeFlags(context, TypeAttributes.Serializable),
KnownAttributeKind.NonSerialized => AddFieldFlags(context, FieldAttributes.NotSerialized),
#pragma warning restore SYSLIB0050 // Formatter-based serialization APIs are obsolete.
KnownAttributeKind.MethodImpl1 or KnownAttributeKind.MethodImpl2 or KnownAttributeKind.MethodImpl3 =>
ApplyMethodImpl(context, known.Kind, arguments),
KnownAttributeKind.MarshalAs1 or KnownAttributeKind.MarshalAs2 => ApplyMarshalAs(context, arguments),
KnownAttributeKind.PreserveSig => AddMethodImplFlags(context, MethodImplAttributes.PreserveSig),
KnownAttributeKind.In => AddParameterFlags(context, ParameterAttributes.In),
KnownAttributeKind.Out => AddParameterFlags(context, ParameterAttributes.Out),
KnownAttributeKind.Optional => AddParameterFlags(context, ParameterAttributes.Optional),
KnownAttributeKind.StructLayout1 or KnownAttributeKind.StructLayout2 =>
ApplyStructLayout(context, known.Kind, arguments),
KnownAttributeKind.FieldOffset => ApplyFieldOffset(context, arguments),
KnownAttributeKind.SpecialName => ApplySpecialName(context),
KnownAttributeKind.WindowsRuntimeImport => AddTypeFlags(context, TypeAttributes.WindowsRuntime),
KnownAttributeKind.DynamicSecurityMethod => AddMethodFlags(context, MethodAttributes.RequireSecObject),
KnownAttributeKind.SuppressUnmanagedCodeSecurity => ApplySuppressUnmanagedCodeSecurity(context),
_ => context.InvalidValue(),
};
}
private static bool AddTypeFlags(LoweringContext context, TypeAttributes flags)
{
((EntityRegistry.TypeDefinitionEntity)context.Owner).Attributes |= flags;
return true;
}
private static bool AddFieldFlags(LoweringContext context, FieldAttributes flags)
{
((EntityRegistry.FieldDefinitionEntity)context.Owner).Attributes |= flags;
return true;
}
private static bool AddMethodFlags(LoweringContext context, MethodAttributes flags)
{
((EntityRegistry.MethodDefinitionEntity)context.Owner).MethodAttributes |= flags;
return true;
}
private static bool AddParameterFlags(LoweringContext context, ParameterAttributes flags)
{
((EntityRegistry.ParameterEntity)context.Owner).Attributes |= flags;
return true;
}
private static bool AddMethodImplFlags(LoweringContext context, MethodImplAttributes flags)
{
((EntityRegistry.MethodDefinitionEntity)context.Owner).ImplementationAttributes |= flags;
return true;
}
private static bool ApplySpecialName(LoweringContext context)
{
switch (context.Owner)
{
case EntityRegistry.TypeDefinitionEntity type:
type.Attributes |= TypeAttributes.SpecialName;
return true;
case EntityRegistry.MethodDefinitionEntity method:
method.MethodAttributes |= MethodAttributes.SpecialName;
return true;
case EntityRegistry.FieldDefinitionEntity field:
field.Attributes |= FieldAttributes.SpecialName;
return true;
case EntityRegistry.PropertyEntity property:
property.Attributes |= PropertyAttributes.SpecialName;
return true;
case EntityRegistry.EventEntity @event:
@event.Attributes |= EventAttributes.SpecialName;
return true;
default:
return context.InvalidTarget();
}
}
private static bool ApplySuppressUnmanagedCodeSecurity(LoweringContext context)
{
switch (context.Owner)
{
case EntityRegistry.TypeDefinitionEntity type:
type.Attributes |= TypeAttributes.HasSecurity;
return true;
case EntityRegistry.MethodDefinitionEntity method:
method.MethodAttributes |= MethodAttributes.HasSecurity;
return true;
default:
return context.InvalidTarget();
}
}
private static bool ApplyFieldOffset(
LoweringContext context,
CustomAttributeValue<SerializationTypeCode> arguments)
{
int offset = GetInt32(arguments.FixedArguments[0].Value);
if (offset < 0)
{
return context.InvalidValue();
}
var field = (EntityRegistry.FieldDefinitionEntity)context.Owner;
if (!field.HasExplicitOffset)
{
field.Offset = offset;
}
return true;
}
private static bool ApplyMethodImpl(
LoweringContext context,
KnownAttributeKind kind,
CustomAttributeValue<SerializationTypeCode> arguments)
{
var method = (EntityRegistry.MethodDefinitionEntity)context.Owner;
CustomAttributeNamedArgument<SerializationTypeCode>? codeTypeArgument =
FindNamedArgument(arguments, MethodImplCodeType);
MethodImplAttributes fixedAttributes = 0;
if (kind is not KnownAttributeKind.MethodImpl1)
{
object? fixedValue = arguments.FixedArguments[0].Value;
int value = kind is KnownAttributeKind.MethodImpl2
? unchecked((ushort)GetInt16(fixedValue))
: GetInt32(fixedValue);
// MethodCodeType owns the low bits. All other bits are available for runtime experiments,
// provided the value fits the two-byte MethodDef.ImplFlags column (ECMA-335 II.22.26).
if ((uint)value > ushort.MaxValue || ((MethodImplAttributes)value & MethodImplAttributes.CodeTypeMask) != 0)
{
return context.InvalidValue();
}
fixedAttributes = (MethodImplAttributes)value;
}
MethodCodeType codeType = codeTypeArgument is { } argument
? (MethodCodeType)GetInt32(argument.Value)
: MethodCodeType.IL;
if (codeType is < MethodCodeType.IL or > MethodCodeType.Runtime)
{
return context.InvalidValue();
}
method.ImplementationAttributes |= fixedAttributes;
method.ImplementationAttributes =
(method.ImplementationAttributes & ~MethodImplAttributes.CodeTypeMask) | (MethodImplAttributes)codeType;
return true;
}
private static bool ApplyStructLayout(
LoweringContext context,
KnownAttributeKind kind,
CustomAttributeValue<SerializationTypeCode> arguments)
{
var type = (EntityRegistry.TypeDefinitionEntity)context.Owner;
// The I2 overload is zero-extended through 16 bits before the layout kind is read.
object? fixedValue = arguments.FixedArguments[0].Value;
LayoutKind layoutKind = (LayoutKind)(kind is KnownAttributeKind.StructLayout1
? unchecked((ushort)GetInt16(fixedValue))
: GetInt32(fixedValue));
TypeAttributes? layout = layoutKind switch
{
LayoutKind.Sequential => TypeAttributes.SequentialLayout,
LayoutKind.Extended => TypeAttributes.ExtendedLayout,
LayoutKind.Explicit => TypeAttributes.ExplicitLayout,
LayoutKind.Auto => TypeAttributes.AutoLayout,
_ => null,
};
if (layout is null)
{
return context.InvalidValue();
}
TypeAttributes attributes = (type.Attributes & ~TypeAttributes.LayoutMask) | layout.Value;
int? packingSize = null;
int? classSize = null;
if (FindNamedArgument(arguments, StructLayoutPack) is { } packArgument)
{
int pack = GetInt32(packArgument.Value);
if (pack < 0 || pack > 128 || (pack & (pack - 1)) != 0)
{
return context.InvalidValue();
}
packingSize = pack;
}
if (FindNamedArgument(arguments, StructLayoutSize) is { } sizeArgument)
{
int size = GetInt32(sizeArgument.Value);
if (size < 0)
{
return context.InvalidValue();
}
classSize = size;
}
if (FindNamedArgument(arguments, StructLayoutCharSet) is { } charSetArgument)
{
TypeAttributes? charSet = (CharSet)GetInt32(charSetArgument.Value) switch
{
CharSet.None or CharSet.Ansi => TypeAttributes.AnsiClass,
CharSet.Unicode => TypeAttributes.UnicodeClass,
CharSet.Auto => TypeAttributes.AutoClass,
_ => null,
};
if (charSet is null)
{
return context.InvalidValue();
}
attributes = (attributes & ~TypeAttributes.StringFormatMask) | charSet.Value;
}
type.Attributes = attributes;
// Explicit directives are emitted after pseudo custom attributes by native ilasm, so they
// win regardless of source order. Otherwise, later attributes overwrite earlier ones.
if (packingSize is not null && !type.HasExplicitPackingSize)
{
type.PackingSize = packingSize;
}
if (classSize is not null && !type.HasExplicitClassSize)
{
type.ClassSize = classSize;
}
return true;
}
private static bool ApplyDllImport(
LoweringContext context,
CustomAttributeValue<SerializationTypeCode> arguments)
{
var method = (EntityRegistry.MethodDefinitionEntity)context.Owner;
if (arguments.FixedArguments[0].Value is not string moduleName || moduleName.Length == 0)
{
return context.InvalidValue();
}
MethodImportAttributes flags = MethodImportAttributes.None;
if (FindNamedArgument(arguments, DllImportCallingConvention) is { } callingConventionArgument)
{
MethodImportAttributes? callingConventionFlags = (CallingConvention)GetInt32(callingConventionArgument.Value) switch
{
CallingConvention.Winapi => MethodImportAttributes.CallingConventionWinApi,
CallingConvention.Cdecl => MethodImportAttributes.CallingConventionCDecl,
CallingConvention.StdCall => MethodImportAttributes.CallingConventionStdCall,
CallingConvention.ThisCall => MethodImportAttributes.CallingConventionThisCall,
CallingConvention.FastCall => MethodImportAttributes.CallingConventionFastCall,
_ => null,
};
if (callingConventionFlags is null)
{
return context.InvalidValue();
}
flags |= callingConventionFlags.Value;
}
else
{
flags |= MethodImportAttributes.CallingConventionWinApi;
}
if (FindNamedArgument(arguments, DllImportCharSet) is { } charSetArgument)
{
MethodImportAttributes? charSetFlags = (CharSet)GetInt32(charSetArgument.Value) switch
{
CharSet.None => MethodImportAttributes.None,
CharSet.Ansi => MethodImportAttributes.CharSetAnsi,
CharSet.Unicode => MethodImportAttributes.CharSetUnicode,
CharSet.Auto => MethodImportAttributes.CharSetAuto,
_ => null,
};
if (charSetFlags is null)
{
return context.InvalidValue();
}
flags |= charSetFlags.Value;
}
if (FindNamedArgument(arguments, DllImportExactSpelling) is { } exactSpellingArgument
&& GetBoolean(exactSpellingArgument.Value))
{
flags |= MethodImportAttributes.ExactSpelling;
}
if (FindNamedArgument(arguments, DllImportSetLastError) is { } setLastErrorArgument
&& GetBoolean(setLastErrorArgument.Value))
{
flags |= MethodImportAttributes.SetLastError;
}
if (FindNamedArgument(arguments, DllImportBestFitMapping) is { } bestFitMappingArgument)
{
flags |= GetBoolean(bestFitMappingArgument.Value)
? MethodImportAttributes.BestFitMappingEnable
: MethodImportAttributes.BestFitMappingDisable;
}
if (FindNamedArgument(arguments, DllImportThrowOnUnmappableChar) is { } throwOnUnmappableCharArgument)
{
flags |= GetBoolean(throwOnUnmappableCharArgument.Value)
? MethodImportAttributes.ThrowOnUnmappableCharEnable
: MethodImportAttributes.ThrowOnUnmappableCharDisable;
}
// PreserveSig defaults to set, and is only cleared by an explicit false value.
if (FindNamedArgument(arguments, DllImportPreserveSig) is { } preserveSigArgument
&& !GetBoolean(preserveSigArgument.Value))
{
method.ImplementationAttributes &= ~MethodImplAttributes.PreserveSig;
}
else
{
method.ImplementationAttributes |= MethodImplAttributes.PreserveSig;
}
string entryPoint = FindNamedArgument(arguments, DllImportEntryPoint) is { } entryPointArgument
? GetString(entryPointArgument.Value)
: method.Name;
// The module reference is created even when an explicit pinvokeimpl clause takes precedence,
// because the native emitter resolves it before it discovers the existing ImplMap row.
var moduleReference = context.Registry.GetOrCreateModuleReference(moduleName, _ => { });
// An explicit pinvokeimpl clause wins: the native assembler emits the ImplMap row for
// explicit clauses in a later phase than the one that applies this attribute.
method.MethodImportInformation ??= (moduleReference, entryPoint, flags);
return true;
}
}