| 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.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; } } }