Skip to content

Commit

Permalink
SE - Nullable: Learn object constraint from value comparison (#6925)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-mikula-sonarsource committed Apr 4, 2023
1 parent 53c7a98 commit 83225ac
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ protected override ProgramState LearnBranchingConstraint(ProgramState state, IBi
private static ProgramState LearnBranchingConstraint<T>(ProgramState state, IBinaryOperationWrapper binary, bool falseBranch)
where T : SymbolicConstraint
{
var useOpposite = falseBranch ^ binary.OperatorKind.IsNotEquals();
// We can fall through ?? because "constraint" and "testedSymbol" are exclusive. Symbols with the constraint will be recognized as "constraint" side.
if ((OperandConstraint(binary.LeftOperand) ?? OperandConstraint(binary.RightOperand)) is { } constraint
&& (OperandSymbolWithoutConstraint(binary.LeftOperand) ?? OperandSymbolWithoutConstraint(binary.RightOperand)) is { } testedSymbol)
&& (OperandSymbolWithoutConstraint(binary.LeftOperand) ?? OperandSymbolWithoutConstraint(binary.RightOperand)) is { } testedSymbol
&& !(useOpposite && constraint is BoolConstraint && testedSymbol.GetSymbolType().IsNullableBoolean())) // We don't want to learn False for "nullableBool != true", because it could also be <null>.
{
constraint = constraint.ApplyOpposite(falseBranch ^ binary.OperatorKind.IsNotEquals());
constraint = constraint.ApplyOpposite(useOpposite); // Beware that opposite of ObjectConstraint.NotNull doesn't exist and returns <null>
return constraint is null ? null : state.SetSymbolConstraint(testedSymbol, constraint);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public void Branching_LearnsObjectConstraint_CS(string expression)
[DataRow("!(bool)(object)!!(null != arg)")]
[DataRow("!!!((object)arg != (object)null)")]
[DataRow("!!!((object)null != (object)arg)")]
public void Branching_LearnsObjectConstraint_Nullable_CS(string expression)
public void Branching_LearnsObjectConstraint_NullableInt_CS(string expression)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary, "int?");
validator.ValidateTag("If", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
Expand All @@ -187,6 +187,27 @@ public void Branching_LearnsObjectConstraint_Nullable_CS(string expression)
.And.ContainSingle(x => x.HasConstraint(ObjectConstraint.NotNull));
}

[DataTestMethod]
[DataRow("arg == null")]
[DataRow("null == arg")]
[DataRow("(object)(object)arg == (object)(object)null")]
[DataRow("(object)(object)arg == (object)(object)isNull")]
[DataRow("!!!(arg != null)")]
[DataRow("!!!(null != arg)")]
[DataRow("!(bool)(object)!!(arg != null)")]
[DataRow("!(bool)(object)!!(null != arg)")]
[DataRow("!!!((object)arg != (object)null)")]
[DataRow("!!!((object)null != (object)arg)")]
public void Branching_LearnsObjectConstraint_NullableBool_CS(string expression)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary, "bool?");
validator.ValidateTag("If", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
validator.ValidateTag("Else", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue());
validator.TagValues("End").Should().HaveCount(2)
.And.ContainSingle(x => x.HasConstraint(ObjectConstraint.Null))
.And.ContainSingle(x => x.HasConstraint(ObjectConstraint.NotNull));
}

[DataTestMethod]
[DataRow("arg != null")]
[DataRow("arg != isNull")]
Expand Down Expand Up @@ -217,7 +238,7 @@ public void Branching_LearnsObjectConstraint_Binary_Negated_CS(string expression
[DataRow("!!!(null == arg)")]
[DataRow("!(bool)(object)!!(arg == null)")]
[DataRow("!(bool)(object)!!(null == arg)")]
public void Branching_LearnsObjectConstraint_Binary_Negated_Nullable_CS(string expression)
public void Branching_LearnsObjectConstraint_Binary_Negated_NullableInt_CS(string expression)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary, "int?");
validator.ValidateTag("If", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue());
Expand All @@ -228,11 +249,36 @@ public void Branching_LearnsObjectConstraint_Binary_Negated_Nullable_CS(string e
}

[DataTestMethod]
[DataRow("arg == isObject")]
[DataRow("isObject == arg")]
public void Branching_LearnsObjectConstraint_Binary_UndefinedInOtherBranch_CS(string expression)
[DataRow("arg != null")]
[DataRow("null != arg")]
[DataRow("(object)(object)arg != (object)(object)null")]
[DataRow("(object)(object)arg != (object)(object)isNull")]
[DataRow("!!!(arg == null)")]
[DataRow("!!!(null == arg)")]
[DataRow("!(bool)(object)!!(arg == null)")]
[DataRow("!(bool)(object)!!(null == arg)")]
public void Branching_LearnsObjectConstraint_Binary_Negated_NullableBool_CS(string expression)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary);
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary, "bool?");
validator.ValidateTag("If", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue());
validator.ValidateTag("Else", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue());
validator.TagValues("End").Should().HaveCount(2)
.And.ContainSingle(x => x.HasConstraint(ObjectConstraint.Null))
.And.ContainSingle(x => x.HasConstraint(ObjectConstraint.NotNull));
}

