March 26th, 2020

IntelliSense Code Linter for C++

Kyle Reed
Principal Software Engineer

[Updated Aug 2020 to show the new way of enabling the experimental capability]

In Visual Studio 2019 version 16.6 Preview 2, we’re excited to announce a new preview feature to help C++ developers identify and fix code defects as they write code. The IntelliSense Code Linter for C++ checks your code “as-you-type, underlines problems in the editor, and Lightbulb actions offer suggested fixes.

Linter demo showing a check and applying a suggested fix.

This new feature is built on the existing IntelliSense capabilities for C++ in Visual Studio. This means results are provided more quickly than results from Background Code Analysis. In order to ensure that IntelliSense stays as fast as possible, the linter checks are focused on easily detected issues. The new linter checks complement existing code analysis tools (like Background Code Analysis using MSVC or Clang-Tidy) which handle complex analysis. 

You can try out the linter today by enabling it from the Experimental pane in the Tools > Options menu, under Text Editor > C/C++.

Image shows Tools Options dialog with the Linter option selected

Design Goals

When deciding what would make a good linter check, we kept a few goals in mind.

  • Identify defects that surprise C++ developers – especially polyglot developers coming from other languages. The linter checks will smooth the learning curve for some C++ language features.
  • Offer suggested fixes for the problems. Just as IntelliSense errors can help you identify when code will fail to build due to syntactic errors, the linter helps you identify and fix logic and runtime errors before the code even builds.
  • Identify problems that make sense to fix. This ends up being a matter of code style and philosophy, but in general they’re focused on the kinds of defects that cause real bugs.

New Checks

With these goals in mind, we have implemented the following checks in Preview 2.

Arithmetic Overflow

This check finds cases where arithmetic is evaluated with 32-bit types and then assigned to a wider type. Assigning to a wider type is a good indication that the developer expected the expression value to exceed the range of a 32-bit type. In C++ the expression will be evaluated as 32-bit, which may overflow, and then widened for assignment.

Arithmetic Overflow Check and Suggested Fix.

Integer Division Assigned to Floating Point

This check finds places where integer division is assigned to a floating-point type. Assigning to a floating-point type is a good indication that the developer wanted the fractional part of the result. In C++, the integer division will be evaluated, and the fractional part will be truncated before the result is assigned to the floating-point type.

Int Division to Float Check and Suggested Check

Logical/Bitwise Mismatch

This check finds cases where logical operators are used with integer values or using bitwise operators with Boolean values. C++ allows this because of implicit conversions, but the practice is error prone and hurts code readability.

Logical Bitwise Mismatch Check and Suggested Fix.

Assignment/Equality Mismatch

Using the assignment operator in conditional expressions is syntactically correct but may be a logical error. This check looks for cases where variables are being assigned from constants in conditions. This is almost always incorrect because it forces the condition to always be true or false.

Assignment Equality Mismatch Check and Suggested Fix.

Accidental Copy

The auto keyword in C++ is a great feature, especially when interacting with templated code. It has one subtle behavior that can be confusing or easily overlooked by C++ developers of all skill levels. auto does not deduce references so in cases where a declared variable is being assigned from an expression that returns a reference, a copy is made. This isn’t always a bug, but we wanted to help developers be aware that a copy is being made, when maybe it wasn’t desired.

Accidental Copy Check and Suggested Fix.

Uninitialized Local

Primitive variables in C++ are not initialized to any value by default. This can lead to non-deterministic behaviors at runtime. The current implementation of this check is very aggressive and will warn on any declaration that doesn’t have an initializer.

Uninitialized Local check and suggested fix.

Coming Soon

The new linter is still a work in progress, but we are excited to be able to ship a preview release that developers can try out. Here are a few features that will be coming in future releases. 

  • Configuration: currently the feature is either enabled or disabled. There isn’t a way in the current version to enable/disable or change the severity of individual checks. 
  • Suppression: many code analysis tools can suppress warnings on a per-instance basis. This is typically done in the source code with an annotation (e.g. #pragma or a code comment). The linter does not support suppression currently. 
  • Integration with other code analysis tools: the linter checks only run in the IDE so currently cannot be used as part of a CI or build. The compiler-based tools (MSVC and Clang-Tidy) should continue to be used. When Background Code Analysis is enabled, you may get green squiggles from MSVC or Clang-Tidy that overlap with the linter results in the editor.  

We’ve been working hard to make an editor that helps developers “shift left” and find bugs earlier in the development loop. We hope that you find the new IntelliSense Code Linter for C++ useful. Please try it out and let us know what you think. We can be reached via the comments below, email (visualcpp@microsoft.com), and Twitter (@VisualC). The best way to file a bug or suggest a feature is via Developer Community. Happy coding!

Author

Kyle Reed
Principal Software Engineer

Joined Microsoft in 2007. Worked on SharePoint, Security, C++ Static Analysis, and C++ Language Services.

7 comments

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

    • Mike Diack

      Agreed - this is REALLY poor Microsoft. The UI option is not there, nor is the function switched on by default, a simple one liner test (e.g. if (i=0) {} )of this fails.
      What is up with your QA.

      This tells me you didn't even do ONE single test of this particular aspect of the 16.6.0 release, to fail so spectacularly.
      Me and many others have bugged VS programme and project management. This is really poor...

      Read more
      • Alastair Taylor

        I am assuming this is only available in the preview releases (not the main ones) like Terminal.

  • David Lowndes

    When you say for the uninitialized local test LNT1006: "The current implementation of this check is very aggressive and will warn on any declaration that doesn’t have an initializer"; it is always producing false positives for every occurrence I've looked at it.
    If you can be more intelligent about it such that you can see it's not written to prior to being read, then keep it; otherwise, it's just an annoyance in (I'd guess) most...

    Read more
  • steve heller

    It might be nice to use examples that don’t continue to cause confusion when corrected.
    E.g., “bool y = b || !b;” is the same as “bool y = true;”. Why would I write the former?
    I would never write “for (auto string : strings)” because “string” is a class name. I’m not saying it wouldn’t work but it is still confusing.

  • shbouwhuis@gmail.com

    Is v16.6 close to being released?