Otzen
Otzen

Reputation: 550

Catch mixed enums in switch

In some legacy code I have a lot of enums, and a huge switch cases. I would like to test that the switches have pure enum types. Nonsense example:

typedef enum EN
{
    EN_0,
    EN_1
} EN_T;

typedef enum DK
{
    DK_0,
    DK_1
} DK_T;

EN_T bar = ...
switch( bar )
{
    case EN_0:
    ...
    break;
    case DK_1: //<-- mixed type
    ...
    break;
}

I tried compiling this with gcc with -Wall -Wextra -pedantic, and get no warnings. Any ideas of how to test for this? Either as compiler warnings or dedicated test code. As both the switches and enums have 100+ members it has to be generic to some level.

Edit: Please notice I am not concerned about if this is legal c, according to the C standard.

It is bad practice, and compiler can warn about bad practice or potential errors that do not break the standard, like if( a = 1)... would always be true, perfectly legal but likely to be a mistake.

I can make the compiler warn if a switch on an enum does not contain all values of that enum a.s.o.

It is preferred if the compiler can to the work, but if a tool like lint or similar can do this I would be happy too.

Upvotes: 11

Views: 1736

Answers (5)

Otzen
Otzen

Reputation: 550

Well I will answer it myself. After some more research i conclude that at least gcc will not complain about this, I need to use an extra program like pc-lint.

I made a slight rewrite to emphasis the problem.

#include <stdio.h>

typedef enum EN
{
    ZERO,
    ONE
} EN_T;

typedef enum DK
{
    EN,  /* Danish word for One */
    TO  /* Danish word for Two */
} DK_T;

char* E2str(  EN_T en )
{
    char* ret;
    switch( en )
    {
        case ZERO:
            ret = "0";
        break;
        case TO:
            ret = "2";
        break;
    }
    return ret;
}
int main( void )
{
    printf( "0 = %s\n", E2str( ZERO ) );
    printf( "1 = %s\n", E2str( ONE ) );
    return 0;
}

This will compile fine, with no warnings even with:

gcc -o t.exe t.c -Wall -Wextra -pedantic

The output will be:

0 = 0
1 = 2

It is clear that this output was probably not the intention of the writer. And yes in this small example it is clear and obvious when just looking at the code. But imagine this being a switch with 200+ cases, and the switch contains other switches, and the naming of the enum is not as clear as in my example in the original question. It becomes close to impossible to spot errors like the one in this example.

Also note that by using -Wextra I enable a check in gcc that will warn if I have a switch on an enum, and the cases does not contain all the values in that enum. But because the TO enum has the sane numeric value as ONE, gcc doesn't even complain about missing Enums in switch, apparently it does only look at the numeric value, and not the provided enum for this check.

My test with pc-lint, spotted both

    --- Module:   t.c (C)
                   _
            case TO:
    t.c  23  Warning 408: Type mismatch with switch expression
        _
        }
    t.c  26  Info 787: enum constant 'EN::ONE' not used within switch

Unfortunately this was not the answer I was hoping for, it would be so much nicer to have this done by the compiler, and not by yet another tool.

Still open to give someone else the credit for an better answer.

Upvotes: 0

quetzalcoatl
quetzalcoatl

Reputation: 33506

case xxx is simple keyword with not-that-hard typical syntax. When in pinch, it should be possible to catch most occurrences it by regular expressions. First candidate for expression that comes to me is something like

(^|\s)case\s+[^:]+:
             \---/anything terminated by colon
\----/drop things like 'uppercase'

This would catch most, if not all, typical occurrences of a case keyword though the file. Then, detect switch keywords:

)\s*{\s*case\s

That should do it. Although it wouldn't look for switch keyword, it detects first closing-parenthesis that is before first case. IMHO, close enough and should work in most cases.

Being able to detect case and switch and their location, you can group cases by their preceding switch, and perform validation of case values.

That of course means you would have to write a small utility that would do that, but for me it sounds like 50-100 lines of non-minimified code.

That way of course will not handle things like:

// macros:
#define SAFE_SWITCH(x) switch(assert_non_negative(x)){
#define SWITCH_END     }

SAFE_SWITCH(..) case BAR: .... SWITCH_END

// clever hacks from bored programmers:
switch(parser.nodetype)
{
    default: throw ..;

    #include "opcodes.inc"
    #include "operands.inc"
    #include "keywords.inc"
}

etc. so that's not a perfect solution, but if your switch/case are 'clean' (no such macros, and so on) then it's worth considering.

Upvotes: 0

pmf
pmf

Reputation: 7749

From the documentation on -Wswitch-enum (assuming you are using GCC): "case labels outside the enumeration range also provoke warnings when this option is used." AFAICK, this switch is not enabled by -Wall or -Wextra.

Upvotes: 0

user2736738
user2736738

Reputation: 30926

From standard there is only one constraint so far case labeled statement

The expression of each case label shall be an integer constant expression and no two of the case constant expressions in the same switch statement shall have the same value after conversion.

As long as it is an integer constant expression it doesn't matter whether they belong to different enums or not. So yes you can't do what you want in C.

Upvotes: 3

Bathsheba
Bathsheba

Reputation: 234685

No, you can't restrict switch case labels to the explicit values of a particular enum. (You can in C++ out of interest from C++11).

If you are able to change the enum values so they don't intersect, that might help you a little, but only at runtime.

Upvotes: 7

Related Questions