[DataTestMethod]
[DataRow("arg == isObject", "object")]
[DataRow("isObject == arg", "object")]
[DataRow("arg == true", "bool?")]
[DataRow("arg == false", "bool?")]
[DataRow("true == arg", "bool?")]
[DataRow("false == arg", "bool?")]
[DataRow("arg == 42", "int?")]
[DataRow("42 == arg", "int?")]
public void Branching_LearnsObjectConstraint_Binary_UndefinedInOtherBranch_CS(string expression, string argType)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary, argType);
validator.ValidateTag("If", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue());
validator.ValidateTag("Else", x => x.Should().BeNull("We can't tell if it is Null or NotNull in this branch"));
validator.TagValues("End").Should().HaveCount(2)
Expand All @@ -241,11 +287,17 @@ public void Branching_LearnsObjectConstraint_Binary_UndefinedInOtherBranch_CS(st
}

[DataTestMethod]
[DataRow("arg != isObject")]
[DataRow("isObject != arg")]
public void Branching_LearnsObjectConstraint_Binary_UndefinedInOtherBranch_Negated_CS(string expression)
[DataRow("arg != isObject", "object")]
[DataRow("isObject != arg", "object")]
[DataRow("arg != true", "bool?")]
[DataRow("arg != false", "bool?")]
[DataRow("true != arg", "bool?")]
[DataRow("false != arg", "bool?")]
[DataRow("arg != 42", "int?")]
[DataRow("42 != arg", "int?")]
public void Branching_LearnsObjectConstraint_Binary_UndefinedInOtherBranch_Negated_CS(string expression, string argType)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary);
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.Binary, argType);
validator.ValidateTag("If", x => x.Should().BeNull("We can't tell if it is Null or NotNull in this branch"));
validator.ValidateTag("Else", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue());
validator.TagValues("End").Should().HaveCount(2)
Expand Down Expand Up @@ -393,8 +445,11 @@ public void Branching_LearnsObjectConstraint_ConstantPattern_Null_Negated(string
[DataTestMethod]
[DataRow("arg is true")]
[DataRow("arg is true", "bool")]
[DataRow("arg is true", "bool?")]
[DataRow("!!(arg is true)")]
[DataRow("!!(arg is true)", "bool?")]
[DataRow("arg is not not true")]
[DataRow("arg is not not true", "bool?")]
public void Branching_LearnsObjectConstraint_ConstantPattern_True(string expression, string argType = "object")
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.ConstantPattern, argType);
Expand All @@ -408,6 +463,7 @@ public void Branching_LearnsObjectConstraint_ConstantPattern_True(string express
[DataTestMethod]
[DataRow("arg is not true", "object")]
[DataRow("arg is not true", "bool")]
[DataRow("arg is not true", "bool?")]
public void Branching_LearnsObjectConstraint_ConstantPattern_True_Negated(string expression, string argType)
{
var validator = CreateIfElseEndValidatorCS(expression, OperationKind.ConstantPattern, argType);
Expand All @@ -433,6 +489,7 @@ public void Branching_LearnsObjectConstraint_ConstantPattern_False(string expres

[DataTestMethod]
[DataRow("arg is 42", "int")]
[DataRow("arg is 42", "int?")]
[DataRow("arg is 42", "T")]
[DataRow("arg is 42", "TStruct")]
public void Branching_LearnsObjectConstraint_ConstantPattern_ValueTypes_InputIsNotReferenceType(string expression, string argType)
Expand All @@ -447,6 +504,7 @@ public void Branching_LearnsObjectConstraint_ConstantPattern_ValueTypes_InputIsN
[DataRow(@"arg is ""some text""")]
[DataRow(@"arg is """"")]
[DataRow("arg is 42")]
[DataRow("arg is 42", "int?")]
[DataRow("arg is System.ConsoleKey.Enter")] // Enum
[DataRow("arg is 42", "TClass")]
[DataRow("arg is 42", "IComparable")] // arg is either a class implementing the interface or a boxed value type
Expand Down

0 comments on commit 83225ac

Please sign in to comment.