Code Analysis Improvements in Visual Studio 17.6

Gabor Horvath

The C++ 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 the improvements we made to our code analysis tools.

Improvements to existing checks

We improved many checks to find more errors and emit fewer false positives. This section contains some highlights of the work we did over the past months.

Unwrapping empty std::optionals

The initial version of C26830, which checks for the safe unwrapping of std::optional, did not support the operator== free function and reported a false C26830 for the code below:

int f(std::optional<int> o) {
  if (o == std::nullopt)
      return 42;
  // Previously, C26830 was emitted below.
  return *o;
}

After our improvements the analyzer no longer warns on the code above. This addition can also help finding more problems when a condition is accidentally inverted:

int f(std::optional<int> o) {
  if (o != std::nullopt)
    return 42;
  return *o; // C26829 emitted.
}

We relaxed C26829 and C26830 to not warn when the value method is invoked on a potentially empty std::optional. Calling value on an empty optional will raise an exception, which some users prefer to catch instead of checking for emptiness upfront. When this exception is unacceptable, we introduced C26859 and C26860, which forces an explicit null-check, guaranteeing that no exception is thrown. This was a request from this DevCom ticket.

Support conditional moves

Some APIs in the STL will consume an argument in some cases but leave it unchanged in others. Previously, the use use after move check assumed that these select APIs will always consume the argument. We improved the check to better understand some commonly used APIs in the STL and avoid emitting false positives. For example, the following code snippet will no longer trigger any warnings:

int f() {
  std::map<int, MyType> myMap;
  MyType val;
  auto emplaceResult = myMap.try_emplace(1, std::move(val));
  if (!emplaceResult.second) { // val was not inserted into the map
    val.method(); // Previously, C26800 was emitted.
  }
}

In this code snippet, when the second element of the returned pair is false, val was not consumed by try_emplace. In that scenario, it is safe to use val. This problem was reported in a DevCom ticket.

Other improvements

We also made many smaller improvements to other existing checks including:

Improvements to the code analysis engine

Better modeling for std::pair and std::swap

Our engine analyzes every function in isolation. Consequently, the analyzer does not always have the necessary information about other functions in order to deduce certain facts. Doing function-local analysis was a deliberate design decision to ensure good performance and scalability in our checks. To mitigate the loss of precision, we often add explicit modeling to widely used constructs like pair, rely on type information like gsl::not_null, or annotations like SAL. Consider the following code:

void f(std::optional<int> opt) {
  auto p = std::make_pair(opt, opt.has_value());
  if (!p.second)
    *p.first = 42; // dereferencing an empty optional! C26829 emitted.
}

Since the analyzer does not look into the definition of std::make_pair, it previously did not understand that this function will store its second argument into the second field of the returned object. As a result, a high-confidence warning was never emitted when p is dereferenced. As of 17.6, we have added explicit modeling of std::pair and its related methods, so that the analyzer will be able catch more bugs such as in the example above, and emit fewer false positives, all while maintaining local static analysis.

The std::swap function received similar treatment. We will correctly diagnose the problem in the code snippet below:

void f() {
  std::optional<int> opt{2};
  std::optional<int> opt2{};
  std::swap(opt, opt2);
  *opt = 42;  // C26829 emitted.
}

Better support for suppression attributes

Previously [[gsl::suppress]] did not work on declarations. This is now fixed so the following code works as expected:

void f(std::optional<int> o) {
  if (o)
    return;
  [[gsl::suppress(26829)]]
  int v = *o; // No longer raise C26829 for unwrapping the empty optional
}

Other fixes

We worked on many other fixes, including:

  • Better support for if constexpr
  • Fixed a problem with modeling while loops reported in this DevCom ticket
  • More precise modeling for temporary objects
  • The engine now understand that the standard throwing operator new will not return null pointers
  • Better modeling for initializer expressions
  • Made the source locations of some warnings more precise

Conclusion

Visual Studio 2022 17.6 features many improvements to our code analysis tools. Give it a try and let us know what you think. The work that we do is heavily influenced by feedback we receive on the 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.

1 comment

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

  • Zura For Reg 0

    It breaks C++ intellisense for C++ projects using v141 (vs2017) toolchain. Reverted to VS 17.4.5, where intellisense works fine.

Feedback usabilla icon