File: Microsoft.NetCore.Analyzers\InteropServices\DisableRuntimeMarshallingAnalyzer.AutoLayoutTypeCache.cs
Web Access
Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;

namespace Microsoft.NetCore.Analyzers.InteropServices
{
    internal sealed partial class DisableRuntimeMarshallingAnalyzer
    {
        private sealed class AutoLayoutTypeCache
        {
            private readonly INamedTypeSymbol? _structLayoutAttribute;
            private readonly ConcurrentDictionary<ITypeSymbol, bool> _cache = new();

            public AutoLayoutTypeCache(Compilation compilation)
            {
                _structLayoutAttribute = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeInteropServicesStructLayoutAttribute);
            }

            public bool TypeIsAutoLayoutOrContainsAutoLayout(ITypeSymbol type)
            {
                if (_structLayoutAttribute is null)
                {
                    // We're in a scenario with a custom core library and we don't have any way to determine layout as the pseudo-attribute is not defined.
                    return false;
                }

                return TypeIsAutoLayoutOrContainsAutoLayout(type, ImmutableHashSet<ITypeSymbol>.Empty.WithComparer(SymbolEqualityComparer.Default));

                bool TypeIsAutoLayoutOrContainsAutoLayout(ITypeSymbol type, ImmutableHashSet<ITypeSymbol> seenTypes)
                {
                    Debug.Assert(type.IsValueType);

                    if (_cache.TryGetValue(type, out bool isAutoLayoutOrContainsAutoLayout))
                    {
                        return isAutoLayoutOrContainsAutoLayout;
                    }

                    if (seenTypes.Contains(type.OriginalDefinition))
                    {
                        // If we have a recursive type, we are in one of two scenarios.
                        // 1. We're analyzing CoreLib and see the struct definition of a primitive type.
                        // In all of these cases, the type does not have auto layout.
                        // 2. We found a recursive type definition.
                        // Recursive type definitions are invalid and Roslyn will emit another error diagnostic anyway,
                        // so we don't care here.
                        _cache.TryAdd(type, false);
                        return false;
                    }

                    foreach (var attr in type.GetAttributes(_structLayoutAttribute))
                    {
                        if (attr.ConstructorArguments.Length > 0
                            && attr.ConstructorArguments[0] is TypedConstant argument
                            && argument.Type is not null)
                        {
                            SpecialType specialType = argument.Type.TypeKind == TypeKind.Enum ?
                                ((INamedTypeSymbol)argument.Type).EnumUnderlyingType!.SpecialType :
                                argument.Type.SpecialType;

                            if (DiagnosticHelpers.TryConvertToUInt64(argument.Value, specialType, out ulong convertedLayoutKindValue) &&
                                convertedLayoutKindValue == (ulong)LayoutKind.Auto)
                            {
                                _cache.TryAdd(type, true);
                                return true;
                            }
                        }
                    }

                    var seenTypesWithCurrentType = seenTypes.Add(type.OriginalDefinition);

                    foreach (var member in type.GetMembers())
                    {
                        if (member is IFieldSymbol { IsStatic: false, Type.IsValueType: true } valueTypeField
                            && TypeIsAutoLayoutOrContainsAutoLayout(valueTypeField.Type, seenTypesWithCurrentType))
                        {
                            _cache.TryAdd(type, true);
                            return true;
                        }
                    }

                    _cache.TryAdd(type, false);
                    return false;
                }
            }
        }
    }
}