Skip to content

Commit 6dceab4

Browse files
authored
Migrate S4158: Passing as argument removes constraint (#7433)
1 parent 897430c commit 6dceab4

11 files changed

+94
-123
lines changed

analyzers/its/expected/Automapper/AutoMapper--net461-S4158.json

-30
This file was deleted.

analyzers/its/expected/Automapper/AutoMapper--netstandard2.0-S4158.json

-30
This file was deleted.

analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/Extensions/IOperationExtensions.cs

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ static bool IsTryDownCast(IConversionOperationWrapper conversion) =>
4343
conversion.IsTryCast && !conversion.Operand.Type.DerivesOrImplements(conversion.Type);
4444
}
4545

46+
internal static IArgumentOperationWrapper? AsArgument(this IOperation operation) =>
47+
operation.As(OperationKindEx.Argument, IArgumentOperationWrapper.FromOperation);
48+
4649
internal static IAssignmentOperationWrapper? AsAssignment(this IOperation operation) =>
4750
operation.As(OperationKindEx.SimpleAssignment, IAssignmentOperationWrapper.FromOperation);
4851

analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyCollectionsShouldNotBeEnumeratedBase.cs

+6
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ protected override ProgramState PreProcessSimple(SymbolicContext context)
141141
{
142142
return context.State.SetOperationConstraint(operation, constraint);
143143
}
144+
else if (operation.AsArgument() is { } argument
145+
&& context.State.ResolveCaptureAndUnwrapConversion(argument.Value).TrackedSymbol() is { } symbol
146+
&& context.State[symbol] is { } symbolValue)
147+
{
148+
return context.State.SetSymbolValue(symbol, symbolValue.WithoutConstraint<CollectionConstraint>());
149+
}
144150
else
145151
{
146152
return context.State;

analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private string SerializeConstraints() =>
108108
private SymbolicValue RemoveConstraint(Type type) =>
109109
Constraints.Count switch
110110
{
111-
1 => Empty,
111+
1 => null,
112112
2 => OtherSingle(type),
113113
3 => OtherPair(type),
114114
_ => this with { Constraints = Constraints.Remove(type) },

analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/ProgramStateTest.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,14 @@ private static void ResetFieldConstraintTests(SymbolicConstraint constraint, boo
360360
symbolValue.HasConstraint(constraint).Should().BeTrue();
361361
sut = sut.ResetFieldConstraints();
362362
symbolValue = sut[field];
363-
symbolValue.HasConstraint(constraint).Should().Be(expectIsPreserved);
363+
if (expectIsPreserved)
364+
{
365+
symbolValue.HasConstraint(constraint).Should().BeTrue();
366+
}
367+
else
368+
{
369+
symbolValue.Should().BeNull();
370+
}
364371
}
365372

366373
private static ISymbol[] CreateSymbols()

analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Invocation.cs

+25-25
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,10 @@ public static void Tag(string name, object arg) {{ }}
191191
}}";
192192
var validator = new SETestContext(code, AnalyzerLanguage.CSharp, Array.Empty<SymbolicCheck>()).Validator;
193193
validator.ValidateContainsOperation(OperationKind.Invocation);
194-
validator.ValidateTag("BeforeField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
195-
validator.ValidateTag("BeforeStaticField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
196-
validator.ValidateTag("AfterField", x => x.Constraint<ObjectConstraint>().Should().BeNull());
197-
validator.ValidateTag("AfterStaticField", x => x.Constraint<ObjectConstraint>().Should().BeNull());
194+
validator.TagValue("BeforeField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
195+
validator.TagValue("BeforeStaticField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
196+
validator.TagValue("AfterField").Should().BeNull();
197+
validator.TagValue("AfterStaticField").Should().BeNull();
198198
}
199199

200200
[DataTestMethod]
@@ -277,8 +277,8 @@ public static void Tag(string name) {{ }}
277277
x => // Branch for "this"
278278
{
279279
x[condition].Should().BeNull(); // Should have BoolConstraint.True
280-
x[field].AllConstraints.Should().BeEmpty();
281-
x[staticField].AllConstraints.Should().BeEmpty();
280+
x[field].Should().BeNull();
281+
x[staticField].Should().BeNull();
282282
},
283283
x => // Branch for "otherInstance"
284284
{
@@ -387,7 +387,7 @@ public void Instance_InstanceMethodCall_ClearsField()
387387
var validator = SETestContext.CreateCS(code).Validator;
388388
validator.TagValues("After").Should().Equal(
389389
SymbolicValue.Empty.WithConstraint(ObjectConstraint.NotNull),
390-
SymbolicValue.Empty);
390+
null);
391391
}
392392

393393
[TestMethod]
@@ -398,7 +398,7 @@ public void Instance_InstanceMethodCall_ClearsFieldWithBranchInArgument()
398398
this.InstanceMethod(boolParameter ? 1 : 0);
399399
Tag(""After"", this.ObjectField);";
400400
var validator = SETestContext.CreateCS(code).Validator;
401-
validator.ValidateTag("After", x => x.AllConstraints.Should().BeEmpty());
401+
validator.TagValue("After").Should().BeNull();
402402
}
403403

404404
[TestMethod]
@@ -465,28 +465,28 @@ public class Other
465465
public static void OtherMethod() { }
466466
}";
467467
var validator = new SETestContext(code, AnalyzerLanguage.CSharp, Array.Empty<SymbolicCheck>()).Validator;
468-
validator.ValidateTag("Start_Base_BaseField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
469-
validator.ValidateTag("Start_Other_OtherField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
470-
validator.ValidateTag("Start_Sample_SampleField1", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
471-
validator.ValidateTag("Start_Sample_SampleField2", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
468+
validator.TagValue("Start_Base_BaseField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
469+
validator.TagValue("Start_Other_OtherField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
470+
validator.TagValue("Start_Sample_SampleField1").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
471+
validator.TagValue("Start_Sample_SampleField2").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
472472

473473
// SampleMethod() resets own field and base class fields, but not other class fields
474-
validator.ValidateTag("SampleMethod_Base_BaseField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeFalse());
475-
validator.ValidateTag("SampleMethod_Other_OtherField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
476-
validator.ValidateTag("SampleMethod_Sample_SampleField1", x => x.HasConstraint(ObjectConstraint.Null).Should().BeFalse());
477-
validator.ValidateTag("SampleMethod_Sample_SampleField2", x => x.HasConstraint(ObjectConstraint.Null).Should().BeFalse());
474+
validator.TagValue("SampleMethod_Base_BaseField").Should().BeNull();
475+
validator.TagValue("SampleMethod_Other_OtherField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
476+
validator.TagValue("SampleMethod_Sample_SampleField1").Should().BeNull();
477+
validator.TagValue("SampleMethod_Sample_SampleField2").Should().BeNull();
478478

479479
// OtherMethod() resets only its own constraints
480-
validator.ValidateTag("OtherMethod_Base_BaseField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
481-
validator.ValidateTag("OtherMethod_Other_OtherField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeFalse());
482-
validator.ValidateTag("OtherMethod_Sample_SampleField1", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
483-
validator.ValidateTag("OtherMethod_Sample_SampleField2", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
480+
validator.TagValue("OtherMethod_Base_BaseField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
481+
validator.TagValue("OtherMethod_Other_OtherField").Should().BeNull();
482+
validator.TagValue("OtherMethod_Sample_SampleField1").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
483+
validator.TagValue("OtherMethod_Sample_SampleField2").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
484484

485485
// BaseMethod() called from Sample only resets Base field
486-
validator.ValidateTag("BaseMethod_Base_BaseField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeFalse());
487-
validator.ValidateTag("BaseMethod_Other_OtherField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
488-
validator.ValidateTag("BaseMethod_Sample_SampleField1", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
489-
validator.ValidateTag("BaseMethod_Sample_SampleField2", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
486+
validator.TagValue("BaseMethod_Base_BaseField").Should().BeNull();
487+
validator.TagValue("BaseMethod_Other_OtherField").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
488+
validator.TagValue("BaseMethod_Sample_SampleField1").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
489+
validator.TagValue("BaseMethod_Sample_SampleField2").HasConstraint(ObjectConstraint.Null).Should().BeTrue();
490490
}
491491

492492
[DataTestMethod]
@@ -507,7 +507,7 @@ public void Invocation_StaticMethodCall_DoesNotClearInstanceFields(string invoca
507507
validator.ValidateTag("BeforeField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
508508
validator.ValidateTag("BeforeStaticField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
509509
validator.ValidateTag("AfterField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
510-
validator.ValidateTag("AfterStaticField", x => x.HasConstraint(ObjectConstraint.Null).Should().BeFalse());
510+
validator.ValidateTag("AfterStaticField", x => x.Should().BeNull());
511511
}
512512

513513
[TestMethod]

analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.InvocationAttribute.cs

+19-19
Original file line numberDiff line numberDiff line change
@@ -185,32 +185,32 @@ public void Invocation_NotNullWhen_Unknown()
185185
[TestMethod]
186186
public void Invocation_NotNullWhen_Unknown_InstanceMethodResetsFieldConstraints()
187187
{
188-
const string code = @"
189-
private object ObjectField;
190-
191-
public void Test()
192-
{
193-
this.ObjectField = null;
194-
string byteString = Unknown<string>();
195-
var success = TryParse(byteString, out var result);
196-
Tag(""End"", null);
197-
}
198-
public bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string s, out object o) { o = null; return true; }";
188+
const string code = """
189+
private object ObjectField;
190+
public void Test()
191+
{
192+
this.ObjectField = null;
193+
string byteString = Unknown<string>();
194+
var success = TryParse(byteString, out var result);
195+
Tag("End", null);
196+
}
197+
public bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string s, out object o) { o = null; return true; }
198+
""";
199199
var validator = SETestContext.CreateCSMethod(code).Validator;
200200
validator.TagStates("End").Should().SatisfyRespectively(
201201
x =>
202202
{
203-
x[validator.Symbol("ObjectField")].HasConstraint<ObjectConstraint>().Should().BeFalse();
204-
x[validator.Symbol("byteString")].HasConstraint(ObjectConstraint.NotNull).Should().BeTrue();
205-
x[validator.Symbol("success")].HasConstraint(BoolConstraint.True).Should().BeTrue();
206-
x[validator.Symbol("result")].Should().BeNull();
203+
x[validator.Symbol("ObjectField")].Should().HaveNoConstraints();
204+
x[validator.Symbol("byteString")].Should().HaveOnlyConstraint(ObjectConstraint.NotNull);
205+
x[validator.Symbol("success")].Should().HaveOnlyConstraints(ObjectConstraint.NotNull, BoolConstraint.True);
206+
x[validator.Symbol("result")].Should().HaveNoConstraints();
207207
},
208208
x =>
209209
{
210-
x[validator.Symbol("ObjectField")].HasConstraint<ObjectConstraint>().Should().BeFalse();
211-
x[validator.Symbol("byteString")].Should().BeNull();
212-
x[validator.Symbol("success")].HasConstraint(BoolConstraint.False).Should().BeTrue();
213-
x[validator.Symbol("result")].Should().BeNull();
210+
x[validator.Symbol("ObjectField")].Should().HaveNoConstraints();
211+
x[validator.Symbol("byteString")].Should().HaveNoConstraints();
212+
x[validator.Symbol("success")].Should().HaveOnlyConstraints(ObjectConstraint.NotNull, BoolConstraint.False);
213+
x[validator.Symbol("result")].Should().HaveNoConstraints();
214214
});
215215
}
216216

analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/SymbolicValueTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void WithoutConstraint_RemovesOnlyTheSame()
7878
.WithoutConstraint(TestConstraint.Second); // Do nothing
7979
sut.HasConstraint(TestConstraint.First).Should().BeTrue();
8080
sut = sut.WithoutConstraint(TestConstraint.First);
81-
sut.HasConstraint(TestConstraint.First).Should().BeFalse();
81+
sut.Should().BeNull();
8282
}
8383

8484
[TestMethod]
@@ -242,14 +242,14 @@ public void TripletCache_ReplaceExistingConstraintType()
242242
public void SingleCache_RemoveLastEntry_Kind()
243243
{
244244
var sut = SymbolicValue.Null;
245-
sut.WithoutConstraint(ObjectConstraint.Null).Should().BeSameAs(SymbolicValue.Empty);
245+
sut.WithoutConstraint(ObjectConstraint.Null).Should().BeNull();
246246
}
247247

248248
[TestMethod]
249249
public void SingleCache_RemoveLastEntry_Type()
250250
{
251251
var sut = SymbolicValue.Null;
252-
sut.WithoutConstraint<ObjectConstraint>().Should().BeSameAs(SymbolicValue.Empty);
252+
sut.WithoutConstraint<ObjectConstraint>().Should().BeNull();
253253
}
254254

255255
[TestMethod]

analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/EmptyCollectionsShouldNotBeEnumerated.cs

+23-11
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,19 @@ public void ConstructorWithEnumerableWithConstraint(bool condition)
105105
var set = new HashSet<int>(baseCollection);
106106
set.Clear(); // Noncompliant
107107

108+
baseCollection = new List<int>();
108109
set = new HashSet<int>(baseCollection, EqualityComparer<int>.Default);
109110
set.Clear(); // Noncompliant
110111

112+
baseCollection = new List<int>();
111113
set = new HashSet<int>(comparer: EqualityComparer<int>.Default, collection: baseCollection);
112114
set.Clear(); // Noncompliant
113115

116+
baseCollection = new List<int>();
114117
set = new HashSet<int>(condition ? baseCollection : baseCollection);
115118
set.Clear(); // Noncompliant
116119

120+
baseCollection = new List<int>();
117121
baseCollection.Add(1);
118122
set = new HashSet<int>(baseCollection);
119123
set.Clear(); // Compliant
@@ -438,7 +442,7 @@ public void UnknownExtensionMethods()
438442
{
439443
var list = new List<int>();
440444
list.CustomExtensionMethod(); // Compliant
441-
list.Clear(); // Noncompliant FP
445+
list.Clear(); // Compliant
442446
}
443447

444448
public void WellKnownExtensionMethods()
@@ -489,14 +493,22 @@ public void WellKnownExtensionMethods()
489493
list.Where(x => true); // FN
490494
list.Zip(list, (x, y) => x); // FN
491495
Enumerable.Reverse(list); // FN
492-
list.Clear(); // Noncompliant
496+
list.Clear(); // FN, should raise, because the methods above should not reset the state
493497
}
494498

495-
public void PassingAsArgument_Removes_Constraints()
499+
public void PassingAsArgument_Removes_Constraints(bool condition)
496500
{
497501
var list = new List<int>();
498502
Foo(list);
499-
list.Clear(); // Noncompliant FP
503+
list.Clear(); // Compliant
504+
505+
list = new List<int>();
506+
Foo(condition ? list : null);
507+
list.Clear(); // Compliant
508+
509+
list = new List<int>();
510+
Foo((condition ? list : null) as List<int>);
511+
list.Clear(); // Compliant
500512
}
501513

502514
public void HigherRank_And_Jagged_Array()
@@ -558,7 +570,7 @@ public void LearnConditions_Size(bool condition, List<int> arg)
558570

559571
if (empty.Count() == 0)
560572
{
561-
empty.Clear(); // Noncompliant
573+
empty.Clear(); // FN
562574
}
563575
else
564576
{
@@ -567,7 +579,7 @@ public void LearnConditions_Size(bool condition, List<int> arg)
567579

568580
if (empty.Count(x => condition) == 0)
569581
{
570-
empty.Clear(); // Noncompliant
582+
empty.Clear(); // FN
571583
}
572584
else
573585
{
@@ -576,16 +588,16 @@ public void LearnConditions_Size(bool condition, List<int> arg)
576588

577589
if (notEmpty.Count(x => condition) == 0)
578590
{
579-
empty.Clear(); // Noncompliant
591+
empty.Clear(); // FN
580592
}
581593
else
582594
{
583-
empty.Clear(); // Noncompliant
595+
empty.Clear(); // FN
584596
}
585597

586598
if (Enumerable.Count(empty) == 0)
587599
{
588-
empty.Clear(); // Noncompliant
600+
empty.Clear(); // FN
589601
}
590602
else
591603
{
@@ -621,7 +633,7 @@ public void LearnConditions_Size_Array(bool condition)
621633

622634
if (empty.Count() == 0)
623635
{
624-
empty.Clone(); // Noncompliant
636+
empty.Clone(); // FN
625637
}
626638
else
627639
{
@@ -631,7 +643,7 @@ public void LearnConditions_Size_Array(bool condition)
631643
notEmpty.Clone(); // Compliant, prevents LVA from throwing notEmpty away during reference capture
632644
}
633645

634-
void Foo(IEnumerable<int> items) { }
646+
void Foo(List<int> items) { }
635647
}
636648

637649
static class CustomExtensions

0 commit comments

Comments
 (0)