// 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.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using System.Text;
namespace ILAssembler;
internal static partial class PseudoCustomAttributes
{
private static bool ApplyMarshalAs(
LoweringContext context,
CustomAttributeValue<SerializationTypeCode> arguments)
{
var descriptor = new BlobBuilder();
if (!TryBuildMarshallingDescriptor(context, arguments, descriptor))
{
return false;
}
switch (context.Owner)
{
case EntityRegistry.FieldDefinitionEntity field:
field.MarshallingDescriptor = descriptor;
return true;
case EntityRegistry.ParameterEntity parameter:
parameter.MarshallingDescriptor = descriptor;
return true;
case EntityRegistry.PropertyEntity property:
ApplyMarshalAsToProperty(context.Registry, property, descriptor);
return true;
default:
return context.InvalidTarget();
}
}
/// <summary>
/// Applies a property's marshalling descriptor to the getter's return parameter and to the
/// setter's last parameter, matching the native emitter.
/// </summary>
private static void ApplyMarshalAsToProperty(
EntityRegistry registry,
EntityRegistry.PropertyEntity property,
BlobBuilder descriptor)
{
EntityRegistry.TypeDefinitionEntity? containingType = FindContainingType(registry, property);
foreach ((MethodSemanticsAttributes semantic, EntityRegistry.EntityBase accessor) in property.Accessors)
{
if (ResolveAccessor(registry, accessor, containingType) is not { } method)
{
continue;
}
int targetSequence;
if (semantic == MethodSemanticsAttributes.Getter)
{
targetSequence = 0;
}
else if (semantic == MethodSemanticsAttributes.Setter)
{
if (!TryGetSignatureParameterCount(method.MethodSignature, out targetSequence) || targetSequence == 0)
{
continue;
}
}
else
{
continue;
}
foreach (EntityRegistry.ParameterEntity parameter in method.Parameters)
{
if (parameter.Sequence == targetSequence)
{
// Each target gets its own builder so that the blobs stay independent.
var copy = new BlobBuilder();
descriptor.WriteContentTo(copy);
parameter.MarshallingDescriptor = copy;
break;
}
}
}
}
private static EntityRegistry.TypeDefinitionEntity? FindContainingType(
EntityRegistry registry,
EntityRegistry.PropertyEntity property)
{
foreach (var entity in registry.GetSeenEntities(TableIndex.TypeDef))
{
if (entity is EntityRegistry.TypeDefinitionEntity type && type.Properties.Contains(property))
{
return type;
}
}
return null;
}
/// <summary>
/// Property accessors are recorded as member references, so they are resolved to their local
/// method definitions the same way attribute owners are, falling back to a lookup in the
/// declaring type when the reference does not carry one.
/// </summary>
private static EntityRegistry.MethodDefinitionEntity? ResolveAccessor(
EntityRegistry registry,
EntityRegistry.EntityBase accessor,
EntityRegistry.TypeDefinitionEntity? containingType)
{
if (ResolveOwner(registry, accessor) is EntityRegistry.MethodDefinitionEntity resolved)
{
return resolved;
}
if (accessor is not EntityRegistry.MemberReferenceEntity memberReference || containingType is null)
{
return null;
}
foreach (EntityRegistry.MethodDefinitionEntity candidate in containingType.Methods)
{
if (candidate.Name == memberReference.Name
&& candidate.MethodSignature is { } signature
&& signature.ContentEquals(memberReference.Signature))
{
return candidate;
}
}
return null;
}
private static unsafe bool TryGetSignatureParameterCount(BlobBuilder? signature, out int count)
{
count = 0;
if (signature is null)
{
return false;
}
byte[] signatureBytes = signature.ToArray();
fixed (byte* signaturePointer = signatureBytes)
{
var reader = new BlobReader(signaturePointer, signatureBytes.Length);
try
{
var header = new SignatureHeader(reader.ReadByte());
if (header.IsGeneric && !reader.TryReadCompressedInteger(out _))
{
return false;
}
return reader.TryReadCompressedInteger(out count);
}
catch (BadImageFormatException)
{
return false;
}
}
}
private static bool TryBuildMarshallingDescriptor(
LoweringContext context,
CustomAttributeValue<SerializationTypeCode> arguments,
BlobBuilder descriptor)
{
CustomAttributeNamedArgument<SerializationTypeCode>? arraySubTypeArgument =
FindNamedArgument(arguments, MarshalArraySubType);
CustomAttributeNamedArgument<SerializationTypeCode>? safeArraySubTypeArgument =
FindNamedArgument(arguments, MarshalSafeArraySubType);
CustomAttributeNamedArgument<SerializationTypeCode>? safeArrayUserDefinedSubTypeArgument =
FindNamedArgument(arguments, MarshalSafeArrayUserDefinedSubType);
CustomAttributeNamedArgument<SerializationTypeCode>? sizeParamIndexArgument =
FindNamedArgument(arguments, MarshalSizeParamIndex);
CustomAttributeNamedArgument<SerializationTypeCode>? sizeConstArgument =
FindNamedArgument(arguments, MarshalSizeConst);
CustomAttributeNamedArgument<SerializationTypeCode>? marshalTypeArgument =
FindNamedArgument(arguments, MarshalType);
CustomAttributeNamedArgument<SerializationTypeCode>? marshalTypeRefArgument =
FindNamedArgument(arguments, MarshalTypeRef);
CustomAttributeNamedArgument<SerializationTypeCode>? marshalCookieArgument =
FindNamedArgument(arguments, MarshalCookie);
CustomAttributeNamedArgument<SerializationTypeCode>? iidParameterIndexArgument =
FindNamedArgument(arguments, MarshalIidParameterIndex);
// For the I2 overload the value was read as a 16-bit quantity and is zero-extended here,
// matching the native emitter which widens the I2 into a U4 before building the descriptor.
int nativeType = GetInt32(arguments.FixedArguments[0].Value);
if (!TryWriteCompressed(context, descriptor, nativeType))
{
return false;
}
switch (nativeType)
{
case (int)UnmanagedType.Interface:
case (int)UnmanagedType.IUnknown:
case (int)UnmanagedType.IDispatch:
if (iidParameterIndexArgument is { } iidParameterIndex)
{
int iidParameterIndexValue = GetInt32(iidParameterIndex.Value);
if (iidParameterIndexValue < 0)
{
return context.InvalidValue();
}
if (!TryWriteCompressed(context, descriptor, iidParameterIndexValue))
{
return false;
}
}
break;
case (int)UnmanagedType.ByValArray:
if (safeArraySubTypeArgument is not null || sizeParamIndexArgument is not null)
{
return context.InvalidValue();
}
if (GetTarget(context.Owner) != CaTargets.FieldDef)
{
return context.InvalidTarget();
}
if (sizeConstArgument is { } sizeConst)
{
int sizeConstValue = GetInt32(sizeConst.Value);
if (sizeConstValue < 0)
{
return context.InvalidValue();
}
if (!TryWriteCompressed(context, descriptor, sizeConstValue))
{
return false;
}
}
else if (!TryWriteCompressed(context, descriptor, 1))
{
return false;
}
if (arraySubTypeArgument is { } arraySubType
&& !TryWriteCompressed(context, descriptor, GetInt32(arraySubType.Value)))
{
return false;
}
break;
case (int)UnmanagedType.ByValTStr:
if (sizeConstArgument is not { } fixedStringSize)
{
return context.InvalidValue();
}
if (arraySubTypeArgument is not null
|| sizeParamIndexArgument is not null
|| safeArraySubTypeArgument is not null)
{
return context.InvalidValue();
}
if (GetTarget(context.Owner) != CaTargets.FieldDef)
{
return context.InvalidTarget();
}
if (!TryWriteCompressed(context, descriptor, GetInt32(fixedStringSize.Value)))
{
return false;
}
break;
#pragma warning disable CS0618 // VBByRefStr remains supported for existing marshalling descriptors.
case (int)UnmanagedType.VBByRefStr:
#pragma warning restore CS0618
if (GetTarget(context.Owner) != CaTargets.ParamDef)
{
return context.InvalidTarget();
}
break;
case (int)UnmanagedType.SafeArray:
if (arraySubTypeArgument is not null
|| sizeParamIndexArgument is not null
|| sizeConstArgument is not null)
{
return context.InvalidValue();
}
if (safeArraySubTypeArgument is { } safeArraySubType)
{
int safeArraySubTypeValue = GetInt32(safeArraySubType.Value);
if (!TryWriteCompressed(context, descriptor, safeArraySubTypeValue))
{
return false;
}
if (safeArrayUserDefinedSubTypeArgument is { } userDefinedSubType)
{
if (safeArraySubTypeValue != (int)VarEnum.VT_RECORD
&& safeArraySubTypeValue != (int)VarEnum.VT_DISPATCH
&& safeArraySubTypeValue != (int)VarEnum.VT_UNKNOWN)
{
return context.InvalidValue();
}
if (!TryWriteCountedString(context, descriptor, GetString(userDefinedSubType.Value)))
{
return false;
}
}
}
break;
case (int)UnmanagedType.LPArray:
if (safeArraySubTypeArgument is not null)
{
return context.InvalidValue();
}
if (arraySubTypeArgument is { } lpArraySubType)
{
int arraySubTypeValue = GetInt32(lpArraySubType.Value);
if (arraySubTypeValue == (int)UnmanagedType.CustomMarshaler)
{
return context.InvalidValue();
}
if (!TryWriteCompressed(context, descriptor, arraySubTypeValue))
{
return false;
}
}
else if (!TryWriteCompressed(context, descriptor, (int)UnmanagedType.Max))
{
return false;
}
if (sizeParamIndexArgument is { } sizeParamIndex)
{
int sizeParamIndexValue = GetInt16(sizeParamIndex.Value);
if (sizeParamIndexValue < 0)
{
return context.InvalidValue();
}
if (!TryWriteCompressed(context, descriptor, sizeParamIndexValue))
{
return false;
}
if (sizeConstArgument is { } indexedSizeConst)
{
int sizeConstValue = GetInt32(indexedSizeConst.Value);
if (sizeConstValue < 0)
{
return context.InvalidValue();
}
if (!TryWriteCompressed(context, descriptor, sizeConstValue)
|| !TryWriteCompressed(context, descriptor, UnmanagedType.ArraySizeParamIndexSpecified))
{
return false;
}
}
}
else if (sizeConstArgument is { } unindexedSizeConst)
{
if (!TryWriteCompressed(context, descriptor, 0)
|| !TryWriteCompressed(context, descriptor, GetInt32(unindexedSizeConst.Value))
|| !TryWriteCompressed(context, descriptor, 0))
{
return false;
}
}
break;
case (int)UnmanagedType.CustomMarshaler:
if (marshalTypeArgument is null && marshalTypeRefArgument is null)
{
return context.InvalidValue();
}
// Placeholders for the unmanaged type library GUID and the unmanaged type name.
if (!TryWriteCompressed(context, descriptor, 0) || !TryWriteCompressed(context, descriptor, 0))
{
return false;
}
string marshaler = marshalTypeArgument is { } marshalType
? GetString(marshalType.Value)
: GetString(marshalTypeRefArgument!.Value.Value);
string cookie = marshalCookieArgument is { } marshalCookie
? GetString(marshalCookie.Value)
: "";
if (!TryWriteCountedString(context, descriptor, marshaler)
|| !TryWriteCountedString(context, descriptor, cookie))
{
return false;
}
break;
}
return true;
}
private static bool TryWriteCompressed(LoweringContext context, BlobBuilder builder, int value)
{
if (value < 0 || value > 0x1FFFFFFF)
{
return context.InvalidBlob();
}
builder.WriteCompressedInteger(value);
return true;
}
private static bool TryWriteCountedString(LoweringContext context, BlobBuilder builder, string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
if (!TryWriteCompressed(context, builder, bytes.Length))
{
return false;
}
builder.WriteBytes(bytes);
return true;
}
}