private void AnalyzeOperationBlock(OperationBlockAnalysisContext context, INamedTypeSymbol iDisposableTypeSymbol)
        {
            if (context.OwningSymbol.Kind != SymbolKind.Method)
            {
                return;
            }

            var method = (IMethodSymbol)context.OwningSymbol;

            // We are only interested in private explicit interface implementations within a public non-sealed type.
            if (method.ExplicitInterfaceImplementations.Length == 0 ||
                method.GetResultantVisibility() != SymbolVisibility.Private ||
                method.ContainingType.IsSealed ||
                method.ContainingType.GetResultantVisibility() != SymbolVisibility.Public)
            {
                return;
            }

            // Avoid false reports from simple explicit implementations where the deriving type is not expected to access the base implementation.
            if (ShouldExcludeOperationBlock(context.OperationBlocks))
            {
                return;
            }

            var hasPublicInterfaceImplementation = false;
            foreach (IMethodSymbol interfaceMethod in method.ExplicitInterfaceImplementations)
            {
                // If any one of the explicitly implemented interface methods has a visible alternate, then effectively, they all do.
                if (HasVisibleAlternate(method.ContainingType, interfaceMethod, iDisposableTypeSymbol))
                {
                    return;
                }

                hasPublicInterfaceImplementation = hasPublicInterfaceImplementation ||
                    interfaceMethod.ContainingType.GetResultantVisibility() == SymbolVisibility.Public;
            }

            // Even if none of the interface methods have alternates, there's only an issue if at least one of the interfaces is public.
            if (hasPublicInterfaceImplementation)
            {
                ReportDiagnostic(context, method.ContainingType.Name, method.Name);
            }
        }
        private void PerformFlowAnalysisOnOperationBlock(
            OperationBlockAnalysisContext operationBlockContext,
            DisposeAnalysisHelper disposeAnalysisHelper,
            ConcurrentDictionary <Location, bool> reportedLocations,
            IMethodSymbol containingMethod)
        {
            // We can skip interprocedural analysis for certain invocations.
            var interproceduralAnalysisPredicateOpt = new InterproceduralAnalysisPredicate(
                skipAnalysisForInvokedMethodPredicateOpt: SkipInterproceduralAnalysis,
                skipAnalysisForInvokedLambdaOrLocalFunctionPredicateOpt: null,
                skipAnalysisForInvokedContextPredicateOpt: null);

            // Compute dispose dataflow analysis result for the operation block.
            if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockContext, containingMethod,
                                                            s_disposeObjectsBeforeLosingScopeRule,
                                                            InterproceduralAnalysisKind.ContextSensitive,
                                                            trackInstanceFields: false,
                                                            out var disposeAnalysisResult, out var pointsToAnalysisResult,
                                                            interproceduralAnalysisPredicateOpt))
            {
                var notDisposedDiagnostics = ArrayBuilder <Diagnostic> .GetInstance();

                var mayBeNotDisposedDiagnostics = ArrayBuilder <Diagnostic> .GetInstance();

                try
                {
                    // Compute diagnostics for undisposed objects at exit block.
                    var exitBlock         = disposeAnalysisResult.ControlFlowGraph.GetExit();
                    var disposeDataAtExit = disposeAnalysisResult.ExitBlockOutput.Data;
                    ComputeDiagnostics(disposeDataAtExit, notDisposedDiagnostics, mayBeNotDisposedDiagnostics,
                                       disposeAnalysisResult, pointsToAnalysisResult);

                    if (disposeAnalysisResult.ControlFlowGraph.OriginalOperation.HasAnyOperationDescendant(o => o.Kind == OperationKind.None))
                    {
                        // Workaround for https://github.com/dotnet/roslyn/issues/32100
                        // Bail out in presence of OperationKind.None - not implemented IOperation.
                        return;
                    }

                    // Report diagnostics preferring *not* disposed diagnostics over may be not disposed diagnostics
                    // and avoiding duplicates.
                    foreach (var diagnostic in notDisposedDiagnostics.Concat(mayBeNotDisposedDiagnostics))
                    {
                        if (reportedLocations.TryAdd(diagnostic.Location, true))
                        {
                            operationBlockContext.ReportDiagnostic(diagnostic);
                        }
                    }
                }
                finally
                {
                    notDisposedDiagnostics.Free();
                    mayBeNotDisposedDiagnostics.Free();
                }
            }

            return;

            // Local functions.
            bool SkipInterproceduralAnalysis(IMethodSymbol invokedMethod)
            {
                // Skip interprocedural analysis if we are invoking a method and not passing any disposable object as an argument
                // and not receiving a disposable object as a return value.
                // We also check that we are not passing any object type argument which might hold disposable object
                // and also check that we are not passing delegate type argument which can
                // be a lambda or local function that has access to disposable object in current method's scope.

                if (CanBeDisposable(invokedMethod.ReturnType))
                {
                    return(false);
                }

                foreach (var p in invokedMethod.Parameters)
                {
                    if (CanBeDisposable(p.Type))
                    {
                        return(false);
                    }
                }

                return(true);

                bool CanBeDisposable(ITypeSymbol type)
                => type.SpecialType == SpecialType.System_Object ||
                type.IsDisposable(disposeAnalysisHelper.IDisposableType) ||
                type.TypeKind == TypeKind.Delegate;
            }

            void ComputeDiagnostics(
                ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeData,
                ArrayBuilder <Diagnostic> notDisposedDiagnostics,
                ArrayBuilder <Diagnostic> mayBeNotDisposedDiagnostics,
                DisposeAnalysisResult disposeAnalysisResult,
                PointsToAnalysisResult pointsToAnalysisResult)
            {
                foreach (var kvp in disposeData)
                {
                    var location     = kvp.Key;
                    var disposeValue = kvp.Value;

                    // Ignore non-disposable locations and locations without a Creation operation.
                    if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposable ||
                        location.CreationOpt == null)
                    {
                        continue;
                    }

                    // Check if the disposable creation is definitely not disposed or may be not disposed.
                    var isNotDisposed = disposeValue.Kind == DisposeAbstractValueKind.NotDisposed ||
                                        (disposeValue.DisposingOrEscapingOperations.Count > 0 &&
                                         disposeValue.DisposingOrEscapingOperations.All(d => d.IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph) && !location.CreationOpt.IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph)));
                    var isMayBeNotDisposed = !isNotDisposed &&
                                             (disposeValue.Kind == DisposeAbstractValueKind.MaybeDisposed || disposeValue.Kind == DisposeAbstractValueKind.NotDisposedOrEscaped);

                    if (isNotDisposed || isMayBeNotDisposed)
                    {
                        var syntax = location.TryGetNodeToReportDiagnostic(pointsToAnalysisResult);
                        if (syntax == null)
                        {
                            continue;
                        }

                        var rule = isNotDisposed ? s_disposeObjectsBeforeLosingScopeRule : s_useRecommendedDisposePatternRule;

                        // Ensure that we do not include multiple lines for the object creation expression in the diagnostic message.
                        var objectCreationText = syntax.ToString();
                        var indexOfNewLine     = objectCreationText.IndexOf(Environment.NewLine);
                        if (indexOfNewLine > 0)
                        {
                            objectCreationText = objectCreationText.Substring(0, indexOfNewLine);
                        }

                        var diagnostic = Diagnostic.Create(
                            rule,
                            syntax.GetLocation(),
                            additionalLocations: null,
                            properties: null,
                            objectCreationText);

                        if (isNotDisposed)
                        {
                            notDisposedDiagnostics.Add(diagnostic);
                        }
                        else
                        {
                            mayBeNotDisposedDiagnostics.Add(diagnostic);
                        }
                    }
                }
            }
        }
 protected override NonCopyableWalker CreateWalker(OperationBlockAnalysisContext context, NonCopyableTypesCache cache)
 => new CSharpNonCopyableWalker(context, cache);
