private static void OnCompilationStart(CompilationStartAnalysisContext context) { if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) { return; } context.RegisterOperationAction(AnalyzeOperation, OperationKind.Binary); return; // Local functions void AnalyzeOperation(OperationAnalysisContext context) { var operation = (IBinaryOperation)context.Operation; foreach (var selector in CaseSelectors) { if (selector(operation, symbols)) { context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); return; } } } }
public List <MemberReference> GetRequiredSymbolList(string symbol) { List <MemberReference> rv; if (!RequiredSymbols.TryGetValue(symbol, out rv)) { required_symbols [symbol] = rv = new List <MemberReference> (); } return(rv); }
public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols?symbols) { symbols = default; var stringType = compilation.GetSpecialType(SpecialType.System_String); var boolType = compilation.GetSpecialType(SpecialType.System_Boolean); if (stringType is null || boolType is null) { return(false); } if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison, out var stringComparisonType)) { return(false); } var compareMethods = stringType.GetMembers(nameof(string.Compare)) .OfType <IMethodSymbol>() .Where(x => x.IsStatic); var compareStringString = compareMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType); var compareStringStringBool = compareMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType, boolType); var compareStringStringStringComparison = compareMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType, stringComparisonType); var equalsMethods = stringType.GetMembers(nameof(string.Equals)) .OfType <IMethodSymbol>() .Where(x => x.IsStatic); var equalsStringString = equalsMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType); var equalsStringStringStringComparison = equalsMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType, stringComparisonType); // Bail if we do not have at least one complete pair of Compare-Equals methods in the compilation. if ((compareStringString is null || equalsStringString is null) && (compareStringStringBool is null || equalsStringStringStringComparison is null) && (compareStringStringStringComparison is null || equalsStringStringStringComparison is null)) { return(false); } symbols = new RequiredSymbols( stringType, boolType, stringComparisonType, compareStringString, compareStringStringBool, compareStringStringStringComparison, equalsStringString, equalsStringStringStringComparison); return(true); }
public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) { var stringType = compilation.GetSpecialType(SpecialType.System_String); var charType = compilation.GetSpecialType(SpecialType.System_Char); if (stringType is null || charType is null) { symbols = default; return(false); } var readOnlySpanOfCharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1)?.Construct(charType); var memoryExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); if (readOnlySpanOfCharType is null || memoryExtensionsType is null) { symbols = default; return(false); } var intParamInfo = ParameterInfo.GetParameterInfo(compilation.GetSpecialType(SpecialType.System_Int32)); var stringParamInfo = ParameterInfo.GetParameterInfo(stringType); var substringMembers = stringType.GetMembers(nameof(string.Substring)).OfType <IMethodSymbol>(); var substringStart = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo); var substringStartLength = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo, intParamInfo); var asSpanMembers = memoryExtensionsType.GetMembers(nameof(MemoryExtensions.AsSpan)).OfType <IMethodSymbol>(); var asSpanStart = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); var asSpanStartLength = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); if (substringStart is null || substringStartLength is null || asSpanStart is null || asSpanStartLength is null) { symbols = default; return(false); } symbols = new RequiredSymbols( stringType, readOnlySpanOfCharType, substringStart, substringStartLength, asSpanStart, asSpanStartLength); return(true); }
private static void OnCompilationStart(CompilationStartAnalysisContext context) { if (!RequiredSymbols.TryGetSymbols(context.Compilation, out RequiredSymbols symbols)) { return; } context.RegisterOperationBlockStartAction(OnOperationBlockStart); return; // Local functions void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { var invocations = PooledConcurrentSet <IInvocationOperation> .GetInstance(); context.RegisterOperationAction(context => { var argument = (IArgumentOperation)context.Operation; if (symbols.IsAnySubstringInvocation(argument.Value.WalkDownConversion(c => c.IsImplicit)) && argument.Parent is IInvocationOperation invocation) { invocations.Add(invocation); } }, OperationKind.Argument); context.RegisterOperationBlockEndAction(context => { foreach (var invocation in invocations) { // We search for an overload of the invoked member whose signature matches the signature of // the invoked member, except with ReadOnlySpan<char> substituted in for some of the // arguments that are Substring invocations. if (!GetBestSpanBasedOverloads(symbols, invocation, context.CancellationToken).IsEmpty) { Diagnostic diagnostic = invocation.CreateDiagnostic(Rule); context.ReportDiagnostic(diagnostic); } } invocations.Free(context.CancellationToken); }); } }
private static void OnCompilationStart(CompilationStartAnalysisContext context) { if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) { return; } context.RegisterOperationAction(AnalyzeOperation, OperationKind.Conditional); return; void AnalyzeOperation(OperationAnalysisContext context) { var conditional = (IConditionalOperation)context.Operation; if (symbols.IsSimpleAffirmativeCheck(conditional, out _) || symbols.IsNegatedCheckWithThrowingElseClause(conditional, out _)) { context.ReportDiagnostic(conditional.CreateDiagnostic(Rule)); } } }
private void OnCompilationStart(CompilationStartAnalysisContext context) { if (!RequiredSymbols.TryGetSymbols(context.Compilation, out RequiredSymbols symbols)) { return; } context.RegisterOperationBlockStartAction(OnOperationBlockStart); return; // Local functions void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { // Maintain set of all top-most concat operations so we don't report sub-expressions of an // already-reported violation. // We also don't report any diagnostic if the concat operation has too many operands for the span-based // Concat overloads to handle. var topMostConcatOperations = PooledConcurrentSet <IBinaryOperation> .GetInstance(); context.RegisterOperationAction(PopulateTopMostConcatOperations, OperationKind.Binary); context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls); void PopulateTopMostConcatOperations(OperationAnalysisContext context) { // If the current operation is a string-concatenation operation, walk up to the top-most concat // operation and add it to the set. var binary = (IBinaryOperation)context.Operation; if (!TryGetTopMostConcatOperation(binary, out var topMostConcatOperation)) { return; } topMostConcatOperations.Add(topMostConcatOperation); } void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) { // We report diagnostics for all top-most concat operations that contain // direct or conditional substring invocations when there is an applicable span-based overload of // the string.Concat method. // We don't report when the concatenation contains anything other than strings or character literals. foreach (var operation in topMostConcatOperations) { if (ShouldBeReported(operation)) { context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); } } topMostConcatOperations.Free(context.CancellationToken); } } bool ShouldBeReported(IBinaryOperation topMostConcatOperation) { var concatOperands = FlattenBinaryOperation(topMostConcatOperation); // Bail if no suitable overload of 'string.Concat' exists. if (!symbols.TryGetRoscharConcatMethodWithArity(concatOperands.Length, out _)) { return(false); } bool anySubstringInvocations = false; foreach (var operand in concatOperands) { var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); switch (value.Type?.SpecialType) { // Report diagnostics only when operands are exclusively strings and character literals. case SpecialType.System_String: case SpecialType.System_Char when value is ILiteralOperation: if (IsAnyDirectOrConditionalSubstringInvocation(value)) { anySubstringInvocations = true; } break; default: return(false); } } return(anySubstringInvocations); } bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation) { if (operation is IConditionalAccessOperation conditionallAccessOperation) { operation = conditionallAccessOperation.WhenNotNull; } return(operation is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)); } }