Using clang-tidy and clang-format
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!