Example #4
0
#pragma warning disable CA1801 // Review unused parameters
            /// <summary>
            /// Checks rule: Modify {0} so that it calls Dispose(false) and then returns.
            /// </summary>
            private static void CheckFinalizeImplementationRule(IMethodSymbol method, INamedTypeSymbol type, ImmutableArray <IOperation> operationBlocks, OperationBlockAnalysisContext context)
#pragma warning restore CA1801 // Review unused parameters
            {
                var validator = new FinalizeImplementationValidator(type);

                if (!validator.Validate(operationBlocks))
                {
                    context.ReportDiagnostic(method.CreateDiagnostic(FinalizeImplementationRule, $"{type.Name}.{method.Name}"));
                }
            }
            public void OperationBlockEndAction(OperationBlockAnalysisContext context)
            {
                // Check to see if the method just throws a NotImplementedException/NotSupportedException. If it does,
                // we shouldn't warn about parameters.
                // Note that VB method bodies with 1 action have 3 operations.
                // The first is the actual operation, the second is a label statement, and the third is a return
                // statement. The last two are implicit in these scenarios.

                // Filter out operation roots with no IOperation API support (OperationKind.None)
                var operationBlocks = context.OperationBlocks;

                if (operationBlocks.Any(operation => operation.IsOperationNoneRoot()))
                {
                    operationBlocks = operationBlocks.Where(operation => !operation.IsOperationNoneRoot()).ToImmutableArray();
                }

                if (operationBlocks.Length == 1 &&
                    operationBlocks[0] is IBlockOperation methodBlock)
                {
                    bool IsSingleStatementBody(IBlockOperation body)
                    {
                        return(body.Operations.Length == 1 ||
                               (body.Operations.Length == 3 && body.Syntax.Language == LanguageNames.VisualBasic &&
                                body.Operations[1] is ILabeledOperation labeledOp && labeledOp.IsImplicit &&
                                body.Operations[2] is IReturnOperation returnOp && returnOp.IsImplicit));
                    }

                    if (IsSingleStatementBody(methodBlock))
                    {
                        var innerOperation = methodBlock.Operations.First();

                        // Because of https://github.com/dotnet/roslyn/issues/23152, there can be an expression-statement
                        // wrapping expression-bodied throw operations. Compensate by unwrapping if necessary.
                        if (innerOperation.Kind == OperationKind.ExpressionStatement &&
                            innerOperation is IExpressionStatementOperation exprStatement)
                        {
                            innerOperation = exprStatement.Operation;
                        }

                        if (innerOperation.Kind == OperationKind.Throw &&
                            innerOperation is IThrowOperation throwOperation &&
                            throwOperation.Exception.Kind == OperationKind.ObjectCreation &&
                            throwOperation.Exception is IObjectCreationOperation createdException)
                        {
                            if (_exceptionsToSkip.Contains(createdException.Type.OriginalDefinition))
                            {
                                return;
                            }
                        }
                    }
                }

                // Do not raise warning for unused 'this' parameter of an extension method.
                if (_method.IsExtensionMethod)
                {
                    var thisParamter = _unusedParameters.Where(p => p.Ordinal == 0).FirstOrDefault();
                    _unusedParameters.Remove(thisParamter);
                }

                _finalUnusedParameters.Add(_method, _unusedParameters);
            }
 private void ProcessOperationBlock(OperationBlockAnalysisContext obj)
 {
 }
 /// <summary>
 /// Checks rule: Modify {0} so that it calls Dispose(true), then calls GC.SuppressFinalize on the current object instance ('this' or 'Me' in Visual Basic), and then returns.
 /// </summary>
 private void CheckDisposeImplementationRule(IMethodSymbol method, INamedTypeSymbol type, ImmutableArray<IOperation> operationBlocks, OperationBlockAnalysisContext context)
 {
     var validator = new DisposeImplementationValidator(_suppressFinalizeMethod, type);
     if (!validator.Validate(operationBlocks))
     {
         context.ReportDiagnostic(method.CreateDiagnostic(DisposeImplementationRule, $"{type.Name}.{method.Name}"));
     }
 }
 private static void ReportDiagnostic(OperationBlockAnalysisContext context, params object[] messageArgs)
 {
     Diagnostic diagnostic = context.OwningSymbol.CreateDiagnostic(Rule, messageArgs);
     context.ReportDiagnostic(diagnostic);
 }
 private void Report(OperationBlockAnalysisContext context, ILocalSymbol local, DiagnosticDescriptor descriptor)
 {
     context.ReportDiagnostic(Diagnostic.Create(descriptor, local.Locations.FirstOrDefault()));
 }
                private void AnalyzeUnusedValueAssignments(
                    OperationBlockAnalysisContext context,
                    bool isComputingUnusedParams,
                    PooledHashSet <SymbolUsageResult> symbolUsageResultsBuilder,
                    out bool hasBlockWithAllUsedSymbolWrites,
                    out bool hasOperationNoneDescendant)
                {
                    hasBlockWithAllUsedSymbolWrites = false;
                    hasOperationNoneDescendant      = false;

                    foreach (var operationBlock in context.OperationBlocks)
                    {
                        if (!ShouldAnalyze(operationBlock, context.OwningSymbol, ref hasOperationNoneDescendant))
                        {
                            continue;
                        }

                        // First perform the fast, aggressive, imprecise operation-tree based analysis.
                        // This analysis might flag some "used" symbol writes as "unused", but will not miss reporting any truly unused symbol writes.
                        // This initial pass helps us reduce the number of methods for which we perform the slower second pass.
                        // We perform the first fast pass only if there are no delegate creations/lambda methods.
                        // This is due to the fact that tracking which local/parameter points to which delegate creation target
                        // at any given program point needs needs flow analysis (second pass).
                        if (!_hasDelegateCreationOrAnonymousFunction)
                        {
                            var resultFromOperationBlockAnalysis = SymbolUsageAnalysis.Run(operationBlock, context.OwningSymbol, context.CancellationToken);
                            if (!resultFromOperationBlockAnalysis.HasUnreadSymbolWrites())
                            {
                                // Assert that even slow pass (dataflow analysis) would have yielded no unused symbol writes.
                                Debug.Assert(!SymbolUsageAnalysis.Run(context.GetControlFlowGraph(operationBlock), context.OwningSymbol, context.CancellationToken)
                                             .HasUnreadSymbolWrites());

                                hasBlockWithAllUsedSymbolWrites = true;
                                continue;
                            }
                        }

                        // Now perform the slower, precise, CFG based dataflow analysis to identify the actual unused symbol writes.
                        var controlFlowGraph  = context.GetControlFlowGraph(operationBlock);
                        var symbolUsageResult = SymbolUsageAnalysis.Run(controlFlowGraph, context.OwningSymbol, context.CancellationToken);
                        symbolUsageResultsBuilder.Add(symbolUsageResult);

                        foreach (var(symbol, unreadWriteOperation) in symbolUsageResult.GetUnreadSymbolWrites())
                        {
                            if (unreadWriteOperation == null)
                            {
                                // Null operation is used for initial write for the parameter from method declaration.
                                // So, the initial value of the parameter is never read in this operation block.
                                // However, we do not report this as an unused parameter here as a different operation block
                                // might be reading the initial parameter value.
                                // For example, a constructor with both a constructor initializer and body will have two different operation blocks
                                // and a parameter must be unused across both these blocks to be marked unused.

                                // However, we do report unused parameters for local function here.
                                // Local function parameters are completely scoped to this operation block, and should be reported per-operation block.
                                var unusedParameter = (IParameterSymbol)symbol;
                                if (isComputingUnusedParams &&
                                    unusedParameter.ContainingSymbol.IsLocalFunction())
                                {
                                    var hasReference = symbolUsageResult.SymbolsRead.Contains(unusedParameter);

                                    bool shouldReport;
                                    switch (unusedParameter.RefKind)
                                    {
                                    case RefKind.Out:
                                        // Do not report out parameters of local functions.
                                        // If they are unused in the caller, we will flag the
                                        // out argument at the local function callsite.
                                        shouldReport = false;
                                        break;

                                    case RefKind.Ref:
                                        // Report ref parameters only if they have no read/write references.
                                        // Note that we always have one write for the parameter input value from the caller.
                                        shouldReport = !hasReference && symbolUsageResult.GetSymbolWriteCount(unusedParameter) == 1;
                                        break;

                                    default:
                                        shouldReport = true;
                                        break;
                                    }

                                    if (shouldReport)
                                    {
                                        _symbolStartAnalyzer.ReportUnusedParameterDiagnostic(unusedParameter, hasReference, context.ReportDiagnostic, context.Options, context.CancellationToken);
                                    }
                                }

                                continue;
                            }

                            if (ShouldReportUnusedValueDiagnostic(symbol, unreadWriteOperation, symbolUsageResult, out var properties))
                            {
                                var diagnostic = DiagnosticHelper.Create(s_valueAssignedIsUnusedRule,
                                                                         _symbolStartAnalyzer._compilationAnalyzer.GetDefinitionLocationToFade(unreadWriteOperation),
                                                                         _options.UnusedValueAssignmentSeverity,
                                                                         additionalLocations: null,
                                                                         properties,
                                                                         symbol.Name);
                                context.ReportDiagnostic(diagnostic);
                            }
                        }
                    }

                    return;

                    // Local functions.
                    bool ShouldReportUnusedValueDiagnostic(
                        ISymbol symbol,
                        IOperation unreadWriteOperation,
                        SymbolUsageResult resultFromFlowAnalysis,
                        out ImmutableDictionary <string, string> properties)
                    {
                        Debug.Assert(!(symbol is ILocalSymbol local) || !local.IsRef);

                        properties = null;

                        // Bail out in following cases:
                        //   1. End user has configured the diagnostic to be suppressed.
                        //   2. Symbol has error type, hence the diagnostic could be noised
                        //   3. Static local symbols. Assignment to static locals
                        //      is not unnecessary as the assigned value can be used on the next invocation.
                        //   4. Ignore special discard symbol names (see https://github.com/dotnet/roslyn/issues/32923).
                        if (_options.UnusedValueAssignmentSeverity == ReportDiagnostic.Suppress ||
                            symbol.GetSymbolType().IsErrorType() ||
                            (symbol.IsStatic && symbol.Kind == SymbolKind.Local) ||
                            IsSymbolWithSpecialDiscardName(symbol))
                        {
                            return(false);
                        }

                        // Flag to indicate if the symbol has no reads.
                        var isUnusedLocalAssignment = symbol is ILocalSymbol localSymbol &&
                                                      !resultFromFlowAnalysis.SymbolsRead.Contains(localSymbol);

                        var isRemovableAssignment = IsRemovableAssignmentWithoutSideEffects(unreadWriteOperation);

                        if (isUnusedLocalAssignment &&
                            !isRemovableAssignment &&
                            _options.UnusedValueAssignmentPreference == UnusedValuePreference.UnusedLocalVariable)
                        {
                            // Meets current user preference of using unused local symbols for storing computation result.
                            // Skip reporting diagnostic.
                            return(false);
                        }

                        properties = s_propertiesMap[(_options.UnusedValueAssignmentPreference, isUnusedLocalAssignment, isRemovableAssignment)];
                        return(true);
                    }
 public ReturnValueAnalyzer(OperationBlockAnalysisContext context)
 {
     this.context = context;
 }
            public ReturnStatementCollector([NotNull] SequenceTypeInfo sequenceTypeInfo, OperationBlockAnalysisContext context)
            {
                Guard.NotNull(sequenceTypeInfo, nameof(sequenceTypeInfo));

                this.sequenceTypeInfo = sequenceTypeInfo;
                this.context          = context;
            }
Example #13
0
 /// <summary>
 /// Creates a new instance of the <see cref="ContextualLoggableGeneratorDiagnosticReceiver{T}"/> class that accepts only <see cref="OperationBlockAnalysisContext"/>.
 /// </summary>
 /// <param name="generator">Target <see cref="LoggableSourceGenerator"/>.</param>
 /// <param name="context">Context of this <see cref="ContextualDiagnosticReceiver{T}"/>.</param>
 public static ContextualLoggableGeneratorDiagnosticReceiver <OperationBlockAnalysisContext> OperationBlock(LoggableSourceGenerator generator, OperationBlockAnalysisContext context)
 {
     return(new ContextualLoggableGeneratorDiagnosticReceiver <OperationBlockAnalysisContext>(generator, (context, diag) => context.ReportDiagnostic(diag), context));
 }
        private void AnalyzeOperationBlock(Analyzer analyzer, OperationBlockAnalysisContext context)
        {
            if (context.OperationBlocks.Length != 1)
            {
                return;
            }

            var owningSymbol = context.OwningSymbol;
            var operation    = context.OperationBlocks[0];

            var(accessesBase, hashedMembers, statements) = analyzer.GetHashedMembers(
                owningSymbol,
                operation
                );
            var elementCount =
                (accessesBase ? 1 : 0)
                + (hashedMembers.IsDefaultOrEmpty ? 0 : hashedMembers.Length);

            // No members to call into HashCode.Combine with.  Don't offer anything here.
            if (elementCount == 0)
            {
                return;
            }

            // Just one member to call into HashCode.Combine. Only offer this if we have multiple statements that we can
            // reduce to a single statement.  It's not worth it to offer to replace:
            //
            //      `return x.GetHashCode();` with `return HashCode.Combine(x);`
            //
            // But it is work it to offer to replace:
            //
            //      `return (a, b).GetHashCode();` with `return HashCode.Combine(a, b);`
            if (elementCount == 1 && statements.Length < 2)
            {
                return;
            }

            // We've got multiple members to hash, or multiple statements that can be reduced at this point.
            Debug.Assert(elementCount >= 2 || statements.Length >= 2);

            var syntaxTree        = operation.Syntax.SyntaxTree;
            var cancellationToken = context.CancellationToken;

            var option = context.Options.GetOption(
                CodeStyleOptions2.PreferSystemHashCode,
                operation.Language,
                syntaxTree,
                cancellationToken
                );

            if (option?.Value != true)
            {
                return;
            }

            var operationLocation   = operation.Syntax.GetLocation();
            var declarationLocation = context.OwningSymbol.DeclaringSyntaxReferences[0]
                                      .GetSyntax(cancellationToken)
                                      .GetLocation();

            context.ReportDiagnostic(
                DiagnosticHelper.Create(
                    Descriptor,
                    owningSymbol.Locations[0],
                    option.Notification.Severity,
                    new[] { operationLocation, declarationLocation },
                    ImmutableDictionary <string, string> .Empty
                    )
                );
        }
Example #15
0
                private void AnalyzeUnusedValueAssignments(
                    OperationBlockAnalysisContext context,
                    bool isComputingUnusedParams,
                    PooledHashSet <SymbolUsageResult> symbolUsageResultsBuilder,
                    out bool hasBlockWithAllUsedSymbolWrites)
                {
                    hasBlockWithAllUsedSymbolWrites = false;

                    foreach (var operationBlock in context.OperationBlocks)
                    {
                        if (!ShouldAnalyze(operationBlock, context.OwningSymbol))
                        {
                            continue;
                        }

                        // First perform the fast, aggressive, imprecise operation-tree based analysis.
                        // This analysis might flag some "used" symbol writes as "unused", but will not miss reporting any truly unused symbol writes.
                        // This initial pass helps us reduce the number of methods for which we perform the slower second pass.
                        // We perform the first fast pass only if there are no delegate creations/lambda methods.
                        // This is due to the fact that tracking which local/parameter points to which delegate creation target
                        // at any given program point needs needs flow analysis (second pass).
                        if (!_hasDelegateCreationOrAnonymousFunction)
                        {
                            var resultFromOperationBlockAnalysis = SymbolUsageAnalysis.Run(operationBlock, context.OwningSymbol, context.CancellationToken);
                            if (!resultFromOperationBlockAnalysis.HasUnreadSymbolWrites())
                            {
                                // Assert that even slow pass (dataflow analysis) would have yielded no unused symbol writes.
                                Debug.Assert(!SymbolUsageAnalysis.Run(context.GetControlFlowGraph(operationBlock), context.OwningSymbol, context.CancellationToken)
                                             .HasUnreadSymbolWrites());

                                hasBlockWithAllUsedSymbolWrites = true;
                                continue;
                            }
                        }

                        // Now perform the slower, precise, CFG based dataflow analysis to identify the actual unused symbol writes.
                        var controlFlowGraph  = context.GetControlFlowGraph(operationBlock);
                        var symbolUsageResult = SymbolUsageAnalysis.Run(controlFlowGraph, context.OwningSymbol, context.CancellationToken);
                        symbolUsageResultsBuilder.Add(symbolUsageResult);

                        foreach (var(symbol, unreadWriteOperation) in symbolUsageResult.GetUnreadSymbolWrites())
                        {
                            if (unreadWriteOperation == null)
                            {
                                // Null operation is used for initial write for the parameter from method declaration.
                                // So, the initial value of the parameter is never read in this operation block.
                                // However, we do not report this as an unused parameter here as a different operation block
                                // might be reading the initial parameter value.
                                // For example, a constructor with both a constructor initializer and body will have two different operation blocks
                                // and a parameter must be unused across both these blocks to be marked unused.

                                // However, we do report unused parameters for local function here.
                                // Local function parameters are completely scoped to this operation block, and should be reported per-operation block.
                                var unusedParameter = (IParameterSymbol)symbol;
                                if (isComputingUnusedParams &&
                                    unusedParameter.ContainingSymbol.IsLocalFunction())
                                {
                                    var hasReference = symbolUsageResult.SymbolsRead.Contains(unusedParameter);
                                    _symbolStartAnalyzer.ReportUnusedParameterDiagnostic(unusedParameter, hasReference, context.ReportDiagnostic, context.Options, context.CancellationToken);
                                }

                                continue;
                            }

                            if (ShouldReportUnusedValueDiagnostic(symbol, unreadWriteOperation, symbolUsageResult, out var properties))
                            {
                                var diagnostic = DiagnosticHelper.Create(s_valueAssignedIsUnusedRule,
                                                                         _symbolStartAnalyzer._compilationAnalyzer.GetDefinitionLocationToFade(unreadWriteOperation),
                                                                         _options.UnusedValueAssignmentSeverity,
                                                                         additionalLocations: null,
                                                                         properties,
                                                                         symbol.Name);
                                context.ReportDiagnostic(diagnostic);
                            }
                        }
                    }

                    return;

                    // Local functions.
                    bool ShouldReportUnusedValueDiagnostic(
                        ISymbol symbol,
                        IOperation unreadWriteOperation,
                        SymbolUsageResult resultFromFlowAnalysis,
                        out ImmutableDictionary <string, string> properties)
                    {
                        properties = null;
                        if (_options.UnusedValueAssignmentSeverity == ReportDiagnostic.Suppress ||
                            symbol.GetSymbolType().IsErrorType())
                        {
                            return(false);
                        }

                        // Flag to indicate if the symbol has no reads.
                        var isUnusedLocalAssignment = symbol is ILocalSymbol localSymbol &&
                                                      !resultFromFlowAnalysis.SymbolsRead.Contains(localSymbol);

                        var isRemovableAssignment = IsRemovableAssignmentWithoutSideEffects(unreadWriteOperation);

                        if (isUnusedLocalAssignment &&
                            !isRemovableAssignment &&
                            _options.UnusedValueAssignmentPreference == UnusedValuePreference.UnusedLocalVariable)
                        {
                            // Meets current user preference of using unused local symbols for storing computation result.
                            // Skip reporting diagnostic.
                            return(false);
                        }

                        properties = s_propertiesMap[(_options.UnusedValueAssignmentPreference, isUnusedLocalAssignment, isRemovableAssignment)];
                        return(true);
                    }
Example #16
0
 public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ISymbol symbol, params string[] messageArgs)
 {
     ReportDiagnostic(context, descriptor, ImmutableDictionary <string, string> .Empty, symbol, messageArgs);
 }
            /// <summary>
            /// Checks rule: Modify {0} so that it calls Dispose(true), then calls GC.SuppressFinalize on the current object instance ('this' or 'Me' in Visual Basic), and then returns.
            /// </summary>
            private void CheckDisposeImplementationRule(IMethodSymbol method, INamedTypeSymbol type, ImmutableArray <IOperation> operationBlocks, OperationBlockAnalysisContext context)
            {
                var validator = new DisposeImplementationValidator(_suppressFinalizeMethod, type);

                if (!validator.Validate(operationBlocks))
                {
                    context.ReportDiagnostic(method.CreateDiagnostic(DisposeImplementationRule, $"{type.Name}.{method.Name}"));
                }
            }
Example #18
0
 void Report(OperationBlockAnalysisContext context, ILocalSymbol local, ITypeSymbol moreSpecificType, DiagnosticDescriptor descriptor)
 {
     context.ReportDiagnostic(Diagnostic.Create(descriptor, local.Locations.FirstOrDefault(), local, moreSpecificType));
 }
 public void OperationBlockEndAction(OperationBlockAnalysisContext context)
 {
     // Check for absence of GC.SuppressFinalize
     if (!_suppressFinalizeCalled && _expectedUsage == SuppressFinalizeUsage.MustCall)
     {
         var descriptor = _containingMethodSymbol.ContainingType.HasFinalizer() ? NotCalledWithFinalizerRule : NotCalledRule;
         context.ReportDiagnostic(_containingMethodSymbol.CreateDiagnostic(
             descriptor,
             _containingMethodSymbol.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat),
             _gcSuppressFinalizeMethodSymbol.ToDisplayString(SymbolDisplayFormats.ShortSymbolDisplayFormat)));
     }
 }
