File: Symbols\Metadata\PE\DynamicTypeDecoder.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE
{
    /// <summary>
    /// Decodes System.Runtime.CompilerServices.DynamicAttribute applied to a specified metadata symbol and
    /// transforms the specified metadata type, using the decoded dynamic transforms attribute argument,
    /// by replacing each occurrence of <see cref="System.Object"/> type with dynamic type.
    /// </summary>
    /// <remarks>
    /// This is a port of TypeManager::ImportDynamicTransformType from the native compiler.
    /// Comments from the C# design document for Dynamic:
    /// SPEC:   To represent the dynamic type in metadata, any indexer, field or return value typed as dynamic or known to be a constructed type
    /// SPEC:   containing dynamic will have each occurrence of dynamic erased to object and will be annotated with a [DynamicAttribute].
    /// SPEC:   If the relevant type is a constructed type, the attribute's constructor is passed a bool array.
    /// SPEC:   This array represents a preorder traversal of each "node" in the constructed type's "tree of types",
    /// SPEC:   with true set for each "node" that is dynamic, and false set for all other types.
    /// SPEC:   When dynamic occurs as part of the base type of a type, the applicable [DynamicAttribute] is applied to the type itself.
    /// </remarks>
    internal struct DynamicTypeDecoder
    {
        private readonly ImmutableArray<bool> _dynamicTransformFlags;
        private readonly AssemblySymbol _containingAssembly;
        private readonly bool _haveCustomModifierFlags;
        private readonly bool _checkLength;
 
        /// <remarks>
        /// Should be accessed through <see cref="HasFlag"/>, <see cref="PeekFlag"/>, and <see cref="ConsumeFlag"/>.
        /// </remarks>
        private int _index;
 
        private DynamicTypeDecoder(ImmutableArray<bool> dynamicTransformFlags, bool haveCustomModifierFlags, bool checkLength, AssemblySymbol containingAssembly)
        {
            Debug.Assert(!dynamicTransformFlags.IsEmpty);
            Debug.Assert((object)containingAssembly != null);
 
            _dynamicTransformFlags = dynamicTransformFlags;
            _containingAssembly = containingAssembly;
            _haveCustomModifierFlags = haveCustomModifierFlags;
            _checkLength = checkLength;
            _index = 0;
        }
 
        /// <summary>
        /// Decodes the attributes applied to the given <see paramref="targetSymbol"/> from metadata and checks if System.Runtime.CompilerServices.DynamicAttribute is applied.
        /// If so, it transforms the given <see paramref="metadataType"/>, using the decoded dynamic transforms attribute argument,
        /// by replacing each occurrence of <see cref="System.Object"/> type with dynamic type.
        /// If no System.Runtime.CompilerServices.DynamicAttribute is applied or the decoded dynamic transforms attribute argument is erroneous,
        /// returns the unchanged <see paramref="metadataType"/>.
        /// </summary>
        /// <remarks>This method is a port of TypeManager::ImportDynamicTransformType from the native compiler.</remarks>
        internal static TypeSymbol TransformType(
            TypeSymbol metadataType,
            int targetSymbolCustomModifierCount,
            EntityHandle targetSymbolToken,
            PEModuleSymbol containingModule,
            RefKind targetSymbolRefKind = RefKind.None)
        {
            Debug.Assert((object)metadataType != null);
 
            ImmutableArray<bool> dynamicTransformFlags;
            if (containingModule.Module.HasDynamicAttribute(targetSymbolToken, out dynamicTransformFlags))
            {
                return TransformTypeInternal(metadataType, containingModule.ContainingAssembly,
                    targetSymbolCustomModifierCount, targetSymbolRefKind, dynamicTransformFlags,
                    haveCustomModifierFlags: true,
                    checkLength: true);
            }
 
            // No DynamicAttribute applied to the target symbol, return unchanged metadataType.
            return metadataType;
        }
 
        internal static TypeSymbol TransformTypeWithoutCustomModifierFlags(
            TypeSymbol type,
            AssemblySymbol containingAssembly,
            RefKind targetSymbolRefKind,
            ImmutableArray<bool> dynamicTransformFlags,
            bool checkLength = true)
        {
            return TransformTypeInternal(
                type,
                containingAssembly,
                0,
                targetSymbolRefKind,
                dynamicTransformFlags,
                haveCustomModifierFlags: false,
                checkLength: checkLength);
        }
 
        private static TypeSymbol TransformTypeInternal(
            TypeSymbol metadataType,
            AssemblySymbol containingAssembly,
            int targetSymbolCustomModifierCount,
            RefKind targetSymbolRefKind,
            ImmutableArray<bool> dynamicTransformFlags,
            bool haveCustomModifierFlags,
            bool checkLength)
        {
            Debug.Assert((object)metadataType != null);
            Debug.Assert((object)containingAssembly != null);
            Debug.Assert(!dynamicTransformFlags.IsDefault);
 
            if (dynamicTransformFlags.Length == 0)
            {
                return new UnsupportedMetadataTypeSymbol();
            }
 
            var decoder = new DynamicTypeDecoder(dynamicTransformFlags, haveCustomModifierFlags, checkLength, containingAssembly);
 
            // Native compiler encodes bools (always false) for custom modifiers and parameter ref-kinds, if ref-kind is ref or out.
            if (decoder.HandleCustomModifiers(targetSymbolCustomModifierCount) && decoder.HandleRefKind(targetSymbolRefKind))
            {
                TypeSymbol transformedType = decoder.TransformType(metadataType);
 
                if ((object)transformedType != null && (!checkLength || decoder._index == dynamicTransformFlags.Length))
                {
                    // Even when we're not checking the length, there shouldn't be any unconsumed "true"s.
                    Debug.Assert(checkLength || decoder._dynamicTransformFlags.LastIndexOf(true) < decoder._index);
                    return transformedType;
                }
            }
 
            // We ignore the dynamic transformation and return unchanged metadataType to match Dev11 behavior.
            return metadataType;
        }
 
        private TypeSymbol TransformType(TypeSymbol type)
        {
            Debug.Assert(_index >= 0);
 
            if (!HasFlag ||
                PeekFlag() && (type.SpecialType != SpecialType.System_Object && !type.IsDynamic()))
            {
                // Bail, since flags are invalid.
                return null;
            }
 
            switch (type.Kind)
            {
                case SymbolKind.ErrorType:
                case SymbolKind.NamedType:
                    if (type.SpecialType == SpecialType.System_Object)
                    {
                        // Replace the given System.Object type with dynamic type if the corresponding dynamicTransformFlag is set to true.
                        return ConsumeFlag() ? DynamicTypeSymbol.Instance : type;
                    }
 
                    return TransformNamedType((NamedTypeSymbol)type);
 
                case SymbolKind.ArrayType:
                    return TransformArrayType((ArrayTypeSymbol)type);
 
                case SymbolKind.PointerType:
                    return TransformPointerType((PointerTypeSymbol)type);
 
                case SymbolKind.FunctionPointerType:
                    return TransformFunctionPointerType((FunctionPointerTypeSymbol)type);
 
                case SymbolKind.DynamicType:
                    Debug.Assert(!_haveCustomModifierFlags, "This shouldn't happen during decoding.");
                    return ConsumeFlag()
                        ? type
                        : _containingAssembly.GetSpecialType(SpecialType.System_Object);
 
                default:
                    ConsumeFlag();
                    return HandleCustomModifiers(type.CustomModifierCount()) ? type : null;
            }
        }
 
        // Native compiler encodes bools (always false) for custom modifiers and parameter ref-kinds, if ref-kind is ref or out.
        private bool HandleCustomModifiers(int customModifiersCount)
        {
            // If we're in source, then we're actually working on copying custom modifiers,
            // so we should not assume they are in their final state.  Instead, we will
            // ignore them completely.
            if (!_haveCustomModifierFlags)
            {
                return true;
            }
 
            Debug.Assert(customModifiersCount >= 0);
 
            for (int i = 0; i < customModifiersCount; i++)
            {
                if (!HasFlag || ConsumeFlag())
                {
                    return false;
                }
            }
 
            return true;
        }
 
        // Native compiler encodes bools (always false) for custom modifiers and parameter ref-kinds, if ref-kind is not none.
        private bool HandleRefKind(RefKind refKind)
        {
            Debug.Assert(_index >= 0);
            return refKind == RefKind.None || !ConsumeFlag();
        }
 
        private NamedTypeSymbol TransformNamedType(NamedTypeSymbol namedType, bool isContaining = false)
        {
            // Native compiler encodes a bool for the given namedType, but none for its containing types.
            if (!isContaining)
            {
                var flag = ConsumeFlag();
                Debug.Assert(!flag);
            }
 
            NamedTypeSymbol containingType = namedType.ContainingType;
            NamedTypeSymbol newContainingType;
            if ((object)containingType != null && containingType.IsGenericType)
            {
                newContainingType = TransformNamedType(namedType.ContainingType, isContaining: true);
                if ((object)newContainingType == null)
                {
                    return null;
                }
 
                Debug.Assert(newContainingType.IsGenericType);
            }
            else
            {
                newContainingType = containingType;
            }
 
            // Native compiler encodes bools for each type argument, starting from type arguments for the outermost containing type to those for the given namedType.
            ImmutableArray<TypeWithAnnotations> typeArguments = namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
 
            ImmutableArray<TypeWithAnnotations> transformedTypeArguments = TransformTypeArguments(typeArguments); // Note, modifiers are not involved, this is behavior of the native compiler.
 
            if (transformedTypeArguments.IsDefault)
            {
                return null;
            }
 
            // Construct a new namedType, if required.
            bool containerIsChanged = (!TypeSymbol.Equals(newContainingType, containingType, TypeCompareKind.ConsiderEverything2));
 
            if (containerIsChanged || transformedTypeArguments != typeArguments)
            {
                if (containerIsChanged)
                {
                    namedType = namedType.OriginalDefinition.AsMember(newContainingType);
                    return namedType.ConstructIfGeneric(transformedTypeArguments);
                }
 
                return namedType.ConstructedFrom.Construct(transformedTypeArguments, unbound: false).WithTupleDataFrom(namedType);
            }
            else
            {
                return namedType;
            }
        }
 
        private ImmutableArray<TypeWithAnnotations> TransformTypeArguments(ImmutableArray<TypeWithAnnotations> typeArguments)
        {
            if (!typeArguments.Any())
            {
                return typeArguments;
            }
 
            var transformedTypeArgsBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance();
            bool anyTransformed = false;
            foreach (var typeArg in typeArguments)
            {
                TypeSymbol transformedTypeArg = TransformType(typeArg.Type);
                if ((object)transformedTypeArg == null)
                {
                    transformedTypeArgsBuilder.Free();
                    return default(ImmutableArray<TypeWithAnnotations>);
                }
 
                // Note, modifiers are not involved, this is behavior of the native compiler.
                transformedTypeArgsBuilder.Add(typeArg.WithTypeAndModifiers(transformedTypeArg, typeArg.CustomModifiers));
                anyTransformed |= !TypeSymbol.Equals(transformedTypeArg, typeArg.Type, TypeCompareKind.ConsiderEverything2);
            }
 
            if (!anyTransformed)
            {
                transformedTypeArgsBuilder.Free();
                return typeArguments;
            }
 
            return transformedTypeArgsBuilder.ToImmutableAndFree();
        }
 
        private ArrayTypeSymbol TransformArrayType(ArrayTypeSymbol arrayType)
        {
            var flag = ConsumeFlag();
            Debug.Assert(!flag);
 
            if (!HandleCustomModifiers(arrayType.ElementTypeWithAnnotations.CustomModifiers.Length))
            {
                return null;
            }
 
            TypeSymbol transformedElementType = TransformType(arrayType.ElementType);
            if ((object)transformedElementType == null)
            {
                return null;
            }
 
            return TypeSymbol.Equals(transformedElementType, arrayType.ElementType, TypeCompareKind.ConsiderEverything2) ?
                arrayType :
                arrayType.IsSZArray ?
                    ArrayTypeSymbol.CreateSZArray(_containingAssembly, arrayType.ElementTypeWithAnnotations.WithTypeAndModifiers(transformedElementType, arrayType.ElementTypeWithAnnotations.CustomModifiers)) :
                    ArrayTypeSymbol.CreateMDArray(_containingAssembly, arrayType.ElementTypeWithAnnotations.WithTypeAndModifiers(transformedElementType, arrayType.ElementTypeWithAnnotations.CustomModifiers), arrayType.Rank, arrayType.Sizes, arrayType.LowerBounds);
        }
 
        private PointerTypeSymbol TransformPointerType(PointerTypeSymbol pointerType)
        {
            var flag = ConsumeFlag();
            Debug.Assert(!flag);
 
            if (!HandleCustomModifiers(pointerType.PointedAtTypeWithAnnotations.CustomModifiers.Length))
            {
                return null;
            }
 
            TypeSymbol transformedPointedAtType = TransformType(pointerType.PointedAtType);
            if ((object)transformedPointedAtType == null)
            {
                return null;
            }
 
            return TypeSymbol.Equals(transformedPointedAtType, pointerType.PointedAtType, TypeCompareKind.ConsiderEverything2) ?
                pointerType :
                new PointerTypeSymbol(pointerType.PointedAtTypeWithAnnotations.WithTypeAndModifiers(transformedPointedAtType, pointerType.PointedAtTypeWithAnnotations.CustomModifiers));
        }
 
#nullable enable
        private FunctionPointerTypeSymbol? TransformFunctionPointerType(FunctionPointerTypeSymbol type)
        {
            var flag = ConsumeFlag();
            Debug.Assert(!flag);
 
            var sig = type.Signature;
 
            var (transformedReturnWithAnnotations, madeChanges) = handle(ref this, sig.RefKind, sig.RefCustomModifiers, sig.ReturnTypeWithAnnotations);
            if (transformedReturnWithAnnotations.IsDefault)
            {
                return null;
            }
 
            var transformedParameters = ImmutableArray<TypeWithAnnotations>.Empty;
            if (sig.ParameterCount > 0)
            {
                var paramsTransformed = false;
                var paramsBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(sig.ParameterCount);
                try
                {
                    foreach (var param in sig.Parameters)
                    {
                        var (transformedParamType, paramTransformed) = handle(ref this, param.RefKind, param.RefCustomModifiers, param.TypeWithAnnotations);
                        if (transformedParamType.IsDefault)
                        {
                            return null;
                        }
 
                        paramsBuilder.Add(transformedParamType);
                        paramsTransformed |= paramTransformed;
                    }
 
                    transformedParameters = paramsTransformed ? paramsBuilder.ToImmutable() : sig.ParameterTypesWithAnnotations;
                    madeChanges |= paramsTransformed;
                }
                finally
                {
                    paramsBuilder.Free();
                }
            }
 
            if (madeChanges)
            {
                return type.SubstituteTypeSymbol(transformedReturnWithAnnotations, transformedParameters,
                                                 refCustomModifiers: default, paramRefCustomModifiers: default);
            }
            else
            {
                return type;
            }
 
            static (TypeWithAnnotations, bool madeChanges) handle(ref DynamicTypeDecoder decoder, RefKind refKind, ImmutableArray<CustomModifier> refCustomModifiers, TypeWithAnnotations typeWithAnnotations)
            {
                if (!decoder.HandleCustomModifiers(refCustomModifiers.Length)
                    || !decoder.HandleRefKind(refKind)
                    || !decoder.HandleCustomModifiers(typeWithAnnotations.CustomModifiers.Length))
                {
                    return (default, false);
                }
 
                var transformedType = decoder.TransformType(typeWithAnnotations.Type);
                if (transformedType is null)
                {
                    return (default, false);
                }
 
                if (transformedType.Equals(typeWithAnnotations.Type, TypeCompareKind.ConsiderEverything))
                {
                    return (typeWithAnnotations, false);
                }
 
                return (typeWithAnnotations.WithType(transformedType), true);
            }
        }
#nullable disable
 
        private bool HasFlag => _index < _dynamicTransformFlags.Length || !_checkLength;
 
        private bool PeekFlag() => _index < _dynamicTransformFlags.Length && _dynamicTransformFlags[_index];
 
        private bool ConsumeFlag()
        {
            var result = PeekFlag();
            _index++;
            return result;
        }
    }
}