Using clang-tidy and clang-format

Date Sat 23 April 2016 By Emmanuel Fleury Category programming

The LLVM framework produces quite good outcomes, especially when it comes to development tools. My last discovery are the tools clang-tidy and clang-format, which, respectively, helps to perform static-analysis on a project and to enforce the coding style among the developers. I will summarize what I found and explain how to integrate it easily in a project using CMake.

Using clang-tidy

clang-tidy is not really a novelty for somebody which is used to clang-analyzer. In fact, it is just a more convenient front-end to the well known clang static-analyzer. But, it worth using it to quickly scan your sources looking for the bugs that may be discovered by these checks.

Basically, you have to provide clang-tidy with the files you want to check, a few options about the checks you want to perform and some hints on compile options you will use. More precisely it will look like this for a C++ project:

$> clang-tidy Example.[ch]pp -checks=* -- -std=c++11 -I/usr/include/c++/5/
2574 warnings generated.
3852 warnings generated.
Example.cpp:37:1: warning: #includes are not sorted properly [llvm-include-order]
#include <sstream>
^
Suppressed 3851 warnings (3851 in non-user code).
Use -header-filter='.*' to display errors from all non-system headers.

Here, I applied all the possible checks by giving the -checks=* option. We can list the checks used by giving the option -list-checks like this:

$> clang-tidy Example.[ch]pp -list-checks -checks=* -- -std=c++11 -I/usr/include/c++/5/
Enabled checks:
    clang-analyzer-alpha.core.BoolAssignment
    clang-analyzer-alpha.core.CallAndMessageUnInitRefArg
    clang-analyzer-alpha.core.CastSize
    clang-analyzer-alpha.core.CastToStruct
    clang-analyzer-alpha.core.FixedAddr
    clang-analyzer-alpha.core.IdenticalExpr
    clang-analyzer-alpha.core.PointerArithm
    clang-analyzer-alpha.core.PointerSub
    clang-analyzer-alpha.core.SizeofPtr
    clang-analyzer-alpha.core.TestAfterDivZero
    clang-analyzer-alpha.cplusplus.VirtualCall
    clang-analyzer-alpha.deadcode.UnreachableCode
    clang-analyzer-alpha.osx.cocoa.Dealloc
    clang-analyzer-alpha.osx.cocoa.DirectIvarAssignment
    clang-analyzer-alpha.osx.cocoa.DirectIvarAssignmentForAnnotatedFunctions
    clang-analyzer-alpha.osx.cocoa.InstanceVariableInvalidation
    clang-analyzer-alpha.osx.cocoa.MissingInvalidationMethod
    clang-analyzer-alpha.security.ArrayBound
    clang-analyzer-alpha.security.ArrayBoundV2
    clang-analyzer-alpha.security.MallocOverflow
    clang-analyzer-alpha.security.ReturnPtrRange
    clang-analyzer-alpha.security.taint.TaintPropagation
    clang-analyzer-alpha.unix.Chroot
    clang-analyzer-alpha.unix.MallocWithAnnotations
    clang-analyzer-alpha.unix.PthreadLock
    clang-analyzer-alpha.unix.SimpleStream
    clang-analyzer-alpha.unix.Stream
    clang-analyzer-alpha.unix.cstring.BufferOverlap
    clang-analyzer-alpha.unix.cstring.NotNullTerminated
    clang-analyzer-alpha.unix.cstring.OutOfBounds
    clang-analyzer-core.CallAndMessage
    clang-analyzer-core.DivideZero
    clang-analyzer-core.DynamicTypePropagation
    clang-analyzer-core.NonNullParamChecker
    clang-analyzer-core.NullDereference
    clang-analyzer-core.StackAddressEscape
    clang-analyzer-core.UndefinedBinaryOperatorResult
    clang-analyzer-core.VLASize
    clang-analyzer-core.builtin.BuiltinFunctions
    clang-analyzer-core.builtin.NoReturnFunctions
    clang-analyzer-core.uninitialized.ArraySubscript
    clang-analyzer-core.uninitialized.Assign
    clang-analyzer-core.uninitialized.Branch
    clang-analyzer-core.uninitialized.CapturedBlockVariable
    clang-analyzer-core.uninitialized.UndefReturn

    ... 8< ...

    llvm-header-guard
    llvm-include-order
    llvm-namespace-comment
    llvm-twine-local
    misc-argument-comment
    misc-bool-pointer-implicit-conversion
    misc-swapped-arguments
    misc-undelegated-constructor
    misc-uniqueptr-reset-release
    misc-unused-raii
    misc-use-override
    readability-braces-around-statements
    readability-function-size
    readability-redundant-smartptr-get