Example #20
0
 public TrimDataFlowAnalysis(OperationBlockAnalysisContext context, ControlFlowGraph cfg)
 {
     ControlFlowGraph = new ControlFlowGraphProxy(cfg);
     Lattice          = new (new ValueSetLattice <SingleValue> ());
     Context          = context;
 }
            // CA1801: Remove unused parameters.
#pragma warning disable CA1801
            /// <summary>
            /// Checks rule: Modify {0} so that it calls Dispose(false) and then returns.
            /// </summary>
            private static void CheckFinalizeImplementationRule(IMethodSymbol method, INamedTypeSymbol type, ImmutableArray<IOperation> operationBlocks, OperationBlockAnalysisContext context)
#pragma warning restore CA1801
            {
                // TODO: Implement check of Finalize
            }
Example #22
0
 public LocalDataFlowVisitor(LocalStateLattice <TValue, TValueLattice> lattice, OperationBlockAnalysisContext context, ImmutableDictionary <CaptureId, FlowCaptureKind> lValueFlowCaptures) =>
 (LocalStateLattice, Context, this.lValueFlowCaptures) = (lattice, context, lValueFlowCaptures);
 /// <summary>
 /// Creates a new instance of the <see cref="ContextualDiagnosticReceiver{T}"/> class that accepts only <see cref="OperationBlockAnalysisContext"/>.
 /// </summary>
 /// <param name="context">Context of this <see cref="ContextualDiagnosticReceiver{T}"/>.</param>
 public static ContextualDiagnosticReceiver <OperationBlockAnalysisContext> OperationBlock(OperationBlockAnalysisContext context)
 {
     return(new ContextualDiagnosticReceiver <OperationBlockAnalysisContext>((context, diag) => context.ReportDiagnostic(diag), context));
 }
