From 7d8cc90d65445c72b2664fffe4fe06a9600eb1eb Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Mon, 4 May 2026 19:55:56 +0200 Subject: [PATCH] Remove RequiresUnsafeAnalyzer --- .../RequiresUnsafeCodeFixProvider.cs | 541 ----- .../illink/src/ILLink.CodeFix/Resources.resx | 6 - ...hodMissingRequiresUnsafeCodeFixProvider.cs | 83 - .../DynamicallyAccessedMembersAnalyzer.cs | 3 - .../MSBuildPropertyOptionNames.cs | 3 - .../RequiresUnsafeAnalyzer.cs | 103 - ...safeMethodMissingRequiresUnsafeAnalyzer.cs | 76 - .../Microsoft.NET.ILLink.Analyzers.props | 1 - .../illink/src/ILLink.Shared/DiagnosticId.cs | 13 - .../src/ILLink.Shared/SharedStrings.resx | 30 - .../RequiresUnsafeAnalyzerTests.cs | 465 ---- .../RequiresUnsafeCodeFixTests.cs | 1913 ----------------- .../TestCaseCompilation.cs | 3 - .../UnsafeMethodMissingRequiresUnsafeTests.cs | 278 --- 14 files changed, 3518 deletions(-) delete mode 100644 src/tools/illink/src/ILLink.CodeFix/RequiresUnsafeCodeFixProvider.cs delete mode 100644 src/tools/illink/src/ILLink.CodeFix/UnsafeMethodMissingRequiresUnsafeCodeFixProvider.cs delete mode 100644 src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnsafeAnalyzer.cs delete mode 100644 src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMethodMissingRequiresUnsafeAnalyzer.cs delete mode 100644 src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeAnalyzerTests.cs delete mode 100644 src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs delete mode 100644 src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMethodMissingRequiresUnsafeTests.cs diff --git a/src/tools/illink/src/ILLink.CodeFix/RequiresUnsafeCodeFixProvider.cs b/src/tools/illink/src/ILLink.CodeFix/RequiresUnsafeCodeFixProvider.cs deleted file mode 100644 index 654bd74905366c..00000000000000 --- a/src/tools/illink/src/ILLink.CodeFix/RequiresUnsafeCodeFixProvider.cs +++ /dev/null @@ -1,541 +0,0 @@ -// 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.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using ILLink.CodeFixProvider; -using ILLink.RoslynAnalyzer; -using ILLink.Shared; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; - -namespace ILLink.CodeFix -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RequiresUnsafeCodeFixProvider)), Shared] - public sealed class RequiresUnsafeCodeFixProvider : BaseAttributeCodeFixProvider - { - private const string WrapInUnsafeBlockTitle = "Wrap in unsafe block"; - - public static ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnsafe)); - - public sealed override ImmutableArray FixableDiagnosticIds => SupportedDiagnostics.Select(dd => dd.Id).ToImmutableArray(); - - private protected override LocalizableString CodeFixTitle => new LocalizableResourceString(nameof(Resources.RequiresUnsafeCodeFixTitle), Resources.ResourceManager, typeof(Resources)); - - private protected override string FullyQualifiedAttributeName => RequiresUnsafeAnalyzer.FullyQualifiedRequiresUnsafeAttribute; - - private protected override AttributeableParentTargets AttributableParentTargets => AttributeableParentTargets.MethodOrConstructor; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - // Register the base code fix (add RequiresUnsafe attribute) - await BaseRegisterCodeFixesAsync(context).ConfigureAwait(false); - - // Register the "wrap in unsafe block" code fix - var document = context.Document; - var diagnostic = context.Diagnostics.First(); - - if (await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root) - return; - - SyntaxNode targetNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); - - // Find the statement containing the unsafe call - var containingStatement = targetNode.AncestorsAndSelf().OfType().FirstOrDefault(); - - // Check if this is a local function with expression body - treat it like expression-bodied member - if (containingStatement is LocalFunctionStatementSyntax localFunc && localFunc.ExpressionBody != null) - { - if (!HasDirectiveTrivia(localFunc.ExpressionBody)) - { - context.RegisterCodeFix(CodeAction.Create( - title: WrapInUnsafeBlockTitle, - createChangedDocument: ct => ConvertExpressionBodyToUnsafeBlockAsync(document, localFunc.ExpressionBody, ct), - equivalenceKey: WrapInUnsafeBlockTitle), diagnostic); - } - return; - } - - if (containingStatement is null || containingStatement is BlockSyntax) - { - // Try expression-bodied member - var arrowExpr = targetNode.AncestorsAndSelf().OfType().FirstOrDefault(); - if (arrowExpr != null && !HasDirectiveTrivia(arrowExpr)) - { - context.RegisterCodeFix(CodeAction.Create( - title: WrapInUnsafeBlockTitle, - createChangedDocument: ct => ConvertExpressionBodyToUnsafeBlockAsync(document, arrowExpr, ct), - equivalenceKey: WrapInUnsafeBlockTitle), diagnostic); - } - return; - } - - // Find the parent block containing this statement - var parentBlock = containingStatement.Parent as BlockSyntax; - if (parentBlock != null) - { - context.RegisterCodeFix(CodeAction.Create( - title: WrapInUnsafeBlockTitle, - createChangedDocument: ct => WrapStatementsInUnsafeBlockAsync(document, parentBlock, containingStatement, ct), - equivalenceKey: WrapInUnsafeBlockTitle), diagnostic); - return; - } - - // Handle switch case sections - var switchSection = containingStatement.Parent as SwitchSectionSyntax; - if (switchSection != null) - { - context.RegisterCodeFix(CodeAction.Create( - title: WrapInUnsafeBlockTitle, - createChangedDocument: ct => WrapSwitchSectionStatementInUnsafeBlockAsync(document, switchSection, containingStatement, ct), - equivalenceKey: WrapInUnsafeBlockTitle), diagnostic); - return; - } - - // Handle embedded statements (if/else/while/for without braces) - if (IsEmbeddedStatement(containingStatement)) - { - context.RegisterCodeFix(CodeAction.Create( - title: WrapInUnsafeBlockTitle, - createChangedDocument: ct => WrapEmbeddedStatementInUnsafeBlockAsync(document, containingStatement, ct), - equivalenceKey: WrapInUnsafeBlockTitle), diagnostic); - return; - } - } - - private static bool IsEmbeddedStatement(StatementSyntax statement) - { - // An embedded statement is a statement that is the direct child of a control flow statement - // without being wrapped in a block (e.g., "if (x) return;" instead of "if (x) { return; }") - return statement.Parent is IfStatementSyntax - || statement.Parent is ElseClauseSyntax - || statement.Parent is WhileStatementSyntax - || statement.Parent is ForStatementSyntax - || statement.Parent is ForEachStatementSyntax - || statement.Parent is DoStatementSyntax - || statement.Parent is UsingStatementSyntax - || statement.Parent is LockStatementSyntax - || statement.Parent is FixedStatementSyntax; - } - - private static async Task WrapStatementsInUnsafeBlockAsync( - Document document, - BlockSyntax parentBlock, - StatementSyntax triggerStatement, - CancellationToken cancellationToken) - { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (semanticModel == null) - return document; - - // Find the trigger statement in the block - var statements = parentBlock.Statements; - int triggerIndex = statements.IndexOf(triggerStatement); - if (triggerIndex < 0) - return document; - - // Check if any statement has directive trivia that would be lost or mangled - var leadingTrivia = triggerStatement.GetLeadingTrivia(); - if (leadingTrivia.Any(t => t.IsDirective)) - { - // Skip the fix - directives in statement trivia would be lost or malformed - return document; - } - - var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - - // Create the TODO comment - var todoComment = SyntaxFactory.Comment("// TODO(unsafe): Baselining unsafe usage"); - var newLine = SyntaxFactory.ElasticCarriageReturnLineFeed; - - // Check if we can use forward declaration strategy - // This applies when the trigger is a local declaration with a single variable - // Ref locals (including ref readonly and scoped ref) cannot be forward-declared - LocalDeclarationStatementSyntax? forwardDecl = null; - StatementSyntax statementToWrap = triggerStatement; - int endIndex = triggerIndex; // For block expansion when forward decl not possible - - bool isRefOrScopedLocal = triggerStatement is LocalDeclarationStatementSyntax localDeclCheck && - (localDeclCheck.Declaration.Type is RefTypeSyntax || localDeclCheck.Declaration.Type is ScopedTypeSyntax); - - if (triggerStatement is LocalDeclarationStatementSyntax localDecl && - !localDecl.IsConst && - !isRefOrScopedLocal && - localDecl.Declaration.Variables.Count == 1 && - localDecl.Declaration.Variables[0].Initializer != null) - { - var variable = localDecl.Declaration.Variables[0]; - TypeSyntax? typeSyntax = localDecl.Declaration.Type; - - // If using 'var', resolve to explicit type - if (typeSyntax.IsVar) - { - var typeInfo = semanticModel.GetTypeInfo(typeSyntax, cancellationToken); - if (typeInfo.Type is not null and not IErrorTypeSymbol) - { - typeSyntax = SyntaxFactory.ParseTypeName(typeInfo.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)) - .WithTrailingTrivia(SyntaxFactory.Space); - } - else - { - // Can't resolve type, fall back to wrapping the whole declaration - typeSyntax = null; - } - } - - if (typeSyntax != null) - { - // Create forward declaration: Type varName; - var forwardDeclVariable = SyntaxFactory.VariableDeclarator(variable.Identifier); - var forwardDeclDeclaration = SyntaxFactory.VariableDeclaration(typeSyntax) - .AddVariables(forwardDeclVariable); - forwardDecl = SyntaxFactory.LocalDeclarationStatement(forwardDeclDeclaration) - .WithLeadingTrivia(triggerStatement.GetLeadingTrivia()) - .WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed)); - - // Create assignment: varName = initializer; - var assignment = SyntaxFactory.AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - SyntaxFactory.IdentifierName(variable.Identifier), - variable.Initializer!.Value); - statementToWrap = SyntaxFactory.ExpressionStatement(assignment) - .WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed)); - } - } - else if (isRefOrScopedLocal) - { - // For ref/scoped locals, we must expand the block until no variables - // declared inside are used outside. This handles chains of dependencies - // where ref locals lead to regular locals that are also used later. - var localDeclStmt = (LocalDeclarationStatementSyntax)triggerStatement; - if (localDeclStmt.Declaration.Variables.Count == 1) - { - // Iteratively expand until no variables declared inside escape outside - bool expanded = true; - while (expanded && endIndex < statements.Count - 1) - { - expanded = false; - - // Collect all variables declared in the current range (using symbols for correct scoping) - var declaredVariableSymbols = new HashSet(SymbolEqualityComparer.Default); - for (int i = triggerIndex; i <= endIndex; i++) - { - if (statements[i] is LocalDeclarationStatementSyntax rangeLocalDecl) - { - foreach (var variable in rangeLocalDecl.Declaration.Variables) - { - var symbol = semanticModel.GetDeclaredSymbol(variable, cancellationToken); - if (symbol is not null) - declaredVariableSymbols.Add(symbol); - } - } - } - - // Check ALL remaining statements - does any use a declared variable? - for (int nextIndex = endIndex + 1; nextIndex < statements.Count; nextIndex++) - { - var stmt = statements[nextIndex]; - - bool usesAnyDeclaredVariable = stmt.DescendantNodes() - .OfType() - .Any(id => - { - var symbolInfo = semanticModel.GetSymbolInfo(id, cancellationToken); - return symbolInfo.Symbol is not null && declaredVariableSymbols.Contains(symbolInfo.Symbol); - }); - - if (usesAnyDeclaredVariable) - { - // Check for directive trivia that would be mangled - if (stmt.GetLeadingTrivia().Any(t => t.IsDirective)) - { - // Stop expansion here - don't include statements with directives - break; - } - - // Expand to include this statement - endIndex = nextIndex; - expanded = true; - break; // Restart the outer loop to recollect variables - } - } - } - } - } - - // Build the statements to wrap - List statementsToWrap; - if (forwardDecl != null) - { - statementsToWrap = new List { statementToWrap }; - } - else - { - statementsToWrap = statements.Skip(triggerIndex).Take(endIndex - triggerIndex + 1).ToList(); - } - - // Create the unsafe block - var wrappedStatements = statementsToWrap.Select(s => - s.WithoutTrivia() - .WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed))); - var unsafeBlock = SyntaxFactory.UnsafeStatement( - SyntaxFactory.Block(wrappedStatements)) - .WithLeadingTrivia(forwardDecl != null - ? SyntaxFactory.TriviaList(todoComment, newLine) - : triggerStatement.GetLeadingTrivia().InsertRange(0, new[] { todoComment, newLine })) - .WithTrailingTrivia(forwardDecl != null - ? triggerStatement.GetTrailingTrivia() - : statementsToWrap.Last().GetTrailingTrivia()); - - // Build the new list of statements - var newStatements = new List(); - for (int i = 0; i < statements.Count; i++) - { - if (i == triggerIndex) - { - if (forwardDecl != null) - { - newStatements.Add(forwardDecl); - } - newStatements.Add(unsafeBlock); - } - else if (i > triggerIndex && i <= endIndex) - { - // Skip - already included in unsafe block - } - else - { - newStatements.Add(statements[i]); - } - } - - var newBlock = parentBlock.WithStatements(SyntaxFactory.List(newStatements)); - editor.ReplaceNode(parentBlock, newBlock); - - return editor.GetChangedDocument(); - } - - private static async Task WrapSwitchSectionStatementInUnsafeBlockAsync( - Document document, - SwitchSectionSyntax _, - StatementSyntax triggerStatement, - CancellationToken cancellationToken) - { - var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - - // Create the TODO comment - var todoComment = SyntaxFactory.Comment("// TODO(unsafe): Baselining unsafe usage"); - var newLine = SyntaxFactory.CarriageReturnLineFeed; - - // Create the unsafe block wrapping just the trigger statement - var unsafeBlock = SyntaxFactory.UnsafeStatement( - SyntaxFactory.Block(triggerStatement.WithoutTrivia())) - .WithLeadingTrivia(triggerStatement.GetLeadingTrivia().InsertRange(0, new[] { todoComment, newLine })) - .WithTrailingTrivia(triggerStatement.GetTrailingTrivia()); - - // Replace the trigger statement with the unsafe block - editor.ReplaceNode(triggerStatement, unsafeBlock); - - return editor.GetChangedDocument(); - } - - private static async Task WrapEmbeddedStatementInUnsafeBlockAsync( - Document document, - StatementSyntax embeddedStatement, - CancellationToken cancellationToken) - { - var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - - // Create the TODO comment - var todoComment = SyntaxFactory.Comment("// TODO(unsafe): Baselining unsafe usage"); - var newLine = SyntaxFactory.CarriageReturnLineFeed; - - // Create the unsafe block wrapping the statement - var unsafeBlock = SyntaxFactory.UnsafeStatement( - SyntaxFactory.Block(embeddedStatement.WithoutTrivia())) - .WithLeadingTrivia(todoComment, newLine); - - // Wrap in a block to replace the embedded statement - var wrappingBlock = SyntaxFactory.Block(unsafeBlock) - .WithLeadingTrivia(embeddedStatement.GetLeadingTrivia()) - .WithTrailingTrivia(embeddedStatement.GetTrailingTrivia()); - - editor.ReplaceNode(embeddedStatement, wrappingBlock); - - return editor.GetChangedDocument(); - } - - private static async Task ConvertExpressionBodyToUnsafeBlockAsync( - Document document, - ArrowExpressionClauseSyntax arrowExpr, - CancellationToken cancellationToken) - { - var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - - // Create the TODO comment - var todoComment = SyntaxFactory.Comment("// TODO(unsafe): Baselining unsafe usage"); - var newLine = SyntaxFactory.CarriageReturnLineFeed; - - // Get the parent member to determine the return type - var parent = arrowExpr.Parent; - bool isVoid = false; - - if (parent is MethodDeclarationSyntax method) - { - isVoid = method.ReturnType is PredefinedTypeSyntax pts && pts.Keyword.IsKind(SyntaxKind.VoidKeyword); - } - else if (parent is LocalFunctionStatementSyntax localFunc) - { - isVoid = localFunc.ReturnType is PredefinedTypeSyntax pts && pts.Keyword.IsKind(SyntaxKind.VoidKeyword); - } - else if (parent is DestructorDeclarationSyntax) - { - isVoid = true; - } - else if (parent is AccessorDeclarationSyntax or PropertyDeclarationSyntax or IndexerDeclarationSyntax) - { - isVoid = false; - } - - // Create the statement that goes inside the unsafe block - StatementSyntax innerStatement = isVoid - ? SyntaxFactory.ExpressionStatement(arrowExpr.Expression.WithoutTrivia()) - : SyntaxFactory.ReturnStatement(arrowExpr.Expression.WithoutTrivia()); - - // Create the unsafe block - var unsafeBlock = SyntaxFactory.UnsafeStatement( - SyntaxFactory.Block(innerStatement)) - .WithLeadingTrivia(todoComment, newLine); - - // Create the block body - var blockBody = SyntaxFactory.Block(unsafeBlock); - - // Replace based on parent type - switch (parent) - { - case MethodDeclarationSyntax methodDecl: - var newMethod = methodDecl - .WithExpressionBody(null) - .WithSemicolonToken(default) - .WithBody(blockBody); - editor.ReplaceNode(methodDecl, newMethod); - break; - - case LocalFunctionStatementSyntax localFunc: - var newLocalFunc = localFunc - .WithExpressionBody(null) - .WithSemicolonToken(default) - .WithBody(blockBody); - editor.ReplaceNode(localFunc, newLocalFunc); - break; - - case DestructorDeclarationSyntax destructorDecl: - var newDestructor = destructorDecl - .WithExpressionBody(null) - .WithSemicolonToken(default) - .WithBody(blockBody); - editor.ReplaceNode(destructorDecl, newDestructor); - break; - - case PropertyDeclarationSyntax propDecl: - var getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithBody(SyntaxFactory.Block(unsafeBlock)); - var accessorList = SyntaxFactory.AccessorList(SyntaxFactory.SingletonList(getter)); - var newProp = propDecl - .WithExpressionBody(null) - .WithSemicolonToken(default) - .WithAccessorList(accessorList); - editor.ReplaceNode(propDecl, newProp); - break; - - case AccessorDeclarationSyntax accessor: - var newAccessor = accessor - .WithExpressionBody(null) - .WithSemicolonToken(default) - .WithBody(SyntaxFactory.Block(unsafeBlock)); - editor.ReplaceNode(accessor, newAccessor); - break; - - case IndexerDeclarationSyntax indexerDecl: - var indexerGetter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithBody(SyntaxFactory.Block(unsafeBlock)); - var indexerAccessorList = SyntaxFactory.AccessorList(SyntaxFactory.SingletonList(indexerGetter)); - var newIndexer = indexerDecl - .WithExpressionBody(null) - .WithSemicolonToken(default) - .WithAccessorList(indexerAccessorList); - editor.ReplaceNode(indexerDecl, newIndexer); - break; - } - - return editor.GetChangedDocument(); - } - - protected override SyntaxNode[] GetAttributeArguments(ISymbol? attributableSymbol, ISymbol targetSymbol, SyntaxGenerator syntaxGenerator, Diagnostic diagnostic) => - RequiresHelpers.GetAttributeArgumentsForRequires(targetSymbol, syntaxGenerator, HasPublicAccessibility(attributableSymbol)); - - /// - /// Checks if the arrow expression clause or its expression has preprocessor directive trivia. - /// Converting expression bodies with directives to block bodies is error-prone, so we skip the fix. - /// - private static bool HasDirectiveTrivia(ArrowExpressionClauseSyntax arrowExpr) - { - // Check the arrow expression clause's leading trivia (e.g., #if or #else before =>) - if (arrowExpr.GetLeadingTrivia().Any(t => t.IsDirective)) - return true; - - // Check the arrow token's trailing trivia (e.g., #if right after =>) - if (arrowExpr.ArrowToken.TrailingTrivia.Any(t => t.IsDirective)) - return true; - - // Check the expression's leading trivia (e.g., #if before the expression) - if (arrowExpr.Expression.GetLeadingTrivia().Any(t => t.IsDirective)) - return true; - - // Check the expression's trailing trivia (e.g., #endif after the expression) - if (arrowExpr.Expression.GetTrailingTrivia().Any(t => t.IsDirective)) - return true; - - // Check the arrow expression clause's trailing trivia - if (arrowExpr.GetTrailingTrivia().Any(t => t.IsDirective)) - return true; - - // Check the parent member for directives between the signature and the arrow - // e.g., method() #if FOO => expr1; #else => expr2; #endif - if (arrowExpr.Parent is MethodDeclarationSyntax method) - { - // Check trivia after the parameter list (where #if might appear) - if (method.ParameterList.GetTrailingTrivia().Any(t => t.IsDirective)) - return true; - // Check constraint clauses if present - foreach (var constraint in method.ConstraintClauses) - { - if (constraint.GetTrailingTrivia().Any(t => t.IsDirective)) - return true; - } - } - else if (arrowExpr.Parent is LocalFunctionStatementSyntax localFunc) - { - if (localFunc.ParameterList.GetTrailingTrivia().Any(t => t.IsDirective)) - return true; - } - else if (arrowExpr.Parent is PropertyDeclarationSyntax prop) - { - if (prop.Identifier.TrailingTrivia.Any(t => t.IsDirective)) - return true; - } - - return false; - } - } -} -#endif diff --git a/src/tools/illink/src/ILLink.CodeFix/Resources.resx b/src/tools/illink/src/ILLink.CodeFix/Resources.resx index 0bd940a98334e2..1e674b6318540a 100644 --- a/src/tools/illink/src/ILLink.CodeFix/Resources.resx +++ b/src/tools/illink/src/ILLink.CodeFix/Resources.resx @@ -126,16 +126,10 @@ Add RequiresDynamicCode attribute to parent method - - Add RequiresUnsafe attribute to parent method - Add UnconditionalSuppressMessage attribute to parent method Add DynamicallyAccessedMembers attribute to source of warning - - Add RequiresUnsafe attribute to method with pointer types - \ No newline at end of file diff --git a/src/tools/illink/src/ILLink.CodeFix/UnsafeMethodMissingRequiresUnsafeCodeFixProvider.cs b/src/tools/illink/src/ILLink.CodeFix/UnsafeMethodMissingRequiresUnsafeCodeFixProvider.cs deleted file mode 100644 index 67769e6394bb4f..00000000000000 --- a/src/tools/illink/src/ILLink.CodeFix/UnsafeMethodMissingRequiresUnsafeCodeFixProvider.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using ILLink.CodeFixProvider; -using ILLink.RoslynAnalyzer; -using ILLink.Shared; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Simplification; - -namespace ILLink.CodeFix -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnsafeMethodMissingRequiresUnsafeCodeFixProvider)), Shared] - public sealed class UnsafeMethodMissingRequiresUnsafeCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider - { - public static ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnsafeMethodMissingRequiresUnsafe)); - - public sealed override ImmutableArray FixableDiagnosticIds => - SupportedDiagnostics.Select(dd => dd.Id).ToImmutableArray(); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var document = context.Document; - var diagnostic = context.Diagnostics.First(); - - if (await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root) - return; - - var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); - var declarationNode = node.AncestorsAndSelf().FirstOrDefault( - n => n is Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax - or Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax - or Microsoft.CodeAnalysis.CSharp.Syntax.PropertyDeclarationSyntax - or Microsoft.CodeAnalysis.CSharp.Syntax.IndexerDeclarationSyntax); - if (declarationNode is null) - return; - - var title = new LocalizableResourceString( - nameof(Resources.UnsafeMethodMissingRequiresUnsafeCodeFixTitle), - Resources.ResourceManager, - typeof(Resources)).ToString(); - - context.RegisterCodeFix(CodeAction.Create( - title: title, - createChangedDocument: ct => AddRequiresUnsafeAttributeAsync(document, declarationNode, ct), - equivalenceKey: title), diagnostic); - } - - private static async Task AddRequiresUnsafeAttributeAsync( - Document document, - SyntaxNode declarationNode, - CancellationToken cancellationToken) - { - if (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false) is not { } model) - return document; - - if (model.Compilation.GetBestTypeByMetadataName(RequiresUnsafeAnalyzer.FullyQualifiedRequiresUnsafeAttribute) is not { } attributeSymbol) - return document; - - var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var generator = editor.Generator; - - var attribute = generator.Attribute(generator.TypeExpression(attributeSymbol)) - .WithAdditionalAnnotations(Simplifier.Annotation, Simplifier.AddImportsAnnotation); - - editor.AddAttribute(declarationNode, attribute); - - return editor.GetChangedDocument(); - } - } -} -#endif diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 4492be99e291fc..5aea7858179e71 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -31,9 +31,6 @@ private static ImmutableArray GetRequiresAnalyzers() builder.Add(new RequiresAssemblyFilesAnalyzer()); builder.Add(new RequiresUnreferencedCodeAnalyzer()); builder.Add(new RequiresDynamicCodeAnalyzer()); -#if DEBUG - builder.Add(new RequiresUnsafeAnalyzer()); -#endif return builder.ToImmutable(); } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs index e924fce0a24d3e..51a5645b4318c6 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs @@ -9,9 +9,6 @@ public static class MSBuildPropertyOptionNames public const string IncludeAllContentForSelfExtract = nameof(IncludeAllContentForSelfExtract); public const string EnableTrimAnalyzer = nameof(EnableTrimAnalyzer); public const string EnableAotAnalyzer = nameof(EnableAotAnalyzer); -#if DEBUG - public const string EnableUnsafeAnalyzer = nameof(EnableUnsafeAnalyzer); -#endif public const string VerifyReferenceAotCompatibility = nameof(VerifyReferenceAotCompatibility); public const string VerifyReferenceTrimCompatibility = nameof(VerifyReferenceTrimCompatibility); } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnsafeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnsafeAnalyzer.cs deleted file mode 100644 index 57f8bc961df4f9..00000000000000 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnsafeAnalyzer.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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; -using System.Collections.Immutable; -using ILLink.Shared; -using ILLink.Shared.TrimAnalysis; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace ILLink.RoslynAnalyzer -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class RequiresUnsafeAnalyzer : RequiresAnalyzerBase - { - internal const string RequiresUnsafeAttributeName = "RequiresUnsafeAttribute"; - public const string FullyQualifiedRequiresUnsafeAttribute = "System.Diagnostics.CodeAnalysis." + RequiresUnsafeAttributeName; - - private static readonly DiagnosticDescriptor s_requiresUnsafeOnStaticCtor = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnsafeOnStaticConstructor); - private static readonly DiagnosticDescriptor s_requiresUnsafeOnEntryPoint = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnsafeOnEntryPoint); - private static readonly DiagnosticDescriptor s_requiresUnsafeRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnsafe); - private static readonly DiagnosticDescriptor s_requiresUnsafeAttributeMismatch = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnsafeAttributeMismatch); - - public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(s_requiresUnsafeRule, s_requiresUnsafeAttributeMismatch, s_requiresUnsafeOnStaticCtor, s_requiresUnsafeOnEntryPoint); - - private protected override string RequiresAttributeName => RequiresUnsafeAttributeName; - - internal override string RequiresAttributeFullyQualifiedName => FullyQualifiedRequiresUnsafeAttribute; - - private protected override DiagnosticTargets AnalyzerDiagnosticTargets => DiagnosticTargets.MethodOrConstructor; - - private protected override DiagnosticDescriptor RequiresDiagnosticRule => s_requiresUnsafeRule; - - private protected override DiagnosticId RequiresDiagnosticId => DiagnosticId.RequiresUnsafe; - - private protected override DiagnosticDescriptor RequiresAttributeMismatch => s_requiresUnsafeAttributeMismatch; - - private protected override DiagnosticDescriptor RequiresOnStaticCtor => s_requiresUnsafeOnStaticCtor; - - private protected override DiagnosticDescriptor RequiresOnEntryPoint => s_requiresUnsafeOnEntryPoint; - - internal override bool IsAnalyzerEnabled(AnalyzerOptions options) => - options.IsMSBuildPropertyValueTrue(MSBuildPropertyOptionNames.EnableUnsafeAnalyzer); - - private protected override bool IsRequiresCheck(IPropertySymbol propertySymbol, Compilation compilation) - { - // No feature check property for RequiresUnsafe - return false; - } - - protected override bool IsInRequiresScope(ISymbol containingSymbol, in DiagnosticContext context) - { - if (base.IsInRequiresScope(containingSymbol, context)) - return true; - - if (!context.Location.IsInSource) - return false; - - // Check to see if we're in an unsafe block or unsafe member - var syntaxTree = context.Location.SourceTree!; - var root = syntaxTree.GetRoot(); - var node = root.FindNode(context.Location.SourceSpan); - while (node != null && node != root) - { - if (node.IsKind(SyntaxKind.UnsafeStatement)) - return true; - - // Check for unsafe modifier on the containing member or type - if (node is MethodDeclarationSyntax method && method.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is LocalFunctionStatementSyntax localFunc && localFunc.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is PropertyDeclarationSyntax prop && prop.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is IndexerDeclarationSyntax indexer && indexer.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is OperatorDeclarationSyntax op && op.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is ConversionOperatorDeclarationSyntax conv && conv.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is ConstructorDeclarationSyntax ctor && ctor.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is FieldDeclarationSyntax field && field.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - if (node is TypeDeclarationSyntax type && type.Modifiers.Any(SyntaxKind.UnsafeKeyword)) - return true; - - node = node.Parent; - } - - return false; - } - - protected override bool VerifyAttributeArguments(AttributeData attribute) => true; - - protected override string GetMessageFromAttribute(AttributeData? requiresAttribute) => ""; - } -} -#endif diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMethodMissingRequiresUnsafeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMethodMissingRequiresUnsafeAnalyzer.cs deleted file mode 100644 index 279a328f70ccaa..00000000000000 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/UnsafeMethodMissingRequiresUnsafeAnalyzer.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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.Immutable; -using ILLink.Shared; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace ILLink.RoslynAnalyzer -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class UnsafeMethodMissingRequiresUnsafeAnalyzer : DiagnosticAnalyzer - { - private static readonly DiagnosticDescriptor s_rule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnsafeMethodMissingRequiresUnsafe, diagnosticSeverity: DiagnosticSeverity.Info); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s_rule); - - public override void Initialize(AnalysisContext context) - { - context.EnableConcurrentExecution(); - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.RegisterCompilationStartAction(context => - { - if (!context.Options.IsMSBuildPropertyValueTrue(MSBuildPropertyOptionNames.EnableUnsafeAnalyzer)) - return; - - if (context.Compilation.GetTypeByMetadataName(RequiresUnsafeAnalyzer.FullyQualifiedRequiresUnsafeAttribute) is null) - return; - - context.RegisterSymbolAction( - AnalyzeMethod, - SymbolKind.Method); - }); - } - - private static void AnalyzeMethod(SymbolAnalysisContext context) - { - if (context.Symbol is not IMethodSymbol method) - return; - - if (!HasPointerInSignature(method)) - return; - - if (method.HasAttribute(RequiresUnsafeAnalyzer.RequiresUnsafeAttributeName)) - return; - - // For property/indexer accessors, check the containing property instead - if (method.AssociatedSymbol is IPropertySymbol property - && property.HasAttribute(RequiresUnsafeAnalyzer.RequiresUnsafeAttributeName)) - return; - - foreach (var location in method.Locations) - { - context.ReportDiagnostic(Diagnostic.Create(s_rule, location, method.GetDisplayName())); - } - } - - private static bool HasPointerInSignature(IMethodSymbol method) - { - if (IsPointerType(method.ReturnType)) - return true; - - foreach (var param in method.Parameters) - { - if (IsPointerType(param.Type)) - return true; - } - - return false; - } - - private static bool IsPointerType(ITypeSymbol type) => type is IPointerTypeSymbol or IFunctionPointerTypeSymbol; - } -} -#endif diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props b/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props index e8a5e17d0c5725..18ae6cb8fd5e8b 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props @@ -3,7 +3,6 @@ - diff --git a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs index d1224b487188f3..997f3f232ae018 100644 --- a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs +++ b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs @@ -219,16 +219,6 @@ public enum DiagnosticId // Feature guard diagnostic ids. ReturnValueDoesNotMatchFeatureGuards = 4000, InvalidFeatureGuard = 4001, - -#if DEBUG - // RequiresUnsafe diagnostics are in the 5000 range, separate from other diagnostics. - RequiresUnsafe = 5000, - RequiresUnsafeAttributeMismatch = 5001, - RequiresUnsafeOnStaticConstructor = 5002, - RequiresUnsafeOnEntryPoint = 5003, - UnsafeMethodMissingRequiresUnsafe = 5004, - _EndRequiresUnsafeWarningsSentinel, -#endif } public static class DiagnosticIdExtensions @@ -253,9 +243,6 @@ public static string GetDiagnosticSubcategory(this DiagnosticId diagnosticId) => >= 2109 and < (int)DiagnosticId._EndTrimAnalysisWarningsSentinel => MessageSubCategory.TrimAnalysis, >= 3050 and <= 3052 => MessageSubCategory.AotAnalysis, >= 3054 and <= 3058 => MessageSubCategory.AotAnalysis, -#if DEBUG - >= 5000 and < (int)DiagnosticId._EndRequiresUnsafeWarningsSentinel => MessageSubCategory.None, -#endif _ => MessageSubCategory.None, }; diff --git a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx index 4e177adb3eb645..77eb755610ecb3 100644 --- a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx +++ b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx @@ -1269,34 +1269,4 @@ Trim dataflow analysis of member '{0}' took too long to complete. Trim safety cannot be guaranteed. - - Calling methods annotated with 'RequiresUnsafeAttribute' may break functionality in environments that do not support unsafe code. - - - Using member '{0}' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'.{1}{2} - - - 'RequiresUnsafeAttribute' annotations must match across all interface implementations or overrides. - - - {0}. 'RequiresUnsafeAttribute' annotations must match across all interface implementations or overrides. - - - 'RequiresUnsafeAttribute' cannot be placed directly on static constructor '{0}'. - - - 'RequiresUnsafeAttribute' cannot be used on static constructors because they are not directly callable. Consider placing the attribute on the type instead. - - - 'RequiresUnsafeAttribute' cannot be placed directly on application entry point '{0}'. - - - The use of 'RequiresUnsafeAttribute' on entry points is disallowed since the method will be called from outside the visible app. - - - Methods with pointer types in their signature should be annotated with 'RequiresUnsafeAttribute'. - - - Method '{0}' has pointer types in its signature but is not annotated with 'RequiresUnsafeAttribute'. - diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeAnalyzerTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeAnalyzerTests.cs deleted file mode 100644 index 49a709960ca8c6..00000000000000 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeAnalyzerTests.cs +++ /dev/null @@ -1,465 +0,0 @@ -// 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; -using System.Threading.Tasks; -using ILLink.Shared; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Testing; -using Xunit; -using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpAnalyzerVerifier< - ILLink.RoslynAnalyzer.DynamicallyAccessedMembersAnalyzer>; - -namespace ILLink.RoslynAnalyzer.Tests -{ - public class RequiresUnsafeAnalyzerTests - { - static readonly string unsafeAttribute = @" -#nullable enable - -namespace System.Diagnostics.CodeAnalysis -{ - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute - { } -}"; - - static async Task VerifyRequiresUnsafeAnalyzer( - string source, - params DiagnosticResult[] expected) - { - await VerifyCS.VerifyAnalyzerAsync( - source + unsafeAttribute, - consoleApplication: false, - TestCaseUtils.UseMSBuildProperties(MSBuildPropertyOptionNames.EnableUnsafeAnalyzer), - Array.Empty(), - allowUnsafe: true, - expected); - } - - [Fact] - public async Task SimpleDiagnostic() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - int M2() => M1(); - } - class D - { - public int M3(C c) => c.M1(); - - public class E - { - public int M4(C c) => c.M1(); - } - } - public class E - { - public class F - { - public int M5(C c) => c.M1(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer( - source: test, - new[] { - // /0/Test0.cs(8,17): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(8, 17, 8, 19).WithArguments("C.M1()", "", ""), - // /0/Test0.cs(12,27): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(12, 27, 12, 31).WithArguments("C.M1()", "", ""), - // /0/Test0.cs(16,31): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(16, 31, 16, 35).WithArguments("C.M1()", "", ""), - // /0/Test0.cs(23,31): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(23, 31, 23, 35).WithArguments("C.M1()", "", "") - }); - } - - [Fact] - public Task InLambda() - { - var src = """ - using System; - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafeAttribute] - public int M1() => 0; - - Action M2() - { - return () => M1(); - } - } - """; - var diag = new[] { - // /0/Test0.cs(11,22): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(11, 22, 11, 24).WithArguments("C.M1()", "", "") - }; - return VerifyRequiresUnsafeAnalyzer(src, diag); - } - - [Fact] - public Task InLocalFunc() - { - var src = """ - using System; - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - Action M2() - { - void Wrapper() => M1(); - return Wrapper; - } - } - """; - return VerifyRequiresUnsafeAnalyzer( - source: src, - new[] { - // /0/Test0.cs(11,27): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(11, 27, 11, 29).WithArguments("C.M1()", "", "") - }); - } - - [Fact] - public Task InCtor() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public C() => M1(); - } - """; - return VerifyRequiresUnsafeAnalyzer( - source: src, - expected: new[] { - // /0/Test0.cs(9,19): warning IL3059: Using member 'C.M1()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(8, 19, 8, 21).WithArguments("C.M1()", "", "") - }); - } - - [Fact] - public async Task RequiresUnsafeOnConstructor() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public C() { } - - public void M() - { - new C(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source, - // /0/Test0.cs(10,9): warning IL3059: Using member 'C.C()' which has 'RequiresUnsafeAttribute' requires an unsafe context, such as an unsafe block or a method marked with 'RequiresUnsafeAttribute'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe).WithSpan(10, 9, 10, 16).WithArguments("C.C()", "", "") - ); - } - - [Fact] - public async Task RequiresUnsafeInSameScope() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public void M1() { } - - [RequiresUnsafe] - public void M2() - { - M1(); // Should not warn - already in RequiresUnsafe scope - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source); - } - - [Fact] - public async Task RequiresUnsafeOnStaticConstructor() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - static C() { } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source, - // /0/Test0.cs(6,12): warning IL3061: 'RequiresUnsafeAttribute' cannot be placed directly on static constructor 'C.C()'. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafeOnStaticConstructor).WithSpan(6, 12, 6, 13).WithArguments("C..cctor()") - ); - } - - [Fact] - public async Task RequiresUnsafeAttributeMismatchOnOverride() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class Base - { - [RequiresUnsafe] - public virtual void M() { } - } - - public class Derived : Base - { - public override void M() { } // Should warn about mismatch - } - """; - - await VerifyRequiresUnsafeAnalyzer(source, - // (11,26): warning IL3060: Base member 'Base.M()' with 'RequiresUnsafeAttribute' has a derived member 'Derived.M()' without 'RequiresUnsafeAttribute'. 'RequiresUnsafeAttribute' annotations must match across all interface implementations or overrides. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafeAttributeMismatch).WithSpan(11, 26, 11, 27).WithArguments("Base member 'Base.M()' with 'RequiresUnsafeAttribute' has a derived member 'Derived.M()' without 'RequiresUnsafeAttribute'") - ); - } - - [Fact] - public async Task RequiresUnsafeAttributeMismatchOnInterface() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public interface IFoo - { - [RequiresUnsafe] - void M(); - } - - public class Foo : IFoo - { - public void M() { } // Should warn about mismatch - } - """; - - await VerifyRequiresUnsafeAnalyzer(source, - // (11,17): warning IL3060: Interface member 'IFoo.M()' with 'RequiresUnsafeAttribute' has an implementation member 'Foo.M()' without 'RequiresUnsafeAttribute'. 'RequiresUnsafeAttribute' annotations must match across all interface implementations or overrides. - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafeAttributeMismatch).WithSpan(11, 17, 11, 18).WithArguments("Interface member 'IFoo.M()' with 'RequiresUnsafeAttribute' has an implementation member 'Foo.M()' without 'RequiresUnsafeAttribute'") - ); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeBlock() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - public int M2() - { - unsafe - { - return M1(); - } - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeMethod() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - public unsafe int M2() - { - return M1(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeClass() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public unsafe class C - { - [RequiresUnsafe] - public int M1() => 0; - - public int M2() - { - return M1(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeProperty() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - public unsafe int P => M1(); - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeLocalFunction() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - public int M2() - { - unsafe int Local() => M1(); - return Local(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeConstructor() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public unsafe C() - { - _ = M1(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideLambdaInUnsafeMethod() - { - var src = """ - using System; - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - public unsafe void M2() - { - Action a = () => M1(); - a(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideAnonymousDelegateInUnsafeMethod() - { - var src = """ - using System; - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public int M1() => 0; - - public unsafe void M2() - { - Action a = delegate { M1(); }; - a(); - } - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - - [Fact] - public async Task RequiresUnsafeInsideUnsafeFieldInitializer() - { - var src = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - private static unsafe int _field = M1(); - } - """; - - await VerifyRequiresUnsafeAnalyzer(source: src); - } - } -} -#endif diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs deleted file mode 100644 index 3ef160f71e6eb9..00000000000000 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresUnsafeCodeFixTests.cs +++ /dev/null @@ -1,1913 +0,0 @@ -// 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; -using System.Threading.Tasks; -using ILLink.Shared; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; -using Xunit; -using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpCodeFixVerifier< - ILLink.RoslynAnalyzer.DynamicallyAccessedMembersAnalyzer, - ILLink.CodeFix.RequiresUnsafeCodeFixProvider>; - -namespace ILLink.RoslynAnalyzer.Tests -{ - public class RequiresUnsafeCodeFixTests - { - static Task VerifyRequiresUnsafeCodeFix( - string source, - string fixedSource, - DiagnosticResult[] baselineExpected, - DiagnosticResult[] fixedExpected, - int? numberOfIterations = null, - int codeActionIndex = 1) - { - var test = new VerifyCS.Test - { - TestCode = source, - FixedCode = fixedSource, - CodeActionIndex = codeActionIndex - }; - test.ExpectedDiagnostics.AddRange(baselineExpected); - test.TestState.AnalyzerConfigFiles.Add( - ("/.editorconfig", SourceText.From(@$" -is_global = true -build_property.{MSBuildPropertyOptionNames.EnableUnsafeAnalyzer} = true"))); - // Enable unsafe code compilation - test.SolutionTransforms.Add((solution, projectId) => - { - var project = solution.GetProject(projectId)!; - var compilationOptions = (CSharpCompilationOptions)project.CompilationOptions!; - compilationOptions = compilationOptions.WithAllowUnsafe(true); - return solution.WithProjectCompilationOptions(projectId, compilationOptions); - }); - if (numberOfIterations != null) - { - test.NumberOfIncrementalIterations = numberOfIterations; - test.NumberOfFixAllIterations = numberOfIterations; - } - test.FixedState.ExpectedDiagnostics.AddRange(fixedExpected); - return test.RunAsync(); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_SimpleStatement() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x = M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - x = M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 17, 10, 19) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionStatement() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - public void M2() - { - M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - public void M2() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 9, 10, 11) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ReturnStatement() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - { - return M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 16, 10, 18) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_IfStatement() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static bool M1() => true; - - public void M2() - { - if (M1()) - { - System.Console.WriteLine("yes"); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static bool M1() => true; - - public void M2() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - if (M1()) - { - System.Console.WriteLine("yes"); - } - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 13, 10, 15) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task NoWarning_InsideUnsafeBlock() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - unsafe - { - int x = M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // No diagnostics expected - already in unsafe block - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: test, // No change expected - baselineExpected: Array.Empty(), - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task NoWarning_InsideUnsafeMethod() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public unsafe void M2() - { - int x = M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // No diagnostics expected - method has unsafe modifier - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: test, // No change expected - baselineExpected: Array.Empty(), - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task NoWarning_InsideUnsafeClass() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public unsafe class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x = M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // No diagnostics expected - class has unsafe modifier - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: test, // No change expected - baselineExpected: Array.Empty(), - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task NoWarning_InsideUnsafeProperty() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public unsafe int P - { - get => M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // No diagnostics expected - property has unsafe modifier - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: test, // No change expected - baselineExpected: Array.Empty(), - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task NoWarning_InsideUnsafeLocalFunction() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - unsafe int Local() => M1(); - _ = Local(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // No diagnostics expected - local function has unsafe modifier - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: test, // No change expected - baselineExpected: Array.Empty(), - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionBodiedMethod() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() => M1(); - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(8, 24, 8, 26) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionBodiedVoidMethod() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - public void M2() => M1(); - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - public void M2() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(8, 25, 8, 27) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionBodiedDestructor() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - ~C() => M1(); - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - ~C() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - M1(); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(8, 13, 8, 15) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionBodiedProperty() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int P => M1(); - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int P - { - get - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(8, 21, 8, 23) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty(), - codeActionIndex: 0); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionBodiedAccessor() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int P - { - get => M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int P - { - [RequiresUnsafe()] - get => M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 16, 10, 18) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty(), - codeActionIndex: 0); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_LocalFunction_ConvertsExpressionBody() - { - // Local functions with expression bodies are converted to block bodies - // with the unsafe block inside, preserving their scope. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int Local() => M1(); - _ = Local(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int Local() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - } - - _ = Local(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 24, 10, 26) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ForwardDeclaration() - { - // When a variable is declared with unsafe code and used later, - // use forward declaration instead of expanding the unsafe block. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x = M1(); - int y = x + 1; - System.Console.WriteLine(y); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - x = M1(); - } - int y = x + 1; - System.Console.WriteLine(y); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 17, 10, 19) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_UnusedVariable() - { - // When a variable declared with unsafe code is not used after, - // forward declaration still applies consistently. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x = M1(); - int y = 42; - System.Console.WriteLine(y); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - x = M1(); - } - int y = 42; - System.Console.WriteLine(y); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 17, 10, 19) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ForwardDeclaration_VarType() - { - // When using 'var', the forward declaration should use the explicit type. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - var x = M1(); - System.Console.WriteLine(x); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - x = M1(); - } - System.Console.WriteLine(x); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 17, 10, 19) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_RefLocal_NoForwardDeclaration() - { - // Ref locals cannot use forward declaration (can't declare without initializer), - // so wrap the entire declaration in the unsafe block. - // Note: If the ref local is used after the declaration, those uses will be broken - // and need manual fixing. This test has no usage after the declaration. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - private int _field; - - [RequiresUnsafe] - public static ref int M1(ref int x) => ref x; - - public void M2() - { - ref int x = ref M1(ref _field); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // ref locals can't be forward-declared, so wrap the whole declaration - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - private int _field; - - [RequiresUnsafe] - public static ref int M1(ref int x) => ref x; - - public void M2() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - ref int x = ref M1(ref _field); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(12, 25, 12, 27) - .WithArguments("C.M1(ref Int32)", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_RefLocalFromUnsafeAs_NoForwardDeclaration() - { - // Pattern matching real-world usage: ref byte x = ref Unsafe.As(ref source) - // The Unsafe.As call has [RequiresUnsafe], and the result is assigned to a ref local. - // When the ref local is used after the declaration, those statements must be included - // in the unsafe block since ref locals can't be forward-declared. - var test = """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - - public class C - { - public void M2() - { - char c = 'x'; - ref byte x = ref Unsafe.As(ref c); - int y = x + 1; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - - namespace System.Runtime.CompilerServices - { - public static class Unsafe - { - [RequiresUnsafe] - public static ref TTo As(ref TFrom source) => throw null!; - } - } - """; - - // ref locals can't be forward-declared, so must expand block to include usages - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - - public class C - { - public void M2() - { - char c = 'x'; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - ref byte x = ref Unsafe.As(ref c); - int y = x + 1; - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - - namespace System.Runtime.CompilerServices - { - public static class Unsafe - { - [RequiresUnsafe] - public static ref TTo As(ref TFrom source) => throw null!; - } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(9, 26, 9, 47) - .WithArguments("System.Runtime.CompilerServices.Unsafe.As(ref TFrom)", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ChainedRefLocals_ExpandsToIncludeAll() - { - // When a ref local is used to create another ref local, and that second ref local - // is used later, the unsafe block must expand to include ALL usages. - // This matches the pattern in CharUnicodeInfo.cs where rsStart is used to create rsDelta. - var test = """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - - public class C - { - public void M2() - { - byte b = 0; - ref ushort rsStart = ref Unsafe.As(ref b); - ref ushort rsDelta = ref Unsafe.Add(ref rsStart, 1); - int delta = rsDelta; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - - namespace System.Runtime.CompilerServices - { - public static class Unsafe - { - [RequiresUnsafe] - public static ref TTo As(ref TFrom source) => throw null!; - public static ref T Add(ref T source, int elementOffset) => throw null!; - } - } - """; - - // Both ref locals and the usage of rsDelta must be inside the unsafe block - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - - public class C - { - public void M2() - { - byte b = 0; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - ref ushort rsStart = ref Unsafe.As(ref b); - ref ushort rsDelta = ref Unsafe.Add(ref rsStart, 1); - int delta = rsDelta; - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - - namespace System.Runtime.CompilerServices - { - public static class Unsafe - { - [RequiresUnsafe] - public static ref TTo As(ref TFrom source) => throw null!; - public static ref T Add(ref T source, int elementOffset) => throw null!; - } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(9, 34, 9, 57) - .WithArguments("System.Runtime.CompilerServices.Unsafe.As(ref TFrom)", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_RefLocal_RegularVarEscapes_ExpandsToIncludeUsage() - { - // When a ref local block expansion pulls in a regular variable declaration, - // and that regular variable is used after the block, we must expand further. - // This matches the CharUnicodeInfo.cs pattern. - var test = """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - - public class C - { - public int M2() - { - byte b = 0; - ref ushort rsStart = ref Unsafe.As(ref b); - ref ushort rsDelta = ref Unsafe.Add(ref rsStart, 1); - int delta = rsDelta; - return delta + 1; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - - namespace System.Runtime.CompilerServices - { - public static class Unsafe - { - [RequiresUnsafe] - public static ref TTo As(ref TFrom source) => throw null!; - public static ref T Add(ref T source, int elementOffset) => throw null!; - } - } - """; - - // Block must expand to include return statement since delta is used there - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - - public class C - { - public int M2() - { - byte b = 0; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - ref ushort rsStart = ref Unsafe.As(ref b); - ref ushort rsDelta = ref Unsafe.Add(ref rsStart, 1); - int delta = rsDelta; - return delta + 1; - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - - namespace System.Runtime.CompilerServices - { - public static class Unsafe - { - [RequiresUnsafe] - public static ref TTo As(ref TFrom source) => throw null!; - public static ref T Add(ref T source, int elementOffset) => throw null!; - } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(9, 34, 9, 57) - .WithArguments("System.Runtime.CompilerServices.Unsafe.As(ref TFrom)", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_RefReadonlyLocal_NoForwardDeclaration() - { - // Ref readonly locals cannot use forward declaration, so wrap the declaration. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static ref readonly int M1(in int x) => ref x; - - public void M2() - { - int value = 42; - ref readonly int x = ref M1(in value); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static ref readonly int M1(in int x) => ref x; - - public void M2() - { - int value = 42; - // TODO(unsafe): Baselining unsafe usage - unsafe - { - ref readonly int x = ref M1(in value); - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(11, 34, 11, 36) - .WithArguments("C.M1(in Int32)", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_NotOfferedForStatementsWithPragmaDirectives() - { - // When statements to wrap have #pragma directives in their leading trivia, - // the "Wrap in unsafe block" fix should NOT be offered because it would - // destroy the directive structure. Only the "Add attribute" fix should be available. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public void M2() - { - int x = M1(); - #pragma warning disable CS0168 - if (x > 0) - #pragma warning restore CS0168 - { - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // The fix adds [RequiresUnsafe] attribute to the method instead of wrapping in unsafe block - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - [RequiresUnsafe()] - public void M2() - { - int x = M1(); - #pragma warning disable CS0168 - if (x > 0) - #pragma warning restore CS0168 - { - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // Use codeActionIndex: 0 since only "Add attribute" is offered (not "Wrap in unsafe block") - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 17, 10, 19) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty(), - codeActionIndex: 0); - } - - [Fact] - public async Task CodeFix_AddRequiresUnsafeAttribute_Method() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - { - return M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - [RequiresUnsafe()] - public int M2() - { - return M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var addAttributeTest = new VerifyCS.Test - { - TestCode = test, - FixedCode = fixedSource, - CodeActionIndex = 0 - }; - addAttributeTest.ExpectedDiagnostics.Add( - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 16, 10, 18) - .WithArguments("C.M1()", "", "")); - addAttributeTest.TestState.AnalyzerConfigFiles.Add( - ("/.editorconfig", SourceText.From(@$" -is_global = true -build_property.{MSBuildPropertyOptionNames.EnableUnsafeAnalyzer} = true"))); - addAttributeTest.SolutionTransforms.Add((solution, projectId) => - { - var project = solution.GetProject(projectId)!; - var compilationOptions = (CSharpCompilationOptions)project.CompilationOptions!; - compilationOptions = compilationOptions.WithAllowUnsafe(true); - return solution.WithProjectCompilationOptions(projectId, compilationOptions); - }); - await addAttributeTest.RunAsync(); - } - - [Fact] - public async Task CodeFix_AddRequiresUnsafeAttribute_Constructor() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - public C() - { - M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static void M1() { } - - [RequiresUnsafe()] - public C() - { - M1(); - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var addAttributeTest = new VerifyCS.Test - { - TestCode = test, - FixedCode = fixedSource, - CodeActionIndex = 0 - }; - addAttributeTest.ExpectedDiagnostics.Add( - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(10, 9, 10, 11) - .WithArguments("C.M1()", "", "")); - addAttributeTest.TestState.AnalyzerConfigFiles.Add( - ("/.editorconfig", SourceText.From(@$" -is_global = true -build_property.{MSBuildPropertyOptionNames.EnableUnsafeAnalyzer} = true"))); - addAttributeTest.SolutionTransforms.Add((solution, projectId) => - { - var project = solution.GetProject(projectId)!; - var compilationOptions = (CSharpCompilationOptions)project.CompilationOptions!; - compilationOptions = compilationOptions.WithAllowUnsafe(true); - return solution.WithProjectCompilationOptions(projectId, compilationOptions); - }); - await addAttributeTest.RunAsync(); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_NotOfferedForExpressionBodyWithPreprocessorDirectives() - { - // When an expression-bodied member has preprocessor directives (#if/#else/#endif), - // the "Wrap in unsafe block" fix should NOT be offered because it would destroy - // the conditional compilation structure. Only the "Add attribute" fix should be available. - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - #if SOME_DEFINE - => 42; - #else - => M1(); - #endif - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - // The fix should add [RequiresUnsafe] attribute (CodeActionIndex = 0) - // The "Wrap in unsafe block" fix (CodeActionIndex = 1) should NOT be available - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - [RequiresUnsafe()] - public int M2() - #if SOME_DEFINE - => 42; - #else - => M1(); - #endif - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var addAttributeTest = new VerifyCS.Test - { - TestCode = test, - FixedCode = fixedSource, - CodeActionIndex = 0, - NumberOfIncrementalIterations = 1, - NumberOfFixAllIterations = 1 - }; - addAttributeTest.ExpectedDiagnostics.Add( - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(12, 12, 12, 14) - .WithArguments("C.M1()", "", "")); - addAttributeTest.TestState.AnalyzerConfigFiles.Add( - ("/.editorconfig", SourceText.From(@$" -is_global = true -build_property.{MSBuildPropertyOptionNames.EnableUnsafeAnalyzer} = true"))); - addAttributeTest.SolutionTransforms.Add((solution, projectId) => - { - var project = solution.GetProject(projectId)!; - var compilationOptions = (CSharpCompilationOptions)project.CompilationOptions!; - compilationOptions = compilationOptions.WithAllowUnsafe(true); - return solution.WithProjectCompilationOptions(projectId, compilationOptions); - }); - await addAttributeTest.RunAsync(); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_SwitchCaseSection() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2(int x) - { - switch (x) - { - case 1: - return M1(); - default: - return 0; - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2(int x) - { - switch (x) - { - case 1: - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - default: - return 0; - } - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(13, 24, 13, 26) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_IfStatementWithoutBraces() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2(bool condition) - { - if (condition) - return M1(); - return 0; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2(bool condition) - { - if (condition) - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - } - return 0; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(11, 20, 11, 22) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - - [Fact] - public async Task CodeFix_WrapInUnsafeBlock_ExpressionBodiedLocalFunction() - { - var test = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - { - int x = 1; - static int LocalFunc() => M1(); - return LocalFunc() + x; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public static int M1() => 0; - - public int M2() - { - int x = 1; - static int LocalFunc() - { - // TODO(unsafe): Baselining unsafe usage - unsafe - { - return M1(); - } - } - - return LocalFunc() + x; - } - } - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - await VerifyRequiresUnsafeCodeFix( - source: test, - fixedSource: fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.RequiresUnsafe) - .WithSpan(11, 35, 11, 37) - .WithArguments("C.M1()", "", "") - }, - fixedExpected: Array.Empty()); - } - } -} -#endif diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs index 6e1d5b070749f9..5bfbc2ac5dcd6b 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs @@ -26,9 +26,6 @@ private static ImmutableArray CreateSupportedDiagnosticAnaly builder.Add(new RequiresAssemblyFilesAnalyzer()); builder.Add(new RequiresUnreferencedCodeAnalyzer()); builder.Add(new DynamicallyAccessedMembersAnalyzer()); -#if DEBUG - builder.Add(new RequiresUnsafeAnalyzer()); -#endif return builder.ToImmutable(); } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMethodMissingRequiresUnsafeTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMethodMissingRequiresUnsafeTests.cs deleted file mode 100644 index b606022621af78..00000000000000 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMethodMissingRequiresUnsafeTests.cs +++ /dev/null @@ -1,278 +0,0 @@ -// 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; -using System.Threading.Tasks; -using ILLink.Shared; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; -using Xunit; -using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpCodeFixVerifier< - ILLink.RoslynAnalyzer.UnsafeMethodMissingRequiresUnsafeAnalyzer, - ILLink.CodeFix.UnsafeMethodMissingRequiresUnsafeCodeFixProvider>; - -namespace ILLink.RoslynAnalyzer.Tests -{ - public class UnsafeMethodMissingRequiresUnsafeTests - { - static readonly string RequiresUnsafeAttributeDefinition = """ - - namespace System.Diagnostics.CodeAnalysis - { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, Inherited = false)] - public sealed class RequiresUnsafeAttribute : Attribute { } - } - """; - - static Task VerifyCodeFix( - string source, - string fixedSource, - DiagnosticResult[] baselineExpected, - DiagnosticResult[] fixedExpected, - int? numberOfIterations = null) - { - var test = new VerifyCS.Test { - TestCode = source, - FixedCode = fixedSource, - }; - test.ExpectedDiagnostics.AddRange(baselineExpected); - test.TestState.AnalyzerConfigFiles.Add( - ("/.editorconfig", SourceText.From(@$" -is_global = true -build_property.{MSBuildPropertyOptionNames.EnableUnsafeAnalyzer} = true"))); - test.SolutionTransforms.Add((solution, projectId) => { - var project = solution.GetProject(projectId)!; - var compilationOptions = (CSharpCompilationOptions)project.CompilationOptions!; - compilationOptions = compilationOptions.WithAllowUnsafe(true); - return solution.WithProjectCompilationOptions(projectId, compilationOptions); - }); - if (numberOfIterations != null) { - test.NumberOfIncrementalIterations = numberOfIterations; - test.NumberOfFixAllIterations = numberOfIterations; - } - test.FixedState.ExpectedDiagnostics.AddRange(fixedExpected); - return test.RunAsync(); - } - - static Task VerifyNoDiagnostic(string source) - { - var test = new VerifyCS.Test { - TestCode = source, - FixedCode = source, - }; - test.TestState.AnalyzerConfigFiles.Add( - ("/.editorconfig", SourceText.From(@$" -is_global = true -build_property.{MSBuildPropertyOptionNames.EnableUnsafeAnalyzer} = true"))); - test.SolutionTransforms.Add((solution, projectId) => { - var project = solution.GetProject(projectId)!; - var compilationOptions = (CSharpCompilationOptions)project.CompilationOptions!; - compilationOptions = compilationOptions.WithAllowUnsafe(true); - return solution.WithProjectCompilationOptions(projectId, compilationOptions); - }); - return test.RunAsync(); - } - - [Fact] - public async Task MethodAlreadyAttributed_NoDiagnostic() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public unsafe int* M() => default; - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyNoDiagnostic(source); - } - - [Fact] - public async Task UnsafeMethodWithoutPointerTypes_NoDiagnostic() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - public unsafe void M() { } - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyNoDiagnostic(source); - } - - [Fact] - public async Task NonUnsafeMethod_NoDiagnostic() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - public void M() { } - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyNoDiagnostic(source); - } - - [Fact] - public async Task CodeFix_MethodReturningPointer_AddsAttribute() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - public unsafe int* M() => default; - } - """ + RequiresUnsafeAttributeDefinition; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public unsafe int* M() => default; - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyCodeFix( - source, - fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.UnsafeMethodMissingRequiresUnsafe) - .WithSpan(5, 24, 5, 25) - .WithArguments("C.M()") - .WithSeverity(DiagnosticSeverity.Info) - }, - fixedExpected: Array.Empty ()); - } - - [Fact] - public async Task CodeFix_MethodTakingPointerParam_AddsAttribute() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - public unsafe void M(int* p) { } - } - """ + RequiresUnsafeAttributeDefinition; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public unsafe void M(int* p) { } - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyCodeFix( - source, - fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.UnsafeMethodMissingRequiresUnsafe) - .WithSpan(5, 24, 5, 25) - .WithArguments("C.M(Int32*)") - .WithSeverity(DiagnosticSeverity.Info) - }, - fixedExpected: Array.Empty ()); - } - - [Fact] - public async Task CodeFix_MethodTakingFunctionPointer_AddsAttribute() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - public unsafe void M(delegate* f) { } - } - """ + RequiresUnsafeAttributeDefinition; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public class C - { - [RequiresUnsafe] - public unsafe void M(delegate* f) { } - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyCodeFix( - source, - fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.UnsafeMethodMissingRequiresUnsafe) - .WithSpan(5, 24, 5, 25) - .WithArguments("C.M(delegate*)") - .WithSeverity(DiagnosticSeverity.Info) - }, - fixedExpected: Array.Empty ()); - } - - [Fact] - public async Task CodeFix_PropertyReturningPointer_AddsAttribute() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public unsafe class C - { - public int* P => default; - } - """ + RequiresUnsafeAttributeDefinition; - - var fixedSource = """ - using System.Diagnostics.CodeAnalysis; - - public unsafe class C - { - [RequiresUnsafe] - public int* P => default; - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyCodeFix( - source, - fixedSource, - baselineExpected: new[] { - VerifyCS.Diagnostic(DiagnosticId.UnsafeMethodMissingRequiresUnsafe) - .WithSpan(5, 22, 5, 29) - .WithArguments("C.P.get") - .WithSeverity(DiagnosticSeverity.Info) - }, - fixedExpected: Array.Empty ()); - } - - [Fact] - public async Task PropertyAlreadyAttributed_NoDiagnostic() - { - var source = """ - using System.Diagnostics.CodeAnalysis; - - public unsafe class C - { - [RequiresUnsafe] - public int* P => default; - } - """ + RequiresUnsafeAttributeDefinition; - - await VerifyNoDiagnostic(source); - } - } -} -#endif