Note that, we do not want to use the clang-analyzer-alpha.* checks (because they are still in alpha) and that this llvm-include-order is just annoying because it does not match our way of doing. So, lets remove all these by adding some restrictions in the command line options (restrictions are starting with a ‘-):

$> clang-tidy Example.[ch]pp -list-checks -checks=*,-clang-analyzer-alpha.*,-llvm-include-order -- -std=c++11 -I/usr/include/c++/5/
Enabled checks:
    clang-analyzer-core.CallAndMessage
    clang-analyzer-core.DivideZero
    clang-analyzer-core.DynamicTypePropagation
    clang-analyzer-core.NonNullParamChecker
    clang-analyzer-core.NullDereference
    clang-analyzer-core.StackAddressEscape
    clang-analyzer-core.UndefinedBinaryOperatorResult
    clang-analyzer-core.VLASize
    clang-analyzer-core.builtin.BuiltinFunctions
    clang-analyzer-core.builtin.NoReturnFunctions
    clang-analyzer-core.uninitialized.ArraySubscript
    clang-analyzer-core.uninitialized.Assign
    clang-analyzer-core.uninitialized.Branch
    clang-analyzer-core.uninitialized.CapturedBlockVariable
    clang-analyzer-core.uninitialized.UndefReturn
    clang-analyzer-cplusplus.NewDelete
    clang-analyzer-cplusplus.NewDeleteLeaks
    clang-analyzer-deadcode.DeadStores
    clang-analyzer-llvm.Conventions
    clang-analyzer-osx.API
    clang-analyzer-osx.SecKeychainAPI
    clang-analyzer-osx.cocoa.AtSync
    clang-analyzer-osx.cocoa.ClassRelease
    clang-analyzer-osx.cocoa.IncompatibleMethodTypes
    clang-analyzer-osx.cocoa.Loops
    clang-analyzer-osx.cocoa.MissingSuperCall
    clang-analyzer-osx.cocoa.NSAutoreleasePool
    clang-analyzer-osx.cocoa.NSError
    clang-analyzer-osx.cocoa.NilArg
    clang-analyzer-osx.cocoa.NonNilReturnValue
    clang-analyzer-osx.cocoa.RetainCount
    clang-analyzer-osx.cocoa.SelfInit
    clang-analyzer-osx.cocoa.UnusedIvars
    clang-analyzer-osx.cocoa.VariadicMethodTypes
    clang-analyzer-osx.coreFoundation.CFError
    clang-analyzer-osx.coreFoundation.CFNumber
    clang-analyzer-osx.coreFoundation.CFRetainRelease
    clang-analyzer-osx.coreFoundation.containers.OutOfBounds
    clang-analyzer-osx.coreFoundation.containers.PointerSizedValues
    clang-analyzer-security.FloatLoopCounter
    clang-analyzer-security.insecureAPI.UncheckedReturn
    clang-analyzer-security.insecureAPI.getpw
    clang-analyzer-security.insecureAPI.gets
    clang-analyzer-security.insecureAPI.mkstemp
    clang-analyzer-security.insecureAPI.mktemp
    clang-analyzer-security.insecureAPI.rand
    clang-analyzer-security.insecureAPI.strcpy
    clang-analyzer-security.insecureAPI.vfork
    clang-analyzer-unix.API
    clang-analyzer-unix.Malloc
    clang-analyzer-unix.MallocSizeof
    clang-analyzer-unix.MismatchedDeallocator
    clang-analyzer-unix.cstring.BadSizeArg
    clang-analyzer-unix.cstring.NullArg
    google-build-explicit-make-pair
    google-build-namespaces
    google-build-using-namespace
    google-explicit-constructor
    google-readability-braces-around-statements
    google-readability-casting
    google-readability-function
    google-readability-function-size
    google-readability-namespace-comments
    google-readability-redundant-smartptr-get
    google-readability-todo
    google-runtime-int
    google-runtime-member-string-references
    google-runtime-memset
    google-runtime-operator
    llvm-header-guard
    llvm-namespace-comment
    llvm-twine-local
    misc-argument-comment
    misc-bool-pointer-implicit-conversion
    misc-swapped-arguments
    misc-undelegated-constructor
    misc-uniqueptr-reset-release
    misc-unused-raii
    misc-use-override
    readability-braces-around-statements
    readability-function-size
    readability-redundant-smartptr-get