Example #24
0
 public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, SyntaxToken token, params string[] messageArgs)
 {
     ReportDiagnostic(context, descriptor, ImmutableDictionary <string, string?> .Empty, token.GetLocation(), messageArgs);
 }
            // CA1801: Remove unused parameters.
#pragma warning disable CA1801
            /// <summary>
            /// Checks rule: Modify {0} so that it calls Dispose(false) and then returns.
            /// </summary>
            private static void CheckFinalizeImplementationRule(IMethodSymbol method, INamedTypeSymbol type, ImmutableArray <IOperation> operationBlocks, OperationBlockAnalysisContext context)
#pragma warning restore CA1801
            {
                // TODO: Implement check of Finalize
            }
Example #26
0
 public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary <string, string?>?properties, SyntaxNode syntax, params string[] messageArgs)
 {
     ReportDiagnostic(context, descriptor, properties, syntax.GetLocation(), messageArgs);
 }
        private static void ReportDiagnostic(OperationBlockAnalysisContext context, params object[] messageArgs)
        {
            Diagnostic diagnostic = context.OwningSymbol.CreateDiagnostic(Rule, messageArgs);

            context.ReportDiagnostic(diagnostic);
        }
Example #28
0
 public static void ReportDiagnostic(this OperationBlockAnalysisContext context, DiagnosticDescriptor descriptor, ImmutableDictionary <string, string?>?properties, Location location, params string[] messageArgs)
 {
     context.ReportDiagnostic(CreateDiagnostic(descriptor, location, properties, messageArgs));
 }
 public CSharpNonCopyableWalker(OperationBlockAnalysisContext context, NonCopyableTypesCache cache)
     : base(context, cache)
 {
 }
 public ReturnValueAnalyzer(OperationBlockAnalysisContext context,
                            [NotNull] IDictionary <ILocalSymbol, EvaluationResult> variableEvaluationCache)
 {
     this.context = context;
     this.variableEvaluationCache = variableEvaluationCache;
 }
 public void OperationBlockEndAction(OperationBlockAnalysisContext context)
 {
     // Report diagnostics for unused parameters.
     foreach (var parameter in _unusedParameters)
     {
         var diagnostic = Diagnostic.Create(Rule, parameter.Locations[0], parameter.Name, parameter.ContainingSymbol.Name);
         context.ReportDiagnostic(diagnostic);
     }
 }
 private void AnalyzeReturnStatement([NotNull] IReturnStatement returnStatement, OperationBlockAnalysisContext context,
                                     [NotNull] IDictionary <ILocalSymbol, EvaluationResult> variableEvaluationCache)
 {
     if (!ReturnsConstant(returnStatement) && !IsYieldBreak(returnStatement))
     {
         var analyzer = new ReturnValueAnalyzer(context, variableEvaluationCache);
         analyzer.Analyze(returnStatement);
     }
 }
 private void Report(OperationBlockAnalysisContext context, ILocalSymbol local, DiagnosticDescriptor descriptor)
 {
     context.ReportDiagnostic(Diagnostic.Create(descriptor, local.Locations.FirstOrDefault()));
 }
            public void AnalyzeOperationBlock(OperationBlockAnalysisContext context)
            {
                foreach (KeyValuePair<ISymbol, XmlDocumentEnvironment> p in _xmlDocumentEnvironments)
                {
                    XmlDocumentEnvironment env = p.Value;
                    if (!(env.IsXmlResolverSet | env.IsSecureResolver))
                    {
                        Diagnostic diag = Diagnostic.Create(
                            RuleDoNotUseInsecureDtdProcessing,
                            env.XmlDocumentDefinition.GetLocation(),
                            SecurityDiagnosticHelpers.GetLocalizableResourceString(
                                nameof(DesktopAnalyzersResources.XmlDocumentWithNoSecureResolverMessage)
                            )
                        );
                        context.ReportDiagnostic(diag);
                    }
                }


                foreach (KeyValuePair<ISymbol, XmlTextReaderEnvironment> p in _xmlTextReaderEnvironments)
                {
                    XmlTextReaderEnvironment env = p.Value;
                    if (!(env.IsXmlResolverSet | env.IsSecureResolver) ||
                        !(env.IsDtdProcessingSet | env.IsDtdProcessingDisabled))
                    {
                        Diagnostic diag = Diagnostic.Create(
                            RuleDoNotUseInsecureDtdProcessing,
                            env.XmlTextReaderDefinition.GetLocation(),
                            SecurityDiagnosticHelpers.GetLocalizableResourceString(
                                nameof(DesktopAnalyzersResources.XmlTextReaderConstructedWithNoSecureResolutionMessage)
                            )
                        );

                        context.ReportDiagnostic(diag);
                    }
                }
            }
            /// <summary>
            /// Checks rule: Modify {0} so that it calls Dispose(false) and then returns.
            /// </summary>
            private static void CheckFinalizeImplementationRule(IMethodSymbol method, INamedTypeSymbol type, ImmutableArray <IOperation> operationBlocks, OperationBlockAnalysisContext context)
            {
                // Bail out if any base type also provides a finalizer - we will fire CheckFinalizeOverrideRule for that case.
                if (GetFirstBaseTypeWithFinalizerOrDefault(type) != null)
                {
                    return;
                }

                var validator = new FinalizeImplementationValidator(type);

                if (!validator.Validate(operationBlocks))
                {
                    context.ReportDiagnostic(method.CreateDiagnostic(FinalizeImplementationRule, $"{type.Name}.{method.Name}"));
                }
            }
        public static bool IsMethodNotImplementedOrSupported(this OperationBlockAnalysisContext context)
        {
            // Note that VB method bodies with 1 action have 3 operations.
            // The first is the actual operation, the second is a label statement, and the third is a return
            // statement. The last two are implicit in these scenarios.

            var operationBlocks = context.OperationBlocks.WhereAsArray(operation => !operation.IsOperationNoneRoot());

            IBlockOperation methodBlock = null;

            if (operationBlocks.Length == 1 && operationBlocks[0].Kind == OperationKind.Block)
            {
                methodBlock = (IBlockOperation)operationBlocks[0];
            }
            else if (operationBlocks.Length > 1)
            {
                foreach (var block in operationBlocks)
                {
                    if (block.Kind == OperationKind.Block)
                    {
                        methodBlock = (IBlockOperation)block;
                        break;
                    }
                }
            }

            if (methodBlock != null)
            {
                bool IsSingleStatementBody(IBlockOperation body)
                {
                    return(body.Operations.Length == 1 ||
                           (body.Operations.Length == 3 && body.Syntax.Language == LanguageNames.VisualBasic &&
                            body.Operations[1] is ILabeledOperation labeledOp && labeledOp.IsImplicit &&
                            body.Operations[2] is IReturnOperation returnOp && returnOp.IsImplicit));
                }

                if (IsSingleStatementBody(methodBlock))
                {
                    var innerOperation = methodBlock.Operations.First();

                    // Because of https://github.com/dotnet/roslyn/issues/23152, there can be an expression-statement
                    // wrapping expression-bodied throw operations. Compensate by unwrapping if necessary.
                    if (innerOperation.Kind == OperationKind.ExpressionStatement &&
                        innerOperation is IExpressionStatementOperation exprStatement)
                    {
                        innerOperation = exprStatement.Operation;
                    }

                    if (innerOperation.Kind == OperationKind.Throw &&
                        innerOperation is IThrowOperation throwOperation &&
                        throwOperation.Exception.Kind == OperationKind.ObjectCreation &&
                        throwOperation.Exception is IObjectCreationOperation createdException)
                    {
                        if (Equals(WellKnownTypes.NotImplementedException(context.Compilation), createdException.Type.OriginalDefinition) ||
                            Equals(WellKnownTypes.NotSupportedException(context.Compilation), createdException.Type.OriginalDefinition))
                        {
                            return(true);
                        }
                    }
                }
            }

            return(false);
        }
            private void AnalyzeOperationBlock(OperationBlockAnalysisContext context)
            {
                var method = context.OwningSymbol as IMethodSymbol;
                if (method == null)
                {
                    return;
                }

                bool isFinalizerMethod = method.IsFinalizer();
                bool isDisposeMethod = method.Name == DisposeMethodName;
                if (isFinalizerMethod || isDisposeMethod)
                {
                    INamedTypeSymbol type = method.ContainingType;
                    if (type != null && type.TypeKind == TypeKind.Class &&
                        !type.IsSealed && type.DeclaredAccessibility != Accessibility.Private)
                    {
                        if (ImplementsDisposableDirectly(type))
                        {
                            IMethodSymbol disposeMethod = FindDisposeMethod(type);
                            if (disposeMethod != null)
                            {
                                if (method == disposeMethod)
                                {
                                    CheckDisposeImplementationRule(method, type, context.OperationBlocks, context);
                                }
                                else if (isFinalizerMethod)
                                {
                                    // Check implementation of finalizer only if the class explicitly implements IDisposable
                                    // If class implements interface inherited from IDisposable and IDisposable is implemented in base class
                                    // then implementation of finalizer is ignored
                                    CheckFinalizeImplementationRule(method, type, context.OperationBlocks, context);
                                }
                            }
                        }
                    }
                }
            }
 public LocalDataFlowVisitor(LocalStateLattice <TValue, TValueLattice> lattice, OperationBlockAnalysisContext context) =>
 (LocalStateLattice, Context) = (lattice, context);