May 3rd, 2024

New Checks Since Visual Studio 2022 17.8

Gabor Horvath
Senior Software Engineer

The C++ team is committed to making your C++ coding experience as safe as possible. In the last couple of releases, we added new safety checks based on the requests of internal customers like the Windows group. Below is the overview of the new checks. For additional information for each of the checks, please refer to the linked help documents.

C6395: Helping migration to C++17 and above

Consider the following snippet:

a[++i] = i;

The meaning of the code depends on the order in which we evaluate the subexpressions. In case we evaluate the left-hand side of the assignment first, the code stores the incremented value of i into the array. Otherwise, it stores the original value before the increment. Before C++17, the order of evaluation in this expression is unspecified, and the write and the read of i are not sequenced, resulting in undefined behavior. Starting in C++17 the evaluation order is standardized, and it does not match what MSVC had pre-C++17. As a result, MSVC has a different evaluation order depending on the language mode. A new rule help find cases where changing the language mode will change the meaning of the program.

Checks to find typos

The next checks will find common cases where a typo results in code that compiles but the behavior is probably not what the author intended.

C6392: Error-prone stream usage

What does the following program do?

#include <iostream>

int main() {
     std::cout << L"Foo\n";
}

In this snippet, the string literal will decay to a pointer that points to the beginning of the literal. This pointer points to a wide string. While wcout can print wide strings, cout cannot. The operator<< of std::basic_ostream has an overload that takes a void* argument. The compiler will pick that overload and the user will see the address of the literal on the terminal. This problem can be easily fixed by using wcout or a nonwide string.

C6392 to the rescue, it will find cases where the code inadvertently prints an address instead of the value of a string. In case printing the address is intentional, an explicit cast to void* will make the intent explicit and suppress the warning.

C6396: Error-prone sizeof usage

The following code snippet has a typo in the argument of the sizeof operator, and as a result it does incorrect offset calculations:

#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d

typedef struct {
               int a;
               bool b;
} SOMESTRUCT_THAT_MATTERS;

if (somedata.length >= sizeof(SOMESTRUCT_ERRNO_THAT_MATTERS))  {
    /// Do something
}

C6396 finds typos like this where the argument of sizeof is an integral constant.

C6397: Bogus pointer comparison to null

The address-of operator returns the address of its operand. This value should never be compared to nullptr.

  • The address-of a field can only be nullptr if the base pointer was nullptr and the field is at the zero offset (&p->field == nullptr implies p == nullptr). In this case, the expression should be simplified.
  • In any other cases, the value of the unary & operator can’t be nullptr unless there’s undefined behavior in the code. &v == nullptr always evaluates to false.

C6397 finds typos where & was inadvertently added to null-checking code.

Find InterlockedCompareExchange misuse

C26735: Comparand value read from destination location twice

The InterlockedCompareExchange function and its derivatives such as InterlockedCompareExchangePointer perform an atomic compare-and-exchange operation on the specified values. It is not hard to use these functions, but unfortunately optimizations performed by the compiler can produce machine instructions that were not obvious from the source code. Sometimes, the comparand value that is meant to be read just once from the destination location and used multiple times can really be read multiple times through the optimized machine instructions. Here is an example code:

#include <Windows.h>

bool TryLock(__int64* plock)
{
    __int64 lock = *plock;
    return (lock & 1) &&
        _InterlockedCompareExchange64(plock, lock & ~1, lock) == lock;
}

It seems like we are reading the value of *plock just once. However, when compiled by MSVC with /O1 option, this source code will turn into machine instructions that read the value of *plock multiple times, which makes use of InterlockedCompareExchange pointless.

Please refer to the online help document for the for more details of how this can happen, what consequences of the generated machine instructions might be, and how the source code can be fixed.

C26837 finds above mentioned potential misuses of InterlockedCompareExchange and its derivatives in source code.

Leap year checks

A few leap-day mishandling checks have been added to 17.8. Among these leap year related rules, rules are “opt-in” rules, meaning that code analysis should use a ruleset file, and the rules should be explicitly included in the ruleset file, and enabled for them to be applied. For more information on creating a custom ruleset for code analysis, please refer to Use Rule Sets to Specify the C++ Rules to Run.

C6393 / C6394: Containers with insufficient storage

It is common to use data structures that are indexed by the day of the year. If those data structures have only 365 elements, the code has a bug. Leap years have 366 days. C6393 will find data structures like vectors that are constant and initialized to 365 elements. This warning can falsely flag some code where the size of the data structure is not related to the days of the year, it is merely a coincidence. C6394 is a stricter version of this check that also warns on variables that are not const.

C26861: Modifying field of date-time object without leap year check

If one or more of the year, month, or day fields of a date or a date-time object are changed directly without proper leap year validation, the resultant date or date-time object can become invalid. For example, incrementing a year of a date-time object whose year-month-day fields represent a leap-day will make the resulting date-time object invalid.

C26861 finds direct modifications to year, month, or day field of a date-time object when there is no leap year checking before or after the modification.

C26862: Incomplete conversion of date-time objects

There are many date-time object types, and there is a need to convert from a date-time object of one type to a date-time object of another type. Unfortunately, different date-time object types may use different year, month, or day bases. For example, SYSTEMTIME struct uses a 0-based year, but 1-based month and day fields. On the other hand, tm struct uses a 1900-based year, a 0-based month, and a 1-based day. To convert an object of one of these types to an object of another type, the year, month, and day fields must be adjusted appropriately.

C26862 finds incomplete conversion between objects of SYSTEMTIME and tm structures.

C26863: Ignored return value from date-time handling function

Some functions that create or handle date-time objects return values indicating success or failure. It’s important to verify the return value of such a function, especially without proper leap year handling. Otherwise, the function may have failed, and execution continues with an output value containing invalid data.

C26863 finds ignored return values from a set of known date-time handling functions when there is no leap year checking.

C26864: Day field manipulation assuming 365 days per year

It is a subset of C26861, specifically looking for modifications of the day field of date-time object assuming there are 365 days in all years. To advance a date-time object by a year, either calculate the correct number of days to advance through checking leap year or advance the year field directly and adjust the resultant date-time per leap year rule.

C26864 finds manipulations of date-time objects assuming 365 days per year.

Conclusion

Recent releases of Visual Studio 2022 feature many new checks in our code analysis tools. Give it a try and let us know what you think. The work that we do is heavily influenced by the feedback we receive on the Developer Community. Please file feedback tickets and let us know if there is a check that you would like to see added. 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 X.

Author

Gabor Horvath
Senior Software Engineer

Gabor finished a program analysis Ph.D. in 2020. He is a contributor to research projects related to static analysis since 2012. He is a clang contributor, participated in Google Summer of Code twice as a student and many times as a mentor, interned for Apple, Microsoft and Google. He taught C++ and compiler construction to undergrads at Eotvos Lorand University. Currently, he is working at Microsoft's C++ Static Analysis team to improve MSVC's static analysis capabilities.

2 comments

Discussion is closed. Login to edit/delete existing comments.

  • Georgi Hadzhigeorgiev

    Nice ones, thanks!

  • Larry Newman

    A logical extension of this might be the leap seconds! Thanks G.H.