| File: UnsafeMigrationAnalyzerHelpers.cs | Web Access |
| Project: src\runtime\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer) |
// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #if DEBUG using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace ILLink.RoslynAnalyzer { /// <summary> /// Provides shared declaration, signature, documentation, and layout analysis for the unsafe-v2 migration tooling. /// The helpers encode the compatibility rules used by the <c>IL5005</c> and <c>IL5006</c> analyzers. /// </summary> internal static class UnsafeMigrationAnalyzerHelpers { /// <summary> /// Gets source declarations for a symbol, normalizing field-like variable declarators to their shared declaration. /// </summary> internal static IEnumerable<SyntaxNode> GetDeclarations(ISymbol symbol, CancellationToken cancellationToken) { foreach (SyntaxReference reference in symbol.DeclaringSyntaxReferences) { SyntaxNode declaration = reference.GetSyntax(cancellationToken); if (declaration is VariableDeclaratorSyntax variable && variable.Parent?.Parent is BaseFieldDeclarationSyntax field) { if (field.Declaration.Variables[0] != variable) continue; declaration = field; } yield return declaration; } } /// <summary> /// Filters out declarations such as static constructors and destructors that cannot expose caller-unsafe contracts. /// </summary> internal static bool IsUnsafeContractMember(ISymbol symbol) => symbol switch { IMethodSymbol { MethodKind: MethodKind.StaticConstructor or MethodKind.Destructor } => false, IMethodSymbol => true, IFieldSymbol { IsConst: false } => true, IPropertySymbol or IEventSymbol => true, _ => false, }; /// <summary> /// Implements the legacy pointer-signature compatibility test used by both migration analyzers. /// </summary> internal static bool HasPointerOrFunctionPointerSignature(ISymbol symbol) => // Match Roslyn's compatibility rule: only member signature types participate, not constraints. symbol switch { IMethodSymbol method => ContainsPointerOrFunctionPointer(method.ReturnType) || method.Parameters.Any(static parameter => ContainsPointerOrFunctionPointer(parameter.Type)), IPropertySymbol property => ContainsPointerOrFunctionPointer(property.Type) || property.Parameters.Any(static parameter => ContainsPointerOrFunctionPointer(parameter.Type)), IFieldSymbol { IsFixedSizeBuffer: false } field => ContainsPointerOrFunctionPointer(field.Type), IEventSymbol @event => ContainsPointerOrFunctionPointer(@event.Type), _ => false, }; /// <summary> /// Finds safety documentation on this declaration, another partial part, or an associated property. /// </summary> internal static bool HasSafetyDocumentation( SyntaxNode declaration, ISymbol symbol, CancellationToken cancellationToken) { if (HasSafetyDocumentation(declaration)) return true; // Documentation on another partial declaration or on an associated property also describes this contract. return GetDocumentationSymbols(symbol) .SelectMany(static relatedSymbol => relatedSymbol.DeclaringSyntaxReferences) .Select(reference => reference.GetSyntax(cancellationToken)) .Any(HasSafetyDocumentation); } /// <summary> /// Determines whether removing unsafe must be suppressed to avoid recreating CS9392. /// </summary> internal static bool RequiresExplicitSafetyModifier(SyntaxNode declaration, ISymbol symbol) { if (declaration is AccessorDeclarationSyntax || symbol.ContainingType is not { } containingType) return false; // Removing unsafe from a field-backed declaration in these layouts would immediately reintroduce CS9392. bool hasInstanceField = symbol switch { IFieldSymbol { IsStatic: false, IsConst: false } => true, IPropertySymbol property => HasInstanceBackingField(property), IEventSymbol { IsStatic: false } when declaration is EventFieldDeclarationSyntax => true, IEventSymbol @event => HasInstanceBackingField(@event), _ => false, }; return hasInstanceField && HasExplicitOrExtendedLayout(containingType); } internal static Location GetIdentifierLocation(SyntaxNode declaration) => declaration switch { MethodDeclarationSyntax method => method.Identifier.GetLocation(), ConstructorDeclarationSyntax constructor => constructor.Identifier.GetLocation(), OperatorDeclarationSyntax @operator => @operator.OperatorToken.GetLocation(), ConversionOperatorDeclarationSyntax conversion => conversion.Type.GetLocation(), LocalFunctionStatementSyntax localFunction => localFunction.Identifier.GetLocation(), PropertyDeclarationSyntax property => property.Identifier.GetLocation(), IndexerDeclarationSyntax indexer => indexer.ThisKeyword.GetLocation(), AccessorDeclarationSyntax accessor => accessor.Keyword.GetLocation(), EventDeclarationSyntax @event => @event.Identifier.GetLocation(), BaseFieldDeclarationSyntax field when field.Declaration.Variables.FirstOrDefault() is { } variable => variable.Identifier.GetLocation(), _ => declaration.GetFirstToken().GetLocation(), }; private static bool ContainsPointerOrFunctionPointer(ITypeSymbol type) { var types = new Stack<ITypeSymbol>(); types.Push(type); while (types.Count > 0) { switch (types.Pop()) { case IPointerTypeSymbol or IFunctionPointerTypeSymbol: return true; case IArrayTypeSymbol array: types.Push(array.ElementType); break; case INamedTypeSymbol namedType: // Nested types can carry pointer-containing arguments on an enclosing type. if (namedType.ContainingType is { } containingType) types.Push(containingType); foreach (ITypeSymbol typeArgument in namedType.TypeArguments) types.Push(typeArgument); break; } } return false; } private static IEnumerable<ISymbol> GetDocumentationSymbols(ISymbol symbol) { yield return symbol; // Partial declarations share one contract, and accessor contracts may be documented on the property. switch (symbol) { case IMethodSymbol method: if (method.PartialDefinitionPart is { } methodDefinition) yield return methodDefinition; if (method.PartialImplementationPart is { } methodImplementation) yield return methodImplementation; if (method.AssociatedSymbol is { } associatedSymbol) { foreach (ISymbol relatedSymbol in GetDocumentationSymbols(associatedSymbol)) yield return relatedSymbol; } break; case IPropertySymbol property: if (property.PartialDefinitionPart is { } propertyDefinition) yield return propertyDefinition; if (property.PartialImplementationPart is { } propertyImplementation) yield return propertyImplementation; break; case IEventSymbol @event: if (@event.PartialDefinitionPart is { } eventDefinition) yield return eventDefinition; if (@event.PartialImplementationPart is { } eventImplementation) yield return eventImplementation; break; } } private static bool HasSafetyDocumentation(SyntaxNode declaration) => declaration.GetLeadingTrivia().Any(static trivia => trivia.GetStructure() is DocumentationCommentTriviaSyntax documentationComment && documentationComment.DescendantNodes().Any(static node => node is XmlElementSyntax { StartTag.Name.LocalName.ValueText: "safety" } or XmlEmptyElementSyntax { Name.LocalName.ValueText: "safety" })); private static bool HasInstanceBackingField(ISymbol associatedSymbol) => associatedSymbol.ContainingType.GetMembers() .OfType<IFieldSymbol>() .Any(field => !field.IsStatic && SymbolEqualityComparer.Default.Equals(field.AssociatedSymbol, associatedSymbol)); private static bool HasExplicitOrExtendedLayout(INamedTypeSymbol type) => type.GetAttributes().Any(static attribute => attribute.AttributeClass is { } attributeType && (attributeType.IsTypeOf("System.Runtime.InteropServices", "ExtendedLayoutAttribute") || (attributeType.IsTypeOf("System.Runtime.InteropServices", nameof(StructLayoutAttribute)) && attribute.ConstructorArguments is [{ Value: int layoutKind }] && layoutKind == (int)LayoutKind.Explicit))); } } #endif