diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs index 4950b1fb6a6..054a7218b0a 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs @@ -57,6 +57,7 @@ internal static class OperationDispatcher { OperationKindEx.Increment, new IncrementOrDecrement() }, { OperationKindEx.InstanceReference, new InstanceReference() }, { OperationKindEx.LocalReference, new LocalReference() }, + { OperationKindEx.MethodReference, new MethodReference() }, { OperationKindEx.ObjectCreation, new ObjectCreation() }, { OperationKindEx.ParameterReference, new ParameterReference() }, { OperationKindEx.RecursivePattern, new RecursivePattern() }, diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs index 6b2d27dd1f7..76a91a33e2d 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.ObjectModel; using SonarAnalyzer.SymbolicExecution.Constraints; namespace SonarAnalyzer.SymbolicExecution.Roslyn.OperationProcessors; @@ -43,6 +44,21 @@ internal static class CollectionTracker KnownType.System_Collections_Generic_IDictionary_TKey_TValue, KnownType.System_Collections_Immutable_IImmutableDictionary_TKey_TValue); + public static readonly HashSet AddMethods = new() + { + nameof(ICollection.Add), + nameof(List.AddRange), + nameof(List.Insert), + nameof(List.InsertRange), + nameof(HashSet.UnionWith), + nameof(HashSet.SymmetricExceptWith), // This can add and/or remove items => It should remove all CollectionConstraints. + // However, just learning NotEmpty (and thus unlearning Empty) is good enough for now. + nameof(Queue.Enqueue), + nameof(Stack.Push), + nameof(Collection.Insert), + "TryAdd" + }; + public static CollectionConstraint ObjectCreationConstraint(ProgramState state, IObjectCreationOperationWrapper operation) { if (operation.Type.IsAny(CollectionTypes)) @@ -65,6 +81,11 @@ public static CollectionConstraint ArrayCreationConstraint(IArrayCreationOperati ? CollectionConstraint.Empty : CollectionConstraint.NotEmpty; + public static CollectionConstraint MethodReferenceConstraint(IMethodReferenceOperationWrapper operation) => + operation.Instance is not null && AddMethods.Contains(operation.Method.Name) + ? CollectionConstraint.NotEmpty + : null; + public static ProgramState ApplyConstraints(ProgramState state, IPropertyReferenceOperationWrapper operation, ISymbol instanceSymbol) { if (operation.Property.IsIndexer) diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/MethodReference.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/MethodReference.cs new file mode 100644 index 00000000000..2a1d3dd129e --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/MethodReference.cs @@ -0,0 +1,33 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.SymbolicExecution.Roslyn.OperationProcessors; + +internal sealed class MethodReference : SimpleProcessor +{ + protected override IMethodReferenceOperationWrapper Convert(IOperation operation) => + IMethodReferenceOperationWrapper.FromOperation(operation); + + protected override ProgramState Process(SymbolicContext context, IMethodReferenceOperationWrapper operation) => + CollectionTracker.MethodReferenceConstraint(operation) is { } constraint + && operation.Instance.TrackedSymbol(context.State) is { } symbol + ? context.State.SetSymbolConstraint(symbol, constraint) + : context.State; +} diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyCollectionsShouldNotBeEnumeratedBase.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyCollectionsShouldNotBeEnumeratedBase.cs index 999081cdb8b..f29b38d4779 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyCollectionsShouldNotBeEnumeratedBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyCollectionsShouldNotBeEnumeratedBase.cs @@ -84,21 +84,6 @@ public abstract class EmptyCollectionsShouldNotBeEnumeratedBase : SymbolicRuleCh nameof(Dictionary.TryGetValue) }; - private static readonly HashSet AddMethods = new() - { - nameof(ICollection.Add), - nameof(List.AddRange), - nameof(List.Insert), - nameof(List.InsertRange), - nameof(HashSet.UnionWith), - nameof(HashSet.SymmetricExceptWith), // This can add and/or remove items => It should remove all CollectionConstraints. - // However, just learning NotEmpty (and thus unlearning Empty) is good enough for now. - nameof(Queue.Enqueue), - nameof(Stack.Push), - nameof(Collection.Insert), - "TryAdd" - }; - private static readonly HashSet RemoveMethods = new() { nameof(ICollection.Remove), @@ -115,22 +100,10 @@ public abstract class EmptyCollectionsShouldNotBeEnumeratedBase : SymbolicRuleCh private readonly HashSet emptyAccess = new(); private readonly HashSet nonEmptyAccess = new(); - protected override ProgramState PreProcessSimple(SymbolicContext context) - { - var operation = context.Operation.Instance; - if (operation.AsInvocation() is { } invocation) - { - return ProcessInvocation(context, invocation); - } - else if (operation.AsMethodReference() is { Instance: not null } methodReference) - { - return ProcessAddMethod(context.State, methodReference.Method, methodReference.Instance) ?? context.State; - } - else - { - return context.State; - } - } + protected override ProgramState PreProcessSimple(SymbolicContext context) => + context.Operation.Instance.AsInvocation() is { } invocation + ? ProcessInvocation(context, invocation) + : context.State; public override void ExecutionCompleted() { @@ -176,7 +149,7 @@ bool HasFilteringPredicate() => } private static ProgramState ProcessAddMethod(ProgramState state, IMethodSymbol method, IOperation instance) => - AddMethods.Contains(method.Name) + CollectionTracker.AddMethods.Contains(method.Name) ? SetOperationAndSymbolConstraint(state, instance, CollectionConstraint.NotEmpty) : null;