| File: Microsoft.NetCore.Analyzers\Usage\UseVolatileReadWrite.Fixer.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.NetAnalyzers; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Text; namespace Microsoft.NetCore.Analyzers.Usage { public abstract class UseVolatileReadWriteFixer : SyntaxEditorBasedCodeFixProvider { private const string ThreadVolatileReadMethodName = nameof(Thread.VolatileRead); private const string ThreadVolatileWriteMethodName = nameof(Thread.VolatileWrite); private const string VolatileReadMethodName = nameof(Volatile.Read); private const string VolatileWriteMethodName = nameof(Volatile.Write); public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create("SYSLIB0054"); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); if (TryGetObsoleteCall(semanticModel, root, context.Span, context.CancellationToken).Invocation is null) { return; } RegisterCodeFix(context, MicrosoftNetCoreAnalyzersResources.DoNotUseThreadVolatileReadWriteCodeFixTitle, nameof(MicrosoftNetCoreAnalyzersResources.DoNotUseThreadVolatileReadWriteCodeFixTitle)); } protected sealed override async Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var (invocation, volatileType) = TryGetObsoleteCall(semanticModel, editor.OriginalRoot, diagnostic.Location.SourceSpan, cancellationToken); if (invocation is null || volatileType is null) { return; } var generator = editor.Generator; string methodName; ImmutableArray<IParameterSymbol> parameters; if (invocation.TargetMethod.Name.Equals(ThreadVolatileReadMethodName, StringComparison.Ordinal)) { methodName = VolatileReadMethodName; parameters = volatileType.GetMembers(VolatileReadMethodName).OfType<IMethodSymbol>().First().Parameters; } else { methodName = VolatileWriteMethodName; parameters = volatileType.GetMembers(VolatileWriteMethodName).OfType<IMethodSymbol>().First().Parameters; } // IInvocationOperation.Arguments is in parameter order, which is the order the rewritten // call is emitted in, but the arguments themselves have to be taken from the syntax. Map // each one back to where it appears in source so the two can be zipped up below. var originalArguments = GetArguments(invocation.Syntax); var sourceIndices = invocation.Arguments.Select(argument => originalArguments.IndexOf(argument.Syntax)).ToImmutableArray(); var parameterNames = invocation.Arguments.Select(argument => parameters[argument.Parameter!.Ordinal].Name).ToImmutableArray(); var methodExpression = generator.MemberAccessExpression( generator.TypeExpressionForStaticMemberAccess(volatileType), methodName); // Thread.VolatileWrite takes its value by value, so one diagnosed call can sit inside // another's argument. The arguments are therefore read off the node as already rewritten // rather than off SyntaxEditor.OriginalRoot, which would re-emit the inner call from its // pre-fix syntax and drop the annotation the fix-all provider tracks it by. editor.ReplaceNode(invocation.Syntax, (currentNode, currentGenerator) => { var currentArguments = GetArguments(currentNode); var arguments = sourceIndices.Select((sourceIndex, i) => WithParameterName(currentArguments[sourceIndex], parameterNames[i])); return currentGenerator.InvocationExpression(methodExpression, arguments).WithTriviaFrom(currentNode); }); } protected abstract ImmutableArray<SyntaxNode> GetArguments(SyntaxNode invocationSyntax); /// <summary> /// Renames an explicitly named argument to <paramref name="parameterName"/>, returning /// <paramref name="argumentSyntax"/> unchanged when the argument is positional. /// </summary> protected abstract SyntaxNode WithParameterName(SyntaxNode argumentSyntax, string parameterName); private static (IInvocationOperation? Invocation, INamedTypeSymbol? VolatileType) TryGetObsoleteCall( SemanticModel semanticModel, SyntaxNode root, TextSpan span, CancellationToken cancellationToken) { var node = root.FindNode(span, getInnermostNodeForTie: true); var typeProvider = WellKnownTypeProvider.GetOrCreate(semanticModel.Compilation); if (typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingThread) is not INamedTypeSymbol threadType || typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingVolatile) is not INamedTypeSymbol volatileType || semanticModel.GetOperation(node, cancellationToken) is not IInvocationOperation invocationOperation) { return default; } var obsoleteMethodsBuilder = ImmutableArray.CreateBuilder<IMethodSymbol>(); obsoleteMethodsBuilder.AddRange(threadType.GetMembers(ThreadVolatileReadMethodName).OfType<IMethodSymbol>()); obsoleteMethodsBuilder.AddRange(threadType.GetMembers(ThreadVolatileWriteMethodName).OfType<IMethodSymbol>()); var obsoleteMethods = obsoleteMethodsBuilder.ToImmutable(); if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, threadType) || !obsoleteMethods.Any(SymbolEqualityComparer.Default.Equals, invocationOperation.TargetMethod) || volatileType.GetMembers(VolatileReadMethodName).OfType<IMethodSymbol>().FirstOrDefault() is null || volatileType.GetMembers(VolatileWriteMethodName).OfType<IMethodSymbol>().FirstOrDefault() is null) { return default; } return (invocationOperation, volatileType); } } }