New Enumeration Checks in Visual Studio 2022 version 17.2 Preview 3

Gabor Horvath

The C++ static analysis team is committed to making your C++ coding experience as safe as possible. We are adding richer code safety checks and addressing high impact customer feedback bugs posted on the C++ Developer Community page. Thank you for engaging with us and giving us great feedback on the past releases and early previews leading to this point. Below is the detailed overview of some new code analysis checks that can detect enumeration misuse errors, along with some improvements to an existing enumeration check.

Overview

We introduced three new checks to find potential misuse of enumerations with bitwise operations. These checks were inspired by real bugs found in production code. We also improved a check that suggests using the C++11 scoped enum instead of its legacy counterpart. See the documentation on how to enable code analysis for your project.

Bitwise enumerations

Some enumerations hold values that are powers of two. Most of these are flags used to represent non-exclusive states that can be combined and queried using bitwise operations. The new checks are trying to detect wrong uses of such enumerations. Unfortunately, there is no clear definition of what enumerations should be considered bitwise, so our checks rely on a set of heuristics to infer the intent from the source code. First of all, let us consider an enum with only two elements:

enum E
{
    A = 1,
    B = 2
};

It is not clear whether E‘s values are intended to form the start of a sequence of powers of two (1,2,4,8,...), or are simply intended to increment by 1 (1,2,3,4,...). Without additional context, we therefore cannot tell whether E is a bitwise enum, intended to be used with bitwise operations, or if it is just a regular enum.

enum E
{
    A = 1,
    B = 2,
    C = 4,
    D = 8,
    ALL = 15
};

Here, the last enum constant is not a power of two. It is a common idiom to define a constant where all of the relevant bits are set. These constants usually have a value of 2^k-1 for some positive k. Our checks will consider enums of this shape as bitwise. We have to be careful with the interplay of these two heuristics.

enum E1
{
    A = 1,
    B = 2,
    C = 3
};

enum E2
{
    A = 1,
    B = 2,
    C = 4
};

In the above example, we do not want to consider E1 bitwise, despite its last element having the value of 2^2-1. On the other hand, we definitely want to consider E2 a bitwise enum.

These heuristics worked reasonably well for the projects we tested our checks on. In case you encounter any false positives or false negatives, please let us know.

C26813

C26813 will find code patterns where a bitwise enumeration’s value is being tested using operator==. In most cases, a variable holding values from a bitwise enumeration represents a bitfield. To query whether a particular enumerator value was set in this variable, bitwise operations should be used. Failing to do so will yield incorrect results:

enum BitWise
{
    A = 1,
    B = 2,
    C = 4
};

void useEqualsWithBitwiseEnum(BitWise a) 
{
    BitWise a = A; // turn on flag A
    a |= B; // turn on flag B

    /*
    *  Attempt to query if flag 'B' was set.
    */

    if (a == B) // Warning C26813: Use 'bitwise and' to check if a flag is set
    {
        foo(); // never reached, since a != B
    }

    if (a & B)
    {
        bar(); // the branch is taken, as expected
    }
}

After running this check on some real world code we also encountered cases where the enum had power of two values, but was never used with bitwise operations. In those cases it might be good idea to change the values of the enum constants to make the intent clearer in the code.

C26827

C26827 can help detect enumerator constants in bitwise enumerations where we forgot to add an initializer. Consider the following example:

enum class AlmostBitWise
{
    A = 1,
    B = 2,
    C = 4,
    D
};

int almostBitwiseEnums(AlmostBitWise a) 
{
    return (int)a|(int)AlmostBitWise::A; // Warning C26827: Did you forget to initialize an enum, or intend to use another type?
}

Here, we use an enum constant in a bitwise operation while not all of its constants have the form of 2^k or 2^k-1. In fact, one of its constants D has the form of 2^k+1. This pattern can happen when we add a new constant without defining its value.

Unfortunately, there is a limitation of this check. It will only work with scoped enums as the compiler will desugar regular enums into integers before the static analysis phase. We are looking into ways to improve our internal representation of the code to have a higher fidelity representation of how the code was written, and this will enable better diagnostics in the future.

C26828

C26828 will flag bitwise expressions where values from different enumerations are mixed. Consider the following example:


enum BitWiseA
{
    A = 1,
    B = 2,
    C = 4
};

