diff --git a/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/HardcodedBytesRuleBase.cs b/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/HardcodedBytesRuleBase.cs index 2c7b7bd0c25..86aa00c5555 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/HardcodedBytesRuleBase.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/HardcodedBytesRuleBase.cs @@ -61,7 +61,7 @@ protected ProgramState ProcessArraySetValue(ProgramState state, IInvocationOpera { if (invocation.TargetMethod.Name == nameof(Array.SetValue) && invocation.TargetMethod.ContainingType.Is(KnownType.System_Array) - && invocation.Instance.TrackedSymbol(state) is { } array) + && invocation.GetInstance(state).TrackedSymbol(state) is { } array) { return invocation.ArgumentValue("value") is { ConstantValue.HasValue: true } ? state @@ -74,7 +74,7 @@ protected ProgramState ProcessArraySetValue(ProgramState state, IInvocationOpera protected ProgramState ProcessArrayInitialize(ProgramState state, IInvocationOperationWrapper invocation) => invocation.TargetMethod.Name == nameof(Array.Initialize) && invocation.TargetMethod.ContainingType.Is(KnownType.System_Array) - && invocation.Instance.TrackedSymbol(state) is { } array + && invocation.GetInstance(state).TrackedSymbol(state) is { } array ? state.SetSymbolConstraint(array, Hardcoded) : null; diff --git a/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/SecureRandomSeedsShouldNotBePredictable.cs b/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/SecureRandomSeedsShouldNotBePredictable.cs index 4187979f60b..621abf19e8d 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/SecureRandomSeedsShouldNotBePredictable.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/SecureRandomSeedsShouldNotBePredictable.cs @@ -119,7 +119,7 @@ private static ProgramState ProcessSecureRandomGetInstance(ProgramState state, I private static ProgramState ProcessSeedingMethods(ProgramState state, IInvocationOperationWrapper invocation) { return (IsSetSeed() || IsAddSeedMaterial()) - && invocation.Instance.TrackedSymbol(state) is { } instance + && invocation.GetInstance(state).TrackedSymbol(state) is { } instance // If it is already unpredictable, do nothing. // Seeding methods do not overwrite the state, but _mix_ it with the new value. && state[instance]?.HasConstraint(CryptographicSeedConstraint.Predictable) is true @@ -143,7 +143,7 @@ bool IsAddSeedMaterial() => private ProgramState ProcessNextMethods(ProgramState state, IInvocationOperationWrapper invocation) { if ((IsSecureRandomMethod() || IsRandomGeneratorMethod()) - && invocation.Instance is { } instance + && invocation.GetInstance(state) is { } instance && state[instance]?.HasConstraint(CryptographicSeedConstraint.Predictable) is true) { ReportIssue(invocation.WrappedOperation); diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs index 170188f4dd5..1a8410ea7df 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/CollectionTracker.cs @@ -149,7 +149,7 @@ public static ProgramState LearnFrom(ProgramState state, IInvocationOperationWra { return state.SetOperationConstraint(invocation, constraint); } - if (invocation.Instance is { } instance && instance.Type.DerivesOrImplementsAny(CollectionTypes)) + if (invocation.GetInstance(state) is { } instance && instance.Type.DerivesOrImplementsAny(CollectionTypes)) { var targetMethod = invocation.TargetMethod; var symbolValue = state[instance] ?? SymbolicValue.Empty; @@ -182,8 +182,7 @@ private static NumberConstraint EnumerableCountConstraint(ProgramState state, II { if (invocation.TargetMethod.Is(KnownType.System_Linq_Enumerable, nameof(Enumerable.Count))) { - var instance = invocation.Instance ?? invocation.Arguments[0].ToArgument().Value; - if (instance.TrackedSymbol(state) is { } symbol + if (invocation.GetInstance(state).TrackedSymbol(state) is { } symbol && state[symbol]?.Constraint() is { } collection) { if (collection == CollectionConstraint.Empty) diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Invocation.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Invocation.cs index 677b92022dc..7dc9871f98d 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Invocation.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Invocation.cs @@ -113,7 +113,7 @@ protected override ProgramState[] Process(SymbolicContext context, IInvocationOp if (invocation.TargetMethod.IsExtensionMethod && invocation.TargetMethod.ReducedFrom is { } reducedFrom // VB reduces method symbol to 'instance.Extension()' without annotated ArgumentOperation && reducedFrom.Parameters.First().HasNotNullAttribute() - && invocation.Instance.TrackedSymbol(state) is { } instanceSymbol) + && invocation.GetInstance(state).TrackedSymbol(state) is { } instanceSymbol) { state = state.SetSymbolConstraint(instanceSymbol, ObjectConstraint.NotNull); } diff --git a/analyzers/tests/SonarAnalyzer.Test/SymbolicExecution/Roslyn/IInvocationOperationExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.Test/SymbolicExecution/Roslyn/IInvocationOperationExtensionsTest.cs new file mode 100644 index 00000000000..beb3f9e164e --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/SymbolicExecution/Roslyn/IInvocationOperationExtensionsTest.cs @@ -0,0 +1,63 @@ +/* + * 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. + */ + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using SonarAnalyzer.SymbolicExecution.Roslyn; +using StyleCop.Analyzers.Lightup; + +namespace SonarAnalyzer.Test.SymbolicExecution.Roslyn; + +[TestClass] +public class IInvocationOperationExtensionsTest +{ + [DataTestMethod] + [DataRow("public void Method() {}", "sample.Method()", OperationKind.FieldReference)] + [DataRow("", "this.Method()", OperationKind.InstanceReference)] + [DataRow("", "sample.ExtensionMethod()", OperationKind.FieldReference)] + [DataRow("public void Method() {}", "sample.GetSample().Method()", OperationKind.Invocation)] + + public void Invocation_GetInstance_ReturnsSymbol(string definition, string invocation, OperationKind kind) + { + var code = $$""" + class Test + { + Sample sample = new Sample(); + void Method() {} + void M() => {{invocation}}; + } + + public class Sample + { + {{definition}} + public Sample GetSample() => new Sample(); + } + + public static class Extensions + { + public static void ExtensionMethod(this Sample sample) {} + } + """; + var (tree, model) = TestHelper.CompileCS(code); + var invocationSyntax = tree.GetRoot().DescendantNodesAndSelf().OfType().First(); + var operation = IInvocationOperationWrapper.FromOperation(model.GetOperation(invocationSyntax)); + var d = operation.GetInstance(ProgramState.Empty); + d.Should().NotBeNull().And.BeAssignableTo().Which.Kind.Should().Be(kind); + } +}