Skip to content

Commit

Permalink
SE - Nullable: Add Null constraint for empty nullable constructor (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-mikula-sonarsource authored Mar 6, 2023
1 parent efe42d8 commit 26ba796
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ internal static class OperationDispatcher
{ OperationKindEx.FlowCapture, new FlowCapture() },
{ OperationKindEx.InstanceReference, new InstanceReference() },
{ OperationKindEx.LocalReference, new LocalReference() },
{ OperationKindEx.ObjectCreation, new NotNullOperation() },
{ OperationKindEx.ObjectCreation, new ObjectCreation() },
{ OperationKindEx.ParameterReference, new ParameterReference() },
{ OperationKindEx.PropertyReference, new PropertyReference() },
{ OperationKindEx.RecursivePattern, new RecursivePattern() },
Expand All @@ -67,10 +67,15 @@ public static IEnumerable<SymbolicContext> Process(SymbolicContext context)
{
if (Simple.TryGetValue(context.Operation.Instance.Kind, out var simple)) // Operations that return single state
{
context = context.WithState(simple.Process(context));
return new[] { context.WithState(simple.Process(context)) };
}
else if (Branching.TryGetValue(context.Operation.Instance.Kind, out var processor)) // Operations that can return multiple states
{
return processor.Process(context).Select(context.WithState);
}
else
{
return new[] { context };
}
return Branching.TryGetValue(context.Operation.Instance.Kind, out var processor) // Operations that can return multiple states
? processor.Process(context).Select(x => context.WithState(x))
: new[] { context };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 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.Constraints;

namespace SonarAnalyzer.SymbolicExecution.Roslyn.OperationProcessors;

internal sealed class ObjectCreation : SimpleProcessor<IObjectCreationOperationWrapper>
{
protected override IObjectCreationOperationWrapper Convert(IOperation operation) =>
IObjectCreationOperationWrapper.FromOperation(operation);

protected override ProgramState Process(SymbolicContext context, IObjectCreationOperationWrapper operation) =>
operation.Type.IsNullableValueType() && operation.Arguments.IsEmpty
? context.SetOperationConstraint(ObjectConstraint.Null)
: context.SetOperationConstraint(ObjectConstraint.NotNull);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ namespace SonarAnalyzer.UnitTest.SymbolicExecution.Roslyn
{
public partial class RoslynSymbolicExecutionTest
{
private static IEnumerable<object[]> ThrowHelperCalls =>
new object[][]
{
new[] { @"System.Diagnostics.Debug.Fail(""Fail"");" },
new[] { @"Environment.FailFast(""Fail"");" },
new[] { @"Environment.Exit(-1);" },
};

[TestMethod]
public void InstanceReference_SetsNotNull_VB()
{
Expand Down Expand Up @@ -887,7 +895,7 @@ public void Invocation_TargetMethodIsDelegateInvoke()
[DataRow("null", "null", true, ConstraintKind.ObjectNull, ConstraintKind.ObjectNull)]
[DataRow("null", "new object()", false, ConstraintKind.ObjectNull, ConstraintKind.ObjectNotNull)]
[DataRow("new object()", "null", false, ConstraintKind.ObjectNotNull, ConstraintKind.ObjectNull)]
[DataRow("new int?()", "null", false, ConstraintKind.ObjectNotNull, ConstraintKind.ObjectNull)] // Should be "true" and "Null" because new int()? is still null
[DataRow("new int?()", "null", true, ConstraintKind.ObjectNull, ConstraintKind.ObjectNull)]
[DataRow("new int?(42)", "null", false, ConstraintKind.ObjectNotNull, ConstraintKind.ObjectNull)]
public void Invocation_Equals_LearnResult(string left, string right, bool expectedResult, ConstraintKind expectedConstraintLeft, ConstraintKind expectedConstraintRight)
{
Expand All @@ -909,7 +917,6 @@ public void Invocation_Equals_LearnResult(string left, string right, bool expect
[DataRow("new object()", "Unknown<object>()")]
[DataRow("Unknown<object>()", "new object()")]
[DataRow("Unknown<object>()", "Unknown<object>()")]
[DataRow("new int?()", "5")]
public void Invocation_Equals_DoesNotLearnResult(string left, string right)
{
var code = $@"
Expand Down Expand Up @@ -945,6 +952,22 @@ public void Invocation_Equals_SplitsToBothResults(string left, string right)
});
}

[DataTestMethod]
[DataRow("new int?()", "42")] // ToDo: Should be moved to Invocation_Equals_LearnResult once we build NotNull for int
public void Invocation_Equals_SplitsToBothResults_Nullable(string left, string right)
{
var code = $@"
object left = {left};
object right = {right};
var result = object.Equals(left, right);
Tag(""End"");";
var validator = SETestContext.CreateCS(code).Validator;
var result = validator.Symbol("result");
validator.TagStates("End").Should().SatisfyRespectively(
x => x[result].HasConstraint(BoolConstraint.True).Should().BeTrue(),
x => x[result].HasConstraint(BoolConstraint.False).Should().BeTrue());
}

[TestMethod]
public void Invocation_Equals_CustomSignatures_NotSupported()
{
Expand Down Expand Up @@ -972,13 +995,5 @@ public void Main()
validator.ValidateTag("NoArgs", x => x.Should().BeNull());
validator.ValidateTag("MoreArgs", x => x.Should().BeNull());
}

private static IEnumerable<object[]> ThrowHelperCalls =>
new object[][]
{
new[] { @"System.Diagnostics.Debug.Fail(""Fail"");" },
new[] { @"Environment.FailFast(""Fail"");" },
new[] { @"Environment.Exit(-1);" },
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,27 @@ public void Nullable_HasValue_ReadsBoolConstraintFromObjectConstraint()
validator.ValidateTag("HasValueAfterNull", x => x.HasConstraint(BoolConstraint.False).Should().BeTrue());
validator.ValidateTag("SymbolAfterNull", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
}

[TestMethod]
public void Nullable_Ctor_NoArguments_SetsNullConstraint()
{
const string code = """
public void Main<T>() where T: struct
{
int? explicitType = new Nullable<int>();
int? targetTyped = new();
T? genericValue = new T();
T? genericNull = new T?();
Tag("ExplicitType", explicitType);
Tag("TargetTyped", targetTyped);
Tag("GenericValue", genericValue);
Tag("GenericNull", genericNull);
}
""";
var validator = SETestContext.CreateCSMethod(code).Validator;
validator.ValidateTag("ExplicitType", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
validator.ValidateTag("TargetTyped", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue("new() of int produces value 0"));
validator.ValidateTag("GenericValue", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue("new() of T produces value T"));
validator.ValidateTag("GenericNull", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
}
}

0 comments on commit 26ba796

Please sign in to comment.