Skip to content
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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

afd
Copy link
Member

@afd afd commented Jul 16, 2024

Fixes #240.

Copy link
Collaborator

@JamesLee-Jones JamesLee-Jones left a 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).

Copy link
Collaborator

@JonathanFoo0523 JonathanFoo0523 left a 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)?

test/single_file/unsigned_comparisons_with_zero.c.expected Outdated Show resolved Hide resolved
src/libdredd/src/mutation_replace_binary_operator.cc Outdated Show resolved Hide resolved
src/libdredd/src/mutation_replace_binary_operator.cc Outdated Show resolved Hide resolved
test/single_file/unsigned_comparisons_with_zero.c.expected Outdated Show resolved Hide resolved
@afd
Copy link
Member Author

afd commented Jul 17, 2024

@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.

@afd
Copy link
Member Author

afd commented Jul 17, 2024

@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.

@afd afd requested a review from JonathanFoo0523 July 17, 2024 07:44
@afd
Copy link
Member Author

afd commented Jul 17, 2024

@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).

@afd
Copy link
Member Author

afd commented Jul 17, 2024

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.

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;
Copy link
Collaborator

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

Copy link
Member Author

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.

Copy link
Collaborator

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;
Copy link
Collaborator

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

Copy link
Member Author

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.

Copy link
Collaborator

@JonathanFoo0523 JonathanFoo0523 Jul 18, 2024

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

Copy link
Collaborator

@JonathanFoo0523 JonathanFoo0523 left a 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.

@afd
Copy link
Member Author

afd commented Jul 18, 2024

@JamesLee-Jones would you be able to take a look again please?

Copy link
Collaborator

@JamesLee-Jones JamesLee-Jones left a 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!

Comment on lines +1092 to +1096
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;
}
Copy link
Collaborator

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.

Comment on lines +1097 to +1102
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;
}
}
Copy link
Collaborator

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

Comment on lines +1118 to +1122
if (binary_operator_->getOpcode() == clang::BO_EQ &&
replacement_operator == clang::BO_LE) {
// "a == 0" is equivalent to "a <= 0"
return true;
}
Copy link
Collaborator

@JamesLee-Jones JamesLee-Jones Jul 18, 2024

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.

Comment on lines +1123 to +1126
if (binary_operator_->getOpcode() == clang::BO_NE &&
replacement_operator == clang::BO_GT) {
// "a != 0" equivalent to "a > 0"
return true;
Copy link
Collaborator

@JamesLee-Jones JamesLee-Jones Jul 18, 2024

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 &&
Copy link
Collaborator

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?

Comment on lines +1123 to +1124
if (binary_operator_->getOpcode() == clang::BO_NE &&
replacement_operator == clang::BO_GT) {
Copy link
Collaborator

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() ||
Copy link
Collaborator

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
Copy link
Collaborator

@JonathanFoo0523 JonathanFoo0523 Jul 18, 2024

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 ||
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Avoid equivalent mutation to unsigned int
3 participants