public bool TryGetOrComputeResult( OperationBlockAnalysisContext context, IMethodSymbol containingMethod, DiagnosticDescriptor rule, InterproceduralAnalysisKind interproceduralAnalysisKind, bool trackInstanceFields, out DisposeAnalysisResult disposeAnalysisResult, out PointsToAnalysisResult pointsToAnalysisResult, InterproceduralAnalysisPredicate interproceduralAnalysisPredicateOpt = null) { // Compute the dispose analysis result - skip Attribute blocks (OperationKind.None) foreach (var operationBlock in context.OperationBlocks.Where(o => o.Kind != OperationKind.None)) { var cfg = context.GetControlFlowGraph(operationBlock); if (cfg != null) { var wellKnownTypeProvider = Analyzer.Utilities.WellKnownTypeProvider.GetOrCreate(context.Compilation); disposeAnalysisResult = FlowAnalysis.DataFlow.DisposeAnalysis.DisposeAnalysis.TryGetOrComputeResult(cfg, containingMethod, wellKnownTypeProvider, context.Options, rule, _disposeOwnershipTransferLikelyTypes, trackInstanceFields, exceptionPathsAnalysis: false, context.CancellationToken, out pointsToAnalysisResult, interproceduralAnalysisKind, interproceduralAnalysisPredicateOpt: interproceduralAnalysisPredicateOpt, defaultDisposeOwnershipTransferAtConstructor: true, defaultDisposeOwnershipTransferAtMethodCall: true); if (disposeAnalysisResult != null) { return(true); } } } disposeAnalysisResult = null; pointsToAnalysisResult = null; return(false); }
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); }
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); }