-
Notifications
You must be signed in to change notification settings - Fork 222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SE: Learn bool constraint from Equals when parameters are numerical values #7906
Conversation
393cdf2
to
1097ca6
Compare
@@ -186,15 +186,30 @@ private static ProgramState ProcessAssertedBoolSymbol(ProgramState state, IOpera | |||
invocation switch | |||
{ | |||
{ Arguments.Length: 2, TargetMethod.IsStatic: true } => ProcessEquals(context, invocation.Arguments[0].ToArgument().Value, invocation.Arguments[1].ToArgument().Value), | |||
{ Arguments.Length: 1 } when invocation.TargetMethod.ContainingType.IsNullableValueType() => ProcessEquals(context, invocation.Instance, invocation.Arguments[0].ToArgument().Value), | |||
{ Arguments.Length: 1 } when invocation.TargetMethod.ContainingType.IsNullableValueType() || invocation.TargetMethod.ContainingType.IsStruct() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super confident about this.
This is so we can process also invocations for 1.Equals(2)
and false.Equals(true)
.
I limited it only to structs as for classes it might be overridden and create weird side efffects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this was limited to nullables in the first place. Seems like a reasonable improvement to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I can be very specific here again and ask that the containing type is bool or int?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You would need to include all the integral types (long
, byte
etc.). Keep it as is.
[DataRow("42", "42")] | ||
[DataRow("42", "0")] | ||
[DataRow("0", "42")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these are now detected as equal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good for the most part. Just some improvements.
@@ -186,15 +186,30 @@ private static ProgramState ProcessAssertedBoolSymbol(ProgramState state, IOpera | |||
invocation switch | |||
{ | |||
{ Arguments.Length: 2, TargetMethod.IsStatic: true } => ProcessEquals(context, invocation.Arguments[0].ToArgument().Value, invocation.Arguments[1].ToArgument().Value), | |||
{ Arguments.Length: 1 } when invocation.TargetMethod.ContainingType.IsNullableValueType() => ProcessEquals(context, invocation.Instance, invocation.Arguments[0].ToArgument().Value), | |||
{ Arguments.Length: 1 } when invocation.TargetMethod.ContainingType.IsNullableValueType() || invocation.TargetMethod.ContainingType.IsStruct() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this was limited to nullables in the first place. Seems like a reasonable improvement to me.
&& context.State.Constraint<BoolConstraint>(rightOperation) is { } leftBool | ||
? context.SetOperationConstraint(BoolConstraint.From(leftBool == rightBool)).ToArray() | ||
: ProcessEqualsObject(context, leftOperation, rightOperation); | ||
private static ProgramState[] ProcessEquals(SymbolicContext context, IOperation leftOperation, IOperation rightOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you extract all three options into methods you can do something like this:
ProcessEqualsBool(context, leftOperation, rightOperation)
?? ProcessEqualsNumber(context, leftOperation, rightOperation)
?? ProcessEqualsObject(context, leftOperation, rightOperation);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reverted - it does not look natural to return null when the return type is an array of ProgramState.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pavel-mikula-sonarsource any better way to split ProcessEquals
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it feel unnatural? This is a pattern used quite commonly in the code-base.
I'm not super opposed to the if-else tree, but I find the alternative more readable.
else if (context.State.Constraint<NumberConstraint>(leftOperation) is { } leftNumber | ||
&& context.State.Constraint<NumberConstraint>(rightOperation) is { } rightNumber | ||
&& leftNumber.IsSingleValue | ||
&& rightNumber.IsSingleValue) | ||
{ | ||
return context.SetOperationConstraint(BoolConstraint.From(leftNumber.Equals(rightNumber))).ToArray(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be really good if we would also learn false for non-SingleValues ranges that do not Overlap. It should be very easy to add here. But feel free to not include it in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like a good idea :D
I'll see how "easy" it is.
EDIT: Done
var code = $""" | ||
var result = {left}.Equals({right}); | ||
Tag("Result", result); | ||
|
||
var resultStaticCall = object.Equals({left}, {right}); | ||
Tag("ResultStaticCall", resultStaticCall); | ||
"""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var code = $""" | |
var result = {left}.Equals({right}); | |
Tag("Result", result); | |
var resultStaticCall = object.Equals({left}, {right}); | |
Tag("ResultStaticCall", resultStaticCall); | |
"""; | |
var code = $""" | |
var result = {left}.Equals({right}); | |
Tag("Result", result); | |
var resultStaticCall = object.Equals({left}, {right}); | |
Tag("ResultStaticCall", resultStaticCall); | |
"""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is correct - there's no need to move it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no reason for the extra indention and I cannot find anything about string literals in our code conventions. Why do you think this is correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just followed the same pattern of the other examples in the file - which I now noticed that it's not consistent. :/
I'll update everything to 3 * 4 spaces.
var code = """ | ||
if (i>1 && i<10) | ||
{ | ||
var result = object.Equals(i, 0); | ||
Tag("Result", result); | ||
} | ||
"""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var code = """ | |
if (i>1 && i<10) | |
{ | |
var result = object.Equals(i, 0); | |
Tag("Result", result); | |
} | |
"""; | |
var code = """ | |
if (i>1 && i<10) | |
{ | |
var result = object.Equals(i, 0); | |
Tag("Result", result); | |
} | |
"""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
public void Invocation_NumberEquals_DoesNotLearn() | ||
{ | ||
var code = """ | ||
if (i>1 && i<10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (i>1 && i<10) | |
if (i > 1 && i < 10) |
} | ||
"""; | ||
var validator = SETestContext.CreateCS(code, "int i").Validator; | ||
validator.TagValue("Result").Should().HaveOnlyConstraints(ObjectConstraint.NotNull); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment to document the not-implemented behavior.
validator.TagValue("Result").Should().HaveOnlyConstraints(ObjectConstraint.NotNull); | |
validator.TagValue("Result").Should().HaveOnlyConstraints(ObjectConstraint.NotNull); // Should learn false |
8efbbe9
to
ae69dca
Compare
f52bd19
to
cf326a1
Compare
cf326a1
to
376fefd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some nitpicks
} | ||
"""; | ||
var validator = SETestContext.CreateCS(code, "int i").Validator; | ||
validator.TagValue("Result").Should().HaveOnlyConstraints(ObjectConstraint.NotNull); // Should learn 'false' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is in range, so it could be true or false
validator.TagValue("Result").Should().HaveOnlyConstraints(ObjectConstraint.NotNull); // Should learn 'false' | |
validator.TagValue("Result").Should().HaveOnlyConstraints(ObjectConstraint.NotNull); |
public void Invocation_NumberEquals_RangesDoNotOverlap_LearnsResult() | ||
{ | ||
var code = $$""" | ||
if (i > 0 && j < 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing, easier to imagine (negative first, on the left of the axis)
if (i > 0 && j < 0) | |
if (i < 0 && j > 0) |
} | ||
|
||
[TestMethod] | ||
public void Invocation_NumberEquals_DoesNotLearn() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to explain how is it different from the others
public void Invocation_NumberEquals_DoesNotLearn() | |
public void Invocation_NumberEquals_ValueInRange_DoesNotLearn() |
if (context.State.Constraint<BoolConstraint>(leftOperation) is { } rightBool | ||
&& context.State.Constraint<BoolConstraint>(rightOperation) is { } leftBool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swapped names
if (context.State.Constraint<BoolConstraint>(leftOperation) is { } rightBool | |
&& context.State.Constraint<BoolConstraint>(rightOperation) is { } leftBool) | |
if (context.State.Constraint<BoolConstraint>(leftOperation) is { } leftBool | |
&& context.State.Constraint<BoolConstraint>(rightOperation) is { } rightBool) |
else if (!left.Overlaps(right)) | ||
{ | ||
return context.SetOperationConstraint(BoolConstraint.False); | ||
} | ||
else | ||
{ | ||
return context.State; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make the condition positive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I make it positive then I don't know the outcome.
If the number constrains don't overlap I know that the condition is false - if they do overlap then I know nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll approve this besides some open discussions, since I think they should not be blocking.
Proceed on based on your own judgment.
&& context.State.Constraint<BoolConstraint>(rightOperation) is { } leftBool | ||
? context.SetOperationConstraint(BoolConstraint.From(leftBool == rightBool)).ToArray() | ||
: ProcessEqualsObject(context, leftOperation, rightOperation); | ||
private static ProgramState[] ProcessEquals(SymbolicContext context, IOperation leftOperation, IOperation rightOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it feel unnatural? This is a pattern used quite commonly in the code-base.
I'm not super opposed to the if-else tree, but I find the alternative more readable.
var code = $""" | ||
var result = {left}.Equals({right}); | ||
Tag("Result", result); | ||
|
||
var resultStaticCall = object.Equals({left}, {right}); | ||
Tag("ResultStaticCall", resultStaticCall); | ||
"""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no reason for the extra indention and I cannot find anything about string literals in our code conventions. Why do you think this is correct?
Kudos, SonarCloud Quality Gate passed! |
Kudos, SonarCloud Quality Gate passed! |
Fixes #7704