mc-imperial / dredd

Framework for evaluating C/C++ compiler testing tools
Apache License 2.0
11 stars 3 forks source link

Delete-statement mutation not applied for first switch-case statement #257

Closed JonathanFoo0523 closed 1 month ago

JonathanFoo0523 commented 1 month ago

I noted that dredd doesn't apply delete-statement mutation for the first statement of case/default in a switch statement.

This function:

char* foo() {
  int x = 10;
  switch (x) {
  case 2:
    return "bar"; 
  default:
    return "baz"; 
  }
}

is mutated to something as:

char* foo() {
  int x = __dredd_replace_expr_int_constant(10, 0);
  if (!__dredd_enabled_mutation(13)) { switch (__dredd_replace_expr_int(__dredd_replace_expr_int_lvalue(&(x), 5), 7)) {
  case 2:
    return "bar";
  default:
    return "baz";
  } }
}

Is there any reason we don't mutate it to something as follow?

char* foo() {
  int x = __dredd_replace_expr_int_constant(10, 0);
  if (!__dredd_enabled_mutation(13)) { switch (__dredd_replace_expr_int(__dredd_replace_expr_int_lvalue(&(x), 5), 7)) {
  case 2:
    if(!__dredd_enabled_mutation()) { return "bar"; }
  default:
    if(!__dredd_enabled_mutation()) { return "baz"; }
  } }
}
afd commented 1 month ago

I wonder if it comes from here:

https://github.com/mc-imperial/dredd/blob/842649b4a43e9bb10db7d0e88653a51dbf3533cb/src/libdredd/src/mutate_visitor.cc#L550

Perhaps a switch statement is a kind of compound statement.

JonathanFoo0523 commented 1 month ago

If that's the case, then clearly the sub-statement is not considered for removal.

JonathanFoo0523 commented 1 month ago

I believe this is a bug. Perhaps a better example is:

int main() {
  int x = 10;
  switch (x) {
  case 2:
    printf("bar\n");
    printf("buz\n");
  default:
    printf("baz\n");
  }
}

which dredd mutate to:

int main() {
  int x = __dredd_replace_expr_int_constant(10, 0);
  if (!__dredd_enabled_mutation(26)) { switch (__dredd_replace_expr_int(__dredd_replace_expr_int_lvalue(&(x), 5), 7)) {
  case 2:
    __dredd_replace_expr_int(printf("bar\n"), 13);
    if (!__dredd_enabled_mutation(19)) { printf("buz\n"); }
  default:
    __dredd_replace_expr_int(printf("baz\n"), 20);
  } }
}

Note that: (a) The first statement under case/default is not considered for removal (b) The printf statement under case/default is treated as int expression

afd commented 1 month ago

Interesting.

First, printf does have return type int, so it's correct in principle to wrap a printf call in __dredd_replace_expr_int. The reason this doesn't happen to printf("buz\n") appears to be due to an optimisation performed by Dredd. If I run with --no-mutation-opts I get this code:

int main() {
  int x = __dredd_replace_expr_int(10, 0);
  if (!__dredd_enabled_mutation(33)) { switch (__dredd_replace_expr_int(__dredd_replace_expr_int_lvalue(&(x), 6), 8)) {
  case 2:
    __dredd_replace_expr_int(printf("bar\n"), 14);
    if (!__dredd_enabled_mutation(26)) { __dredd_replace_expr_int(printf("buz\n"), 20); }
  default:
    __dredd_replace_expr_int(printf("baz\n"), 27);
  } }
}

In this case, all of the printf statements are getting wrapped in __dredd_replace_expr_int.

What is confusing then is why the first statements in each case and default are getting treated differently from the rest.

I think I can see what is going on, via the --dump-asts option, which gives this:

`-FunctionDecl 0x57061dd005f0 </home/afd/dev/dredd/tomutate.c:3:1, line:12:1> line:3:5 main 'int ()'
  `-CompoundStmt 0x57061dd00b10 <col:12, line:12:1>
    |-DeclStmt 0x57061dd00738 <line:4:3, col:13>
    | `-VarDecl 0x57061dd006b0 <col:3, col:11> col:7 used x 'int' cinit
    |   `-IntegerLiteral 0x57061dd00718 <col:11> 'int' 10
    `-SwitchStmt 0x57061dd00788 <line:5:3, line:11:3>
      |-ImplicitCastExpr 0x57061dd00770 <line:5:11> 'int' <LValueToRValue>
      | `-DeclRefExpr 0x57061dd00750 <col:11> 'int' lvalue Var 0x57061dd006b0 'x' 'int'
      `-CompoundStmt 0x57061dd00ae8 <col:14, line:11:3>
        |-CaseStmt 0x57061dd007e8 <line:6:3, line:7:19>
        | |-ConstantExpr 0x57061dd007d0 <line:6:8> 'int'
        | | `-IntegerLiteral 0x57061dd007b0 <col:8> 'int' 2
        | `-CallExpr 0x57061dd008e0 <line:7:5, col:19> 'int'
        |   |-ImplicitCastExpr 0x57061dd008c8 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
        |   | `-DeclRefExpr 0x57061dd00810 <col:5> 'int (const char *, ...)' Function 0x57061dce8200 'printf' 'int (const char *, ...)'
        |   `-ImplicitCastExpr 0x57061dd00920 <col:12> 'const char *' <NoOp>
        |     `-ImplicitCastExpr 0x57061dd00908 <col:12> 'char *' <ArrayToPointerDecay>
        |       `-StringLiteral 0x57061dd00868 <col:12> 'char[5]' lvalue "bar\n"
        |-CallExpr 0x57061dd009a8 <line:8:5, col:19> 'int'
        | |-ImplicitCastExpr 0x57061dd00990 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
        | | `-DeclRefExpr 0x57061dd00938 <col:5> 'int (const char *, ...)' Function 0x57061dce8200 'printf' 'int (const char *, ...)'
        | `-ImplicitCastExpr 0x57061dd009e8 <col:12> 'const char *' <NoOp>
        |   `-ImplicitCastExpr 0x57061dd009d0 <col:12> 'char *' <ArrayToPointerDecay>
        |     `-StringLiteral 0x57061dd00958 <col:12> 'char[5]' lvalue "buz\n"
        `-DefaultStmt 0x57061dd00ac8 <line:9:3, line:10:19>
          `-CallExpr 0x57061dd00a70 <col:5, col:19> 'int'
            |-ImplicitCastExpr 0x57061dd00a58 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
            | `-DeclRefExpr 0x57061dd00a00 <col:5> 'int (const char *, ...)' Function 0x57061dce8200 'printf' 'int (const char *, ...)'
            `-ImplicitCastExpr 0x57061dd00ab0 <col:12> 'const char *' <NoOp>
              `-ImplicitCastExpr 0x57061dd00a98 <col:12> 'char *' <ArrayToPointerDecay>
                `-StringLiteral 0x57061dd00a20 <col:12> 'char[5]' lvalue "baz\n"

I'm going to do some more investigation, but it seems like case and default statements are not being wrapped due to code at line 543 of mutate_visitor.cc.

I'll do some more digging.