private static void OnSymbolStart(SymbolStartAnalysisContext symbolStartContext)
        {
            if (!DisposeAnalysisHelper.TryGetOrCreate(symbolStartContext.Compilation, out DisposeAnalysisHelper? disposeAnalysisHelper) ||
                !ShouldAnalyze(symbolStartContext, disposeAnalysisHelper))
            {
                return;
            }

            var fieldDisposeValueMap = PooledConcurrentDictionary <IFieldSymbol, /*disposed*/ bool> .GetInstance();

            var hasErrors = false;

            symbolStartContext.RegisterOperationAction(_ => hasErrors = true, OperationKind.Invalid);

            // Disposable fields with initializer at declaration must be disposed.
            symbolStartContext.RegisterOperationAction(OnFieldInitializer, OperationKind.FieldInitializer);

            // Instance fields initialized in constructor/method body with a locally created disposable object must be disposed.
            symbolStartContext.RegisterOperationBlockStartAction(OnOperationBlockStart);

            // Report diagnostics at symbol end.
            symbolStartContext.RegisterSymbolEndAction(OnSymbolEnd);

            return;

            // Local functions
            void AddOrUpdateFieldDisposedValue(IFieldSymbol field, bool disposed)
            {
                Debug.Assert(!field.IsStatic);
                Debug.Assert(disposeAnalysisHelper !.IsDisposable(field.Type));

                fieldDisposeValueMap.AddOrUpdate(field,
                                                 addValue: disposed,
                                                 updateValueFactory: (f, currentValue) => currentValue || disposed);
            }
Пример #2
0
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(context =>
            {
                if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemString, out INamedTypeSymbol? stringType) ||
                    !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemChar, out INamedTypeSymbol? charType) ||
                    !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison, out INamedTypeSymbol? stringComparisonType))
                {
                    return;
                }

                // First get all the string.IndexOf methods that we are interested in tagging
                var stringIndexOfMethods = stringType
                                           .GetMembers("IndexOf")
                                           .OfType <IMethodSymbol>()
                                           .WhereAsArray(s =>
                                                         s.Parameters.Length <= 2);

                var stringArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(stringType));
                var charArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType));
                var stringAndComparisonTypeArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(stringType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));
                var charAndComparisonTypeArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));

                // Check that the contains methods that take 2 parameters exist
                // string.Contains(char) is also .NETStandard2.1+
                var stringContainsMethods = stringType
                                            .GetMembers("Contains")
                                            .OfType <IMethodSymbol>()
                                            .WhereAsArray(s =>
                                                          s.Parameters.Length <= 2);
                var stringAndComparisonTypeArgumentContainsMethod = stringContainsMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(stringType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));
                var charAndComparisonTypeArgumentContainsMethod = stringContainsMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));
                var charArgumentContainsMethod = stringContainsMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType));
                if (stringAndComparisonTypeArgumentContainsMethod == null ||
                    charAndComparisonTypeArgumentContainsMethod == null ||
                    charArgumentContainsMethod == null)
                {
                    return;
                }

                // Roslyn doesn't yet support "FindAllReferences" at a file/block level. So instead, find references to local int variables in this block.
                context.RegisterOperationBlockStartAction(OnOperationBlockStart);
                return;

                void OnOperationBlockStart(OperationBlockStartAnalysisContext context)
                {
                    if (context.OwningSymbol is not IMethodSymbol method)
                    {
                        return;
                    }

                    // Algorithm:
                    // We aim to change string.IndexOf -> string.Contains
                    // 1. We register 1 callback for invocations of IndexOf.
                    //      1a. Check if invocation.Parent is a binary operation we care about (string.IndexOf >= 0 OR string.IndexOf == -1). If so, report a diagnostic and return from the callback.
                    //      1b. Otherwise, check if invocation.Parent is a variable declarator. If so, add the invocation as a potential violation to track into variableNameToOperationsMap.
                    // 2. We register another callback for local references
                    //      2a. If the local reference is not a type int, bail out.
                    //      2b. If the local reference operation's parent is not a binary operation, add it to "localsToBailOut".
                    // 3. In an operation block end, we check if entries in "variableNameToOperationsMap" exist in "localToBailOut". If an entry is NOT present, we report a diagnostic at that invocation.
                    PooledConcurrentSet <ILocalSymbol> localsToBailOut = PooledConcurrentSet <ILocalSymbol> .GetInstance();
                    PooledConcurrentDictionary <ILocalSymbol, IInvocationOperation> variableNameToOperationsMap = PooledConcurrentDictionary <ILocalSymbol, IInvocationOperation> .GetInstance();

                    context.RegisterOperationAction(PopulateLocalReferencesSet, OperationKind.LocalReference);

                    context.RegisterOperationAction(AnalyzeInvocationOperation, OperationKind.Invocation);

                    context.RegisterOperationBlockEndAction(OnOperationBlockEnd);

                    return;

                    // Local Functions
                    void PopulateLocalReferencesSet(OperationAnalysisContext context)
                    {
                        ILocalReferenceOperation localReference = (ILocalReferenceOperation)context.Operation;
                        if (localReference.Local.Type.SpecialType != SpecialType.System_Int32)
                        {
                            return;
                        }

                        var parent = localReference.Parent;
                        if (parent is IBinaryOperation binaryOperation)
                        {
                            var otherOperand = binaryOperation.LeftOperand is ILocalReferenceOperation ? binaryOperation.RightOperand : binaryOperation.LeftOperand;
                            if (CheckOperatorKindAndOperand(binaryOperation, otherOperand))
                            {
                                // Do nothing. This is a valid case to the tagged in the analyzer
                                return;
                            }
                        }
                        localsToBailOut.Add(localReference.Local);
                    }

                    void AnalyzeInvocationOperation(OperationAnalysisContext context)
                    {
                        var invocationOperation = (IInvocationOperation)context.Operation;
                        if (!IsDesiredTargetMethod(invocationOperation.TargetMethod))
                        {
                            return;
                        }

                        var parent = invocationOperation.Parent;
                        if (parent is IBinaryOperation binaryOperation)
                        {
                            var otherOperand = binaryOperation.LeftOperand is IInvocationOperation ? binaryOperation.RightOperand : binaryOperation.LeftOperand;
                            if (CheckOperatorKindAndOperand(binaryOperation, otherOperand))
                            {
                                context.ReportDiagnostic(binaryOperation.CreateDiagnostic(Rule));
                            }
                        }
                        else if (parent is IVariableInitializerOperation variableInitializer)
                        {
                            if (variableInitializer.Parent is IVariableDeclaratorOperation variableDeclaratorOperation)
                            {
                                variableNameToOperationsMap.TryAdd(variableDeclaratorOperation.Symbol, invocationOperation);
                            }
                            else if (variableInitializer.Parent is IVariableDeclarationOperation variableDeclarationOperation && variableDeclarationOperation.Declarators.Length == 1)
                            {
                                variableNameToOperationsMap.TryAdd(variableDeclarationOperation.Declarators[0].Symbol, invocationOperation);
                            }
                        }
                    }

                    static bool CheckOperatorKindAndOperand(IBinaryOperation binaryOperation, IOperation otherOperand)
                    {
                        var operatorKind = binaryOperation.OperatorKind;
                        if (otherOperand.ConstantValue.HasValue && otherOperand.ConstantValue.Value is int intValue)
                        {
                            if ((operatorKind == BinaryOperatorKind.Equals && intValue < 0) ||
                                (operatorKind == BinaryOperatorKind.GreaterThanOrEqual && intValue == 0))
                            {
                                // This is the only case we are targeting in this analyzer
                                return(true);
                            }
                        }
                        return(false);
                    }

                    void OnOperationBlockEnd(OperationBlockAnalysisContext context)
                    {
                        foreach (var variableNameAndLocation in variableNameToOperationsMap)
                        {
                            ILocalSymbol variable = variableNameAndLocation.Key;
                            if (!localsToBailOut.Contains(variable))
                            {
                                context.ReportDiagnostic(variableNameAndLocation.Value.CreateDiagnostic(Rule));
                            }
                        }
                        variableNameToOperationsMap.Free(context.CancellationToken);
                        localsToBailOut.Free(context.CancellationToken);
                    }

                    bool IsDesiredTargetMethod(IMethodSymbol targetMethod) =>
                    targetMethod.Equals(stringArgumentIndexOfMethod) ||
                    targetMethod.Equals(charArgumentIndexOfMethod) ||
                    targetMethod.Equals(stringAndComparisonTypeArgumentIndexOfMethod) ||
                    targetMethod.Equals(charAndComparisonTypeArgumentIndexOfMethod);
                }
            });