enum class BitWiseB
{
    AA = 1,
    BB = 2,
    CC = 4,
    All = 7
};

int overlappingBitwiseEnums(BitWiseA a) 
{
    return (int)a|(int)BitWiseB::AA; // Warning C26828: Different enum types have overlapping values. Did you want to use another enum constant here?
}

Here BitWiseA and BitWiseB have overlapping values. It is unlikely that we wanted to use both in the same bitwise expression. It can be the result of a typo.

This check has similar limitations to C26827.

Improvements to C26812

C26812 suggests to use the new C++11 scoped enums over legacy enums. This check has been the part of our offerings for a really long time but had room for improvement. Previously, it diagnosed legacy enums at their use-sites instead of at their declarations. This had several consequences:

  • We did not emit a warning for unused enumerations.
  • It was not possible to suppress all instances of this warning at the declaration. This was also reported by our users as a Developer Community ticket.
  • It did not work well with the new /external feature. See the corresponding Developer Community ticket for details.
  • The location of the diagnostic is potentially far from the declaration, which makes it harder to address the warning.

The new version of this check will emit the diagnostic on the declarations instead of the uses. This should improve the user experience in most cases. We also realized that addressing this check is sometimes costly or not possible at all (e.g., a library might need to be backward compatible). To address this, we removed this rule from the NativeRecommendedRules ruleset and created a new ruleset named CppCoreCheckEnumRules to include all 4 of the rules we mentioned in this blog post.

Conclusion

The upcoming Visual Studio 2022 17.2 Preview 3 will feature new checks to find hard-to-find misuses of enumerations. It will also include some quality of life improvements for the existing check that can help migrate a code base to scoped enums. The three new enum related checks are part of the NativeRecommendedRules ruleset and the updated warning was moved to CppCoreCheckEnumRules.

Try it out and let us know what you think:

The work that we do is heavily influenced by feedback we receive on Developer Community so thank you again for your participation. Please continue to file feedback and let us know if there is a checker or rule that you would like to see added to C++ Core Check. Stay tuned for more C++ static analysis blogs. In the meanwhile, do not hesitate to reach out to us. We can be reached via the comments below or @VisualC on Twitter.

6 comments

Leave a comment

  • Dwayne Robinson

    > C26812 suggests to use the new C++11 scoped enums over legacy enums. …
    > our checks rely on a set of heuristics …

    Sadly `enum class` isn’t a panacea, as it makes bit flag casts quite tedious, short of hand-written macros or operator overloads (or on Windows `DEFINE_ENUM_FLAG_OPERATORS`). For something so basic and often used at lower levels, which is where C++ targets, support for the concept ought to be in the language itself. Even the higher level C# has the `[Flags]` attribute. 🙃 Has anyone on the VC team considered proposing an attribute to the C++ committee, such as below?

    [[bitflags]] enum Foo {...}

    That would really make the user’s intention clear and obviate the need for heuristics.

    • Gabor HorvathMicrosoft employee

      > Sadly `enum class` isn’t a panacea, as it makes bit flag casts quite tedious

      Agreed. This was one of the main motivation to make scoped enum check is opt-in.

      > Has anyone on the VC team considered proposing an attribute to the C++ committee, such as below?

      I am not aware of such a proposal at the moment, but I think this is a great idea 🙂 I’ll ask around whether someone is interested in writing up a formal proposal about this. I think one possible reason why people are not pushing this because they expect static reflection and compile time code generation to solve some of these problems in the (far?) future.

      Unfortunately, we will always need some heuristics for legacy code.

  • Firas A

    Thanks for continuing to add new checks!

    I’ve just ran into C26827 with the following enum:

    enum class E { A = 1, B = 2, BOTH = 3 }

    The actual line triggering the warning (the real code is all in this file on github, if it makes a difference)

    return other_bool && (static_cast<int>(value) & static_cast<int>(E::A)) != 0;

    I understand that you can’t distinguish between a bitwise enum and a non-bitwise one in the 1, 2, 3 case. But I find the BOTH useful in many cases, as a shortcut for A | B, especially given the verbosity of having to cast whenever I need to express this operation.

    I could ignore the warning or suppress it, but I’m wondering if there’s anything else I’m encouraged to do here.