Skip to content

Commit

Permalink
SE: Add IInvocationOperationExtensions.GetInstance tests (#9513)
Browse files Browse the repository at this point in the history
  • Loading branch information
mary-georgiou-sonarsource committed Jul 12, 2024
1 parent 823fec5 commit 333ad30
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static bool HasThisReceiver(this IInvocationOperationWrapper invocation,
&& !invocation.Arguments.IsEmpty
&& state.ResolveCaptureAndUnwrapConversion(invocation.Arguments[0].ToArgument().Value).Kind == OperationKindEx.InstanceReference);

public static IOperation GetInstance(this IInvocationOperationWrapper invocation, ProgramState state) =>
public static IOperation Target(this IInvocationOperationWrapper invocation, ProgramState state) =>
invocation.Instance
?? (invocation.TargetMethod.IsExtensionMethod
? state.ResolveCaptureAndUnwrapConversion(invocation.Arguments[0].ToArgument().Value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.Target(state) is { } instance && instance.Type.DerivesOrImplementsAny(CollectionTypes))
{
var targetMethod = invocation.TargetMethod;
var symbolValue = state[instance] ?? SymbolicValue.Empty;
Expand Down Expand Up @@ -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.Target(state).TrackedSymbol(state) is { } symbol
&& state[symbol]?.Constraint<CollectionConstraint>() is { } collection)
{
if (collection == CollectionConstraint.Empty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private static ProgramState[] ProcessLinqEnumerableAndQueryable(ProgramState sta

static IEnumerable<ProgramState> ProcessElementOrDefaultMethods(ProgramState state, IInvocationOperationWrapper invocation)
{
var constraint = invocation.GetInstance(state).TrackedSymbol(state) is { } instanceSymbol
var constraint = invocation.Target(state).TrackedSymbol(state) is { } instanceSymbol
&& GetElementType(instanceSymbol) is { } elementType
&& (elementType.IsReferenceType || elementType.IsNullableValueType())
? state[instanceSymbol]?.Constraint<CollectionConstraint>()
Expand All @@ -119,7 +119,7 @@ static IEnumerable<ProgramState> ProcessElementOrDefaultMethods(ProgramState sta

private static ProgramState[] ProcessElementExistsCheckMethods(ProgramState state, IInvocationOperationWrapper invocation)
{
if (ElementExistsCheckMethods.Contains(invocation.TargetMethod.Name) && invocation.GetInstance(state).TrackedSymbol(state) is { } instanceSymbol)
if (ElementExistsCheckMethods.Contains(invocation.TargetMethod.Name) && invocation.Target(state).TrackedSymbol(state) is { } instanceSymbol)
{
return state[instanceSymbol]?.Constraint<CollectionConstraint>() switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public override void ExecutionCompleted()
protected override ProgramState PreProcessSimple(SymbolicContext context)
{
if (context.Operation.Instance.AsInvocation() is { } invocation
&& invocation.Instance is { } instance
&& invocation.Target(context.State) is { } instance
&& RaisingMethods.Contains(invocation.TargetMethod.Name))
{
if (context.State[instance]?.HasConstraint(CollectionConstraint.Empty) is true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 SonarAnalyzer.SymbolicExecution.Roslyn;
using StyleCop.Analyzers.Lightup;
using SyntaxCS = Microsoft.CodeAnalysis.CSharp.Syntax;
using SyntaxVB = Microsoft.CodeAnalysis.VisualBasic.Syntax;

namespace SonarAnalyzer.Test.SymbolicExecution.Roslyn;

[TestClass]
public class IInvocationOperationExtensionsTest
{
[DataTestMethod]
[DataRow("sample.Method()", OperationKind.FieldReference)]
[DataRow("this.Method()", OperationKind.InstanceReference)]
[DataRow("sample.ExtensionMethod()", OperationKind.FieldReference)]
[DataRow("sample.GetSample().Method()", OperationKind.Invocation)]

public void Invocation_GetInstance_ReturnsSymbol_CS(string invocation, OperationKind kind)
{
var code = $$"""

public class Sample
{
Sample sample = new Sample();

public void Method() {}

void M() => {{invocation}};

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<SyntaxCS.InvocationExpressionSyntax>().First();
var operation = IInvocationOperationWrapper.FromOperation(model.GetOperation(invocationSyntax));
operation.Target(ProgramState.Empty).Should().NotBeNull().And.BeAssignableTo<IOperation>().Which.Kind.Should().Be(kind);
}

[DataTestMethod]
[DataRow("sample.Method()", OperationKind.FieldReference)]
[DataRow("Me.Method()", OperationKind.InstanceReference)]
[DataRow("sample.ExtensionMethod()", OperationKind.FieldReference)]
[DataRow("sample.GetSample().Method()", OperationKind.Invocation)]

public void Invocation_GetInstance_ReturnsSymbol_VB(string invocation, OperationKind kind)
{
var code = $$"""
Public Class Sample
Private sample As Sample = New Sample()

Public Sub Method()
End Sub

Private Sub M()
{{invocation}}
End Sub

Public Function GetSample() As Sample
Return New Sample()
End Function

End Class

Public Module Extensions

<System.Runtime.CompilerServices.Extension()>
Public Sub ExtensionMethod(sample As Sample)
End Sub

End Module
""";
var (tree, model) = TestHelper.CompileVB(code);
var invocationSyntax = tree.GetRoot().DescendantNodesAndSelf().OfType<SyntaxVB.InvocationExpressionSyntax>().First();
var operation = IInvocationOperationWrapper.FromOperation(model.GetOperation(invocationSyntax));
operation.Target(ProgramState.Empty).Should().NotBeNull().And.BeAssignableTo<IOperation>().Which.Kind.Should().Be(kind);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,8 @@ public void Invocation_CollectionMethods_SetCollectionConstraint()
list.RemoveAt(0);
tag = "remove";

void Invoke(Action<int> action) { }
list.Add(1, 2); // Extension method
tag = "addExtension";
""";
var validator = SETestContext.CreateCS(code, "List<int> list", new PreserveTestCheck("list")).Validator;
validator.ValidateContainsOperation(OperationKind.Invocation);
Expand All @@ -720,6 +721,7 @@ void Invoke(Action<int> action) { }
Verify("clear", ObjectConstraint.NotNull, CollectionConstraint.Empty);
Verify("add", ObjectConstraint.NotNull, CollectionConstraint.NotEmpty);
Verify("remove", ObjectConstraint.NotNull);
Verify("addExtension", ObjectConstraint.NotNull, CollectionConstraint.NotEmpty);

void Verify(string state, params SymbolicConstraint[] constraints) =>
validator.TagValue(state, "list").Should().HaveOnlyConstraints(constraints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,23 @@ private static int GetChar(string s, int v1, int v2)
}
}

namespace Extensions
{
class ExtensionMethods
{
public void Test()
{
var list = new List<int>();
list.Clear(3); // Noncompliant
}
}

public static class Extensions
{
public static void Clear(this List<int> list, int i) { }
}
}

// See https://github.com/SonarSource/sonar-dotnet/issues/1002
class Program
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ Public Class AdvancedTests
Dim List As New List(Of Integer)
List.All(Function(X) True) ' FN
List.Any() ' FN
Enumerable.Reverse(List) ' FN
Enumerable.Reverse(List) ' Noncompliant
List.Clear() ' Noncompliant
End Sub

Expand Down
Loading

0 comments on commit 333ad30

Please sign in to comment.