Then, we can run a check again (just by removing the -check-list option):

$> clang-tidy Example.[ch]pp -checks=*,-clang-analyzer-alpha.*,-llvm-include-order -- -std=c++11 -I/usr/include/c++/5/
2533 warnings generated.
3786 warnings generated.
Suppressed 3786 warnings (3786 in non-user code).
Use -header-filter='.*' to display errors from all non-system headers.

So, we have reached a good setting, lets dump a config file and include it into our project. For this, we will use the -dump-config option:

$> clang-tidy Example.[ch]pp -dump-config -checks=*,-clang-analyzer-alpha.*,-llvm-include-order -- -std=c++11 -I/usr/include/c++/5/
---
Checks:          '*,-clang-analyzer-alpha.*,-llvm-include-order,*,-clang-analyzer-alpha.*,-llvm-include-order'
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: true
User:            perror
CheckOptions:
  - key:             google-readability-braces-around-statements.ShortStatementLines
    value:           '1'
  - key:             google-readability-function-size.BranchThreshold
    value:           '4294967295'
  - key:             google-readability-function-size.LineThreshold
    value:           '4294967295'
  - key:             google-readability-function-size.StatementThreshold
    value:           '800'
  - key:             google-readability-namespace-comments.ShortNamespaceLines
    value:           '10'
  - key:             google-readability-namespace-comments.SpacesBeforeComments
    value:           '2'
  - key:             llvm-namespace-comment.ShortNamespaceLines
    value:           '1'
  - key:             llvm-namespace-comment.SpacesBeforeComments
    value:           '1'
  - key:             readability-braces-around-statements.ShortStatementLines
    value:           '0'
  - key:             readability-function-size.BranchThreshold
    value:           '4294967295'
  - key:             readability-function-size.LineThreshold
    value:           '4294967295'
  - key:             readability-function-size.StatementThreshold
    value:           '800'
...

I do not know why but the checks are repeated twice… Well, probably a small bug. So, we just have to write this in .clang-tidy in our project root directory and, now, we just have to run part of the previous command with the option -config='' to get the content of the .clang-tidy file as default configuration file:

$> clang-tidy Example.[ch]pp -config='' -- -std=c++11 -I/usr/include/c++/5/
2533 warnings generated.
3786 warnings generated.
Suppressed 3786 warnings (3786 in non-user code).
Use -header-filter='.*' to display errors from all non-system headers.

And, that is about all! Except that the number of checks is growing fast from one version of clang-tidy to another. So, you will benefit of these new checks to improve your code as long as it does not interfere with your way of coding in your project. And, if it does, just filter out the checks you do not want as I showed previously.

Discovering clang-format

This tool is somehow similar to the famous indent command (old, but still quite useful). It provides a way to enforce a coding style in an automatic way.

The program works in a very similar manner as clang-tidy you first need to generate a coding style specification based on a given profile (available profiles are currently: LLVM, Google, Chromium, Mozilla and WebKit). And, then, you can start customizing it.

Lets first look at the LLVM coding style specification as an example:

