File: Parsing\Parser.Records.cs
Web Access
Project: src\src\Generators\Microsoft.Gen.Logging\Microsoft.Gen.Logging.csproj (Microsoft.Gen.Logging)
// 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.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.Gen.Shared;
 
namespace Microsoft.Gen.Logging.Parsing
{
    internal partial class Parser
    {
        internal bool RecordHasSensitivePublicMembers(ITypeSymbol type, SymbolHolder symbols)
        {
            if (symbols.DataClassificationAttribute is null)
            {
                return false;
            }
 
            // Check full type hierarchy for sensitive members, including base types and transitive fields/properties
            var typesChain = new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default);
            _ = typesChain.Add(type); // Add itself
            return RecordHasSensitivePublicMembers(type, typesChain, symbols, _cancellationToken);
        }
 
        private readonly record struct MemberInfo(
            string Name,
            ITypeSymbol Type,
            IPropertySymbol? Property);
 
        private static bool RecordHasSensitivePublicMembers(ITypeSymbol type, HashSet<ITypeSymbol> typesChain, SymbolHolder symbols, CancellationToken token)
        {
            var overriddenMembers = new HashSet<string>();
            var namedType = type as INamedTypeSymbol;
            while (namedType != null && namedType.SpecialType != SpecialType.System_Object)
            {
                token.ThrowIfCancellationRequested();
 
                // Check if the type itself has any data classification attributes applied:
                var recordDataClassAttributes = GetDataClassificationAttributes(namedType, symbols);
                if (recordDataClassAttributes.Count > 0)
                {
                    return true;
                }
 
                var members = namedType.GetMembers();
                if (members.IsDefaultOrEmpty)
                {
                    namedType = namedType.BaseType;
                    continue;
                }
 
                foreach (var m in members)
                {
                    // Skip static, non-public members:
                    if (m.DeclaredAccessibility != Accessibility.Public ||
                        m.IsStatic)
                    {
                        continue;
                    }
 
                    MemberInfo? maybeMemberInfo =
                        m switch
                        {
                            IPropertySymbol property => new(property.Name, property.Type, property),
                            IFieldSymbol field => new(field.Name, field.Type, Property: null),
                            _ => null
                        };
 
                    if (maybeMemberInfo is null)
                    {
                        if (m is not IMethodSymbol { MethodKind: MethodKind.Constructor } ctorMethod)
                        {
                            continue;
                        }
 
                        // Check if constructor has any sensitive parameters:
                        foreach (var param in ctorMethod.Parameters)
                        {
                            // We don't check for "DataClassifierWrappers" here as they will be covered in field/property checks:
                            var parameterDataClassAttributes = GetDataClassificationAttributes(param, symbols);
                            if (parameterDataClassAttributes.Count > 0)
                            {
                                return true;
                            }
                        }
 
                        // No sensitive parameters in the ctor, continue with next member:
                        continue;
                    }
 
                    var memberInfo = maybeMemberInfo.Value;
                    if (memberInfo.Property is not null)
                    {
                        var property = memberInfo.Property;
                        var getMethod = property.GetMethod;
 
                        // We don't check if getter is "public", it doesn't matter: https://github.com/dotnet/runtime/issues/86700
                        if (getMethod is null)
                        {
                            continue;
                        }
 
                        // Property is already overridden in derived class, skip it:
                        if ((property.IsVirtual || property.IsOverride) &&
                            overriddenMembers.Contains(property.Name))
                        {
                            continue;
                        }
 
                        // Adding property that overrides some base class virtual property:
                        if (property.IsOverride)
                        {
                            _ = overriddenMembers.Add(property.Name);
                        }
                    }
 
                    // Skip members with delegate types:
                    var memberType = memberInfo.Type;
                    if (memberType.TypeKind == TypeKind.Delegate)
                    {
                        continue;
                    }
 
                    var extractedType = memberType;
                    if (memberType.IsNullableOfT())
                    {
                        // extract the T from a Nullable<T>
                        extractedType = ((INamedTypeSymbol)memberType).TypeArguments[0];
                    }
 
                    // Checking "extractedType" here:
                    if (typesChain.Contains(extractedType))
                    {
                        continue; // Move to the next member
                    }
 
                    var propertyDataClassAttributes = GetDataClassificationAttributes(m, symbols);
                    if (propertyDataClassAttributes.Count > 0)
                    {
                        return true;
                    }
 
                    if (extractedType.IsRecord && // Going inside record types only
                        extractedType.Kind == SymbolKind.NamedType &&
                        !extractedType.IsSpecialType(symbols))
                    {
                        _ = typesChain.Add(namedType);
                        if (RecordHasSensitivePublicMembers(extractedType, typesChain, symbols, token))
                        {
                            return true;
                        }
 
                        _ = typesChain.Remove(namedType);
                    }
                }
 
                // This is to support inheritance, i.e. to fetch public members of base class(-es) as well:
                namedType = namedType.BaseType;
            }
 
            return false;
        }
    }
}