-
Notifications
You must be signed in to change notification settings - Fork 3
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
Avoid redundant/equivalent unsigned comparison mutations #297
base: main
Are you sure you want to change the base?
Conversation
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! It may be worth copying the test case added to C++ as we have done elsewhere in case the functionality diverges in the future (although unlikely).
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 the role of candidate mutation and the binary operator to apply mutation ion is switched on IsRedundantReplacementForUnsignedComparison
, and as a result, subsumed mutation are produced. I might be wrong tho. Let me know if I am wrong, I will have another look.
Does it work if we don't have an unsigned literal(eg: x !=0
instead of x != 0u
)?
@JamesLee-Jones Thanks for the comment. The C++ single file tests take considerably longer to run compared with the C ones, so I have decided not to add them in this instance, feeling confident this won't change. |
@JonathanFoo0523 - the optimisation does work if 0u is replaced by 0 - in next iteration I have added tests to check for this. It raises room for another optimisation, though, which is that when an int is cast (implicitly or otherwise) to unsigned, we mutate both before and after the cast, which will lead to replacement with 0 and 1 occurring redundantly as they will be used in both places. I'll open an issue for this. |
@JonathanFoo0523 Thanks for your comments - good spots. I have revised - and to make it less likely that I'll make a mistake like this again I renamed "operator_kind" to "replacement_operator" in all of the functions that are considering whether a replacement is legitimate (both for unary and binary operator mutations). |
Actually I think I got this wrong - perhaps I looked at the non-optimised code. |
|
||
static int __dredd_replace_binary_operator_LE_arg1_unsigned_int_arg2_unsigned_int_rhs_zero(unsigned int arg1, unsigned int arg2, int local_mutation_id) { | ||
if (!__dredd_some_mutation_enabled) return arg1 <= arg2; | ||
if (__dredd_enabled_mutation(local_mutation_id + 0)) return arg1 == arg2; |
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.
Something might not be right here, as
a <= 0 is equivalent to a == 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.
Good catch - I have made the equivalence check symmetric now.
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.
+----+------+------+-------+
| a | a<=0 | TRUE | FALSE |
+----+------+------+-------+
| 0 | T | T | F |
+----+------+------+-------+
| >0 | F | T | F |
+----+------+------+-------+
Still persist. I would say we shouldnt have mutation in this function
|
||
static int __dredd_replace_binary_operator_GT_arg1_unsigned_int_arg2_unsigned_int_rhs_zero(unsigned int arg1, unsigned int arg2, int local_mutation_id) { | ||
if (!__dredd_some_mutation_enabled) return arg1 > arg2; | ||
if (__dredd_enabled_mutation(local_mutation_id + 0)) return arg1 != arg2; |
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.
something wrong
a > 0 is equivalent to a != 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.
Good catch - I have made the equivalence check symmetric now.
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.
still persist
+----+-----+------+-------+
| a | a>0 | FALSE| TRUE |
+----+-----+------+-------+
| 0 | F | F | T |
+----+-----+------+-------+
| >0 | T | F | T |
+----+-----+------+-------+
I would say we shouldnt have mutation in this function
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.
Didn't look through the source code as I think the produced expected testcase doesn't look right. I will re-review the code after.
"replacement_operator" wherever appropriate in unary and binary operator mutations, to improve readability.
@JamesLee-Jones would you be able to take a look again please? |
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 believe there are a couple of cases that have been missed and a couple of small questions but otherwise looks good!
if (std::set({binary_operator_->getOpcode(), replacement_operator}) == | ||
std::set<clang::BinaryOperatorKind>({clang::BO_EQ, clang::BO_GE})) { | ||
// "0 == a" is equivalent to "0 >= a" | ||
return true; | ||
} |
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.
By the fact that 0 == a
is equivalent to 0 >= a
and commutativity, it is also redundant to replace 0 >= a
by 0 == a
for signed integers (which is unlikely but may occur). I think equality on ordered sets compare element wise so this wouldn't currently cover the second case.
if (std::set({binary_operator_->getOpcode(), replacement_operator}) == | ||
std::set<clang::BinaryOperatorKind>({clang::BO_NE, clang::BO_LT})) { | ||
// "0 != a" equivalent to "0 < a" | ||
return true; | ||
} | ||
} |
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.
As above, it is also redundant to replace 0 < a
with 0 != a
if (binary_operator_->getOpcode() == clang::BO_EQ && | ||
replacement_operator == clang::BO_LE) { | ||
// "a == 0" is equivalent to "a <= 0" | ||
return true; | ||
} |
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.
As above, it is also redundant to replace a <= 0
with 0 == a
.
if (binary_operator_->getOpcode() == clang::BO_NE && | ||
replacement_operator == clang::BO_GT) { | ||
// "a != 0" equivalent to "a > 0" | ||
return true; |
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.
As above, it is also redundant to replace a > 0
with a != 0
.
// and "true", respectively. | ||
return true; | ||
} | ||
if (binary_operator_->getOpcode() == clang::BO_EQ && |
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.
Above you used set equality but here used two equality checks, is there any reason?
if (binary_operator_->getOpcode() == clang::BO_NE && | ||
replacement_operator == clang::BO_GT) { |
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.
Above you used set equality but here used two equality checks, is there any reason?
bool MutationReplaceBinaryOperator::IsRedundantReplacementForUnsignedComparison( | ||
clang::BinaryOperatorKind replacement_operator, | ||
const clang::ASTContext& ast_context) const { | ||
if (!binary_operator_->getLHS()->getType()->isUnsignedIntegerType() || |
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.
(1) is it possible for LHS and RHS to have different signedness ? I have thought that one side will be wrapped under implicit cast to match the type, such that we can always sure both side will have same type(and thus same signedness) at this point.
(2) I would suggest checking for signedness in the caller(ie: IsRedundantReplacementForBooleanValuedOperator
) before calling a function named :IsRedundantReplacementForUnsignedComparison
.
} | ||
if (MutationReplaceExpr::ExprIsEquivalentToInt(*binary_operator_->getRHS(), 0, | ||
ast_context)) { | ||
// RHS is 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.
I hypothesize that everything under this if-statement can be made more succinct with the following checking logic
if binary_operator_->getOpcode() in ['<' '>=']:
return replacement_operator not in ['!=', '==']
return True
if (MutationReplaceExpr::ExprIsEquivalentToInt(*binary_operator_->getLHS(), 0, | ||
ast_context)) { | ||
// LHS is 0 | ||
if (replacement_operator == clang::BO_GT || |
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 there's a room for simplification here. See the corresponding comment for RHS is 0
Fixes #240.