Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Force compile-time error/warning on implicit fall-through in switch

switch statements can be super useful, but lead to a common bug where a programmer forgot a break statement:

switch(val) {
    case 0:
        foo();
        break;
    case 1:
        bar();
        // oops
    case 2:
        baz();
        break;
    default:
        roomba();
}

You won't get a warning obviously since sometimes fall-through is explicitly desired. Good coding style suggests to comment when your fall-through is deliberate, but sometimes that is insufficient.

I'm pretty sure the answer to this question is no, but: is there any way currently (or proposed in the future) to be able to ask the compiler to throw an error (or at least a warning!) if your case does not have at least one of break; or something to the effect of // fallthru? It would be nice to have a defensive programming option for using switch statements.

like image 892
Barry Avatar asked Jan 15 '15 14:01

Barry


People also ask

Why do switch statements fall through?

Code Inspection: Fallthrough in 'switch' statement Reports a switch statement where control can proceed from a branch to the next one. Such "fall-through" often indicates an error, for example, a missing break or return .

How to suppress gcc warnings?

To answer your question about disabling specific warnings in GCC, you can enable specific warnings in GCC with -Wxxxx and disable them with -Wno-xxxx. From the GCC Warning Options: You can request many specific warnings with options beginning -W , for example -Wimplicit to request warnings on implicit declarations.

What is Werror?

-Werror= Make the specified warning into an error. The specifier for a warning is appended; for example -Werror=switch turns the warnings controlled by -Wswitch into errors.

What is fall through condition in switch case in C?

Fall through is a type of error that occurs in various programming languages like C, C++, Java, Dart …etc. It occurs in switch-case statements where when we forget to add a break statement and in that case flow of control jumps to the next line.


3 Answers

Well clang has -Wimplicit-fallthrough which I did not know about but found by using -Weverything. So for this code it gives me the following warning (see it live):

warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough]
case 2:
^
note: insert '[[clang::fallthrough]];' to silence this warning
case 2:
^
[[clang::fallthrough]]; 
note: insert 'break;' to avoid fall-through
case 2:
^
break; 

The only documentation I can find for this flag is in the Attribute Reference which says:

The clang::fallthrough attribute is used along with the -Wimplicit-fallthrough argument to annotate intentional fall-through between switch labels. It can only be applied to a null statement placed at a point of execution between any statement and the next switch label. It is common to mark these places with a specific comment, but this attribute is meant to replace comments with a more strict annotation, which can be checked by the compiler.

and provides an example of how to mark explicit fall-through:

case 44:  // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55:  // no warning

This use of an attribute to mark explicit fall-through has the disadvantage of not being portable. Visual Studio generate an error and gcc generates the following warning:

warning: attributes at the beginning of statement are ignored [-Wattributes]

which is a problem if you want to use -Werror.

I tried this with gcc 4.9 and it looks like gcc does not support this warning:

error: unrecognized command line option '-Wimplicit-fallthrough'

As of GCC 7, -Wimplicit-fallthrough is supported and __attribute__((fallthrough)) can be used to suppress the warnings when fallthrough is intentional. GCC does recognize "fallthrough" comments in certain scenarios, but it can be confused fairly easily.

I do not see a way of generating such a warning for Visual Studio.

Note, Chandler Carruth explains that -Weverything is not for production use:

This is an insane group that literally enables every warning in Clang. Don't use this on your code. It is intended strictly for Clang developers or for exploring what warnings exist.

but it is useful for figuring out what warnings exist.

C++17 changes

In C++17 we get the attribute [[fallthrough]] covered in [dcl.attr.fallthrough]p1:

The attribute-token fallthrough may be applied to a null statement (9.2); such a statement is a fallthrough statement. The attribute-token fallthrough shall appear at most once in each attribute-list and no attributeargument- clause shall be present. A fallthrough statement may only appear within an enclosing switch statement (9.4.2). The next statement that would be executed after a fallthrough statement shall be a labeled statement whose label is a case label or default label for the same switch statement. The program is ill-formed if there is no such statement.

...

[ Example:
void f(int n) {
void g(), h(), i();
switch (n) {
  case 1:
  case 2:
    g();
    [[fallthrough]];
  case 3: // warning on fallthrough discouraged
    h();
  case 4: // implementation may warn on fallthrough
    i();
    [[fallthrough]]; // ill-formed
  }
}
—end example ]

See live example using attribute.

like image 149
Shafik Yaghmour Avatar answered Oct 03 '22 01:10

Shafik Yaghmour


I always write a break; before each case, as follows:

switch(val) {
    break; case 0:
        foo();
    break; case 1:
        bar();
    break; case 2:
        baz();
    break; default:
        roomba();
}

This way, it is much more obvious to the eye if a break; is missing. The initial break; is redundant I suppose, but it helps to be consistent.

This is a conventional switch statement, I've simply used whitespace in a different way, removing the newline that is normally after a break; and before the next case.

like image 29
Aaron McDaid Avatar answered Oct 03 '22 00:10

Aaron McDaid


Advice: if you consistently put a blank line in between case clauses, the absence of a 'break' becomes more visible to a human skimming the code:

switch (val) {
    case 0:
        foo();
        break;

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}

This isn't as effective when there's a lot of code inside individual case clauses, but that tends to be a bad code smell in itself.

like image 45
zwol Avatar answered Oct 03 '22 01:10

zwol