$> clang-format --style=llvm -dump-config
---
Language:        Cpp
# BasedOnStyle:  LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: true
AlignEscapedNewlinesLeft: false
AlignOperands:   true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakTemplateDeclarations: false
AlwaysBreakBeforeMultilineStrings: false
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BinPackParameters: true
BinPackArguments: true
ColumnLimit:     80
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: false
IndentWrappedFunctionNames: false
IndentFunctionDeclarationAfterType: false
MaxEmptyLinesToKeep: 1
KeepEmptyLinesAtTheStartOfBlocks: true
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakString: 1000
PenaltyBreakFirstLessLess: 120
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
SpacesBeforeTrailingComments: 1
Cpp11BracedListStyle: true
Standard:        Cpp11
IndentWidth:     2
TabWidth:        8
UseTab:          Never
BreakBeforeBraces: Attach
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpacesInAngles:  false
SpaceInEmptyParentheses: false
SpacesInCStyleCastParentheses: false
SpaceAfterCStyleCast: false
SpacesInContainerLiterals: true
SpaceBeforeAssignmentOperators: true
ContinuationIndentWidth: 4
CommentPragmas:  '^ IWYU pragma:'
ForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]
SpaceBeforeParens: ControlStatements
DisableFormat:   false
...

If you like it, take it as a start and write it to .clang-format at the root of your project and edit it to change few things. If it is too far from what you want, try the other profiles.

Once you get your configuration file ready for a first test, just try (note that we changed the style option to -style=file in order to read the .clang-format as default configuration file):

$> clang-format -style=file Example.[ch]pp

This command should output the Example.[ch]pp files reformated as specified in the .clang-format file.

After a few tries, you may want to directly overwrite your files with the new formatted files, then use the -i option for that.

This tool is remarkably flexible and rich. It allows a lot of configurations and can trigger much more than you can do with indent. It seems to be quite useable. Moreover, having the specifications of the coding style at the root of your project in a readable format, allows any new developer to read it if they want know some details.

Integration with CMake

Now that we saw the way these tools are working, lets speak about automation and some ways to integrate it easily in a CMake-based build-system.

The nice thing with CMake is that you can easily write some nice generic plugins to use in all your projects. Usually, we create a cmake/ directory at the root directory of your project to store all these plugins. So, lets write the cmake/clang-cxx-dev-tools.cmake file:

# Additional targets to perform clang-format/clang-tidy
# Get all project files
file(GLOB_RECURSE
     ALL_CXX_SOURCE_FILES
     *.[chi]pp *.[chi]xx *.cc *.hh *.ii *.[CHI]
     )

# Adding clang-format target if executable is found
find_program(CLANG_FORMAT "clang-format")
if(CLANG_FORMAT)
  add_custom_target(
    clang-format
    COMMAND /usr/bin/clang-format
    -i
    -style=file
    ${ALL_CXX_SOURCE_FILES}
    )
endif()

# Adding clang-tidy target if executable is found
find_program(CLANG_TIDY "clang-tidy")
if(CLANG_TIDY)
  add_custom_target(
    clang-tidy
    COMMAND /usr/bin/clang-tidy
    ${ALL_CXX_SOURCE_FILES}
    -config=''
    --
    -std=c++11
    ${INCLUDE_DIRECTORIES}
    )
endif()

Then, just add the following line to your CMakeLists.txt:

# Including custom cmake rules
include(cmake/clang-cxx-dev-tools.cmake)

After regenerating your build-system, you should be able to easily perform a make clang-format and a make clang-tidy.

Final words and some possible extensions

The major benefit from these tools is automation, you can reformat your code or run a full static-analysis in one single command. This is really nice and efficient.

But, we can imagine more!

For example, why should we dare execute the reformatting before committing when you can have a git hook that will perform the operation for you at each commit. This can be easily done by editing the file .git/hooks/pre-commit and adding the following lines:

C_CHANGED_FILES = $(git diff --cached --name-only --diff-filter=ACM | grep -Ee "\.[ch]$")
CXX_CHANGED_FILES = $(git diff --cached --name-only --diff-filter=ACM | grep -Ee "\.([chi](pp|xx)|(cc|hh|ii)|[CHI])$")
/usr/bin/clang-format -i -style=file ${CXX_CHANGED_FILES}

This simple hook will enforce all your developers to strictly follow the coding styles without any pain.

Another example to improve the process, is that you can develop your own static-analysis passes and include it into the project. These passes may be quite specific to your project or your development process. I remember that, back in the 90’s having your own static-analysis tool would have required a team working almost full time on it… it is no more the case right now, thanks to LLVM.

Finally, clang-tidy and clang-format together with CMake and git and many other open source tools allow you to have an extremely efficient development environment for almost no cost (except the time you spend to set it at first).

So, do not hesitate anymore to use it!