EXTRACLANGTOOLS(1)
๐ NAME
okay so bestie, buckle up: this is extraclangtools(1) โ the Extra Clang Tools Documentation. Welcome to the clang-tools-extra project, which (literally) contains extra tools built using Clang's tooling APIs. โจ
๐ EXTRA CLANG TOOLS 21.1.8 RELEASE NOTES ๐
Here's the whole map before we dive in, iykyk:
- Introduction
- What's New in Extra Clang Tools 21.1.8?
- Major New Features
- Improvements to clangd
- Language feature support
- New Language Server Protocol features
- Compile flags
- Hover
- Code completion
- Cross-references
- Call hierarchy
- Inlay hints
- Code actions
- Clang-tidy integration
- Include-cleaner integration
- Miscellaneous
- Improvements to clang-doc
- Improvements to clang-query
- Improvements to include-cleaner
- Improvements to clang-tidy
- New checks
- New check aliases
- Changes in existing checks
- Removed checks
- Miscellaneous
- Improvements to include-fixer
- Improvements to clang-include-fixer
- Improvements to modularize
- Improvements to pp-trace
- Clang-tidy Visual Studio plugin
Written by the LLVM Team https://llvm.org/ ๐ซถ
๐ธ Introduction ๐ธ
This document contains the release notes for the Extra Clang Tools, part of the Clang release 21.1.8. Bestie, this is where we describe the status of the Extra Clang Tools in some detail โ including major improvements from the previous release and new feature work. All LLVM releases may be downloaded from the LLVM releases web site https://llvm.org/releases/. ๐
For more information about Clang or LLVM โ including info about the latest release โ please see the Clang Web Site https://clang.llvm.org or the LLVM Web Site https://llvm.org. ๐
ngl, heads up: if you're reading this file from a Git checkout or the main Clang web page, this document applies to the next release, not the current one. To see the release notes for a specific release, please see the releases page https://llvm.org/releases/. ๐ซก
โจ What's New in Extra Clang Tools 21.1.8? โจ
Some of the major new features and improvements to Extra Clang Tools are listed here, bestie. Generic improvements to Extra Clang Tools as a whole โ or to its underlying infrastructure โ are described first, followed by the tool-specific sections. ๐ฆ
๐ Major New Features ๐
(crickets โ this one's an empty section in the original, and we're not gonna invent lore ๐)
๐ Improvements to clangd ๐
๐ง Language feature support ๐ง
- Performance improvements and bugfixes to C++20 Modules support ๐ฅ
- Improved support for C++23 "deducing this"
- Improvements to objective-c++ support
โญ New Language Server Protocol features โญ
- Added support for
textDocument/rangesFormatting - Added support for
positionEncoding
๐ Compile flags ๐
- Added
BuiltinHeadersconfig key which controls whether clangd's built-in headers are used or ones extracted from the driver.
๐ธ Hover ๐ธ
- Fixed a bug that would sometimes prevent documentation comments of standard library functions from being shown ๐ญ
๐ Code completion ๐
- Added
HeaderInsertionconfig option to control whether code completion inserts a missing header needed for the symbol being completed. This is equivalent to the--header-insertioncommand-line option. - Added a
CodePatternsconfig option to control whether code completion should offer code patterns as completions in addition to symbols.
๐ฆ Cross-references ๐ฆ
- References to symbols are now collected in array designators
- Find-references now works for operators
newanddelete - Improvements to code navigation in templated code
๐ Call hierarchy ๐
- Call hierarchy now works with the remote index
- Fixed a bug where call hierarchy could sometimes return bogus results
๐ง Inlay hints ๐ง
- Parameter hint forwarding now works for variadic forwarding functions declared in header files
- Improved presentation of block-end hints
โจ Code actions โจ
- Improved the rename refactor's name collision checking logic
๐ Clang-tidy integration ๐
- Disabled the
cppcoreguidelines-macro-to-enumchecker which is incompatible with clangd
๐ Include-cleaner integration ๐
- Clangd now respects the
AngledHeadersandQuotedHeadersconfig options for headers inserted to resolve include-cleaner diagnostics
๐ Miscellaneous ๐
- Various crash fixes and other stability improvements ๐ซถ
๐ฅ Improvements to clang-doc ๐ฅ
(empty section, no cap โ nothing listed here)
๐ Improvements to clang-query ๐
(also empty, we move)
โญ Improvements to include-cleaner โญ
- Deprecated the
-insertand-removecommand line options, and added the-disable-removeand-disable-insertcommand line options as replacements. The previous command line options were confusing because they did not imply the default state of the option (which is inserts and removes being enabled). The new options are easier to understand the semantics of. ๐ง
๐ Improvements to clang-tidy ๐
- Changed the
check_clang_tidy.pytool to use FileCheck's--match-full-linesinstead ofstrict-whitespaceforCHECK-FIXESclauses. Added a--match-partial-fixesoption to keep previous behavior on specific tests. This may break tests for users with custom out-of-tree checks who usecheck_clang_tidy.pyas-is. โ ๏ธ - Improved
clang-tidy-diff.pyscript. Add the-warnings-as-errorsargument to treat warnings as errors. - Improved clang-tidy to show
CheckOptionsonly for checks enabled inCheckswhen running--dump-config. - Fixed bug in clang-tidy by which
HeaderFilterRegexdid not take effect when passed via the.clang-tidyfile. - Fixed bug in
run_clang_tidy.pywhere the program would not correctly display the checks enabled by the top-level.clang-tidyfile.
๐ธ New checks ๐ธ
- New
bugprone-capturing-this-in-member-variable<> check. ๐
Finds lambda captures and bind function calls that capture the this pointer and store it as class members without handle the copy and move constructors and the assignments.
- New
bugprone-misleading-setter-of-reference<> check.
Finds setter-like member functions that take a pointer parameter and set a reference member of the same class with the pointed value.
- New
bugprone-unintended-char-ostream-output<> check.
Finds unintended character output from unsigned char and signed char to an ostream.
- New
cppcoreguidelines-use-enum-class<> check.
Finds unscoped (non-class) enum declarations and suggests using enum class instead.
- New
llvm-prefer-static-over-anonymous-namespace<> check.
Finds function and variable declarations inside anonymous namespace and suggests replacing them with static declarations.
- New
modernize-use-scoped-lock<> check.
Finds uses of std::lock_guard and suggests replacing them with C++17's alternative std::scoped_lock.
- New
portability-avoid-pragma-once<> check.
Finds uses of #pragma once and suggests replacing them with standard include guards (#ifndef/#define/#endif) for improved portability.
- New
readability-ambiguous-smartptr-reset-call<> check.
Finds potentially erroneous calls to reset method on smart pointers when the pointee type also has a reset method.
- New
readability-use-concise-preprocessor-directives<> check.
Finds uses of #if that can be simplified to #ifdef or #ifndef and, since C23 and C++23, uses of #elif that can be simplified to #elifdef or #elifndef.
๐ New check aliases ๐
(empty โ nothing to see here bestie)
๐ Changes in existing checks ๐
- Improved
bugprone-crtp-constructor-accessibility<> check by fixing false positives on deleted constructors that cannot be used to construct objects, even if they have public or protected access. - Improved
bugprone-exception-escape<> check to print stack trace of a potentially escaped exception. - Added an option to
bugprone-multi-level-implicit-pointer-conversion<> to choose whether to enable the check in C code or not. - Improved
bugprone-optional-value-conversion<> check to detect conversion in argument ofstd::make_optional. - Improved
bugprone-sizeof-expression<> check by addingWarnOnSizeOfInLoopTerminationoption to detect misuses ofsizeofexpression in loop conditions. - Improved
bugprone-string-constructor<> check to find suspicious calls ofstd::stringconstructor withcharpointer, start position and length parameters. - Improved
bugprone-unchecked-optional-access<> fixing false positives from smart pointer accessors repeated in checkinghas_valueand accessingvalue. The optionIgnoreSmartPointerDereferenceshould no longer be needed and will be removed. Also fixing false positive fromconstreference accessors to objects containing optional member. - Improved
bugprone-unsafe-functions<> check to allow specifying additional C++ member functions to match. - Improved
cert-err33-c<> check by fixing false positives when a function name is just prefixed with a targeted function name. - Improved
concurrency-mt-unsafe<> check by fixing a false positive wherestrerrorwas flagged as MT-unsafe. - Improved
cppcoreguidelines-avoid-goto<> check by adding the optionIgnoreMacrosto ignoregotolabels defined in macros. - Improved
cppcoreguidelines-interfaces-global-init<> check by fixing false positives on uses ofconstinitvariables. - Improved
cppcoreguidelines-missing-std-forward<> check by adding a flag to specify the function used for forwarding instead ofstd::forward. - Improved
cppcoreguidelines-pro-bounds-pointer-arithmetic<> check by fixing false positives when calling indexing operators that do not perform pointer arithmetic in template, for examplestd::map::operator[]and when pointer arithmetic was used through type aliases. - Improved
cppcoreguidelines-rvalue-reference-param-not-moved<> check by adding a flag to specify the function used for moving instead ofstd::move. - Improved
cppcoreguidelines-special-member-functions<> check by adding the optionIgnoreMacrosto ignore classes defined in macros. - Improved
google-readability-namespace-comments<> check by adding the optionAllowOmittingNamespaceCommentsto accept if a namespace comment is omitted entirely. - Improved
hicpp-avoid-goto<> check by adding the optionIgnoreMacrosto ignoregotolabels defined in macros. - Improved
hicpp-special-member-functions<> check by adding the optionIgnoreMacrosto ignore classes defined in macros. - Improved
llvm-namespace-comment<> check by adding the optionAllowOmittingNamespaceCommentsto accept if a namespace comment is omitted entirely. misc-const-correctnessโ glow up! โจ New optionAllowedTypesthat excludes whatever types you name from const-correctness checking. Also fixes false positives when you're modifying avariantviaoperator[]with a template in the parameters, plus it now supports checking pointee mutation via theAnalyzePointersoption, and it stops tripping when you use aconstarray type. ๐misc-include-cleanerโ now with the optionsUnusedIncludesandMissingIncludes, which say whether the check should report unused includes or missing includes (respectively, bestie, that's literally the whole mapping). ๐misc-redundant-expressionโ got more examples added and had some macro-related false positives fixed. Iconic behavior.misc-unconventional-assign-operatorโ fixed false positives for when a copy assignment operator function inside a template class returns the result of another assignment to*this(likereturn *this=...). No more drama. ๐misc-unused-using-declsโ fixed false positives onoperator""with template parameters.misc-use-internal-linkageโ fixed false positives for a function or variable in a header file that contains macro expansion, and it now excludes variables with thethread_localstorage class specifier from being matched. ๐งmodernize-pass-by-valueโ fixed false positives when a class passed by const-reference had a private move constructor. She said stop accusing people.modernize-type-traitsโ now detects more type traits. More matches, more slay.modernize-use-default-member-initโ now matches arithmetic operations,constexprand static values, and detects explicit casting of built-in types within member list initialization. ๐ฅmodernize-use-designated-initializersโ avoids diagnosing designated initializers forstd::arrayinitializations. We leave those alone, respectfully. ๐modernize-use-rangesโ suppress-warnings logic fornullptrinstd::findgot updated.modernize-use-starts-ends-withโ added more matched scenarios offindandrfindmethods, and fixed false positives when those methods are called with 3 arguments. ๐modernize-use-std-numbersโ now supports math functions of different precisions.modernize-use-trailing-return-typeโ big one, bestie: now supports modernizing lambda signatures to use trailing return type, and adds two whole new options,TransformFunctionsandTransformLambdas, to control whether function declarations and lambdas should be transformed by the check. Also fixed false positives when a lambda was matched as a function in C++11 mode. ๐performance-move-const-argโ fixed false negatives on ternary operators callingstd::move.performance-unnecessary-value-paramโ performance improved by tolerating fix-it breaking compilation when functions are used as pointers, so it avoids matching usage of functions within the current compilation unit. Plus a new option,IgnoreCoroutines, with the default valuetrue, which suppresses this check for coroutines where passing by reference may be unsafe. Very mindful, very demure. ๐ฆreadability-convert-member-functions-to-staticโ fixed false positives on member functions with an explicit object parameter.readability-function-sizeโ new optionCountMemberInitAsStmtthat lets you count class member initializers in constructors as statements. Numbers, but make it fashion. โญreadability-math-missing-parenthesesโ fixed false negatives where math expressions are the operand of assignment operators or comparison operators.readability-named-parameterโ new optionInsertPlainNamesInForwardDeclsto insert parameter names without comments for forward declarations only. Specific? Yes. Correct? Also yes.readability-qualified-autoโ new optionAllowedTypes, which excludes the types you specify from getting qualifiers added. ๐readability-redundant-inline-specifierโ fixed false positives on out-of-line explicitly defaulted functions.readability-redundant-smartptr-getโ fixed some false positives involving smart pointers to arrays. ๐ธ
๐๏ธ Removed checks
(nothing listed here in the original โ it's giving minimalism, and honestly? respect.)
๐ Miscellaneous
๐ง Improvements to include-fixer
The improvements are...
๐ง Improvements to clang-include-fixer
The improvements are...
๐งฉ Improvements to modularize
The improvements are...
๐ Improvements to pp-trace
(no notes, bestie โ the original just leaves the heading sitting there)
๐ฅ๏ธ Clang-tidy Visual Studio plugin
(also just vibing as a heading, no body text)
โ๏ธ CLANG-TIDY
๐ Contents
- Clang-Tidy
- Using Clang-Tidy
- Suppressing Undesired Diagnostics
See also:
(the original points you at "See also:" with nothing attached โ use your imagination, or likeโฆ read the rest of this page โจ)
๐งฐ Clang-Tidy Checks
abseil-cleanup-ctad
Suggests switching the initialization pattern of absl::Cleanup instances from the factory function to class template argument deduction (CTAD), in C++17 and higher. It's giving "let the compiler do the typing, bestie." ๐
auto c1 = absl::MakeCleanup([] {});
const auto c2 = absl::MakeCleanup(std::function<void()>([] {}));
becomes
absl::Cleanup c1 = [] {};
const absl::Cleanup c2 = std::function<void()>([] {});
abseil-duration-addition
Checks for cases where addition should be happening in the absl::Time domain instead. When you add two values and one of them is known to be an absl::Time, we can infer the other one should be interpreted as an absl::Duration of a similar scale โ and then we make that inference, like, explicit. No more sneaky domain mixing. ๐ฅ
Examples:
// Original - Addition in the integer domain
int x;
absl::Time t;
int result = absl::ToUnixSeconds(t) + x;
// Suggestion - Addition in the absl::Time domain
int result = absl::ToUnixSeconds(t + absl::Seconds(x));
abseil-duration-comparison
Checks for comparisons that should be living in the absl::Duration domain instead of the floating point or integer domains. Vibe check: are you comparing in the right domain? ๐
N.B.: In cases where aDurationwas being converted to an integer and then compared against a floating-point value, truncation during theDurationconversion might yield a different result. In practice this is very rare โ and it still indicates a bug which should be fixed. So fix it. ๐
Examples:
// Original - Comparison in the floating point domain
double x;
absl::Duration d;
if (x < absl::ToDoubleSeconds(d)) ...
// Suggested - Compare in the absl::Duration domain instead
if (absl::Seconds(x) < d) ...
// Original - Comparison in the integer domain
int x;
absl::Duration d;
if (x < absl::ToInt64Microseconds(d)) ...
// Suggested - Compare in the absl::Duration domain instead
if (absl::Microseconds(x) < d) ...
abseil-duration-conversion-cast
Checks for casts of absl::Duration conversion functions and recommends the right conversion function instead. Basically: stop casting, start using the function that already does what you want. ๐
Examples:
// Original - Cast from a double to an integer
absl::Duration d;
int i = static_cast<int>(absl::ToDoubleSeconds(d));
// Suggested - Use the integer conversion function directly.
int i = absl::ToInt64Seconds(d);
// Original - Cast from a double to an integer
absl::Duration d;
double x = static_cast<double>(absl::ToInt64Seconds(d));
// Suggested - Use the integer conversion function directly.
double x = absl::ToDoubleSeconds(d);
Note: In the second example, the suggested fix could yield a different result, as the conversion to integer could truncate. In practice, this is very rare, and you should use absl::Trunc to perform this operation explicitly instead. ๐ญ
abseil-duration-division
absl::Duration arithmetic works like it does with integers. That means division of two absl::Duration objects returns an int64 with any fractional component truncated toward 0. See this link for more information on arithmetic with absl::Duration: https://github.com/abseil/abseil-cpp/blob/29ff6d4860070bf8fcbd39c8805d0c32d56628a3/absl/time/time.h#L137
For example:
absl::Duration d = absl::Seconds(3.5);
int64 sec1 = d / absl::Seconds(1); // Truncates toward 0.
int64 sec2 = absl::ToInt64Seconds(d); // Equivalent to division.
assert(sec1 == 3 && sec2 == 3);
double dsec = d / absl::Seconds(1); // WRONG: Still truncates toward 0.
assert(dsec == 3.0);
If you want floating-point division, you should use either the absl::FDivDuration() function, or one of the unit conversion functions such as absl::ToDoubleSeconds(). For example:
absl::Duration d = absl::Seconds(3.5);
double dsec1 = absl::FDivDuration(d, absl::Seconds(1)); // GOOD: No truncation.
double dsec2 = absl::ToDoubleSeconds(d); // GOOD: No truncation.
assert(dsec1 == 3.5 && dsec2 == 3.5);
This check looks for uses of absl::Duration division that is done in a floating-point context, and recommends the use of a function that returns a floating-point value. Truncation found crying in the club rn. ๐ซถ
abseil-duration-factory-float
Checks for cases where the floating-point overloads of various absl::Duration factory functions are being called when the more-efficient integer versions could be used instead. Efficiency is the aesthetic. โญ
This check will not suggest fixes for literals which contain fractional floating point values or non-literals. It will suggest removing superfluous casts.
Examples:
// Original - Providing a floating-point literal.
absl::Duration d = absl::Seconds(10.0);
// Suggested - Use an integer instead.
absl::Duration d = absl::Seconds(10);
// Original - Explicitly casting to a floating-point type.
absl::Duration d = absl::Seconds(static_cast<double>(10));
// Suggested - Remove the explicit cast
absl::Duration d = absl::Seconds(10);
abseil-duration-factory-scale
Checks for cases where arguments to absl::Duration factory functions are scaled internally and could be changed to a different factory function. This check also looks for arguments with a zero value and suggests using absl::ZeroDuration() instead. Math? In my factory call? More likely than you'd think. ๐ง
Examples:
// Original - Internal multiplication.
int x;
absl::Duration d = absl::Seconds(60 * x);
// Suggested - Use absl::Minutes instead.
absl::Duration d = absl::Minutes(x);
// Original - Internal division.
int y;
absl::Duration d = absl::Milliseconds(y / 1000.);
// Suggested - Use absl:::Seconds instead.
absl::Duration d = absl::Seconds(y);
// Original - Zero-value argument.
absl::Duration d = absl::Hours(0);
// Suggested = Use absl::ZeroDuration instead
absl::Duration d = absl::ZeroDuration();
abseil-duration-subtraction
Checks for cases where subtraction should be performed in the absl::Duration domain. When you subtract two values and the first one is known to be a conversion from absl::Duration, we can infer that the second should also be interpreted as an absl::Duration โ and we make that inference explicit. Deduction queen. ๐
Examples:
// Original - Subtraction in the double domain
double x;
absl::Duration d;
double result = absl::ToDoubleSeconds(d) - x;
// Suggestion - Subtraction in the absl::Duration domain instead
double result = absl::ToDoubleSeconds(d - absl::Seconds(x));
// Original - Subtraction of two Durations in the double domain
absl::Duration d1, d2;
double result = absl::ToDoubleSeconds(d1) - absl::ToDoubleSeconds(d2);
// Suggestion - Subtraction in the absl::Duration domain instead
double result = absl::ToDoubleSeconds(d1 - d2);
๐ก Heads up bestie: just like every other clang-tidy check, fixes can overlap (nested expressions, looking at you ๐), so not every occurrence gets transformed in a single run. This is especially a thing with nested subtraction expressions. Just run clang-tidy multiple times and it'll catch those overlaps. ๐ซถ
๐ซ abseil-duration-unnecessary-conversion
Okay so this one hunts down and fixes cases where absl::Duration values get converted to numeric types and then back again. Round-trip nonsense. We're cancelling that. ๐ซ
Floating-point examples, incoming:
// Original - Conversion to double and back again
absl::Duration d1;
absl::Duration d2 = absl::Seconds(absl::ToDoubleSeconds(d1));
// Suggestion - Remove unnecessary conversions
absl::Duration d2 = d1;
// Original - Division to convert to double and back again
absl::Duration d2 = absl::Seconds(absl::FDivDuration(d1, absl::Seconds(1)));
// Suggestion - Remove division and conversion
absl::Duration d2 = d1;
And the integer flavour:
// Original - Conversion to integer and back again
absl::Duration d1;
absl::Duration d2 = absl::Hours(absl::ToInt64Hours(d1));
// Suggestion - Remove unnecessary conversions
absl::Duration d2 = d1;
// Original - Integer division followed by conversion
absl::Duration d2 = absl::Seconds(d1 / absl::Seconds(1));
// Suggestion - Remove division and conversion
absl::Duration d2 = d1;
It also unwraps scalar operations, like:
// Original - Multiplication by a scalar
absl::Duration d1;
absl::Duration d2 = absl::Seconds(absl::ToInt64Seconds(d1) * 2);
// Suggestion - Remove unnecessary conversion
absl::Duration d2 = d1 * 2;
โ ๏ธ Real talk: converting to an integer and back to anabsl::Durationmight be a truncating operation if the value isn't aligned to the scale of conversion. In the rare case where that truncation is actually what you want, callers should useabsl::Truncto truncate explicitly. Don't let it happen by accident, bestie. ๐ญ
๐ซ abseil-faster-strsplit-delimiter
Finds instances of absl::StrSplit() or absl::MaxSplits() where the delimiter is a single-character string literal, and swaps it for a character. The check offers a suggestion to change the string literal into a character. It'll also catch code using absl::ByAnyChar() for just a single character and transform that into a single character too. โจ
These changes give the same result, but using characters instead of single-character string literals is more efficient and more readable. It's giving glow-up. ๐
Examples:
// Original - the argument is a string literal.
for (auto piece : absl::StrSplit(str, "B")) {
// Suggested - the argument is a character, which causes the more efficient
// overload of absl::StrSplit() to be used.
for (auto piece : absl::StrSplit(str, 'B')) {
// Original - the argument is a string literal inside absl::ByAnyChar call.
for (auto piece : absl::StrSplit(str, absl::ByAnyChar("B"))) {
// Suggested - the argument is a character, which causes the more efficient
// overload of absl::StrSplit() to be used and we do not need absl::ByAnyChar
// anymore.
for (auto piece : absl::StrSplit(str, 'B')) {
// Original - the argument is a string literal inside absl::MaxSplits call.
for (auto piece : absl::StrSplit(str, absl::MaxSplits("B", 1))) {
// Suggested - the argument is a character, which causes the more efficient
// overload of absl::StrSplit() to be used.
for (auto piece : absl::StrSplit(str, absl::MaxSplits('B', 1))) {
๐ซ abseil-no-internal-dependencies
Warns you if your code using Abseil depends on internal details. If something lives in a namespace that includes the word "internal", you are not allowed to depend on it, because it's an implementation detail. No friending it, no including it, no mentioning it, no referring to it in any way whatsoever. ๐
โโ๏ธ Doing so violates Abseil's compatibility guidelines and may result in breakage. Full deets at https://abseil.io/about/compatibility.
The following cases trigger warnings:
absl::strings_internal::foo();
// warning triggered on this line
class foo {
friend struct absl::container_internal::faa;
// warning triggered on this line
};
absl::memory_internal::MakeUniqueResult();
// warning triggered on this line
๐ซ abseil-no-namespace
Makes sure your code doesn't open namespace absl, because that violates Abseil's compatibility guidelines. Like, code should not open namespace absl โ it conflicts with Abseil's compatibility guidelines and may result in breakage. ๐จ
Any code doing this:
namespace absl {
...
}
gets a warning. Periodt. ๐
See the full Abseil compatibility guidelines at https://abseil.io/about/compatibility for more.
๐ซ abseil-redundant-strcat-calls
Suggests removing unnecessary calls to absl::StrCat when the result is being passed to another call to absl::StrCat or absl::StrAppend. Nesting cats inside cats?? Jail. ๐ค
The extra calls construct unnecessary temporary strings, and removing them makes the code smaller and faster. Iconic behaviour. โจ
Examples:
std::string s = absl::StrCat("A", absl::StrCat("B", absl::StrCat("C", "D")));
//before
std::string s = absl::StrCat("A", "B", "C", "D");
//after
absl::StrAppend(&s, absl::StrCat("E", "F", "G"));
//before
absl::StrAppend(&s, "E", "F", "G");
//after
๐ซ abseil-str-cat-append
Flags uses of absl::StrCat() to append to a std::string, and suggests absl::StrAppend() instead. ๐
Again: the extra calls construct unnecessary temporary strings, and removing them makes the code smaller and faster.
a = absl::StrCat(a, b); // Use absl::StrAppend(&a, b) instead.
๐ Little caveat: it does not diagnose cases where absl::StrCat() is used as a template argument for a functor.
๐ซ abseil-string-find-startswith
Checks whether a std::string::find() or std::string::rfind() result (and the corresponding std::string_view methods) is compared with 0, and suggests replacing it with absl::StartsWith(). It's both a readability and performance issue, so bestie, it's a win-win. ๐
Heads up: starts_with was added as a built-in function on those types in C++20. If it's available, prefer enabling modernize-use-starts-ends-with <> instead of this check. Iykyk.
string s = "...";
if (s.find("Hello World") == 0) { /* do something */ }
if (s.rfind("Hello World", 0) == 0) { /* do something */ }
becomes
string s = "...";
if (absl::StartsWith(s, "Hello World")) { /* do something */ }
if (absl::StartsWith(s, "Hello World")) { /* do something */ }
โจ Options โจ
StringLikeClassesโ semicolon-separated list of names of string-like classes. By default bothstd::basic_stringandstd::basic_string_vieware considered. The list of methods to be considered is fixed.IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.AbseilStringsMatchHeaderโ the location of Abseil'sstrings/match.h. Defaults toabsl/strings/match.h.
๐ซ abseil-string-find-str-contains
Finds s.find(...) == string::npos comparisons (across various string-like types) and suggests replacing them with absl::StrContains(). ๐ฆ
This improves readability and reduces the likelihood of accidentally mixing find() and npos from different string-like types. Which, honestly, is the kind of bug that ruins your whole day. ๐ญ
By default, "string-like types" includes ::std::basic_string, ::std::basic_string_view, and ::absl::string_view. See the StringLikeClasses option to change this.
std::string s = "...";
if (s.find("Hello World") == std::string::npos) { /* do something */ }
absl::string_view a = "...";
if (absl::string_view::npos != a.find("Hello World")) { /* do something */ }
becomes
std::string s = "...";
if (!absl::StrContains(s, "Hello World")) { /* do something */ }
absl::string_view a = "...";
if (absl::StrContains(a, "Hello World")) { /* do something */ }
โจ Options โจ
StringLikeClassesโ semicolon-separated list of names of string-like classes. By default includes::std::basic_string,::std::basic_string_view, and::absl::string_view.IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.AbseilStringsMatchHeaderโ the location of Abseil'sstrings/match.h. Defaults toabsl/strings/match.h.
๐ซ abseil-time-comparison
Prefer comparisons in the absl::Time domain instead of the integer domain. Time domain is the main character here. ๐
โ ๏ธ N.B.: in cases where anabsl::Timeis being converted to an integer, alignment may occur. If the comparison depends on that alignment, doing the comparison in theabsl::Timedomain may yield a different result. In practice this is very rare โ and it still indicates a bug that should be fixed. So like, fix it either way. ๐
Examples:
// Original - Comparison in the integer domain
int x;
absl::Time t;
if (x < absl::ToUnixSeconds(t)) ...
// Suggested - Compare in the absl::Time domain instead
if (absl::FromUnixSeconds(x) < t) ...
๐ซ abseil-time-subtraction
Finds and fixes absl::Time subtraction expressions so the subtraction happens in the Time domain instead of the numeric domain. Math, but make it typed. ๐งฎโจ
There are two cases of Time subtraction where extra type information has to be deduced:
- When the result is an
absl::Durationand the first argument is anabsl::Time. - When the second argument is an
absl::Time.
In the first case, we have to know the result of the operation, since without that the second operand could be either an absl::Time or an absl::Duration. In the second case, the first operand must be an absl::Time, because subtracting an absl::Time from an absl::Duration is not defined. No cap. ๐ซถ
Examples:
int x;
absl::Time t;
// Original - absl::Duration result and first operand is an absl::Time.
absl::Duration d = absl::Seconds(absl::ToUnixSeconds(t) - x);
// Suggestion - Perform subtraction in the Time domain instead.
absl::Duration d = t - absl::FromUnixSeconds(x);
// Original - Second operand is an absl::Time.
int i = x - absl::ToUnixSeconds(t);
// Suggestion - Perform subtraction in the Time domain instead.
int i = absl::ToInt64Seconds(absl::FromUnixSeconds(x) - t);
abseil-upgrade-duration-conversions ๐
Okay so this check hunts down every call to absl::Duration arithmetic operators and factory functions where the argument is gonna need an explicit cast to keep compiling after the upcoming API glow-up. fr fr.
The operators *=, /=, *, and / for absl::Duration currently accept an argument of class type that's convertible to an arithmetic type. And likeโฆ such a call currently converts the value to an int64_t, even in a case such as std::atomic<float> that would result in lossy conversion ๐ญ.
Also (not me forgetting the factories ๐) โ the absl::Duration factory functions (absl::Hours, absl::Minutes, etc) currently accept an int64_t or a floating-point type. Same energy as the arithmetic operators: calls with an argument of class type that's convertible to an arithmetic type go through the int64_t path.
These operators and factories will be changed to only accept arithmetic types to prevent unintended behavior. After these changes are released, passing an argument of class type will no longer compile, even if the type is implicitly convertible to an arithmetic type. so like, heads up ๐
Here are example fixes created by this check:
std::atomic<int> a;
absl::Duration d = absl::Milliseconds(a);
d *= a;
becomes โจ
std::atomic<int> a;
absl::Duration d = absl::Milliseconds(static_cast<int64_t>(a));
d *= static_cast<int64_t>(a);
Heads up bestie ๐ซถ: this check always adds a cast to int64_t in order to preserve the current behavior of user code. It's possible that this uncovers unintended behavior due to types implicitly convertible to a floating-point type. iykyk ๐
altera-id-dependent-backward-branch ๐ฆ
Finds ID-dependent variables and fields that are used within loops. This causes branches to occur inside the loops, and thus leads to performance degradation ๐ญ๐.
// The following code will produce a warning because this ID-dependent
// variable is used in a loop condition statement.
int ThreadID = get_local_id(0);
// The following loop will produce a warning because the loop condition
// statement depends on an ID-dependent variable.
for (int i = 0; i < ThreadID; ++i) {
std::cout << i << std::endl;
}
// The following loop will not produce a warning, because the ID-dependent
// variable is not used in the loop condition statement.
for (int i = 0; i < 100; ++i) {
std::cout << ThreadID << std::endl;
}
Based on the Altera SDK for OpenCL: Best Practices Guide โ https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.
altera-kernel-name-restriction ๐
Finds kernel files and include directives whose filename is kernel.cl, Verilog.cl, or VHDL.cl. The check is case insensitive.
Such kernel file names cause the offline compiler to generate intermediate design files that have the same names as certain internal files, which leads to a compilation error. likeโฆ rude ๐ค
Based on the Guidelines for Naming the Kernel section in the Intel FPGA SDK for OpenCL Pro Edition: Programming Guide โ https://www.intel.com/content/www/us/en/programmable/documentation/mwh1391807965224.html#ewa1412973930963.
altera-single-work-item-barrier ๐ธ
Finds OpenCL kernel functions that call a barrier function but do not call an ID function (get_local_id, get_local_id, get_group_id, or get_local_linear_id).
These kernels may be viable single work-item kernels, but will be forced to execute as NDRange kernels if using a newer version of the Altera Offline Compiler (>= v17.01).
If using an older version of the Altera Offline Compiler, these kernel functions will be treated as single work-item kernels, which could be inefficient or lead to errors if NDRange semantics were intended. ๐
Based on the Altera SDK for OpenCL: Best Practices Guide โ https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.
Examples:
// error: function calls barrier but does not call an ID function.
void __kernel barrier_no_id(__global int * foo, int size) {
for (int i = 0; i < 100; i++) {
foo[i] += 5;
}
barrier(CLK_GLOBAL_MEM_FENCE);
}
// ok: function calls barrier and an ID function.
void __kernel barrier_with_id(__global int * foo, int size) {
for (int i = 0; i < 100; i++) {
int tid = get_global_id(0);
foo[tid] += 5;
}
barrier(CLK_GLOBAL_MEM_FENCE);
}
// ok with AOC Version 17.01: the reqd_work_group_size turns this into
// an NDRange.
__attribute__((reqd_work_group_size(2,2,2)))
void __kernel barrier_with_id(__global int * foo, int size) {
for (int i = 0; i < 100; i++) {
foo[tid] += 5;
}
barrier(CLK_GLOBAL_MEM_FENCE);
}
Options
AOCVersionโ Defines the version of the Altera Offline Compiler. Defaults to1600(corresponding to version 16.00).
altera-struct-pack-align ๐ง
Finds structs that are inefficiently packed or aligned, and recommends packing and/or aligning of said structs as needed. it's giving โจstruct glow-upโจ
Structs that are not packed take up more space than they should, and accessing structs that are not well aligned is inefficient.
Fix-its are provided to fix both of these issues by inserting and/or amending relevant struct attributes. slay ๐
Based on the Altera SDK for OpenCL: Best Practices Guide โ https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.
// The following struct is originally aligned to 4 bytes, and thus takes up
// 12 bytes of memory instead of 10. Packing the struct will make it use
// only 10 bytes of memory, and aligning it to 16 bytes will make it
// efficient to access.
struct example {
char a; // 1 byte
double b; // 8 bytes
char c; // 1 byte
};
// The following struct is arranged in such a way that packing is not needed.
// However, it is aligned to 4 bytes instead of 8, and thus needs to be
// explicitly aligned.
struct implicitly_packed_example {
char a; // 1 byte
char b; // 1 byte
char c; // 1 byte
char d; // 1 byte
int e; // 4 bytes
};
// The following struct is explicitly aligned and packed.
struct good_example {
char a; // 1 byte
double b; // 8 bytes
char c; // 1 byte
} __attribute__((packed)) __attribute__((aligned(16));
// Explicitly aligning a struct to the wrong value will result in a warning.
// The following example should be aligned to 16 bytes, not 32.
struct badly_aligned_example {
char a; // 1 byte
double b; // 8 bytes
char c; // 1 byte
} __attribute__((packed)) __attribute__((aligned(32)));
altera-unroll-loops ๐ฅ
Finds inner loops that have not been unrolled, as well as fully unrolled loops with unknown loop bounds or a large number of iterations.
Unrolling inner loops could improve the performance of OpenCL kernels. However, if they have unknown loop bounds or a large number of iterations, they cannot be fully unrolled, and should be partially unrolled. no cap ๐
Notes:
- This check is unable to determine the number of iterations in a
whileordo..whileloop; hence if such a loop is fully unrolled, a note is emitted advising the user to partially unroll instead. - In
forloops, our check only works with simple arithmetic increments (+,-,*,/). For all other increments, partial unrolling is advised. - Depending on the exit condition, the calculations for determining if the number of iterations is large may be off by 1. This should not be an issue since the cut-off is generally arbitrary. ๐
Based on the Altera SDK for OpenCL: Best Practices Guide โ https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.
for (int i = 0; i < 10; i++) { // ok: outer loops should not be unrolled
int j = 0;
do { // warning: this inner do..while loop should be unrolled
j++;
} while (j < 15);
int k = 0;
#pragma unroll
while (k < 20) { // ok: this inner loop is already unrolled
k++;
}
}
int A[1000];
#pragma unroll
// warning: this loop is large and should be partially unrolled
for (int a : A) {
printf("%d", a);
}
#pragma unroll 5
// ok: this loop is large, but is partially unrolled
for (int a : A) {
printf("%d", a);
}
#pragma unroll
// warning: this loop is large and should be partially unrolled
for (int i = 0; i < 1000; ++i) {
printf("%d", i);
}
#pragma unroll 5
// ok: this loop is large, but is partially unrolled
for (int i = 0; i < 1000; ++i) {
printf("%d", i);
}
#pragma unroll
// warning: << operator not supported, recommend partial unrolling
for (int i = 0; i < 1000; i<<1) {
printf("%d", i);
}
std::vector<int> someVector (100, 0);
int i = 0;
#pragma unroll
// note: loop may be large, recommend partial unrolling
while (i < someVector.size()) {
someVector[i]++;
}
#pragma unroll
// note: loop may be large, recommend partial unrolling
while (true) {
printf("In loop");
}
#pragma unroll 5
// ok: loop may be large, but is partially unrolled
while (i < someVector.size()) {
someVector[i]++;
}
Options
MaxLoopIterationsโ Defines the maximum number of loop iterations that a fully unrolled loop can have. By default, it is set to100.
In practice, this refers to the integer value of the upper bound within the loop statement's condition expression. ๐
android-cloexec-accept ๐
The usage of accept() is not recommended, it's better to use accept4(). Without this flag, an opened sensitive file descriptor would remain open across a fork+exec to a lower-privileged SELinux domain. ๐ฌ
Examples:
accept(sockfd, addr, addrlen);
// becomes
accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);
android-cloexec-accept4 ๐
accept4() should include SOCK_CLOEXEC in its type argument to avoid the file descriptor leakage. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. literally just add it bestie ๐ซถ
Examples:
accept4(sockfd, addr, addrlen, SOCK_NONBLOCK);
// becomes
accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
android-cloexec-creat ๐
The usage of creat() is not recommended, it's better to use open(). periodt ๐
Examples:
int fd = creat(path, mode);
// becomes
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
android-cloexec-dup ๐
Ok so using dup() is not recommended, bestie โ the better move is fcntl(), because it can set the close-on-exec flag. Why do we care? Because otherwise an opened sensitive file would stay open across a fork+exec into a lower-privileged SELinux domain. And that's giving security leak, not giving slay. ๐ญ
Examples:
int fd = dup(oldfd);
// becomes
int fd = fcntl(oldfd, F_DUPFD_CLOEXEC);
android-cloexec-epoll-create โจ
Yeah so epoll_create() is not recommended either โ epoll_create1() is the moment, because it allows close-on-exec. Iconic behavior only. ๐
Examples:
epoll_create(size);
// becomes
epoll_create1(EPOLL_CLOEXEC);
android-cloexec-epoll-create1 ๐ซถ
epoll_create1() needs to include EPOLL_CLOEXEC in its type argument or the file descriptor leakage is literally right there. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. No cap.
Examples:
epoll_create1(0);
// becomes
epoll_create1(EPOLL_CLOEXEC);
android-cloexec-fopen ๐
fopen() should include e in the mode string, so re would be valid. This is equivalent to having set FD_CLOEXEC on that descriptor. Little letter, big energy. ๐
Examples:
fopen("fn", "r");
// becomes
fopen("fn", "re");
android-cloexec-inotify-init ๐ฆ
Using inotify_init() is not recommended, bestie โ inotify_init1() is the upgrade we deserve.
Examples:
inotify_init();
// becomes
inotify_init1(IN_CLOEXEC);
android-cloexec-inotify-init1 ๐ธ
inotify_init1() should include IN_CLOEXEC in its type argument to avoid that file descriptor leakage. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. We do not want that vibe. ๐ซ
Examples:
inotify_init1(IN_NONBLOCK);
// becomes
inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
android-cloexec-memfd-create โญ
memfd_create() should include MFD_CLOEXEC in its type argument to avoid the file descriptor leakage. Same deal: without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain.
Examples:
memfd_create(name, MFD_ALLOW_SEALING);
// becomes
memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
android-cloexec-open ๐ฅ
So a very common source of security bugs is code that opens a file without using the O_CLOEXEC flag. Without that flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain, leaking that sensitive data. ๐ Open-like functions including open(), openat(), and open64() should include O_CLOEXEC in their flags argument. It's giving basic hygiene.
Examples:
open("filename", O_RDWR);
open64("filename", O_RDWR);
openat(0, "filename", O_RDWR);
// becomes
open("filename", O_RDWR | O_CLOEXEC);
open64("filename", O_RDWR | O_CLOEXEC);
openat(0, "filename", O_RDWR | O_CLOEXEC);
android-cloexec-pipe ๐
This check detects usage of pipe(). Using pipe() is not recommended โ pipe2() is the suggested replacement. The check also adds the O_CLOEXEC flag that marks the file descriptor to be closed in child processes. Without this flag a sensitive file descriptor can be leaked to a child process, potentially into a lower-privileged SELinux domain. ๐
Examples:
pipe(pipefd);
Suggested replacement:
pipe2(pipefd, O_CLOEXEC);
android-cloexec-pipe2 ๐
This check makes sure pipe2() is called with the O_CLOEXEC flag. It also adds the O_CLOEXEC flag that marks the file descriptor to be closed in child processes. Without this flag a sensitive file descriptor can be leaked to a child process, potentially into a lower-privileged SELinux domain. Same story, different syscall. ๐
Examples:
pipe2(pipefd, O_NONBLOCK);
Suggested replacement:
pipe2(pipefd, O_NONBLOCK | O_CLOEXEC);
android-cloexec-socket ๐ง
socket() should include SOCK_CLOEXEC in its type argument to avoid the file descriptor leakage. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. Slap that flag on and move on, fr fr.
Examples:
socket(domain, type, SOCK_STREAM);
// becomes
socket(domain, type, SOCK_STREAM | SOCK_CLOEXEC);
android-comparison-in-temp-failure-retry ๐
Diagnoses comparisons that appear to be incorrectly placed in the argument to the TEMP_FAILURE_RETRY macro. Having such a use is incorrect in the vast majority of cases, and will often silently defeat the purpose of the TEMP_FAILURE_RETRY macro. Silently!! Defeating!! Its whole purpose!! ๐ญ
For context, TEMP_FAILURE_RETRY is a convenience macro <https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html> provided by both glibc and Bionic. Its purpose is to repeatedly run a syscall until it either succeeds, or fails for reasons other than being interrupted. That's its entire job and we respect it.
Example buggy usage looks like:
char cs[1];
while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs)) != 0)) {
// Do something with cs.
}
Because TEMP_FAILURE_RETRY will check for whether the result of the comparison is -1, and retry if so. Yeah. Not the move.
If you encounter this, the fix is simple: lift the comparison out of the TEMP_FAILURE_RETRY argument, like so:
char cs[1];
while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs))) != 0) {
// Do something with cs.
}
Options
RetryMacrosโ a comma-separated list of the names of retry macros to be checked. Default isTEMP_FAILURE_RETRY.
boost-use-ranges ๐
Detects calls to standard library iterator algorithms that could be replaced with a Boost ranges version instead. It's literally glow-up-for-your-algorithms energy.
Example
auto Iter1 = std::find(Items.begin(), Items.end(), 0);
auto AreSame = std::equal(Items1.cbegin(), Items1.cend(), std::begin(Items2),
std::end(Items2));
Transforms to:
auto Iter1 = boost::range::find(Items, 0);
auto AreSame = boost::range::equal(Items1, Items2);
Supported algorithms
Calls to the following std library algorithms are checked โ yes, all of them, bestie, I'm not skipping a single one:
std::accumulate, std::adjacent_difference, std::adjacent_find, std::all_of, std::any_of, std::binary_search, std::copy_backward, std::copy_if, std::copy, std::count_if, std::count, std::equal_range, std::equal, std::fill, std::find_end, std::find_first_of, std::find_if_not, std::find_if, std::find, std::for_each, std::generate, std::includes, std::iota, std::is_partitioned, std::is_permutation, std::is_sorted_until, std::is_sorted, std::lexicographical_compare, std::lower_bound, std::make_heap, std::max_element, std::merge, std::min_element, std::mismatch, std::next_permutation, std::none_of, std::partial_sum, std::partial_sort_copy, std::partition_copy, std::partition_point, std::partition, std::pop_heap, std::prev_permutation, std::push_heap, std::random_shuffle, std::reduce, std::remove_copy_if, std::remove_copy, std::remove_if, std::remove, std::replace_copy_if, std::replace_copy, std::replace_if, std::replace, std::reverse_copy, std::reverse, std::search, std::set_difference, std::set_intersection, std::set_symmetric_difference, std::set_union, std::sort_heap, std::sort, std::stable_partition, std::stable_sort, std::transform, std::unique_copy, std::unique, std::upper_bound.
The check will also look for the following functions from the boost::algorithm namespace:
all_of_equal, any_of_equal, any_of, apply_permutation, apply_reverse_permutation, clamp_range, copy_if_until, copy_if_while, copy_if, copy_until, copy_while, find_backward, find_if_backward, find_if_not_backward, find_if_not, find_not_backward, hex_lower, hex, iota, all_of, is_decreasing, is_increasing, is_palindrome, is_partitioned_until, is_partitioned, is_permutation, is_sorted_until, is_sorted, is_strictly_decreasing, is_strictly_increasing, none_of_equal, none_of, one_of_equal, one_of, partition_copy, partition_point, reduce, unhex.
Reverse Iteration
If calls are made using reverse iterators on containers, the code will be fixed using the boost::adaptors::reverse adaptor. Fancy, right? ๐ฆ
auto AreSame = std::equal(Items1.rbegin(), Items1.rend(),
std::crbegin(Items2), std::crend(Items2));
Transforms to:
auto AreSame = boost::range::equal(boost::adaptors::reverse(Items1),
boost::adaptors::reverse(Items2));
Options
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.IncludeBoostSystemโ iftrue(default value) the boost headers are included as system headers with angle brackets (#include <boost.hpp>), otherwise quotes are used (#include "boost.hpp").UseReversePipeโ whentrue(defaultfalse), fixes which involve reverse ranges will use the pipe adaptor syntax instead of the function syntax.
std::find(Items.rbegin(), Items.rend(), 0);
Transforms to:
boost::range::find(Items | boost::adaptors::reversed, 0);
boost-use-to-string ๐
This check finds conversion from integer type like int to std::string or std::wstring using boost::lexical_cast, and replaces it with calls to std::to_string and std::to_wstring. Modern problems require modern solutions, periodt.
Heads up though โ it doesn't replace conversion from floating points despite the to_string overloads, because it would change the behavior. So don't @ me about floats. ๐
auto str = boost::lexical_cast<std::string>(42);
auto wstr = boost::lexical_cast<std::wstring>(2137LL);
// Will be changed to
auto str = std::to_string(42);
auto wstr = std::to_wstring(2137LL);
bugprone-argument-comment ๐
Checks that argument comments match parameter names. The vibe check is on your comments, bestie.
The check understands argument comments in the form /*parameter_name=*/ that are placed right before the argument.
void f(bool foo);
...
f(/*bar=*/true);
// warning: argument name 'bar' in comment does not match parameter name 'foo'
The check tries to detect typos and suggest automated fixes for them. It's giving helpful bestie energy. ๐ซถ
Options
StrictModeโ whenfalse, the check will ignore leading and trailing underscores and case when comparing names โ otherwise they are taken into account. Default isfalse.IgnoreSingleArgumentโ whentrue, the check will ignore the single argument. Default isfalse.CommentBoolLiteralsโ whentrue, the check will add argument comments in the format/*ParameterName=*/right before the boolean literal argument. Default isfalse.
Before:
void foo(bool TurnKey, bool PressButton);
foo(true, false);
After:
void foo(bool TurnKey, bool PressButton);
foo(/*TurnKey=*/true, /*PressButton=*/false);
CommentIntegerLiteralsโ whentrue, the check will add argument comments in the format/*ParameterName=*/right before the integer literal argument. Default isfalse.
Before:
void foo(int MeaningOfLife);
foo(42);
After:
Options (continued ๐)
CommentFloatLiteralsโ whentrue, the check drops an argument comment in the/*ParameterName=*/format right before the float/double literal argument. Default isfalse.
Before:
void foo(float Pi);
foo(3.14159);
After:
void foo(float Pi);
foo(/*Pi=*/3.14159);
CommentStringLiteralsโ whentrue, the check adds argument comments in the/*ParameterName=*/format right before the string literal argument. Default isfalse.
Before:
void foo(const char *String);
void foo(const wchar_t *WideString);
foo("Hello World");
foo(L"Hello World");
After:
void foo(const char *String);
void foo(const wchar_t *WideString);
foo(/*String=*/"Hello World");
foo(/*WideString=*/L"Hello World");
CommentCharacterLiteralsโ whentrue, the check slaps an argument comment in the/*ParameterName=*/format right before the character literal argument. Default isfalse.
Before:
void foo(char *Character);
foo('A');
After:
void foo(char *Character);
foo(/*Character=*/'A');
CommentUserDefinedLiteralsโ whentrue, the check adds argument comments in the/*ParameterName=*/format right before the user defined literal argument. Default isfalse.
Before:
void foo(double Distance);
double operator"" _km(long double);
foo(402.0_km);
After:
void foo(double Distance);
double operator"" _km(long double);
foo(/*Distance=*/402.0_km);
CommentNullPtrsโ whentrue, the check adds argument comments in the/*ParameterName=*/format right before thenullptrliteral argument. Default isfalse.
Before:
void foo(A* Value);
foo(nullptr);
After:
void foo(A* Value);
foo(/*Value=*/nullptr);
๐ธ bugprone-assert-side-effect
Finds assert() with side effect. ๐ฅ
The condition of assert() is evaluated only in debug builds, so a condition with a side effect can cause different behavior in debug / release builds. That's the whole vibe check it's failing, bestie. ๐ญ
Options
AssertMacrosโ a comma-separated list of the names of assert macros to be checked. Default isassert,NSAssert,NSCAssert.CheckFunctionCallsโ whether to treat non-const member and non-member functions as if they produce side effects. Disabled by default because it can increase the number of false positive warnings. (iykyk, false positives are the enemy ๐)IgnoredFunctionsโ a semicolon-separated list of the names of functions or methods to be considered as not having side-effects. Regular expressions are accepted, e.g.[Rr]ef(erence)?$matches every type with suffixRef,ref,Referenceandreference. The default is empty. If a name in the list contains the sequence::it is matched against the qualified type name (i.e.namespace::Type), otherwise it is matched against only the type name (i.e.Type).
๐ bugprone-assignment-in-if-condition
Finds assignments within conditions of if statements. Such assignments are bug-prone because they may have been intended as equality tests. ๐ซ
This check finds all assignments within if conditions, including ones that are not flagged by -Wparentheses due to an extra set of parentheses, and including assignments that call an overloaded operator=(). The identified assignments violate BARR group "Rule 8.2.c" https://barrgroup.com/embedded-systems/books/embedded-c-coding-standard/statement-rules/if-else-statements.
int f = 3;
if(f = 4) { // This is identified by both `Wparentheses` and this check - should it have been: `if (f == 4)` ?
f = f + 1;
}
if((f == 5) || (f = 6)) { // the assignment here `(f = 6)` is identified by this check, but not by `-Wparentheses`. Should it have been `(f == 6)` ?
f = f + 2;
}
๐ bugprone-bad-signal-to-kill-thread
Finds pthread_kill function calls when a thread is terminated by raising SIGTERM signal and the signal kills the entire process, not just the individual thread. Use any signal except SIGTERM. Fr fr, this one's giving catastrophic. ๐ญ
pthread_kill(thread, SIGTERM);
This check corresponds to the CERT C Coding Standard rule POS44-C. Do not use signals to terminate threads https://wiki.sei.cmu.edu/confluence/display/c/POS44-C.+Do+not+use+signals+to+terminate+threads.
cert-pos44-c redirects here as an alias of this check. Slay, aliases. โจ
๐ฆ bugprone-bitwise-pointer-cast
Warns about code that tries to cast between pointers by means of std::bit_cast or memcpy. ๐
The motivation is that std::bit_cast is advertised as the safe alternative to type punning via reinterpret_cast in modern C++. However, one should not blindly replace reinterpret_cast with std::bit_cast, as follows:
int x{};
-float y = *reinterpret_cast<float*>(&x);
+float y = *std::bit_cast<float*>(&x);
The drop-in replacement behaves exactly the same as reinterpret_cast, and Undefined Behavior is still invoked. std::bit_cast is copying the bytes of the input pointer, not the pointee, into an output pointer of a different type, which may violate the strict aliasing rules. However, simply looking at the code, it looks "safe", because it uses std::bit_cast which is advertised as safe. It's giving fake-safe, bestie. ๐ญ
The solution to safe type punning is to apply std::bit_cast on value types, not on pointer types:
int x{};
float y = std::bit_cast<float>(x);
This way, the bytes of the input object are copied into the output object, which is much safer. Do note that Undefined Behavior can still occur, if there is no value of type To corresponding to the value representation produced. Compilers may be able to optimize this copy and generate identical assembly to the original reinterpret_cast version.
Code before C++20 may backport std::bit_cast by means of memcpy, or simply call memcpy directly, which is equally problematic. This is also detected by this check:
int* x{};
float* y{};
std::memcpy(&y, &x, sizeof(x));
Alternatively, if a cast between pointers is truly wanted, reinterpret_cast should be used, to clearly convey the intent and enable warnings from compilers and linters, which should be addressed accordingly. ๐
โญ bugprone-bool-pointer-implicit-conversion
Checks for conditions based on implicit conversion from a bool pointer to bool. ๐
Example:
bool *p;
if (p) {
// Never used in a pointer-specific way.
}
๐ bugprone-branch-clone
Checks for repeated branches in if/else if/else chains, consecutive repeated branches in switch statements and identical true and false branches in conditional operators. Copy-paste girlies, this one's for you. ๐ญ
if (test_value(x)) {
y++;
do_something(x, y);
} else {
y++;
do_something(x, y);
}
In this simple example (which could arise e.g. as a copy-paste error) the then and else branches are identical and the code is equivalent the following shorter and cleaner code:
test_value(x); // can be omitted unless it has side effects
y++;
do_something(x, y);
If this is the intended behavior, then there is no reason to use a conditional statement; otherwise the issue can be solved by fixing the branch that is handled incorrectly.
The check detects repeated branches in longer if/else if/else chains where it would be even harder to notice the problem. ๐
The check also detects repeated inner and outer if statements that may be a result of a copy-paste error. This check cannot currently detect identical inner and outer if statements if code is between the if conditions. An example is as follows.
void test_warn_inner_if_1(int x) {
if (x == 1) { // warns, if with identical inner if
if (x == 1) // inner if is here
;
if (x == 1) { // does not warn, cannot detect
int y = x;
if (x == 1)
;
}
}
In switch statements the check only reports repeated branches when they are consecutive, because it is relatively common that the case: labels have some natural ordering and rearranging them would decrease the readability of the code. For example:
switch (ch) {
case 'a':
return 10;
case 'A':
return 10;
case 'b':
return 11;
case 'B':
return 11;
default:
return 10;
}
Here the check reports that the 'a' and 'A' branches are identical (and that the 'b' and 'B' branches are also identical), but does not report that the default: branch is also identical to the first two branches. If this is indeed the correct behavior, then it could be implemented as:
switch (ch) {
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
default:
return 10;
}
Here the check does not warn for the repeated return 10;, which is good if we want to preserve that 'a' is before 'b' and default: is the last branch.
Switch cases marked with the [[fallthrough]] attribute are ignored.
Finally, the check also examines conditional operators and reports code like:
return test_value(x) ? x : x;
Unlike if statements, the check does not detect chains of conditional operators.
Note: This check also reports situations where branches become identical only after preprocessing. ๐ง
๐ bugprone-capturing-this-in-member-variable
Finds lambda captures that capture the this pointer and store it as class members without handle the copy and move constructors and the assignments. ๐ซถ
Capture this in a lambda and store it as a class member is dangerous because the lambda can outlive the object it captures. Especially when the object is copied or moved, the captured this pointer will be implicitly propagated to the new object. Most of the time, people will believe that the captured this pointer points to the new object, which will lead to bugs. And that's the plot twist nobody asked for. ๐ญ
struct C {
C() : Captured([this]() -> C const * { return this; }) {}
std::function<C const *()> Captured;
};
void foo() {
C v1{};
C v2 = v1; // v2.Captured capture v1's 'this' pointer
assert(v2.Captured() == v1.Captured()); // v2.Captured capture v1's 'this' pointer
assert(v2.Captured() == &v2); // assertion failed.
}
Possible fixes:
- marking copy and move constructors and assignment operators
deleted. - using class member method instead of class member variable with function object types.
- passing
thispointer as parameter
โจ Options โจ
FunctionWrapperTypesโ a semicolon-separated list of type names. It tells the check which function-wrapper types are allowed to hold lambda expressions, bestie. The default is::std::function;::std::move_only_function;::boost::function.BindFunctionsโ a semicolon-separated list of fully qualified function names that are allowed to capture thethispointer. The default is::std::bind;::boost::bind;::std::bind_front;::std::bind_back;::boost::compat::bind_front;::boost::compat::bind_back.
๐ bugprone-casting-through-void ๐
Okay so this check catches unsafe or redundant two-step casting operations that go through void*. Per the C++ Standard, that kind of cast is equivalent to reinterpret_cast (https://eel.is/c++draft/expr.reinterpret.cast#7).
Two-step type conversions via void* are discouraged for several reasons:
- They make code harder to read and less understandable, which makes maintenance way more painful.
- They bypass valuable compiler support and erase warnings about pointer alignment. They may violate the strict aliasing rule and lead to undefined behavior.
- With multiple inheritance, ambiguity and unexpected outcomes can pop off because type information gets lost, which can cause runtime issues.
TL;DR: avoid two-step type conversions through void* so your code stays clearer, keeps essential compiler warnings, and dodges ambiguity and potential runtime errorsโespecially in complex inheritance scenarios. If such a cast is wanted, it should be done via reinterpret_cast so the intent is more clearly expressed.
Heads-up: after you apply the suggested fix and usereinterpret_cast, itโs expected that the checkcppcoreguidelines-pro-type-reinterpret-castwill emit a warning. That is intentional:reinterpret_castis a dangerous operation that can easily break the strict aliasing rules when you dereference the casted pointer, invoking Undefined Behavior. The warning is there to prompt you to carefully analyze whether usingreinterpret_castis safeโand if it is, the warning may be suppressed.
Examples:
using IntegerPointer = int *;
double *ptr;
static_cast<IntegerPointer>(static_cast<void *>(ptr)); // WRONG
reinterpret_cast<IntegerPointer>(reinterpret_cast<void *>(ptr)); // WRONG
(IntegerPointer)(void *)ptr; // WRONG
IntegerPointer(static_cast<void *>(ptr)); // WRONG
reinterpret_cast<IntegerPointer>(ptr); // OK, clearly expresses intent.
// NOTE: dereferencing this pointer violates
// the strict aliasing rules, invoking
// Undefined Behavior.
๐ธ bugprone-chained-comparison ๐ธ
This check detects chained comparison operators that can lead to unintended behavior or logical errors.
Chained comparisons are expressions that use multiple comparison operators to compare three or more values. For example, the expression a < b < c compares the values of a, b, and c. But it does not evaluate as (a < b) && (b < c), which is probably what the developer intended. Instead, it evaluates as (a < b) < c, which may produce unintended resultsโespecially when the types of a, b, and c are different.
To avoid those errors, the check will issue a warning when it detects a chained comparison operator. It suggests using parentheses to specify the order of evaluation, or using a logical operator to separate the comparison expressions.
Consider the following examples:
int a = 2, b = 6, c = 4;
if (a < b < c) {
// This block will be executed
}
In this example, the developer intended to check if a is less than b and b is less than c. But the expression a < b < c is equivalent to (a < b) < c. Since a < b is true, the expression (a < b) < c is evaluated as 1 < c, which is equivalent to true < c and is invalid in this case because b < c is false.
Even though the above issue could be detected as comparison of int to bool, thereโs a more dangerous example:
bool a = false, b = false, c = true;
if (a == b == c) {
// This block will be executed
}
In this example, the developer intended to check if a, b, and c are all equal. But the expression a == b == c is evaluated as (a == b) == c. Since a == b is true, the expression (a == b) == c is evaluated as true == c, which is equivalent to true == true. This comparison yields true, even though a and b are false and are not equal to c.
To avoid this issue, the developer can use a logical operator to separate the comparison expressions, like this:
if (a == b && b == c) {
// This block will not be executed
}
Alternatively, using parentheses in the comparison expressions can make the developerโs intention more explicit and help avoid misunderstanding.
if ((a == b) == c) {
// This block will be executed
}
๐ฆ bugprone-compare-pointer-to-member-virtual-function ๐ฆ
This check detects unspecified behavior around equality comparison between a pointer to a member virtual function and anything other than a null-pointer constant.
struct A {
void f1();
void f2();
virtual void f3();
virtual void f4();
void g1(int);
};
void fn() {
bool r1 = (&A::f1 == &A::f2); // ok
bool r2 = (&A::f1 == &A::f3); // bugprone
bool r3 = (&A::f1 != &A::f3); // bugprone
bool r4 = (&A::f3 == nullptr); // ok
bool r5 = (&A::f3 == &A::f4); // bugprone
void (A::*v1)() = &A::f3;
bool r6 = (v1 == &A::f1); // bugprone
bool r6 = (v1 == nullptr); // ok
void (A::*v2)() = &A::f2;
bool r7 = (v2 == &A::f1); // false positive, but potential risk if assigning other value to v2.
void (A::*v3)(int) = &A::g1;
bool r8 = (v3 == &A::g1); // ok, no virtual function match void(A::*)(int) signature.
}
It provides warnings on equality comparisons that involve pointers to member virtual functions, or variables that are potential pointers to member virtual functions, and any entity other than a null-pointer constant.
In certain compilers, virtual function addresses are not conventional pointersโthey consist of offsets and indexes inside a virtual function table (vtable). So those pointers may vary between base and derived classes, leading to unpredictable behavior when compared directly. This gets especially challenging with pointers to pure virtual functions, because they may not even have a valid address, which complicates comparisons even more.
Instead, itโs recommended to use the typeid operator or other appropriate mechanisms for comparing objects so behavior stays robust and predictable. By heeding this detection and adopting a more reliable comparison method, you can mitigate potential issues related to unspecified behaviorโespecially when dealing with pointers to member virtual functions or pure virtual functionsโand improve the overall stability and maintainability of your code. In scenarios involving pointers to member virtual functions, itโs only advisable to use nullptr for comparisons.
๐ Limitations ๐
It does not analyze values stored in a variable. For a variable, it only analyzes all virtual methods in the same class or struct and diagnoses when assigning a pointer to member virtual function to this variable is possible.
๐ bugprone-copy-constructor-init ๐
This one finds copy constructors where the constructor doesnโt call the copy constructor of the base class.
class Copyable {
public:
Copyable() = default;
Copyable(const Copyable &) = default;
int memberToBeCopied = 0;
};
class X2 : public Copyable {
X2(const X2 &other) {} // Copyable(other) is missing
};
It also finds copy constructors where the constructor of the base class doesnโt have a parameter.
class X3 : public Copyable {
X3(const X3 &other) : Copyable() {} // other is missing
};
Failure to properly initialize base class sub-objects during copy construction can result in undefined behavior, crashes, data corruption, or other unexpected outcomes. This check makes sure the copy constructor of a derived class properly calls the copy constructor of the base class, helping prevent bugs and improve code quality.
Limitations:
- It wonโt generate warnings for empty classes, because there are no class members (including base class sub-objects) to worry about.
- It wonโt generate warnings for base classes that have a private or deleted copy constructor.
- It wonโt generate warnings for base classes that are initialized using another non-default constructor, because this could be intentional.
The check also suggests fix-its in some cases.
๐ง bugprone-crtp-constructor-accessibility ๐ง
Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP can be constructed outside itself and the derived class.
The CRTP is an idiom where a class derives from a template class, and itself is the template argument. If a class is intended to be a base class in this idiom, make sure it can only be instantiated when the derived class is its template argument.
Example:
template <typename T> class CRTP {
private:
CRTP() = default;
friend T;
};
class Derived : CRTP<Derived> {};
Below are some common mistakes that will allow the idiom to be broken.
If the constructor of a class intended to be used in a CRTP is public, then users can construct that class on its own.
Example:
template <typename T> class CRTP {
public:
CRTP() = default;
};
class Good : CRTP<Good> {};
Good GoodInstance;
CRTP<int> BadInstance;
If the constructor is protected, the possibility of accidental instantiation is prevented. However, it can hide an error when a different class is used as the template parameter instead of the derived one.
Example:
template <typename T> class CRTP {
protected:
CRTP() = default;
};
class Good : CRTP<Good> {};
Good GoodInstance;
class Bad : CRTP<Good> {};
Bad BadInstance;
To ensure no accidental instantiation happens, the best practice is to make the constructor private and declare the derived class as a friend. Note that as a tradeoff, this also gives the derived class access to every other private member of the CRTP. However, constructors can still be public or protected if they are deleted.
Example:
template <typename T> class CRTP {
CRTP() = default;
friend T;
};
class Good : CRTP<Good> {};
Good GoodInstance;
class Bad : CRTP<Good> {};
Bad CompileTimeError;
CRTP<int> AlsoCompileTimeError;
Limitations
Bestie, this check has some gaps, and we're gonna be honest about them:
- It's not supported below C++11 โ periodt, don't even try.
- It does not handle the case where the derived class gets passed as a variadic template argument.
- Accessible functions that can construct the CRTP โ like factory functions โ are not checked. ๐
The check also suggests fix-its in some cases. โจ
bugprone-dangling-handle
Okay this one is so important. It detects dangling references in value handles like std::string_view. ๐
Basically: these dangling references can happen when you construct a handle from a temporary value โ and then that temporary gets destroyed, like, immediately after the handle is created. The handle is left pointing at nothing and it's giving ghost town. ๐ป
Examples:
string_view View = string(); // View will dangle.
string A;
View = A + "A"; // still dangle.
vector<string_view> V;
V.push_back(string()); // V[0] is dangling.
V.resize(3, string()); // V[1] and V[2] will also dangle.
string_view f() {
// All these return values will dangle.
return string();
string S;
return S;
char Array[10]{};
return Array;
}
span<int> g() {
array<int, 1> V;
return {V};
int Array[10]{};
return {Array};
}
Options
HandleClassesโ a semicolon-separated list of class names that should be treated as handles. By default, onlystd::basic_string_view,std::experimental::basic_string_viewandstd::spanare considered. ๐
bugprone-dynamic-static-initializers
This one finds instances of static variables that are dynamically initialized in header files. Yep, in headers. ๐ญ
Why do we care? Because this can pose problems in certain multithreaded contexts. For example, when you disable compiler-generated synchronization instructions for static variables initialized at runtime (e.g. by -fno-threadsafe-statics), even if a particular project takes the necessary precautions to prevent race conditions during initialization by providing their own synchronization, header files included from other projects may not. So this check is genuinely helpful for making sure that disabling compiler-generated synchronization for static variable initialization won't cause problems. ๐ซถ
Consider the following code:
int foo() {
static int k = bar();
return k;
}
When synchronization of static initialization is disabled, if two threads both call foo for the first time, there is the possibility that k will be double initialized โ creating a race condition. Not the vibe. ๐
bugprone-easily-swappable-parameters
Bestie. This check finds function definitions where parameters of convertible types follow each other directly โ which makes call sites prone to calling the function with swapped (or badly ordered) arguments. ๐ญ
void drawPoint(int X, int Y) { /* ... */ }
FILE *open(const char *Dir, const char *Name, Flags Mode) { /* ... */ }
A potential call like drawPoint(-2, 5) or openPath("a.txt", "tmp", Read) is perfectly legal from the language's perspective โ but it might not be what the developer of the function intended. ๐
More elaborate and type-safe constructs, such as opaque typedefs or strong types, should be used instead, to prevent a mistaken order of arguments:
struct Coord2D { int X; int Y; };
void drawPoint(const Coord2D Pos) { /* ... */ }
FILE *open(const Path &Dir, const Filename &Name, Flags Mode) { /* ... */ }
Because of the potentially elaborate refactoring and API-breaking that would be necessary to strengthen the type safety of a project, no automatic fix-its are offered. Ngl, that's fair. ๐
Options
Extension/relaxation options
Relaxation (or extension) options can be used to broaden the scope of the analysis and fine-tune the enabling of more mixes between types. Some mixes may depend on coding style or preference specific to a project โ however, it should be noted that enabling all of these relaxations models the way of mixing at call sites the most. These options are expected to make the check report for more functions, and report longer mixable ranges. ๐ฅ
QualifiersMixโ whether to consider parameters of some cvr-qualifiedTand a differently cvr-qualifiedT(i.e.Tandconst T,const Tandvolatile T, etc.) mixable between one another. Iffalse, the check will consider differently qualified types unmixable.Trueturns the warnings on. Defaults tofalse.
The following example produces a diagnostic only if QualifiersMix is enabled:
void *memcpy(const void *Destination, void *Source, std::size_t N) { /* ... */ }
ModelImplicitConversionsโ whether to consider parameters of typeTandUmixable if there exists an implicit conversion fromTtoUandUtoT. Iffalse, the check will not consider implicitly convertible types for mixability.Trueturns warnings for implicit conversions on. Defaults totrue.
The following examples produce a diagnostic only if ModelImplicitConversions is enabled:
void fun(int Int, double Double) { /* ... */ }
void compare(const char *CharBuf, std::string String) { /* ... */ }
Note: changing the qualifiers of an expression's type (e.g. frominttoconst int) is defined as an implicit conversion in the C++ Standard. However, the check separates this decision-making on the mixability of differently qualified types based on whetherQualifiersMixwas enabled. ๐ง For example, the following code snippet will only produce a diagnostic if bothQualifiersMixandModelImplicitConversionsare enabled:cpp void fun2(int Int, const double Double) { /* ... */ }
Filtering options
Filtering options can be used to lessen the size of the diagnostics emitted by the checker โ whether the aim is to ignore certain constructs or dampen the noisiness. ๐
MinimumLengthโ the minimum length required from an adjacent parameter sequence to be diagnosed. Defaults to2. Might be any positive integer greater or equal to2. If0or1is given, the default value2will be used instead.
For example, if 3 is specified, the examples above will not be matched.
IgnoredParameterNamesโ the list of parameter names that should never be considered part of a swappable adjacent parameter sequence. The value is a;-separated list of names. To ignore unnamed parameters, add""to the list verbatim (not the empty string, but the two quotes โ potentially escaped!). This option is case-sensitive!
By default, the following parameter names, and their Uppercase-initial variants, are ignored: "" (unnamed parameters), iterator, begin, end, first, last, lhs, rhs.
IgnoredParameterTypeSuffixesโ the list of parameter type name suffixes that should never be considered part of a swappable adjacent parameter sequence. Parameters whose type, as written in the source code, ends with an element of this option will be ignored. The value is a;-separated list of names. This option is case-sensitive!
By default, the following, and their lowercase-initial variants, are ignored: bool, It, Iterator, InputIt, ForwardIt, BidirIt, RandomIt, random_iterator, ReverseIt, reverse_iterator, reverse_const_iterator, RandomIt, random_iterator, ReverseIt, reverse_iterator, reverse_const_iterator, Const_Iterator, ConstIterator, const_reverse_iterator, ConstReverseIterator. In addition, _Bool (but not _bool) is also part of the default value. โจ
SuppressParametersUsedTogetherโ suppresses diagnostics about parameters that are used together or in a similar fashion inside the function's body. Defaults totrue. Specifyingfalsewill turn off the heuristics.
Currently, the following heuristics are implemented, which will suppress the warning about the parameter pair involved:
- The parameters are used in the same expression, e.g.
f(a, b)ora < b. - The parameters are further passed to the same function to the same parameter of that function, of the same overload. E.g.
f(a, 1)andf(b, 2)to somef(T, int).
Note: the check does not perform path-sensitive analysis, and as such, "same function" in this context means the same function declaration. If the same member function of a type on two distinct instances are called with the parameters, it will still be regarded as "same function". ๐
- The same member field is accessed, or member method is called, of the two parameters, e.g.
a.foo()andb.foo(). - Separate return statements return either of the parameters on different code paths.
NamePrefixSuffixSilenceDissimilarityTresholdโ the number of characters two parameter names might be different on either the head or the tail end, with the rest of the name the same, so that the warning about the two parameters is silenced. Defaults to1. Might be any positive integer. If0, the filtering heuristic based on the parameters' names is turned off.
This option can be used to silence warnings about parameters where the naming scheme indicates that the order of those parameters does not matter. ๐ฆ
For example, the parameters LHS and RHS are 1-dissimilar suffixes of each other: L and R is the different character, while HS is the common suffix. Similarly, parameters text1, text2, text3 are 1-dissimilar prefixes of each other, with the numbers at the end being the dissimilar part. If the value is at least 1, such cases will not be reported.
Limitations
Lowkey big disclaimer incoming: this check is designed to check function signatures! ๐
The check does not investigate functions that are generated by the compiler in a context that is only determined from a call site. These cases include variadic functions, functions in C code that do not have an argument list, and C++ template instantiations. Most of these cases โ which are otherwise swappable from a caller's standpoint โ have no way of getting "fixed" at the definition point. In the case of C++ templates, only primary template definitions and explicit specializations are matched and analyzed. ๐ง
None of the following cases produce a diagnostic:
int printf(const char *Format, ...) { /* ... */ }
int someOldCFunction() { /* ... */ }
template <typename T, typename U>
int add(T X, U Y) { return X + Y };
okay so these next snippets are the ones that do not get warned about, and honestly the reason why is kind of important ๐
void theseAreNotWarnedAbout() {
printf("%d %d\n", 1, 2); // Two ints passed, they could be swapped.
someOldCFunction(1, 2, 3); // Similarly, multiple ints passed.
add(1, 2); // Instantiates 'add<int, int>', but that's not a user-defined function.
}
Because of that limitation up there, if a parameterโs type is further dependent on template instantiations to prove that it mixes with another parameterโs type, the check just does not diagnose it. Itโs giving โcanโt prove it, wonโt flag itโ energy, fr fr.
template <typename T>
struct Vector {
typedef T element_type;
};
// Diagnosed: Explicit instantiation was done by the user, we can prove it
// is the same type.
void instantiated(int A, Vector<int>::element_type B) { /* ... */ }
// Diagnosed: The two parameter types are exactly the same.
template <typename T>
void exact(typename Vector<T>::element_type A,
typename Vector<T>::element_type B) { /* ... */ }
// Skipped: The two parameters are both 'T' but we cannot prove this
// without actually instantiating.
template <typename T>
void falseNegative(T A, typename Vector<T>::element_type B) { /* ... */ }
Now, when weโre in the context of implicit conversions โ thatโs when ModelImplicitConversions is enabled โ the modelling that this check does will warn if the parameters are swappable and the swapped order matches implicit conversions. But it does not model whether thereโs some unrelated third type that both parameters could be given from in a function call. So in the example below, even though strs() clearly has the possibility of being called with swapped arguments (as long as the arguments are string literals), it will not be warned about. Thatโs just the limitation, no cap.
struct String {
String(const char *Buf);
};
struct StringView {
StringView(const char *Buf);
operator const char *() const;
};
// Skipped: Directly swapping expressions of the two type cannot mix.
// (Note: StringView -> const char * -> String would be **two**
// user-defined conversions, which is disallowed by the language.)
void strs(String Str, StringView SV) { /* ... */ }
// Diagnosed: StringView implicitly converts to and from a buffer.
void cStr(StringView SV, const char *Buf() { /* ... */ }
๐ bugprone-empty-catch โ no swallowing allowed
This check detects empty catch statements and suggests you actually address them. Itโs giving โswallowing exceptionsโ and bestie, we do not swallow exceptions in this house ๐
try {
// Some code that can throw an exception
} catch(const std::exception&) {
}
Empty catch statements in a codebase can be a serious problem that devs should be aware of. Catch statements are used to handle exceptions that are thrown during program execution. When an exception is thrown, the program jumps to the nearest catch statement that matches the type of the exception.
Empty catch statements โ also known as โswallowingโ exceptions โ catch the exception but do nothing with it. That means the exception is not handled properly, and the program continues to run as if nothing happened. This can lead to several issues, such as:
- Hidden Bugs: If an exception is caught and ignored, it can lead to hidden bugs that are difficult to diagnose and fix. The root cause of the problem may not be apparent, and the program may continue to behave in unexpected ways.
- Security Issues: Ignoring exceptions can lead to security issues, such as buffer overflows or null pointer dereferences. Hackers can exploit these vulnerabilities to gain access to sensitive data or execute malicious code.
- Poor Code Quality: Empty catch statements can indicate poor code quality and a lack of attention to detail. This can make the codebase difficult to maintain and update, leading to longer development cycles and increased costs.
- Unreliable Code: Code that ignores exceptions is often unreliable and can lead to unpredictable behavior. This can cause frustration for users and erode trust in the software.
To avoid these issues, devs should always handle exceptions properly. This means either fixing the underlying issue that caused the exception or propagating the exception up the call stack to a higher-level handler. If an exception is not important, it should still be logged or reported in some way so that it can be tracked and addressed later.
If the exception is something that can be handled locally, then it should be handled within the catch block. This could involve logging the exception or taking other appropriate action to ensure that the exception is not ignored. Hereโs an example:
try {
// Some code that can throw an exception
} catch (const std::exception& ex) {
// Properly handle the exception, e.g.:
std::cerr << "Exception caught: " << ex.what() << std::endl;
}
If the exception cannot be handled locally and needs to be propagated up the call stack, it should be re-thrown or a new exception should be thrown. Hereโs an example:
try {
// Some code that can throw an exception
} catch (const std::exception& ex) {
// Re-throw the exception
throw;
}
In some cases, catching the exception at this level may not be necessary, and it may be appropriate to let the exception propagate up the call stack. This can be done simply by not using a try/catch block. Hereโs an example:
void function() {
// Some code that can throw an exception
}
void callerFunction() {
try {
function();
} catch (const std::exception& ex) {
// Handling exception on higher level
std::cerr << "Exception caught: " << ex.what() << std::endl;
}
}
Another potential solution to avoid empty catch statements is to modify the code to avoid throwing the exception in the first place. This can be achieved by using a different API, checking for error conditions beforehand, or handling errors in a different way that does not involve exceptions. By eliminating the need for try-catch blocks, the code becomes simpler and less error-prone. Hereโs an example:
// Old code:
try {
mapContainer["Key"].callFunction();
} catch(const std::out_of_range&) {
}
// New code
if (auto it = mapContainer.find("Key"); it != mapContainer.end()) {
it->second.callFunction();
}
In conclusion, empty catch statements are a bad practice that can lead to hidden bugs, security issues, poor code quality, and unreliable code. By handling exceptions properly, devs can ensure that their code is robust, secure, and maintainable. Periodt. โจ
Options โจ
IgnoreCatchWithKeywordsโ This option lets you ignore specific catch statements that contain certain keywords. If a catch statement body contains โ case-insensitive โ any of the keywords listed in this semicolon-separated option, then the catch will be ignored, and no warning will be raised. Default value:@TODO;@FIXME.AllowEmptyCatchForExceptionsโ This option lets you ignore empty catch statements for specific exception types. By default, the check will raise a warning if an empty catch statement is detected, regardless of the type of exception being caught. But in certain situations, like when a dev wants to intentionally ignore certain exceptions or handle them in a different way, it may be desirable to allow empty catch statements for specific exception types. To configure this option, provide a semicolon-separated list of exception type names. If an exception type name in the list is caught in an empty catch statement, no warning will be raised. Default value: empty string.
๐ฅ bugprone-exception-escape โ the no-throw zone
This check finds functions which may throw an exception directly or indirectly, but they should not. The functions which should not throw exceptions are the following:
- Destructors
- Move constructors
- Move assignment operators
- The
main()functions swap()functionsiter_swap()functionsiter_move()functions- Functions marked with
throw()ornoexcept - Other functions given as option
A destructor throwing an exception may result in undefined behavior, resource leaks, or unexpected termination of the program. Throwing a move constructor or move assignment also may result in undefined behavior or a resource leak. The swap() operations are expected to be non-throwing in most cases, and they are always possible to implement in a non-throwing way. Non-throwing swap() operations are also used to create move operations. A throwing main() function also results in unexpected termination.
Functions declared explicitly with noexcept(false) or throw(exception) will be excluded from the analysis. Even though itโs not recommended for functions like swap(), main(), move constructors, move assignment operators, and destructors, itโs a clear indication of the developerโs intention and should be respected. iykyk.
โ ๏ธ WARNING! This check may be expensive on large source files. Like, it can be pricey on big files, so maybe donโt run it on a whole monorepo unless youโre ready.
Options โจ
FunctionsThatShouldNotThrowโ Comma-separated list containing function names which should not throw. An example value for this parameter can beWinMain, which adds functionWinMain()in the Windows API to the list of the functions which should not throw. Default value is an empty string.IgnoredExceptionsโ Comma-separated list containing type names which are not counted as thrown exceptions in the check. Default value is an empty string.
๐ธ bugprone-fold-init-type โ precision loss in folds
This check flags type mismatches in folds โ like the Wikipedia page https://en.wikipedia.org/wiki/Fold_(higher-order_function) if youโre curious โ such as std::accumulate that might result in loss of precision. std::accumulate folds an input range into an initial value using the type of the latter, with operator+ by default. This can cause loss of precision through:
- Truncation: The following code uses a floating point range and an
intinitial value, so truncation will happen at every application ofoperator+and the result will be0, which might not be what the user expected.
auto a = {0.5f, 0.5f, 0.5f, 0.5f};
return std::accumulate(std::begin(a), std::end(a), 0);
- Overflow: The following code also returns
0.
auto a = {65536LL * 65536 * 65536};
return std::accumulate(std::begin(a), std::end(a), 0);
๐ฆ bugprone-forward-declaration-namespace โ namespace vibe check
This check looks at whether an unused forward declaration is hanging out in the wrong namespace. Thatโs the whole vibe check. Simple as that. ๐
The check inspects all unused forward declarations and checks if there is any declaration/definition with the same name existing, which could indicate that the forward declaration is in a potentially wrong namespace.
namespace na { struct A; }
namespace nb { struct A {}; }
nb::A a;
// warning : no definition found for 'A', but a definition with the same name
// 'A' found in another namespace 'nb::'
Bestie, heads up: this check can only generate warnings โ it can't suggest a fix at this point. So it's giving "noticed a vibe issue" energy, not "here's the patch" energy. ๐
bugprone-forwarding-reference-overload ๐
This one looks for perfect forwarding constructors that can hide copy or move constructors. Like, imagine a non const lvalue reference gets passed to the constructor โ the forwarding reference parameter is a better match than the const reference parameter of the copy constructor, so the perfect forwarding constructor gets called instead, which is highkey confusing. ngl, for a detailed description of this issue see: Scott Meyers, Effective Modern C++, Item 26.
Consider the following example:
class Person {
public:
// C1: perfect forwarding ctor
template<typename T>
explicit Person(T&& n) {}
// C2: perfect forwarding ctor with parameter default value
template<typename T>
explicit Person(T&& n, int x = 1) {}
// C3: perfect forwarding ctor guarded with enable_if
template<typename T, typename X = enable_if_t<is_special<T>, void>>
explicit Person(T&& n) {}
// C4: variadic perfect forwarding ctor guarded with enable_if
template<typename... A,
enable_if_t<is_constructible_v<tuple<string, int>, A&&...>, int> = 0>
explicit Person(A&&... a) {}
// C5: perfect forwarding ctor guarded with requires expression
template<typename T>
requires requires { is_special<T>; }
explicit Person(T&& n) {}
// C6: perfect forwarding ctor guarded with concept requirement
template<Special T>
explicit Person(T&& n) {}
// (possibly compiler generated) copy ctor
Person(const Person& rhs);
};
So the check warns for constructors C1 and C2, because those can hide copy and move constructors. ๐ซถ
We suppress warnings if the copy and the move constructors are both disabled (deleted or private) โ because there's literally nothing the perfect forwarding constructor could hide in that case. We also suppress warnings for constructors like C3โC6 that are guarded with an enable_if or a concept, assuming the programmer was aware of the possible hiding. Respect the guard. โจ
Background
For deciding whether a constructor is guarded with enable_if, we consider the types of the constructor parameters, the default values of template type parameters, and the types of non-type template parameters with a default literal value. If any part of these types is std::enable_if or std::enable_if_t, we assume the constructor is guarded. Periodt.
bugprone-implicit-widening-of-multiplication-result ๐
The check diagnoses instances where a result of a multiplication is implicitly widened, and suggests (with fix-it) to either silence the code by making the widening explicit, or to perform the multiplication in a wider type, to avoid the widening afterwards.
This is mainly useful when operating on very large buffers. For example, consider:
void zeroinit(char* base, unsigned width, unsigned height) {
for(unsigned row = 0; row != height; ++row) {
for(unsigned col = 0; col != width; ++col) {
char* ptr = base + row * width + col;
*ptr = 0;
}
}
}
This is fine in general, but if width * height overflows, you end up wrapping back to the beginning of base instead of processing the entire requested buffer. ๐ญ Not the slay we wanted.
Indeed, this only matters for pretty large buffers (4GB+), but that can happen very easily โ for example in image processing, where for that to happen you "only" need a ~269MPix image. ๐ผ๏ธ
Options
UseCXXStaticCastsInCppSourcesโ when suggesting fix-its for C++ code, should C++-stylestatic_cast<>()s be suggested, or C-style casts. Defaults totrue.UseCXXHeadersInCppSourcesโ when suggesting to include the appropriate header in C++ code, should<cstddef>header be suggested, or<stddef.h>. Defaults totrue.IgnoreConstantIntExprโ if the multiplication operands are compile-time constants (like literals, or are constexpr) and fit within the source expression type, do not emit a diagnostic or suggested fix. Only considers expressions where the source expression is a signed integer type. Defaults tofalse.
Examples:
long mul(int a, int b) {
return a * b; // warning: performing an implicit widening conversion to type 'long' of a multiplication performed in type 'int'
}
char* ptr_add(char *base, int a, int b) {
return base + a * b; // warning: result of multiplication in type 'int' is used as a pointer offset after an implicit widening conversion to type 'ssize_t'
}
char ptr_subscript(char *base, int a, int b) {
return base[a * b]; // warning: result of multiplication in type 'int' is used as a pointer offset after an implicit widening conversion to type 'ssize_t'
}
bugprone-inaccurate-erase ๐งฝ
Checks for inaccurate use of the erase() method.
Algorithms like remove() do not actually remove any element from the container โ they just return an iterator to the first redundant element at the end of the container. Those redundant elements must be removed using the erase() method. This check warns when not all of the elements will be removed, because you used an inappropriate overload. ๐
For example, the following code erases only one element:
std::vector<int> xs;
...
xs.erase(std::remove(xs.begin(), xs.end(), 10));
Call the two-argument overload of erase() to remove the subrange:
std::vector<int> xs;
...
xs.erase(std::remove(xs.begin(), xs.end(), 10), xs.end());
See how the second one ate and left no crumbs? That's the one. ๐
bugprone-inc-dec-in-conditions โ๏ธ
Detects when a variable is both incremented/decremented and referenced inside a complex condition, and suggests moving them outside to avoid ambiguity in the variable's value.
When a variable is modified and also used in a complex condition, it can lead to unexpected behavior. The side-effect of changing the variable's value within the condition can make the code difficult to reason about. Additionally, the developer's intended timing for the modification of the variable may not be clear, leading to misunderstandings and errors. This can be particularly problematic when the condition involves logical operators like && and ||, where the order of evaluation can further complicate the situation. ๐ตโ๐ซ
Consider the following example:
int i = 0;
// ...
if (i++ < 5 && i > 0) {
// do something
}
In this example, the result of the expression may not be what the developer intended. The original intention of the developer could be to increment i after the entire condition is evaluated, but in reality, i will be incremented before i > 0 is executed. This can lead to unexpected behavior and bugs in the code. To fix this issue, the developer should separate the increment operation from the condition and perform it separately. For example, they can increment i in a separate statement before or after the condition is evaluated. This ensures that the value of i is predictable and consistent throughout the code. โจ
int i = 0;
// ...
i++;
if (i <= 5 && i > 0) {
// do something
}
Another common issue occurs when multiple increments or decrements are performed on the same variable inside a complex condition. For example:
int i = 4;
// ...
if (i++ < 5 || --i > 2) {
// do something
}
There is a potential issue with this code due to the order of evaluation in C++. The || operator used in the condition statement guarantees that if the first operand evaluates to true, the second operand will not be evaluated. This means that if i were initially 4, the first operand i < 5 would evaluate to true and the second operand i > 2 would not be evaluated. As a result, the decrement operation --i would not be executed and i would hold value 5, which may not be the intended behavior for the developer. ๐
To avoid this potential issue, the both increment and decrement operation on i should be moved outside the condition statement.
bugprone-incorrect-enable-if ๐ง
Detects incorrect usages of std::enable_if that don't name the nested type type.
In C++11 introduced std::enable_if as a convenient way to leverage SFINAE. One form of using std::enable_if is to declare an unnamed template type parameter with a default type equal to typename std::enable_if<condition>::type. If the author forgets to name the nested type type, then the code will always consider the candidate template even if the condition is not met. ๐ฌ
Below are some examples of code using std::enable_if correctly and incorrect examples that this check flags.
template <typename T, typename = typename std::enable_if<T::some_trait>::type>
void valid_usage() { ... }
template <typename T, typename = std::enable_if_t<T::some_trait>>
void valid_usage_with_trait_helpers() { ... }
// The below code is not a correct application of SFINAE. Even if
// T::some_trait is not true, the function will still be considered in the
// set of function candidates. It can either incorrectly select the function
// when it should not be a candidates, and/or lead to hard compile errors
// if the body of the template does not compile if the condition is not
// satisfied.
template <typename T, typename = std::enable_if<T::some_trait>>
void invalid_usage() { ... }
// The tool suggests the following replacement for 'invalid_usage':
template <typename T, typename = typename std::enable_if<T::some_trait>::type>
void fixed_invalid_usage() { ... }
C++14 introduced the trait helper std::enable_if_t which reduces the likelihood of this error. C++20 introduces constraints, which generally supersede the use of std::enable_if. See modernize-type-traits <> for another tool that will replace std::enable_if with std::enable_if_t, and see modernize-use-constraints <> for another tool that replaces std::enable_if with C++20 constraints. Consider these newer mechanisms where possible. ๐
bugprone-incorrect-enable-shared-from-this ๐ช
Detect classes or structs that do not publicly inherit from std::enable_shared_from_this, because unintended behavior will otherwise occur when calling shared_from_this.
Consider the following code:
#include <memory>
So, continuing from that inheritance situation: this is the code that gets messy when shared_from_this is used with private inheritance. ๐
// private inheritance
class BadExample : std::enable_shared_from_this<BadExample> {
// ``shared_from_this``` unintended behaviour
// `libstdc++` implementation returns uninitialized ``weak_ptr``
public:
BadExample* foo() { return shared_from_this().get(); }
void bar() { return; }
};
void using_not_public() {
auto bad_example = std::make_shared<BadExample>();
auto* b_ex = bad_example->foo();
b_ex->bar();
}
With the libstdc++ implementation, shared_from_this will throw std::bad_weak_ptr. When using_not_public() is called, this code will crash without exception handling. ๐ญ
๐ bugprone-incorrect-roundings
This one checks for patterns that are known to produce incorrect rounding. Programmers often reach for this:
(int)(double_expression + 0.5)
to round the double expression to an integer. The problem with it is:
- It is unnecessarily slow.
- It is incorrect. The number
0.499999975(smallest representable float number below0.5) rounds to1.0. Even worse behavior for negative numbers where both-0.5fand-1.4fboth round to0.0.
๐ฆ bugprone-infinite-loop
Finds obvious infinite loops โ loops where the condition variable is not changed at all. ๐
Finding infinite loops is well-known to be impossible (halting problem). However, it is possible to detect some obvious infinite loops, for example, if the loop condition is not changed. This check detects such loops.
A loop is considered infinite if it does not have any loop exit statement (break, continue, goto, return, throw or a call to a function called as [[noreturn]]) and all of the following conditions hold for every variable in the condition:
- It is a local variable.
- It has no reference or pointer aliases.
- It is not a structure or class member.
Furthermore, the condition must not contain a function call to consider the loop infinite since functions may return different values for different calls.
Example: this loop counts as infinite because i is not changed in the body:
int i = 0, j = 0;
while (i < 10) {
++j;
}
๐
bugprone-integer-division
Finds cases where integer division in a floating point context is likely to cause unintended loss of precision. ๐ฌ
No reports are made if divisions are part of the following expressions:
- operands of operators expecting integral or
booltypes, - call expressions of integral or
booltypes, and - explicit cast expressions to integral or
booltypes,
as these are interpreted as signs of deliberateness from the programmer.
Examples:
float floatFunc(float);
int intFunc(int);
double d;
int i = 42;
// Warn, floating-point values expected.
d = 32 * 8 / (2 + i);
d = 8 * floatFunc(1 + 7 / 2);
d = i / (1 << 4);
// OK, no integer division.
d = 32 * 8.0 / (2 + i);
d = 8 * floatFunc(1 + 7.0 / 2);
d = (double)i / (1 << 4);
// OK, there are signs of deliberateness.
d = 1 << (i / 2);
d = 9 + intFunc(6 * i / 32);
d = (int)(i / 32) - 8;
๐ธ bugprone-lambda-function-name
Checks for attempts to get the name of a function from within a lambda expression. The name of a lambda is always something like operator(), which is almost never what was intended. ๐
Example:
void FancyFunction() {
[] { printf("Called from %s\n", __func__); }();
[] { printf("Now called from %s\n", __FUNCTION__); }();
}
Output:
Called from operator()
Now called from operator()
Likely intended output:
Called from FancyFunction
Now called from FancyFunction
โ๏ธ Options
IgnoreMacrosโ The valuetruespecifies that attempting to get the name of a function from within a macro should not be diagnosed. The default value isfalse.
๐ซถ bugprone-macro-parentheses
Finds macros that can have unexpected behavior due to missing parentheses. ๐
Macros are expanded by the preprocessor as-is. As a result, there can be unexpected behavior; operators may be evaluated in unexpected order and unary operators may become binary operators, etc.
When the replacement list has an expression, it is recommended to surround it with parentheses. This ensures that the macro result is evaluated completely before it is used.
It is also recommended to surround macro arguments in the replacement list with parentheses. This ensures that the argument value is calculated properly.
This check corresponds to the CERT C Coding Standard rule PRE20-C โ โMacro replacement lists should be parenthesized.โ The page gives this URL exactly: <https://wiki.sei.cmu.edu/confluence/display/c/PRE02-C.+Macro+replacement+lists+should+be+parenthesized>.
๐ bugprone-macro-repeated-side-effects
Checks for repeated argument with side effects in macros. ๐ฅ
๐ bugprone-misleading-setter-of-reference
Finds setter-like member functions that take a pointer parameter and set a reference member of the same class with the pointed value.
The check detects member functions that take a single pointer parameter, and contain a single expression statement that dereferences the parameter and assigns the result to a data member with a reference type.
The fact that a setter function takes a pointer might cause the belief that an internal reference (if it would be a pointer) is changed instead of the pointed-to (or referenced) value.
Example:
class MyClass {
int &InternalRef; // non-const reference member
public:
MyClass(int &Value) : InternalRef(Value) {}
// Warning: This setter could lead to unintended behaviour.
void setRef(int *Value) {
InternalRef = *Value; // This assigns to the referenced value, not changing what InternalRef references.
}
};
int main() {
int Value1 = 42;
int Value2 = 100;
MyClass X(Value1);
// This might look like it changes what InternalRef references to,
// but it actually modifies Value1 to be 100.
X.setRef(&Value2);
}
Possible fixes:
- Change the parameter type of the
"set"function to non-pointer type (for example, a const reference). - Change the type of the member variable to a pointer and in the
"set"function assign a value to the pointer (without dereference).
๐ง bugprone-misplaced-operator-in-strlen-in-alloc
Finds cases where 1 is added to the string in the argument to strlen(), strnlen(), strnlen_s(), wcslen(), wcsnlen(), and wcsnlen_s() instead of the result and the value is used as an argument to a memory allocation function (malloc(), calloc(), realloc(), alloca()) or the new[] operator in C++. The check detects error cases even if one of these functions (except the new[] operator) is called by a constant function pointer. Cases where 1 is added both to the parameter and the result of the strlen()-like function are ignored, as are cases where the whole addition is surrounded by extra parentheses.
C example code:
void bad_malloc(char *str) {
char *c = (char*) malloc(strlen(str + 1));
}
The suggested fix is to add 1 to the return value of strlen() and not to its argument. In the example above the fix would be
char *c = (char*) malloc(strlen(str) + 1);
C++ example code:
void bad_new(char *str) {
char *c = new char[strlen(str + 1)];
}
As in the C code with the malloc() function, the suggested fix is to add 1 to the return value of strlen() and not to its argument. In the example above the fix would be
char *c = new char[strlen(str) + 1];
Example for silencing the diagnostic:
void bad_malloc(char *str) {
char *c = (char*) malloc(strlen((str + 1)));
}
๐ bugprone-misplaced-pointer-arithmetic-in-alloc
Finds cases where an integer expression is added to or subtracted from the result of a memory allocation function (malloc(), calloc(), realloc(), alloca()) instead of its argument. The check detects error cases even if one of these functions is called by a constant function pointer.
Example code:
void bad_malloc(int n) {
char *p = (char*) malloc(n) + 10;
}
The suggested fix is to add the integer expression to the argument of malloc and not to its result. In the example above the fix would be
char *p = (char*) malloc(n + 10);
๐ bugprone-misplaced-widening-cast
This check will warn when there is a cast of a calculation result to a bigger type. If the intention of the cast is to avoid loss of precision then the cast is misplaced, and there can be loss of precision. Otherwise the cast is ineffective.
Example code:
long f(int x) {
return (long)(x * 1000);
}
The result x * 1000 is first calculated using int precision. If the result exceeds int precision there is loss of precision. Then the result is casted to long.
If there is no loss of precision then the cast can be removed or you can explicitly cast to int instead.
If you want to avoid loss of precision then put the cast in a proper location, for instance:
long f(int x) {
return (long)x * 1000;
}
๐ Implicit casts
Forgetting to place the cast at all is at least as dangerous and at least as common as misplacing it. If CheckImplicitCasts is enabled the check also detects these cases, for instance:
long f(int x) {
return x * 1000;
}
๐ธ Floating point
Currently warnings are only written for integer conversion. No warning is written for this code:
double f(float x) {
return (double)(x * 10.0f);
}
โ๏ธ Options
CheckImplicitCastsโ Iftrue, enables detection of implicit casts. Default isfalse.
โญ bugprone-move-forwarding-reference
Warns if std::move is called on a forwarding reference, for example:
template <typename T>
void foo(T&& t) {
bar(std::move(t));
}
Forwarding references (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf) should typically be passed to std::forward instead of std::move, and this is the fix that will be suggested.
(A forwarding reference is an rvalue reference of a type that is a deduced function template argument.)
In this example, the suggested fix would be
bar(std::forward<T>(t));
๐ Background
Code like the example above is sometimes written with the expectation that T&& will always end up being an rvalue reference, no matter what type is deduced for T, and that it is therefore not possible to pass an lvalue to foo(). However, this is not true. Consider this example:
std::string s = "Hello, world";
foo(s);
okay so this code compiles and โ after the call to foo() โ s is left in an indeterminate state because it has been moved from. This may be surprising to the caller of foo() because no std::move was used when calling foo().
The reason for this behavior lies in the special rule for template argument deduction on function templates like foo() โ i.e. on function templates that take an rvalue reference argument of a type that is a deduced function template argument. (See section [temp.deduct.call]/3 in the C++11 standard.)
If foo() is called on an lvalue (as in the example above), then T is deduced to be an lvalue reference. In the example, T is deduced to be std::string &. The type of the argument t therefore becomes std::string& &&; by the reference collapsing rules, this collapses to std::string&.
This means that the foo(s) call passes s as an lvalue reference, and foo() ends up moving s and thereby placing it into an indeterminate state.
๐ bugprone-multi-level-implicit-pointer-conversion ๐
Detects implicit conversions between pointers of different levels of indirection.
Conversions between pointer types of different levels of indirection can be dangerous and may lead to undefined behavior, particularly if the converted pointer is later cast to a type with a different level of indirection. For example, converting a pointer to a pointer to an int (int**) to a void* can result in the loss of information about the original level of indirection, which can cause problems when attempting to use the converted pointer. If the converted pointer is later cast to a type with a different level of indirection and dereferenced, it may lead to access violations, memory corruption, or other undefined behavior.
Consider the following example:
void foo(void* ptr);
int main() {
int x = 42;
int* ptr = &x;
int** ptr_ptr = &ptr;
foo(ptr_ptr); // warning will trigger here
return 0;
}
In this example, foo() is called with ptr_ptr as its argument. However, ptr_ptr is a int** pointer, while foo() expects a void* pointer. This results in an implicit pointer level conversion, which could cause issues if foo() dereferences the pointer assuming it's a int* pointer.
Using an explicit cast is a recommended solution to prevent issues caused by implicit pointer level conversion, as it allows the developer to explicitly state their intention and show their reasoning for the type conversion. Additionally, it is recommended that developers thoroughly check and verify the safety of the conversion before using an explicit cast. This extra level of caution can help catch potential issues early on in the development process, improving the overall reliability and maintainability of the code.
โจ Options โจ
EnableInCโ If true, enables the check in C code (it is always enabled in C++ code). Default istrue.
๐ฅ bugprone-multiple-new-in-one-expression ๐ฅ
Finds multiple new operator calls in a single expression, where the allocated memory by the first new may leak if the second allocation fails and throws exception.
C++ does often not specify the exact order of evaluation of the operands of an operator or arguments of a function. Therefore if a first allocation succeeds and a second fails, in an exception handler it is not possible to tell which allocation has failed and free the memory. Even if the order is fixed the result of a first new may be stored in a temporary location that is not reachable at the time when a second allocation fails. It is best to avoid any expression that contains more than one operator new call, if exception handling is used to check for allocation errors.
Different rules apply for the short-circuit operators || and && and the , operator, where evaluation of one side must be completed before the other starts. Expressions of a list-initialization (initialization or construction using { and } characters) are evaluated in fixed order. Similarly, condition of a ? operator is evaluated before the branches are evaluated.
The check reports warning if two new calls appear in one expression at different sides of an operator, or if new calls appear in different arguments of a function call (that can be an object construction with () syntax). These new calls can be nested at any level. For any warning to be emitted the new calls should be in a code block where exception handling is used with catch for std::bad_alloc or std::exception. At ||, &&, ,, ? (condition and one branch) operators no warning is emitted. No warning is emitted if both of the memory allocations are not assigned to a variable or not passed directly to a function. The reason is that in this case the memory may be intentionally not freed or the allocated objects can be self-destructing objects.
Examples:
struct A {
int Var;
};
struct B {
B();
B(A *);
int Var;
};
struct C {
int *X1;
int *X2;
};
void f(A *, B *);
int f1(A *);
int f1(B *);
bool f2(A *);
void foo() {
A *PtrA;
B *PtrB;
try {
// Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated.
f(new A, new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined
// List (aggregate) initialization is used.
C C1{new int, new int}; // no warning
// Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated but not yet passed to function 'f1'.
int X = f1(new A) + f1(new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined
// Allocation of 'B' may fail after memory for 'A' was allocated.
// From C++17 on memory for 'B' is allocated first but still may leak if allocation of 'A' fails.
PtrB = new B(new A); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception
// 'new A' and 'new B' may be performed in any order.
// 'new B'/'new A' may fail after memory for 'A'/'B' was allocated but not assigned to 'PtrA'/'PtrB'.
(PtrA = new A)->Var = (PtrB = new B)->Var; // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined
// Evaluation of 'f2(new A)' must be finished before 'f1(new B)' starts.
// If 'new B' fails the allocated memory for 'A' is supposedly handled correctly because function 'f2' could take the ownership.
bool Z = f2(new A) || f1(new B); // no warning
X = (f2(new A) ? f1(new A) : f1(new B)); // no warning
// No warning if the result of both allocations is not passed to a function
// or stored in a variable.
(new A)->Var = (new B)->Var; // no warning
// No warning if at least one non-throwing allocation is used.
f(new(std::nothrow) A, new B); // no warning
} catch(std::bad_alloc) {
}
// No warning if the allocation is outside a try block (or no catch handler exists for std::bad_alloc).
// (The fact if exceptions can escape from 'foo' is not taken into account.)
f(new A, new B); // no warning
}
๐ซถ bugprone-multiple-statement-macro ๐ซถ
Detect multiple statement macros that are used in unbraced conditionals. Only the first statement of the macro will be inside the conditional and the other ones will be executed unconditionally.
Example:
#define INCREMENT_TWO(x, y) (x)++; (y)++
if (do_increment)
INCREMENT_TWO(a, b); // (b)++ will be executed unconditionally.
๐ธ bugprone-narrowing-conversions ๐ธ
cppcoreguidelines-narrowing-conversions redirects here as an alias for this check.
Checks for silent narrowing conversions, e.g: int i = 0; i += 0.1;. While the issue is obvious in this former example, it might not be so in the following: void MyClass::f(double d) { int_member_ += d; }.
We flag narrowing conversions from:
- an integer to a narrower integer (e.g.
chartounsigned char) ifWarnOnIntegerNarrowingConversionOption is set, - an integer to a narrower floating-point (e.g.
uint64_ttofloat) ifWarnOnIntegerToFloatingPointNarrowingConversionOption is set, - a floating-point to an integer (e.g.
doubletoint), - a floating-point to a narrower floating-point (e.g.
doubletofloat) ifWarnOnFloatingPointNarrowingConversionOption is set.
This check will flag:
- All narrowing conversions that are not marked by an explicit cast (c-style or
static_cast). For example:int i = 0; i += 0.1;,void f(int); f(0.1);, - All applications of binary operators with a narrowing conversions. For example:
int i; i+= 0.1;.
Arithmetic with smaller integer types than int trigger implicit conversions, as explained under "Integral Promotion" on cppreference.com https://en.cppreference.com/w/cpp/language/implicit_conversion. This check diagnoses more instances of narrowing than the compiler warning -Wconversion does. The example below demonstrates this behavior.
// The following function definition demonstrates usage of arithmetic with
// integer types smaller than `int` and how the narrowing conversion happens
// implicitly.
void computation(short argument1, short argument2) {
// Arithmetic written by humans:
short result = argument1 + argument2;
// Arithmetic actually performed by C++:
short result = static_cast<short>(static_cast<int>(argument1) + static_cast<int>(argument2));
}
void recommended_resolution(short argument1, short argument2) {
short result = argument1 + argument2;
// ^ warning: narrowing conversion from 'int' to signed type 'short' is implementation-defined
// The cppcoreguidelines recommend to resolve this issue by using the GSL
// in one of two ways. Either by a cast that throws if a loss of precision
// would occur.
short result = gsl::narrow<short>(argument1 + argument2);
// Or it can be resolved without checking the result risking invalid results.
short result = gsl::narrow_cast<short>(argument1 + argument2);
// A classical `static_cast` will silence the warning as well if the GSL
// is not available.
short result = static_cast<short>(argument1 + argument2);
}
โจ Options โจ
WarnOnIntegerNarrowingConversionโ When true, the check will warn on narrowing integer conversion (e.g.inttosize_t).trueby default.WarnOnIntegerToFloatingPointNarrowingConversionโ when this istrue, the check will absolutely yell at you for narrowing an integer into a floating-point value (thinksize_tโdouble, the classic). Default:true. ๐WarnOnFloatingPointNarrowingConversionโ whentrue, it warns on narrowing floating point conversions too (looking at you,doubleโfloat). Default:true. ๐WarnWithinTemplateInstantiationโ whentrue, it'll also warn about narrowing conversions hiding inside template instantiations. Default:false. (yes bestie, templates are a sneaky place for these to lurk ๐ซฃ)WarnOnEquivalentBitWidthโ whentrue, it warns on narrowing conversions that come from casting between types with an equivalent bit width. Likeint n = uint(0);orlong long n = double(0);. Default:true. โญIgnoreConversionFromTypesโ any narrowing conversion from a type in this semicolon-separated list gets ignored completely. Super useful for weeding out the commonly occurring but lowkey-not-that-problematic assignments, likeint n = std::vector<char>().size();orint n = std::difference(it1, it2);. The default list is empty, but for a legacy codebase a suggested list would besize_t;ptrdiff_t;size_type;difference_type. ๐PedanticModeโ whentrue, the check will warn about assigning a floating point constant to an integer value even if that floating point value is exactly representable in the destination type (e.g.int i = 1.0;). Default:false. ๐
๐ธ FAQ ๐ธ
- What does "narrowing conversion from 'int' to 'float'" even mean?
So, an IEEE754 Floating Point number can rep all integer values in the range [-2^PrecisionBits, 2^PrecisionBits], where PrecisionBits is the number of bits in the mantissa. โจ
For float that would be [-2^23, 2^23], while int can represent values in the range [-2^31, 2^31-1]. See the problem? ๐
- What does "implementation-defined" mean?
You've probably run into messages like "narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined". Basically the C/C++ standard does not mandate two's complement for signed integers, so the compiler gets to decide what the semantics are when converting an unsigned integer to a signed integer. Clang's implementation uses the two's complement format. No cap. ๐
๐ฆ bugprone-no-escape ๐ฆ
Finds pointers with the noescape attribute that get captured by an asynchronously-executed block. The block arguments in dispatch_async() and dispatch_after() are guaranteed to escape, so it's a whole error if a pointer with the noescape attribute gets captured by one of those blocks. ๐ญ
Here's an example of an invalid use of the noescape attribute, bestie:
void foo(__attribute__((noescape)) int *p) {
dispatch_async(queue, ^{
*p = 123;
});
});
๐ bugprone-non-zero-enum-to-bool-conversion ๐
Detects implicit and explicit casts of an enum type into bool where the enum type doesn't have a zero-value enumerator. Because like โ if the enum is only ever used to hold values equal to its enumerators, then converting to bool will always come out as true. Which is giving unnecessary code that reduces readability and maintainability and can literally result in bugs. ๐ฅ
It may produce false positives if the enum is used to store other values (like used as a bit-mask, or zero-initialized on purpose). To deal with those, // NOLINT or casting first to the underlying type before casting to bool is the move. ๐
Important to note: this check will not generate warnings if the definition of the enumeration type isn't available. Also, C++11 enumeration classes are supported by this check. ๐
Overall, this check is here to improve code quality and readability by identifying and flagging the instances where implicit or explicit casts from enumeration types to boolean could cause potential issues. Periodt. โจ
Example
enum EStatus {
OK = 1,
NOT_OK,
UNKNOWN
};
void process(EStatus status) {
if (!status) {
// this true-branch won't be executed
return;
}
// proceed with "valid data"
}
Options
EnumIgnoreListโ this option is used to ignore certain enum types when checking for implicit/explicit casts tobool. It accepts a semicolon-separated list of (fully qualified) enum type names, or regular expressions that match the enum type names. The default value is an empty string, which means no enums will be ignored. ๐ซถ
๐ bugprone-nondeterministic-pointer-iteration-order ๐
Finds nondeterministic usages of pointers in unordered containers. Iykyk. ๐
One canonical example is iteration across a container of pointers:
{
int a = 1, b = 2;
std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
for (auto i : UnorderedPtrSet)
f(i);
}
Another such example is sorting a container of pointers:
{
int a = 1, b = 2;
std::vector<int *> VectorOfPtr = {&a, &b};
std::sort(VectorOfPtr.begin(), VectorOfPtr.end());
}
Iteration of a container of pointers may present the order of different pointers differently across different runs of a program. In some cases that's totally acceptable behavior, and in others it's very much unexpected behavior. That's exactly why this check is advisory. ๐คทโโ๏ธ
This check only detects range-based for loops over unordered sets and maps. It also detects calls to sorting-like algorithms on containers holding pointers. Other similar usages will not be found and are false negatives. ๐ซฃ
Limitations:
- This check currently does not check if a nondeterministic iteration order is likely to be a mistake โ instead it marks all such iterations as bugprone.
std::reference_wrapperis not considered yet.- Only
forloops are considered; other iterators can be included in improvements.
๐ bugprone-not-null-terminated-result ๐
Finds function calls where it's possible to cause a not-null-terminated result. Usually the proper length of a string is strlen(src) + 1 or equal length of this expression, because the null terminator needs an extra space. Without that null terminator it can result in undefined behavior when the string is read. ๐ฑ
These following functions, and their respective wchar_t based functions, are checked:
memcpy, memcpy_s, memchr, memmove, memmove_s, strerror_s, strncmp, strxfrm
Here's a real-world example where the programmer forgot to increase the passed third argument, which is size_t length. That's exactly why the length of the allocated memory isn't enough to hold the null terminator:
static char *stringCpy(const std::string &str) {
char *result = reinterpret_cast<char *>(malloc(str.size()));
memcpy(result, str.data(), str.size());
return result;
}
In addition to issuing warnings, fix-it rewrites all the necessary code. It also tries to adjust the capacity of the destination array:
static char *stringCpy(const std::string &str) {
char *result = reinterpret_cast<char *>(malloc(str.size() + 1));
strcpy(result, str.data());
return result;
}
Note: it cannot guarantee to rewrite every one of the path-sensitive memory allocations. ๐
Transformation rules of memcpy()
It's possible to rewrite the memcpy() and memcpy_s() calls as the following four functions: strcpy(), strncpy(), strcpy_s(), strncpy_s(), where the latter two are the safer versions of the former two. It rewrites the wchar_t based memory handler functions respectively. โจ
Rewrite based on the destination array
- If copy to the destination array cannot overflow [1], the new function should be the older copy function (ending with
cpy), because it's more efficient than the safe version. ๐ - If copy to the destination array can overflow [1] and
WantToUseSafeFunctionsis set totrueand it's possible to obtain the capacity of the destination array, then the new function could be the safe version (ending withcpy_s). - If the new function could be the safe version and C++ files are analyzed and the destination array is plain
char/wchar_twithout un/signed, then the length of the destination array can be omitted. - If the new function could be the safe version and the destination array is un/signed, it needs to be casted to plain
char */wchar_t *. ๐
[1] It is possible to overflow:
- If the capacity of the destination array is unknown.
- If the given length is equal to the destination array's capacity.
Rewrite based on the length of the source string
- If the given length is
strlen(source)or equal length of this expression, then the new function should be the older copy function (ending withcpy), as it's more efficient than the safe version (ending withcpy_s). โญ - Otherwise we assume that the programmer wanted to copy 'N' characters, so the new function is
ncpy-like, which copies 'N' characters.
Transformations with strlen() or equal length of this expression
It transforms the wchar_t based memory and string handler functions respectively (where only strerror_s does not have a wchar_t based alias). ๐ธ
Memory handler functions
memcpyโ please visit the Transformation rules ofmemcpy()section. ๐memchrโ usually there's a C-style cast and it needs to be removed, because the new functionstrchr's return type is correct. The given length is going to be removed.memmoveโ if safe functions are available, the new function ismemmove_s, which has a new second argument which is the length of the destination array, it is adjusted, and the length of the source string is incremented by one. If safe functions aren't available, the given length is incremented by one. ๐ฅmemmove_sโ the given length is incremented by one.
String handler functions
strerror_sโ the given length is incremented by one.strncmpโ if the third argument is the first or the second argument's length + 1, it has to be truncated without the + 1 operation.strxfrmโ the given length is incremented by one.
Options
WantToUseSafeFunctionsโ the valuetruespecifies that the target environment is considered to implement'_s'suffixed memory and string handler functions, which are safer than the older versions (e.g.'memcpy_s()'). The default value istrue. ๐ซถ
๐ง bugprone-optional-value-conversion ๐ง
Detects potentially unintentional and redundant conversions where a value gets extracted from an optional-like type and then used to create a new instance of that same optional-like type. ๐ญ
These conversions might be the result of developer oversight, leftovers from code refactoring, or other situations that could lead to unintended exceptions โ or cases where the resulting optional is always initialized, which might be unexpected behavior. Ngl, sneaky.
To illustrate, consider the following problematic code snippet:
okay so like, hereโs the code thatโs being a whole mess:
#include <optional>
void print(std::optional<int>);
int main()
{
std::optional<int> opt;
// ...
// Unintentional conversion from std::optional<int> to int and back to
// std::optional<int>:
print(opt.value());
// ...
}
A better approach would be to directly pass opt to the print function without extracting its value:
#include <optional>
void print(std::optional<int>);
int main()
{
std::optional<int> opt;
// ...
// Proposed code: Directly pass the std::optional<int> to the print
// function.
print(opt);
// ...
}
By passing opt directly to the print function, unnecessary conversions are avoided, and potential unintended behavior or exceptions are minimized. โจ
Value extraction using operator * is matched by default. The support for non-standard optional types such as boost::optional or absl::optional may be limited.
Options ๐
OptionalTypesโ semicolon-separated list of (fully qualified) optional type names or regular expressions that match the optional types. Default value is::std::optional;::absl::optional;::boost::optional.ValueMethodsโ semicolon-separated list of (fully qualified) method names or regular expressions that match the methods. Default value is::value$;::get$.
๐ฆ bugprone-parent-virtual-call
Detects and fixes calls to grand-...parent virtual methods instead of calls to overridden parentโs virtual methods. Like, no cap, itโs giving โplease call the right ancestorโ energy.
struct A {
int virtual foo() {...}
};
struct B: public A {
int foo() override {...}
};
struct C: public B {
int foo() override { A::foo(); }
// ^^^^^^^^
// warning: qualified name A::foo refers to a member overridden in subclass; did you mean 'B'? [bugprone-parent-virtual-call]
};
๐ฅ bugprone-pointer-arithmetic-on-polymorphic-object
Finds pointer arithmetic performed on classes that contain a virtual function.
Pointer arithmetic on polymorphic objects where the pointerโs static type is different from its dynamic type is undefined behavior, as the two types could have different sizes, and thus the vtable pointer could point to an invalid address. Big yikes. ๐
Finding pointers where the static type contains a virtual member function is a good heuristic, as the pointer is likely to point to a different, derived object.
Example:
struct Base {
virtual ~Base();
int i;
};
struct Derived : public Base {};
void foo(Base* b) {
b += 1;
// warning: pointer arithmetic on class that declares a virtual function can
// result in undefined behavior if the dynamic type differs from the
// pointer type
}
int bar(const Derived d[]) {
return d[1].i; // warning due to pointer arithmetic on polymorphic object
}
// Making Derived final suppresses the warning
struct FinalDerived final : public Base {};
int baz(const FinalDerived d[]) {
return d[1].i; // no warning as FinalDerived is final
}
Options ๐
IgnoreInheritedVirtualFunctionsโ Whentrue, objects that only inherit a virtual function are not checked. Classes that do not declare a new virtual function are excluded by default, as they make up the majority of false positives. Default:false.
void bar(Base b[], Derived d[]) {
b += 1; // warning, as Base declares a virtual destructor
d += 1; // warning only if IgnoreVirtualDeclarationsOnly is set to false
}
References ๐
This check corresponds to the SEI Cert rule CTR56-CPP. Do not use pointer arithmetic on polymorphic objects โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects.
๐ bugprone-posix-return
Checks if any calls to pthread_* or posix_* functions (except posix_openpt) expect negative return values. These functions return either 0 on success or an errno on failure, which is positive only.
Example buggy usage looks like:
if (posix_fadvise(...) < 0) {
This will never happen as the return value is always non-negative. A simple fix could be:
if (posix_fadvise(...) > 0) {
๐ bugprone-redundant-branch-condition
Finds condition variables in nested if statements that were also checked in the outer if statement and were not changed.
Simple example:
bool onFire = isBurning();
if (onFire) {
if (onFire)
scream();
}
Here onFire is checked both in the outer if and the inner if statement without a possible change between the two checks. The check warns for this code and suggests removal of the second checking of variable onFire. Iconic redundancy, honestly. ๐
The checker also detects redundant condition checks if the condition variable is an operand of a logical โandโ (&&) or a logical โorโ (||) operator:
bool onFire = isBurning();
if (onFire) {
if (onFire && peopleInTheBuilding > 0)
scream();
}
bool onFire = isBurning();
if (onFire) {
if (onFire || isCollapsing())
scream();
}
In the first case (logical โandโ) the suggested fix is to remove the redundant condition variable and keep the other side of the &&. In the second case (logical โorโ) the whole if is removed similarly to the simple case on the top.
The condition of the outer if statement may also be a logical โandโ (&&) expression:
bool onFire = isBurning();
if (onFire && fireFighters < 10) {
if (someOtherCondition()) {
if (onFire)
scream();
}
}
The error is also detected if both the outer statement is a logical โandโ (&&) and the inner statement is a logical โandโ (&&) or โorโ (||). The inner if statement does not have to be a direct descendant of the outer one.
No error is detected if the condition variable may have been changed between the two checks:
bool onFire = isBurning();
if (onFire) {
tryToExtinguish(onFire);
if (onFire && peopleInTheBuilding > 0)
scream();
}
Every possible change is considered, thus if the condition variable is not a local variable of the function, it is a volatile or it has an alias (pointer or reference) then no warning is issued.
Known limitations ๐ง
The else branch is not checked currently for negated condition variable:
bool onFire = isBurning();
if (onFire) {
scream();
} else {
if (!onFire) {
continueWork();
}
}
The checker currently only detects redundant checking of single condition variables. More complex expressions are not checked:
if (peopleInTheBuilding == 1) {
if (peopleInTheBuilding == 1) {
doSomething();
}
}
๐ bugprone-reserved-identifier
cert-dcl37-c and cert-dcl51-cpp redirect here as an alias for this check.
Checks for usages of identifiers reserved for use by the implementation.
The C and C++ standards both reserve the following names for such use:
- identifiers that begin with an underscore followed by an uppercase letter;
- identifiers in the global namespace that begin with an underscore.
The C standard additionally reserves names beginning with a double underscore, while the C++ standard strengthens this to reserve names with a double underscore occurring anywhere.
Violating the naming rules above results in undefined behavior. Not me finding out the hard way ๐ญ
namespace NS {
void __f(); // name is not allowed in user code
using _Int = int; // same with this
#define cool__macro // also this
}
int _g(); // disallowed in global namespace only
The check can also be inverted, i.e. it can be configured to flag any identifier that is not a reserved identifier. This mode is for use by e.g. standard library implementors, to ensure they donโt infringe on the user namespace.
This check does not (yet) check for other reserved names, e.g. macro names identical to language keywords, and names specifically reserved by language standards, e.g. C++ โzombie namesโ and C future library directions.
This check corresponds to CERT C Coding Standard rule DCL37-C. Do not declare or define a reserved identifier โ https://wiki.sei.cmu.edu/confluence/display/c/DCL37-C.+Do+not+declare+or+define+a+reserved+identifier โ as well as its C++ counterpart, DCL51-CPP. Do not declare or define a reserved identifier โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/DCL51-CPP.+Do+not+declare+or+define+a+reserved+identifier.
Options ๐
Invertโ Iftrue, inverts the check, i.e. flags names that are not reserved. Default isfalse.AllowedIdentifiersโ semicolon-separated list of regular expressions that the check ignores. Default is an empty list.
๐ bugprone-return-const-ref-from-parameter
Detects return statements that return a constant reference parameter as constant reference. This may cause use-after-free errors if the caller uses xvalues as arguments. Itโs giving โreturning a vibe thatโs already been deletedโ energy. ๐
In C++, constant reference parameters can accept xvalues which will be destructed after the call. When the function returns such a parameter also as constant reference, then the returned reference can be used after the object it refers to has been destroyed.
Example โจ
struct S {
int v;
S(int);
~S();
};
const S &fn(const S &a) {
return a;
}
const S& s = fn(S{1});
s.v; // use after free
This issue can be resolved by declaring an overload of the problematic function where the const & parameter is instead declared as &&. The developer has to ensure that the implementation of that function does not produce a use-after-free, the exact error that this check is warning against. Marking such an && overload as deleted, will silence the warning as well. In the case of different const & parameters being returned depending on the control flow of the function, an overload where all problematic const & parameters have been declared as && will resolve the issue.
This issue can also be resolved by adding [[clang::lifetimebound]]. Clang enable -Wdangling warning by default which can detect mis-uses of the annotated function. See lifetimebound attribute โ https://clang.llvm.org/docs/AttributeReference.html#lifetimebound โ for details.
const int &f(const int &a [[clang::lifetimebound]]) { return a; } // no warning
const int &v = f(1); // warning: temporary bound to local reference 'v' will be destroyed at the end of the full-expression [-Wdangling]
๐ฆ bugprone-shared-ptr-array-mismatch
okay so this one catches the moment you make a std::shared_ptr to a non-array type and then hand it an array โ yes bestie, that's a problem ๐
when a shared pointer std::shared_ptr<T> gets initialized with a new-expression like new T[], the memory does not get deallocated correctly. the pointer uses plain delete in that case to free the target memory, when honestly what it needed was a delete[] call. a std::shared_ptr<T[]> calls the correct delete operator, slay โจ
the check will offer to replace shared_ptr<T> with shared_ptr<T[]> โ but only when it's used at a single variable declaration (one variable in one statement, no mixing ๐).
Example:
std::shared_ptr<Foo> x(new Foo[10]); // -> std::shared_ptr<Foo[]> x(new Foo[10]);
// ^ warning: shared pointer to non-array is initialized with array [bugprone-shared-ptr-array-mismatch]
std::shared_ptr<Foo> x1(new Foo), x2(new Foo[10]); // no replacement
// ^ warning: shared pointer to non-array is initialized with array [bugprone-shared-ptr-array-mismatch]
std::shared_ptr<Foo> x3(new Foo[10], [](const Foo *ptr) { delete[] ptr; }); // no warning
struct S {
std::shared_ptr<Foo> x(new Foo[10]); // no replacement in this case
// ^ warning: shared pointer to non-array is initialized with array [bugprone-shared-ptr-array-mismatch]
};
this check partially covers the CERT C++ Coding Standard rule MEM51-CPP. Properly deallocate dynamically allocated resources โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM51-CPP.+Properly+deallocate+dynamically+allocated+resources โ but like, only the std::shared_ptr case gets detected by this check. lowkey partial coverage, but we take it ๐ซถ
๐ธ bugprone-signal-handler
this one finds specific constructs inside signal handler functions that can cause undefined behavior ๐ญ and the rules for what's allowed differ between C++ language versions, so pay attention.
Checked signal handler rules for C:
- calls to non-asynchronous-safe functions are not allowed.
Checked signal handler rules for up to and including C++14:
- calls to non-asynchronous-safe functions are not allowed.
- C++-specific code constructs are not allowed in signal handlers. in other words, only the common subset of C and C++ is allowed to be used.
- calls to functions with non-C linkage are not allowed (including the signal handler itself).
the check is disabled on C++17 and later. fr fr, it just doesn't run there.
asynchronous-safety gets determined by comparing the function's name against a set of known functions. on top of that, the function must come from a system header include and live in a global namespace. the (possible) arguments passed to the function are not checked. any function that can't be determined to be asynchronous-safe is assumed to be non-asynchronous-safe by the check โ including user functions where only the declaration is visible. calls to user-defined functions with visible definitions do get checked recursively, so no hiding ๐
this check implements the CERT C Coding Standard rule SIG30-C. Call only asynchronous-safe functions within signal handlers โ https://www.securecoding.cert.org/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers โ and the rule MSC54-CPP. A signal handler must be a plain old function โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC54-CPP.+A+signal+handler+must+be+a+plain+old+function. it has the alias names cert-sig30-c and cert-msc54-cpp ๐
โจ Options โจ
AsyncSafeFunctionSetโ picks which set of functions counts as asynchronous-safe (and therefore allowed in signal handlers). it can be set to the following values:minimalโ selects a minimal set that is defined in the CERT SIG30-C rule. and includes functionsabort(),_Exit(),quick_exit()andsignal().POSIXโ selects a larger set of functions that is listed in POSIX.1-2017 (see this linkhttps://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03for more information). the following functions are included:
_Exit, _exit, abort, accept, access, aio_error, aio_return, aio_suspend, alarm, bind,
cfgetispeed, cfgetospeed, cfsetispeed, cfsetospeed, chdir, chmod, chown, clock_gettime,
close, connect, creat, dup, dup2, execl, execle, execv, execve, faccessat, fchdir, fchmod,
fchmodat, fchown, fchownat, fcntl, fdatasync, fexecve, ffs, fork, fstat, fstatat, fsync,
ftruncate, futimens, getegid, geteuid, getgid, getgroups, getpeername, getpgrp, getpid,
getppid, getsockname, getsockopt, getuid, htonl, htons, kill, link, linkat, listen, longjmp,
lseek, lstat, memccpy, memchr, memcmp, memcpy, memmove, memset, mkdir, mkdirat, mkfifo,
mkfifoat, mknod, mknodat, ntohl, ntohs, open, openat, pause, pipe, poll, posix_trace_event,
pselect, pthread_kill, pthread_self, pthread_sigmask, quick_exit, raise, read, readlink,
readlinkat, recv, recvfrom, recvmsg, rename, renameat, rmdir, select, sem_post, send,
sendmsg, sendto, setgid, setpgid, setsid, setsockopt, setuid, shutdown, sigaction,
sigaddset, sigdelset, sigemptyset, sigfillset, sigismember, siglongjmp, signal, sigpause,
sigpending, sigprocmask, sigqueue, sigset, sigsuspend, sleep, sockatmark, socket,
socketpair, stat, stpcpy, stpncpy, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat,
strncmp, strncpy, strnlen, strpbrk, strrchr, strspn, strstr, strtok_r, symlink, symlinkat,
tcdrain, tcflow, tcflush, tcgetattr, tcgetpgrp, tcsendbreak, tcsetattr, tcsetpgrp, time,
timer_getoverrun, timer_gettime, timer_settime, times, umask, uname, unlink, unlinkat,
utime, utimensat, utimes, wait, waitpid, wcpcpy, wcpncpy, wcscat, wcschr, wcscmp, wcscpy,
wcscspn, wcslen, wcsncat, wcsncmp, wcsncpy, wcsnlen, wcspbrk, wcsrchr, wcsspn, wcsstr,
wcstok, wmemchr, wmemcmp, wmemcpy, wmemmove, wmemset, write
heads up: the function quick_exit is not included in the POSIX list but it is included here in the set of safe functions. lil bonus ๐
the default value is POSIX.
๐ bugprone-signed-char-misuse
cert-str34-c redirects here as an alias for this check, iykyk. for the CERT alias, the DiagnoseSignedUnsignedCharComparisons option is set to false.
this one finds those signed char -> integer conversions that might indicate a programming error ๐ the basic problem with signed char is that it might store the non-ASCII characters as negative values. and that behavior can cause a whole misunderstanding of the code you wrote โ both when an explicit conversion happens and when an implicit one does.
when the code contains an explicit signed char -> integer conversion, the human programmer probably expects the converted value to match the character code (a value from [0..255]), except bestie... the actual value is in the [-128..127] interval ๐ to dodge this kind of misinterpretation, the desired way of converting from a signed char to an integer value is converting to unsigned char first, which stores all the characters in the positive [0..255] interval that matches the known character codes. iconic, correct, no notes โจ
in the case of an implicit conversion, the programmer might not even be aware that a conversion happened and that the char value is being used as an integer ๐ there are some use cases where this unawareness leads to functionally imperfect code. for example, checking the equality of a signed char and an unsigned char variable is something we should avoid in C++ code. during that comparison, the two variables get converted to integers which have totally different value ranges. for signed char, the non-ASCII characters are stored as a value in the [-128..-1] interval, while the same characters get stored in the [128..255] interval for an unsigned char. not the vibe ๐ญ
it depends on the actual platform whether plain char is handled as signed char by default, and so whether it gets caught by this check or not. to change the default behavior you can use -funsigned-char and -fsigned-char compilation options ๐ซถ
currently, this check warns in the following cases:
signed charis assigned to an integer variablesigned charandunsigned charare compared with equality/inequality operatorsigned charis converted to an integer in the array subscript
see also: STR34-C. Cast characters to unsigned char before converting to larger integer sizes โ https://wiki.sei.cmu.edu/confluence/display/c/STR34-C.+Cast+characters+to+unsigned+char+before+converting+to+larger+integer+sizes
a good example from the CERT description is when a char variable is used to read from a file that might contain non-ASCII characters. the problem comes up when the code uses the -1 integer value as EOF, while the 255 character code is also stored as -1 in two's complement form of char type. see a simple example of this below. this code stops not only when it reaches the end of the file, but also when it gets a character with the 255 code ๐
#define EOF (-1)
int read(void) {
char CChar;
int IChar = EOF;
if (readChar(CChar)) {
IChar = CChar;
}
return IChar;
}
a proper way to fix the code above is converting the char variable to an unsigned char value first ๐
#define EOF (-1)
int read(void) {
char CChar;
int IChar = EOF;
if (readChar(CChar)) {
IChar = static_cast<unsigned char>(CChar);
}
return IChar;
}
another use case is checking the equality of two char variables with different signedness. inside the non-ASCII value range, this comparison between a signed char and an unsigned char always returns false. always. ๐ญ
bool compare(signed char SChar, unsigned char USChar) {
if (SChar == USChar)
return true;
return false;
}
the easiest way to fix this kind of comparison is casting one of the arguments so both arguments end up with the same type ๐
bool compare(signed char SChar, unsigned char USChar) {
if (static_cast<unsigned char>(SChar) == USChar)
return true;
return false;
}
โจ Options โจ
CharTypdefsToIgnoreโ a semicolon-separated list of typedef names. in this list we can list typedefs forcharorsigned char, which will be ignored by the check. this is useful when a typedef introduces an integer alias likesal_Int8orint8_tโ in that case, human misinterpretation is not an issue. default is an empty string. (and yes, the spelling isCharTypdefsToIgnore, we stan a quirky-but-iconic name ๐)DiagnoseSignedUnsignedCharComparisonsโ when it'strue, the check will warn on signed char/unsigned char comparisons; otherwise those comparisons are ignored. By default, this option is set totrue.
๐ bugprone-sizeof-container ๐
Okay bestie, this check catches sizeof being used on expressions of STL container types. Most likely the user wanted .size() instead. It's giving "wrong tool for the job" energy, fr fr.
All class/struct types declared in namespace std:: that have a const size() method are considered containers โ with the exception of std::bitset and std::array. No cap.
Examples, because examples ate:
std::string s;
int a = 47 + sizeof(s); // warning: sizeof() doesn't return the size of the container. Did you mean .size()?
int b = sizeof(std::string); // no warning, probably intended.
std::string array_of_strings[10];
int c = sizeof(array_of_strings) / sizeof(array_of_strings[0]); // no warning, definitely intended.
std::array<int, 3> std_array;
int d = sizeof(std_array); // no warning, probably intended.
๐ฆ bugprone-sizeof-expression ๐ฆ
This check finds usages of sizeof expressions that are most likely errors. Like, the vibe check failed and the compiler is side-eyeing you.
The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. Misuse of this operator may be leading to errors and possible software vulnerabilities. Not iconic. ๐
๐ Suspicious usage of 'sizeof(K)' ๐
A common mistake is to query the sizeof of an integer literal. This is equivalent to querying the size of its type (probably int). The intent of the programmer was probably to simply get the integer and not its size.
#define BUFLEN 42
char buf[BUFLEN];
memset(buf, 0, sizeof(BUFLEN)); // sizeof(42) ==> sizeof(int)
๐ Suspicious usage of 'sizeof(expr)' ๐
In cases where there is an enum or integer to represent a type, a common mistake is to query the sizeof on the integer or enum that represents the type that should be used by sizeof. This results in the size of the integer and not of the type the integer represents:
enum data_type {
FLOAT_TYPE,
DOUBLE_TYPE
};
struct data {
data_type type;
void* buffer;
data_type get_type() {
return type;
}
};
void f(data d, int numElements) {
// should be sizeof(float) or sizeof(double), depending on d.get_type()
int numBytes = numElements * sizeof(d.get_type());
...
}
๐ Suspicious usage of 'sizeof(this)' ๐
The this keyword is evaluated to a pointer to an object of a given type. The expression sizeof(this) is returning the size of a pointer. The programmer most likely wanted the size of the object and not the size of the pointer.
class Point {
[...]
size_t size() { return sizeof(this); } // should probably be sizeof(*this)
[...]
};
๐ Suspicious usage of 'sizeof(char*)' ๐
There is a subtle difference between declaring a string literal with char* A = "" and char A[] = "". The first case has the type char* instead of the aggregate type char[]. Using sizeof on an object declared with char* type is returning the size of a pointer instead of the number of characters (bytes) in the string literal.
const char* kMessage = "Hello World!"; // const char kMessage[] = "...";
void getMessage(char* buf) {
memcpy(buf, kMessage, sizeof(kMessage)); // sizeof(char*)
}
๐ Suspicious usage of 'sizeof(A*)' ๐
A common mistake is to compute the size of a pointer instead of its pointee. These cases may occur because of explicit cast or implicit conversion.
int A[10];
memset(A, 0, sizeof(A + 0));
struct Point point;
memset(point, 0, sizeof(&point));
๐ Suspicious usage of 'sizeof(...)/sizeof(...)' ๐
Dividing sizeof expressions is typically used to retrieve the number of elements of an aggregate. This check warns on incompatible or suspicious cases.
In the following example, the entity has 10-bytes and is incompatible with the type int which has 4 bytes.
char buf[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // sizeof(buf) => 10
void getMessage(char* dst) {
memcpy(dst, buf, sizeof(buf) / sizeof(int)); // sizeof(int) => 4 [incompatible sizes]
}
In the following example, the expression sizeof(Values) is returning the size of char*. One can easily be fooled by its declaration, but in parameter declaration the size 10 is ignored and the function is receiving a char*.
char OrderedValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return CompareArray(char Values[10]) {
return memcmp(OrderedValues, Values, sizeof(Values)) == 0; // sizeof(Values) ==> sizeof(char*) [implicit cast to char*]
}
๐ Suspicious 'sizeof' by 'sizeof' expression ๐
Multiplying sizeof expressions typically makes no sense and is probably a logic error. In the following example, the programmer used * instead of /.
const char kMessage[] = "Hello World!";
void getMessage(char* buf) {
memcpy(buf, kMessage, sizeof(kMessage) * sizeof(char)); // sizeof(kMessage) / sizeof(char)
}
This check may trigger on code using the arraysize macro. The following code is working correctly but should be simplified by using only the sizeof operator.
extern Object objects[100];
void InitializeObjects() {
memset(objects, 0, arraysize(objects) * sizeof(Object)); // sizeof(objects)
}
๐ Suspicious usage of 'sizeof(sizeof(...))' ๐
Getting the sizeof of a sizeof makes no sense and is typically an error hidden through macros.
#define INT_SZ sizeof(int)
int buf[] = { 42 };
void getInt(int* dst) {
memcpy(dst, buf, sizeof(INT_SZ)); // sizeof(sizeof(int)) is suspicious.
}
๐ Suspicious usages of 'sizeof(...)' in pointer arithmetic ๐
Arithmetic operators on pointers automatically scale the result with the size of the pointed typed. Further use of sizeof around pointer arithmetic will typically result in an unintended result. Highkey a trap.
Scaling the result of pointer difference
Subtracting two pointers results in an integer expression (of type ptrdiff_t) which expresses the distance between the two pointed objects in "number of objects between". A common mistake is to think that the result is "number of bytes between", and scale the difference with sizeof, such as P1 - P2 == N * sizeof(T) (instead of P1 - P2 == N) or (P1 - P2) / sizeof(T) instead of P1 - P2.
void splitFour(const Obj* Objs, size_t N, Obj Delimiter) {
const Obj *P = Objs;
while (P < Objs + N) {
if (*P == Delimiter) {
break;
}
}
if (P - Objs != 4 * sizeof(Obj)) { // Expecting a distance multiplied by sizeof is suspicious.
error();
}
}
void iterateIfEvenLength(int *Begin, int *End) {
auto N = (Begin - End) / sizeof(int); // Dividing by sizeof() is suspicious.
if (N % 2)
return;
// ...
}
Stepping a pointer with a scaled integer
Conversely, when performing pointer arithmetics to add or subtract from a pointer, the arithmetic operator implicitly scales the value actually added to the pointer with the size of the pointee, as Ptr + N expects N to be "number of objects to step", and not "number of bytes to step".
Seeing the calculation of a pointer where sizeof appears is suspicious, and the result is typically unintended, often out of bounds. Ptr + sizeof(T) will offset the pointer by sizeof(T) elements, effectively exponentiating the scaling factor to the power of 2.
Similarly, multiplying or dividing a numeric value with the sizeof of an element or the whole buffer is suspicious, because the dimensional connection between the numeric value and the actual sizeof result can not always be deduced. While scaling an integer up (multiplying) with sizeof is likely always an issue, a scaling down (division) is not always inherently dangerous, in case the developer is aware that the division happens between an appropriate number of bytes and a sizeof value. Turning WarnOnOffsetDividedBySizeOf off will restrict the warnings to the multiplication case.
This case also checks suspicious alignof and offsetof usages in pointer arithmetic, as both return the "size" in bytes and not elements, potentially resulting in doubly-scaled offsets.
void printEveryEvenIndexElement(int *Array, size_t N) {
int *P = Array;
while (P <= Array + N * sizeof(int)) { // Suspicious pointer arithmetic using sizeof()!
printf("%d ", *P);
P += 2 * sizeof(int); // Suspicious pointer arithmetic using sizeof()!
}
}
struct Message { /* ... */; char Flags[8]; };
void clearFlags(Message *Array, size_t N) {
const Message *End = Array + N;
while (Array < End) {
memset(Array + offsetof(Message, Flags), // Suspicious pointer arithmetic using offsetof()!
0, sizeof(Message::Flags));
++Array;
}
}
For this checked bogus pattern, cert-arr39-c redirects here as an alias of this check.
This check corresponds to the CERT C Coding Standard rule ARR39-C. Do not add or subtract a scaled integer to a pointer โ <http://wiki.sei.cmu.edu/confluence/display/c/ARR39-C.+Do+not+add+or+subtract+a+scaled+integer+to+a+pointer>.
๐ง Limitations ๐ง
Cases where the pointee type has a size of 1 byte (such as, and most importantly, char) are excluded.
๐ Options ๐
WarnOnSizeOfConstantโ whentrue, the check will warn on an expression likesizeof(CONSTANT). Default istrue.WarnOnSizeOfIntegerExpressionโ whentrue, the check will warn on an expression likesizeof(expr)where the expression results in an integer. Default isfalse.WarnOnSizeOfThisโ whentrue, the check will warn on an expression likesizeof(this). Default istrue.WarnOnSizeOfCompareToConstantโ whentrue, the check will warn on an expression likesizeof(expr) <= kfor a suspicious constantkwhilekis0or greater than0x8000. Default istrue.WarnOnSizeOfPointerToAggregateโ whentrue, the check will warn when the argument ofsizeofis either a pointer-to-aggregate type, an expression returning a pointer-to-aggregate value or an expression that returns a pointer from an array-to-pointer conversion (that may be implicit or explicit, for examplearray + 2or(int *)array). Default istrue.WarnOnSizeOfPointerโ whentrue, the check will report all expressions where the argument ofsizeofis an expression that produces a pointer (except for a few idiomatic expressions that are probably intentional and correct). This detects occurrences of CWE 467. Default isfalse.WarnOnOffsetDividedBySizeOfโ when this istrue, the check will warn on pointer arithmetic where the element count comes from a division withsizeof(...), likePtr + Bytes / sizeof(*T). Default istrue. ๐WarnOnSizeOfInLoopTerminationโ whentrue, the check warns about an incorrect use of asizeofexpression in a loop termination condition. The warning fires if thesizeofexpression looks like it's being (wrongly) used to work out how many elements are in an array/buffer. e.g.long arr[10]; for(int i = 0; i < sizeof(arr); i++) { ... }. Default istrue. ๐ซถ
๐ bugprone-spuriously-wake-up-functions
It finds calls to cnd_wait, cnd_timedwait, wait, wait_for, or wait_until when the function isn't invoked from a loop that checks whether a condition predicate holds โ or when the function doesn't have a condition parameter. ๐ญ
if (condition_predicate) {
condition.wait(lk);
}
if (condition_predicate) {
if (thrd_success != cnd_wait(&condition, &lock)) {
}
}
This check corresponds to the CERT C++ Coding Standard rule CON54-CPP. Wrap functions that can spuriously wake up in a loop <https://wiki.sei.cmu.edu/confluence/display/cplusplus/CON54-CPP.+Wrap+functions+that+can+spuriously+wake+up+in+a+loop>. and CERT C Coding Standard rule CON36-C. Wrap functions that can spuriously wake up in a loop <https://wiki.sei.cmu.edu/confluence/display/c/CON36-C.+Wrap+functions+that+can+spuriously+wake+up+in+a+loop>. ๐ฅ
๐ซ bugprone-standalone-empty
Warns when empty() is used on a range and the result is justโฆ ignored. It suggests clear() if that happens to be an existing member function. ๐
So empty() on a bunch of common ranges returns a Boolean telling you whether the range is empty or not โ but bestie, people keep misreading it as "clear the contents." Some ranges do offer a clear() method for exactly that. This check warns when a call to empty returns a result that gets ignored, and suggests swapping in a call to clear() if that's available as a member function of the range. โจ
For example, the following code could be used to indicate whether a range is empty or not, but the result is ignored:
std::vector<int> v;
...
v.empty();
A call to clear() would appropriately clear the contents of the range:
std::vector<int> v;
...
v.clear();
Limitations: ๐
- Doesn't warn if
empty()is defined and used with the ignore result in the class template definition (like, in the library implementation). These error cases can be caught with the[[nodiscard]]attribute.
๐งต bugprone-string-constructor
Finds string constructors that are suspicious and probably errors. ๐
A super common mistake is swapping the parameters to the 'fill' string-constructor.
Examples:
std::string str('x', 50); // should be str(50, 'x')
Calling the string-literal constructor with a length bigger than the literal is suspicious and adds extra random characters to the string. ๐ญ
Examples:
std::string("test", 200); // Will include random characters after "test".
std::string("test", 2, 5); // Will include random characters after "st".
std::string_view("test", 200);
Creating an empty string from constructors with parameters is also considered suspicious โ the programmer should use the empty constructor instead. ๐
Examples:
std::string("test", 0); // Creation of an empty string.
std::string("test", 1, 0);
std::string_view("test", 0);
Passing an invalid first character position parameter to the constructor will cause a std::out_of_range exception at runtime. ๐
Examples:
std::string("test", -1, 10); // Negative first character position.
std::string("test", 10, 10); // First character position is bigger than string literal character range".
๐ ๏ธ Options
WarnOnLargeLengthโ whentrue, the check will warn on a string with a length greater thanLargeLengthThreshold. Default istrue.LargeLengthThresholdโ an integer specifying the large length threshold. Default is0x800000.StringNamesโ default is::std::basic_string;::std::basic_string_view.
Semicolon-delimited list of class names to apply this check to. By default ::std::basic_string applies to std::string and std::wstring. Set it to e.g. ::std::basic_string;llvm::StringRef;QString to perform this check on custom classes. ๐
๐ข bugprone-string-integer-assignment
The check finds assignments of an integer to std::basic_string<CharT> (std::string, std::wstring, etc.). The source of the problem is this assignment operator of std::basic_string<CharT>:
basic_string& operator=( CharT ch );
Numeric types can be implicitly casted to character types. ๐ฑ
std::string s;
int x = 5965;
s = 6;
s = x;
Use the appropriate conversion functions or character literals instead, bestie:
std::string s;
int x = 5965;
s = '6';
s = std::to_string(x);
In order to suppress false positives, use an explicit cast:
std::string s;
s = static_cast<char>(6);
๐ณ๏ธ bugprone-string-literal-with-embedded-nul
Finds occurrences of string literal with embedded NUL character and validates their usage. ๐
๐ต Invalid escaping
Special characters can be escaped within a string literal by using their hexadecimal encoding like \x42. A common mistake is to escape them like this \0x42, where the \0 stands for the NUL character. ๐
const char* Example[] = "Invalid character: \0x12 should be \x12";
const char* Bytes[] = "\x03\0x02\0x01\0x00\0xFF\0xFF\0xFF";
โ๏ธ Truncated literal
String-like classes can manipulate strings with embedded NUL as they're keeping track of the bytes and the length. That is not the case for a char* (NUL-terminated) string. ๐
A common mistake is passing a string-literal with embedded NUL to a string constructor expecting a NUL-terminated string. Everything after the first NUL character gets truncated.
std::string str("abc\0def"); // "def" is truncated
str += "\0"; // This statement is doing nothing
if (str == "\0abc") return; // This expression is always true
๐ซฅ bugprone-stringview-nullptr
Checks for the various ways the const CharT* constructor of std::basic_string_view can be passed a null argument, and replaces them with the default constructor in most cases. For the comparison operators, a braced initializer list doesn't compile, so instead a call to .empty() or the empty string literal gets used, where appropriate. โจ
This prevents code from invoking behavior which is unconditionally undefined. The single-argument const CharT* constructor does not check for the null case before dereferencing its input. The standard is slated to add an explicitly-deleted overload to catch some of these cases: wg21.link/p2166 ๐ฅ
To catch the additional cases of NULL (which expands to __null) and 0, first run the modernize-use-nullptr check to convert the callers to nullptr.
std::string_view sv = nullptr;
sv = nullptr;
bool is_empty = sv == nullptr;
bool isnt_empty = sv != nullptr;
accepts_sv(nullptr);
accepts_sv({{}}); // A
accepts_sv({nullptr, 0}); // B
is translated intoโฆ
std::string_view sv = {};
sv = {};
bool is_empty = sv.empty();
bool isnt_empty = !sv.empty();
accepts_sv("");
accepts_sv(""); // A
accepts_sv({nullptr, 0}); // B
Note: the source pattern with trailing comment "A" selects the(const CharT*)constructor overload and then value-initializes the pointer, causing a null dereference. It happens to not include thenullptrliteral, but it's still within the scope of this ClangTidy check. ๐ง
Note: the source pattern with trailing comment "B" selects the(const CharT*, size_type)constructor, which is perfectly valid since the length argument is0. It is not changed by this ClangTidy check. ๐
๐ฉ bugprone-suspicious-enum-usage
The checker detects various cases when an enum is probably misused (as a bitmask). ๐
- When
"ADD"or"bitwise OR"is used between two enums that come from different types and these types' value ranges are not disjoint.
The following cases will be investigated only using StrictMode <#cmdoption-arg-StrictMode>. We regard the enum as a (suspicious) bitmask if the three conditions below are true at the same time:
- at most half of the elements of the enum are non pow-of-2 numbers (because of short enumerations)
- there is another non pow-of-2 number than the enum constant representing all choices (the result
"bitwise OR"operation of all enum elements) - enum type variable/enumconstant is used as an argument of a
+or"bitwise OR "operator
So whenever the non pow-of-2 element is used as a bitmask element we diagnose a misuse and give a warning. ๐จ
- Investigating the right hand side of
+=and|=operator. - Check only the enum value side of a
|and+operator if one of them is not enum val. - Check both side of
|or+operator where the enum values are from the same enum type.
Examples:
enum { A, B, C };
enum { D, E, F = 5 };
enum { G = 10, H = 11, I = 12 };
unsigned flag;
flag =
A |
H; // OK, disjoint value intervals in the enum types ->probably good use.
flag = B | F; // Warning, have common values so they are probably misused.
// Case 2:
enum Bitmask {
A = 0,
B = 1,
C = 2,
D = 4,
E = 8,
F = 16,
G = 31 // OK, real bitmask.
};
enum Almostbitmask {
AA = 0,
BB = 1,
CC = 2,
DD = 4,
EE = 8,
FF = 16,
GG // Problem, forgot to initialize.
};
unsigned flag = 0;
flag |= E; // OK.
flag |=
EE; // Warning at the decl, and note that it was used here as a bitmask.
๐ ๏ธ Options
StrictModeโ default value:0. When non-null, the suspicious bitmask usage will be investigated additionally to the different enum usage check.
๐ bugprone-suspicious-include
The check detects various cases when an include refers to what appears to be an implementation file โ which often leads to hard-to-track-down ODR violations. ๐ญโ
Examples:
#include "Dinosaur.hpp" // OK, .hpp files tend not to have definitions.
#include "Pterodactyl.h" // OK, .h files tend not to have definitions.
#include "Velociraptor.cpp" // Warning, filename is suspicious.
#include_next <stdio.c> // Warning, filename is suspicious.
๐ง bugprone-suspicious-memory-comparison
Okay so this check is out here catching potentially incorrect calls to memcmp() by vibe-checking the properties of the arguments. It covers these cases:
- Case 1: Non-standard-layout type โ comparing the object representations of non-standard-layout objects may not properly compare the value representations. ๐
- Case 2: Types with no unique object representation โ objects with the same value may not have the same object representation. This may be caused by padding or floating-point types. ๐ญ
See also: EXP42-C. Do not compare padding data โ https://wiki.sei.cmu.edu/confluence/display/c/EXP42-C.+Do+not+compare+padding+data โ and FLP37-C. Do not use object representations to compare floating-point values โ https://wiki.sei.cmu.edu/confluence/display/c/FLP37-C.+Do+not+use+object+representations+to+compare+floating-point+values.
This check is also related to and partially overlaps the CERT C++ Coding Standard rules OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP57-CPP.+Prefer+special+member+functions+and+overloaded+operators+to+C+Standard+Library+functions โ and EXP62-CPP. Do not access the bits of an object representation that are not part of the object's value representation โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/EXP62-CPP.+Do+not+access+the+bits+of+an+object+representation+that+are+not+part+of+the+object%27s+value+representation.
cert-exp42-c redirects here as an alias of this check. slay. โจ
๐งผ bugprone-suspicious-memset-usage
This one finds memset() calls with potential mistakes in their arguments. Considering the function as void* memset(void* destination, int fill_value, size_t byte_count), these cases are covered:
- Case 1: Fill value is a character
'0'โ filling up a memory area with ASCII code 48 characters is not customary, and possibly integer zeroes were intended instead. The check offers a replacement of'0'with0. Memsetting character pointers with'0'is allowed. ๐ซถ - Case 2: Fill value is truncated โ
memsetconvertsfill_valuetounsigned charbefore using it. Iffill_valueis out of unsigned character range, it gets truncated and memory will not contain the desired pattern. ๐ - Case 3: Byte count is zero โ calling
memsetwith a literal zero in itsbyte_countargument is likely to be unintended and swapped withfill_value. The check offers to swap these two arguments. ๐
Corresponding cpplint.py check name: runtime/memset.
Examples:
void foo() {
int i[5] = {1, 2, 3, 4, 5};
int *ip = i;
char c = '1';
char *cp = &c;
int v = 0;
// Case 1
memset(ip, '0', 1); // suspicious
memset(cp, '0', 1); // OK
// Case 2
memset(ip, 0xabcd, 1); // fill value gets truncated
memset(ip, 0x00, 1); // OK
// Case 3
memset(ip, sizeof(int), v); // zero length, potentially swapped
memset(ip, 0, 1); // OK
}
๐งฉ bugprone-suspicious-missing-comma
String literals placed side-by-side are concatenated at translation phase 6 (after the preprocessor). This feature is used to represent a long string literal on multiple lines.
For instance, the following declarations are equivalent:
const char* A[] = "This is a test";
const char* B[] = "This" " is a " "test";
A common mistake done by programmers is to forget a comma between two string literals in an array initializer list.
const char* Test[] = {
"line 1",
"line 2" // Missing comma!
"line 3",
"line 4",
"line 5"
};
The array contains the string "line 2line3" at offset 1 (i.e. Test[1]). Clang won't generate warnings at compile time. ๐ญ
This check may warn incorrectly on cases like:
const char* SupportedFormat[] = {
"Error %s",
"Code " PRIu64, // May warn here.
"Warning %s",
};
Options ๐๏ธ
SizeThresholdโ an unsigned integer specifying the minimum size of a string literal to be considered by the check. Default is5U.RatioThresholdโ a string specifying the maximum threshold ratio[0, 1.0]of suspicious string literals to be considered. Default is".2".MaxConcatenatedTokensโ an unsigned integer specifying the maximum number of concatenated tokens. Default is5U.
โป๏ธ bugprone-suspicious-realloc-usage
This check finds usages of realloc where the return value is assigned to the same expression as passed to the first argument: p = realloc(p, size); The problem with this construct is that if realloc fails it returns a null pointer but does not deallocate the original memory. If no other variable is pointing to it, the original memory block is not available any more for the program to use or free. In either case p = realloc(p, size); indicates bad coding style and can be replaced by q = realloc(p, size);. ๐
The pointer expression (used at realloc) can be a variable or a field member of a data structure, but can not contain function calls or unresolved types.
In obvious cases when the pointer used at realloc is assigned to another variable before the realloc call, no warning is emitted. This happens only if a simple expression in form of q = p or void *q = p is found in the same function where p = realloc(p, ...) is found. The assignment has to be before the call to realloc (but otherwise at any place) in the same function. This suppression works only if p is a single variable.
Examples:
struct A {
void *p;
};
A &getA();
void foo(void *p, A *a, int new_size) {
p = realloc(p, new_size); // warning: 'p' may be set to null if 'realloc' fails, which may result in a leak of the original buffer
a->p = realloc(a->p, new_size); // warning: 'a->p' may be set to null if 'realloc' fails, which may result in a leak of the original buffer
getA().p = realloc(getA().p, new_size); // no warning
}
void foo1(void *p, int new_size) {
void *p1 = p;
p = realloc(p, new_size); // no warning
}
๐ณ bugprone-suspicious-semicolon
Finds most instances of stray semicolons that unexpectedly alter the meaning of the code. More specifically, it looks for if, while, for and for-range statements whose body is a single semicolon, and then analyzes the context of the code (e.g. indentation) in an attempt to determine whether that is intentional.
if (x < y);
{
x++;
}
Here the body of the if statement consists of only the semicolon at the end of the first line, and x will be incremented regardless of the condition. ๐
while ((line = readLine(file)) != NULL);
processLine(line);
As a result of this code, processLine() will only be called once, when the while loop with the empty body exits with line == NULL. The indentation of the code indicates the intention of the programmer.
if (x >= y);
x -= y;
While the indentation does not imply any nesting, there is simply no valid reason to have an if statement with an empty body (but it can make sense for a loop). So this check issues a warning for the code above.
To solve the issue remove the stray semicolon or in case the empty body is intentional, reflect this using code indentation or put the semicolon in a new line. For example:
while (readWhitespace());
Token t = readNextToken();
Here the second line is indented in a way that suggests that it is meant to be the body of the while loop โ whose body is in fact empty, because of the semicolon at the end of the first line. ๐
Either remove the indentation from the second line:
while (readWhitespace());
Token t = readNextToken();
โฆ or move the semicolon from the end of the first line to a new line:
while (readWhitespace())
;
Token t = readNextToken();
In this case the check will assume that you know what you are doing, and will not raise a warning. ๐
๐ bugprone-suspicious-string-compare
Find suspicious usage of runtime string comparison functions. This check is valid in C and C++. ๐ซถ
Checks for calls with implicit comparator and proposed to explicitly add it.
if (strcmp(...)) // Implicitly compare to zero
if (!strcmp(...)) // Won't warn
if (strcmp(...) != 0) // Won't warn
Checks that compare function results (i.e., strcmp) are compared to valid constant. The resulting value is
< 0 when lower than,
> 0 when greater than,
== 0 when equals.
A common mistake is to compare the result to 1 or -1.
if (strcmp(...) == -1) // Incorrect usage of the returned value.
Additionally, the check warns if the results value is implicitly cast to a suspicious non-integer type. It's happening when the returned value is used in a wrong context.
if (strcmp(...) < 0.) // Incorrect usage of the returned value.
Options ๐
WarnOnImplicitComparisonโ whentrue, the check will warn on implicit comparison.trueby default.WarnOnLogicalNotComparisonโ whentrue, the check will warn on logical not comparison.falseby default.StringCompareLikeFunctionsโ a string specifying the comma-separated names of the extra string comparison functions. Default is an empty string. The check will detect the following string comparison functions:__builtin_memcmp,__builtin_strcasecmp,__builtin_strcmp,__builtin_strncasecmp,__builtin_strncmp,_mbscmp,_mbscmp_l,_mbsicmp,_mbsicmp_l,_mbsnbcmp,_mbsnbcmp_l,_mbsnbicmp,_mbsnbicmp_l,_mbsncmp,_mbsncmp_l,_mbsnicmp,_mbsnicmp_l,_memicmp,_memicmp_l,_stricmp,_stricmp_l,_strnicmp,_strnicmp_l,_wcsicmp,_wcsicmp_l,_wcsnicmp,_wcsnicmp_l,lstrcmp,lstrcmpi,memcmp,memicmp,strcasecmp,strcmp,strcmpi,stricmp,strncasecmp,strncmp,strnicmp,wcscasecmp,wcscmp,wcsicmp,wcsncmp,wcsnicmp,wmemcmp.
๐งต bugprone-suspicious-stringview-data-usage
Identifies suspicious usages of std::string_view::data() that could lead to reading out-of-bounds data due to inadequate or incorrect string null termination. ๐ญ
It warns when the result of data() is passed to a constructor or function without also passing the corresponding result of size() or length() member function. Such usage can lead to unintended behavior, particularly when assuming the data pointed to by data() is null-terminated. ๐
...and this is exactly why the no c_str() in std::string_view situation is such a whole thing, bestie. Because there's no c_str(), devs keep reaching for data() as the substitute โ especially when they're interfacing with C APIs that expect null-terminated strings. But data() does not guarantee null termination, so if that API relies on proper null termination to actually interpret the string correctly, you get unintended behavior. It's giving chaos. ๐ญ
In today's programming landscape (yes, we're saying it like that), this scenario pops up when you're implicitly converting an std::string_view to an std::string. The constructor in std::string that's designed for string-view-like objects is explicit, so trying to pass an std::string_view to a function expecting an std::string? Compilation error, periodt. So as a workaround, devs get tempted to use the .data() method to make it compile โ and that's where the potential risks sneak in. ๐
For instance:
void printString(const std::string& str) {
std::cout << "String: " << str << std::endl;
}
void something(std::string_view sv) {
printString(sv.data());
}
In this example, directly passing sv to the printString function would lead to a compilation error, because the std::string constructor is explicit. So devs might opt for sv.data() to resolve the compilation error โ albeit introducing the potential hazards we just discussed. Trade-off, not a fix. ๐
โจ Options โจ
StringViewTypesโ lets you specify custom string view-like types for the analysis. It accepts a semicolon-separated list of type names or regular expressions matching those types. Default value is:::std::basic_string_view;::llvm::StringRef.AllowedCalleesโ specifies methods, functions, or classes where the result of.data()is passed to. Lets you exclude such calls from the analysis. Accepts a semicolon-separated list of names or regular expressions matching these entities. Default value is: empty string.
๐ธ bugprone-swapped-arguments ๐ธ
Finds potentially swapped arguments by examining implicit conversions. It analyzes the types of the arguments being passed to a function and compares them to the expected types of the corresponding parameters. If there's a mismatch, or an implicit conversion that indicates a potential swap, a warning is raised. Detective mode: activated. ๐
void printNumbers(int a, float b);
int main() {
// Swapped arguments: float passed as int, int as float)
printNumbers(10.0f, 5);
return 0;
}
Covers a wide range of implicit conversions, including:
- User-defined conversions
- Conversions from floating-point types to boolean or integral types
- Conversions from integral types to boolean or floating-point types
- Conversions from boolean to integer types or floating-point types
- Conversions from (member) pointers to boolean
It is important to note that for most argument swaps, the types need to match exactly. But there are exceptions to this rule, bestie. Specifically, when the swapped argument is of integral type, an exact match is not always necessary โ implicit casts from other integral types are also accepted. Similarly, when dealing with floating-point arguments, implicit casts between different floating-point types are considered acceptable. ๐
And to avoid confusion: swaps where both swapped arguments are of integral types, or both are of floating-point types, do not trigger the warning. In those cases it's assumed the developer intentionally used different integral or floating-point types, so no warning. This approach prevents false positives and keeps things flexible when varying integral or floating-point types are intentionally utilized. Respect the intent. โจ
๐ bugprone-switch-missing-default-case ๐
Ensures that switch statements without default cases are flagged, and it focuses only on covering cases with non-enums where the compiler may not issue warnings.
switch statements without a default case can lead to unexpected behavior and incomplete handling of all possible cases. When a switch statement lacks a default case and a value shows up that doesn't match any of the specified cases, the program just continues execution without any defined behavior or handling. Literally nothing. No thoughts, empty head. ๐
This check helps identify switch statements that are missing a default case, so devs can make sure all possible cases are handled properly. Adding a default case allows for graceful handling of unexpected or unmatched values, reducing the risk of program errors and unexpected behavior. ๐ซถ
Example:
// Example 1:
// warning: switching on non-enum value without default case may not cover all cases
switch (i) {
case 0:
break;
}
// Example 2:
enum E { eE1 };
E e = eE1;
switch (e) { // no-warning
case eE1:
break;
}
// Example 3:
int i = 0;
switch (i) { // no-warning
case 0:
break;
default:
break;
}
Note: Enum types are already covered by compiler warnings (comes under-Wswitch) when aswitchstatement does not handle all enum values. This check focuses on non-enum types where the compiler warnings may not be present.
See also: The CppCoreGuideline ES.79 https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-default provides guidelines on switch statements, including the recommendation to always provide a default case. ๐
๐ฆ bugprone-tagged-union-member-count ๐ฆ
Gives warnings for tagged unions, where the number of tags is different from the number of data members inside the union. Math check: failed. ๐
A struct or a class is considered to be a tagged union if it has exactly one union data member and exactly one enum data member, plus any number of other data members that are neither unions nor enums.
Example:
enum Tags {
Tag1,
Tag2,
};
struct TaggedUnion { // warning: tagged union has more data members (3) than tags (2)
enum Tags Kind;
union {
int I;
float F;
char *Str;
} Data;
};
๐งฎ How enum constants are counted
The main complicating factor when counting the number of enum constants is that some of them might be auxiliary values that purposefully don't have a corresponding union data member and are used for something else. For example, the last enum constant sometimes explicitly "points to" the last declared valid enum constant, or tracks how many enum constants have been declared. Bookkeeping constants, basically. ๐
For an illustration:
enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};
enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};
The check counts the number of distinct values among the enum constants, not the enum constants themselves. That way, enum constants that are essentially just aliases of other enum constants don't get included in the final count. Aliases don't count, iykyk. โจ
Handling of counting enum constants (ones like TagCount in the previous code example) is done by decreasing the number of enum values by one if the name of the last enum constant starts with a prefix or ends with a suffix specified in CountingEnumPrefixes, CountingEnumSuffixes, and its value is one less than the total number of distinct values in the enum.
When the final count is adjusted based on this heuristic, a diagnostic note is emitted showing which enum constant matched the criteria. ๐ฏ
The heuristic can be disabled entirely (EnableCountingEnumHeuristic) or configured to follow your naming convention (CountingEnumPrefixes, CountingEnumSuffixes). The strings specified in CountingEnumPrefixes, CountingEnumSuffixes are matched case insensitively.
Example counts:
// Enum count is 3, because the value 2 is counted only once
enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};
// Enum count is 3, because TagCount is heuristically excluded
enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};
โจ Options โจ
EnableCountingEnumHeuristic
This option enables or disables the counting enum heuristic. It uses the prefixes and suffixes specified in the options CountingEnumPrefixes, CountingEnumSuffixes to find counting enum constants by using them for prefix and suffix matching. ๐ง
This option is enabled by default.
When EnableCountingEnumHeuristic is false:
enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};
struct TaggedUnion {
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When EnableCountingEnumHeuristic is true:
enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};
struct TaggedUnion { // warning: tagged union has more data members (4) than tags (3)
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
CountingEnumPrefixes
See CountingEnumSuffixes below. ๐
CountingEnumSuffixes
CountingEnumPrefixes and CountingEnumSuffixes are lists of semicolon separated strings that are used to search for possible counting enum constants. These strings are matched case insensitively as prefixes and suffixes respectively on the names of the enum constants. If EnableCountingEnumHeuristic is false, then these options do nothing. ๐ซ
The default value of CountingEnumSuffixes is count and of CountingEnumPrefixes is the empty string.
When EnableCountingEnumHeuristic is true and CountingEnumSuffixes is count;size:
enum TagWithCounterCount {
Tag1,
Tag2,
Tag3,
TagCount,
};
struct TaggedUnionCount { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterCount Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
enum TagWithCounterSize {
Tag11,
Tag22,
Tag33,
TagSize,
};
struct TaggedUnionSize { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterSize Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When EnableCountingEnumHeuristic is true and CountingEnumPrefixes is maxsize;last_
enum TagWithCounterLast {
Tag1,
Tag2,
Tag3,
last_tag,
};
struct TaggedUnionLast { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterLast tag;
union {
int I;
short S;
char *C;
float F;
} Data;
};
enum TagWithCounterMaxSize {
Tag1,
Tag2,
Tag3,
MaxSizeTag,
};
struct TaggedUnionMaxSize { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterMaxSize tag;
union {
int I;
short S;
char *C;
float F;
} Data;
};
๐ StrictMode
Okay so when this one's turned on, the check also yells at you when the number of tags is greater than the number of union data members. Yes, the other direction too. Balance is everything, bestie โจ
This option is disabled by default. (So if you want the extra sass, you gotta ask for it.)
When StrictMode is false:
struct TaggedUnion {
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};
When StrictMode is true:
struct TaggedUnion { // warning: tagged union has fewer data members (2) than tags (3)
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};
๐ bugprone-terminating-continue
This one catches do while loops where the condition always evaluates to false and there's a continue statement hanging out inside โ because that continue basically just ends the loop on the spot. Loop's over before it even started, no cap ๐ญ
void f() {
do {
// some code
continue; // terminating continue
// some other code
} while(false);
๐ฏ bugprone-throw-keyword-missing
Warns you about a throw keyword that's probably supposed to be there. The logic: if a temporary object gets created, but that object's type derives from (or is literally the same as) a class that has 'EXCEPTION', 'Exception' or 'exception' in its name โ we're just gonna assume the programmer meant to throw it. Like, why else would you be out here constructing an exception, bestie? ๐คจ
Example:
void f(int i) {
if (i < 0) {
// Exception is created but is not thrown.
std::runtime_error("Unexpected argument");
}
}
๐ bugprone-too-small-loop-variable
Detects for loops where the loop variable has a "too small" type โ meaning that type literally cannot represent all the values that are part of the iteration range. It's giving "I signed up for this but I did not sign up for this" ๐
int main() {
long size = 294967296l;
for (short i = 0; i < size; ++i) {}
}
This for loop is an infinite loop, because the short type can't represent all values in the [0..size] interval. Yeah. It just keeps going. Forever. Iconic in a bad way ๐ซ
In a real use case, size means a container's size, which depends on the user input:
int doSomething(const std::vector& items) {
for (short i = 0; i < items.size(); ++i) {}
}
This algorithm works fine for a small amount of objects, but will lead to freeze for a larger user input. Your app is now a screensaver ๐ฅ
It's recommended to enable the compiler warning -Wtautological-constant-out-of-range-compare as well, since the check does not inspect compile-time constant loop boundaries โ that's to avoid overlapping with the warning. So like, team up with the compiler on this one ๐ซถ
โ๏ธ Options
MagnitudeBitsUpperLimitโ Upper limit for the magnitude bits of the loop variable. If it's set, the check filters out those catches where the loop variable's type has more magnitude bits than the specified upper limit. The default value is16. For example, if the user sets this option to31(bits), then a 32-bitunsigned intis ignored by the check โ however a 32-bitintis not (a 32-bit signedinthas 31 magnitude bits). Math is math, bestie ๐งฎ
int main() {
long size = 294967296l;
for (unsigned i = 0; i < size; ++i) {} // no warning with MagnitudeBitsUpperLimit = 31 on a system where unsigned is 32-bit
for (int i = 0; i < size; ++i) {} // warning with MagnitudeBitsUpperLimit = 31 on a system where int is 32-bit
}
๐ธ bugprone-unchecked-optional-access
Note: This check uses a flow-sensitive static analysis to produce its results. Therefore, it may be more resource intensive (RAM, CPU) than the average clang-tidy check. Your laptop is gonna feel it, bestie ๐ป๐ฎโ๐จ
This check identifies unsafe accesses to values contained in std::optional<T>, absl::optional<T>, base::Optional<T>, folly::Optional<T>, bsl::optional, or BloombergLP::bdlb::NullableValue objects. Below we will refer to all these types collectively as optional<T>.
An access to the value of an optional<T> occurs when one of its value, operator*, or operator-> member functions is invoked. To align with common misconceptions, the check considers these member functions as equivalent โ even though there are subtle differences related to exceptions versus undefined behavior. See Additional notes, below, for more on this topic ๐
An access to the value of an optional<T> is considered safe if and only if code in the local scope (for example, a function body) ensures that the optional<T> has a value in all possible execution paths that can reach the access. That should happen either through an explicit check, using the optional<T>::has_value member function, or by constructing the optional<T> in a way that shows it unambiguously holds a value (e.g. using std::make_optional, which always returns a populated std::optional<T>).
Below we list some examples, starting with unsafe optional access patterns, followed by safe access patterns โจ
๐ Unsafe access patterns
Access the value without checking if it exists
The check flags accesses to the value that are not locally guarded by an existence check:
void f(std::optional<int> opt) {
use(*opt); // unsafe: it is unclear whether `opt` has a value.
}
Access the value in the wrong branch
The check is aware of the state of an optional object in different branches of the code. For example:
void f(std::optional<int> opt) {
if (opt.has_value()) {
} else {
use(opt.value()); // unsafe: it is clear that `opt` does *not* have a value.
}
}
Assume a function result to be stable
The check is aware that function results might not be stable. That is, consecutive calls to the same function might return different values. For example:
void f(Foo foo) {
if (foo.take().has_value()) {
use(*foo.take()); // unsafe: it is unclear whether `foo.take()` has a value.
}
}
Exception: accessor methods
The check assumes accessor methods of a class are stable, with a heuristic to determine which methods are accessors. Specifically, parameter-free const methods and smart pointer-like APIs (non-const overloads of * when there is a parallel const overload) are treated as accessors. Note that this is not guaranteed to be safe โ but it is widely used (safely) in practice. Calls to non-const methods are assumed to modify the state of the object and affect the stability of earlier accessor calls.
Rely on invariants of uncommon APIs
The check is unaware of invariants of uncommon APIs. For example:
void f(Foo foo) {
if (foo.HasProperty("bar")) {
use(*foo.GetProperty("bar")); // unsafe: it is unclear whether `foo.GetProperty("bar")` has a value.
}
}
Check if a value exists, then pass the optional to another function
The check relies on local reasoning. The check and the value access must both happen in the same function. An access is considered unsafe even if the caller of the function performing the access ensures that the optional has a value. For example:
void g(std::optional<int> opt) {
use(*opt); // unsafe: it is unclear whether `opt` has a value.
}
void f(std::optional<int> opt) {
if (opt.has_value()) {
g(opt);
}
}
๐ฆ Safe access patterns
Check if a value exists, then access the value
The check recognizes all straightforward ways for checking if a value exists and accessing the value contained in an optional object. For example:
void f(std::optional<int> opt) {
if (opt.has_value()) {
use(*opt);
}
}
Check if a value exists, then access the value from a copy
The criteria the check uses is semantic, not syntactic. It recognizes when a copy of the optional object being accessed is known to have a value. For example:
void f(std::optional<int> opt1) {
if (opt1.has_value()) {
std::optional<int> opt2 = opt1;
use(*opt2);
}
}
Ensure that a value exists using common macros
The check is aware of common macros like CHECK and DCHECK. Those can be used to ensure that an optional object has a value. For example:
void f(std::optional<int> opt) {
DCHECK(opt.has_value());
use(*opt);
}
Ensure that a value exists, then access the value in a correlated branch
The check is aware of correlated branches in the code and can figure out when an optional object is ensured to have a value on all execution paths that lead to an access. For example:
void f(std::optional<int> opt) {
bool safe = false;
if (opt.has_value() && SomeOtherCondition()) {
safe = true;
}
// ... more code...
if (safe) {
use(*opt);
}
}
Stabilize function results
Function results are not assumed to be stable across calls, except for const accessor methods. For more complex accessors (non-const, or depending on multiple params) it is best to store the result of the function call in a local variable and use that variable to access the value. For example:
void f(Foo foo) {
if (const auto& foo_opt = foo.take(); foo_opt.has_value()) {
use(*foo_opt);
}
}
Do not rely on uncommon-API invariants
When uncommon APIs guarantee that an optional has contents, do not rely on it โ instead, check explicitly that the optional object has a value. For example:
void f(Foo foo) {
if (const auto& property = foo.GetProperty("bar")) {
use(*property);
}
}
instead of the HasProperty, GetProperty pairing we saw above. Yeah, do it the honest way, bestie ๐ซถ
Do not rely on caller-performed checks
If you know that all of a function's callers have checked that an optional argument has a value, either change the function to take the value directly or check the optional again in the local scope of the callee. For example:
void g(int val) {
use(val);
}
void f(std::optional<int> opt) {
if (opt.has_value()) {
g(*opt);
}
}
and
struct S {
std::optional<int> opt;
int x;
};
void g(const S &s) {
if (s.opt.has_value() && s.x > 10) {
use(*s.opt);
}
void f(S s) {
if (s.opt.has_value()) {
g(s);
}
}
Additional notes ๐ฌ
Aliases created via using declarations ๐ท๏ธ
Bestie, the check is aware of optional type aliases made with using declarations. Like, it sees through them. Slay. For example:
using OptionalInt = std::optional<int>;
void f(OptionalInt opt) {
use(opt.value()); // unsafe: it is unclear whether `opt` has a value.
}
Lambdas ๐
Okay so real talk โ the check does not currently report unsafe optional accesses inside lambdas. A future version will expand the scope to lambdas, following the rules outlined above. In the meantime? It's best to follow the same principles when using optionals in lambdas. Be your own check, queen. ๐
Access with operator*() vs. value() ๐ค
So like, given that value() has well-defined behavior (either throwing an exception or terminating the program), why treat it the same as operator*() which causes undefined behavior (UB)? That is, why is it considered unsafe to access an optional with value(), if it's not provably populated with a value? And for that matter, why is CHECK() followed by operator*() any better than value(), given that they are semantically equivalent (on configurations that disable exceptions)?
The answer is that we assume most users do not realize the difference between value() and operator*(). Shifting to operator*() and some form of explicit value-presence check or explicit program termination has two advantages:
- Readability. The check, and any potential side effects like program shutdown, are super clear in the code. Separating access from checks can actually make the checks more obvious. โจ
- Performance. A single check can cover many or even all accesses within scope. This gives the user the best of both worlds โ the safety of a dynamic check, but without incurring redundant costs. ๐ฅ
bugprone-undefined-memory-manipulation ๐
Finds calls of memory manipulation functions memset(), memcpy() and memmove() on non-TriviallyCopyable objects resulting in undefined behavior.
Using memory manipulation functions on non-TriviallyCopyable objects can lead to a whole range of subtle and challenging issues in C++ code. The most immediate concern is the potential for undefined behavior, where the state of the object may become corrupted or invalid. This can manifest as crashes, data corruption, or unexpected behavior at runtime, making it genuinely challenging to identify and diagnose the root cause. ๐ญ Additionally, misuse of memory manipulation functions can bypass essential object-specific operations, such as constructors and destructors, leading to resource leaks or improper initialization. Not it.
For example, when using memcpy to copy std::string, pointer data is being copied, and it can result in a double free issue. Yikes.
#include <cstring>
#include <string>
int main() {
std::string source = "Hello";
std::string destination;
std::memcpy(&destination, &source, sizeof(std::string));
// Undefined behavior may occur here, during std::string destructor call.
return 0;
}
bugprone-undelegated-constructor ๐๏ธ
Finds creation of temporary objects in constructors that look like a function call to another constructor of the same class.
The user most likely meant to use a delegating constructor or base class initializer. We've all been there bestie, no judgment. ๐
bugprone-unhandled-exception-at-new ๐ฅ
Finds calls to new with missing exception handler for std::bad_alloc.
Calls to new may throw exceptions of type std::bad_alloc that should be handled. Alternatively, the nonthrowing form of new can be used. The check verifies that the exception is handled in the function that calls new.
If a nonthrowing version is used or the exception is allowed to propagate out of the function, no warning is generated. ๐ โโ๏ธ
The exception handler is checked if it catches a std::bad_alloc or std::exception exception type, or all exceptions (catch-all). The check assumes that any user-defined operator new is either noexcept or may throw an exception of type std::bad_alloc (or one derived from it). Other exception class types are not taken into account.
int *f() noexcept {
int *p = new int[1000]; // warning: missing exception handler for allocation failure at 'new'
// ...
return p;
}
int *f1() { // not 'noexcept'
int *p = new int[1000]; // no warning: exception can be handled outside
// of this function
// ...
return p;
}
int *f2() noexcept {
try {
int *p = new int[1000]; // no warning: exception is handled
// ...
return p;
} catch (std::bad_alloc &) {
// ...
}
// ...
}
int *f3() noexcept {
int *p = new (std::nothrow) int[1000]; // no warning: "nothrow" is used
// ...
return p;
}
bugprone-unhandled-self-assignment ๐ช
cert-oop54-cpp redirects here as an alias for this check. For the CERT alias, the WarnOnlyIfThisHasSuspiciousField option is set to false.
Finds user-defined copy assignment operators which do not protect the code against self-assignment โ either by checking self-assignment explicitly or using the copy-and-swap or the copy-and-move method. Basically it's catching you assigning yourself to yourself and breaking everything. ๐
By default, this check searches only those classes which have any pointer or C array field to avoid false positives. In case of a pointer or a C array, it's likely that self-copy assignment breaks the object if the copy assignment operator was not written with care.
See also: OOP54-CPP. Gracefully handle self-copy assignment <https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP54-CPP.+Gracefully+handle+self-copy+assignment>
A copy assignment operator must prevent that self-copy assignment ruins the object state. A typical use case is when the class has a pointer field and the copy assignment operator first releases the pointed object and then tries to assign it:
class T {
int* p;
public:
T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
~T() { delete p; }
// ...
T& operator=(const T &rhs) {
delete p;
p = new int(*rhs.p);
return *this;
}
};
There are two common C++ patterns to avoid this problem. The first is the self-assignment check:
class T {
int* p;
public:
T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
~T() { delete p; }
// ...
T& operator=(const T &rhs) {
if(this == &rhs)
return *this;
delete p;
p = new int(*rhs.p);
return *this;
}
};
The second one is the copy-and-swap method when we create a temporary copy (using the copy constructor) and then swap this temporary object with this:
class T {
int* p;
public:
T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
~T() { delete p; }
// ...
void swap(T &rhs) {
using std::swap;
swap(p, rhs.p);
}
T& operator=(const T &rhs) {
T(rhs).swap(*this);
return *this;
}
};
There is a third pattern which is less common. Let's call it the copy-and-move method when we create a temporary copy (using the copy constructor) and then move this temporary object into this (needs a move assignment operator):
class T {
int* p;
public:
T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
~T() { delete p; }
// ...
T& operator=(const T &rhs) {
T t = rhs;
*this = std::move(t);
return *this;
}
T& operator=(T &&rhs) {
p = rhs.p;
rhs.p = nullptr;
return *this;
}
};
Options โ๏ธ
WarnOnlyIfThisHasSuspiciousFieldโ Whentrue, the check will warn only if the container class of the copy assignment operator has any suspicious fields (pointer, C array and C++ smart pointer). This option is set totrueby default.
bugprone-unintended-char-ostream-output ๐จ๏ธ
Finds unintended character output from unsigned char and signed char to an ostream.
Normally, when unsigned char (uint8_t) or signed char (int8_t) is used, it is more likely a number than a character. However, when it is passed directly to std::ostream's operator<<, the result is the character output instead of the numeric value. This often contradicts the developer's intent to print integer values. So rude of the compiler honestly.
uint8_t v = 65;
std::cout << v; // output 'A' instead of '65'
The check will suggest casting the value to an appropriate type to indicate the intent โ by default, it will cast to unsigned int for unsigned char and int for signed char.
std::cout << static_cast<unsigned int>(v); // when v is unsigned char
std::cout << static_cast<int>(v); // when v is signed char
To avoid lengthy cast statements, adding prefix + to the variable can also suppress warnings because unary expression will promote the value to an int. Iconic shortcut behavior tbh. โจ
std::cout << +v;
Or cast to char to explicitly indicate that output should be a character:
std::cout << static_cast<char>(v);
Options โ๏ธ
AllowedTypesโ A semicolon-separated list of type names that will be treated like thechartype: the check will not report variables declared with these types or explicit cast expressions to these types. Note that this distinguishes type aliases from the original type, so specifying e.g.unsigned charhere will not suppress reports aboutuint8_teven if it is defined as a typedef alias forunsigned char. Default isunsigned char;signed char.CastTypeNameโ WhenCastTypeNameis specified, the fix-it will useCastTypeNameas the cast target type. Otherwise, fix-it will automatically infer the type.
bugprone-unique-ptr-array-mismatch ๐งฉ
Finds initializations of C++ unique pointers to non-array type that are initialized with an array. The classic mismatch. ๐
If a pointer std::unique_ptr<T> is initialized with a new-expression new T[] the memory is not deallocated correctly. A plain delete is used in this case to deallocate the target memory. Instead a delete[] call is needed. A std::unique_ptr<T[]> uses the correct delete operator. The check does not emit warning if an unique_ptr with user-specified deleter type is used.
The check offers replacement of unique_ptr<T> to unique_ptr<T[]> if it is used at a single variable declaration (one variable in one statement).
Example:
okay so the exact snippet for that std::unique_ptr replacement tea is:
std::unique_ptr<Foo> x(new Foo[10]); // -> std::unique_ptr<Foo[]> x(new Foo[10]);
// ^ warning: unique pointer to non-array is initialized with array
std::unique_ptr<Foo> x1(new Foo), x2(new Foo[10]); // no replacement
// ^ warning: unique pointer to non-array is initialized with array
D d;
std::unique_ptr<Foo, D> x3(new Foo[10], d); // no warning (custom deleter used)
struct S {
std::unique_ptr<Foo> x(new Foo[10]); // no replacement in this case
// ^ warning: unique pointer to non-array is initialized with array
};
This check partially covers the CERT C++ Coding Standard rule MEM51-CPP. Properly deallocate dynamically allocated resources โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM51-CPP.+Properly+deallocate+dynamically+allocated+resources. However, only the std::unique_ptr case is detected by this check. periodt.
๐จ bugprone-unsafe-functions ๐จ
Checks for functions that have safer, more secure replacements available, or are considered deprecated due to design flaws. The check heavily relies on the functions from the Annex K. "Bounds-checking interfaces" of C11.
The check implements the following rules from the CERT C Coding Standard:
- Recommendation MSC24-C. Do not use deprecated or obsolescent functions โ
https://wiki.sei.cmu.edu/confluence/display/c/MSC24-C.+Do+not+use+deprecated+or+obsolescent+functions. - Rule MSC33-C. Do not pass invalid data to the
asctime()function โhttps://wiki.sei.cmu.edu/confluence/display/c/MSC33-C.+Do+not+pass+invalid+data+to+the+asctime%28%29+function.
cert-msc24-c and cert-msc33-c redirect here as aliases of this check. iykyk.
๐ก๏ธ Unsafe functions ๐ก๏ธ
The following functions are reported if ReportDefaultFunctions is enabled.
If Annex K. is available, a replacement from Annex K. is suggested for the following functions:
asctime, asctime_r, bsearch, ctime, fopen, fprintf, freopen, fscanf,
fwprintf, fwscanf, getenv, gets, gmtime, localtime, mbsrtowcs, mbstowcs,
memcpy, memmove, memset, printf, qsort, scanf, snprintf, sprintf,
sscanf, strcat, strcpy, strerror, strlen, strncat, strncpy, strtok,
swprintf, swscanf, vfprintf, vfscanf, vfwprintf, vfwscanf, vprintf,
vscanf, vsnprintf, vsprintf, vsscanf, vswprintf, vswscanf, vwprintf,
vwscanf, wcrtomb, wcscat, wcscpy, wcslen, wcsncat, wcsncpy, wcsrtombs,
wcstok, wcstombs, wctomb, wmemcpy, wmemmove, wprintf, wscanf.
If Annex K. is not available, replacements are suggested only for the following functions from the previous list:
asctime,asctime_r, suggested replacement:strftimegets, suggested replacement:fgets
The following functions are always checked, regardless of Annex K availability:
rewind, suggested replacement:fseeksetbuf, suggested replacement:setvbuf
If ReportMoreUnsafeFunctions is enabled, the following functions are also checked:
bcmp, suggested replacement:memcmpbcopy, suggested replacement:memcpy_sif Annex K is available, ormemcpybzero, suggested replacement:memset_sif Annex K is available, ormemsetgetpw, suggested replacement:getpwuidvfork, suggested replacement:posix_spawn
Although mentioned in the associated CERT rules, the following functions are ignored by the check:
atof, atoi, atol, atoll, tmpfile.
The availability of Annex K is determined based on the following macros:
__STDC_LIB_EXT1__: feature macro, which indicates the presence of Annex K. "Bounds-checking interfaces" in the library implementation__STDC_WANT_LIB_EXT1__: user-defined macro, which indicates that the user requests the functions from Annex K. to be defined.
Both macros have to be defined to suggest replacement functions from Annex K. __STDC_LIB_EXT1__ is defined by the library implementation, and __STDC_WANT_LIB_EXT1__ must be defined to 1 by the user before including any system headers. no cap, both or it doesn't count.
๐ง Custom functions ๐ง
The option CustomFunctions allows the user to define custom functions to be checked. The format is the following, without newlines:
bugprone-unsafe-functions.CustomFunctions="
functionRegex1[, replacement1[, reason1]];
functionRegex2[, replacement2[, reason2]];
...
"
The functions are matched using POSIX extended regular expressions. (Note: The regular expressions do not support negative (?!) matches.)
The reason is optional and is used to provide additional information about the reasoning behind the replacement. The default reason is is marked as unsafe.
If replacement is empty, the text it should not be used will be shown instead of the suggestion for a replacement.
As an example, the configuration ^original$, replacement, is deprecated; will produce the following diagnostic message.
original(); // warning: function 'original' is deprecated; 'replacement' should be used instead.
::std::original(); // no-warning
original_function(); // no-warning
If the regular expression contains the character :, it is matched against the qualified name (i.e. std::original), otherwise the regex is matched against the unqualified name (original). If the regular expression starts with :: (or ^::), it is matched against the fully qualified name (::std::original). the more you know, bestie.
Note: Fully qualified names can contain template parameters on certain C++ classes, but not on C++ functions. Type aliases are resolved before matching. As an example, the member functionopenin the classstd::ifstreamhas a fully qualified name of::std::basic_ifstream<char>::open. The example could also be matched with the regex::std::basic_ifstream<[^>]*>::open, which matches all potential template parameters, but does not match nested template classes.
๐ Options ๐
ReportMoreUnsafeFunctionsโ Whentrue, additional functions from widely used APIs (such as POSIX) are added to the list of reported functions. See the main documentation of the check for the complete list as to what this option enables. Default istrue.ReportDefaultFunctionsโ Whentrue, the check reports the default set of functions. Consider changing the setting tofalseif you only want to see custom functions matched via custom functions. Default istrue.CustomFunctionsโ A semicolon-separated list of custom functions to be matched. A matched function contains a regular expression, an optional name of the replacement function, and an optional reason, separated by comma. For more information, see Custom functions.
๐ธ Examples ๐ธ
#ifndef __STDC_LIB_EXT1__
#error "Annex K is not supported by the current standard library implementation."
#endif
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h> // Defines functions from Annex K.
#include <stdio.h>
enum { BUFSIZE = 32 };
void Unsafe(const char *Msg) {
static const char Prefix[] = "Error: ";
static const char Suffix[] = "\n";
char Buf[BUFSIZE] = {0};
strcpy(Buf, Prefix); // warning: function 'strcpy' is not bounds-checking; 'strcpy_s' should be used instead.
strcat(Buf, Msg); // warning: function 'strcat' is not bounds-checking; 'strcat_s' should be used instead.
strcat(Buf, Suffix); // warning: function 'strcat' is not bounds-checking; 'strcat_s' should be used instead.
if (fputs(buf, stderr) < 0) {
// error handling
return;
}
}
void UsingSafeFunctions(const char *Msg) {
static const char Prefix[] = "Error: ";
static const char Suffix[] = "\n";
char Buf[BUFSIZE] = {0};
if (strcpy_s(Buf, BUFSIZE, Prefix) != 0) {
// error handling
return;
}
if (strcat_s(Buf, BUFSIZE, Msg) != 0) {
// error handling
return;
}
if (strcat_s(Buf, BUFSIZE, Suffix) != 0) {
// error handling
return;
}
if (fputs(Buf, stderr) < 0) {
// error handling
return;
}
}
๐ฆ bugprone-unused-local-non-trivial-variable ๐ฆ
Warns when a local non trivial variable is unused within a function. literally the "girl you bought it and never used it" of variables. The following types of variables are excluded from this check:
- trivial and trivially copyable
- references and pointers
- exception variables in catch clauses
- static or thread local
- structured bindings
- variables with
[[maybe_unused]]attribute - name-independent variables
This check can be configured to warn on all non-trivial variables by setting IncludeTypes to .*, and excluding specific types using ExcludeTypes.
In this example, my_lock would generate a warning that it is unused.
std::mutex my_lock;
// my_lock local variable is never used
In the next example, future2 would generate a warning that it is unused.
std::future<MyObject> future1;
std::future<MyObject> future2;
// ...
MyObject foo = future1.get();
// future2 is not used.
โจ Options โจ
IncludeTypesโ Semicolon-separated list of regular expressions matching types of variables to check. By default the following types are checked:::std::.*mutex::std::future::std::basic_string::std::basic_regex::std::basic_istringstream::std::basic_stringstream::std::bitset::std::filesystem::path
ExcludeTypesโ A semicolon-separated list of regular expressions matching types that are excluded from theIncludeTypesmatches. By default it is an empty list.
๐ bugprone-unused-raii ๐
Finds temporaries that look like RAII objects.
The canonical example for this is a scoped lock.
{
scoped_lock(&global_mutex);
critical_section();
}
The destructor of the scoped_lock is called before the critical_section is entered, leaving it unprotected. bestie, that is not the vibe. that is a whole unprotected critical section.
We apply a number of heuristics to reduce the false positive count of this check:
- Ignore code expanded from macros. Testing frameworks make heavy use of this.
- Ignore types with trivial destructors. They are very unlikely to be RAII objects and there's no difference when they are deleted.
- Ignore objects at the end of a compound statement (doesn't change behavior).
- Ignore objects returned from a call.
โญ bugprone-unused-return-value โญ
Warns on unused function return values. The checked functions can be configured.
Operator overloading with assignment semantics are ignored. we simply do not care about that assignment-flavored operator overloading here. ๐
Options ๐
CheckedFunctionsโ semicolon-separated list of functions to check, bestie. It supportsregexp. A function gets checked if the name and scope match, with any arguments. By default, the following functions are checked:
^::std::async$, ^::std::launder$, ^::std::remove$,
^::std::remove_if$, ^::std::unique$,
^::std::unique_ptr::release$, ^::std::basic_string::empty$,
^::std::vector::empty$, ^::std::back_inserter$,
^::std::distance$, ^::std::find$, ^::std::find_if$,
^::std::inserter$, ^::std::lower_bound$, ^::std::make_pair$,
^::std::map::count$, ^::std::map::find$,
^::std::map::lower_bound$, ^::std::multimap::equal_range$,
^::std::multimap::upper_bound$, ^::std::set::count$,
^::std::set::find$, ^::std::setfill$, ^::std::setprecision$,
^::std::setw$, ^::std::upper_bound$, ^::std::vector::at$,
^::bsearch$, ^::ferror$, ^::feof$, ^::isalnum$, ^::isalpha$,
^::isblank$, ^::iscntrl$, ^::isdigit$, ^::isgraph$, ^::islower$,
^::isprint$, ^::ispunct$, ^::isspace$, ^::isupper$, ^::iswalnum$,
^::iswprint$, ^::iswspace$, ^::isxdigit$, ^::memchr$, ^::memcmp$,
^::strcmp$, ^::strcoll$, ^::strncmp$, ^::strpbrk$, ^::strrchr$,
^::strspn$, ^::strstr$, ^::wcscmp$, ^::access$, ^::bind$,
^::connect$, ^::difftime$, ^::dlsym$, ^::fnmatch$,
^::getaddrinfo$, ^::getopt$, ^::htonl$, ^::htons$,
^::iconv_open$, ^::inet_addr$, isascii$, isatty$, ^::mmap$,
^::newlocale$, ^::openat$, ^::pathconf$, ^::pthread_equal$,
^::pthread_getspecific$, ^::pthread_mutex_trylock$, ^::readdir$,
^::readlink$, ^::recvmsg$, ^::regexec$, ^::scandir$, ^::semget$,
^::setjmp$, ^::shm_open$, ^::shmget$, ^::sigismember$,
^::strcasecmp$, ^::strsignal$, ^::ttyname$
std::async()โ not using the return value makes the call synchronous.std::launder()โ not using the return value usually means the function interface was misunderstood by the programmer. Only the returned pointer is "laundered", not the argument.std::remove(),std::remove_if()andstd::unique()โ the returned iterator indicates the boundary between elements to keep and elements to be removed. Not using the return value means the info about which elements to remove is lost.std::unique_ptr::release()โ not using the return value can lead to resource leaks if the same pointer isn't stored anywhere else. Often, ignoring therelease()return value indicates that the programmer confused the function withreset().std::basic_string::empty()andstd::vector::empty()โ not using the return value often indicates that the programmer confused the function withclear().
CheckedReturnTypesโ semicolon-separated list of function return types to check. By default, these function return types are checked:
^::std::error_code$, ^::std::error_condition$, ^::std::errc$, ^::std::expected$, ^::boost::system::error_code$
AllowCastToVoidโ controls whether casting return values tovoidis permitted. Default:false.
cert-err33-c is an alias of this check that checks a fixed and large set of standard library functions.
bugprone-use-after-move ๐ฆ
Warns if an object is used after it has been moved, like this:
std::string str = "Hello, world!\n";
std::vector<std::string> messages;
messages.emplace_back(std::move(str));
std::cout << str;
The last line will trigger a warning that str is used after it has been moved.
The check does not trigger a warning if the object is reinitialized after the move and before the use. For example, no warning will be output for this code:
messages.emplace_back(std::move(str));
str = "Greetings, stranger!\n";
std::cout << str;
Subsections below explain more precisely what exactly the check considers to be a move, use, and reinitialization.
The check takes control flow into account. A warning is only emitted if the use can be reached from the move. This means the following code does not produce a warning:
if (condition) {
messages.emplace_back(std::move(str));
} else {
std::cout << str;
}
On the other hand, the following code does produce a warning:
for (int i = 0; i < 10; ++i) {
std::cout << str;
messages.emplace_back(std::move(str));
}
(The use-after-move happens on the second iteration of the loop.)
In some cases, the check may not be able to detect that two branches are mutually exclusive. For example (assuming that i is an int):
if (i == 1) {
messages.emplace_back(std::move(str));
}
if (i == 2) {
std::cout << str;
}
In this case, the check will erroneously produce a warning, even though it is not possible for both the move and the use to be executed. More formally, the analysis is flow-sensitive but not path-sensitive (the original points at https://en.wikipedia.org/wiki/Data-flow_analysis#Sensitivities).
Silencing erroneous warnings ๐
An erroneous warning can be silenced by reinitializing the object after the move:
if (i == 1) {
messages.emplace_back(std::move(str));
str = "";
}
if (i == 2) {
std::cout << str;
}
If you want to avoid the overhead of actually reinitializing the object, you can create a dummy function that causes the check to assume the object was reinitialized:
template <class T>
void IS_INITIALIZED(T&) {}
You can use this as follows:
if (i == 1) {
messages.emplace_back(std::move(str));
}
if (i == 2) {
IS_INITIALIZED(str);
std::cout << str;
}
The check will not output a warning in this case because passing the object to a function as a non-const pointer or reference counts as a reinitialization (see section Reinitialization below).
Unsequenced moves, uses, and reinitializations ๐
In many cases, C++ does not make any guarantees about the order in which sub-expressions of a statement are evaluated. This means that in code like the following, it is not guaranteed whether the use will happen before or after the move:
void f(int i, std::vector<int> v);
std::vector<int> v = { 1, 2, 3 };
f(v[1], std::move(v));
In this kind of situation, the check will note that the use and move are unsequenced.
The check will also take sequencing rules into account when reinitializations occur in the same statement as moves or uses. A reinitialization is only considered to reinitialize a variable if it is guaranteed to be evaluated after the move and before the use.
Move ๐
The check currently only considers calls of std::move on local variables or function parameters. It does not check moves of member variables or global variables.
Any call of std::move on a variable is considered to cause a move of that variable, even if the result of std::move is not passed to an rvalue reference parameter.
This means that the check will flag a use-after-move even on a type that does not define a move constructor or move assignment operator. This is intentional. Developers may use std::move on such a type in the expectation that the type will add move semantics in the future. If such a std::move has the potential to cause a use-after-move, we want to warn about it even if the type does not implement move semantics yet.
Furthermore, if the result of std::move is passed to an rvalue reference parameter, this will always be considered to cause a move, even if the function that consumes this parameter does not move from it, or if it does so only conditionally. For example, in the following situation, the check will assume that a move always takes place:
std::vector<std::string> messages;
void f(std::string &&str) {
// Only remember the message if it isn't empty.
if (!str.empty()) {
messages.emplace_back(std::move(str));
}
}
std::string str = "";
f(std::move(str));
The check will assume that the last line causes a move, even though, in this particular case, it does not. Again, this is intentional.
There is one special case: a call to std::move inside a try_emplace call is conservatively assumed not to move. This is to avoid spurious warnings, as the check has no way to reason about the bool returned by try_emplace.
When analyzing the order in which moves, uses and reinitializations happen (see section Unsequenced moves, uses, and reinitializations), the move is assumed to occur in whichever function the result of the std::move is passed to.
The check also handles perfect-forwarding with std::forward, so the following code will also trigger a use-after-move warning:
void consume(int);
void f(int&& i) {
consume(std::forward<int>(i));
consume(std::forward<int>(i)); // use-after-move
}
Use ๐
Any occurrence of the moved variable that is not a reinitialization (see below) is considered to be a use.
An exception to this are objects of type std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional, and std::any. An exception to this are objects of type std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional, and std::any, which can be reinitialized via reset. For smart pointers specifically, the moved-from objects have a well-defined state of being nullptrs, and only operator*, operator-> and operator[] are considered bad accesses as they would be dereferencing a nullptr.
If multiple uses occur after a move, only the first of these is flagged.
Reinitialization โจ
The check considers a variable to be reinitialized in the following cases:
- The variable occurs on the left-hand side of an assignment.
- The variable is passed to a function as a non-const pointer or non-const lvalue reference. (It is assumed that the variable may be an out-parameter for the function.)
clear()orassign()is called on the variable and the variable is of one of the standard container typesbasic_string,vector,deque,forward_list,list,set,map,multiset,multimap,unordered_set,unordered_map,unordered_multiset,unordered_multimap.reset()is called on the variable and the variable is of typestd::unique_ptr,std::shared_ptr,std::weak_ptr,std::optional, orstd::any.- A member function marked with the
[[clang::reinitializes]]attribute is called on the variable.
So like... the check is not gonna call that a reinitialization, even if you eventually write to every single member. Example time:
struct S {
std::string str;
int i;
};
S s = { "Hello, world!\n", 42 };
S s_other = std::move(s);
s.str = "Lorem ipsum";
s.i = 99;
The check will not consider s to be reinitialized after that last line โ instead, the line assigning to s.str gets flagged as a use-after-move. And that's intentional, bestie, because reinitializing a struct member-by-member is giving bug farm energy. Like, if someone adds one more member variable to S, it is so easy to forget to reinit that new one too. The safer move is to assign to the whole entire struct in one go โ and bonus, that also dodges the use-after-move warning. ๐
bugprone-virtual-near-miss
Warns you when a function is a near miss โ meaning the name is super similar and the signature is the same โ to a virtual function from a base class. It's giving "you almost typed the right thing" ๐ญ
Example:
struct Base {
virtual void func();
};
struct Derived : Base {
virtual void funk();
// warning: 'Derived::funk' has a similar name and the same signature as virtual method 'Base::func'; did you mean to override it?
};
cert-arr39-c
cert-arr39-c is an alias, bestie โ see bugprone-sizeof-expression for the actual tea. ๐ซ
cert-con36-c
cert-con36-c is an alias too โ head to bugprone-spuriously-wake-up-functions for more info.
cert-con54-cpp
cert-con54-cpp? Also an alias. Same destination: bugprone-spuriously-wake-up-functions.
cert-ctr56-cpp
cert-ctr56-cpp is an alias โ go read bugprone-pointer-arithmetic-on-polymorphic-object for more.
cert-dcl03-c
cert-dcl03-c is an alias, see misc-static-assert for more information.
cert-dcl16-c
cert-dcl16-c is an alias, see readability-uppercase-literal-suffix for more.
cert-dcl37-c
cert-dcl37-c is an alias, see bugprone-reserved-identifier for more.
cert-dcl50-cpp
Okay this one's a real one ๐ It flags all function definitions (but not declarations) of C-style variadic functions.
It corresponds to the CERT C++ Coding Standard rule DCL50-CPP. Do not define a C-style variadic function โ https://www.securecoding.cert.org/confluence/display/cplusplus/DCL50-CPP.+Do+not+define+a+C-style+variadic+function
cert-dcl51-cpp
cert-dcl51-cpp is an alias, see bugprone-reserved-identifier for more information.
cert-dcl54-cpp
cert-dcl54-cpp is an alias, see misc-new-delete-overloads for more information.
cert-dcl58-cpp
Modifying the std or posix namespace can result in undefined behavior ๐ฑ and this check warns you about exactly that. The std (or posix) namespace is allowed to be extended with (class or function) template specializations that depend on a user-defined type (a type that is not defined in the standard system headers). The more you know โจ
The check detects the following (user provided) declarations in namespace std or posix:
- Anything that is not a template specialization.
- Explicit specializations of any standard library function template or class template, if it doesn't have any user-defined type as a template argument.
- Explicit specializations of any member function of a standard library class template.
- Explicit specializations of any member function template of a standard library class or class template.
- Explicit or partial specialization of any member class template of a standard library class or class template.
Examples, because seeing it hits different:
namespace std {
int x; // warning: modification of 'std' namespace can result in undefined behavior [cert-dcl58-cpp]
}
namespace posix::a { // warning: modification of 'posix' namespace can result in undefined behavior
}
template <>
struct ::std::hash<long> { // warning: modification of 'std' namespace can result in undefined behavior
unsigned long operator()(const long &K) const {
return K;
}
};
struct MyData { long data; };
template <>
struct ::std::hash<MyData> { // no warning: specialization with user-defined type
unsigned long operator()(const MyData &K) const {
return K.data;
}
};
namespace std {
template <>
void swap<bool>(bool &a, bool &b); // warning: modification of 'std' namespace can result in undefined behavior
template <>
bool less<void>::operator()<MyData &&, MyData &&>(MyData &&, MyData &&) const { // warning: modification of 'std' namespace can result in undefined behavior
return true;
}
}
This check corresponds to the CERT C++ Coding Standard rule DCL58-CPP. Do not modify the standard namespaces โ https://www.securecoding.cert.org/confluence/display/cplusplus/DCL58-CPP.+Do+not+modify+the+standard+namespaces
cert-dcl59-cpp
cert-dcl59-cpp is an alias, see google-build-namespaces for more information.
cert-env33-c
This check flags calls to system(), popen(), and _popen() โ you know, the ones that execute a command processor. It does not flag calls to system() with a null pointer argument, because a call like that just checks for the presence of a command processor and doesn't actually attempt to execute a command. Iconic nuance, honestly.
This check corresponds to the CERT C Coding Standard rule ENV33-C. Do not call system() โ https://www.securecoding.cert.org/confluence/display/c/ENV33-C.+Do+not+call+system()
cert-err09-cpp
cert-err09-cpp is an alias, please see misc-throw-by-value-catch-by-reference for more information.
This check corresponds to the CERT C++ Coding Standard recommendation ERR09-CPP. Throw anonymous temporaries. But โ plot twist โ all of the CERT recommendations have been removed from public view ๐ so their justification for the behavior of this check requires an account on their wiki to view. iykyk.
cert-err33-c
This one warns you about unused function return values. Bestie, so many standard library functions return a value telling you whether the call actually worked โ and ignoring that value can cause unexpected behavior if an error has occurred. The following functions are checked:
aligned_alloc()asctime_s()at_quick_exit()atexit()bsearch()bsearch_s()btowc()c16rtomb()c32rtomb()calloc()clock()cnd_broadcast()cnd_init()cnd_signal()cnd_timedwait()cnd_wait()ctime_s()fclose()fflush()fgetc()fgetpos()fgets()fgetwc()fopen()fopen_s()fprintf()fprintf_s()fputc()fputs()fputwc()fputws()fread()freopen()freopen_s()fscanf()fscanf_s()fseek()fsetpos()ftell()fwprintf()fwprintf_s()fwrite()fwscanf()fwscanf_s()getc()getchar()getenv()getenv_s()gets_s()getwc()getwchar()gmtime()gmtime_s()localtime()localtime_s()malloc()mbrtoc16()mbrtoc32()mbsrtowcs()mbsrtowcs_s()mbstowcs()mbstowcs_s()memchr()mktime()mtx_init()mtx_lock()mtx_timedlock()mtx_trylock()mtx_unlock()printf_s()putc()putwc()raise()realloc()remove()rename()setlocale()setvbuf()scanf()scanf_s()signal()snprintf()snprintf_s()sprintf()sprintf_s()sscanf()sscanf_s()strchr()strerror_s()strftime()strpbrk()strrchr()strstr()strtod()strtof()strtoimax()strtok()strtok_s()strtol()strtold()strtoll()strtoumax()strtoul()strtoull()strxfrm()swprintf()swprintf_s()swscanf()swscanf_s()thrd_create()thrd_detach()thrd_join()thrd_sleep()time()timespec_get()tmpfile()tmpfile_s()tmpnam()tmpnam_s()tss_create()tss_get()tss_set()ungetc()ungetwc()vfprintf()vfprintf_s()vfscanf()vfscanf_s()vfwprintf()vfwprintf_s()vfwscanf()vfwscanf_s()vprintf_s()vscanf()vscanf_s()vsnprintf()vsnprintf_s()vsprintf()vsprintf_s()vsscanf()vsscanf_s()vswprintf()vswprintf_s()vswscanf()vswscanf_s()vwprintf_s()vwscanf()vwscanf_s()wcrtomb()wcschr()wcsftime()wcspbrk()wcsrchr()wcsrtombs()wcsrtombs_s()wcsstr()wcstod()wcstof()wcstoimax()wcstok()wcstok_s()wcstol()wcstold()wcstoll()wcstombs()wcstombs_s()wcstoumax()wcstoul()wcstoull()wcsxfrm()wctob()wctrans()wctype()wmemchr()wprintf_s()wscanf()wscanf_s()
Whew. That's the whole roster, no skips. ๐
This check is an alias of the bugprone-unused-return-value check, but with a fixed set of functions.
Suppressing issues by casting to void is enabled by default, and you can disable that by setting the AllowCastToVoid option to false.
The check corresponds to a part of CERT C Coding Standard rule ERR33-C. Detect and handle standard library errors โ https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors. The list of checked functions is taken from the rule, with the following exception:
- The check can not differentiate if a function is called with
NULLargument. Therefore the following functions are not checked:mblen,mbrlen,mbrtowc,mbtowc,wctomb,wctomb_s
๐ cert-err34-c
Okay so this one is SO important, bestie โ it flags any call to a string-to-number conversion function that just... doesn't check whether the conversion actually worked. We're talking atoi() or scanf() energy. ๐
The thing it does not flag? Calls to strtol(), or other related conversion functions that actually do better error checking. Those are the good ones. Slay.
#include <stdlib.h>
void func(const char *buff) {
int si;
if (buff) {
si = atoi(buff); /* 'atoi' used to convert a string to an integer, but function will
not report conversion errors; consider using 'strtol' instead. */
} else {
/* Handle error */
}
}
This check corresponds to the CERT C Coding Standard rule ERR34-C. Detect errors when converting a string to a number โ https://www.securecoding.cert.org/confluence/display/c/ERR34-C.+Detect+errors+when+converting+a+string+to+a+number.
๐ cert-err52-cpp
This check flags, like, all call expressions involving setjmp() and longjmp(). Every single one. No exceptions, no mercy. โจ
This check corresponds to the CERT C++ Coding Standard rule ERR52-CPP. Do not use setjmp() or longjmp() โ https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=1834.
๐ฆ cert-err58-cpp
Flags all static or thread_local variable declarations where the initializer for the object may throw an exception. Like, bestie, you can't be throwing before main() even starts, that's not the vibe. ๐ญ
This check corresponds to the CERT C++ Coding Standard rule ERR58-CPP. Handle all exceptions thrown before main() begins executing โ https://www.securecoding.cert.org/confluence/display/cplusplus/ERR58-CPP.+Handle+all+exceptions+thrown+before+main%28%29+begins+executing.
๐ธ cert-err60-cpp
Flags all throw expressions where the exception object is not nothrow copy constructible. Ngl, that's the whole check.
This check corresponds to the CERT C++ Coding Standard rule ERR60-CPP. Exception objects must be nothrow copy constructible โ https://www.securecoding.cert.org/confluence/display/cplusplus/ERR60-CPP.+Exception+objects+must+be+nothrow+copy+constructible.
๐ cert-err61-cpp
The cert-err61-cpp check is an alias ๐
โ please see misc-throw-by-value-catch-by-reference for more information.
๐ซถ cert-exp42-c
The cert-exp42-c check is an alias, bestie โ please see bugprone-suspicious-memory-comparison for more information.
โญ cert-fio38-c
The cert-fio38-c check is an alias โ please see misc-non-copyable-objects for more information.
๐ฅ cert-flp30-c
This check flags for loops where the induction expression has a floating-point type. Yes, float loop counters. We don't do that here. ๐ซ
This check corresponds to the CERT C Coding Standard rule FLP30-C. Do not use floating-point variables as loop counters โ https://www.securecoding.cert.org/confluence/display/c/FLP30-C.+Do+not+use+floating-point+variables+as+loop+counters.
๐ cert-flp37-c
The cert-flp37-c check is an alias โ please see bugprone-suspicious-memory-comparison for more information.
๐ง cert-int09-c
The cert-int09-c check is an alias โ please see readability-enum-initial-value for more information.
๐ cert-mem57-cpp
This check flags uses of the default operator new where the type has extended alignment โ meaning an alignment greater than the fundamental alignment. (The default operator new is guaranteed to provide the correct alignment if the requested alignment is less or equal to the fundamental alignment. That part's fine, bb.)
Only cases are detected (by design) where the operator new is not user-defined and is not a placement new โ the reason being that in these cases we assume that the user provided the correct memory allocation. Iconic assumption tbh.
This check corresponds to the CERT C++ Coding Standard rule MEM57-CPP. Avoid using default operator new for over-aligned types โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM57-CPP.+Avoid+using+default+operator+new+for+over-aligned+types.
๐ cert-msc24-c
The cert-msc24-c check is an alias โ please see bugprone-unsafe-functions for more information.
๐ cert-msc30-c
The cert-msc30-c check is an alias โ please see cert-msc50-cpp for more information.
๐ cert-msc32-c
The cert-msc32-c check is an alias โ please see cert-msc51-cpp for more information.
๐ฆ cert-msc33-c
The cert-msc33-c check is an alias โ please see bugprone-unsafe-functions for more information.
๐
cert-msc50-cpp
So, pseudorandom number generators use mathematical algorithms to produce a sequence of numbers with good statistical properties โ but the numbers produced are not genuinely random. Shocking, I know. ๐ญ
The std::rand() function takes a seed (a number), runs a mathematical operation on it, and returns the result. By manipulating the seed, the result can be predictable. And this check warns for the usage of std::rand(). Periodt.
๐ง cert-msc51-cpp
This check flags all pseudo-random number engines, engine adaptor instantiations, and srand() when initialized or seeded with a default argument, a constant expression, or any user-configurable type. Pseudo-random number engines seeded with a predictable value may cause vulnerabilities, e.g. in security protocols. Not the vibe, bestie. ๐จ
This is a CERT security rule, see MSC51-CPP. Ensure your random number generator is properly seeded โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC51-CPP.+Ensure+your+random+number+generator+is+properly+seeded โ and MSC32-C. Properly seed pseudorandom number generators โ https://wiki.sei.cmu.edu/confluence/display/c/MSC32-C.+Properly+seed+pseudorandom+number+generators.
Examples:
void foo() {
std::mt19937 engine1; // Diagnose, always generate the same sequence
std::mt19937 engine2(1); // Diagnose
engine1.seed(); // Diagnose
engine2.seed(1); // Diagnose
std::time_t t;
engine1.seed(std::time(&t)); // Diagnose, system time might be controlled by user
int x = atoi(argv[1]);
std::mt19937 engine3(x); // Will not warn
}
โจ Options
DisallowedSeedTypesโ a comma-separated list of the type names which are disallowed. Default value istime_t,std::time_t. ๐ซถ
๐ธ cert-msc54-cpp
The cert-msc54-cpp check is an alias โ please see bugprone-signal-handler for more information.
โญ cert-oop11-cpp
The cert-oop11-cpp check is an alias โ please see performance-move-constructor-init for more information.
This check corresponds to the CERT C++ Coding Standard recommendation OOP11-CPP. Do not copy-initialize members or base classes from a move constructor.
However, all of the CERT recommendations have been removed from public view, and so their justification for the behavior of this check requires an account on their wiki to view. ๐ญ (RIP, fr fr.)
๐ฆ cert-oop54-cpp
The cert-oop54-cpp check is an alias โ please see bugprone-unhandled-self-assignment for more information.
๐ฅ cert-oop57-cpp
Flags use of the C standard library functions memset, memcpy and memcmp and similar derivatives on non-trivial types. Like, don't be doing raw memory things to your fancy C++ objects, bestie. ๐
โจ Options
MemSetNamesโ specify extra functions to flag that act similarly tomemset. Specify names in a semicolon delimited list. Default is an empty string. The check will detect the following functions:memset,std::memset.MemCpyNamesโ specify extra functions to flag that act similarly tomemcpy. Specify names in a semicolon delimited list. Default is an empty string. The check will detect the following functions:std::memcpy,memcpy,std::memmove,memmove,std::strcpy,strcpy,memccpy,stpncpy,strncpy.MemCmpNamesโ specify extra functions to flag that act similarly tomemcmp. Specify names in a semicolon delimited list. Default is an empty string. The check will detect the following functions:std::memcmp,memcmp,std::strcmp,strcmp,strncmp.
This check corresponds to the CERT C++ Coding Standard rule OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP57-CPP.+Prefer+special+member+functions+and+overloaded+operators+to+C+Standard+Library+functions.
๐ซถ cert-oop58-cpp
Finds assignments to the copied object and its direct or indirect members in copy constructors and copy assignment operators. Like, why are you mutating the thing you're copying from?? ๐
This check corresponds to the CERT C Coding Standard rule OOP58-CPP. Copy operations must not mutate the source object โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP58-CPP.+Copy+operations+must+not+mutate+the+source+object.
๐ cert-pos44-c
The cert-pos44-c check is an alias โ please see bugprone-bad-signal-to-kill-thread for more information.
๐ cert-pos47-c
The cert-pos47-c check is an alias โ please see concurrency-thread-canceltype-asynchronous for more information.
๐ cert-sig30-c
The cert-sig30-c check is an alias โ please see bugprone-signal-handler for more information.
๐ cert-str34-c
The cert-str34-c check is an alias โ please see bugprone-signed-char-misuse for more information.
๐ธ clang-analyzer-core.BitwiseShift
Finds cases where a bitwise shift operation causes undefined behaviour. Yikes. ๐ฌ
The clang-analyzer-core.BitwiseShift check is an alias โ please see Clang Static Analyzer Available Checkers โ https://clang.llvm.org/docs/analyzer/checkers.html#core-bitwiseshift โ for more information.
โญ clang-analyzer-core.CallAndMessage
Checks for logical errors for function calls and Objective-C message expressions (e.g., uninitialized arguments, null function pointers). We love a catch-all. ๐
The clang-analyzer-core.CallAndMessage check is an alias โ please see Clang Static Analyzer Available Checkers โ https://clang.llvm.org/docs/analyzer/checkers.html#core-callandmessage โ for more information.
๐ฅ clang-analyzer-core.DivideZero
Checks for division by zero. Math girlies stay winning, everyone else gets a warning. ๐งฎโจ
The clang-analyzer-core.DivideZero check is an alias โ please see Clang Static Analyzer Available Checkers โ https://clang.llvm.org/docs/analyzer/checkers.html#core-dividezero โ for more information.
๐ฆ clang-analyzer-core.NonNullParamChecker
Checks for null pointers passed as arguments to a function whose arguments are references, or are marked with the 'nonnull' attribute. Like bestie, you said it was non-null. ๐ญ
The clang-analyzer-core.NonNullParamChecker check is an alias โ please see Clang Static Analyzer Available Checkers โ https://clang.llvm.org/docs/analyzer/checkers.html#core-nonnullparamchecker โ for more information.
๐ clang-analyzer-core.NullDereference
Checks for dereferences of null pointers. The classic. The icon. The moment. ๐ซถ
The clang-analyzer-core.NullDereference check is an alias โ please see Clang Static Analyzer Available Checkers โ https://clang.llvm.org/docs/analyzer/checkers.html#core-nulldereference โ for more information.
๐ง clang-analyzer-core.StackAddressEscape
Checks that addresses to stack memory do not escape the function. Don't be handing out pointers to your stack frame, that's so not it. ๐ซ
The clang-analyzer-core.StackAddressEscape check is an alias โ please see Clang Static Analyzer Available Checkers โ https://clang.llvm.org/docs/analyzer/checkers.html#core-stackaddressescape โ for more information.
๐ clang-analyzer-core.UndefinedBinaryOperatorResult
Checks for undefined results of binary operators.
Lowkey the check names alone tell you what's up, but fr fr โ this one's an alias, so pls go see Clang Static Analyzer Available Checkers for the full lore: https://clang.llvm.org/docs/analyzer/checkers.html#core-undefinedbinaryoperatorresult ๐
clang-analyzer-core.VLASize
Flags any declaration of a VLA that's undefined or zero-sized. bestie that's not a size, that's a crisis ๐ซ
It's an alias, so see Clang Static Analyzer Available Checkers for more info: https://clang.llvm.org/docs/analyzer/checkers.html#core-vlasize
clang-analyzer-core.uninitialized.ArraySubscript
Catches uninitialized values being used as array subscripts ๐ญ (yes, garbage index = garbage everything).
Alias check! See Clang Static Analyzer Available Checkers for more: https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-arraysubscript
clang-analyzer-core.uninitialized.Assign
Snitches on you when you assign uninitialized values โจ
It's an alias โ Clang Static Analyzer Available Checkers has the details: https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-assign
clang-analyzer-core.uninitialized.Branch
Spots uninitialized values used as branch conditions ๐ so your if isn't just vibes.
Alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-branch
clang-analyzer-core.uninitialized.CapturedBlockVariable
Checks for blocks that capture uninitialized values ๐ no cap, that's a trap.
It's an alias โ more in Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-capturedblockvariable
clang-analyzer-core.uninitialized.NewArraySize
Checks if the array size in a new[] expression is undefined ๐ ma'am, how many?
Alias energy โ see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-newarraysize
clang-analyzer-core.uninitialized.UndefReturn
Catches uninitialized values getting returned to the caller ๐ the audacity.
It's an alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-undefreturn
clang-analyzer-cplusplus.ArrayDelete
Reports destructions of arrays of polymorphic objects that are destructed as their base class ๐ฅ classic delete-vs-delete[] heartbreak.
Alias, so Clang Static Analyzer Available Checkers is where it's at: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-arraydelete
clang-analyzer-cplusplus.InnerPointer
Checks for inner pointers of C++ containers used after re/deallocation ๐ that pointer is literally expired.
Alias! See Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-innerpointer
clang-analyzer-cplusplus.Move
Finds use-after-move bugs in C++ ๐ฆ you moved it, bestie, let it go.
It's an alias โ Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-move
clang-analyzer-cplusplus.NewDelete
Checks for double-free and use-after-free problems, and traces memory managed by new/delete ๐
Alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-newdelete
clang-analyzer-cplusplus.NewDeleteLeaks
Checks for memory leaks, tracing memory managed by new/delete ๐ง ur allocations are crying.
It's an alias, so see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-newdeleteleaks
clang-analyzer-cplusplus.PlacementNew
Checks if default placement new is given pointers to storage with sufficient capacity ๐ฆ is there room?? check first!!
Alias โ Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-placementnew
clang-analyzer-cplusplus.SelfAssignment
Checks C++ copy and move assignment operators for self assignment ๐ช a = a?? girl.
It's an alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-stringchecker
clang-analyzer-cplusplus.StringChecker
Checks C++ std::string bugs ๐ strings deserve better.
Alias! Clang Static Analyzer Available Checkers for more: https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-stringchecker
clang-analyzer-deadcode.DeadStores
Checks for values stored to variables that are never read afterwards ๐ writing into the void fr fr.
It's an alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#deadcode-deadstores
clang-analyzer-fuchsia.HandleChecker
A checker that detects leaks related to Fuchsia handles ๐ธ leak = not slay.
Alias โ Clang Static Analyzer Available Checkers has the tea: https://clang.llvm.org/docs/analyzer/checkers.html#fuchsia-handlechecker
clang-analyzer-nullability.NullPassedToNonnull
Warns when a null pointer gets passed to a pointer with a _Nonnull type ๐ญ the type said nonnull!!
It's an alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullpassedtononnull
clang-analyzer-nullability.NullReturnedFromNonnull
Warns when a null pointer is returned from a function that has a _Nonnull return type ๐ caught in 4K.
Alias, so see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullreturnedfromnonnull
clang-analyzer-nullability.NullableDereferenced
Warns when a nullable pointer is dereferenced ๐ bestie check it first, i'm begging.
It's an alias โ Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullabledereferenced
clang-analyzer-nullability.NullablePassedToNonnull
Warns when a nullable pointer gets passed to a pointer with a _Nonnull type ๐ซถ the mismatch is giving red flag.
Alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullablepassedtononnull
clang-analyzer-nullability.NullableReturnedFromNonnull
Warns when a nullable pointer is returned from a function that has a _Nonnull return type ๐ญ you promised nonnull!!
It's an alias โ see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullablereturnedfromnonnull
clang-analyzer-optin.core.EnumCastOutOfRange
Checks integer-to-enumeration casts for out-of-range values ๐ฅ that value is not on the enum's guest list.
Alias! Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-core-enumcastoutofrange
clang-analyzer-optin.cplusplus.UninitializedObject
Reports uninitialized fields after object construction ๐ your object got dressed but forgot its shoes.
It's an alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-cplusplus-uninitializedobject
clang-analyzer-optin.cplusplus.VirtualCall
Checks virtual function calls during construction/destruction ๐ฆ it's giving "who am i even".
Alias โ Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-cplusplus-virtualcall
clang-analyzer-optin.mpi.MPI-Checker
Checks MPI code ๐ parallel vibes, verified.
It's an alias, so see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-mpi-mpi-checker
clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker
Checks that NSLocalizedString macros include a comment for context ๐ translators need context bestie!!
This one's an alias โ Clang Static Analyzer Available Checkers for more: https://clang.llvm.org/docs/analyzer/checkers.html#optin-osx-cocoa-localizability-emptylocalizationcontextchecker
clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker
Warns about uses of non-localized NSStrings passed to UI methods that expect localized NSStrings ๐ธ localization check said hello.
Alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-osx-cocoa-localizability-nonlocalizedstringchecker
clang-analyzer-optin.performance.GCDAntipattern
Checks for performance anti-patterns when using Grand Central Dispatch ๐ฅ anti-pattern is right there in the name.
It's an alias โ Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-performance-gcdantipattern
clang-analyzer-optin.performance.Padding
Checks for excessively padded structs ๐ฆ so much empty space, it's giving echo.
Alias! See Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-performance-padding
clang-analyzer-optin.portability.UnixAPI
Finds implementation-defined behavior in UNIX/Posix functions ๐ portable or it didn't happen.
It's an alias, please see Clang Static Analyzer Available Checkers: https://clang.llvm.org/docs/analyzer/checkers.html#optin-portability-unixapi
๐ clang-analyzer-optin.taint.TaintedAlloc
bestie this one is looking at memory allocations where the size parameter might be a tainted value โ like, attacker controlled ๐ญ the whole point is catching a size arg that some rando on the internet could influence.
the clang-analyzer-optin.taint.TaintedAlloc check is an alias, so head over to Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-taint-taintedalloc> for the whole story โจ
๐ฆ clang-analyzer-osx.API
vibe check on your Apple code โ this one checks for proper uses of various Apple APIs ๐ literally just "are you using these right".
it's an alias bestie, see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-api> for more info ๐
๐ clang-analyzer-osx.NumberObjectConversion
caught you converting objects that represent numbers into actual numbers and doing it wrong ๐ erroneous conversions only, the good ones are fine.
clang-analyzer-osx.NumberObjectConversion is an alias โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-numberobjectconversion> has the full tea ๐ซ
๐ clang-analyzer-osx.ObjCProperty
checking that you're using Objective-C properties properly, no cap โจ property misuse is such a vibe killer.
this one's an alias too, so Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-objcproperty> is where the details live ๐ฆ
๐ฅ clang-analyzer-osx.SecKeychainAPI
proper uses of Secure Keychain APIs ๐ security-wise this one matters, like, a lot โ keychain code that's sloppy is giving "I keep my passwords in a notes app".
it's an alias bestie, more at Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-seckeychainapi> ๐ธ
โญ clang-analyzer-osx.cocoa.AtSync
this one clocks nil pointers used as mutexes for @synchronized ๐ญ like bestie you cannot be locking on nil, that's not a vibe, that's a bug.
alias alert โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-atsync> for the deets ๐
๐ง clang-analyzer-osx.cocoa.AutoreleaseWrite
warns about writes to autoreleasing objects from different autoreleasing pools in Objective-C that could potentially crash you ๐ like, the write itself is the threat, no thank you.
it's an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-autoreleasewrite> for more information ๐
๐ clang-analyzer-osx.cocoa.ClassRelease
the way this one checks for sending 'retain', 'release', or 'autorelease' directly to a Class ๐
class objects are not your little toy, they don't play that.
clang-analyzer-osx.cocoa.ClassRelease is an alias โ full info over at Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-classrelease> โจ
๐ซถ clang-analyzer-osx.cocoa.Dealloc
warns about Objective-C classes that are missing a correct implementation of -dealloc ๐ชฆ like bestie where is your cleanup era.
alias check, so Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-dealloc> is your stop ๐ฆ
๐ clang-analyzer-osx.cocoa.IncompatibleMethodTypes
objective-C method signatures with type incompatibilities โ this checker is like "girl those types do not match, what are you doing" ๐ญ it only warns, it's not out here rewriting your code.
alias, ok? Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-incompatiblemethodtypes> for more ๐ฅ
๐ clang-analyzer-osx.cocoa.Loops
improved modeling of loops using Cocoa collection types ๐ not a bug hunter exactly โ it's teaching the analyzer how your fast-enumeration loops actually behave, which is lowkey iconic.
it's an alias, see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-loops> for more info ๐ธ
๐ clang-analyzer-osx.cocoa.MissingSuperCall
warns about Objective-C methods that lack a necessary call to super ๐ค bestie your parent class needs its moment, don't skip it.
alias behaviour only โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-missingsupercall> has more ๐
๐ clang-analyzer-osx.cocoa.NSAutoreleasePool
warns for suboptimal uses of NSAutoreleasePool in Objective-C GC mode โ suboptimal is such a polite word for it ngl ๐
this check is an alias, so Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nsautoreleasepool> for the details ๐
๐ธ clang-analyzer-osx.cocoa.NSError
checking your usage of NSError** parameters โจ error out-params are so easy to fumble and this one is watching.
alias energy โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nserror> for more info ๐ฆ
๐ clang-analyzer-osx.cocoa.NilArg
prohibited nil arguments to ObjC method calls โ bestie some methods are simply not accepting nil and this check will let you know ๐ญ
it's an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nilarg> for more ๐
โญ clang-analyzer-osx.cocoa.NonNilReturnValue
this one models the APIs that are guaranteed to return a non-nil value ๐ฏ it's not yelling at you, it's teaching the analyzer which calls always come back with something.
alias, so head to Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nonnilreturnvalue> for more information ๐ซถ
๐ clang-analyzer-osx.cocoa.ObjCGenerics
type errors when you're using Objective-C generics ๐งฌ yes generics exist in ObjC, yes you can misuse them, yes this catches it.
clang-analyzer-osx.cocoa.ObjCGenerics is an alias โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-objcgenerics> has the rest โจ
๐ฅ clang-analyzer-osx.cocoa.RetainCount
leaks and improper reference count management ๐ฉธ the classic. the icon. the retain/release saga. if your counts are off, this one notices.
it's an alias bestie, more at Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-retaincount> ๐
๐ clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak
leaked memory in autorelease pools that will never be drained ๐ง like the pool just sits there forever holding your objects hostage, so dramatic.
alias check โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-runloopautoreleaseleak> for the full picture ๐ฆ
๐ clang-analyzer-osx.cocoa.SelfInit
checks that 'self' is properly initialized inside an initializer method ๐ญ because self = [super init] and actually checking the result is a whole personality trait.
this one's an alias, see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-selfinit> for more ๐ธ
๐ชฆ clang-analyzer-osx.cocoa.SuperDealloc
warns about improper use of '[super dealloc]' in Objective-C ๐ง improper being the key word, this one is very "read the room".
alias, ok? Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-superdealloc> for the info ๐
๐ต๏ธ clang-analyzer-osx.cocoa.UnusedIvars
warns about private ivars that are never used ๐ป ghost variables. haunted. nobody's calling them, they're just vibing in your class for no reason.
it's an alias bestie โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-unusedivars> has more ๐
๐ง clang-analyzer-osx.cocoa.VariadicMethodTypes
checks for passing non-Objective-C types to variadic collection initialization methods that expect only Objective-C types ๐ซ like bestie you cannot put an int in there, that's not how this works.
alias behaviour, so Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-variadicmethodtypes> for the details โจ
๐ clang-analyzer-osx.coreFoundation.CFError
checks your usage of CFErrorRef* parameters ๐
Core Foundation error out-params, same energy as the NSError one but with more letters.
it's an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cferror> for more information ๐
๐ข clang-analyzer-osx.coreFoundation.CFNumber
proper uses of CFNumber APIs ๐ข because CFNumber has opinions about how you're supposed to do things and this check knows them all.
alias check โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cfnumber> for the full breakdown ๐
๐ clang-analyzer-osx.coreFoundation.CFRetainRelease
checks for null arguments to CFRetain/CFRelease/CFMakeCollectable ๐ passing null into any of those is giving "I like crashing".
it's an alias bestie, Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cfretainrelease> for more ๐ซถ
๐ฆ clang-analyzer-osx.coreFoundation.containers.OutOfBounds
index out-of-bounds when using the 'CFArray' API ๐ฏ classic off-by-one behaviour, and honestly? we've all been there.
clang-analyzer-osx.coreFoundation.containers.OutOfBounds is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-containers-outofbounds> for more ๐
๐งฑ clang-analyzer-osx.coreFoundation.containers.PointerSizedValues
warns if 'CFArray', 'CFDictionary', 'CFSet' are created with non-pointer-size values ๐ญ bestie these containers want pointers, give them pointers.
alias alert โ Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-containers-pointersizedvalues> for more information โจ
๐ clang-analyzer-security.FloatLoopCounter
warns on using a floating point value as a loop counter โ and it cites its receipts: CERT: FLP30-C, FLP30-CPP ๐ floats accumulate error, your loop does not deserve that.
it's an alias, see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#security-floatloopcounter> for more ๐
๐ง clang-analyzer-security.PutenvStackArray
this one finds calls to the putenv function which pass a pointer to a stack-allocated (automatic) array as the argument ๐ฑ and here's why that's scary: function putenv does not copy the passed string โ only a pointer to the data is stored, and this data can be read even by other threads. and the content of a stack-allocated array is likely to be overwritten after exiting from the function. so you've got a dangling-ish env var situation, love that for you ๐ซ
clang-analyzer-security.PutenvStackArray is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#security-putenvstackarray-c> for more information ๐
๐ clang-analyzer-security.SetgidSetuidOrder
this checker looks for sequences of setuid(getuid()) and setgid(getgid()) calls โ in this order ๐ง if such a sequence is found and there's no other privilege-changing function call in between (seteuid, setreuid, setresuid and the GID versions of these), a warning is generated. important caveat bestie: the checker finds only exactly setuid(getuid()) calls (and the GID versions) โ not for example if the result of getuid() is stored in a variable first ๐ so don't get cute with intermediate variables and expect this to catch it.
it's an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#security-setgidsetuidorder-c> for more information ๐
clang-analyzer-security.cert.env.InvalidPtr
Finds usages of possibly invalidated pointers. ๐ Basically it's side-eyeing pointers that might not be valid anymore.
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-cert-env-invalidptr for more information.
clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling
Warn on uses of unsecure or deprecated buffer manipulating functions. ๐ซถ Buffer drama? It's watching.
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-deprecatedorunsafebufferhandling for more information.
clang-analyzer-security.insecureAPI.UncheckedReturn
Warn on uses of functions whose return values must be always checked. ๐ Like, you gotta check the return value, no cap.
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-uncheckedreturn for more information.
clang-analyzer-security.insecureAPI.bcmp
Warn on uses of the 'bcmp' function. ๐
This check is an alias, so head to Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bcmp for more information.
clang-analyzer-security.insecureAPI.bcopy
Warn on uses of the 'bcopy' function. โจ
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bcopy for more information.
clang-analyzer-security.insecureAPI.bzero
Warn on uses of the 'bzero' function. ๐ฆ
This check is an alias, so the full lore is in Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bzero for more information.
clang-analyzer-security.insecureAPI.decodeValueOfObjCType
Warn on uses of the '-decodeValueOfObjCType:at:' method. ๐
This check is an alias of Clang Static Analyzer security.insecureAPI.decodeValueOfObjCType.
clang-analyzer-security.insecureAPI.getpw
Warn on uses of the 'getpw' function. ๐ธ
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-getpw for more information.
clang-analyzer-security.insecureAPI.gets
Warn on uses of the 'gets' function. ๐ญ
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-gets for more information.
clang-analyzer-security.insecureAPI.mkstemp
Warn when 'mkstemp' is passed fewer than 6 X's in the format string. ๐ฅ
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-mkstemp for more information.
clang-analyzer-security.insecureAPI.mktemp
Warn on uses of the 'mktemp' function. ๐
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-mktemp for more information.
clang-analyzer-security.insecureAPI.rand
Warn on uses of the 'rand', 'random', and related functions. ๐ฒ
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-rand for more information.
clang-analyzer-security.insecureAPI.strcpy
Warn on uses of the 'strcpy' and 'strcat' functions. ๐ซถ
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-strcpy for more information.
clang-analyzer-security.insecureAPI.vfork
Warn on uses of the 'vfork' function. ๐
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-vfork for more information.
clang-analyzer-unix.API
Check calls to various UNIX/Posix functions. ๐ป
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-api for more information.
clang-analyzer-unix.BlockInCriticalSection
Check for calls to blocking functions inside a critical section. ๐จ
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-blockincriticalsection for more information.
clang-analyzer-unix.Errno
Check for improper use of 'errno'. ๐
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-errno for more information.
clang-analyzer-unix.Malloc
Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free(). ๐ญ Memory chaos gets a whole vibe check.
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-malloc for more information.
clang-analyzer-unix.MallocSizeof
Check for dubious malloc arguments involving sizeof. ๐
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-mallocsizeof for more information.
clang-analyzer-unix.MismatchedDeallocator
Check for mismatched deallocators. ๐
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocator for more information.
clang-analyzer-unix.StdCLibraryFunctions
Check for invalid arguments of C standard library functions, and apply relations between arguments and return value. ๐
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-stdclibraryfunctions for more information.
clang-analyzer-unix.Stream
Check stream handling functions. ๐
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-stream for more information.
clang-analyzer-unix.Vfork
Check for proper usage of vfork. ๐ง
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-vfork for more information.
clang-analyzer-unix.cstring.BadSizeArg
Check the size argument passed into C string functions for common erroneous patterns. ๐
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-cstring-badsizearg for more information.
clang-analyzer-unix.cstring.NullArg
Check for null pointers being passed as arguments to C string functions. ๐
This check is an alias โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-cstring-nullarg for more information.
clang-analyzer-webkit.NoUncountedMemberChecker
Check for no uncounted member variables. ๐ธ๏ธ
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#webkit-nouncountedmemberchecker for more information.
clang-analyzer-webkit.RefCntblBaseVirtualDtor
Check for any ref-countable base class having virtual destructor. ๐ง
This check is an alias, bestie โ please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#webkit-refcntblbasevirtualdtor for more information.
clang-analyzer-webkit.UncountedLambdaCapturesChecker
Check uncounted lambda captures. โจ
This check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#webkit-uncountedlambdacaptureschecker for more information.
concurrency-mt-unsafe
Checks for some thread-unsafe functions against a black list of known-to-be-unsafe functions. Usually they access static variables without synchronization (e.g. gmtime(3)) or utilize signals in a racy way. The set of functions to check is specified with the FunctionSet option. ๐
Note that using some thread-unsafe functions may be still valid in concurrent programming if only a single thread is used (e.g. setenv(3)), however, some functions may track a state in global variables which would be clobbered by subsequent (non-parallel, but concurrent) calls to a related function. E.g. the following code suffers from unprotected accesses to a global state:
// getnetent(3) maintains global state with DB connection, etc.
// If a concurrent green thread calls getnetent(3), the global state is corrupted.
netent = getnetent();
yield();
netent = getnetent();
Examples:
tm = gmtime(timep); // uses a global buffer
sleep(1); // implementation may use SIGALRM
Options
FunctionSetโ Specifies which functions inlibcshould be considered thread-safe, possible values areposix,glibc, orany.
posix means POSIX defined thread-unsafe functions. POSIX.1-2001 in "2.9.1 Thread-Safety" defines that all functions specified in the standard are thread-safe except a predefined list of thread-unsafe functions.
Glibc defines some of them as thread-safe (e.g. dirname(3)), but adds non-POSIX thread-unsafe ones (e.g. getopt_long(3)). Glibc's list is compiled from GNU web documentation with a search for MT-Safe tag: https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html
If you want to identify thread-unsafe API for at least one libc or unsure which libc will be used, use any (default). ๐ซถ
concurrency-thread-canceltype-asynchronous
okay so this check is out here catching calls to pthread_setcanceltype where a thread's cancellation type gets set to asynchronous ๐ญ. Asynchronous cancellation type (PTHREAD_CANCEL_ASYNCHRONOUS) is generally unsafe โ use type PTHREAD_CANCEL_DEFERRED instead, which is literally the default. And even with deferred cancellation, a cancellation point inside an asynchronous signal handler may still be acted upon, and the effect is as if it was an asynchronous cancellation. so like, no escape hatch bestie ๐
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
This check corresponds to the CERT C Coding Standard rule POS47-C. Do not use threads that can be canceled asynchronously โ https://wiki.sei.cmu.edu/confluence/display/c/POS47-C.+Do+not+use+threads+that+can+be+canceled+asynchronously ๐ซถ
cert-pos47-c redirects here as an alias of this check. iykyk โจ
cppcoreguidelines-avoid-c-arrays
The cppcoreguidelines-avoid-c-arrays check is an alias, please see modernize-avoid-c-arrays for more information. that's it, that's the whole vibe ๐
cppcoreguidelines-avoid-capturing-lambda-coroutines
This one flags C++20 coroutine lambdas with non-empty capture lists that may cause use-after-free errors, and suggests avoiding captures or ensuring the lambda closure object has a guaranteed lifetime. ๐ฆ
This check implements CP.51 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rcoro-capture โ from the C++ Core Guidelines.
Using coroutine lambdas with non-empty capture lists can be risky, as capturing variables can lead to accessing freed memory after the first suspension point. This issue can occur even with refcounted smart pointers and copyable types. When a lambda expression creates a coroutine, it results in a closure object with storage, which is often on the stack and will eventually go out of scope. When the closure object goes out of scope, its captures also go out of scope. While normal lambdas finish executing before this happens, coroutine lambdas may resume from suspension after the closure object has been destructed, resulting in use-after-free memory access for all captures. ๐ญ
Consider the following example:
int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
{
const auto lambda = [value, sharedFoo]() -> std::future<void>
{
co_await something();
// "sharedFoo" and "value" have already been destroyed
// the "shared" pointer didn't accomplish anything
};
lambda();
} // the lambda closure object has now gone out of scope
In this example, the lambda object is defined with two captures: value and sharedFoo. When lambda() is called, the lambda object is created on the stack, and the captures are copied into the closure object. When the coroutine is suspended, the lambda object goes out of scope, and the closure object is destroyed. When the coroutine is resumed, the captured variables may have been destroyed, resulting in use-after-free bugs. it's giving tragic ๐
In conclusion, the use of coroutine lambdas with non-empty capture lists can lead to use-after-free errors when resuming the coroutine after the closure object has been destroyed. This check helps prevent such errors by flagging C++20 coroutine lambdas with non-empty capture lists and suggesting avoiding captures or ensuring the lambda closure object has a guaranteed lifetime. ๐ซถ
Following these guidelines can help ensure the safe and reliable use of coroutine lambdas in C++ code. slay โจ
cppcoreguidelines-avoid-const-or-ref-data-members
This check warns when structs or classes that are copyable or movable have const-qualified or reference (lvalue or rvalue) data members. Having such members is rarely useful, and makes the class only copy-constructible but not copy-assignable. lowkey annoying tbh
Examples:
// Bad, const-qualified member
struct Const {
const int x;
}
// Good:
class Foo {
public:
int get() const { return x; }
private:
int x;
};
// Bad, lvalue reference member
struct Ref {
int& x;
};
// Good:
struct Foo {
int* x;
std::unique_ptr<int> x;
std::shared_ptr<int> x;
gsl::not_null<int*> x;
};
// Bad, rvalue reference member
struct RefRef {
int&& x;
};
This check implements C.12 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-constref โ from the C++ Core Guidelines.
Further reading: Data members: Never const โ https://quuxplusone.github.io/blog/2022/01/23/dont-const-all-the-things/#data-members-never-const ๐
cppcoreguidelines-avoid-do-while
Warns when using do-while loops. They're less readable than plain while loops, since the termination condition is at the end and the condition is not checked prior to the first iteration. This can lead to subtle bugs ๐
This check implements ES.75 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-do โ from the C++ Core Guidelines.
Examples:
int x;
do {
std::cin >> x;
// ...
} while (x < 0);
Options
IgnoreMacrosโ ignore the check when analyzing macros. This is useful for safely defining function-like macros:
#define FOO_BAR(x) \
do { \
foo(x); \
bar(x); \
} while(0)
Defaults to false.
cppcoreguidelines-avoid-goto
The usage of goto for control flow is error prone and should be replaced with looping constructs. Only forward jumps in nested loops are accepted. ๐
This check implements ES.76 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es76-avoid-goto โ from the C++ Core Guidelines, and 6.3.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements โ from High Integrity C++ Coding Standard.
For more information on why to avoid programming with goto you can read the famous paper A Case against the GO TO Statement. โ https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF ๐
The check diagnoses goto for backward jumps in every language mode. These should be replaced with C/C++ looping constructs.
// Bad, handwritten for loop.
int i = 0;
// Jump label for the loop
loop_start:
do_some_operation();
if (i < 100) {
++i;
goto loop_start;
}
// Better
for(int i = 0; i < 100; ++i)
do_some_operation();
Modern C++ needs goto only to jump out of nested loops.
for(int i = 0; i < 100; ++i) {
for(int j = 0; j < 100; ++j) {
if (i * j > 500)
goto early_exit;
}
}
early_exit:
some_operation();
All other uses of goto are diagnosed in C++. ๐
Options
IgnoreMacrosโ if set totrue, the check will not warn if agotostatement is expanded from a macro. Default isfalse.
cppcoreguidelines-avoid-magic-numbers
The cppcoreguidelines-avoid-magic-numbers check is an alias, please see readability-magic-numbers for more information. โจ (yes, another alias, we love a redirect)
cppcoreguidelines-avoid-non-const-global-variables
Finds non-const global variables as described in I.2 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i2-avoid-non-const-global-variables โ of C++ Core Guidelines. As R.6 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-global โ of C++ Core Guidelines is a duplicate of rule I.2 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i2-avoid-non-const-global-variables โ it also covers that rule. two rules, one check, efficiencies! ๐
char a; // Warns!
const char b = 0;
namespace some_namespace
{
char c; // Warns!
const char d = 0;
}
char * c_ptr1 = &some_namespace::c; // Warns!
char *const c_const_ptr = &some_namespace::c; // Warns!
char & c_reference = some_namespace::c; // Warns!
class Foo // No Warnings inside Foo, only namespace scope is covered
{
public:
char e = 0;
const char f = 0;
protected:
char g = 0;
private:
char h = 0;
};
The variables a, c, c_ptr1, c_const_ptr and c_reference will all generate warnings since they are either a non-const globally accessible variable, a pointer or a reference providing global access to non-const data โ or both. caught in 4k ๐ธ
Options
AllowInternalLinkageโ when set totrue,staticnon-const variables and variables in anonymous namespaces will not generate a warning. The default value isfalse.
cppcoreguidelines-avoid-reference-coroutine-parameters
Warns when a coroutine accepts reference parameters. After a coroutine suspend point, references could be dangling and no longer valid. Instead, pass parameters as values. ๐ซถ
Examples:
std::future<int> someCoroutine(int& val) {
co_await ...;
// When the coroutine is resumed, 'val' might no longer be valid.
if (val) ...
}
This check implements CP.53 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rcoro-reference-parameters โ from the C++ Core Guidelines.
cppcoreguidelines-c-copy-assignment-signature
The cppcoreguidelines-c-copy-assignment-signature check is an alias, please see misc-unconventional-assign-operator for more information. ๐
cppcoreguidelines-explicit-virtual-functions
The cppcoreguidelines-explicit-virtual-functions check is an alias, please see modernize-use-override for more information. ๐ฆ
cppcoreguidelines-init-variables
Checks whether there are local variables that are declared without an initial value. These may lead to unexpected behavior if there is a code path that reads the variable before assigning to it. ๐ญ
This rule is part of the Type safety (Type.5) โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-init โ profile and ES.20 โ https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-always โ from the C++ Core Guidelines.
Only integers, booleans, floats, doubles and pointers are checked. The fix option initializes all detected values with the value of zero. An exception is float and double types, which are initialized to NaN. โจ
As an example a function that looks like this:
void function() {
int x;
char *txt;
double d;
// Rest of the function.
}
Would be rewritten to look like this:
#include <math.h>
void function() {
int x = 0;
char *txt = nullptr;
double d = NAN;
// Rest of the function.
}
It warns for the uninitialized enum case, but without a FixIt: ๐
enum A {A1, A2, A3};
enum A_c : char { A_c1, A_c2, A_c3 };
enum class B { B1, B2, B3 };
enum class B_i : int { B_i1, B_i2, B_i3 };
void function() {
A a; // Warning: variable 'a' is not initialized
A_c a_c; // Warning: variable 'a_c' is not initialized
B b; // Warning: variable 'b' is not initialized
B_i b_i; // Warning: variable 'b_i' is not initialized
}
Options ๐
IncludeStyleโ a string that says which include style we're doing,llvmorgoogle. Default?llvm, bestie. Alwaysllvmuntil you say otherwise.MathHeaderโ a string with the header you gotta include to actually get the definition ofNAN. Default is<math.h>.
๐ cppcoreguidelines-interfaces-global-init
This one flags initializers of globals that go and access extern objects โ and like... that's literally how you get order-of-initialization problems. ๐ Spooky static-init-order stuff, no thank you.
It implements I.22 from the C++ Core Guidelines (the link is <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-global-init>, go read it, iykyk).
Heads up: right now it does not flag calls to non-constexpr functions, which means globals could still be accessed from functions themselves. So like... the check is not omniscient, ok? ๐
๐ cppcoreguidelines-macro-to-enum
Alias alert! ๐จ The cppcoreguidelines-macro-to-enum check is an alias, so please go see modernize-macro-to-enum for the actual tea. โ
๐ cppcoreguidelines-macro-usage
Finds macro usage that's considered problematic because โ plot twist โ better language constructs literally exist for the task. โจ
The relevant sections in the C++ Core Guidelines are ES.31 (<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es31-dont-use-macros-for-constants-or-functions>) and ES.32 (<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es32-use-all_caps-for-all-macro-names>). Go off, guidelines.
Examples:
#define C 0
#define F1(x, y) ((a) > (b) ? (a) : (b))
#define F2(...) (__VA_ARGS__)
#define F3(x, y) x##y
#define COMMA ,
#define NORETURN [[noreturn]]
#define DEPRECATED attribute((deprecated))
#if LIB_EXPORTS
#define DLLEXPORTS __declspec(dllexport)
#else
#define DLLEXPORTS __declspec(dllimport)
#endif
results in the following warnings:
4 warnings generated.
test.cpp:1:9: warning: macro 'C' used to declare a constant; consider using a 'constexpr' constant [cppcoreguidelines-macro-usage]
#define C 0
^
test.cpp:2:9: warning: function-like macro 'F1' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define F1(x, y) ((a) > (b) ? (a) : (b))
^
test.cpp:3:9: warning: variadic macro 'F2' used; consider using a 'constexpr' variadic template function [cppcoreguidelines-macro-usage]
#define F2(...) (__VA_ARGS__)
^
(yeah it says 4 warnings generated. and then shows you three, that's canon, don't come for me ๐ญ)
Options ๐
AllowedRegexpโ a regular expression to filter allowed macros. For exampleDEBUG*|LIBTORRENT*|TORRENT*|UNI*could be applied to filter libtorrent. Default value is^DEBUG_*.CheckCapsOnlyโ boolean flag to warn on all macros except those withCAPS_ONLYnames. This option is intended to ease introduction of this check into older code bases (grandma codebase energy ๐ต). Default value isfalse.IgnoreCommandLineMacrosโ boolean flag to toggle ignoring command-line-defined macros. Default value istrue.
๐ cppcoreguidelines-misleading-capture-default-by-value
Warns when a lambda specifies a by-value capture default and captures this. The way this is such a trap. ๐ชค
By-value capture defaults in member functions can be misleading about whether data members are captured by value or by reference. This happens because specifying the capture default [=] actually captures the this pointer by value, not the data members themselves. So as a result, data members are still indirectly accessed via the captured this pointer โ which essentially means they're being accessed by reference. So even when you write [=], data members are effectively captured by reference, which might not align with the user's expectations. Sneaky. ๐ญ
Examples:
struct AClass {
int member;
void misleadingLogic() {
int local = 0;
member = 0;
auto f = [=]() mutable {
local += 1;
member += 1;
};
f();
// Here, local is 0 but member is 1
}
void clearLogic() {
int local = 0;
member = 0;
auto f = [this, local]() mutable {
local += 1;
member += 1;
};
f();
// Here, local is 0 but member is 1
}
};
This check implements F.54 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f54-when-writing-a-lambda-that-captures-this-or-any-class-data-member-dont-use--default-capture>.
๐ cppcoreguidelines-missing-std-forward
Warns when a forwarding reference parameter is not forwarded inside the function body. Like bestie... you received it and then you just didn't pass it on? Rude. ๐
Example:
template <class T>
void wrapper(T&& t) {
impl(std::forward<T>(t), 1, 2); // Correct
}
template <class T>
void wrapper2(T&& t) {
impl(t, 1, 2); // Oops - should use std::forward<T>(t)
}
template <class T>
void wrapper3(T&& t) {
impl(std::move(t), 1, 2); // Also buggy - should use std::forward<T>(t)
}
template <class F>
void wrapper_function(F&& f) {
std::forward<F>(f)(1, 2); // Correct
}
template <class F>
void wrapper_function2(F&& f) {
f(1, 2); // Incorrect - may not invoke the desired qualified function operator
}
Options ๐
ForwardFunctionโ specify the function used for forwarding. Default is::std::forward.
This check implements F.19 from the C++ Core Guidelines โ <http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-forward>.
๐ cppcoreguidelines-narrowing-conversions
This check implements part of ES.46 from the C++ Core Guidelines (<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es46-avoid-lossy-narrowing-truncating-arithmetic-conversions>).
But also: the cppcoreguidelines-narrowing-conversions check is an alias, please see bugprone-narrowing-conversions for more information. โจ
๐ cppcoreguidelines-no-malloc
This check handles C-Style memory management using malloc(), realloc(), calloc() and free(). It warns about its use and tries to suggest the use of an appropriate RAII object (glow up era ๐ธ). Furthermore, it can be configured to check against a user-specified list of functions that are used for memory management (e.g. posix_memalign()).
This check implements R.10 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-mallocfree>.
There is no attempt made to provide fix-it hints, since manual resource management isn't easily transformed automatically into RAII. So no autofix for you, sorry bestie. ๐
// Warns each of the following lines.
// Containers like std::vector or std::string should be used.
char* some_string = (char*) malloc(sizeof(char) * 20);
char* some_string = (char*) realloc(sizeof(char) * 30);
free(some_string);
int* int_array = (int*) calloc(30, sizeof(int));
// Rather use a smartpointer or stack variable.
struct some_struct* s = (struct some_struct*) malloc(sizeof(struct some_struct));
Options ๐
Allocationsโ semicolon-separated list of fully qualified names of memory allocation functions. Defaults to::malloc;::calloc.Deallocationsโ semicolon-separated list of fully qualified names of memory allocation functions. Defaults to::free.Reallocationsโ semicolon-separated list of fully qualified names of memory allocation functions. Defaults to::realloc.
๐ cppcoreguidelines-no-suspend-with-lock
Flags coroutines that suspend while a lock guard is in scope at the suspension point. ๐ฉ
When a coroutine suspends, any mutexes held by the coroutine will remain locked until the coroutine resumes and eventually destructs the lock guard. That can lead to long periods with a mutex held and runs the risk of deadlock. Truly the worst-case scenario, highkey scary. ๐
Instead, locks should be released before suspending a coroutine.
This check only checks suspending coroutines while alock_guardis in scope; it does not consider manual locking or unlocking of mutexes, e.g., through calls tostd::mutex::lock().
Examples:
future bad_coro() {
std::lock_guard lock{mtx};
++some_counter;
co_await something(); // Suspending while holding a mutex
}
future good_coro() {
{
std::lock_guard lock{mtx};
++some_counter;
}
// Destroy the lock_guard to release the mutex before suspending the coroutine
co_await something(); // Suspending while holding a mutex
}
This check implements CP.52 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rcoro-locks>.
๐ cppcoreguidelines-noexcept-destructor
This check implements C.37 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c37-make-destructors-noexcept>.
The cppcoreguidelines-noexcept-destructor check is an alias, please see performance-noexcept-destructor for more information. ๐ซถ
๐ cppcoreguidelines-noexcept-move-operations
This check implements C.66 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c66-make-move-operations-noexcept>.
The cppcoreguidelines-noexcept-move-operations check is an alias, please see performance-noexcept-move-constructor for more information. ๐ซถ
๐ cppcoreguidelines-noexcept-swap
This check implements C.83 โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c83-for-value-like-types-consider-providing-a-noexcept-swap-function> โ , C.84 โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c84-a-swap-function-must-not-fail> โ and C.85 โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c85-make-swap-noexcept> โ from the C++ Core Guidelines. Three guidelines in one check, she's doing the MOST and we love her for it. ๐
The cppcoreguidelines-noexcept-swap check is an alias, please see performance-noexcept-swap for more information.
๐ cppcoreguidelines-non-private-member-variables-in-classes
The cppcoreguidelines-non-private-member-variables-in-classes check is an alias, please see misc-non-private-member-variables-in-classes for more information. That's it, that's the whole section, go look at the other one bestie โจ
๐ cppcoreguidelines-owning-memory
This check implements the type-based semantics of gsl::owner<T*>, which allows static analysis on code that uses raw pointers to handle resources like dynamic memory, but won't introduce RAII concepts. ๐
Its whole vibe is "who actually owns this pointer, bestie?" ๐
and it implements I.11 (never transfer ownership by a raw pointer T* or reference T*), C.33 (if a class has an owning pointer member, define a destructor), R.3 (a raw pointer T* is non-owning) and GSL.Views from the C++ Core Guidelines. And like โ the definition of a gsl::owner<T*> is genuinely straight forward:
namespace gsl { template <typename T> owner = T; }
So you can literally just drop the owner in without even using an implementation of the Guideline Support Library. Iconic, low-effort, high-reward. โจ
All the checks here are purely type based and not (yet) flow sensitive โ keep that in your back pocket, it matters later, iykyk.
Okay so the following examples are gonna show you the correct vs. incorrect initializations of owners. Assignment is handled the same way, no cap. And note: both new and malloc()-like resource functions are considered to produce resources.
// Creating an owner with factory functions is checked.
gsl::owner<int*> function_that_returns_owner() { return gsl::owner<int*>(new int(42)); }
// Dynamic memory must be assigned to an owner
int* Something = new int(42); // BAD, will be caught
gsl::owner<int*> Owner = new int(42); // Good
gsl::owner<int*> Owner = new int[42]; // Good as well
// Returned owner must be assigned to an owner
int* Something = function_that_returns_owner(); // Bad, factory function
gsl::owner<int*> Owner = function_that_returns_owner(); // Good, result lands in owner
// Something not a resource or owner should not be assigned to owners
int Stack = 42;
gsl::owner<int*> Owned = &Stack; // Bad, not a resource assigned
And in the case of dynamic memory as a resource, only gsl::owner<T*> variables are allowed to be deleted ๐
// Example Bad, non-owner as resource handle, will be caught.
int* NonOwner = new int(42); // First warning here, since new must land in an owner
delete NonOwner; // Second warning here, since only owners are allowed to be deleted
// Example Good, Ownership correctly stated
gsl::owner<int*> Owner = new int(42); // Good
delete Owner; // Good as well, statically enforced, that only owners get deleted
The check will furthermore ensure that functions which expect a gsl::owner<T*> as an argument get called with either a gsl::owner<T*> or a newly created resource. So like, no sneaking randos into the owner-only party ๐
void expects_owner(gsl::owner<int*> o) { delete o; }
// Bad Code
int NonOwner = 42;
expects_owner(&NonOwner); // Bad, will get caught
// Good Code
gsl::owner<int*> Owner = new int(42);
expects_owner(Owner); // Good
expects_owner(new int(42)); // Good as well, recognized created resource
// Port legacy code for better resource-safety
gsl::owner<FILE*> File = fopen("my_file.txt", "rw+");
FILE* BadFile = fopen("another_file.txt", "w"); // Bad, warned
// ... use the file
fclose(File); // Ok, File is annotated as 'owner<>'
fclose(BadFile); // BadFile is not an 'owner<>', will be warned
Options
LegacyResourceProducersโ semicolon-separated list of fully qualified names of legacy functions that create resources but cannot introducegsl::owner<>. Defaults to::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile.LegacyResourceConsumersโ semicolon-separated list of fully qualified names of legacy functions expecting resource owners as pointer arguments but cannot introducegsl::owner<>. Defaults to::free;::realloc;::freopen;::fclose.
Limitations
Using gsl::owner<T*> in a typedef or alias is not handled correctly ๐
using heap_int = gsl::owner<int*>;
heap_int allocated = new int(42); // False positive!
The gsl::owner<T*> is declared as a templated type alias. In template functions and classes, like in the example below, the information of the type aliases gets lost. So using gsl::owner<T*> in a heavy templated code base might lead to false positives. (sad but true, bestie ๐ญ)
Known code constructs that do not get diagnosed correctly are:
std::exchangestd::vector<gsl::owner<T*>>
// This template function works as expected. Type information doesn't get lost.
template <typename T>
void delete_owner(gsl::owner<T*> owned_object) {
delete owned_object; // Everything alright
}
gsl::owner<int*> function_that_returns_owner() { return gsl::owner<int*>(new int(42)); }
// Type deduction does not work for auto variables.
// This is caught by the check and will be noted accordingly.
auto OwnedObject = function_that_returns_owner(); // Type of OwnedObject will be int*
// Problematic function template that looses the typeinformation on owner
template <typename T>
void bad_template_function(T some_object) {
// This line will trigger the warning, that a non-owner is assigned to an owner
gsl::owner<T*> new_owner = some_object;
}
// Calling the function with an owner still yields a false positive.
bad_template_function(gsl::owner<int*>(new int(42)));
// The same issue occurs with templated classes like the following.
template <typename T>
class OwnedValue {
public:
const T getValue() const { return _val; }
private:
T _val;
};
// Code, that yields a false positive.
OwnedValue<gsl::owner<int*>> Owner(new int(42)); // Type deduction yield T -> int *
// False positive, getValue returns int* and not gsl::owner<int*>
gsl::owner<int*> OwnedInt = Owner.getValue();
Another limitation of the current implementation is only the type based checking. Suppose you have code like the following:
// Two owners with assigned resources
gsl::owner<int*> Owner1 = new int(42);
gsl::owner<int*> Owner2 = new int(42);
Owner2 = Owner1; // Conceptual Leak of initial resource of Owner2!
Owner1 = nullptr;
The semantic of a gsl::owner<T*> is mostly like a std::unique_ptr<T>, therefore assignment of two gsl::owner<T*> is considered a move, which requires that the resource Owner2 must have been released before the assignment. This kind of condition could be caught in later improvements of this check with flowsensitive analysis. Currently, the Clang Static Analyzer catches this bug for dynamic memory, but not for general types of resources. ๐
cppcoreguidelines-prefer-member-initializer
This one finds member initializations in the constructor body which can be converted into member initializers of the constructor instead. And like โ it doesn't just make the code more readable, it also positively affects its performance. Glow-up and speedrun, we love to see it ๐ธ
Class-member assignments inside a control statement or following the first control statement are ignored.
This check implements C.49 (prefer initialization to assignment in constructors) from the C++ Core Guidelines.
Please note, that this check does not enforce rule C.48 (prefer in-class initializers to member initializers in constructors for constant initializers) from the C++ Core Guidelines. For that purpose see check modernize-use-default-member-init.
Example 1
class C {
int n;
int m;
public:
C() {
n = 1; // Literal in default constructor
if (dice())
return;
m = 1;
}
};
Here n can be initialized in the constructor initializer list, unlike m, as m's initialization follows a control statement (if):
class C {
int n;
int m;
public:
C(): n(1) {
if (dice())
return;
m = 1;
}
};
Example 2
class C {
int n;
int m;
public:
C(int nn, int mm) {
n = nn; // Neither default constructor nor literal
if (dice())
return;
m = mm;
}
};
Here n can be initialized in the constructor initializer list, unlike m, as m's initialization follows a control statement (if):
C(int nn, int mm) : n(nn) {
if (dice())
return;
m = mm;
}
cppcoreguidelines-pro-bounds-array-to-pointer-decay
This check flags all array to pointer decays. Every single one. It's giving "you're on camera, bestie" ๐ธ
Pointers should not be used as arrays. span<T> is a bounds-checked, safe alternative to using pointers to access arrays.
This rule is part of the Bounds safety (Bounds 3) profile from the C++ Core Guidelines.
cppcoreguidelines-pro-bounds-constant-array-index
This check flags all array subscript expressions on static arrays and std::arrays that either do not have a constant integer expression index or are out of bounds (for std::array). For out-of-bounds checking of static arrays, see the -Warray-bounds Clang diagnostic.
This rule is part of the Bounds safety (Bounds 2) profile from the C++ Core Guidelines.
Optionally, this check can generate fixes using gsl::at for indexing. Obsessed with a fix-it ๐
Options
GslHeaderโ the check can generate fixes after this option has been set to the name of the include file that containsgsl::at(), e.g."gsl/gsl.h". Default is an empty string.IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.
cppcoreguidelines-pro-bounds-pointer-arithmetic
This check flags all usage of pointer arithmetic, because it could lead to an invalid pointer. ๐ฉ Subtraction of two pointers is not flagged by this check โ that one gets a pass.
Pointers should only refer to single objects, and pointer arithmetic is fragile and easy to get wrong. span<T> is a bounds-checked, safe type for accessing arrays of data.
This rule is part of the Bounds safety (Bounds 1) profile from the C++ Core Guidelines.
cppcoreguidelines-pro-type-const-cast
Imposes limitations on the use of const_cast within C++ code. It depends on the StrictMode option setting to determine whether it should flag all instances of const_cast or only those that remove either const or volatile qualifier. Choose your fighter, bestie ๐ฅ
okay bestie, real talk time ๐
modifying a variable that's been declared as const in C++ is generally considered undefined behavior, and โ plot twist โ that stays true even when you're using const_cast. In C++, the const qualifier is literally saying "this variable is intended to be read-only, hands off," and the compiler enforces that by disallowing any attempts to change the value of that variable. no cap, it's not being dramatic, it's being correct. ๐ญ
Removing the volatile qualifier in C++ can have serious consequences. This qualifier is telling you that a variable's value can change unpredictably, and stripping it away may lead to undefined behavior, optimization problems, and debugging challenges. So it's essential to retain the volatile qualifier in situations where the variable's volatility is a crucial aspect of program correctness and reliability. fr fr, leave it alone. ๐ซถ
This rule is part of the Type safety (Type 3) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-constcast) and the ES.50: Donโt cast away const rule (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es50-dont-cast-away-const) from the C++ Core Guidelines.
Options ๐
StrictModeโ when this setting istrue, it means that any usage ofconst_castis not allowed, periodt. On the other hand, when it'sfalse, it permits casting toconstorvolatiletypes. Default value isfalse.
cppcoreguidelines-pro-type-cstyle-cast
This check flags all use of C-style casts that perform a static_cast downcast, const_cast, or reinterpret_cast. ๐ฉ
Use of these casts can violate type safety and cause the program to access a variable that is actually of type X as if it were of an unrelated type Z. Note that a C-style (T)expression cast means to perform the first of the following that is possible: a const_cast, a static_cast, a static_cast followed by a const_cast, a reinterpret_cast, or a reinterpret_cast followed by a const_cast. This rule bans (T)expression only when used to perform an unsafe cast. iykyk ๐
This rule is part of the Type safety (Type.4) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-cstylecast) from the C++ Core Guidelines.
cppcoreguidelines-pro-type-member-init
The check flags user-provided constructor definitions that do not initialize all fields that would be left in an undefined state by default construction โ think builtins, pointers and record types without user-provided default constructors containing at least one such type. If these fields aren't initialized, the constructor will leave some of the memory in an undefined state. ๐
For C++11 it suggests fixes to add in-class field initializers. For older versions it inserts the field initializers into the constructor initializer list. It will also initialize any direct base classes that need to be zeroed in the constructor initializer list. slay, it does the work for you โจ
The check takes assignment of fields in the constructor body into account but generates false positives for fields initialized in methods invoked in the constructor body. (yeah, it's doing its best ๐ญ)
The check also flags variables with automatic storage duration that have record types without a user-provided constructor and are not initialized. The suggested fix is to zero initialize the variable via {} for C++11 and beyond or = {} for older language versions.
Options ๐ฆ
IgnoreArraysโ if set totrue, the check will not warn about array members that are not zero-initialized during construction. For performance critical code, it may be important to not initialize fixed-size array members. Default isfalse.UseAssignmentโ if set totrue, the check will provide fix-its with literal initializers (int i = 0;) instead of curly braces (int i{};). Default isfalse.
This rule is part of the Type safety (Type.6) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-memberinit) from the C++ Core Guidelines.
cppcoreguidelines-pro-type-reinterpret-cast
This check flags all uses of reinterpret_cast in C++ code. ๐
Use of these casts can violate type safety and cause the program to access a variable that is actually of type X as if it were of an unrelated type Z. literally the "trust me bro" of casts ๐
This rule is part of the Type safety (Type.1.1) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-reinterpretcast) from the C++ Core Guidelines.
cppcoreguidelines-pro-type-static-cast-downcast
This check flags all usages of static_cast, where a base class is casted to a derived class. In those cases, a fix-it is provided to convert the cast to a dynamic_cast. ๐ฅ
Use of these casts can violate type safety and cause the program to access a variable that is actually of type X as if it were of an unrelated type Z.
This rule is part of the Type safety (Type.2) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-downcast) from the C++ Core Guidelines.
Options ๐ธ
StrictModeโ when set tofalse, no warnings are emitted for casts on non-polymorphic types. Default istrue.
cppcoreguidelines-pro-type-union-access
This check flags all access to members of unions. Passing unions as a whole is not flagged. ๐
Reading from a union member assumes that member was the last one written, and writing to a union member assumes another member with a nontrivial destructor had its destructor called. This is fragile because it cannot generally be enforced to be safe in the language and so relies on programmer discipline to get it right. (so basically... vibes and good intentions ๐ญ)
This rule is part of the Type safety (Type.7) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-unions) from the C++ Core Guidelines.
cppcoreguidelines-pro-type-vararg
This check flags all calls to c-style vararg functions and all use of va_arg. ๐จ
To allow for SFINAE use of vararg functions, a call is not flagged if a literal 0 is passed as the only vararg argument or function is used in unevaluated context. bestie the compiler is being generous here, take the win ๐ซถ
Passing to varargs assumes the correct type will be read. This is fragile because it cannot generally be enforced to be safe in the language and so relies on programmer discipline to get it right.
This rule is part of the Type safety (Type.8) profile (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Pro-type-varargs) from the C++ Core Guidelines.
cppcoreguidelines-rvalue-reference-param-not-moved
Warns when an rvalue reference function parameter is never moved within the function body. ๐
Rvalue reference parameters indicate a parameter that should be moved with std::move from within the function body. Any such parameter that is never moved is confusing and potentially indicative of a buggy program. not me forgetting the std::move again ๐ญ
Example:
void logic(std::string&& Input) {
std::string Copy(Input); // Oops - forgot to std::move
}
Note that parameters that are unused and marked as such will not be diagnosed.
Example:
void conditional_use([[maybe_unused]] std::string&& Input) {
// No diagnostic here since Input is unused and marked as such
}
Options ๐
AllowPartialMoveโ if set totrue, the check acceptsstd::movecalls containing any subexpression containing the parameter. CppCoreGuideline F.18 officially mandates that the parameter itself must be moved. Default isfalse.
// 'p' is flagged by this check if and only if AllowPartialMove is false
void move_members_of(pair<Obj, Obj>&& p) {
pair<Obj, Obj> other;
other.first = std::move(p.first);
other.second = std::move(p.second);
}
// 'p' is never flagged by this check
void move_whole_pair(pair<Obj, Obj>&& p) {
pair<Obj, Obj> other = std::move(p);
}
IgnoreUnnamedParamsโ if set totrue, the check ignores unnamed rvalue reference parameters. Default isfalse.IgnoreNonDeducedTemplateTypesโ if set totrue, the check ignores non-deduced template type rvalue reference parameters. Default isfalse.
template <class T>
struct SomeClass {
// Below, 'T' is not deduced and 'T&&' is an rvalue reference type.
// This will be flagged if and only if IgnoreNonDeducedTemplateTypes is
// false. One suggested fix would be to specialize the class for 'T' and
// 'T&' separately (e.g., see std::future), or allow only one of 'T' or
// 'T&' instantiations of SomeClass (e.g., see std::optional).
SomeClass(T&& t) { }
};
// Never flagged, since 'T' is a forwarding reference in a deduced context
template <class T>
void forwarding_ref(T&& t) {
T other = std::forward<T>(t);
}
MoveFunctionโ specify the function used for moving. Default is::std::move.
This check implements F.18 (http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f18-for-will-move-from-parameters-pass-by-x-and-stdmove-the-parameter) from the C++ Core Guidelines.
cppcoreguidelines-slicing
Flags slicing of member variables or vtable. Slicing happens when copying a derived object into a base object: the members of the derived object (both member variables and virtual member functions) will be discarded. This can be misleading โ especially for member function slicing โ for example:
struct B { int a; virtual int f(); };
struct D : B { int b; int f() override; };
void use(B b) { // Missing reference, intended?
b.f(); // Calls B::f.
}
D d;
use(d); // Slice.
This check implements ES.63 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es63-dont-slice) and C.145 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c145-access-polymorphic-objects-through-pointers-and-references) from the C++ Core Guidelines. ๐ฆ
cppcoreguidelines-special-member-functions
The check finds classes where some but not all of the special member functions are defined. ๐
By default the compiler defines a copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. The default can be suppressed by explicit user-definitions. The relationship between which functions will be suppressed by definitions of other functions is complicated, and it is advised that all five are defaulted or explicitly defined. (yes, all five. the whole squad. no halfsies ๐ )
Note that defining a function with = delete is considered to be a definition.
This check implements C.21 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-five) from the C++ Core Guidelines.
Options โญ
AllowSoleDefaultDtorโ when set totrue(default isfalse), this check will only trigger on destructors if they are defined and not defaulted.
struct A { // This is fine.
virtual ~A() = default;
};
struct B { // This is not fine.
~B() {}
};
struct C {
// This is not checked, because the destructor might be defaulted in
// another translation unit.
~C();
};
AllowMissingMoveFunctionsโ okay so when you set this totrue(default isfalse๐ ), the check stops flagging classes that define no move operations at all. But bestie listen โ it still flags classes that define only one of either the move constructor or the move assignment operator. So with this option on, this class right here doesn't get flagged:
struct A {
A(const A&);
A& operator=(const A&);
~A();
};
AllowMissingMoveFunctionsWhenCopyIsDeletedโ when set totrue(default isfalse, always the default smh), this check doesn't flag classes that define deleted copy operations but define no move operations. This flag is related to the Google C++ Style Guide Copyable and Movable Types section โ <https://google.github.io/styleguide/cppguide.html#Copyable_Movable_Types>. With this option enabled, this class won't be flagged:
struct A {
A(const A&) = delete;
A& operator=(const A&) = delete;
~A();
};
AllowImplicitlyDeletedCopyOrMoveโ when set totrue(defaultfalse๐), the check doesn't flag classes that implicitly delete copy or move operations. With this enabled, the following class won't get flagged:
struct A : boost::noncopyable {
~A() { std::cout << "dtor\n"; }
};
IgnoreMacrosโ if set totrue, the check won't give warnings for classes defined inside macros. Default istrue. (Iconic behaviour honestly, leave those macros alone โจ)
cppcoreguidelines-use-default-member-init
This check implements C.48 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-in-class-initializer>.
So the cppcoreguidelines-use-default-member-init check is literally just an alias, bestie ๐ซถ โ please see modernize-use-default-member-init for more information. Iykyk.
cppcoreguidelines-use-enum-class
This one finds unscoped (non-class) enum declarations and is like "girlโฆ use enum class instead" ๐
โจ
It implements Enum.3 from the C++ Core Guidelines โ <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-class>.
Example, because examples ate:
enum E {}; // use "enum class E {};" instead
enum class E {}; // OK
struct S {
enum E {}; // use "enum class E {};" instead
// OK with option IgnoreUnscopedEnumsInClasses
};
namespace N {
enum E {}; // use "enum class E {};" instead
}
Options
IgnoreUnscopedEnumsInClassesโ whentrue, it ignores unscoped enum declarations in classes. Default isfalse. So your in-class enums get a free pass ๐ธ
cppcoreguidelines-virtual-class-destructor
Finds virtual classes whose destructor is neither public and virtual nor protected and non-virtual ๐ญ A virtual class's destructor has to be specified in one of those two ways to prevent undefined behavior, and undefined behavior is not the vibe.
This check implements C.35 from the C++ Core Guidelines โ <http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-dtor-virtual>.
Ngl, heads up: this check will diagnose a class with a virtual method regardless of whether the class is used as a base class or not. No exceptions, no cap.
Fixes are available for user-declared and implicit destructors that are either public and non-virtual, or protected and virtual. But no fixes are offered for private destructors ๐ โ there, the decision whether to make them private and virtual or protected and non-virtual depends on the use case, so it's left to you, the main character.
Example
For example, the following classes/structs get flagged by the check since they violate guideline C.35:
struct Foo { // NOK, protected destructor should not be virtual
virtual void f();
protected:
virtual ~Foo(){}
};
class Bar { // NOK, public destructor should be virtual
virtual void f();
public:
~Bar(){}
};
This would be rewritten to look like this:
struct Foo { // OK, destructor is not virtual anymore
virtual void f();
protected:
~Foo(){}
};
class Bar { // OK, destructor is now virtual
virtual void f();
public:
virtual ~Bar(){}
};
darwin-avoid-spinlock
Finds usages of OSSpinlock, which is deprecated because of potential livelock problems ๐ So likeโฆ don't.
This check will detect the following function invocations:
OSSpinlockLockOSSpinlockTryOSSpinlockUnlock
The corresponding information about the problem of OSSpinlock: <https://blog.postmates.com/why-spinlocks-are-bad-on-ios-b69fc5221058>
darwin-dispatch-once-nonstatic
Finds declarations of dispatch_once_t variables without static or global storage ๐ฅ The behavior of using dispatch_once_t predicates with automatic or dynamic storage is undefined by libdispatch, and should be avoided. Periodt.
So it's a common pattern to have functions initialize internal static or global data once when the function runs โ but programmers have been known to miss the static on the dispatch_once_t predicate, leading to an uninitialized flag value at the mercy of the stack ๐ญ The stack. The stack, girl.
Programmers have also been known to make dispatch_once_t variables be members of structs or classes, with the intent to lazily perform some expensive struct or class member initialization only once โ however, this violates the libdispatch requirements ๐
See the discussion section of Apple's dispatch_once documentation โ <https://developer.apple.com/documentation/dispatch/1447169-dispatch_once> โ for more information.
fuchsia-default-arguments-calls
Warns if a function or method is called with default arguments. Fuchsia said no ๐
For example, given the declaration:
int foo(int value = 5) { return value; }
A function call expression that uses a default argument will be diagnosed. Calling it without defaults will not cause a warning:
foo(); // warning
foo(0); // no warning
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
fuchsia-default-arguments-declarations
Warns if a function or method is declared with default parameters โจ (Yes, that's a different check from the one above. They're sisters.)
For example, the declaration:
int foo(int value = 5) { return value; }
will cause a warning ๐ญ
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
fuchsia-header-anon-namespaces
The fuchsia-header-anon-namespaces check is an alias, bestie โ please see google-build-namespace for more information ๐ซถ
fuchsia-multiple-inheritance
Warns if a class inherits from multiple classes that are not pure virtual ๐ฆ
For example, declaring a class that inherits from multiple concrete classes is disallowed:
class Base_A {
public:
virtual int foo() { return 0; }
};
class Base_B {
public:
virtual int bar() { return 0; }
};
// Warning
class Bad_Child1 : public Base_A, Base_B {};
A class that inherits from a pure virtual is allowed though โ that's the good timeline:
class Interface_A {
public:
virtual int foo() = 0;
};
class Interface_B {
public:
virtual int bar() = 0;
};
// No warning
class Good_Child1 : public Interface_A, Interface_B {
virtual int foo() override { return 0; }
virtual int bar() override { return 0; }
};
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
fuchsia-overloaded-operator
Warns if an operator is overloaded โ except for the assignment (copy and move) operators, those two get to stay ๐
For example:
int operator+(int); // Warning
B &operator=(const B &Other); // No warning
B &operator=(B &&Other) // No warning
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
fuchsia-statically-constructed-objects
Warns if global, non-trivial objects with static storage are constructed โ unless the object is statically initialized with a constexpr constructor or has no explicit constructor โจ (Those two are the loopholes, bestie.)
For example:
class A {};
class B {
public:
B(int Val) : Val(Val) {}
private:
int Val;
};
class C {
public:
constexpr C(int Val) : Val(Val) {}
C(int Val1, int Val2) : Val(Val1+Val2) {}
private:
int Val;
};
static A a; // No warning, as there is no explicit constructor
static C c(0); // No warning, as constructor is constexpr
static B b(0); // Warning, as constructor is not constexpr
static C c2(0, 1); // Warning, as constructor is not constexpr
static int i; // No warning, as it is trivial
extern int get_i();
static C c3(get_i());// Warning, as the constructor is dynamically initialized
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
fuchsia-trailing-return
Functions that have trailing returns are disallowed ๐คญ โ except for those using decltype specifiers and lambdas with otherwise unutterable return types. Those get a pass.
For example:
// No warning
int add_one(const int arg) { return arg; }
// Warning
auto get_add_one() -> int (*)(const int) {
return add_one;
}
Exceptions are made for lambdas and decltype specifiers:
// No warning
auto lambda = [](double x, double y) -> double {return x + y;};
// No warning
template <typename T1, typename T2>
auto fn(const T1 &lhs, const T2 &rhs) -> decltype(lhs + rhs) {
return lhs + rhs;
}
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
fuchsia-virtual-inheritance
Warns if classes are defined with virtual inheritance ๐
For example, classes should not be defined with virtual inheritance:
class B : public virtual A {}; // warning
See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>
google-build-explicit-make-pair
Check that make_pair's template arguments are deduced ๐
G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are specified explicitly โ and such use isn't intended in any case, so likeโฆ why ๐ญ
Corresponding cpplint.py check name: build/explicit_make_pair.
google-build-namespaces ๐ง
cert-dcl59-cppredirects here as an alias for this check.fuchsia-header-anon-namespacesredirects here as an alias for this check.
This one finds anonymous namespaces in headers โ yes, the ones that lowkey make the style guide side-eye you. ๐
Style guide moment: https://google.github.io/styleguide/cppguide.html#Namespaces
Corresponding cpplint.py check name: build/namespaces.
google-build-using-namespace ๐ซถ
Finds using namespace directives. Itโs giving โplease donโt dump the whole namespace into scopeโ energy.
This check implements the following rule of the Google C++ Style Guide: https://google.github.io/styleguide/cppguide.html#Namespaces
You may not use a using-directive to make all names from a namespace available.
// Forbidden -- This pollutes the namespace.
using namespace foo;
Corresponding cpplint.py check name: build/namespaces.
google-default-arguments ๐ธ
Checks that default arguments are not given for virtual methods. No sneaky defaults on virtuals, bestie.
See https://google.github.io/styleguide/cppguide.html#Default_Arguments
google-explicit-constructor โจ
Checks that constructors callable with a single argument and conversion operators are marked explicit to avoid the risk of unintentional implicit conversions. Basically: no surprise conversions, periodt.
Consider this example:
struct S {
int x;
operator bool() const { return true; }
};
bool f() {
S a{1};
S b{2};
return a == b;
}
The function will return true, since the objects are implicitly converted to bool before comparison, which is unlikely to be the intent. ๐ญ
The check will suggest inserting explicit before the constructor or conversion operator declaration. However, copy and move constructors should not be explicit, as well as constructors taking a single initializer_list argument.
This code:
struct S {
S(int a);
explicit S(const S&);
operator bool() const;
...
will become
struct S {
explicit S(int a);
S(const S&);
explicit operator bool() const;
...
See https://google.github.io/styleguide/cppguide.html#Explicit_Constructors
google-global-names-in-headers ๐
Flags global namespace pollution in header files. Right now it only triggers on using declarations and directives.
The relevant style guide section is https://google.github.io/styleguide/cppguide.html#Namespaces.
google-objc-avoid-nsobject-new ๐
Finds calls to +new or overrides of it, which are prohibited by the Google Objective-C style guide. The guide forbids calling +new or overriding it in class implementations, preferring +alloc and -init methods to instantiate objects.
An example:
NSDate *now = [NSDate new];
Foo *bar = [Foo new];
Instead, code should use +alloc/-init or class factory methods.
NSDate *now = [NSDate date];
Foo *bar = [[Foo alloc] init];
This check corresponds to the Google Objective-C Style Guide rule Do Not Use +new: https://google.github.io/styleguide/objcguide.html#do-not-use-new.
google-objc-avoid-throwing-exception ๐
Finds uses of throwing exceptions usages in Objective-C files. For the same reason as the Google C++ style guide, we prefer not throwing exceptions from Objective-C code.
The corresponding C++ style guide rule: https://google.github.io/styleguide/cppguide.html#Exceptions
Instead, prefer passing in NSError ** and return BOOL to indicate success or failure.
A counterexample:
- (void)readFile {
if ([self isError]) {
@throw [NSException exceptionWithName:...];
}
}
Instead, returning an error via NSError ** is preferred:
- (BOOL)readFileWithError:(NSError **)error {
if ([self isError]) {
*error = [NSError errorWithDomain:...];
return NO;
}
return YES;
}
The corresponding style guide rule: https://google.github.io/styleguide/objcguide.html#avoid-throwing-exceptions
google-objc-function-naming ๐ฆ
Finds function declarations in Objective-C files that do not follow the pattern described in the Google Objective-C Style Guide.
The corresponding style guide rule can be found here: https://google.github.io/styleguide/objcguide.html#function-names
All function names should be in Pascal case. Functions whose storage class is not static should have an appropriate prefix.
The following code sample does not follow this pattern:
static bool is_positive(int i) { return i > 0; }
bool IsNegative(int i) { return i < 0; }
The sample above might be corrected to the following code:
static bool IsPositive(int i) { return i > 0; }
bool *ABCIsNegative(int i) { return i < 0; }
google-objc-global-variable-declaration ๐
Finds global variable declarations in Objective-C files that do not follow the pattern of variable names in Googleโs Objective-C Style Guide.
The corresponding style guide rule: https://google.github.io/styleguide/objcguide.html#variable-names
All the global variables should follow the pattern of g[A-Z].* (variables) or k[A-Z].* (constants). The check will suggest a variable name that follows the pattern if it can be inferred from the original name.
For code:
static NSString* myString = @"hello";
The fix will be:
static NSString* gMyString = @"hello";
Another example of constant:
static NSString* const myConstString = @"hello";
The fix will be:
static NSString* const kMyConstString = @"hello";
However for code that prefixed with non-alphabetical characters like:
static NSString* __anotherString = @"world";
The check will give a warning message but will not be able to suggest a fix. The user needs to fix it on their own. ๐
google-readability-avoid-underscore-in-googletest-name ๐
Checks whether there are underscores in googletest test suite names and test names in test macros:
TESTTEST_FTEST_PTYPED_TESTTYPED_TEST_P
The FRIEND_TEST macro is not included.
For example:
TEST(TestSuiteName, Illegal_TestName) {}
TEST(Illegal_TestSuiteName, TestName) {}
would trigger the check. Underscores are not allowed in test suite name nor test names โ hereโs the link: https://google.github.io/googletest/faq.html#why-should-test-suite-names-and-test-names-not-contain-underscore
The DISABLED_ prefix, which may be used to disable test suites and individual tests (https://google.github.io/googletest/advanced.html#temporarily-disabling-tests), is removed from the test suite name and test name before checking for underscores.
This check does not propose any fixes. ๐
google-readability-braces-around-statements ๐
The google-readability-braces-around-statements check is an alias, please see readability-braces-around-statements <> for more information.
google-readability-casting โญ
Finds usages of C-style casts.
https://google.github.io/styleguide/cppguide.html#Casting
Corresponding cpplint.py check name: readability/casting.
This check is similar to -Wold-style-cast, but it suggests automated fixes in some cases. The reported locations should not be different from the ones generated by -Wold-style-cast.
google-readability-function-size ๐
The google-readability-function-size check is an alias, please see readability-function-size <> for more information.
google-readability-namespace-comments ๐ง
The google-readability-namespace-comments check is an alias, please see llvm-namespace-comment <> for more information.
google-readability-todo ๐
Finds TODO comments without a username or bug number. The relevant style guide section is https://google.github.io/styleguide/cppguide.html#TODO_Comments.
Corresponding cpplint.py check: readability/todo
google-runtime-int ๐ฅ
Finds uses of short, long and long long and suggest replacing them with u?intXX(_t)?.
The corresponding style guide rule: https://google.github.io/styleguide/cppguide.html#Integer_Types
Corresponding cpplint.py check: runtime/int.
Options โจ
UnsignedTypePrefixโ a string specifying the unsigned type prefix. Default isuint.SignedTypePrefixโ a string specifying the signed type prefix. Default isint.TypeSuffixโ a string specifying the type suffix. Default is an empty string.
google-runtime-operator ๐ธ
Finds overloads of unary operator &.
https://google.github.io/styleguide/cppguide.html#Operator_Overloading
Corresponding cpplint.py check name: runtime/operator.
google-upgrade-googletest-case ๐ฆ
Finds uses of deprecated Google Test version 1.9 APIs with names containing case and replaces them with equivalent APIs with suite.
All names containing case are being replaced to be consistent with the meanings of โtest caseโ and โtest suiteโ as used by the International Software Testing Qualifications Board and ISO 29119.
The new names are a part of Google Test version 1.9 (release pending). It is recommended that users update their dependency to version 1.9 and then use this check to remove deprecated names.
The affected APIs are:
- Member functions of
testing::Test,testing::TestInfo,testing::TestEventListener,testing::UnitTest, and any type inheriting from these types - The macros
TYPED_TEST_CASE,TYPED_TEST_CASE_P,REGISTER_TYPED_TEST_CASE_P, andINSTANTIATE_TYPED_TEST_CASE_P - The type alias
testing::TestCase
Examples of fixes created by this check:
class FooTest : public testing::Test {
public:
static void SetUpTestCase();
static void TearDownTestCase();
};
TYPED_TEST_CASE(BarTest, BarTypes);
becomes
class FooTest : public testing::Test {
public:
static void SetUpTestSuite();
static void TearDownTestSuite();
};
TYPED_TEST_SUITE(BarTest, BarTypes);
For better consistency of user code, the check renames both virtual and non-virtual member functions with matching names in derived types. The check tries to provide only a warning when a fix cannot be made safely, as is the case with some template and macro uses. ๐ญ
hicpp-avoid-c-arrays ๐
The hicpp-avoid-c-arrays check is an alias, please see modernize-avoid-c-arrays <> for more information. It partly enforces the rule 4.1.1: https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-conversions.
hicpp-avoid-goto ๐
The hicpp-avoid-goto check is an alias, please see cppcoreguidelines-avoid-goto <> for more information. It enforces the rule 6.3.1: https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements.
hicpp-braces-around-statements โจ
Okay so this one is an alias, bestie โ it's literally just readability-braces-around-statements wearing a different hat ๐ฉ so go peep that one for the full tea. What it does do is enforce rule 6.1.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements.
hicpp-deprecated-headers ๐
Another alias, no cap โ see modernize-deprecated-headers for the deets. It enforces rule 1.3.3 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/general.
hicpp-exception-baseclass ๐
This one makes sure that every single value in a throw expression is an instance of std::exception. It's giving "your exceptions need to be in the family group chat" energy fr fr ๐ค
This enforces rule 15.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-exception-handling โ of the High Integrity C++ Coding Standard.
class custom_exception {};
void throwing() noexcept(false) {
// Problematic throw expressions.
throw int(42);
throw custom_exception();
}
class mathematical_error : public std::exception {};
void throwing2() noexcept(false) {
// These kind of throws are ok.
throw mathematical_error();
throw std::runtime_error();
throw std::exception();
}
hicpp-explicit-conversions ๐
This check is an alias for google-explicit-constructor โจ. Used to enforce parts of rule 5.4.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions. It's gonna make sure your constructors and your conversion operators are marked explicit โ no sneaky implicit glow-ups allowed ๐
โโ๏ธ
Other forms of casting checks live in other places, and these icons can check for more forms of casting:
cppcoreguidelines-pro-type-static-cast-downcastcppcoreguidelines-pro-type-reinterpret-castcppcoreguidelines-pro-type-const-castcppcoreguidelines-pro-type-cstyle-cast
hicpp-function-size ๐ธ
Alias alert ๐จ โ this one's readability-function-size. Super useful for enforcing multiple sections on function complexity:
- rule 8.2.2 โ
https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions - rule 8.3.1 โ
https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions - rule 8.3.2 โ
https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions
hicpp-ignored-remove-result ๐ฆ
Bestie, you know how everyone ignores std::remove's return value and then wonders why their vector still has ghosts in it ๐ญ Yeah. This check makes sure the result of std::remove, std::remove_if and std::unique is not ignored, according to rule 17.5.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-library.
The mutating algorithms std::remove, std::remove_if and both overloads of std::unique operate by swapping or moving elements of the range they are operating over. On completion, they return an iterator to the last valid element. In the majority of cases the correct behavior is to use this result as the first operand in a call to std::erase. Like, that's literally the whole point bestie โ
This check is a subset of bugprone-unused-return-value and depending on used options it can be superfluous to enable both checks. So don't double up unless you want double the yelling ๐ข
Options
AllowCastToVoidโ Controls whether casting return values tovoidis permitted. Default:true.
hicpp-invalid-access-moved ๐ง
This check is an alias for bugprone-use-after-move โ and like, thank god, because reading a moved-from object is giving "texting your ex" energy ๐
Implements parts of rule 8.4.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions โ to check if moved-from objects are accessed.
hicpp-member-init ๐
Alias for cppcoreguidelines-pro-type-member-init ๐ซถ Implements the check for rule 12.4.2 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions โ to initialize class members in the right order. Order matters, bestie, always has ๐โจ
hicpp-move-const-arg ๐
Another alias, go see performance-move-const-arg for more. It enforces rule 17.3.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-library.
hicpp-multiway-paths-covered ๐ฅ
Okay this one is actually main character energy ๐ โ it discovers situations where code paths are not fully covered. And it further suggests using if instead of switch when the code's gonna be clearer that way. Rule 6.1.2 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements โ and rule 6.1.4 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements โ of the High Integrity C++ Coding Standard are enforced.
if-else if chains that miss a final else branch might lead to unexpected program execution and be the result of a logical error. If the missing else branch is intended you can leave it empty with a clarifying comment. This warning can be noisy on some code bases, so it is disabled by default ๐
void f1() {
int i = determineTheNumber();
if(i > 0) {
// Some Calculation
} else if (i < 0) {
// Precondition violated or something else.
}
// ...
}
Similar arguments hold for switch statements which do not cover all possible code paths:
// The missing default branch might be a logical error. It can be kept empty
// if there is nothing to do, making it explicit.
void f2(int i) {
switch (i) {
case 0: // something
break;
case 1: // something else
break;
}
// All other numbers?
}
// Violates this rule as well, but already emits a compiler warning (-Wswitch).
enum Color { Red, Green, Blue, Yellow };
void f3(enum Color c) {
switch (c) {
case Red: // We can't drive for now.
break;
case Green: // We are allowed to drive.
break;
}
// Other cases missing
}
The rule 6.1.4 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements โ requires every switch statement to have at least two case labels other than a default label. Otherwise the switch could be better expressed with an if statement. Degenerated switch statements without any labels are caught as well ๐ฏ
// Degenerated switch that could be better written as `if`
int i = 42;
switch(i) {
case 1: // do something here
default: // do something else here
}
// Should rather be the following:
if (i == 1) {
// do something here
}
else {
// do something here
}
// A completely degenerated switch will be diagnosed.
int i = 42;
switch(i) {}
Options
WarnOnMissingElseโ Boolean flag that activates a warning for missingelsebranches. Default isfalse.
hicpp-named-parameter ๐
This check is an alias for readability-named-parameter.
Implements rule 8.2.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions.
hicpp-new-delete-operators โญ
Alias for misc-new-delete-overloads ๐ซถ Implements rule 12.3.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions โ to ensure the new and delete operators have the correct signature. Signatures are like outfits bestie, they gotta fit right ๐
hicpp-no-array-decay ๐
Alias, bestie, go see cppcoreguidelines-pro-bounds-array-to-pointer-decay for more. It enforces the rule 4.1.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-conversions.
hicpp-no-assembler ๐ญ
Checks for assembler statements. Use of inline assembly should be avoided since it restricts the portability of the code. Like, your code's not gonna slay on other platforms if you hardcode the assembly, no cap ๐ซ
This enforces rule 7.5.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/declarations โ of the High Integrity C++ Coding Standard.
hicpp-no-malloc ๐ง
The hicpp-no-malloc check is an alias, please see cppcoreguidelines-no-malloc for more information. It enforces the rule 5.3.2 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions.
hicpp-noexcept-move ๐
This check is an alias for performance-noexcept-move-constructor. It checks rule 12.5.4 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions โ to mark move assignment and move construction noexcept. Mark it. MARK IT. ๐
hicpp-signed-bitwise ๐
Finds uses of bitwise operations on signed integer types, which may lead to undefined or implementation defined behavior. Yeah. Undefined behavior is not the vibe โ it's giving "chaos gremlin" ๐
The according rule is defined in the High Integrity C++ Standard, Section 5.6.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions.
Options
IgnorePositiveIntegerLiteralsโ If this option is set totrue, the check will not warn on bitwise operations with positive integer literals, e.g.~0,2 << 1, etc. Default value isfalse.
hicpp-special-member-functions ๐
This check is an alias for cppcoreguidelines-special-member-functions. It checks that special member functions have the correct signature, according to rule 12.5.7 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions.
hicpp-static-assert โจ
The hicpp-static-assert check is an alias, please see misc-static-assert for more information. It enforces the rule 7.1.10 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/declarations.
hicpp-undelegated-constructor ๐ฆ
This check is an alias for bugprone-undelegated-constructor. It partially implements rule 12.4.5 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions โ to find misplaced constructor calls inside a constructor. Like, did you mean to delegate or did you just create a random temporary and not notice ๐ญ
struct Ctor {
Ctor();
Ctor(int);
Ctor(int, int);
Ctor(Ctor *i) {
// All Ctor() calls result in a temporary object
Ctor(); // did you intend to call a delegated constructor?
Ctor(0); // did you intend to call a delegated constructor?
Ctor(1, 2); // did you intend to call a delegated constructor?
foo();
}
};
hicpp-uppercase-literal-suffix ๐ธ
The hicpp-uppercase-literal-suffix check is an alias, please see readability-uppercase-literal-suffix for more information.
Partially implements rule 4.2.1 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-conversions โ to ensure that the U suffix is writeln properly โ๏ธ
hicpp-use-auto ๐
The hicpp-use-auto check is an alias, please see modernize-use-auto for more information. It enforces the rule 7.1.8 โ https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/declarations.
๐ซถ hicpp-use-emplace
So hicpp-use-emplace is an alias, bestie โ it's literally just vibing as a redirect, please go see modernize-use-emplace for the whole story. It enforces rule 17.4.2 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-library). โจ
๐ธ hicpp-use-equals-default
This one's an alias for modernize-use-equals-default. It implements rule 12.5.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to explicitly default special member functions. Lowkey iconic behavior. ๐
๐ฆ hicpp-use-equals-delete
Another alias โ this one's for modernize-use-equals-delete. Implements rule 12.5.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to explicitly default or delete special member functions. Yes both. Periodt. ๐
๐ hicpp-use-noexcept
The hicpp-use-noexcept check is an alias, so please go look at modernize-use-noexcept for more info. It enforces rule 1.3.5 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/general). ๐ง
โญ hicpp-use-nullptr
Alias alert ๐จ โ hicpp-use-nullptr is an alias, please see modernize-use-nullptr for more information. It enforces rule 2.5.3 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/lexical-conventions).
๐ hicpp-use-override
This check is an alias for modernize-use-override. It implements rule 10.2.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/derived-classes) to declare a virtual function override when overriding. iykyk ๐
๐ฅ hicpp-vararg
The hicpp-vararg check is an alias, please see cppcoreguidelines-pro-type-vararg for more information. It enforces the rule 14.1.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/templates).
๐ linuxkernel-must-check-errs
Okay so this one actually checks your Linux kernel code to see if you're using the results from the functions in linux/err.h. It also checks whether your code uses the results from functions that directly return a value from one of these error functions. Two-for-one vibe check, honestly. ๐ซถ
Why does this matter so much in the Linux kernel? Because ERR_PTR, PTR_ERR, IS_ERR, IS_ERR_OR_NULL, ERR_CAST, and PTR_ERR_OR_ZERO return values must be checked โ positive pointers and negative error codes are being used in the same context, so if you don't check, it's giving disaster ๐ญ. These functions are marked with __attribute__((warn_unused_result)), but some kernel versions do not have this warning enabled for clang. So the check picks up the slack. Slay.
Examples:
/* Trivial unused call to an ERR function */
PTR_ERR_OR_ZERO(some_function_call());
/* A function that returns ERR_PTR. */
void *fn() { ERR_PTR(-EINVAL); }
/* An invalid use of fn. */
fn();
๐ llvm-else-after-return
The llvm-else-after-return check is an alias, please see readability-else-after-return for more information. Short and sweet. ๐
๐ธ llvm-header-guard
Finds and fixes header guards that do not adhere to LLVM style. That's it, that's the check. Protecting your headers like a bodyguard ๐ .
โจ llvm-include-order
Checks the correct order of #includes. No cap, include order matters.
See https://llvm.org/docs/CodingStandards.html#include-style
๐ฆ llvm-namespace-comment
Heads up: google-readability-namespace-comments redirects here as an alias for this check.
This one checks that long namespaces have a closing comment. Like, a little label so you know which namespace you're closing. Main character energy for your braces ๐.
https://llvm.org/docs/CodingStandards.html#namespace-indentation
https://google.github.io/styleguide/cppguide.html#Namespaces
namespace n1 {
void f();
}
// becomes
namespace n1 {
void f();
} // namespace n1
โญ Options
ShortNamespaceLinesโ requires the closing brace of the namespace definition to be followed by a closing comment if the body of the namespace has more thanShortNamespaceLineslines of code. The value is an unsigned integer that defaults to1U.SpacesBeforeCommentsโ an unsigned integer specifying the number of spaces before the comment closing a namespace definition. Default is1U.AllowOmittingNamespaceCommentsโ whentrue, the check will accept if no namespace comment is present. The check will only fail if the specified namespace comment is different than expected. Default isfalse.
๐ง llvm-prefer-isa-or-dyn-cast-in-conditionals
This one looks at conditionals and finds + replaces cases of cast<> โ which will assert rather than return a null pointer, btw, so likeโฆ careful ๐ โ and dyn_cast<> where the return value is not captured. On top of that, it finds and replaces cases that match the pattern var && isa<X>(var), where var is evaluated twice. Not the vibe.
// Finds these:
if (auto x = cast<X>(y)) {}
// is replaced by:
if (auto x = dyn_cast<X>(y)) {}
if (cast<X>(y)) {}
// is replaced by:
if (isa<X>(y)) {}
if (dyn_cast<X>(y)) {}
// is replaced by:
if (isa<X>(y)) {}
if (var && isa<T>(var)) {}
// is replaced by:
if (isa_and_nonnull<T>(var.foo())) {}
// Other cases are ignored, e.g.:
if (auto f = cast<Z>(y)->foo()) {}
if (cast<Z>(y)->foo()) {}
if (X.cast(y)) {}
๐ llvm-prefer-register-over-unsigned
Finds historical use of unsigned to hold vregs and physregs and rewrites them to use Register. Glow-up for your registers โจ.
Currently this works by finding all variables of unsigned integer type whose initializer begins with an implicit cast from Register to unsigned.
void example(MachineOperand &MO) {
unsigned Reg = MO.getReg();
...
}
becomes:
void example(MachineOperand &MO) {
Register Reg = MO.getReg();
...
}
๐ llvm-prefer-static-over-anonymous-namespace
Finds function and variable declarations inside an anonymous namespace and suggests replacing them with static declarations.
So the LLVM Coding Standards (https://llvm.org/docs/CodingStandards.html#restrict-visibility) recommend keeping anonymous namespaces as small as possible and only use them for class declarations. For functions and variables, the static specifier should be preferred for restricting visibility. It's giving minimalism ๐.
For example, non-compliant code:
namespace {
class StringSort {
public:
StringSort(...)
bool operator<(const char *RHS) const;
};
// warning: place method definition outside of an anonymous namespace
bool StringSort::operator<(const char *RHS) const {}
// warning: prefer using 'static' for restricting visibility
void runHelper() {}
// warning: prefer using 'static' for restricting visibility
int myVariable = 42;
}
Should become:
// Small anonymous namespace for class declaration
namespace {
class StringSort {
public:
StringSort(...)
bool operator<(const char *RHS) const;
};
}
// placed method definition outside of the anonymous namespace
bool StringSort::operator<(const char *RHS) const {}
// used 'static' instead of an anonymous namespace
static void runHelper() {}
// used 'static' instead of an anonymous namespace
static int myVariable = 42;
๐ Options
AllowVariableDeclarationsโ whentrue, allow variable declarations to be in anonymous namespace. Default value istrue.AllowMemberFunctionsInClassโ whentrue, only methods defined in anonymous namespace outside of the corresponding class will be warned. Default value istrue.
๐ซถ llvm-qualified-auto
The llvm-qualified-auto check is an alias, please see readability-qualified-auto for more information. Redirect realness. ๐ฆ
๐ llvm-twine-local
Looks for local Twine variables which are prone to use after frees and should be generally avoided. Like, genuinely avoid these, not in a cute way ๐ญ.
static Twine Moo = Twine("bark") + "bah";
// becomes
static std::string Moo = (Twine("bark") + "bah").str();
๐ฅ llvmlibc-callee-namespace
Checks all calls resolve to functions within the correct namespace. Namespace discipline is the moment.
// Implementation inside the LIBC_NAMESPACE namespace.
// Correct if:
// - LIBC_NAMESPACE is a macro
// - LIBC_NAMESPACE expansion starts with `__llvm_libc`
namespace LIBC_NAMESPACE {
// Allow calls with the fully qualified name.
LIBC_NAMESPACE::strlen("hello");
// Allow calls to compiler provided functions.
(void)__builtin_abs(-1);
// Bare calls are allowed as long as they resolve to the correct namespace.
strlen("world");
// Disallow calling into functions in the global namespace.
::strlen("!");
} // namespace LIBC_NAMESPACE
โญ llvmlibc-implementation-in-namespace
Checks that all declarations in the llvm-libc implementation are within the correct namespace. Stay in your lane (affectionate) ๐.
// Implementation inside the LIBC_NAMESPACE_DECL namespace.
// Correct if:
// - LIBC_NAMESPACE_DECL is a macro
// - LIBC_NAMESPACE_DECL expansion starts with `[[gnu::visibility("hidden")]] __llvm_libc`
namespace LIBC_NAMESPACE_DECL {
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
// Namespaces within LIBC_NAMESPACE_DECL namespace are allowed.
namespace inner {
int localVar = 0;
}
// Functions with C linkage are allowed.
extern "C" void str_fuzz() {}
}
// Incorrect: implementation not in the LIBC_NAMESPACE_DECL namespace.
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
// Incorrect: outer most namespace is not the LIBC_NAMESPACE_DECL macro.
namespace something_else {
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
}
// Incorrect: outer most namespace expansion does not start with `[[gnu::visibility("hidden")]] __llvm_libc`.
#define LIBC_NAMESPACE_DECL custom_namespace
namespace LIBC_NAMESPACE_DECL {
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
}
๐ง llvmlibc-inline-function-decl
Checks that all implicitly and explicitly inline functions in header files are tagged with the LIBC_INLINE macro โ except for functions implicit to classes or deleted functions. Gotta tag 'em all ๐. See the libc style guide (https://libc.llvm.org/dev/code_style.html) for more information about this macro.
๐ธ llvmlibc-restrict-system-libc-headers
Finds includes of system libc headers not provided by the compiler within llvm-libc implementations. Basically: don't sneak system headers in ๐.
#include <stdio.h> // Not allowed because it is part of system libc.
#include <stddef.h> // Allowed because it is provided by the compiler.
#include "internal/stdio.h" // Allowed because it is NOT part of system libc.
This check is necessary because accidentally including system libc headers can lead to subtle and hard to detect bugs. For example consider a system libc whose dirent struct has slightly different field ordering than llvm-libc. While this will compile successfully, this can cause issues during runtime because they are ABI incompatible. ๐
โจ Options โจ
Includesโ a string with a comma-separated glob list of allowed include filenames. Itโs just like the-checksglob list when you run clang-tidy itself, and the two wildcard characters are*and-(include and exclude globs, respectively). Default is-*, which means no includes are allowed, periodt.
You can use this to allow known safe includes like Linux development headers. Peep portability-restrict-system-includes <> for more deets.
๐ฆ misc-confusable-identifiers ๐ฆ
Warns you about confusable identifiers โ like, identifiers that look visually close but use different Unicode characters. This catches a potential attack described in CVE-2021-42574 <https://www.cve.org/CVERecord?id=CVE-2021-42574>.
Example:
int fo; // Initial character is U+0066 (LATIN SMALL LETTER F).
int ๐o; // Initial character is U+1D41F (MATHEMATICAL BOLD SMALL F) not U+0066 (LATIN SMALL LETTER F).
๐ misc-const-correctness ๐
This check detects local variables that could be declared as const but are not. Declaring variables as const is required or recommended by many coding guidelines, like ES.25 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es25-declare-an-object-const-or-constexpr-unless-you-want-to-modify-its-value-later-on> from the C++ Core Guidelines.
Please note: this checkโs analysis is type-based only. Variables that are not modified but used to create a non-const handle that might escape the scope are not diagnosed as potential const.
// Declare a variable, which is not ``const`` ...
int i = 42;
// but use it as read-only. This means that `i` can be declared ``const``.
int result = i * i; // Before transformation
int const result = i * i; // After transformation
The check can analyze values, pointers and references and pointees:
// Normal values like built-ins or objects.
int potential_const_int = 42; // Before transformation
int const potential_const_int = 42; // After transformation
int copy_of_value = potential_const_int;
MyClass could_be_const; // Before transformation
MyClass const could_be_const; // After transformation
could_be_const.const_qualified_method();
// References can be declared const as well.
int &reference_value = potential_const_int; // Before transformation
int const& reference_value = potential_const_int; // After transformation
int another_copy = reference_value;
// The similar semantics of pointers are analyzed.
int *pointer_variable = &potential_const_int; // Before transformation
int const*const pointer_variable = &potential_const_int; // After transformation, both pointer itself and pointee are supported.
int last_copy = *pointer_variable;
The automatic code transformation is only applied to variables that are declared in single declarations. You may want to prepare your code base with readability-isolate-declaration <> first.
Note that there is the check cppcoreguidelines-avoid-non-const-global-variables <> to enforce const correctness on all globals.
๐ง Known Limitations ๐ง
- The check does not run on C code.
- The check will not analyze templated variables or variables that are instantiation dependent. Different instantiations can result in different const correctness properties and in general it is not possible to find all instantiations of a template. The template might be used differently in an independent translation unit.
โจ Options โจ
AnalyzeValuesโ turns on/off analysis of ordinary value variables, likeint i = 42;. Default istrue.
// Warning
int i = 42;
// No warning
int const i = 42;
// Warning
int a[] = {42, 42, 42};
// No warning
int const a[] = {42, 42, 42};
AnalyzeReferencesโ turns on/off analysis of reference variables, likeint &ref = i;. Default istrue.
int i = 42;
// Warning
int& ref = i;
// No warning
int const& ref = i;
AnalyzePointersโ turns on/off analysis of pointer variables, likeint *ptr = &i;. For specific checks, seeWarnPointersAsValuesandWarnPointersAsPointers. Default istrue.WarnPointersAsValuesโ enables the suggestion forconstof the pointer itself. Pointer values have two possibilities to be const: the pointer and the value pointing to. Default isfalse.
int value = 42;
// Warning
const int * pointer_variable = &value;
// No warning
const int *const pointer_variable = &value;
WarnPointersAsPointersโ enables the suggestion forconstof the value pointing to. Default istrue.
Requires AnalyzePointers to be true.
int value = 42;
// No warning
const int *const pointer_variable = &value;
// Warning
int *const pointer_variable = &value;
TransformValuesโ provides fixit-hints for value types that automatically addconstif itโs a single declaration. Default istrue.
// Before
int value = 42;
// After
int const value = 42;
// Before
int a[] = {42, 42, 42};
// After
int const a[] = {42, 42, 42};
// Result is modified later in its life-time. No diagnostic and fixit hint will be emitted.
int result = value * 3;
result -= 10;
TransformReferencesโ provides fixit-hints for reference types that automatically addconstif itโs a single declaration. Default istrue.
// This variable could still be a constant. But because there is a non-const reference to
// it, it can not be transformed (yet).
int value = 42;
// The reference 'ref_value' is not modified and can be made 'const int &ref_value = value;'
// Before
int &ref_value = value;
// After
int const &ref_value = value;
// Result is modified later in its life-time. No diagnostic and fixit hint will be emitted.
int result = ref_value * 3;
result -= 10;
TransformPointersAsValuesโ provides fixit-hints for pointers if their pointee is not changed. This does not analyze if the value-pointed-to is unchanged! Default isfalse.
Requires 'WarnPointersAsValues' to be 'true'.
int value = 42;
// Before
const int * pointer_variable = &value;
// After
const int *const pointer_variable = &value;
// Before
const int * a[] = {&value, &value};
// After
const int *const a[] = {&value, &value};
// Before
int *ptr_value = &value;
// After
int *const ptr_value = &value;
int result = 100 * (*ptr_value); // Does not modify the pointer itself.
// This modification of the pointee is still allowed and not diagnosed.
*ptr_value = 0;
// The following pointer may not become a 'int *const'.
int *changing_pointee = &value;
changing_pointee = &result;
TransformPointersAsPointersโ provides fix-it hints for pointers if the value it pointing to is not changed. Default isfalse.
Requires WarnPointersAsPointers to be true.
int value = 42;
// Before
int * pointer_variable = &value;
// After
const int * pointer_variable = &value;
// Before
int * a[] = {&value, &value};
// After
const int * a[] = {&value, &value};
AllowedTypesโ a semicolon-separated list of names of types that will be excluded from const-correctness checking. Regular expressions are accepted, e.g.[Rr]ef(erence)?$matches every type with suffixRef,ref,Referenceandreference. If a name in the list contains the sequence::, it is matched against the qualified type name (i.e.namespace::Type), otherwise it is matched against only the type name (i.e.Type). Default is empty string.
๐ misc-coroutine-hostile-raii ๐
Detects when objects of certain hostile RAII types persist across suspension points in a coroutine. Such hostile types include scoped-lockable types and types belonging to a configurable denylist.
Some objects require that they be destroyed on the same thread that created them. Traditionally this requirement was often phrased as โmust be a local variableโ, under the assumption that local variables always work this way. However this is incorrect with C++20 coroutines, since an intervening co_await may cause the coroutine to suspend and later be resumed on another thread.
The lifetime of an object that requires being destroyed on the same thread must not encompass a co_await or co_yield point. If you create/destroy an object, you must do so without allowing the coroutine to suspend in the meantime.
Following types are considered as hostile:
- Scoped-lockable types: A scoped-lockable object persisting across a suspension point is problematic as the lock held by this object could be unlocked by a different thread. This would be undefined behaviour. This includes all types annotated with the
scoped_lockableattribute. - Types belonging to a configurable denylist.
// Call some async API while holding a lock.
task coro() {
const std::lock_guard l(&mu_);
// Oops! The async Bar function may finish on a different
// thread from the one that created the lock_guard (and called
// Mutex::Lock). After suspension, Mutex::Unlock will be called on the wrong thread.
co_await Bar();
}
โจ Options โจ
RAIITypesListโ a semicolon-separated list of qualified types which should not be allowed to persist across suspension points. Eg:my::lockable;a::b;::my::other::lockableThe default value of this option isstd::lock_guard;std::scoped_lock.AllowedAwaitablesListโ a semicolon-separated list of qualified types of awaitables types which can be safely awaited while having hostile RAII objects in scope.
Okay so we're continuing the deep dive, bestie, and honestly this is where it gets so good. Buckle up. ๐
So like, co_await-ing an expression of awaitable type is considered safe if that awaitable type is part of this list. RAII objects chillin' across such a co_await expression are also considered safe and hence are not flagged, periodt.
Example usage, and honestly bestie look at this code:
// Consider option AllowedAwaitablesList = "safe_awaitable"
struct safe_awaitable {
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<>) noexcept {}
void await_resume() noexcept {}
};
auto wait() { return safe_awaitable{}; }
task coro() {
// This persists across both the co_await's but is not flagged
// because the awaitable is considered safe to await on.
const std::lock_guard l(&mu_);
co_await safe_awaitable{};
co_await wait();
}
Eg: my::safe::awaitable;other::awaitable Default is an empty string. ๐ซถ
misc-definitions-in-headers โจ
This one finds non-extern non-inline function and variable definitions in header files, which can lead to potential ODR violations in case these headers are included from multiple translation units. Not me being paranoid, it's literally a real problem. ๐ญ
// Foo.h
int a = 1; // Warning: variable definition.
extern int d; // OK: extern variable.
namespace N {
int e = 2; // Warning: variable definition.
}
// Warning: variable definition.
const char* str = "foo";
// OK: internal linkage variable definitions are ignored for now.
// Although these might also cause ODR violations, we can be less certain and
// should try to keep the false-positive rate down.
static int b = 1;
const int c = 1;
const char* const str2 = "foo";
constexpr int k = 1;
namespace { int x = 1; }
// Warning: function definition.
int g() {
return 1;
}
// OK: inline function definition is allowed to be defined multiple times.
inline int e() {
return 1;
}
class A {
public:
int f1() { return 1; } // OK: implicitly inline member function definition is allowed.
int f2();
static int d;
};
// Warning: not an inline member function definition.
int A::f2() { return 1; }
// OK: class static data member declaration is allowed.
int A::d = 1;
// OK: function template is allowed.
template<typename T>
T f3() {
T a = 1;
return a;
}
// Warning: full specialization of a function template is not allowed.
template <>
int f3() {
int a = 1;
return a;
}
template <typename T>
struct B {
void f1();
};
// OK: member function definition of a class template is allowed.
template <typename T>
void B<T>::f1() {}
class CE {
constexpr static int i = 5; // OK: inline variable definition.
};
inline int i = 5; // OK: inline variable definition.
constexpr int f10() { return 0; } // OK: constexpr function implies inline.
// OK: C++14 variable templates are inline.
template <class T>
constexpr T pi = T(3.1415926L);
When clang-tidy is invoked with the --fix-notes option, this check provides fixes that automatically add the inline keyword to discovered functions. Please note that the addition of the inline keyword to variables is not currently supported by this check. ๐ซ
misc-header-include-cycle ๐
Check detects cyclic #include dependencies between user-defined headers. It's giving "toxic friend group but for files."
// Header A.hpp
#pragma once
#include "B.hpp"
// Header B.hpp
#pragma once
#include "C.hpp"
// Header C.hpp
#pragma once
#include "A.hpp"
// Include chain: A->B->C->A
Header files are a crucial part of many C++ programs as they provide a way to organize declarations and definitions shared across multiple source files. However, header files can also create problems when they become entangled in complex dependency cycles. Such cycles can cause issues with compilation times, unnecessary rebuilds, and make it harder to understand the overall structure of the code. It's messy, bestie, we don't love that. ๐ฌ
To address these issues, a check has been developed to detect cyclic dependencies between header files, also known as "include cycles". An include cycle occurs when a header file A includes header file B, and B (or any subsequent included header file) includes back header file A, resulting in a circular dependency cycle. It's a whole loop, like a bad TikTok trend that never ends.
This check operates at the preprocessor level and specifically analyzes user-defined headers and their dependencies. It focuses solely on detecting include cycles while disregarding other types or function dependencies. This specialized analysis helps identify and prevent issues related to header file organization. Focused. Iconic. โจ
By detecting include cycles early in the development process, developers can identify and resolve these issues before they become more difficult and time-consuming to fix. This can lead to faster compile times, improved code quality, and a more maintainable codebase overall. Additionally, by ensuring that header files are organized in a way that avoids cyclic dependencies, developers can make their code easier to understand and modify over time. Basically: catch it early, live your best life. ๐
It's worth noting that only user-defined headers their dependencies are analyzed, System includes such as standard library headers and third-party library headers are excluded. System includes are usually well-designed and free of include cycles, and ignoring them helps to focus on potential issues within the project's own codebase. This limitation doesn't diminish the ability to detect #include cycles within the analyzed code. Like, we're not gonna audit the standard library, she's fine. ๐
Developers should carefully review any warnings or feedback provided by this solution. While the analysis aims to identify and prevent include cycles, there may be situations where exceptions or modifications are necessary. It's important to exercise judgment and consider the specific context of the codebase when making adjustments. Use your brain, bestie, the tool is a helper not a boss. ๐ง
Options ๐
IgnoredFilesListโ Provides a way to exclude specific files/headers from the warnings raised by a check. This can be achieved by specifying a semicolon-separated list of regular expressions or filenames. This option can be used as an alternative to//NOLINTwhen using it is not possible. The default value of this option is an empty string, indicating that no files are ignored by default. No cap, if you can't NOLINT it, filter it.
misc-include-cleaner ๐งน
Checks for unused and missing includes. Generates findings only for the main file of a translation unit. Findings correspond to <https://clangd.llvm.org/design/include-cleaner>. It's literally the Marie Kondo of includes, sparking joy one header at a time. ๐ธ
Example:
// foo.h
class Foo{};
// bar.h
#include "baz.h"
class Bar{};
// baz.h
class Baz{};
// main.cc
#include "bar.h" // OK: uses class Bar from bar.h
#include "foo.h" // warning: unused include "foo.h"
Bar bar;
Baz baz; // warning: missing include "baz.h"
Options ๐
IgnoreHeadersโ A semicolon-separated list of regexes to disable insertion/removal of header files that match this regex as a suffix. E.g.,foo/.*disables insertion/removal for all headers under the directoryfoo. Default is an empty string, no headers will be ignored.DeduplicateFindingsโ A boolean that controls whether the check should deduplicate findings for the same symbol. Defaults totrue.UnusedIncludesโ A boolean that controls whether the check should report unused includes (includes that are not used directly). Defaults totrue.MissingIncludesโ A boolean that controls whether the check should report missing includes (header files from which symbols are used but which are not directly included). Defaults totrue.
misc-misleading-bidirectional โ ๏ธ
Warn about unterminated bidirectional unicode sequence, detecting potential attack as described in the Trojan Source <https://www.trojansource.codes> attack. Okay so this is the scary one, bestie, pay attention. ๐
Example:
#include <iostream>
int main() {
bool isAdmin = false;
/*โฎ } if (isAdmin)โฉ โฆ begin admins only */
std::cout << "You are an admin.\n";
/* end admins only โฎ { */
return 0;
}
Fr fr, this is the kind of code that looks innocent but is hiding a whole invisible agenda. Stay safe out there. ๐ก๏ธ
misc-misleading-identifier ๐ต๏ธโโ๏ธ
Finds identifiers that contain Unicode characters with right-to-left direction, which can be confusing as they may change the understanding of a whole statement line, as described in Trojan Source <https://trojansource.codes>.
An example of such misleading code follows:
#include <stdio.h>
short int ื = (short int)0;
short int ื = (short int)12345;
int main() {
int ื = ื; // a local variable, set to zero?
printf("ื is %d\n", ื);
printf("ื is %d\n", ื);
}
It's giving "read the whole line and cry." ๐ญ
misc-misplaced-const ๐ค
This check diagnoses when a const qualifier is applied to a typedef/using to a pointer type rather than to the pointee, because such constructs are often misleading to developers because the const applies to the pointer rather than the pointee. Sneaky sneaky.
For instance, in the following code, the resulting type is int * const rather than const int *:
typedef int *int_ptr;
void f(const int_ptr ptr) {
*ptr = 0; // potentially quite unexpectedly the int can be modified here
ptr = 0; // does not compile
}
The check does not diagnose when the underlying typedef/using type is a pointer to a const type or a function pointer type. This is because the const qualifier is less likely to be mistaken because it would be redundant (or disallowed) on the underlying pointee type. So like, if it's less confusing, we leave it alone. ๐
misc-new-delete-overloads ๐
cert-dcl54-cpp redirects here as an alias for this check.
The check flags overloaded operator new() and operator delete() functions that do not have a corresponding free store function defined within the same scope. For instance, the check will flag a class implementation of a non-placement operator new() when the class does not also define a non-placement operator delete() function as well. Like, they're a package deal, bestie, you can't have one without the other. ๐ซถ
The check does not flag implicitly-defined operators, deleted or private operators, or placement operators.
This check corresponds to CERT C++ Coding Standard rule DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope <https://www.securecoding.cert.org/confluence/display/cplusplus/DCL54-CPP.+Overload+allocation+and+deallocation+functions+as+a+pair+in+the+same+scope>. Periodt. ๐
Okay and that's not even the end, bestie, keep scrolling because there's so much more coming. ๐
โจ misc-no-recursion โจ
okay so bestie, this one hunts down strongly connected functions โ it analyzes the call graph looking for SCCs (that's Strongly Connected Components, iykyk) that form loops, then diagnoses every single function in the cycle and shows you one example of a possible call graph loop (aka recursion). ๐
References:
- CERT C++ Coding Standard rule DCL56-CPP. Avoid cycles during initialization of static objects โ
https://wiki.sei.cmu.edu/confluence/display/cplusplus/DCL56-CPP.+Avoid+cycles+during+initialization+of+static+objects - JPL Institutional Coding Standard for the C Programming Language (JPL DOCID D-60411) rule 2.4 Do not use direct or indirect recursion.
- OpenCL Specification, Version 1.2 rule 6.9 Restrictions: i. Recursion is not supported. โ
https://www.khronos.org/registry/OpenCL/specs/opencl-1.2.pdf
Limitations:
- The check does not handle calls done through function pointers ๐
- The check does not handle C++ destructors ๐ญ
๐ธ misc-non-copyable-objects ๐ธ
cert-fio38-c redirects here as an alias for this check, bestie.
This one flags dereferences and non-pointer declarations of objects that are not meant to be passed by value โ think C FILE objects or POSIX pthread_mutex_t objects. ๐ซถ
This check corresponds to CERT C++ Coding Standard rule FIO38-C. Do not copy a FILE object โ https://www.securecoding.cert.org/confluence/display/c/FIO38-C.+Do+not+copy+a+FILE+object
๐ misc-non-private-member-variables-in-classes ๐
cppcoreguidelines-non-private-member-variables-in-classes redirects here as an alias for this check.
Finds classes that contain non-static data members plus user-declared non-static member functions, and diagnoses every data member declared with a non-public access specifier. The vibe is: those data members should be private and accessed through member functions, not just chillin' out there exposed to derived classes or class consumers. ๐
โจ Options โจ
IgnoreClassesWithAllMemberVariablesBeingPublicโ whentrue, you get to completely ignore classes if all the member variables in that class are declared with apublicaccess specifier. Default isfalse.IgnorePublicMemberVariablesโ whentrue, it lets you ignore (aka not diagnose) all the member variables declared with apublicaccess specifier. Default isfalse.
๐ฆ misc-redundant-expression ๐ฆ
Detects redundant expressions that are typically errors thanks to copy-paste brain. ๐ญ
Depending on the operator, expressions may be:
- redundant,
- always true,
- always false,
- always a constant (zero or one).
Examples:
((x+1) | (x+1)) // (x+1) is redundant
(p->x == p->x) // always true
(p->x < p->x) // always false
(speed - speed + 1 == 12) // speed - speed is always zero
int b = a | 4 | a // identical expr on both sides
((x=1) | (x=1)) // expression is identical
(DEFINE_1 | DEFINE_1) // same macro on the both sides
((DEF_1 + DEF_2) | (DEF_1+DEF_2)) // expressions differ in spaces only
Floats are handled too โ except in the case that NaNs are checked, like so:
int TestFloat(float F) {
if (F == F) // Identical float values used
return 1;
return 0;
}
int TestFloat(float F) {
// Testing NaN.
if (F != F && F == F) // does not warn
return 1;
return 0;
}
๐ misc-static-assert ๐
cert-dcl03-c redirects here as an alias for this check.
Replaces assert() with static_assert() if the condition is evaluable at compile time. โจ
The condition of static_assert() is evaluated at compile time, which is safer and more efficient โ literally the glow-up your asserts deserve.
๐ misc-throw-by-value-catch-by-reference ๐
cert-err09-cpp and cert-err61-cpp redirect here as aliases of this check.
Finds violations of the rule "Throw by value, catch by reference" โ presented for example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu โ as well as the CERT C++ Coding Standard rule ERR61-CPP. Catch exceptions by lvalue reference โ https://wiki.sei.cmu.edu/confluence/display/cplusplus/ERR61-CPP.+Catch+exceptions+by+lvalue+reference
Exceptions:
- Throwing string literals will not be flagged despite being a pointer. They are not susceptible to slicing, and the usage of string literals is idiomatic.
- Catching character pointers (
char,wchar_t, unicode character types) will not be flagged, to allow catching string literals. - Moved named values will not be flagged as not throwing an anonymous temporary. In this case we can be sure the user knows the object can't be accessed outside catch blocks handling the error.
- Throwing function parameters will not be flagged as not throwing an anonymous temporary. This allows helper functions for throwing.
- Re-throwing caught exception variables will not be flagged as not throwing an anonymous temporary. Although this can usually be done by just writing
throw;, it happens often enough in real code. ๐
โจ Options โจ
CheckThrowTemporariesโ triggers detection of violations of the CERT recommendation ERR09-CPP. Throw anonymous temporaries. Default istrue.WarnOnLargeObjectโ also warns for any large, trivial object caught by value. Catching a large object by value is not dangerous but affects the performance negatively. The maximum size of an object allowed to be caught without warning can be set using theMaxSizeoption. Default isfalse.MaxSizeโ determines the maximum size of an object allowed to be caught without warning. Only applicable ifWarnOnLargeObjectis set totrue. If the option is set by the user tostd::numeric_limits<uint64_t>::max()then it reverts to the default value. Default is the size ofsize_t.
๐ฅ misc-unconventional-assign-operator ๐ฅ
Finds declarations of assign operators with the wrong return and/or argument types, and definitions with a good return type but wrong return statements. ๐ซถ
- The return type must be
Class&. - The assignment may be from the class type by value,
constlvalue reference, non-constrvalue reference, or from a completely different type (e.g.int). - Private and deleted operators are ignored.
- The operator must always return
*this.
๐ง misc-uniqueptr-reset-release ๐ง
Find and replace unique_ptr::reset(release()) with std::move(). it's giving cleanup, honestly.
Example:
std::unique_ptr<Foo> x, y;
x.reset(y.release()); -> x = std::move(y);
If y is already rvalue, std::move() is not added. x and y can also be std::unique_ptr<Foo>*. ๐
โจ Options โจ
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.
๐ misc-unused-alias-decls ๐
Finds unused namespace alias declarations. bye bestie, nobody was using you anyway ๐ญ
namespace my_namespace {
class C {};
}
namespace unused_alias = ::my_namespace;
โญ misc-unused-parameters โญ
Finds unused function parameters. Unused parameters may signify a bug in the code (e.g. when a different parameter is used instead). The suggested fixes either comment the parameter name out or remove the parameter completely โ if all callers of the function are in the same translation unit and can be updated.
The check is similar to the -Wunused-parameter compiler diagnostic and can be used to prepare a codebase for enabling that diagnostic. By default the check is more permissive (see StrictMode below). ๐
void a(int i) { /*some code that doesn't use `i`*/ }
// becomes
void a(int /*i*/) { /*some code that doesn't use `i`*/ }
static void staticFunctionA(int i);
static void staticFunctionA(int i) { /*some code that doesn't use `i`*/ }
// becomes
static void staticFunctionA()
static void staticFunctionA() { /*some code that doesn't use `i`*/ }
โจ Options โจ
StrictModeโ whenfalse(the default value), the check will ignore trivially unused parameters, i.e. when the corresponding function has an empty body (and in the case of constructors โ no constructor initializers). When the function body is empty, an unused parameter is unlikely to be unnoticed by a human reader, and there's basically no place for a bug to hide. ๐IgnoreVirtualโ determines whether virtual method parameters should be inspected. Set totrueto ignore them. Default isfalse.
๐ misc-unused-using-decls ๐
Finds unused using declarations.
Unused using declarations in header files will not be diagnosed, since these using declarations are part of the header's public API. Allowed header file extensions can be configured via the global option HeaderFileExtensions. ๐ธ
Example:
// main.cpp
namespace n { class C; }
using n::C; // Never actually used.
๐
misc-use-anonymous-namespace ๐
Finds instances of static functions or variables declared at global scope that could instead be moved into an anonymous namespace.
Anonymous namespaces are the "superior alternative" according to the C++ Standard. static was proposed for deprecation, but later un-deprecated to keep C compatibility [1]. static is an overloaded term with different meanings in different contexts, so it can create confusion. lowkey a main character with too many personalities.
The following uses of static will not be diagnosed:
- Functions or variables in header files, since anonymous namespaces in headers is considered an antipattern. Allowed header file extensions can be configured via the global option
HeaderFileExtensions. constorconstexprvariables, since they already have implicit internal linkage in C++.
Examples:
// Bad
static void foo();
static int x;
// Good
namespace {
void foo();
int x;
} // namespace
[1] Undeprecating static โ https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012
๐ธ misc-use-internal-linkage ๐ธ
Detects variables and functions that can be marked as static or moved into an anonymous namespace to enforce internal linkage. โจ
Static functions and variables are scoped to a single file. Marking functions and variables as static helps to better remove dead code. In addition, it gives the compiler more information and allows for more aggressive optimizations. literally free performance, bestie.
Example:
int v1; // can be marked as static
void fn1() {} // can be marked as static
namespace {
// already in anonymous namespace
int v2;
void fn2();
}
// already declared as extern
extern int v2;
void fn3(); // without function body in all declaration, maybe external linkage
void fn3();
// export declarations
export void fn4() {}
export namespace t { void fn5() {} }
export int v2;
Options
FixModeโ picks what kind of fix the check hands you ๐ the default isUseStatic.Noneโ don't fix anything automatically, we're just vibing with the warning.UseStaticโ addstaticfor internal linkage variables and functions.
modernize-avoid-bind
Okay so this one is obsessed with catching your std::bind and boost::bind usage and swapping it out for lambdas ๐ and the lambdas use value-capture unless you explicitly ask for reference capture with std::ref or boost::ref.
It handles arbitrary callables โ member functions, function objects, free functions, all the variations, no cap. Anything you could pass as the first argument of bind should be diagnosable. Literally the only known case where a fix-it is unsupported is when the same placeholder is specified multiple times in the parameter list ๐.
Given:
int add(int x, int y) { return x + y; }
Then:
void f() {
int x = 2;
auto clj = std::bind(add, x, _1);
}
gets replaced by:
void f() {
int x = 2;
auto clj = [=](auto && arg1) { return add(x, arg1); };
}
Why bother? std::bind can be hard to read and can result in larger object files and binaries due to type information that won't be produced by equivalent lambdas ๐.
Options
PermissiveParameterListโ if set totrue, the check appendsauto&&...to the end of every placeholder parameter list. Without it, a fix-it can perform an incorrect transformation when the result of the bind is used in the context of a type erased functor likestd::function, which allows mismatched arguments. Default isfalse.
For example:
int add(int x, int y) { return x + y; }
int foo() {
std::function<int(int,int)> ignore_args = std::bind(add, 2, 2);
return ignore_args(3, 3);
}
is valid code, and returns 4. The actual values passed to ignore_args are simply ignored. Without PermissiveParameterList, this would be transformed into
int add(int x, int y) { return x + y; }
int foo() {
std::function<int(int,int)> ignore_args = [] { return add(2, 2); }
return ignore_args(3, 3);
}
which will not compile, since the lambda does not contain an operator() that accepts 2 arguments ๐ญ. With permissive parameter list, it instead generates
int add(int x, int y) { return x + y; }
int foo() {
std::function<int(int,int)> ignore_args = [](auto&&...) { return add(2, 2); }
return ignore_args(3, 3);
}
which is correct โจ.
This check requires using C++14 or higher to run โ fr fr, check your standard before you come for this check.
modernize-avoid-c-arrays
cppcoreguidelines-avoid-c-arrays redirects here as an alias for this check ๐
hicpp-avoid-c-arrays redirects here as an alias for this check ๐
It finds C-style array types and recommends std::array<> / std::vector<> instead. All types of C arrays are diagnosed, periodt.
For parameters of incomplete C-style array type, it would be better to use std::span / gsl::span as replacement ๐ซถ
However, fix-its are potentially dangerous in header files and are therefore not emitted right now. Not me warning you or anything.
int a[] = {1, 2}; // warning: do not declare C-style arrays, use 'std::array' instead
int b[1]; // warning: do not declare C-style arrays, use 'std::array' instead
void foo() {
int c[b[0]]; // warning: do not declare C VLA arrays, use 'std::vector' instead
}
template <typename T, int Size>
class array {
T d[Size]; // warning: do not declare C-style arrays, use 'std::array' instead
int e[1]; // warning: do not declare C-style arrays, use 'std::array' instead
};
array<int[4], 2> d; // warning: do not declare C-style arrays, use 'std::array' instead
using k = int[4]; // warning: do not declare C-style arrays, use 'std::array' instead
However, extern "C" code is ignored, since it's common to share such headers between C code and C++ code ๐
// Some header
extern "C" {
int f[] = {1, 2}; // not diagnosed
int j[1]; // not diagnosed
inline void bar() {
{
int j[j[0]]; // not diagnosed
}
}
}
Similarly, the main() function is ignored. Its second and third parameters can be either char* argv[] or char** argv, but cannot be std::array<> ๐
Options
AllowStringArraysโ when set totrue(default isfalse), variables of character array type with deduced length, initialized directly from string literals, will be ignored. This option doesn't affect cases where length can't be deduced, resembling pointers, as seen in class members and parameters. Example:
const char name[] = "Some name";
modernize-concat-nested-namespaces
Checks for use of nested namespaces like namespace a { namespace b { ... } } and suggests changing to the more concise syntax introduced in C++17: namespace a::b { ... } ๐ Inline namespaces are not modified.
For example:
namespace n1 {
namespace n2 {
void t();
}
}
namespace n3 {
namespace n4 {
namespace n5 {
void t();
}
}
namespace n6 {
namespace n7 {
void t();
}
}
}
// in c++20
namespace n8 {
inline namespace n9 {
void t();
}
}
Will be modified to:
namespace n1::n2 {
void t();
}
namespace n3 {
namespace n4::n5 {
void t();
}
namespace n6::n7 {
void t();
}
}
// in c++20
namespace n8::inline n9 {
void t();
}
modernize-deprecated-headers
Some headers from the C library were deprecated in C++ and are no longer welcome in C++ codebases ๐ซ Some have no effect in C++ at all. For more details refer to the C++14 Standard [depr.c.headers] section.
This check replaces C standard library headers with their C++ alternatives and removes redundant ones.
// C++ source file...
#include <assert.h>
#include <stdbool.h>
// becomes
#include <cassert>
// No 'stdbool.h' here.
Important note: the Standard doesn't guarantee that the C++ headers declare all the same functions in the global namespace. The check in its current form can break the code that uses library symbols from the global namespace ๐ฌ so like, be aware.
<assert.h><complex.h><ctype.h><errno.h><fenv.h>โ// deprecated since C++11<float.h><inttypes.h><limits.h><locale.h><math.h><setjmp.h><signal.h><stdarg.h><stddef.h><stdint.h><stdio.h><stdlib.h><string.h><tgmath.h>โ// deprecated since C++11<time.h><uchar.h>โ// deprecated since C++11<wchar.h><wctype.h>
If the specified standard is older than C++11 the check will only replace headers deprecated before C++11, otherwise โ every header that appeared in the previous list ๐
These headers don't have effect in C++:
<iso646.h><stdalign.h><stdbool.h>
The checker ignores include directives within extern "C" { ... } blocks, since a library might want to expose some API for C and C++ libraries ๐ซถ
// C++ source file...
extern "C" {
#include <assert.h> // Left intact.
#include <stdbool.h> // Left intact.
}
Options
CheckHeaderFileโclang-tidycannot know if the header file included by the currently analyzed C++ source file is not included by any other C source files. Hence, to omit false-positives and wrong fixit-hints, we ignore emitting reports into header files. You can set this option totrueif you know that the header files in the project are only used by C++ source files. Default isfalse.
modernize-deprecated-ios-base-aliases
Detects usage of the deprecated member types of std::ios_base and replaces those that have a non-deprecated equivalent โจ here's the glow-up list:
std::ios_base::io_stateโstd::ios_base::iostatestd::ios_base::open_modeโstd::ios_base::openmodestd::ios_base::seek_dirโstd::ios_base::seekdirstd::ios_base::streamoffโ (no replacement ๐)std::ios_base::streamposโ (no replacement ๐)
modernize-loop-convert
This check converts for(...; ...; ...) loops to use the new range-based loops in C++11 ๐ฅ iconic behavior.
Three kinds of loops can be converted:
- Loops over statically allocated arrays.
- Loops over containers, using iterators.
- Loops over array-like containers, using
operator[]andat().
MinConfidence option
riskyโ in loops where the container expression is more complex than just a reference to a declared expression (a variable, function, enum, etc.), and some part of it appears elsewhere in the loop, we lower our confidence in the transformation due to the increased risk of changing semantics ๐ญ Transformations for these loops are marked asrisky, and thus will only be converted if the minimum required confidence level is set torisky.
int arr[10][20];
int l = 5;
...and the rest of the spicy examples look like this:
for (int j = 0; j < 20; ++j)
int k = arr[l][j] + l; // using l outside arr[l] is considered risky
for (int i = 0; i < obj.getVector().size(); ++i)
obj.foo(10); // using 'obj' is considered risky
Want the receipts on when this goes wrong? Peep Range-based loops evaluate end() only once below for an example of an incorrect transformation when the minimum required confidence level is set to risky. ๐
๐ธ reasonable (Default)
Okay so if a loop calls .end() or .size() after each iteration, the transformation for that loop gets marked as reasonable, and thus will be converted if the required confidence level is set to reasonable (default) or lower.
// using size() is considered reasonable
for (int i = 0; i < container.size(); ++i)
cout << container[i];
๐ safe
Any other loops that don't match the above criteria to be marked as risky or reasonable are marked safe, and thus will be converted if the required confidence level is set to safe or lower.
int arr[] = {1,2,3};
for (int i = 0; i < 3; ++i)
cout << arr[i];
โจ Example โจ
Original:
const int N = 5;
int arr[] = {1,2,3,4,5};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// safe conversion
for (int i = 0; i < N; ++i)
cout << arr[i];
// reasonable conversion
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
cout << *it;
// reasonable conversion
for (vector<int>::iterator it = begin(v); it != end(v); ++it)
cout << *it;
// reasonable conversion
for (vector<int>::iterator it = std::begin(v); it != std::end(v); ++it)
cout << *it;
// reasonable conversion
for (int i = 0; i < v.size(); ++i)
cout << v[i];
// reasonable conversion
for (int i = 0; i < size(v); ++i)
cout << v[i];
After applying the check with minimum confidence level set to reasonable (default):
const int N = 5;
int arr[] = {1,2,3,4,5};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// safe conversion
for (auto & elem : arr)
cout << elem;
// reasonable conversion
for (auto & elem : v)
cout << elem;
// reasonable conversion
for (auto & elem : v)
cout << elem;
๐ฆ Reverse Iterator Support
Plot twist bestie โ the converter is also capable of transforming iterator loops which use rbegin and rend for looping backwards over a container. Out of the box this will automatically happen in C++20 mode using the ranges library, however the check can be configured to work without C++20 by specifying a function to reverse a range and optionally the header file where that function lives. ๐ซถ
๐ Options
UseCxx20ReverseRangesโ When set totrueconvert loops when in C++20 or later mode usingstd::ranges::reverse_view. Default value istrue.MakeReverseRangeFunctionโ Specify the function used to reverse an iterator pair, the function should accept a class withrbeginandrendmethods and return a class withbeginandendmethods that call therbeginandrendmethods respectively. Common examples areranges::reverse_viewandllvm::reverse. Default value is an empty string.MakeReverseRangeHeaderโ Specifies the header file whereMakeReverseRangeFunctionis declared. For the previous examples this option would be set torange/v3/view/reverse.hppandllvm/ADT/STLExtras.hrespectively. If this is an empty string andMakeReverseRangeFunctionis set, the check will proceed on the assumption that the function is already available in the translation unit. This can be wrapped in angle brackets to signify to add the include as a system include. Default value is an empty string.IncludeStyleโ A string specifying which include-style is used,llvmorgoogle. Default isllvm.
๐ Limitations
Not gonna lie, there are certain situations where the tool may erroneously perform transformations that remove information and change semantics. Users of the tool should be aware of the behavior and limitations of the check outlined by the cases below. Read this part, fr fr. ๐
๐ Comments inside loop headers
Comments inside the original loop header are ignored and deleted when transformed.
for (int i = 0; i < N; /* This will be deleted */ ++i) { }
That comment? Gone. Deleted. Not invited to the transformation. ๐
๐ Range-based loops evaluate end() only once
The C++11 range-based for loop calls .end() only once during the initialization of the loop. If in the original loop .end() is called after each iteration the semantics of the transformed loop may differ. Highkey important.
// The following is semantically equivalent to the C++11 range-based for loop,
// therefore the semantics of the header will not change.
for (iterator it = container.begin(), e = container.end(); it != e; ++it) { }
// Instead of calling .end() after each iteration, this loop will be
// transformed to call .end() only once during the initialization of the loop,
// which may affect semantics.
for (iterator it = container.begin(); it != container.end(); ++it) { }
As explained above, calling member functions of the container in the body of the loop is considered risky. If the called member function modifies the container the semantics of the converted loop will differ due to .end() being called only once.
bool flag = false;
for (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) {
// Add a copy of the first element to the end of the vector.
if (!flag) {
// This line makes this transformation 'risky'.
vec.push_back(*it);
flag = true;
}
cout << *it;
}
The original code above prints out the contents of the container including the newly added element while the converted loop, shown below, will only print the original contents and not the newly added element. ๐ญ
bool flag = false;
for (auto & elem : vec) {
// Add a copy of the first element to the end of the vector.
if (!flag) {
// This line makes this transformation 'risky'
vec.push_back(elem);
flag = true;
}
cout << elem;
}
Semantics will also be affected if .end() has side effects. For example, in the case where calls to .end() are logged the semantics will change in the transformed loop if .end() was originally called after each iteration.
iterator end() {
num_of_end_calls++;
return container.end();
}
๐ง Overloaded operator->() with side effects
Similarly, if operator->() was overloaded to have side effects, such as logging, the semantics will change. If the iterator's operator->() was used in the original loop it will be replaced with <container element>.<member> instead due to the implicit dereference as part of the range-based for loop. Therefore any side effect of the overloaded operator->() will no longer be performed.
for (iterator it = c.begin(); it != c.end(); ++it) {
it->func(); // Using operator->()
}
// Will be transformed to:
for (auto & elem : c) {
elem.func(); // No longer using operator->()
}
๐ Pointers and references to containers
While most of the check's risk analysis is dedicated to determining whether the iterator or container was modified within the loop, it is possible to circumvent the analysis by accessing and modifying the container through a pointer or reference.
If the container were directly used instead of using the pointer or reference the following transformation would have only been applied at the risky level since calling a member function of the container is considered risky. The check cannot identify expressions associated with the container that are different than the one used in the loop header, therefore the transformation below ends up being performed at the safe level. sneaky sneaky.
vector<int> vec;
vector<int> *ptr = &vec;
vector<int> &ref = vec;
for (vector<int>::iterator it = vec.begin(), e = vec.end(); it != e; ++it) {
if (!flag) {
// Accessing and modifying the container is considered risky, but the risk
// level is not raised here.
ptr->push_back(*it);
ref.push_back(*it);
flag = true;
}
}
โญ OpenMP
As range-based for loops are only available since OpenMP 5, this check should not be used on code with a compatibility requirement of OpenMP prior to version 5. It is intentional that this check does not make any attempts to exclude incorrect diagnostics on OpenMP for loops prior to OpenMP 5.
To prevent this check to be applied (and to break) OpenMP for loops but still be applied to non-OpenMP for loops the usage of NOLINT (see Suppressing Undesired Diagnostics <#clang-tidy-nolint>) on the specific for loops is recommended. ๐ซก
๐ modernize-macro-to-enum
Replaces groups of adjacent macros with an unscoped anonymous enum. Using an unscoped anonymous enum ensures that everywhere the macro token was used previously, the enumerator name may be safely used. slay, honestly.
This check can be used to enforce the C++ core guideline Enum.1: Prefer enumerations over macros (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#enum1-prefer-enumerations-over-macros), within the constraints outlined below.
Potential macros for replacement must meet the following constraints:
- Macros must expand only to integral literal tokens or expressions of literal tokens. The expression may contain any of the unary operators
-,+,~or!, any of the binary operators,,-,+,*,/,%,&,|,^,<,>,<=,>=,==,!=,||,&&,<<,>>or<=>, the ternary operator?:and its GNU extension (https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html). Parenthesized expressions are also recognized. This recognizes most valid expressions. In particular, expressions with thesizeofoperator are not recognized. - Macros must be defined on sequential source file lines, or with only comment lines in between macro definitions.
- Macros must all be defined in the same source file.
- Macros must not be defined within a conditional compilation block. (Conditional include guards are exempt from this constraint.)
- Macros must not be defined adjacent to other preprocessor directives.
- Macros must not be used in any conditional preprocessing directive.
- Macros must not be used as arguments to other macros.
- Macros must not be undefined.
- Macros must be defined at the top-level, not inside any declaration or definition.
Each cluster of macros that meets those constraints is presumed to be a set of values that are totally fine to swap out for an anonymous enum. From there, a dev can give the anonymous enum a name and keep refactoring to a scoped enum if they're feeling fancy. Comments on the same line as a macro definition โ or sitting between subsequent macro definitions โ get preserved in the output. No formatting is assumed in the replacements provided, although clang-tidy can optionally format all the fixes for you. ๐
Warning: initializing expressions are assumed to be valid initializers for an enum. C requires that enum values fit into anint, but that may not be the case for some accepted constant expressions. For instance1 << 40will not fit into anintwhen the size of anintis 32 bits. ๐ญ So likeโฆ be careful bestie.
Examples:
#define RED 0xFF0000
#define GREEN 0x00FF00
#define BLUE 0x0000FF
#define TM_NONE (-1) // No method selected.
#define TM_ONE 1 // Use tailored method one.
#define TM_TWO 2 // Use tailored method two. Method two
// is preferable to method one.
#define TM_THREE 3 // Use tailored method three.
becomes
enum {
RED = 0xFF0000,
GREEN = 0x00FF00,
BLUE = 0x0000FF
};
enum {
TM_NONE = (-1), // No method selected.
TM_ONE = 1, // Use tailored method one.
TM_TWO = 2, // Use tailored method two. Method two
// is preferable to method one.
TM_THREE = 3 // Use tailored method three.
};
๐ซถ modernize-make-shared
This check finds std::shared_ptr objects being created by explicitly calling the constructor plus a new expression, and replaces the whole thing with a call to std::make_shared. Slay. โจ
auto my_ptr = std::shared_ptr<MyPair>(new MyPair(1, 2));
// becomes
auto my_ptr = std::make_shared<MyPair>(1, 2);
It also catches calls to std::shared_ptr::reset() with a new expression, and swaps them for a call to std::make_shared.
my_ptr.reset(new MyPair(1, 2));
// becomes
my_ptr = std::make_shared<MyPair>(1, 2);
โจ Options
MakeSmartPtrFunctionโ a string specifying the name of the make-shared-ptr function. Default isstd::make_shared.MakeSmartPtrFunctionHeaderโ a string specifying the corresponding header of the make-shared-ptr function. Default ismemory.IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.IgnoreDefaultInitializationโ if set tofalse, the check does not suggest edits that will transform default initialization into value initialization, since that can cause performance regressions. Default istrue.
๐ฆ modernize-make-unique
This check finds std::unique_ptr objects being created by explicitly calling the constructor plus a new expression, and replaces it with a call to std::make_unique, which โ fun fact โ was introduced in C++14. ๐
auto my_ptr = std::unique_ptr<MyPair>(new MyPair(1, 2));
// becomes
auto my_ptr = std::make_unique<MyPair>(1, 2);
It also catches calls to std::unique_ptr::reset() with a new expression, and replaces them with a call to std::make_unique.
my_ptr.reset(new MyPair(1, 2));
// becomes
my_ptr = std::make_unique<MyPair>(1, 2);
โจ Options
MakeSmartPtrFunctionโ a string specifying the name of the make-unique-ptr function. Default isstd::make_unique.MakeSmartPtrFunctionHeaderโ a string specifying the corresponding header of the make-unique-ptr function. Default is<memory>.IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.IgnoreDefaultInitializationโ if set tofalse, the check does not suggest edits that will transform default initialization into value initialization, since that can cause performance regressions. Default istrue.
๐ธ modernize-min-max-use-initializer-list
Replaces nested std::min and std::max calls with an initializer list where applicable. Lowkey so satisfying to watch happen. ๐
For instance, consider the following code:
int a = std::max(std::max(i, j), k);
The check will transform the above code to:
int a = std::max({i, j, k});
๐ฅ Performance Considerations
So likeโฆ while this check simplifies the code and makes it more readable, it may cause performance degradation for non-trivial types, because of the need to copy objects into the initializer list. Ngl that's a real cost.
To avoid this, it is recommended to use std::ref or std::cref for non-trivial types:
std::string b = std::max({std::ref(i), std::ref(j), std::ref(k)});
โจ Options
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.IgnoreNonTrivialTypesโ a boolean specifying whether to ignore non-trivial types. Default istrue.IgnoreTrivialTypesOfSizeAboveโ an integer specifying the size (in bytes) above which trivial types are ignored. Default is32.
๐ modernize-pass-by-value
Okay so, with move semantics added to the language and the standard library updated with move constructors added for so many types, it's now genuinely interesting to take an argument directly by value instead of by const-reference and then copy. This check lets the compiler take care of choosing the best way to construct the copy. Main character energy, honestly. โจ
The transformation is usually beneficial when the calling code passes an rvalue and assumes the move construction is a cheap operation. This short example illustrates how the construction of the value happens:
void foo(std::string s);
std::string get_str();
void f(const std::string &str) {
foo(str); // lvalue -> copy construction
foo(get_str()); // prvalue -> move construction
}
Note: currently, only constructors are transformed to make use of pass-by-value. Contributions that handle other situations are welcome! ๐
๐ง Pass-by-value in constructors
Replaces the uses of const-references constructor parameters that get copied into class fields. The parameter is then moved with std::move().
Since std::move() is a library function declared in <utility> it may be necessary to add this include. The check will add the include directive when necessary.
#include <string>
class Foo {
public:
- Foo(const std::string &Copied, const std::string &ReadOnly)
- : Copied(Copied), ReadOnly(ReadOnly)
+ Foo(std::string Copied, const std::string &ReadOnly)
+ : Copied(std::move(Copied)), ReadOnly(ReadOnly)
{}
private:
std::string Copied;
const std::string &ReadOnly;
};
std::string get_cwd();
void f(const std::string &Path) {
// The parameter corresponding to 'get_cwd()' is move-constructed. By
// using pass-by-value in the Foo constructor we managed to avoid a
// copy-construction.
Foo foo(get_cwd(), Path);
}
If the parameter is used more than once, no transformation is performed, since moved objects have an undefined state. That means the following code will be left untouched:
#include <string>
void pass(const std::string &S);
struct Foo {
Foo(const std::string &S) : Str(S) {
pass(S);
}
std::string Str;
};
๐ Known limitations
A situation where the generated code can be wrong is when the object referenced is modified before the assignment in the init-list through a "hidden" reference. Yeah. ๐ญ
Example:
std::string s("foo");
struct Base {
Base() {
s = "bar";
}
};
struct Derived : Base {
- Derived(const std::string &S) : Field(S)
+ Derived(std::string S) : Field(std::move(S))
{ }
std::string Field;
};
void f() {
- Derived d(s); // d.Field holds "bar"
+ Derived d(s); // d.Field holds "foo"
}
๐ Note about delayed template parsing
When delayed template parsing is enabled, constructors that are part of templated contexts โ templated constructors, constructors in class templates, constructors of inner classes of template classes, etc. โ are not transformed. Delayed template parsing is enabled by default on Windows as a Microsoft extension: Clang Compiler User's Manual - Microsoft extensions (<https://clang.llvm.org/docs/UsersManual.html#microsoft-extensions>).
Delayed template parsing can be enabled using the -fdelayed-template-parsing flag and disabled using -fno-delayed-template-parsing.
Example:
template <typename T> class C {
std::string S;
public:
= // using -fdelayed-template-parsing (default on Windows)
= C(const std::string &S) : S(S) {}
+ // using -fno-delayed-template-parsing (default on non-Windows systems)
+ C(std::string S) : S(std::move(S)) {}
};
See also: for more information about the pass-by-value idiom, read: Want Speed? Pass by Value (<https://web.archive.org/web/20140205194657/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/>). ๐ซถ
โจ Options
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.ValuesOnlyโ whentrue, the check only warns about copied parameters that are already passed by value. Default isfalse.
๐ modernize-raw-string-literal
This check selectively replaces string literals containing escaped characters with raw string literals. It's giving glow-up. ๐
Example:
const char *const Quotes{"embedded \"quotes\""};
const char *const Paragraph{"Line one.\nLine two.\nLine three.\n"};
const char *const SingleLine{"Single line.\n"};
const char *const TrailingSpace{"Look here -> \n"};
const char *const Tab{"One\tTwo\n"};
const char *const Bell{"Hello!\a And welcome!"};
const char *const Path{"C:\\Program Files\\Vendor\\Application.exe"};
const char *const RegEx{"\\w\\([a-z]\\)"};
becomes
const char *const Quotes{R"(embedded "quotes")"};
const char *const Paragraph{"Line one.\nLine two.\nLine three.\n"};
const char *const SingleLine{"Single line.\n"};
const char *const TrailingSpace{"Look here -> \n"};
const char *const Tab{"One\tTwo\n"};
const char *const Bell{"Hello!\a And welcome!"};
const char *const Path{R"(C:\Program Files\Vendor\Application.exe)"};
const char *const RegEx{R"(\w\([a-z]\))"};
The presence of any of the following escapes can cause the string to be converted to a raw string literal: \\, \', \", \?, and octal or hexadecimal escapes for printable ASCII characters.
A string literal containing only escaped newlines is a common way of writing lines of text output. Introducing physical newlines with raw string literals in this case is likely to impede readability. These string literals are left unchanged.
An escaped horizontal tab, form feed, or vertical tab prevents the string literal from being converted. The presence of a horizontal tab, form feed or vertical tab in source code is not visually obvious.
โจ Options โจ
DelimiterStemโ a custom delimiter to escape characters in raw string literals. It gets used in this construction here:R"stem_delimiter(contents)stem_delimiter". The default value islit.ReplaceShorterLiteralsโ controls replacing shorter non-raw string literals with longer raw string literals. Setting this option totrueenables the replacement. The default value isfalse(so like, shorter literals are not replaced, bestie).
๐ฆ modernize-redundant-void-arg ๐ฆ
Find and remove redundant void argument lists. Iconic little cleanup, no notes.
Examples โ left column is "Initial code", right column is "Code with applied fixes":
int f(void);โint f();int (*f(void))(void);โint (*f())();typedef int (*f_t(void))(void);โtypedef int (*f_t())();void (C::*p)(void);โvoid (C::*p)();C::C(void) {}โC::C() {}C::~C(void) {}โC::~C() {}
๐ modernize-replace-auto-ptr ๐
This check replaces the uses of the deprecated class std::auto_ptr by std::unique_ptr (introduced in C++11). The transfer of ownership, done by the copy-constructor and the assignment operator, is changed to match std::unique_ptr usage by using explicit calls to std::move().
Migration example, look at her go:
-void take_ownership_fn(std::auto_ptr<int> int_ptr);
+void take_ownership_fn(std::unique_ptr<int> int_ptr);
void f(int x) {
- std::auto_ptr<int> a(new int(x));
- std::auto_ptr<int> b;
+ std::unique_ptr<int> a(new int(x));
+ std::unique_ptr<int> b;
- b = a;
- take_ownership_fn(b);
+ b = std::move(a);
+ take_ownership_fn(std::move(b));
}
Since std::move() is a library function declared in <utility> it may be necessary to add this include. The check will add the include directive when necessary.
๐ญ Known Limitations ๐ญ
- If headers modification is not activated or if a header is not allowed to be changed this check will produce broken code (compilation error), where the headers' code will stay unchanged while the code using them will be changed. Yeah. That one hurts.
- Client code that declares a reference to an
std::auto_ptrcoming from code that can't be migrated (such as a header coming from a 3rd party library) will produce a compilation error after migration. This is because the type of the reference will be changed tostd::unique_ptrbut the type returned by the library won't change, binding a reference tostd::unique_ptrfrom anstd::auto_ptr. This pattern doesn't make much sense and usuallystd::auto_ptrare stored by value (otherwise what is the point in using them instead of a reference or a pointer?).
// <3rd-party header...>
std::auto_ptr<int> get_value();
const std::auto_ptr<int> & get_ref();
// <calling code (with migration)...>
-std::auto_ptr<int> a(get_value());
+std::unique_ptr<int> a(get_value()); // ok, unique_ptr constructed from auto_ptr
-const std::auto_ptr<int> & p = get_ptr();
+const std::unique_ptr<int> & p = get_ptr(); // won't compile
- Non-instantiated templates aren't modified. Periodt.
template <typename X>
void f() {
std::auto_ptr<X> p;
}
// only 'f<int>()' (or similar) will trigger the replacement.
โจ Options โจ
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.
๐ modernize-replace-disallow-copy-and-assign-macro ๐
Finds macro expansions of DISALLOW_COPY_AND_ASSIGN(Type) and replaces them with a deleted copy constructor and a deleted assignment operator.
Before the delete keyword was introduced in C++11 it was common practice to declare a copy constructor and an assignment operator as private members. This effectively makes them unusable to the public API of a class.
With the advent of the delete keyword in C++11 we can abandon the private access of the copy constructor and the assignment operator and delete the methods entirely. Slay, honestly.
When running this check on a code like this:
class Foo {
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
It will be transformed to this:
class Foo {
private:
Foo(const Foo &) = delete;
const Foo &operator=(const Foo &) = delete;
};
๐ Known Limitations ๐
- Notice that the migration example above leaves the private access specification untouched. You might want to run the check
modernize-use-equals-deleteto get warnings for deleted functions in private sections.
๐ธ Options ๐ธ
MacroNameโ a string specifying the macro name whose expansion will be replaced. Default isDISALLOW_COPY_AND_ASSIGN.
See: https://en.cppreference.com/w/cpp/language/function#Deleted_functions
๐ฅ modernize-replace-random-shuffle ๐ฅ
This check will find occurrences of std::random_shuffle and replace it with std::shuffle. In C++17 std::random_shuffle will no longer be available and thus we need to replace it. Gotta migrate or get left behind, bestie.
Below are two examples of what kind of occurrences will be found and two examples of what it will be replaced with.
std::vector<int> v;
// First example
std::random_shuffle(vec.begin(), vec.end());
// Second example
std::random_shuffle(vec.begin(), vec.end(), randomFunc);
Both of these examples will be replaced with:
std::shuffle(vec.begin(), vec.end(), std::mt19937(std::random_device()()));
The second example will also receive a warning that randomFunc is no longer supported in the same way as before so if the user wants the same functionality, the user will need to change the implementation of the randomFunc.
One thing to be aware of here is that std::random_device is quite expensive to initialize. So if you are using the code in a performance critical place, you probably want to initialize it elsewhere. Another thing is that the seeding quality of the suggested fix is quite poor: std::mt19937 has an internal state of 624 32-bit integers, but is only seeded with a single integer. So if you require higher quality randomness, you should consider seeding better, for example:
std::shuffle(v.begin(), v.end(), []() {
std::mt19937::result_type seeds[std::mt19937::state_size];
std::random_device device;
std::uniform_int_distribution<typename std::mt19937::result_type> dist;
std::generate(std::begin(seeds), std::end(seeds), [&] { return dist(device); });
std::seed_seq seq(std::begin(seeds), std::end(seeds));
return std::mt19937(seq);
}());
๐ง modernize-return-braced-init-list ๐ง
Replaces explicit calls to the constructor in a return with a braced initializer list. This way the return type is not needlessly duplicated in the function definition and the return statement. It's giving โจefficiencyโจ.
Foo bar() {
Baz baz;
return Foo(baz);
}
// transforms to:
Foo bar() {
Baz baz;
return {baz};
}
๐ modernize-shrink-to-fit ๐
Replace copy and swap tricks on shrinkable containers with the shrink_to_fit() method call.
The shrink_to_fit() method is more readable and more effective than the copy and swap trick to reduce the capacity of a shrinkable container. Note that, the shrink_to_fit() method is only available in C++11 and up โ so like, check your standard before you go off.
โญ modernize-type-traits โญ
Converts standard library type traits of the form traits<...>::type and traits<...>::value into traits_t<...> and traits_v<...> respectively.
For example:
std::is_integral<T>::value
std::is_same<int, float>::value
typename std::add_const<T>::type
std::make_signed<unsigned>::type
Would be converted into:
std::is_integral_v<T>
std::is_same_v<int, float>
std::add_const_t<T>
std::make_signed_t<unsigned>
๐ซถ Options ๐ซถ
IgnoreMacrosโ iftruedon't diagnose traits defined in macros.
Note: Fixes will never be emitted for code inside of macros.
#define IS_SIGNED(T) std::is_signed<T>::value
Defaults to false.
๐ Limitations ๐
Does not currently diagnose uses of type traits with nested name specifiers (e.g. std::chrono::is_clock, std::chrono::treat_as_floating_point).
๐ modernize-unary-static-assert ๐
The check diagnoses any static_assert declaration with an empty string literal and provides a fix-it to replace the declaration with a single-argument static_assert declaration.
The check is only applicable for C++17 and later code.
The following code:
void f_textless(int a) {
static_assert(sizeof(a) <= 10, "");
}
is replaced by:
void f_textless(int a) {
static_assert(sizeof(a) <= 10);
}
modernize-use-auto
okay bestie, this check is literally the glow-up artist for your variable declarations โ it swaps in the auto type specifier so your code reads better and is easier to keep alive long-term. iykyk. ๐
std::vector<int>::iterator I = my_container.begin();
// transforms to:
auto I = my_container.begin();
But like, it's not feral about it. auto only shows up when the variable type matches the type of the initializer expression. Translation: auto should deduce the same type that was originally spelled out in the source. But not every situation deserves the transformation, bestie:
int val = 42;
InfoStruct &I = SomeObject.getInfo();
// Should not become:
auto val = 42;
auto &I = SomeObject.getInfo();
See? Using auto for builtins here doesn't help readability at all. In other situations it actually makes the code less self-documenting and tanks readability and maintainability. So auto only gets introduced in the specific situations listed below. periodt. โจ
Iterators
Iterator type specifiers are lowkey always long and used constantly, especially in loop constructs. Since the functions that generate iterators have a common format, the type specifier can be swapped out without hiding what the code means โ and it improves readability and maintainability while doing it. ๐ฆ
for (std::vector<int>::iterator I = my_container.begin(),
E = my_container.end();
I != E; ++I) {
}
// becomes
for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) {
}
The check will only replace iterator type-specifiers when all of the following conditions are satisfied:
- The iterator is for one of the standard containers in the
stdnamespace:arraydequeforward_listlistvectormapmultimapsetmultisetunordered_mapunordered_multimapunordered_setunordered_multisetqueuepriority_queuestack
- The iterator is one of the possible iterator types for standard containers:
iteratorreverse_iteratorconst_iteratorconst_reverse_iterator
- In addition to using iterator types directly, typedefs or other ways of referring to those types are also allowed. However, implementation-specific types for which a type like
std::vector<int>::iteratoris itself a typedef will not be transformed. Consider the following examples:
// The following direct uses of iterator types will be transformed.
std::vector<int>::iterator I = MyVec.begin();
{
using namespace std;
list<int>::iterator I = MyList.begin();
}
// The type specifier for J would transform to auto since it's a typedef
// to a standard iterator type.
typedef std::map<int, std::string>::const_iterator map_iterator;
map_iterator J = MyMap.begin();
// The following implementation-specific iterator type for which
// std::vector<int>::iterator could be a typedef would not be transformed.
__gnu_cxx::__normal_iterator<int*, std::vector> K = MyVec.begin();
- The initializer for the variable being declared is not a braced initializer list. Otherwise, using
autowould cause the type of the variable to be deduced asstd::initializer_list. ๐
New expressions
A lot of the time, when a pointer gets declared and initialized with new, the pointee type gets written twice โ once in the declaration type, once in the new expression. When that happens, the declaration type can be replaced with auto, and the readability/maintainability glow-up is real. ๐ฅ
TypeName *my_pointer = new TypeName(my_param);
// becomes
auto *my_pointer = new TypeName(my_param);
The check will also replace the declaration type in multiple declarations, if the following conditions are satisfied:
- All declared variables have the same type (i.e. all of them are pointers to the same type).
- All declared variables are initialized with a
newexpression. - The types of all the
newexpressions are the same than the pointee of the declaration type.
TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;
// becomes
auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;
Cast expressions
Same energy as above, honestly โ when a variable is declared and initialized with a cast, the variable type gets written twice: in the declaration type and in the cast expression. When that happens, the declaration type can be replaced with auto for that readability + maintainability boost. ๐ธ
TypeName *my_pointer = static_cast<TypeName>(my_param);
// becomes
auto *my_pointer = static_cast<TypeName>(my_param);
The check handles static_cast, dynamic_cast, const_cast, reinterpret_cast, functional casts, C-style casts and function templates that behave as casts, such as llvm::dyn_cast, boost::lexical_cast and gsl::narrow_cast. Calls to function templates are considered to behave as casts if the first template argument is explicit and is a type, and the function returns that type, or a pointer or reference to it. no cap. ๐ฅ
Known Limitations
- If the initializer is an explicit conversion constructor, the check will not replace the type specifier even though it would be safe to do so.
- User-defined iterators are not handled at this time.
ngl, these are the spots where the check just... politely minds its own business. ๐ซถ
Options
MinTypeNameLength
If the option is set to non-zero (default 5), the check will ignore type names having a length less than the option value. The option affects expressions only, not iterators. Spaces between multi-lexeme type names (long int) are considered as one. If the RemoveStars option (see below) is set to true, then *s in the type are also counted as a part of the type name.
// MinTypeNameLength = 0, RemoveStars=0
int a = static_cast<int>(foo()); // ---> auto a = ...
// length(bool *) = 4
bool *b = new bool; // ---> auto *b = ...
unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
// MinTypeNameLength = 5, RemoveStars=0
int a = static_cast<int>(foo()); // ---> int a = ...
bool b = static_cast<bool>(foo()); // ---> bool b = ...
bool *pb = static_cast<bool*>(foo()); // ---> bool *pb = ...
unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
// length(long <on-or-more-spaces> int) = 8
long int d = static_cast<long int>(foo()); // ---> auto d = ...
// MinTypeNameLength = 5, RemoveStars=1
int a = static_cast<int>(foo()); // ---> int a = ...
// length(int * * ) = 5
int **pa = static_cast<int**>(foo()); // ---> auto pa = ...
bool b = static_cast<bool>(foo()); // ---> bool b = ...
bool *pb = static_cast<bool*>(foo()); // ---> auto pb = ...
unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
long int d = static_cast<long int>(foo()); // ---> auto d = ...
RemoveStars
If the option is set to true (default is false), the check will remove stars from the non-typedef pointer types when replacing type names with auto. Otherwise, the check will leave stars. For example:
TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;
// RemoveStars = 0
auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;
// RemoveStars = 1
auto my_first_pointer = new TypeName, my_second_pointer = new TypeName;
modernize-use-bool-literals
This one finds integer literals that are being cast to bool. iconic, honestly. ๐
bool p = 1;
bool f = static_cast<bool>(1);
std::ios_base::sync_with_stdio(0);
bool x = p ? 1 : 0;
// transforms to
bool p = true;
bool f = true;
std::ios_base::sync_with_stdio(false);
bool x = p ? true : false;
Options
IgnoreMacros
If set to true, the check will not give warnings inside macros. Default is true.
modernize-use-constraints
Replace std::enable_if with C++20 requires clauses. big main character energy, tbh. ๐
std::enable_if is a SFINAE mechanism for selecting the desired function or class template based on type traits or other requirements. enable_if changes the meta-arity of the template, and has other adverse side effects <https://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0225r0.html> in the code. C++20 introduces concepts and constraints as a cleaner language provided solution to achieve the same outcome.
This check finds some common std::enable_if patterns that can be replaced by C++20 requires clauses. The tool can replace some of these patterns automatically โ otherwise, the tool will emit a diagnostic without a replacement. The tool can detect the following std::enable_if patterns
std::enable_ifin the return type of a functionstd::enable_ifas the trailing template parameter for function templates
Other uses, for example, in class templates for function parameters, are not currently supported by this tool. Other variants such as boost::enable_if are not currently supported by this tool.
Below are some examples of code using std::enable_if. โจ
// enable_if in function return type
template <typename T>
std::enable_if_t<T::some_trait, int> only_if_t_has_the_trait() { ... }
// enable_if in the trailing template parameter
template <typename T, std::enable_if_t<T::some_trait, int> = 0>
void another_version() { ... }
template <typename T>
typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
return Obj{};
}
template <typename T, std::enable_if_t<T::some_trait, int> = 0>
struct my_class {};
The tool will replace the above code with,
// warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
template <typename T>
int only_if_t_has_the_trait() requires T::some_trait { ... }
// warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
template <typename T>
void another_version() requires T::some_trait { ... }
// The tool will emit a diagnostic for the following, but will
// not attempt to replace the code.
// warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
template <typename T>
typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
return Obj{};
}
// The tool will not emit a diagnostic or attempt to replace the code.
template <typename T, std::enable_if_t<T::some_trait, int> = 0>
struct my_class {};
Note: system headers are not analyzed by this check. Not even a little. ๐
modernize-use-default
This one got renamed, bestie โ it's now modernize-use-equals-default ๐ฆ (go look there, it's the same icon, new name).
modernize-use-default-member-init
Okay so this icon takes constructors' member initializers and yeets them into the shiny new C++11 default member initializers. And any other member initializers that match the default one? Gone. Deleted. Not needed. This lowkey cuts down on repeated code and can even unlock = default for you โจ
struct A {
A() : i(5), j(10.0) {}
A(int i) : i(i), j(10.0) {}
int i;
double j;
};
// becomes
struct A {
A() {}
A(int i) : i(i) {}
int i{5};
double j{10.0};
};
Note: it only converts member initializers for built-in types, enums, and pointers. For classes, the readability-redundant-member-init check is the one that'll strip out the redundant member initializers. Teamwork, fr.
Options
UseAssignmentโ set this totrue(default isfalse) and the check initializes members with an assignment instead. Like so:
struct A {
A() {}
A(int i) : i(i) {}
int i = 5;
double j = 10.0;
};
IgnoreMacrosโ set totrue(default istrue) and it won't warn about members declared inside macros. ๐
modernize-use-designated-initializers
This icon hunts for initializer lists of aggregate types that could be written as designated initializers instead. Glow-up for your structs, basically ๐
Here's the tea: with plain initializer lists, it's so easy to sneak a bug in when you add new fields in the middle of a struct or class. Same chaos when someone changes the order of the fields. ๐ญ
C++20 supports designated initializer syntax for aggregate types โ and with it, we can always be sure aggregates get constructed correctly, because every variable being initialized is referenced by name. Iconic behaviour.
Example:
struct S { int i, j; };
is an aggregate type that should be initialized as
S s{.i = 1, .j = 2};
instead of
S s{1, 2};
which could easily become a whole issue when i and j get swapped in the declaration of S. ๐
And even when you're compiling in a language version older than C++20, designated initializers might be supported depending on your compiler. So by default the check stays restricted to C99/C++20 and above. Check out the options -Wc99-designator for support of mixed designators in initializer lists in C, and -Wc++20-designator for designated initializers in older C++ language modes. ๐ซถ
Options
IgnoreMacrosโ a value offalsemeans components of initializer lists expanded from macros are not checked. Default value istrue.IgnoreSingleElementAggregatesโ a value offalsemeans even initializers for aggregate types with only a single element get checked. Default istrue.std::arrayinitializations are always excluded, because the type is a standard library abstraction and was not intended to be initialized with designated initializers. Periodt.RestrictToPODTypesโtruemeans only Plain Old Data (POD) types get checked. That makes the check applicable to even older C++ standards. Default isfalse.StrictCStandardComplianceโ when set tofalse, the check won't restrict itself to C99 and above. Default istrue.StrictCppStandardComplianceโ when set tofalse, the check won't restrict itself to C++20 and above. Default istrue.
modernize-use-emplace
This check flags insertions into an STL-style container where you called the push_back, push, or push_front methods with an explicitly-constructed temporary of the container's element type. In that case the matching emplace methods give you less verbose and potentially more efficient code. Slay ๐
Right now the check doesn't support insert. It also doesn't support insert functions for associative containers, because swapping insert for emplace may result in a speed regression <https://htmlpreview.github.io/?https://github.com/HowardHinnant/papers/blob/master/insert_vs_emplace.html> โ but it might get support with some addition flag in the future, iykyk.
The ContainersWithPushBack, ContainersWithPush, and ContainersWithPushFront options specify the container types that support push_back, push, and push_front respectively. Defaults are:
ContainersWithPushBack:std::vector,std::deque, andstd::list.ContainersWithPush:std::stack,std::queue, andstd::priority_queue.ContainersWithPushFront:std::forward_list,std::list, andstd::deque.
This check also reports when an emplace-like method is improperly used โ for example using emplace_back while also calling a constructor. That creates a temporary that requires at best a move and at worst a copy. Almost all emplace-like functions in the STL are covered by this, with try_emplace on std::map and std::unordered_map being the exception, since it behaves slightly differently than all the others. More containers can be added with the EmplacyFunctions option, so long as the container defines a value_type type, and the emplace-like functions construct a value_type object.
Before:
std::vector<MyClass> v;
v.push_back(MyClass(21, 37));
v.emplace_back(MyClass(21, 37));
std::vector<std::pair<int, int>> w;
w.push_back(std::pair<int, int>(21, 37));
w.push_back(std::make_pair(21L, 37L));
w.emplace_back(std::make_pair(21L, 37L));
After:
std::vector<MyClass> v;
v.emplace_back(21, 37);
v.emplace_back(21, 37);
std::vector<std::pair<int, int>> w;
w.emplace_back(21, 37);
w.emplace_back(21L, 37L);
w.emplace_back(21L, 37L);
By default the check can remove unnecessary std::make_pair and std::make_tuple calls from push_back calls on containers of std::pair and std::tuple. Custom tuple-like types can be tweaked via the TupleTypes option; custom make functions via the TupleMakeFunctions option. ๐
Other situation: when we pass arguments that will be converted to a type inside a container.
Before:
std::vector<boost::optional<std::string> > v;
v.push_back("abc");
After:
std::vector<boost::optional<std::string> > v;
v.emplace_back("abc");
In some cases the transformation would be valid, but the code wouldn't be exception safe. In that case the push_back calls won't be replaced. ๐จ
std::vector<std::unique_ptr<int>> v;
v.push_back(std::unique_ptr<int>(new int(0)));
auto *ptr = new int(1);
v.push_back(std::unique_ptr<int>(ptr));
That's because replacing it with emplace_back could cause a leak of this pointer if emplace_back throws an exception before emplacement (e.g. not enough memory to add a new element). Rough.
For more info, read item 42 โ "Consider emplacement instead of insertion." โ of Scott Meyers' "Effective Modern C++".
The default smart pointers considered are std::unique_ptr, std::shared_ptr, std::auto_ptr. To specify other smart pointers or other classes, use the SmartPointers option.
Also, the check doesn't fire if any argument of the constructor call would be:
- a bit-field (bit-fields can't bind to rvalue/universal reference)
- a
newexpression (to avoid leak) - an argument that would be converted via derived-to-base cast.
This check requires C++11 or higher to run. โญ
Options
ContainersWithPushBackโ semicolon-separated list of class names of custom containers that supportpush_back.ContainersWithPushโ semicolon-separated list of class names of custom containers that supportpush.ContainersWithPushFrontโ semicolon-separated list of class names of custom containers that supportpush_front.IgnoreImplicitConstructorsโ whentrue, the check ignores implicitly constructed arguments ofpush_back, e.g.
std::vector<std::string> v;
v.push_back("a"); // Ignored when IgnoreImplicitConstructors is `true`.
Default is false.
SmartPointersโ semicolon-separated list of class names of custom smart pointers.TupleTypesโ semicolon-separated list ofstd::tuple-like class names.TupleMakeFunctionsโ semicolon-separated list ofstd::make_tuple-like function names. Those function calls get removed frompush_backcalls and turned intoemplace_back.EmplacyFunctionsโ semicolon-separated list of containers without their template parameters, plus some emplace-like method of the container. Example:vector::emplace_back. Those methods get checked for improper use, and the check reports when a temporary is unnecessarily created. All STL containers with such member functions are supported by default. ๐ฅ
Example
std::vector<MyTuple<int, bool, char>> x;
x.push_back(MakeMyTuple(1, false, 'x'));
x.emplace_back(MakeMyTuple(1, false, 'x'));
transforms to:
std::vector<MyTuple<int, bool, char>> x;
x.emplace_back(1, false, 'x');
x.emplace_back(1, false, 'x');
when TupleTypes is set to MyTuple, TupleMakeFunctions is set to MakeMyTuple, and EmplacyFunctions is set to vector::emplace_back. Chef's kiss ๐ค
modernize-use-equals-default
This icon replaces default bodies of special member functions with = default;. Why do we care? Because explicitly defaulted function declarations enable more opportunities in optimization โ the compiler might treat explicitly defaulted functions as trivial. Main character energy for your copy constructors ๐
struct A {
A() {}
~A();
};
A::~A() {}
// becomes
struct A {
A() = default;
~A();
};
A::~A() = default;
Note: move-constructor and move-assignment operator are not supported yet. ๐ญ Soon, hopefully.
Options
IgnoreMacrosโ if set totrue, the check won't give warnings inside macros and will ignore special members whose bodies contain macros or preprocessor directives. Default istrue.
modernize-use-equals-delete
This check identifies unimplemented private special member functions and recommends using = delete for them instead. Bonus: it also recommends relocating any deleted member function from the private section to the public section. Glow-up AND a relocation ๐
Before C++11, the primary method to effectively "erase" a particular function was declaring it private without providing a definition. That approach would result in either a compiler error (when attempting to call a private function) or a linker error (due to an undefined reference). Truly the dark ages ๐
...but then C++11 showed up and, ngl, a way more conventional approach emerged for pulling off this exact same thing โ you just flag those functions as = delete and keep them chillin' in the public section of the class. main character energy fr fr. ๐
So that this check doesn't go around accusing innocent code of stuff (false positives, we hate her), it's only active inside a translation unit where literally all the other member functions have been implemented. And heads up: the fixes it generates are partial โ it'll drop the = delete in for you, but you are the one responsible for manually relocating those functions into the public section. no cap, that bit's on you ๐ซถ
// Example: bad
class A {
private:
A(const A&);
A& operator=(const A&);
};
// Example: good
class A {
public:
A(const A&) = delete;
A& operator=(const A&) = delete;
};
IgnoreMacrosโ if you set this totrue(andtrueis the default, bestie), the check will simply not warn about functions declared inside macros. ๐
modernize-use-integer-sign-comparison
Swaps comparisons between signed and unsigned integers for their safe C++20 std::cmp_* alternative โ if one's available. โจ
Important vibe check though: the check only hands you a replacement when you're on C++20 or later. Otherwise it just highlights the problem and expects you to fix it manually. sorry, that one's a DIY situation ๐ญ
Examples of the fixes this check creates:
unsigned int func(int a, unsigned int b) {
return a == b;
}
becomes
#include <utility>
unsigned int func(int a, unsigned int b) {
return std::cmp_equal(a, b);
}
Options ๐
IncludeStyleโ a string telling the check which include-style you're on:llvmorgoogle. Default isllvm.EnableQtSupportโ makes the C++17q20::cmp_*alternative available for Qt-based applications. Default isfalse.
modernize-use-nodiscard
Adds [[nodiscard]] attributes (C++17 introduced those, hi ๐) to member functions so that the compiler can call out at compile time which return values you should not be ignoring. very "the compiler is watching" of it. ๐ฅ
For a member function to even be considered by this check, it has to tick all of these boxes:
- no
[[nodiscard]],[[noreturn]],__attribute__((warn_unused_result)),[[clang::warn_unused_result]]nor[[gcc::warn_unused_result]]attribute, - non-void return type,
- non-template return types,
- const member function,
- non-variadic functions,
- no non-const reference parameters,
- no pointer parameters,
- no template parameters,
- no template function parameters,
- not be a member of a class with mutable member variables,
- no Lambdas,
- no conversion functions.
The logic is that functions like these have no means of altering any state or passing values other than via the return type โ unless they're altering state through some external call (e.g. I/O). ๐
Example ๐ธ
bool empty() const;
bool empty(int i) const;
transforms to:
[[nodiscard]] bool empty() const;
[[nodiscard]] bool empty(int i) const;
Options ๐
ReplacementStringโ specifies a macro to use instead of[[nodiscard]]. this is the move when you're maintaining source code that still has to compile with a pre-C++17 compiler. ๐
Example ๐ธ
bool empty() const;
bool empty(int i) const;
transforms to:
NO_DISCARD bool empty() const;
NO_DISCARD bool empty(int i) const;
...if the ReplacementString option is set to NO_DISCARD.
Note: if the ReplacementString is not a C++ attribute, but instead a macro, then that macro must be defined in scope or the fix-it will not be applied. ๐ฌ
Note: for alternative__attribute__syntax options to mark functions as[[nodiscard]]in non-c++17 source code, seehttps://clang.llvm.org/docs/AttributeReference.html#nodiscard-warn-unused-result
modernize-use-noexcept
This check replaces deprecated dynamic exception specifications with the appropriate noexcept specification (C++11 said hi โจ). By default it replaces throw() with noexcept, and throw(<exception>[,...]) or throw(...) with noexcept(false).
Example ๐ธ
void foo() throw();
void bar() throw(int) {}
transforms to:
void foo() noexcept;
void bar() noexcept(false) {}
Options ๐
ReplacementStringโ lets you specify a macro to use instead ofnoexcept. this is useful when you're maintaining source code that uses custom exception specification marking other thannoexcept. heads up: fix-it hints will only be generated for non-throwing specifications.
Example ๐ธ
void bar() throw(int);
void foo() throw();
transforms to:
void bar() throw(int); // No fix-it generated.
void foo() NOEXCEPT;
...if the ReplacementString option is set to NOEXCEPT.
UseNoexceptFalseโ enabled by default; disabling it will generate fix-it hints that remove throwing dynamic exception specs, e.g.throw(<something>), completely, without providing a replacement text โ except for destructors and delete operators that arenoexcept(true)by default. ๐
Example ๐ธ
void foo() throw(int) {}
struct bar {
void foobar() throw(int);
void operator delete(void *ptr) throw(int);
void operator delete[](void *ptr) throw(int);
~bar() throw(int);
};
transforms to:
void foo() {}
struct bar {
void foobar();
void operator delete(void *ptr) noexcept(false);
void operator delete[](void *ptr) noexcept(false);
~bar() noexcept(false);
};
...if the UseNoexceptFalse option is set to false.
modernize-use-nullptr
This check converts the usage of null pointer constants (e.g. NULL, 0) over to the new C++11 and C23 nullptr keyword. glow-up for your pointers, honestly. โญ
Example ๐ธ
void assignment() {
char *a = NULL;
char *b = 0;
char c = 0;
}
int *ret_ptr() {
return 0;
}
transforms to:
void assignment() {
char *a = nullptr;
char *b = nullptr;
char c = 0;
}
int *ret_ptr() {
return nullptr;
}
Options ๐
IgnoredTypesโ semicolon-separated list of regular expressions matching pointer types for which implicit casts will be ignored. Default value:std::_CmpUnspecifiedParam::;^std::__cmp_cat::__unspecNullMacrosโ comma-separated list of macro names that get transformed along withNULL. By default the check only replaces theNULLmacro and skips any similar user-defined macros. ๐ โโ๏ธ
Example ๐ธ
#define MY_NULL (void*)0
void assignment() {
void *p = MY_NULL;
}
transforms to:
#define MY_NULL NULL
void assignment() {
int *p = nullptr;
}
...if the NullMacros option is set to MY_NULL.
modernize-use-override
Adds override (C++11 introduced it โจ) to overridden virtual functions, and removes virtual from those same functions since it's not required anymore. slay, less typing.
Real talk: virtual on non base class implementations used to be the way you'd hint to the user that a function was virtual. C++ compilers did not use the presence of it to signify an overridden function. nope.
Then C++11 introduced the override and final keywords so overridden functions could be marked appropriately, and their presence lets compilers verify that an overridden function actually, correctly overrides a base class implementation. ๐
This is genuinely useful, because compilers can generate a compile time error when:
- the base class implementation function signature changes,
- the user has not created the override with the correct signature.
Options ๐
IgnoreDestructorsโ if set totrue, this check will not diagnose destructors. Default isfalse.IgnoreTemplateInstantiationsโ if set totrue, tells the check to ignore virtual function overrides that are part of template instantiations. Default isfalse.AllowOverrideAndFinalโ if set totrue, the check will not diagnoseoverrideas redundant withfinal. useful when code gets compiled by a compiler with warning/error checking flags that explicitly requireoverrideon overridden members, likegcc -Wsuggest-override/gcc -Werror=suggest-override. Default isfalse.OverrideSpellingโ specifies a macro to use instead ofoverride. useful when maintaining source code that also needs to compile with a pre-C++11 compiler.FinalSpellingโ specifies a macro to use instead offinal. same deal, useful when maintaining source code that also needs to compile with a pre-C++11 compiler. ๐
Note: for more information on the use ofoverride, seehttps://en.cppreference.com/w/cpp/language/override
modernize-use-ranges
Detects calls to standard library iterator algorithms that could be swapped out for a ranges version instead. lowkey the best kind of glow-up: same behavior, way cuter syntax. ๐ฆ
Example ๐ธ
auto Iter1 = std::find(Items.begin(), Items.end(), 0);
auto AreSame = std::equal(Items1.cbegin(), Items1.cend(),
std::begin(Items2), std::end(Items2));
Transforms to:
auto Iter1 = std::ranges::find(Items, 0);
auto AreSame = std::ranges::equal(Items1, Items2);
Supported algorithms ๐
Calls to the following std library algorithms get checked:
std::adjacent_find, std::all_of, std::any_of, std::binary_search, std::copy_backward, std::copy_if, std::copy, std::destroy, std::equal_range, std::equal, std::fill, std::find_end, std::find_if_not, std::find_if, std::find, std::for_each, std::generate, std::includes, std::inplace_merge, std::iota, std::is_heap_until, std::is_heap, std::is_partitioned, std::is_permutation, std::is_sorted_until, std::is_sorted, std::lexicographical_compare, std::lower_bound, std::make_heap, std::max_element, std::merge, std::min_element, std::minmax_element, std::mismatch, std::move_backward, std::move, std::next_permutation, std::none_of, std::partial_sort_copy, std::partition_copy, std::partition_point, std::partition, std::pop_heap, std::prev_permutation, std::push_heap, std::remove_copy_if, std::remove_copy, std::remove, std::remove_if, std::replace_if, std::replace, std::reverse_copy, std::reverse, std::rotate, std::rotate_copy, std::sample, std::search, std::set_difference, std::set_intersection, std::set_symmetric_difference, std::set_union, std::shift_left, std::shift_right, std::sort_heap, std::sort, std::stable_partition, std::stable_sort, std::transform, std::uninitialized_copy, std::uninitialized_default_construct, std::uninitialized_fill, std::uninitialized_move, std::uninitialized_value_construct, std::unique_copy, std::unique, std::upper_bound.
Note incoming, bestie: some range algorithms for vector<bool> need C++23 because it uses proxy iterators. That's just how it is, no notes. ๐
Reverse Iteration
So if calls are made using reverse iterators on containers, the code gets fixed using the std::views::reverse adaptor. Like:
auto AreSame = std::equal(Items1.rbegin(), Items1.rend(),
std::crbegin(Items2), std::crend(Items2));
Transforms to:
auto AreSame = std::ranges::equal(std::ranges::reverse_view(Items1),
std::ranges::reverse_view(Items2));
Options
IncludeStyleโ a string saying which include-style is in play,llvmorgoogle. Default isllvm. ๐UseReversePipeโ whentrue(defaultfalse), fixes that involve reverse ranges use the pipe adaptor syntax instead of the function syntax. So this:
std::find(Items.rbegin(), Items.rend(), 0);
Transforms to:
std::ranges::find(Items | std::views::reverse, 0);
modernize-use-scoped-lock
Okay so this one finds uses of std::lock_guard and is like "bestieโฆ C++17 gave us std::scoped_lock, let's use that instead." โจ
Fix-its are provided for single declarations of std::lock_guard, and a warning is emitted for multiple declarations of std::lock_guard that can be replaced with a single declaration of std::scoped_lock.
Examples
Single std::lock_guard declaration:
std::mutex M;
std::lock_guard<std::mutex> L(M);
Transforms to:
std::mutex M;
std::scoped_lock L(M);
Single std::lock_guard declaration with std::adopt_lock:
std::mutex M;
std::lock(M);
std::lock_guard<std::mutex> L(M, std::adopt_lock);
Transforms to:
std::mutex M;
std::lock(M);
std::scoped_lock L(std::adopt_lock, M);
Multiple std::lock_guard declarations only emit warnings:
std::mutex M1, M2;
std::lock(M1, M2);
std::lock_guard Lock1(M, std::adopt_lock); // warning: use single 'std::scoped_lock' instead of multiple 'std::lock_guard'
std::lock_guard Lock2(M, std::adopt_lock); // note: additional 'std::lock_guard' declared here
Limitations
The check will not emit warnings if std::lock_guard is used implicitly via template parameter:
template <template <typename> typename Lock>
void TemplatedLock() {
std::mutex M;
Lock<std::mutex> L(M); // no warning
}
void instantiate() {
TemplatedLock<std::lock_guard>();
}
Options
WarnOnSingleLocksโ whentrue, the check warns on singlestd::lock_guarddeclarations. Set this option tofalseif you want warnings only on multiplestd::lock_guarddeclarations that can be replaced with a singlestd::scoped_lock. Default istrue. ๐ซถWarnOnUsingAndTypedefโ whentrue, the check emits warnings ifstd::lock_guardis used in ausingortypedefcontext. Default istrue.
template <typename T>
using Lock = std::lock_guard<T>; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'
using LockMutex = std::lock_guard<std::mutex>; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'
typedef std::lock_guard<std::mutex> LockDef; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'
using std::lock_guard; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'
modernize-use-starts-ends-with
This icon checks for the common roundabout ways people express starts_with and ends_with and is like "girl, just use the simpler method, it's available." Notably, this will work with std::string and std::string_view. ๐ฆ
Covered scenarios, one by one, no skips:
u.find(v) == 0โu.starts_with(v)u.find(v, 0) == 0โu.starts_with(v)u.find(v, 0, v.size()) == 0โu.starts_with(v)u.rfind(v, 0) != 0โ!u.starts_with(v)u.rfind(v, 0, v.size()) != 0โ!u.starts_with(v)u.compare(0, v.size(), v) == 0โu.starts_with(v)u.substr(0, v.size()) == vโu.starts_with(v)v != u.substr(0, v.size())โ!u.starts_with(v)u.compare(u.size() - v.size(), v.size(), v) == 0โu.ends_with(v)u.rfind(v) == u.size() - v.size()โu.ends_with(v)
modernize-use-std-format
Converts calls to absl::StrFormat โ or other functions, via configuration options โ to C++20's std::format, or another function via a configuration option. It modifies the format string appropriately and removes now-unnecessary calls to std::string::c_str() and std::string::data(). Obsessed, honestly. ๐ฅ
For example, it turns lines like
return absl::StrFormat("The %s is %3d", description.c_str(), value);
into:
return std::format("The {} is {:3}", description, value);
The check uses the same format-string-conversion algorithm as modernize-use-std-print <../modernize/use-std-print.html> and its shortcomings and behaviour in combination with macros are described in the documentation for that check. iykyk ๐
Options
StrictModeโ whentrue, the check will add casts when converting from variadic functions and printing signed or unsigned integer types (including fixed-width integer types from<cstdint>,ptrdiff_t,size_tandssize_t) as the opposite signedness to ensure that the output would matches that of a simple wrapper forstd::sprintfthat accepted a C-style variable argument list.
For example, with StrictMode enabled,
extern std::string strprintf(const char *format, ...);
int i = -42;
unsigned int u = 0xffffffff;
return strprintf("%u %d\n", i, u);
would be converted to
return std::format("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));
to ensure that the output will continue to be the unsigned representation of -42 and the signed representation of 0xffffffff (often 4294967254 and -1 respectively). When false (which is the default), these casts will not be added which may cause a change in the output. Note that this option makes no difference for the default value of StrFormatLikeFunctions since absl::StrFormat takes a function parameter pack and is not a variadic function. ๐ญ
StrFormatLikeFunctionsโ a semicolon-separated list of (fully qualified) function names to replace, with the requirement that the first parameter contains the printf-style format string and the arguments to be formatted follow immediately afterwards. Qualified member function names are supported, but the replacement function name must be unqualified. The default value for this option isabsl::StrFormat. ๐ReplacementFormatFunctionโ the function that will be used to replace the function set by theStrFormatLikeFunctionsoption rather than the defaultstd::format. It is expected that the function provides an interface that is compatible withstd::format. A suitable candidate would befmt::format. โจFormatHeaderโ the header that must be included for the declaration ofReplacementFormatFunctionso that a#includedirective can be added if required. IfReplacementFormatFunctionisstd::formatthen this option will default to<format>, otherwise this option will default to nothing and no#includedirective will be added. ๐ธ
modernize-use-std-numbers
Finds constants and function calls to math functions that can be replaced with C++20's mathematical constants from the numbers header and offers fix-it hints. Does not match the use of variables with that value โ instead, it offers a replacement for the definition of those variables. Function calls that match the pattern of how the constant is calculated are matched and replaced with the std::numbers constant. The use of macros gets replaced with the corresponding std::numbers constant, instead of changing the macro definition. Big brain energy fr fr. ๐ง โจ
The following list of constants from the numbers header are supported:
elog2elog10epiinv_piinv_sqrtpiln2ln10sqrt2sqrt3inv_sqrt3egammaphi
The list currently includes all constants as of C++20. ๐
The replacements use the type of the matched constant and can remove explicit casts, i.e., switching between std::numbers::e, std::numbers::e_v<float> and std::numbers::e_v<long double> where appropriate. ๐
double sqrt(double);
double log2(double);
void sink(auto&&) {}
void floatSink(float);
#define MY_PI 3.1415926
void foo() {
const double Pi = 3.141592653589; // const double Pi = std::numbers::pi
const auto Use = Pi / 2; // no match for Pi
static constexpr double Euler = 2.7182818; // static constexpr double Euler = std::numbers::e;
log2(exp(1)); // std::numbers::log2e;
log2(Euler); // std::numbers::log2e;
1 / sqrt(MY_PI); // std::numbers::inv_sqrtpi;
sink(MY_PI); // sink(std::numbers::pi);
floatSink(MY_PI); // floatSink(std::numbers::pi);
floatSink(static_cast<float>(MY_PI)); // floatSink(std::numbers::pi_v<float>);
}
โจ Options โจ
DiffThresholdโ a floating point value that sets the detection threshold for when literals match a constant. A literal matches a constant ifabs(literal - constant) < DiffThresholdevaluates to true. Default is0.001. ๐ฏIncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm. ๐
๐ modernize-use-std-print ๐
Converts calls to printf, fprintf, absl::PrintF and absl::FPrintf to equivalent calls to C++23's std::print or std::println as appropriate, modifying the format string appropriately. The replaced and replacement functions can be customised by configuration options. Each argument that is the result of a call to std::string::c_str() and std::string::data() will have that now-unnecessary call removed in a similar manner to the readability-redundant-string-cstr check. ๐
In other words, it turns lines like:
fprintf(stderr, "The %s is %3d\n", description.c_str(), value);
into:
std::println(stderr, "The {} is {:3}", description, value);
If the ReplacementPrintFunction or ReplacementPrintlnFunction options are left at or set to their default values then this check is only enabled with -std=c++23 or later. โญ
Macros starting with PRI and __PRI from <inttypes.h> are expanded, escaping is handled and adjacent strings are concatenated to form a single StringLiteral before the format string is converted. Use of any other macros in the format string will cause a warning message to be emitted and no conversion will be performed. The converted format string will always be a single string literal. ๐ง
The check doesn't do a bad job, but it's not perfect. In particular: ๐
- It assumes that the format string is correct for the arguments. If you get any warnings when compiling with
-Wformatthen misbehaviour is possible. ๐ - At the point that the check runs, the AST contains a single
StringLiteralfor the format string where escapes have been expanded. The check tries to reconstruct escape sequences, they may not be the same as they were written (e.g."\x41\x0a"will become"A\n"and"ab""cd"will become"abcd".) ๐ญ - It supports field widths, precision, positional arguments, leading zeros, leading
+, alignment and alternative forms. โจ - Use of any unsupported flags or specifiers will cause the entire statement to be left alone and a warning to be emitted. Particular unsupported features are:
- The
%'flag for thousands separators. ๐ - The glibc extension
%m. ๐
- The
printfand similar functions return the number of characters printed.std::printdoes not. This means that any invocations that use the return value will not be converted. Unfortunately this currently includes explicitly-casting tovoid. Deficiencies in this check mean that any invocations inside GCC compound statements cannot be converted even if the resulting value is not used. ๐ค
If conversion would be incomplete or unsafe then the entire invocation will be left unchanged. ๐ซถ
If the call is deemed suitable for conversion then:
printf,fprintf,absl::PrintF,absl::FPrintFand any functions specified by thePrintfLikeFunctionsoption orFprintfLikeFunctionsare replaced with the function specified by theReplacementPrintlnFunctionoption if the format string ends with\norReplacementPrintFunctionotherwise.- the format string is rewritten to use the
std::formatterlanguage. If a\nis found at the end of the format string not preceded byrthen it is removed andReplacementPrintlnFunctionis used rather thanReplacementPrintFunction. - any arguments that corresponded to
%pspecifiers thatstd::formatterwouldn't accept are wrapped in astatic_casttoconst void *. - any arguments that corresponded to
%sspecifiers where the argument is ofsigned charorunsigned chartype are wrapped in areinterpret_cast<const char *>. - any arguments where the format string and the parameter differ in signedness will be wrapped in an appropriate
static_castifStrictModeis enabled. - any arguments that end in a call to
std::string::c_str()orstd::string::data()will have that call removed.
โจ Options โจ
StrictModeโ when true, the check will add casts when converting from variadic functions likeprintfand printing signed or unsigned integer types (including fixed-width integer types from<cstdint>,ptrdiff_t,size_tandssize_t) as the opposite signedness to ensure that the output matches that ofprintf. This does not apply when converting from non-variadic functions such asabsl::PrintFandfmt::printf. For example, withStrictModeenabled:
int i = -42;
unsigned int u = 0xffffffff;
printf("%u %d\n", i, u);
would be converted to:
std::print("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));
to ensure that the output will continue to be the unsigned representation of -42 and the signed representation of 0xffffffff (often 4294967254 and -1 respectively.) When false (which is the default), these casts will not be added which may cause a change in the output. ๐ฌ
PrintfLikeFunctionsโ a semicolon-separated list of (fully qualified) function names to replace, with the requirement that the first parameter contains the printf-style format string and the arguments to be formatted follow immediately afterwards. Qualified member function names are supported, but the replacement function name must be unqualified. If neither this option norFprintfLikeFunctionsare set then the default value for this option isprintf; absl::PrintF, otherwise it is empty. ๐FprintfLikeFunctionsโ a semicolon-separated list of (fully qualified) function names to replace, with the requirement that the first parameter is retained, the second parameter contains the printf-style format string and the arguments to be formatted follow immediately afterwards. Qualified member function names are supported, but the replacement function name must be unqualified. If neither this option norPrintfLikeFunctionsare set then the default value for this option isfprintf; absl::FPrintF, otherwise it is empty. ๐ReplacementPrintFunctionโ the function that will be used to replaceprintf,fprintfetc. during conversion rather than the defaultstd::printwhen the originalformat string does not end with\n. It is expected that the function provides an interface that is compatible withstd::print. A suitable candidate would befmt::print. ๐ReplacementPrintlnFunctionโ the function that will be used to replaceprintf,fprintfetc. during conversion rather than the defaultstd::printlnwhen the original format string ends with\n. It is expected that the function provides an interface that is compatible withstd::println. A suitable candidate would befmt::println. ๐PrintHeaderโ the header that must be included for the declaration ofReplacementPrintFunctionso that a#includedirective can be added if required. IfReplacementPrintFunctionisstd::printthen this option will default to<print>, otherwise this option will default to nothing and no#includedirective will be added. ๐
๐ฆ modernize-use-trailing-return-type ๐ฆ
Rewrites function and lambda signatures to use a trailing return type (introduced in C++11). This transformation is purely stylistic. The return type before the function name is replaced by auto and inserted after the function parameter list (and qualifiers). ๐ซ
๐ธ Example ๐ธ
int f1();
inline int f2(int arg) noexcept;
virtual float f3() const && = delete;
auto lambda = []() {};
transforms to:
auto f1() -> int;
inline auto f2(int arg) -> int noexcept;
virtual auto f3() const && -> float = delete;
auto lambda = []() -> void {};
๐ Known Limitations ๐
The following categories of return types cannot be rewritten currently: ๐ญ
- function pointers
- member function pointers
- member pointers
Unqualified names in the return type might erroneously refer to different entities after the rewrite. Preventing such errors requires a full lookup of all unqualified names present in the return type in the scope of the trailing return type location. This location includes e.g. function parameter names and members of the enclosing class (including all inherited classes). Such a lookup is currently not implemented. ๐ค
Given the following piece of code
struct S { long long value; };
S f(unsigned S) { return {S * 2}; }
class CC {
int S;
struct S m();
};
S CC::m() { return {0}; }
a careless rewrite would produce the following output:
struct S { long long value; };
auto f(unsigned S) -> S { return {S * 2}; } // error
class CC {
int S;
auto m() -> struct S;
};
auto CC::m() -> S { return {0}; } // error
This code fails to compile because the S in the context of f refers to the equally named function parameter. Similarly, the S in the context of m refers to the equally named class member. The check can currently only detect and avoid a clash with a function parameter name. ๐
โจ Options โจ
TransformFunctionsโ when set to true, function declarations will be transformed to use trailing return. Default istrue. โญallโ every single lambda without an explicit return type gets transformed to use a trailing return type. If the type can't be deduced,autowill be used since C++14, and a generic message will be emitted otherwise. (iykyk)all_except_autoโ transform all lambdas except the ones whose return type can't be deduced.noneโ do not transform any lambda expressions. Hands off. ๐
Default is all. ๐
โจ modernize-use-transparent-functors โจ
Prefer transparent functors to non-transparent ones, bestie. When you use transparent functors, the type does not need to be repeated โ and honestly? The code is easier to read, easier to maintain, and way less prone to errors. Bonus: it is not possible to introduce unwanted conversions. Periodt. ๐
// Non-transparent functor
std::map<int, std::string, std::greater<int>> s;
// Transparent functor.
std::map<int, std::string, std::greater<>> s;
// Non-transparent functor
using MyFunctor = std::less<MyType>;
It is not always a safe transformation though, so lowkey be careful. The following case will be untouched, to preserve the semantics:
// Non-transparent functor
std::map<const char *, std::string, std::greater<std::string>> s;
Options
SafeModeโ if this option is set totrue, the check will not diagnose cases where using a transparent functor cannot be guaranteed to produce identical results as the original code. The default value for this option isfalse.
This check requires using C++14 or higher to run. ๐
๐ธ modernize-use-uncaught-exceptions ๐ธ
This check will warn on calls to std::uncaught_exception and replace them with calls to std::uncaught_exceptions, since std::uncaught_exception was deprecated in C++17. Rip to a real one ๐ญ
Below are a few examples of what kind of occurrences will be found and what they will be replaced with:
#define MACRO1 std::uncaught_exception
#define MACRO2 std::uncaught_exception
int uncaught_exception() {
return 0;
}
int main() {
int res;
res = uncaught_exception();
// No warning, since it is not the deprecated function from namespace std
res = MACRO2();
// Warning, but will not be replaced
res = std::uncaught_exception();
// Warning and replaced
using std::uncaught_exception;
// Warning and replaced
res = uncaught_exception();
// Warning and replaced
}
After applying the fixes the code will look like the following:
#define MACRO1 std::uncaught_exception
#define MACRO2 std::uncaught_exception
int uncaught_exception() {
return 0;
}
int main() {
int res;
res = uncaught_exception();
res = MACRO2();
res = std::uncaught_exceptions();
using std::uncaught_exceptions;
res = uncaught_exceptions();
}
๐ฆ modernize-use-using ๐ฆ
The check converts the usage of typedef with the using keyword. Glow-up arc fr fr. โจ
Before:
typedef int variable;
class Class{};
typedef void (Class::* MyPtrType)() const;
typedef struct { int a; } R_t, *R_p;
After:
using variable = int;
class Class{};
using MyPtrType = void (Class::*)() const;
using R_t = struct { int a; };
using R_p = R_t*;
The checker ignores typedef within extern "C" { ... } blocks. Like, it just minds its own business there.
extern "C" {
typedef int InExternC; // Left intact.
}
This check requires using C++11 or higher to run. ๐ซถ
Options
IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.IgnoreExternCโ if set totrue, the check will not give warning insideextern "C"scope. Default isfalse.
๐ mpi-buffer-deref ๐
This check verifies if a buffer passed to an MPI (Message Passing Interface) function is sufficiently dereferenced. Buffers should be passed as a single pointer or array. But here's the thing โ since MPI function signatures specify void * for their buffer types, insufficiently dereferenced buffers can be passed, like for example as double pointers or multidimensional arrays, without a compiler warning emitted. The compiler says nothing. The compiler is silent. Not me finding out the hard way ๐ญ
Examples:
// A double pointer is passed to the MPI function.
char *buf;
MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
// A multidimensional array is passed to the MPI function.
short buf[1][1];
MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD);
// A pointer to an array is passed to the MPI function.
short *buf[1];
MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD);
๐ mpi-type-mismatch ๐
This check verifies if buffer type and MPI (Message Passing Interface) datatype pairs match for used MPI functions. All MPI datatypes defined by the MPI standard (3.1) are verified by this check. User defined typedefs, custom MPI datatypes and null pointer constants are skipped, in the course of verification. It's giving "not my department." ๐
Example:
// In this case, the buffer type matches MPI datatype.
char buf;
MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
// In the following case, the buffer type does not match MPI datatype.
int buf;
MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
๐ objc-assert-equals ๐
Finds improper usages of XCTAssertEqual and XCTAssertNotEqual and replaces them with XCTAssertEqualObjects or XCTAssertNotEqualObjects.
This makes tests less fragile, as many improperly rely on pointer equality for strings that have equal values. And that assumption is not guaranteed by the language, bestie โ it's giving false confidence. ๐
๐ objc-avoid-nserror-init ๐
Finds improper initialization of NSError objects.
According to Apple developer document, we should always use factory method errorWithDomain:code:userInfo: to create new NSError objects instead of [NSError alloc] init]. Otherwise it will lead to a warning message during runtime. Yeah. Don't do it. ๐
The corresponding information about NSError creation: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html
๐ objc-dealloc-in-category ๐
Finds implementations of -dealloc in Objective-C categories. The category implementation will override any -dealloc in the class implementation, potentially causing issues. Big yikes energy. ๐
Classes implement -dealloc to perform important actions to deallocate an object. If a category on the class implements -dealloc, it will override the class's implementation and unexpected deallocation behavior may occur. Not the vibe.
๐ง objc-forbidden-subclassing ๐ง
Finds Objective-C classes which are subclasses of classes which are not designed to be subclassed. Like, they literally told you no. ๐ญ
By default, includes a list of Objective-C classes which are publicly documented as not supporting subclassing.
Note: Instead of using this check, for code under your control, you should add__attribute__((objc_subclassing_restricted))before your@interfacedeclarations to ensure the compiler prevents others from subclassing your Objective-C classes. Seehttps://clang.llvm.org/docs/AttributeReference.html#objc-subclassing-restricted
Options
ForbiddenSuperClassNamesโ semicolon-separated list of names of Objective-C classes which do not support subclassing.
Defaults to ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView.
๐ฆ objc-missing-hash ๐ฆ
Finds Objective-C implementations that implement -isEqual: without also appropriately implementing -hash.
Apple documentation highlights that objects that are equal must have the same hash value: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
Note that the check only verifies the presence of -hash in scenarios where its omission could result in unexpected behavior. The verification of the implementation of -hash is the responsibility of the developer, e.g., through the addition of unit tests to verify the implementation. So like, it checks that you showed up โ it's not grading your work. ๐
๐จ objc-nsdate-formatter ๐จ
When NSDateFormatter is used to convert an NSDate type to a String type, the user can specify a custom format string. Certain format specifiers are undesirable despite being legal. See http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns for all legal date patterns.
This checker reports as warnings the following string patterns in a date format specifier:
yyyy+ww: Calendar year specified with week of a week year (unlessYYYYis also specified).- Example 1: Input Date: 29 December 2014 ; Format String:
yyyy-ww; Output string:2014-01(Wrong because it's not the first week of 2014) - Example 2: Input Date: 29 December 2014 ; Format String:
dd-MM-yyyy(ww-YYYY); Output string:29-12-2014(01-2015) (This is correct)
- Example 1: Input Date: 29 December 2014 ; Format String:
Fwithoutee/EE: Numeric day of week in a month without actual day.- Example: Input Date: 29 December 2014 ; Format String:
F-MM; Output string:5-12(Wrong because it reads as 5th ___ of Dec in English)
- Example: Input Date: 29 December 2014 ; Format String:
FwithoutMM: Numeric day of week in a month without month.- Example: Input Date: 29 December 2014 ; Format String:
F-EEOutput string:5-Mon(Wrong because it reads as 5th Mon of ___ in English)
- Example: Input Date: 29 December 2014 ; Format String:
WWwithoutMM: Week of the month without the month.- Example: Input Date: 29 December 2014 ; Format String:
WW-yyyyOutput string:05-2014(Wrong because it reads as 5th Week of ___ in English)
- Example: Input Date: 29 December 2014 ; Format String:
YYYY+QQ: Week year specified with quarter of normal year (unlessyyyyis also specified).- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY-QQOutput string:2015-04(Wrong because it's not the 4th quarter of 2015) - Example 2: Input Date: 29 December 2014 ; Format String:
ww-YYYY(QQ-yyyy) Output string:01-2015(04-2014) (This is correct)
- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY+MM: Week year specified with Month of a calendar year (unlessyyyyis also specified).- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY-MMOutput string:2015-12(Wrong because it's not the 12th month of 2015) - Example 2: Input Date: 29 December 2014 ; Format String:
ww-YYYY(MM-yyyy) Output string:01-2015(12-2014) (This is correct)
- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY+DD: Week year with day of a calendar year (unlessyyyyis also specified).- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY-DDOutput string:2015-363(Wrong because it's not the 363rd day of 2015)
- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY+WWโ week year with week of a calendar year (unlessyyyyis also specified, bestie).- Example 1: Input Date:
29 December 2014; Format String:YYYY-WWOutput string:2015-05(wrong โ like, it's literally not the 5th week of 2015 ๐ญ) - Example 2: Input Date:
29 December 2014; Format String:ww-YYYY(WW-MM-yyyy) Output string:01-2015(05-12-2014) (correct, we love to see it โจ)
- Example 1: Input Date:
YYYY+Fโ week year with day of week in a calendar month (unlessyyyyis also specified).- Example 1: Input Date:
29 December 2014; Format String:YYYY-ww-F-EEOutput string:2015-01-5-Mon(wrong โ it is not the 5th Monday of January in 2015, no cap ๐) - Example 2: Input Date:
29 December 2014; Format String:ww-YYYY(F-EE-MM-yyyy) Output string:01-2015(5-Mon-12-2014) (correct ๐ซถ)
- Example 1: Input Date:
objc-nsinvocation-argument-lifetime</p>
Okay so this one catches calls to NSInvocation methods under ARC that don't have proper argument object lifetimes. Here's the tea: when you pass Objective-C objects as parameters to the NSInvocation methods getArgument:atIndex: and getReturnValue:, the values get copied by value into the argument pointer โ which leads to incorrect releasing behavior if the object pointers aren't declared __unsafe_unretained. ๐ฉ
For code:
id arg;
[invocation getArgument:&arg atIndex:2];
__strong id returnValue;
[invocation getReturnValue:&returnValue];
The fix will be:
__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:2];
__unsafe_unretained id returnValue;
[invocation getReturnValue:&returnValue];
The check will warn on being passed instance variable references that have lifetimes other than __unsafe_unretained, but it does not propose a fix โ you're on your own for that one, bestie:
// "id _returnValue" is declaration of instance variable of class.
[invocation getReturnValue:&self->_returnValue];
objc-property-declaration</p>
Finds property declarations in Objective-C files that don't follow the pattern of property names in Apple's programming guide. The property name should be in the format of Lower Camel Case. โจ
For code:
@property(nonatomic, assign) int LowerCamelCase;
The fix will be:
@property(nonatomic, assign) int lowerCamelCase;
The check will only fix 'CamelCase' to 'camelCase'. In some other cases we will only provide warning messages since the property name could be complicated. Users will need to come up with a proper name by their own. ๐ฌ
This check also accepts special acronyms as prefixes or suffixes. Such prefixes or suffixes will suppress the Lower Camel Case check according to the guide: <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-1002931-BBCFHEAB>
For a full list of well-known acronyms: <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE>
The corresponding style rule: <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-1001757>
The check will also accept a property declared in a category with a prefix of lowercase letters followed by a '_' to avoid naming conflict. For example:
@property(nonatomic, assign) int abc_lowerCamelCase;
The corresponding style rule: <https://developer.apple.com/library/content/qa/qa1908/_index.html>
objc-super-self</p>
Finds invocations of -self on super instances in initializers of subclasses of NSObject and recommends calling a superclass initializer instead. ๐
Invoking -self on super instances in initializers is a common programmer error when the programmer's original intent is to call a superclass initializer. Failing to call a superclass initializer breaks initializer chaining and can result in invalid object initialization. Fr fr, do not do this. ๐
openmp-exception-escape</p>
Analyzes OpenMP Structured Blocks and checks that no exception escapes out of the Structured Block it was thrown in. ๐ง
As per the OpenMP specification, a structured block is an executable statement, possibly compound, with a single entry at the top and a single exit at the bottom. Which means, throw may not be used to 'exit' out of the structured block. If an exception is not caught in the same structured block it was thrown in, the behavior is undefined. ๐ญ
FIXME: this check does not model SEH,setjmp/longjmp.
WARNING! This check may be expensive on large source files. Like, your compile time is gonna feel it. ๐ฅ
Options
IgnoredExceptionsโ comma-separated list containing type names which are not counted as thrown exceptions in the check. Default value is an empty string. ๐
openmp-use-default-none</p>
Finds OpenMP directives that are allowed to contain a default clause, but either don't specify it, or the clause is specified but with the kind other than none โ and suggests you use the default(none) clause. Slay. โจ
Using the default(none) clause forces developers to explicitly specify data sharing attributes for the variables referenced in the construct, thus making it obvious which variables are referenced, and what is their data sharing attribute โ thus increasing readability and possibly making errors easier to spot. It's giving "actually know what your variables are doing." ๐
Example
// ``for`` directive cannot have ``default`` clause, no diagnostics.
void n0(const int a) {
#pragma omp for
for (int b = 0; b < a; b++)
;
}
// ``parallel`` directive.
// ``parallel`` directive can have ``default`` clause, but said clause is not
// specified, diagnosed.
void p0_0() {
#pragma omp parallel
;
// WARNING: OpenMP directive ``parallel`` does not specify ``default``
// clause. Consider specifying ``default(none)`` clause.
}
// ``parallel`` directive can have ``default`` clause, and said clause is
// specified, with ``none`` kind, all good.
void p0_1() {
#pragma omp parallel default(none)
;
}
// ``parallel`` directive can have ``default`` clause, and said clause is
// specified, but with ``shared`` kind, which is not ``none``, diagnose.
void p0_2() {
#pragma omp parallel default(shared)
;
// WARNING: OpenMP directive ``parallel`` specifies ``default(shared)``
// clause. Consider using ``default(none)`` clause instead.
}
// ``parallel`` directive can have ``default`` clause, and said clause is
// specified, but with ``firstprivate`` kind, which is not ``none``, diagnose.
void p0_3() {
#pragma omp parallel default(firstprivate)
;
// WARNING: OpenMP directive ``parallel`` specifies ``default(firstprivate)``
// clause. Consider using ``default(none)`` clause instead.
}
performance-avoid-endl</p>
Checks for uses of std::endl on streams and suggests using the newline character '\n' instead. ๐ฆ
Rationale: Using std::endl on streams can be less efficient than using the newline character '\n' because std::endl performs two operations: it writes a newline character to the output stream and then flushes the stream buffer. Writing a single newline character using '\n' does not trigger a flush, which can improve performance. In addition, flushing the stream buffer can cause additional overhead when working with streams that are buffered. Highkey a free glow-up for your speed. ๐ฅ
Example:
Consider the following code:
#include <iostream>
int main() {
std::cout << "Hello" << std::endl;
}
Which gets transformed into:
#include <iostream>
int main() {
std::cout << "Hello" << '\n';
}
This code writes a single newline character to the std::cout stream without flushing the stream buffer. โจ
Additionally, it is important to note that the standard C++ streams (like std::cerr, std::wcerr, std::clog and std::wclog) always flush after a write operation, unless std::ios_base::sync_with_stdio is set to false โ regardless of whether std::endl or '\n' is used. Therefore, using '\n' with these streams will not result in any performance gain, but it is still recommended to use '\n' for consistency and readability. ๐
If you do need to flush the stream buffer, you can use std::flush explicitly like this:
#include <iostream>
int main() {
std::cout << "Hello\n" << std::flush;
}
performance-enum-size</p>
Recommends the smallest possible underlying type for an enum or enum class based on the range of its enumerators. It analyzes the values of the enumerators in an enum or enum class โ including signed values โ to recommend the smallest possible underlying type that can represent all the values of the enum. The suggested underlying types are the integral types std::uint8_t, std::uint16_t, and std::uint32_t for unsigned types, and std::int8_t, std::int16_t, and std::int32_t for signed types. Using the suggested underlying types can help reduce the memory footprint of the program and improve performance in some cases. Obsessed. ๐ซถ
For example:
// BEFORE
enum Color {
RED = -1,
GREEN = 0,
BLUE = 1
};
std::optional<Color> color_opt;
The Color enum uses the default underlying type, which is int in this case, and its enumerators have values of -1, 0, and 1. Additionally, the std::optional<Color> object uses 8 bytes due to padding (platform dependent). ๐ญ
// AFTER
enum Color : std::int8_t {
RED = -1,
GREEN = 0,
BLUE = 1
}
std::optional<Color> color_opt;
In the revised version of the Color enum, the underlying type has been changed to std::int8_t. The enumerator RED has a value of -1, which can be represented by a signed 8-bit integer. โจ
By using a smaller underlying type, the memory footprint of the Color enum is reduced from 4 bytes to 1 byte. The revised version of the std::optional<Color> object would only require 2 bytes (due to lack of padding), since it contains a single byte for the Color enum and a single byte for the bool flag that indicates whether the optional value is set. That's a glow-down in size and a glow-up in vibes. ๐
Reducing the memory footprint of an enum can have significant benefits in terms of memory usage and cache performance. However, it's important to consider the trade-offs and potential impact on code readability and maintainability. ๐
Enums without enumerators (empty) are excluded from analysis.
Requires C++11 or above. Does not provide auto-fixes. ๐
Options
EnumIgnoreListโ bestie, this one is for ignoring certain enum types. It accepts a semicolon-separated list of (fully qualified) enum type names or regular expressions that match the enum type names. The default value is an empty string, which means no enums will be ignored. ๐
performance-faster-string-find โจ
Optimize calls to std::string::find() and friends when the needle passed is a single character string literal. The character literal overload is more efficient. Itโs giving โwhy are you passing a whole string for one tiny charโ energy, and honestly? Valid.
Examples:
str.find("A");
// becomes
str.find('A');
Options
StringLikeClassesโ semicolon-separated list of names of string-like classes. By default only::std::basic_stringand::std::basic_string_vieware considered. The check will only consider member functions namedfind,rfind,find_first_of,find_first_not_of,find_last_of, orfind_last_not_ofwithin these classes.
performance-for-range-copy ๐
Finds C++11 for ranges where the loop variable is copied in each iteration but it would suffice to obtain it by const reference.
The check is only applied to loop variables of types that are expensive to copy which means they are not trivially copyable or have a non-trivial copy constructor or destructor.
To ensure that it is safe to replace the copy with a const reference the following heuristic is employed:
- The loop variable is const qualified.
- The loop variable is not const, but only const methods or operators are invoked on it, or it is used as const reference or value argument in constructors or function calls.
Options
WarnOnAllAutoCopiesโ when true, warns on any use ofautoas the type of the range-based for loop variable. Default isfalse.AllowedTypesโ a semicolon-separated list of names of types allowed to be copied in each iteration. Regular expressions are accepted, e.g.[Rr]ef(erence)?$matches every type with suffixRef,ref,Referenceandreference. The default is empty. If a name in the list contains the sequence::, it is matched against the qualified type name (i.e.namespace::Type), otherwise it is matched against only the type name (i.e.Type).
performance-implicit-cast-in-loop ๐
This check has been renamed to performance-implicit-conversion-in-loop <>.
performance-implicit-conversion-in-loop ๐ญ
This warning appears in a range-based loop with a loop variable of const ref type where the type of the variable does not match the one returned by the iterator. This means that an implicit conversion happens, which can for example result in expensive deep copies.
Example:
map<int, vector<string>> my_map;
for (const pair<int, vector<string>>& p : my_map) {}
// The iterator type is in fact pair<const int, vector<string>>, which means
// that the compiler added a conversion, resulting in a copy of the vectors.
The easiest solution is usually to use const auto& instead of writing the type manually. Trust.
performance-inefficient-algorithm ๐ฅ
Warns on inefficient use of STL algorithms on associative containers.
Associative containers implement some of the algorithms as methods which should be preferred to the algorithms in the algorithm header. The methods can take advantage of the order of the elements.
std::set<int> s;
auto it = std::find(s.begin(), s.end(), 43);
// becomes
auto it = s.find(43);
std::set<int> s;
auto c = std::count(s.begin(), s.end(), 43);
// becomes
auto c = s.count(43);
performance-inefficient-string-concatenation ๐
This check warns about the performance overhead arising from concatenating strings using the operator+, for instance:
std::string a("Foo"), b("Bar");
a = a + b;
Instead of this structure you should use operator+= or std::string's (std::basic_string) class member function append(). For instance:
std::string a("Foo"), b("Baz");
for (int i = 0; i < 20000; ++i) {
a = a + "Bar" + b;
}
Could be rewritten in a greatly more efficient way like:
std::string a("Foo"), b("Baz");
for (int i = 0; i < 20000; ++i) {
a.append("Bar").append(b);
}
And this can be rewritten too:
void f(const std::string&) {}
std::string a("Foo"), b("Baz");
void g() {
f(a + "Bar" + b);
}
In a slightly more efficient way like:
void f(const std::string&) {}
std::string a("Foo"), b("Baz");
void g() {
f(std::string(a).append("Bar").append(b));
}
Options
StrictModeโ whenfalse, the check will only check the string usage inwhile,forand for-range statements. Default isfalse.
performance-inefficient-vector-operation ๐ฆ
Finds possible inefficient std::vector operations (e.g. push_back, emplace_back) that may cause unnecessary memory reallocations.
It can also find calls that add element to protobuf repeated field in a loop without calling Reserve() before the loop. Calling Reserve() first can avoid unnecessary memory reallocations.
Currently, the check only detects following kinds of loops with a single statement body:
- Counter-based for loops start with 0:
std::vector<int> v;
for (int i = 0; i < n; ++i) {
v.push_back(n);
// This will trigger the warning since the push_back may cause multiple
// memory reallocations in v. This can be avoid by inserting a 'reserve(n)'
// statement before the for statement.
}
SomeProto p;
for (int i = 0; i < n; ++i) {
p.add_xxx(n);
// This will trigger the warning since the add_xxx may cause multiple memory
// reallocations. This can be avoid by inserting a
// 'p.mutable_xxx().Reserve(n)' statement before the for statement.
}
- For-range loops like
for (range-declaration : range_expression), the type ofrange_expressioncan bestd::vector,std::array,std::deque,std::set,std::unordered_set,std::map,std::unordered_set:
std::vector<int> data;
std::vector<int> v;
for (auto element : data) {
v.push_back(element);
// This will trigger the warning since the 'push_back' may cause multiple
// memory reallocations in v. This can be avoid by inserting a
// 'reserve(data.size())' statement before the for statement.
}
Options
VectorLikeClassesโ semicolon-separated list of names of vector-like classes. By default only::std::vectoris considered.EnableProtoโ when true, the check will also warn on inefficient operations for proto repeated fields. Otherwise, the check only warns on inefficient vector operations. Default isfalse.
performance-move-const-arg ๐
The check warns:
- if
std::move()is called with a constant argument, - if
std::move()is called with an argument of a trivially-copyable type, - if the result of
std::move()is passed as a const reference argument.
In all three cases, the check will suggest a fix that removes the std::move().
Here are examples of each of the three cases:
const string s;
return std::move(s); // Warning: std::move of the const variable has no effect
int x;
return std::move(x); // Warning: std::move of the variable of a trivially-copyable type has no effect
void f(const string &s);
string s;
f(std::move(s)); // Warning: passing result of std::move as a const reference argument; no move will actually happen
Options
CheckTriviallyCopyableMoveโ if true, enables detection of trivially copyable types that do not have a move constructor. Default istrue.CheckMoveToConstRefโ if true, enables detection ofstd::move()passed as a const reference argument. Default istrue.
performance-move-constructor-init ๐
"cert-oop11-cpp" redirects here as an alias for this check.
The check flags user-defined move constructors that have a ctor-initializer initializing a member or base class through a copy constructor instead of a move constructor.
performance-no-automatic-move ๐ธ
Finds local variables that cannot be automatically moved due to constness.
Under certain conditions <https://en.cppreference.com/w/cpp/language/return#automatic_move_from_local_variables_and_parameters>, local values are automatically moved out when returning from a function. A common mistake is to declare local lvalue variables const, which prevents the move.
Example [1] <https://godbolt.org/z/x7SYYA>:
StatusOr<std::vector<int>> Cool() {
std::vector<int> obj = ...;
return obj; // calls StatusOr::StatusOr(std::vector<int>&&)
}
StatusOr<std::vector<int>> NotCool() {
const std::vector<int> obj = ...;
return obj; // calls `StatusOr::StatusOr(const std::vector<int>&)`
}
The former version (Cool) should be preferred over the latter (NotCool) as it will avoid allocations and potentially large memory copies.
Semantics
In the example above, StatusOr::StatusOr(T&&) have the same semantics as long as the copy and move constructors for T have the same semantics. Note that there is no guarantee that S::S(T&&) and S::S(const T&) have the same semantics for any single S, so we're not providing automated fixes for this check, and judgement should be exerted when making the suggested changes.
-Wreturn-std-move
Another case where the move cannot happen is the following:
StatusOr<std::vector<int>> Uncool() {
std::vector<int>&& obj = ...;
return obj; // calls `StatusOr::StatusOr(const std::vector<int>&)`
}
In that case the fix is more consensual: just return std::move(obj). This is handled by the -Wreturn-std-move warning.
performance-no-int-to-ptr ๐
Diagnoses every integer to pointer cast.
While casting an (integral) pointer to an integer is obvious - you just get the integral value of the pointer, casting an integer to an (integral) pointer is deceivingly different. While you will get a pointer with that integral value, if you got that integral value via a pointer-to-integer cast originally, the new pointer will lack the provenance information from the original pointer.
So while (integral) pointer to integer casts are effectively no-ops, and are transparent to the optimizer, integer to (integral) pointer casts are NOT transparent, and may conceal information from optimizer.
While that may be the intention, it is not always so. For example, let's take a look at a routine to align the pointer up to the multiple of 16: The obvious, naive implementation for that is:
char* src(char* maybe_underbiased_ptr) {
uintptr_t maybe_underbiased_intptr = (uintptr_t)maybe_underbiased_ptr;
uintptr_t aligned_biased_intptr = maybe_underbiased_intptr + 15;
uintptr_t aligned_intptr = aligned_biased_intptr & (~15);
return (char*)aligned_intptr; // warning: avoid integer to pointer casts [performance-no-int-to-ptr]
}
And yeah โ the check will rightfully diagnose that cast, no notes. ๐
But when provenance concealment is not the goal of the code and it's just an accident, you can rewrite it like this, with zero integer-to-pointer casts:
char*
tgt(char* maybe_underbiased_ptr) {
uintptr_t maybe_underbiased_intptr = (uintptr_t)maybe_underbiased_ptr;
uintptr_t aligned_biased_intptr = maybe_underbiased_intptr + 15;
uintptr_t aligned_intptr = aligned_biased_intptr & (~15);
uintptr_t bias = aligned_intptr - maybe_underbiased_intptr;
return maybe_underbiased_ptr + bias;
}
performance-noexcept-destructor ๐ซถ
This check flags your user-defined destructors that are marked noexcept(expr) where expr evaluates to false โ but like, only when the false isn't a literal false itself, okay? subtle.
When a destructor is marked noexcept, it's promising the compiler that no exceptions will be thrown while an object is being destroyed, and that lets the compiler do cute little optimizations, like straight-up omitting exception handling code. โจ
performance-noexcept-move-constructor ๐ฆ
Flags user-defined move constructors and assignment operators that aren't marked noexcept, or that are marked noexcept(expr) where expr evaluates to false (again: not a false literal itself).
Move constructors of, like, all the types used with STL containers need to be declared noexcept โ otherwise STL will quietly choose copy constructors instead. ๐ญ Same energy for move assignment operations.
performance-noexcept-swap ๐ธ
Flags user-defined swap and iter_swap functions that aren't marked noexcept, or that are marked noexcept(expr) where expr evaluates to false (not a false literal itself, we been knew).
When a swap or iter_swap function is marked as noexcept, it assures the compiler that no exceptions will be thrown during the swapping of two objects, which lets the compiler do certain optimizations such as omitting exception handling code.
performance-trivially-destructible ๐
Finds types that could be made trivially-destructible by removing out-of-line defaulted destructor declarations. iconic behavior, honestly.
struct A: TrivialType {
~A(); // Makes A non-trivially-destructible.
TrivialType trivial_fields;
};
A::~A() = default;
performance-type-promotion-in-math-fn โญ
Finds calls to C math library functions (from math.h or, in C++, cmath) that come with implicit float to double promotions.
For example, it warns on ::sin(0.f), because this function's parameter is a double. You probably meant to call std::sin(0.f) (in C++), or sinf(0.f) (in C). ๐
float a;
asin(a);
// becomes
float a;
std::asin(a);
performance-unnecessary-copy-initialization ๐
Finds local variable declarations that are initialized using the copy constructor of a non-trivially-copyable type โ when honestly it would've been enough to get a const reference.
The check only fires if it's safe to replace the copy with a const reference. That's the case when the variable is const qualified, or when it's only used as a const โ like, only const methods or operators get invoked on it, or it's used as a const reference or value argument in constructors or function calls. ๐ซถ
Example:
const string& constReference();
void Function() {
// The warning will suggest making this a const reference.
const string UnnecessaryCopy = constReference();
}
struct Foo {
const string& name() const;
};
void Function(const Foo& foo) {
// The warning will suggest making this a const reference.
string UnnecessaryCopy1 = foo.name();
UnnecessaryCopy1.find("bar");
// The warning will suggest making this a const reference.
string UnnecessaryCopy2 = UnnecessaryCopy1;
UnnecessaryCopy2.find("bar");
}
Options ๐
AllowedTypesโ a semicolon-separated list of names of types that are allowed to be initialized by copying. Regular expressions are accepted, e.g.[Rr]ef(erence)?$matches every type with suffixRef,ref,Referenceandreference. The default is empty. If a name in the list contains the sequence::, it's matched against the qualified type name (i.e.namespace::Type); otherwise it's matched against only the type name (i.e.Type).ExcludedContainerTypesโ a semicolon-separated list of names of types whose methods are allowed to return theconstreference the variable is copied from. When an expensive-to-copy variable is copy initialized by the return value from a type on this list, the check does not trigger. This can be used to exclude types known to beconstincorrect, or where the lifetime or immutability of returned references isn't tied to mutations of the container. An example is view types that don't own the underlying data. Like forAllowedTypesabove, regular expressions are accepted and the inclusion of::determines whether the qualified typename is matched or not.
performance-unnecessary-value-param ๐
Flags value parameter declarations of expensive-to-copy types that get copied for each invocation โ when it would've sufficed to pass them by const reference. the way that copy constructor is doing the most for no reason ๐ญ
The check is only applied to parameters of types that are expensive to copy, which means they're not trivially copyable, or they have a non-trivial copy constructor or destructor.
To make sure it's safe to swap the value parameter for a const reference, this heuristic is employed:
- the parameter is
constqualified; - the parameter is not
const, but onlyconstmethods or operators are invoked on it, or it's used as aconstreference or value argument in constructors or function calls.
Example:
void f(const string Value) {
// The warning will suggest making Value a reference.
}
void g(ExpensiveToCopy Value) {
// The warning will suggest making Value a const reference.
Value.ConstMethd();
ExpensiveToCopy Copy(Value);
}
If the parameter is not const, is only copied or assigned once, and has a non-trivial move-constructor or move-assignment operator respectively, the check will suggest to move it. ๐
Example:
void setValue(string Value) {
Field = Value;
}
Will become:
#include <utility>
void setValue(string Value) {
Field = std::move(Value);
}
Because the fix-it needs to change the signature of the function, it may break builds if the function is used in multiple translation units, or if some code depends on function signatures. ๐ be careful bestie.
Options โจ
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.AllowedTypesโ a semicolon-separated list of names of types allowed to be passed by value. Regular expressions are accepted, e.g.[Rr]ef(erence)?$matches every type with suffixRef,ref,Referenceandreference. The default is empty. If a name in the list contains the sequence::, it's matched against the qualified type name (i.e.namespace::Type); otherwise it's matched against only the type name (i.e.Type).IgnoreCoroutinesโ a boolean specifying whether the check should suggest passing parameters by reference in coroutines. Passing parameters by reference in coroutines may not be safe, please seecppcoreguidelines-avoid-reference-coroutine-parametersfor more information. Default istrue.
portability-avoid-pragma-once ๐ง
Finds uses of #pragma once and suggests replacing them with standard include guards (#ifndef/#define/#endif) for improved portability.
#pragma once is a non-standard extension, despite being widely supported by modern compilers. Relying on it can lead to portability issues in some environments.
Some older or specialized C/C++ compilers โ particularly in embedded systems โ may not fully support #pragma once.
It can also fail in certain file system configurations, like network drives or complex symbolic links, potentially leading to compilation issues. ๐
Consider the following header file:
// my_header.h
#pragma once // warning: avoid 'pragma once' directive; use include guards instead
The warning suggests using include guards:
// my_header.h
#ifndef PATH_TO_MY_HEADER_H // Good: use include guards.
#define PATH_TO_MY_HEADER_H
#endif // PATH_TO_MY_HEADER_H
portability-restrict-system-includes ๐
Checks to selectively allow or disallow a configurable list of system headers. so you get to play favorites, basically ๐
For example:
In order to only allow zlib.h from the system, you would set the options to -*,zlib.h.
#include <curses.h> // Bad: disallowed system header.
#include <openssl/ssl.h> // Bad: disallowed system header.
#include <zlib.h> // Good: allowed system header.
#include "src/myfile.h" // Good: non-system header always allowed.
In order to allow everything except zlib.h from the system, you would set the options to *,-zlib.h.
#include <curses.h> // Good: allowed system header.
#include <openssl/ssl.h> // Good: allowed system header.
#include <zlib.h> // Bad: disallowed system header.
#include "src/myfile.h" // Good: non-system header always allowed.
Since the options support globbing, you can use wildcarding to allow groups of headers. ๐ฅ
-*,openssl/*.h will allow all openssl headers but disallow any others.
#include <curses.h> // Bad: disallowed system header.
#include <openssl/ssl.h> // Good: allowed system header.
#include <openssl/rsa.h> // Good: allowed system header.
#include <zlib.h> // Bad: disallowed system header.
#include "src/myfile.h" // Good: non-system header always allowed.
Options ๐ธ
Includesโ a string containing a comma separated glob list of allowed include filenames. Similar to the-checksglob list for runningclang-tidyitself, the two wildcard characters are*and-, to include and exclude globs, respectively. The default is*, which allows all includes.
portability-simd-intrinsics ๐ฆ
Finds SIMD intrinsics calls and suggests std::experimental::simd (P0214 โ https://wg21.link/p0214) alternatives. out with the old, in with the shiny ๐
If the option Suggest is set to true, for
_mm_add_epi32(a, b); // x86
vec_add(a, b); // Power
...the check suggests an alternative: operator+ on std::experimental::simd objects. ๐
Otherwise, it just complains the intrinsics are non-portable (and hey, there are P0214 alternatives โ https://wg21.link/p0214).
Lowkey a lot of architectures serve SIMD realness (x86 SSE/AVX, Power AltiVec/VSX, ARM NEON, the whole lineup). And it's super common that SIMD code implementing the same algorithm gets written in multiple target-dispatching pieces so it can optimize for different architectures or micro-architectures. ๐
The C++ standard proposal P0214 (https://wg21.link/p0214) and its extensions cover many common SIMD operations. So by migrating from target-dependent intrinsics to P0214 (https://wg21.link/p0214) operations, your SIMD code can be simplified and the pieces for different targets can be unified. Iconic behavior, honestly. โจ
Go read P0214 (https://wg21.link/p0214) for the intro and the motivation behind the data-parallel standard library.
Options
Suggestโ if this is set totrue(default isfalse), the check will suggest P0214 (https://wg21.link/p0214) alternatives; otherwise it only points out that the intrinsic function is non-portable. ๐ซถStdโ the namespace used to suggest P0214 (https://wg21.link/p0214) alternatives. If not specified, it'sstd::for-std=c++20andstd::experimental::for-std=c++11.
๐ธ portability-std-allocator-const ๐ธ
Reports use of std::vector<const T> (and similar containers of const elements). These are not allowed in standard C++, and should usually be std::vector<T> instead.
Per C++ [allocator.requirements.general]: "T is any cv-unqualified object type", std::allocator<const T> is undefined. Many standard containers use std::allocator by default and therefore their const T instantiations are undefined. ๐ญ
libc++ defines std::allocator<const T> as an extension which will be removed in the future.
libstdc++ and MSVC do not support std::allocator<const T> at all:
// libstdc++ has a better diagnostic since https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48101
std::deque<const int> deque; // error: static assertion failed: std::deque must have a non-const, non-volatile value_type
std::set<const int> set; // error: static assertion failed: std::set must have a non-const, non-volatile value_type
std::vector<int* const> vector; // error: static assertion failed: std::vector must have a non-const, non-volatile value_type
// MSVC
// error C2338: static_assert failed: 'The C++ Standard forbids containers of const elements because allocator<const T> is ill-formed.'
Code bases that are only compiled with libc++ may accrue such undefined usage. ๐ This check finds such code and prevents backsliding while clean-up is ongoing. We love a check that keeps you honest.
๐ง portability-template-virtual-member-function ๐ง
Finds cases when an uninstantiated virtual member function in a template class causes cross-compiler incompatibility.
Upon instantiating a template class, non-virtual member functions don't have to be instantiated unless they are used. Virtual member function instantiation, on the other hand, is unspecified and depends on the implementation of the compiler. So it's giving chaos โ the compiler-dependent kind. ๐
In the following snippets the virtual member function is not instantiated by GCC and Clang, but it is instantiated by MSVC, so while the snippet is accepted by the former compilers, it is rejected by the latter.
template<typename T>
struct CrossPlatformError {
virtual ~CrossPlatformError() = default;
static void used() {}
virtual void unused() {
T MSVCError = this;
};
};
int main() {
CrossPlatformError<int>::used();
return 0;
}
Cross-platform projects that need to support MSVC on Windows might see compiler errors because certain virtual member functions get instantiated, which are not instantiated by other compilers on other platforms. This check highlights such virtual member functions. ๐ฅ
๐ readability-ambiguous-smartptr-reset-call ๐
Finds potentially erroneous calls to the reset method on smart pointers when the pointee type also has a reset method. Having a reset method in both classes makes it super easy to accidentally make the pointer null when you were intending to reset the underlying object. Not me making that typo... ๐ญ
struct Resettable {
void reset() { /* Own reset logic */ }
};
auto ptr = std::make_unique<Resettable>();
ptr->reset(); // Calls underlying reset method
ptr.reset(); // Makes the pointer null
Both calls are valid C++ code, but the second one might not be what the developer intended, since it destroys the pointed-to object rather than resetting its state. It's easy to make such a typo because the difference between . and -> is really small. ๐ซ
The recommended approach is to make the intent explicit by using either member access or direct assignment:
std::unique_ptr<Resettable> ptr = std::make_unique<Resettable>();
(*ptr).reset(); // Clearly calls underlying reset method
ptr = nullptr; // Clearly makes the pointer null
The default smart pointers and classes that are considered are std::unique_ptr, std::shared_ptr, boost::shared_ptr. To specify other smart pointers or other classes, use the SmartPointers option.
Note: the check may emit invalid fix-its and misleading warning messages when you specify custom smart pointers or other classes in theSmartPointersoption. For example,boost::scoped_ptrdoes not have anoperator=, which makes fix-its invalid. ๐
Note: automatic fix-its are enabled only ifclang-tidyis invoked with the--fix-notesoption.
Options
SmartPointersโ semicolon-separated list of fully qualified class names of custom smart pointers. Default value is::std::unique_ptr;::std::shared_ptr; ::boost::shared_ptr.
๐ readability-avoid-const-params-in-decls ๐
Checks whether a function declaration has parameters that are top level const.
const values in declarations do not affect the signature of a function, so they should not be put there. Periodt. ๐
Examples:
void f(const string); // Bad: const is top level.
void f(const string&); // Good: const is not top level.
Options
IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.
๐ฆ readability-avoid-nested-conditional-operator ๐ฆ
Identifies instances of nested conditional operators in the code.
Nested conditional operators โ also known as ternary operators โ can contribute to reduced code readability and comprehension. So they should be split into several statements, and the intermediate results stored in a temporary variable. Slay with clarity. โจ
Examples:
int NestInConditional = (condition1 ? true1 : false1) ? true2 : false2;
int NestInTrue = condition1 ? (condition2 ? true1 : false1) : false2;
int NestInFalse = condition1 ? true1 : condition2 ? true2 : false1;
๐ readability-avoid-return-with-void-value ๐
Finds return statements with void values used within functions with void result types.
A function with a void return type is intended to perform a task without producing a return value. return statements with expressions could lead to confusion and may miscommunicate the function's intended behavior. ๐ซถ
Example:
void g();
void f() {
// ...
return g();
}
In a long function body, the return statement suggests that the function returns a value. However, return g(); is a combination of two statements that should be written as
g();
return;
to make clear that g() is called and immediately afterwards the function returns (nothing).
In C, the same issue is detected by the compiler if the -Wpedantic mode is enabled.
Options
IgnoreMacrosโ the valuefalsespecifies thatreturnstatements expanded from macros are not checked. The default value istrue.StrictModeโ the valuefalsespecifies that a directreturnstatement shall be excluded from the analysis if it is the only statement not contained in a block, likeif (cond) return g();. The default value istrue.
๐ readability-avoid-unconditional-preprocessor-if ๐
Finds code blocks that are constantly enabled or disabled in preprocessor directives by analyzing #if conditions, such as #if 0 and #if 1, etc.
#if 0
// some disabled code
#endif
#if 1
// some enabled code that can be disabled manually
#endif
Unconditional preprocessor directives, such as #if 0 for disabled code and #if 1 for enabled code, can lead to dead code and always-enabled code, respectively. Dead code can make understanding the codebase more difficult, hinder readability, and may be a sign of unfinished functionality or abandoned features. That can cause maintenance issues, confusion for future developers, and potential compilation problems. ๐ญ
As a solution for both cases, consider using preprocessor macros or defines, like #ifdef DEBUGGING_ENABLED, to control code enabling or disabling. This approach provides better coordination and flexibility when working with different parts of the codebase. Alternatively, you can comment out the entire code using /* */ block comments and add a hint, such as @todo, to indicate future actions. Chef's kiss. ๐
๐ธ readability-braces-around-statements ๐ธ
google-readability-braces-around-statements redirects here as an alias for this check.
Checks that bodies of if statements and loops (for, do while, and while) are inside braces.
Before:
if (condition)
statement;
After:
if (condition) {
statement;
}
Options
ShortStatementLinesโ defines the minimal number of lines that the statement should have in order to trigger this check.
The number of lines is counted from the end of the condition or initial keyword (do/else) until the last line of the inner statement. Default value 0 means that braces will be added to all statements (not having them already).
โญ readability-const-return-type โญ
Checks for functions with a const-qualified return type and recommends removal of the const keyword. Such use of const is usually superfluous, and can prevent valuable compiler optimizations. Does not (yet) fix trailing return types. Ngl, the "yet" is doing a lot of work there. ๐
Examples:
const int foo();
const Clazz foo();
Clazz *const foo();
Note that this applies strictly to top-level qualification, which excludes pointers or references to const values. For example, these are fine:
const int* foo();
const int& foo();
const Clazz* foo();
Options
IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.
readability-container-contains
Okay so this one is obsessed with container.count() and container.find() == container.end() ๐
If you're using either of those to just... check if a thing is in a container? The check says replace them with a call to container.contains(). Periodt.
Why tho? Because checking whether an element is contained inside a container should be done with contains instead of count/find โ contains literally conveys the intent more clearly. And highkey the efficiency thing matters: for containers that permit multiple entries per key (multimap, multiset, ...), contains is more efficient than count because count has to do unnecessary additional work. No cap. ๐ญ
Examples โ here's the glow-up in list form:
myMap.find(x) == myMap.end()โ!myMap.contains(x)myMap.find(x) != myMap.end()โmyMap.contains(x)if (myMap.count(x))โif (myMap.contains(x))bool exists = myMap.count(x)โbool exists = myMap.contains(x)bool exists = myMap.count(x) > 0โbool exists = myMap.contains(x)bool exists = myMap.count(x) >= 1โbool exists = myMap.contains(x)bool missing = myMap.count(x) == 0โbool missing = !myMap.contains(x)
This check will apply to any class that has a contains method โ notably including std::set, std::unordered_set, std::map, and std::unordered_map as of C++20, and std::string and std::string_view as of C++23. ๐ฆ
readability-container-data-pointer
Finds cases where code could use data() rather than the address of the element at index 0 in a container. This pattern is commonly used to materialize a pointer to the backing data of a container. std::vector and std::string provide a data() accessor to retrieve the data pointer, and that one should be preferred. โจ
It also ensures that in the case that the container is empty, the data pointer access does not perform an errant memory access. So like, safety AND style? We love that for you. ๐ซถ
Options
IgnoredContainersโ Semicolon-separated list of containers regexp for which this check won't be enforced. Default is an empty string.
readability-container-size-empty
Checks whether a call to the size()/length() method can be replaced with a call to empty(). ๐
The emptiness of a container should be checked using the empty() method instead of the size()/length() method. It shows clearer intent to use empty(). Furthermore some containers (for example, a std::forward_list) may implement the empty() method but not implement the size() or length() method. Using empty() whenever possible makes it easier to switch to another container in the future. Future-proof queen behavior, honestly. ๐
The check issues a warning if a container has empty() and size() or length() methods matching following signatures:
size_type size() const;
size_type length() const;
bool empty() const;
size_type can be any kind of integer type.
Options
ExcludedComparisonTypesโ A semicolon-separated list of class names for which the check will ignore comparisons of objects with default-constructed objects of the same type. If a class is listed here, the check will not suggest usingempty()instead of such comparisons for objects of that class. Default value is:::std::array.
readability-convert-member-functions-to-static
Finds non-static member functions that can be made static because the functions don't use this. ๐ (imagine not needing this โ couldn't be your fave member function)
Heads up tho: after applying modifications as suggested by the check, running the check again might find more opportunities to mark member functions static. It's giving layers. ๐ง
And after making a member function static, you might want to run the check readability-static-accessed-through-instance to replace calls like Instance.method() by Class::method(). Glow-up chain, iykyk. โจ
readability-delete-null-pointer
Checks the if statements where a pointer's existence is checked and then deletes the pointer. The check is unnecessary as deleting a null pointer has no effect. Literally pointless, bestie. ๐
int *p;
if (p)
delete p;
readability-duplicate-include
Looks for duplicate includes and removes them. The check maintains a list of included files and looks for duplicates. If a macro is defined or undefined then the list of included files is cleared. ๐
Examples:
#include <memory>
#include <vector>
#include <memory>
becomes
#include <memory>
#include <vector>
Because of the intervening macro definitions, this code remains unchanged:
#undef NDEBUG
#include "assertion.h"
// ...code with assertions enabled
#define NDEBUG
#include "assertion.h"
// ...code with assertions disabled
readability-else-after-return
The LLVM Coding Standards advises to reduce indentation where possible and where it makes understanding code easier. Early exit is one of the suggested enforcements of that. Please do not use else or else if after something that interrupts control flow โ like return, break, continue, throw. ๐ช (just leave!! the function is over!!)
The following piece of code illustrates how the check works. This piece of code:
void foo(int Value) {
int Local = 0;
for (int i = 0; i < 42; i++) {
if (Value == 1) {
return;
} else {
Local++;
}
if (Value == 2)
continue;
else
Local++;
if (Value == 3) {
throw 42;
} else {
Local++;
}
}
}
Would be transformed into:
void foo(int Value) {
int Local = 0;
for (int i = 0; i < 42; i++) {
if (Value == 1) {
return;
}
Local++;
if (Value == 2)
continue;
Local++;
if (Value == 3) {
throw 42;
}
Local++;
}
}
Options
WarnOnUnfixableโ Whentrue, emit a warning for cases where the check can't output a Fix-It. These can occur with declarations inside theelsebranch that would have an extended lifetime if theelsebranch was removed. Default value istrue.WarnOnConditionVariablesโ Whentrue, the check will attempt to refactor a variable defined inside the condition of theifstatement that is used in theelsebranch defining them just before theifstatement. This can only be done if theifstatement is the last statement in its parent's scope. Default value istrue.
LLVM alias
There is an alias of this check called llvm-else-after-return. In that version the options WarnOnUnfixable and WarnOnConditionVariables are both set to false by default. ๐
This check helps to enforce this LLVM Coding Standards recommendation.
readability-enum-initial-value
Enforces consistent style for enumerators' initialization, covering three styles: none, first only, or all initialized explicitly. Pick a lane, bestie. ๐
An inconsistent style and strictness to defining the initializing value of enumerators may cause issues if the enumeration is extended with new enumerators that obtain their integer representation implicitly. And like... that's the scary part. ๐ญ
The following three cases are accepted:
- No enumerators are explicit initialized.
- Exactly the first enumerator is explicit initialized.
- All enumerators are explicit initialized.
enum A { // (1) Valid, none of enumerators are initialized.
a0,
a1,
a2,
};
enum B { // (2) Valid, the first enumerator is initialized.
b0 = 0,
b1,
b2,
};
enum C { // (3) Valid, all of enumerators are initialized.
c0 = 0,
c1 = 1,
c2 = 2,
};
enum D { // Invalid, d1 is not explicitly initialized!
d0 = 0,
d1,
d2 = 2,
};
enum E { // Invalid, e1, e3, and e5 are not explicitly initialized.
e0 = 0,
e1,
e2 = 2,
e3, // Dangerous, as the numeric values of e3 and e5 are both 3, and this is not explicitly visible in the code!
e4 = 2,
e5,
};
This check corresponds to the CERT C Coding Standard recommendation INT09-C. Ensure enumeration constants map to unique values.
cert-int09-c redirects here as an alias of this check. ๐
Options
AllowExplicitZeroFirstInitialValueโ If set tofalse, the first enumerator must not be explicitly initialized to a literal0. Default istrue.
enum F {
f0 = 0, // Not allowed if AllowExplicitZeroFirstInitialValue is false.
f1,
f2,
};
AllowExplicitSequentialInitialValuesโ If set tofalse, explicit initialization to sequential values are not allowed. Default istrue.
enum G {
g0 = 1, // Not allowed if AllowExplicitSequentialInitialValues is false.
g1 = 2,
g2 = 3,
readability-function-cognitive-complexity
Checks function Cognitive Complexity metric. ๐ง (yes your function is being graded on vibes now)
the metric is implemented as per the COGNITIVE COMPLEXITY by SonarSource specification, version 1.2 (19 April 2017) โ [https://www.sonarsource.com/docs/CognitiveComplexity.pdf](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) (yes, the actual PDF, bestie, the receipts are right there).
๐ Options
Thresholdโ flags functions whose Cognitive Complexity goes over this number. Default is25. (25 is like... a lot of vibes already ngl)DescribeBasicIncrementsโ if set totrue, then for every function that blows past the complexity threshold, the check drops extra diagnostics on every single piece of code that contributes to that complexity (loops, if statements, all of it). Peep the examples below. Default istrue.IgnoreMacrosโ if set totrue, the check just ignores code inside macros. โ ๏ธ heads up: also any macro arguments are ignored, even if they should count to the complexity. And because this might change in the future, this option isn't guaranteed to be forward-compatible. Default isfalse.
๐งฑ Building blocks
Okay so there are literally three basic building blocks of the Cognitive Complexity metric, and they are the whole personality of this check:
โ Increment
These structures bump up the function's Cognitive Complexity metric (by 1):
- Conditional operators:
if()else if()elsecond ? true : false
switch()- Loops:
for()- C++11 range-based
for() while()do while()
catch ()goto LABEL,goto *(&&LABEL))- sequences of binary logical operators:
boolean1 || boolean2boolean1 && boolean2
๐ช Nesting level
By itself, the nesting level does not change the function's Cognitive Complexity metric โ it's just getting tracked, and it gets used by the next, third building block. These structures increase the nesting level (by 1):
- Conditional operators:
if()else if()elsecond ? true : false
switch()- Loops:
for()- C++11 range-based
for() while()do while()
catch ()- Nested functions:
- C++11 Lambda
- Nested class
- Nested struct
- GNU statement expression
- Apple Block Declaration
๐ฅ Nesting increment
this is where the previous basic building block, Nesting level, actually matters!! These structures increase the function's Cognitive Complexity metric by the current Nesting level:
- Conditional operators:
if()cond ? true : false
switch()- Loops:
for()- C++11 range-based
for() while()do while()
catch ()
๐ธ Examples
The simplest case, periodt. This function has Cognitive Complexity of 0.
void function0() {}
Slightly better example. This function has Cognitive Complexity of 1.
int function1(bool var) {
if(var) // +1, nesting level +1
return 42;
return 0;
}
Full example. This function has Cognitive Complexity of 3.
int function3(bool var1, bool var2) {
if(var1) { // +1, nesting level +1
if(var2) // +2 (1 + current nesting level of 1), nesting level +1
return 42;
}
return 0;
}
In that last example, the check flags function3 if the option Threshold is set to 2 or smaller. And if DescribeBasicIncrements is set to true, it additionally flags those two if statements with the amounts by which they increase the complexity of the function and the current nesting level. double dip, we love to see it โจ
๐ Limitations
The metric is implemented with two notable exceptions:
- preprocessor conditionals (
#ifdef,#if,#elif,#else,#endif) are not accounted for. - each method in a recursion cycle is not accounted for. It can't be fully implemented, because cross-translational-unit analysis would be needed, which is currently not possible in
clang-tidy.
๐ซถ readability-function-size
google-readability-function-size redirects here as an alias for this check. (same check, different wig)
Checks for large functions based on various metrics.
โจ Options
LineThresholdโ flags functions exceeding this number of lines. Default isnone(as in, ignore the number of lines).StatementThresholdโ flags functions exceeding this number of statements. Real talk: this may differ significantly from the number of lines for macro-heavy code. Default is800.BranchThresholdโ flags functions exceeding this number of control statements. Default isnone(ignore the number of branches).ParameterThresholdโ flags functions that exceed a specified number of parameters. Default isnone(ignore the number of parameters).NestingThresholdโ flags compound statements which create the next nesting level afterNestingThreshold. This may differ significantly from the expected value for macro-heavy code. Default isnone(ignore the nesting level).VariableThresholdโ flags functions exceeding this number of variables declared in the body. Default isnone(ignore the number of variables). ๐ Please note: function parameters and variables declared in lambdas, GNU Statement Expressions, and nested class inline functions are not counted.CountMemberInitAsStmtโ whentrue, counts class member initializers in constructors as statements. Default istrue.
๐ฆ readability-identifier-length
This check finds variables and function parameters whose length is too short. The desired name length is configurable.
Special cases are supported for loop counters and for exception variable names. because sometimes i is simply doing its job, okay??
๐ Options
The following options are described below:
MinimumVariableNameLength,IgnoredVariableNamesMinimumParameterNameLength,IgnoredParameterNames<#cmdoption-arg-IgnoredParameterNames>MinimumLoopCounterNameLength,IgnoredLoopCounterNamesMinimumExceptionNameLength,IgnoredExceptionVariableNamesMinimumVariableNameLengthโ all variables (other than loop counter, exception names and function parameters) are expected to have at least a length ofMinimumVariableNameLength(default is3). Setting it to0or1disables the check entirely.
int i = 42; // warns that 'i' is too short
This check does not have any fix suggestions in the general case, since variable names have semantic value. (we're not out here renaming your whole soul, bestie)
IgnoredVariableNamesโ specifies a regular expression for variable names that are to be ignored. The default value is empty, thus no names are ignored.MinimumParameterNameLengthโ all function parameter names are expected to have a length of at leastMinimumParameterNameLength(default is3). Setting it to0or1disables the check entirely.
int doubler(int x) // warns that x is too short
{
return 2 * x;
}
This check does not have any fix suggestions in the general case, since variable names have semantic value.
IgnoredParameterNamesโ specifies a regular expression for parameters that are to be ignored. The default value is^[n]$for historical reasons. (history said so, we don't question history)MinimumLoopCounterNameLengthโ loop counter variables are expected to have a length of at leastMinimumLoopCounterNameLengthcharacters (default is2). Setting it to0or1disables the check entirely.
// This warns that 'q' is too short.
for (int q = 0; q < size; ++ q) {
// ...
}
IgnoredLoopCounterNamesโ specifies a regular expression for counter names that are to be ignored. The default value is^[ijk_]$; the first three symbols for historical reasons, and the last one since it's frequently used as a "don't care" value, specifically in tools such as Google Benchmark.
// This does not warn by default, for historical reasons.
for (int i = 0; i < size; ++ i) {
// ...
}
MinimumExceptionNameLengthโ exception clause variables are expected to have a length of at leastMinimumExceptionNameLength(default is2). Setting it to0or1disables the check entirely.
try {
// ...
}
// This warns that 'e' is too short.
catch (const std::exception& x) {
// ...
}
๐ okay yes, the comment says'e'and the variable is literallyxโ that's exactly what the page says, i'm just the messenger, do not @ me ๐
IgnoredExceptionVariableNamesโ specifies a regular expression for exception variable names that are to be ignored. The default value is^[e]$mainly for historical reasons.
try {
// ...
}
// This does not warn by default, for historical reasons.
catch (const std::exception& e) {
// ...
}
๐ readability-identifier-naming
Checks for identifiers naming style mismatch.
This check will try to enforce coding guidelines on the identifiers' naming. It supports one of the following casing types and tries to convert from one to another if a mismatch is detected. the glow-up arc of your variable names โจ
Casing types include:
lower_caseUPPER_CASEcamelBackCamelCasecamel_Snake_BackCamel_Snake_CaseaNy_CasELeading_upper_snake_case
It also supports a fixed prefix and suffix that will be prepended or appended to the identifiers, regardless of the casing.
Many configuration options are available, in order to be able to create different rules for different kinds of identifiers. In general, the rules are falling back to a more generic rule if the specific case is not configured.
The naming of virtual methods is reported where they occur in the base class, but not where they are overridden, as it can't be fixed locally there. This also applies for pseudo-override patterns like CRTP.
Leading_upper_snake_case is a naming convention where the first word is capitalized, followed by lower case word(s) separated by underscore(s) '_'. Examples include: Cap_snake_case, Cobra_case, Foo_bar_baz, and Master_copy_8gb.
Hungarian notation can be customized using different HungarianPrefix settings. The options and their corresponding values are:
Offโ the default settingOnโ example:int iVariableLowerCaseโ example:int i_VariableCamelCaseโ example:int IVariable
โญ Options
The following options are described below:
AbstractClassCase,AbstractClassPrefix,AbstractClassSuffix,AbstractClassIgnoredRegexp,AbstractClassHungarianPrefixAggressiveDependentMemberLookupCheckAnonFieldInParentClassCase,ClassPrefix,ClassSuffix,ClassIgnoredRegexp,ClassHungarianPrefixClassConstantCase,ClassConstantPrefix,ClassConstantSuffix,ClassConstantIgnoredRegexp,ClassConstantHungarianPrefixClassMemberCase,ClassMemberPrefix,ClassMemberSuffix,ClassMemberIgnoredRegexp,ClassMemberHungarianPrefixClassMethodCase,ClassMethodPrefix,ClassMethodSuffix,ClassMethodIgnoredRegexpConceptCase,ConceptPrefix,ConceptSuffix,ConceptIgnoredRegexpConstantCase,ConstantPrefix,ConstantSuffix,ConstantIgnoredRegexp,ConstantHungarianPrefixConstantMemberCase,ConstantMemberPrefix,ConstantMemberSuffix,ConstantMemberIgnoredRegexp,ConstantMemberHungarianPrefixConstantParameterCase,ConstantParameterPrefix,ConstantParameterSuffix,ConstantParameterIgnoredRegexp,ConstantParameterHungarianPrefixConstantPointerParameterCase,ConstantPointerParameterPrefix,ConstantPointerParameterSuffix,ConstantPointerParameterIgnoredRegexp,ConstantPointerParameterHungarianPrefixConstexprFunctionCase,ConstexprFunctionPrefix,ConstexprFunctionSuffix,ConstexprFunctionIgnoredRegexpConstexprMethodCase,ConstexprMethodPrefix,ConstexprMethodSuffix,ConstexprMethodIgnoredRegexpConstexprVariableCase,ConstexprVariablePrefix,ConstexprVariableSuffix,ConstexprVariableIgnoredRegexp,ConstexprVariableHungarianPrefixEnumCase,EnumPrefix,EnumSuffix,EnumIgnoredRegexpEnumConstantCase,EnumConstantPrefix,EnumConstantSuffix,EnumConstantIgnoredRegexp,EnumConstantHungarianPrefixFunctionCase,FunctionPrefix,FunctionSuffix,FunctionIgnoredRegexpGetConfigPerFileGlobalConstantCase,GlobalConstantPrefix,GlobalConstantSuffix,GlobalConstantIgnoredRegexp,GlobalConstantHungarianPrefixGlobalConstantPointerCase,GlobalConstantPointerPrefix,GlobalConstantPointerSuffix,GlobalConstantPointerIgnoredRegexp,GlobalConstantPointerHungarianPrefixGlobalFunctionCase,GlobalFunctionPrefix,GlobalFunctionSuffix,GlobalFunctionIgnoredRegexpGlobalPointerCase,GlobalPointerPrefix,GlobalPointerSuffix,GlobalPointerIgnoredRegexp,GlobalPointerHungarianPrefixGlobalVariableCase,GlobalVariablePrefix,GlobalVariableSuffix,GlobalVariableIgnoredRegexp,GlobalVariableHungarianPrefixIgnoreMainLikeFunctionsInlineNamespaceCase,InlineNamespacePrefix,InlineNamespaceSuffix,InlineNamespaceIgnoredRegexpLocalConstantCase,LocalConstantPrefix,LocalConstantSuffix,LocalConstantIgnoredRegexp,LocalConstantHungarianPrefixLocalConstantPointerCase,LocalConstantPointerPrefix,LocalConstantPointerSuffix,LocalConstantPointerIgnoredRegexp,LocalConstantPointerHungarianPrefixLocalPointerCase,LocalPointerPrefix,LocalPointerSuffix,LocalPointerIgnoredRegexp,LocalPointerHungarianPrefixLocalVariableCase,LocalVariablePrefix,LocalVariableSuffix,LocalVariableIgnoredRegexp,LocalVariableHungarianPrefixMacroDefinitionCase,MacroDefinitionPrefix,MacroDefinitionSuffix,MacroDefinitionIgnoredRegexpMemberCase,MemberPrefix,MemberSuffix,MemberIgnoredRegexp,MemberHungarianPrefixMethodCase,MethodPrefix,MethodSuffix,MethodIgnoredRegexpNamespaceCase,NamespacePrefix,NamespaceSuffix,NamespaceIgnoredRegexpParameterCase,ParameterPrefix,ParameterSuffix,ParameterIgnoredRegexp,ParameterHungarianPrefixParameterPackCase,ParameterPackPrefix,ParameterPackSuffix,ParameterPackIgnoredRegexpPointerParameterCase,PointerParameterPrefix,PointerParameterSuffix,PointerParameterIgnoredRegexp,PointerParameterHungarianPrefixPrivateMemberCase,PrivateMemberPrefix,PrivateMemberSuffix,PrivateMemberIgnoredRegexp,PrivateMemberHungarianPrefixPrivateMethodCase,PrivateMethodPrefix,PrivateMethodSuffix,PrivateMethodIgnoredRegexpProtectedMemberCase,ProtectedMemberPrefix,ProtectedMemberSuffix,ProtectedMemberIgnoredRegexp,ProtectedMemberHungarianPrefixProtectedMethodCase,ProtectedMethodPrefix,ProtectedMethodSuffix,ProtectedMethodIgnoredRegexpPublicMemberCase,PublicMemberPrefix,PublicMemberSuffix,PublicMemberIgnoredRegexp,PublicMemberHungarianPrefixPublicMethodCase,PublicMethodPrefix,PublicMethodSuffix,PublicMethodIgnoredRegexpScopedEnumConstantCase,ScopedEnumConstantPrefix,ScopedEnumConstantSuffix,ScopedEnumConstantIgnoredRegexpStaticConstantCase,StaticConstantPrefix,StaticConstantSuffix,StaticConstantIgnoredRegexp,StaticConstantHungarianPrefixStaticVariableCase,StaticVariablePrefix,StaticVariableSuffix,StaticVariableIgnoredRegexp,StaticVariableHungarianPrefixStructCase,StructPrefix,StructSuffix,StructIgnoredRegexpTemplateParameterCase,TemplateParameterPrefix,TemplateParameterSuffix,TemplateParameterIgnoredRegexpTemplateTemplateParameterCase,TemplateTemplateParameterPrefix,TemplateTemplateParameterSuffix,TemplateTemplateParameterIgnoredRegexpTypeAliasCase,TypeAliasPrefix,TypeAliasSuffix,TypeAliasIgnoredRegexpTypedefCase,TypedefPrefix,TypedefSuffix,TypedefIgnoredRegexpTypeTemplateParameterCase,TypeTemplateParameterPrefix,TypeTemplateParameterSuffix,TypeTemplateParameterIgnoredRegexpUnionCase,UnionPrefix,UnionSuffix,UnionIgnoredRegexpValueTemplateParameterCase,ValueTemplateParameterPrefix,ValueTemplateParameterSuffix,ValueTemplateParameterIgnoredRegexpVariableCase,VariablePrefix,VariableSuffix,VariableIgnoredRegexp,VariableHungarianPrefixVirtualMethodCase,VirtualMethodPrefix,VirtualMethodSuffix,VirtualMethodIgnoredRegexp
AbstractClassCase
When you define this one, the check makes sure your abstract class names conform to the casing you picked. That's it, that's the whole vibe โจ
AbstractClassPrefix
When defined, the check makes sure abstract class names get prefixed with the value you give it (regardless of casing, bestie โ it does not care).
AbstractClassIgnoredRegexp
The naming checks straight up won't be enforced for abstract class names matching this regular expression. Like, they're exempt ๐
AbstractClassSuffix
When defined, the check makes sure abstract class names get the suffix you give it tacked on (regardless of casing, again, no notes).
AbstractClassHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type. iykyk ๐ง
So for example, if you're using values of:
AbstractClassCaseoflower_caseAbstractClassPrefixofpre_AbstractClassSuffixof_postAbstractClassHungarianPrefixofOn
It identifies and/or transforms abstract class names like this:
Before:
class ABSTRACT_CLASS {
public:
ABSTRACT_CLASS();
};
After:
class pre_abstract_class_post {
public:
pre_abstract_class_post();
};
Literally glow-up of the century ๐
AggressiveDependentMemberLookup
Set this to true and the check will go looking in dependent base classes for dependent member references that need changing. But heads up โ this can lead to errors with template specializations, so the default value is false. Defaults exist for a reason, bestie ๐ญ
For example, using values of:
ClassMemberCaseoflower_case
Before:
template <typename T>
struct Base {
T BadNamedMember;
};
template <typename T>
struct Derived : Base<T> {
void reset() {
this->BadNamedMember = 0;
}
};
After if AggressiveDependentMemberLookup is false:
template <typename T>
struct Base {
T bad_named_member;
};
template <typename T>
struct Derived : Base<T> {
void reset() {
this->BadNamedMember = 0;
}
};
After if AggressiveDependentMemberLookup is true:
template <typename T>
struct Base {
T bad_named_member;
};
template <typename T>
struct Derived : Base<T> {
void reset() {
this->bad_named_member = 0;
}
};
See how the second one actually follows through? That's the whole point, and also the whole risk. Iconic behaviour honestly ๐ฅ
CheckAnonFieldInParent
When set to true, fields inside anonymous records (i.e. anonymous unions and structs) get treated as names in the enclosing scope rather than as public members of the anonymous record โ but only for the purpose of name checking, okurrr.
For example:
class Foo {
private:
union {
int iv_;
float fv_;
};
};
If CheckAnonFieldInParent is false, you may get warnings that iv_ and fv_ are not coherent to public member names โ because iv_ and fv_ are public members of the anonymous union. When CheckAnonFieldInParent is true, iv_ and fv_ get treated as private data members of Foo for the purpose of name checking, and thus no warnings will be emitted. Peace and quiet, finally ๐ฆ
ClassCase
When defined, the check makes sure class names conform to the selected casing.
ClassPrefix
When defined, the check makes sure class names get prefixed with the given value (regardless of casing).
ClassIgnoredRegexp
Identifier naming checks won't be enforced for class names matching this regular expression. Exempt. Untouchable. ๐
ClassSuffix
When defined, the check makes sure class names get the suffix you give it (regardless of casing).
ClassHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type.
For example, using values of:
ClassCaseoflower_caseClassPrefixofpre_ClassSuffixof_postClassHungarianPrefixofOn
It identifies and/or transforms class names as follows:
Before:
class FOO {
public:
FOO();
~FOO();
};
After:
class pre_foo_post {
public:
pre_foo_post();
~pre_foo_post();
};
The destructor gets the glow-up too, obviously. No one left behind ๐
ClassConstantCase
When defined, the check makes sure class constant names conform to the selected casing.
Okay, class constants first โ they deserve main-character energy too. ๐
ClassConstantPrefixโ when defined, the check makes sure class constant names get prefixed with the value you give it, regardless of casing. โจClassConstantIgnoredRegexpโ identifier naming checks will not be enforced for class constant names that match this regular expression. ๐ClassConstantSuffixโ when defined, the check makes sure class constant names get the suffix you give it, regardless of casing. ๐ClassConstantHungarianPrefixโ when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐
For example, with values of:
ClassConstantCaseoflower_caseClassConstantPrefixofpre_ClassConstantSuffixof_postClassConstantHungarianPrefixofOn
it identifies and/or transforms class constant names like this:
Before:
class FOO {
public:
static const int CLASS_CONSTANT;
};
After:
class FOO {
public:
static const int pre_class_constant_post;
};
Class members are up next, and yes, they also get the full glow-up. ๐ฆ
ClassMemberCaseโ when defined, the check makes sure class member names conform to the selected casing. โจClassMemberPrefixโ when defined, the check makes sure class member names get prefixed with the value you give it, regardless of casing. ๐ClassMemberIgnoredRegexpโ identifier naming checks will not be enforced for class member names that match this regular expression. ๐ClassMemberSuffixโ when defined, the check makes sure class member names get the suffix you give it, regardless of casing. ๐ClassMemberHungarianPrefixโ when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐
For example, with values of:
ClassMemberCaseoflower_caseClassMemberPrefixofpre_ClassMemberSuffixof_postClassMemberHungarianPrefixofOn
it identifies and/or transforms class member names like this:
Before:
class FOO {
public:
static int CLASS_CONSTANT;
};
After:
class FOO {
public:
static int pre_class_constant_post;
};
Class methods, stand up bestie โ itโs your turn. ๐ธ
ClassMethodCaseโ when defined, the check makes sure class method names conform to the selected casing. โจClassMethodPrefixโ when defined, the check makes sure class method names get prefixed with the value you give it, regardless of casing. ๐ClassMethodIgnoredRegexpโ identifier naming checks will not be enforced for class method names that match this regular expression. ๐ClassMethodSuffixโ when defined, the check makes sure class method names get the suffix you give it, regardless of casing. ๐
For example, with values of:
ClassMethodCaseoflower_caseClassMethodPrefixofpre_ClassMethodSuffixof_post
it identifies and/or transforms class method names like this:
Before:
class FOO {
public:
int CLASS_MEMBER();
};
After:
class FOO {
public:
int pre_class_member_post();
};
Concepts get their moment too, no cap. ๐ง
ConceptCaseโ when defined, the check makes sure concept names conform to the selected casing. โจConceptPrefixโ when defined, the check makes sure concept names get prefixed with the value you give it, regardless of casing. ๐ConceptIgnoredRegexpโ identifier naming checks will not be enforced for concept names that match this regular expression. ๐ConceptSuffixโ when defined, the check makes sure concept names get the suffix you give it, regardless of casing. ๐
For example, with values of:
ConceptCaseofCamelCaseConceptPrefixofPreConceptSuffixofPost
it identifies and/or transforms concept names like this:
Before:
template<typename T> concept my_concept = requires (T t) { {t++}; };
After:
template<typename T> concept PreMyConceptPost = requires (T t) { {t++}; };
Constants are literally iconic and they know it. ๐
ConstantCaseโ when defined, the check makes sure constant names conform to the selected casing. โจConstantPrefixโ when defined, the check makes sure constant names get prefixed with the value you give it, regardless of casing. ๐ConstantIgnoredRegexpโ identifier naming checks will not be enforced for constant names that match this regular expression. ๐ConstantSuffixโ when defined, the check makes sure constant names get the suffix you give it, regardless of casing. ๐ConstantHungarianPrefixโ when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐
For example, with values of:
ConstantCaseoflower_caseConstantPrefixofpre_ConstantSuffixof_postConstantHungarianPrefixofOn
it identifies and/or transforms constant names like this:
Before:
void function() { unsigned const MyConst_array[] = {1, 2, 3}; }
After:
void function() { unsigned const pre_myconst_array_post[] = {1, 2, 3}; }
Constant members? Yeah, they get the whole treatment too. ๐
ConstantMemberCaseโ when defined, the check makes sure constant member names conform to the selected casing. โจConstantMemberPrefixโ when defined, the check makes sure constant member names get prefixed with the value you give it, regardless of casing. ๐ConstantMemberIgnoredRegexpโ identifier naming checks will not be enforced for constant member names that match this regular expression. ๐ConstantMemberSuffixโ when defined, the check makes sure constant member names get the suffix you give it, regardless of casing. ๐ConstantMemberHungarianPrefixโ when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐
For example, with values of:
ConstantMemberCaseoflower_caseConstantMemberPrefixofpre_ConstantMemberSuffixof_postConstantMemberHungarianPrefixofOn
it identifies and/or transforms constant member names like this:
Before:
class Foo {
char const MY_ConstMember_string[4] = "123";
}
After:
class Foo {
char const pre_my_constmember_string_post[4] = "123";
}
Constant parameters are next, and they are also getting a makeover. ๐ซถ
ConstantParameterCaseโ when defined, the check makes sure constant parameter names conform to the selected casing. โจConstantParameterPrefixโ when defined, the check makes sure constant parameter names get prefixed with the value you give it, regardless of casing. ๐ConstantParameterIgnoredRegexpโ identifier naming checks will not be enforced for constant parameter names that match this regular expression. ๐ConstantParameterSuffixโ when defined, the check makes sure constant parameter names get the suffix you give it, regardless of casing. ๐ConstantParameterHungarianPrefixโ when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐
For example, with values of:
ConstantParameterCaseoflower_caseConstantParameterPrefixofpre_ConstantParameterSuffixof_postConstantParameterHungarianPrefixofOn
it identifies and/or transforms constant parameter names like this:
Before:
void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);
After:
void GLOBAL_FUNCTION(int PARAMETER_1, int const pre_const_parameter_post);
Constant pointer parameters? Say less. Theyโre getting renamed too. โญ
ConstantPointerParameterCaseโ when defined, the check makes sure constant pointer parameter names conform to the selected casing. โจConstantPointerParameterPrefixโ when defined, the check makes sure constant pointer parameter names get prefixed with the value you give it, regardless of casing. ๐ConstantPointerParameterIgnoredRegexpโ identifier naming checks will not be enforced for constant pointer parameter names that match this regular expression. ๐ConstantPointerParameterSuffixโ when defined, the check makes sure constant pointer parameter names get the suffix you give it, regardless of casing. ๐ConstantPointerParameterHungarianPrefixโ when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐
For example, with values of:
ConstantPointerParameterCaseoflower_caseConstantPointerParameterPrefixofpre_ConstantPointerParameterSuffixof_postConstantPointerParameterHungarianPrefixofOn
it identifies and/or transforms constant pointer parameter names like this:
Before:
void GLOBAL_FUNCTION(int const *CONST_parameter);
After:
void GLOBAL_FUNCTION(int const *pre_const_parameter_post);
Constexpr functions are also in the group chat. ๐ฅ
ConstexprFunctionCaseโ when defined, the check makes sure constexpr function names conform to the selected casing. โจConstexprFunctionPrefixโ when defined, the check makes sure constexpr function names get prefixed with the value you give it, regardless of casing. ๐ConstexprFunctionIgnoredRegexpโ identifier naming checks will not be enforced for constexpr function names that match this regular expression. ๐ConstexprFunctionSuffixโ when defined, the check makes sure constexpr function names get the suffix you give it, regardless of casing. ๐
For example, with values of:
ConstexprFunctionCaseoflower_caseConstexprFunctionPrefixofpre_ConstexprFunctionSuffixof_post
it identifies and/or transforms constexpr function names like this:
Before:
constexpr int CE_function() { return 3; }
After:
constexpr int pre_ce_function_post() { return 3; }
And constexpr methods are right there with them. ๐
ConstexprMethodCaseโ when defined, the check makes sure constexpr method names conform to the selected casing. โจConstexprMethodPrefixโ when defined, the check makes sure constexpr method names get prefixed with the value you give it, regardless of casing. ๐ConstexprMethodIgnoredRegexpโ identifier naming checks will not be enforced for constexpr method names that match this regular expression. ๐ConstexprMethodSuffixโ set this and the check makes everyconstexprmethod name slap the given suffix on the end (casing totally irrelevant here, bestie, we're just stapling it on) ๐
So like, if you're rolling with:
ConstexprMethodCaseoflower_caseConstexprMethodPrefixofpre_ConstexprMethodSuffixof_post
Then it spots and/or glow-ups your constexpr method names like this:
Before:
class Foo {
public:
constexpr int CST_expr_Method() { return 2; }
}
After:
class Foo {
public:
constexpr int pre_cst_expr_method_post() { return 2; }
}
ConstexprVariableCaseโ when defined, the check makes sureconstexprvariable names match the casing you picked โจConstexprVariablePrefixโ when defined, the check makesconstexprvariable names get prefixed with the given value (regardless of casing) ๐ConstexprVariableIgnoredRegexpโ identifier naming checks just won't be enforced forconstexprvariable names that match this regular expression ๐ConstexprVariableSuffixโ when defined, the check makesconstexprvariable names get the given suffix added (regardless of casing) ๐ฆConstexprVariableHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type ๐
For example using values of:
ConstexprVariableCaseoflower_caseConstexprVariablePrefixofpre_ConstexprVariableSuffixof_postConstexprVariableHungarianPrefixofOn
It identifies and/or transforms constexpr variable names as follows:
Before:
constexpr int ConstExpr_variable = MyConstant;
After:
constexpr int pre_constexpr_variable_post = MyConstant;
EnumCaseโ when defined, the check makes sure enumeration names conform to the selected casing ๐ธEnumPrefixโ when defined, the check makes enumeration names get prefixed with the given value (regardless of casing) ๐EnumIgnoredRegexpโ identifier naming checks won't be enforced for enumeration names matching this regular expression ๐ซถEnumSuffixโ when defined, the check makes enumeration names get the given suffix added (regardless of casing) โญ
For example using values of:
EnumCaseoflower_caseEnumPrefixofpre_EnumSuffixof_post
It identifies and/or transforms enumeration names as follows:
Before:
enum FOO { One, Two, Three };
After:
enum pre_foo_post { One, Two, Three };
EnumConstantCaseโ when defined, the check makes sure enumeration constant names conform to the selected casing โจEnumConstantPrefixโ when defined, the check makes enumeration constant names get prefixed with the given value (regardless of casing) ๐EnumConstantIgnoredRegexpโ identifier naming checks won't be enforced for enumeration constant names matching this regular expression ๐ญEnumConstantSuffixโ when defined, the check makes enumeration constant names get the given suffix added (regardless of casing) ๐ฆEnumConstantHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type ๐
For example using values of:
EnumConstantCaseoflower_caseEnumConstantPrefixofpre_EnumConstantSuffixof_postEnumConstantHungarianPrefixofOn
It identifies and/or transforms enumeration constant names as follows:
Before:
enum FOO { One, Two, Three };
After:
enum FOO { pre_One_post, pre_Two_post, pre_Three_post };
FunctionCaseโ when defined, the check makes sure function names conform to the selected casing ๐FunctionPrefixโ when defined, the check makes function names get prefixed with the given value (regardless of casing) ๐FunctionIgnoredRegexpโ identifier naming checks won't be enforced for function names matching this regular expression ๐FunctionSuffixโ when defined, the check makes function names get the given suffix added (regardless of casing) ๐ง
For example using values of:
FunctionCaseoflower_caseFunctionPrefixofpre_FunctionSuffixof_post
It identifies and/or transforms function names as follows:
Before:
char MY_Function_string();
After:
char pre_my_function_string_post();
GetConfigPerFileโ whentrue, the check goes looking for the configuration where an identifier is declared. Honestly iconic when included header files are using a whole different style. Default value istrue๐ฅGlobalConstantCaseโ when defined, the check makes sure global constant names conform to the selected casing โจGlobalConstantPrefixโ when defined, the check makes global constant names get prefixed with the given value (regardless of casing) ๐GlobalConstantIgnoredRegexpโ identifier naming checks won't be enforced for global constant names matching this regular expression ๐GlobalConstantSuffixโ when defined, the check makes global constant names get the given suffix added (regardless of casing) ๐GlobalConstantHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type ๐
For example using values of:
GlobalConstantCaseoflower_caseGlobalConstantPrefixofpre_GlobalConstantSuffixof_postGlobalConstantHungarianPrefixofOn
It identifies and/or transforms global constant names as follows:
Before:
unsigned const MyConstGlobal_array[] = {1, 2, 3};
After:
unsigned const pre_myconstglobal_array_post[] = {1, 2, 3};
GlobalConstantPointerCaseโ when defined, the check makes sure global constant pointer names conform to the selected casing ๐ฆGlobalConstantPointerPrefixโ when defined, the check makes global constant pointer names get prefixed with the given value (regardless of casing) ๐GlobalConstantPointerIgnoredRegexpโ identifier naming checks won't be enforced for global constant pointer names matching this regular expression ๐ญGlobalConstantPointerSuffixโ when defined, the check makes global constant pointer names get the given suffix added (regardless of casing) โญGlobalConstantPointerHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type ๐
For example using values of:
GlobalConstantPointerCaseoflower_caseGlobalConstantPointerPrefixofpre_GlobalConstantPointerSuffixof_postGlobalConstantPointerHungarianPrefixofOn
It identifies and/or transforms global constant pointer names as follows:
Before:
int *const MyConstantGlobalPointer = nullptr;
After:
int *const pre_myconstantglobalpointer_post = nullptr;
GlobalFunctionCaseโ when defined, the check makes sure global function names conform to the selected casing ๐GlobalFunctionPrefixโ when defined, the check makes global function names get prefixed with the given value (regardless of casing) ๐GlobalFunctionIgnoredRegexpโ identifier naming checks won't be enforced for global function names matching this regular expression ๐GlobalFunctionSuffixโ when defined, the check makes global function names get the given suffix added (regardless of casing) ๐ง
For example using values of:
GlobalFunctionCaseoflower_caseGlobalFunctionPrefixofpre_GlobalFunctionSuffixof_post
It identifies and/or transforms global function names as follows:
Before:
void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);
After:
void pre_global_function_post(int PARAMETER_1, int const CONST_parameter);
GlobalPointerCaseโ when defined, the check makes sure global pointer names conform to the selected casing โจGlobalPointerPrefixโ when defined, the check makes global pointer names get prefixed with the given value (regardless of casing) ๐GlobalPointerIgnoredRegexpโ identifier naming checks won't be enforced for global pointer names matching this regular expression ๐GlobalPointerSuffixโ when defined, the check makes global pointer names get the given suffix added (regardless of casing) ๐GlobalPointerHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type ๐
For example using values of:
GlobalPointerCaseoflower_caseGlobalPointerPrefixofpre_GlobalPointerSuffixof_postGlobalPointerHungarianPrefixofOn
It identifies and/or transforms global pointer names as follows:
Before:
int *GLOBAL3;
After:
int *pre_global3_post;
GlobalVariableCaseโ when defined, the check makes sure global variable names conform to the selected casing ๐ฆGlobalVariablePrefixโ when defined, the check makes global variable names get prefixed with the given value (regardless of casing) ๐GlobalVariableIgnoredRegexpโ identifier naming checks won't be enforced for global variable names matching this regular expression ๐ญGlobalVariableSuffixโ when defined, the check makes global variable names get the given suffix added (regardless of casing) โญGlobalVariableHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type ๐
For example using values of:
GlobalVariableCaseoflower_caseGlobalVariablePrefixofpre_GlobalVariableSuffixof_postGlobalVariableHungarianPrefixofOn
It identifies and/or transforms global variable names as follows:
Before:
int GLOBAL3;
After:
int pre_global3_post;
IgnoreMainLikeFunctionsโ set this totrueand functions rockin' a signature that's basicallymainorwmain's twin won't get their parameter names policed anymore. Default value?false. ๐InlineNamespaceCaseโ when you define it, the check makes sure inline namespace names match the casing you picked. โจInlineNamespacePrefixโ when defined, the check makes inline namespace names get prefixed with the value you give it (regardless of casing, bestie).InlineNamespaceIgnoredRegexpโ identifier naming checks just won't be enforced for inline namespace names that match this regular expression. ๐InlineNamespaceSuffixโ when defined, the check makes inline namespace names get the suffix you give it (regardless of casing, periodt).
For example, using values of:
InlineNamespaceCaseoflower_caseInlineNamespacePrefixofpre_InlineNamespaceSuffixof_post
It identifies and/or transforms inline namespace names like this:
Before:
namespace FOO_NS {
inline namespace InlineNamespace {
...
}
} // namespace FOO_NS
After:
namespace FOO_NS {
inline namespace pre_inlinenamespace_post {
...
}
} // namespace FOO_NS
LocalConstantCaseโ when defined, the check ensures local constant names conform to the selected casing. ๐ธLocalConstantPrefixโ when defined, the check ensures local constant names get prefixed with the given value (regardless of casing).LocalConstantIgnoredRegexpโ identifier naming checks won't be enforced for local constant names matching this regular expression.LocalConstantSuffixโ when defined, the check ensures local constant names get the suffix with the given value (regardless of casing).LocalConstantHungarianPrefixโ when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type. ๐
For example, using values of:
LocalConstantCaseoflower_caseLocalConstantPrefixofpre_LocalConstantSuffixof_postLocalConstantHungarianPrefixofOn
It identifies and/or transforms local constant names like this:
Before:
void foo() { int const local_Constant = 3; }
After:
void foo() { int const pre_local_constant_post = 3; }
LocalConstantPointerCaseโ when defined, the check ensures local constant pointer names conform to the selected casing.LocalConstantPointerPrefixโ when defined, the check ensures local constant pointer names get prefixed with the given value (regardless of casing).LocalConstantPointerIgnoredRegexpโ identifier naming checks won't be enforced for local constant pointer names matching this regular expression.LocalConstantPointerSuffixโ when defined, the check ensures local constant pointer names get the suffix with the given value (regardless of casing).LocalConstantPointerHungarianPrefixโ when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.
For example, using values of:
LocalConstantPointerCaseoflower_caseLocalConstantPointerPrefixofpre_LocalConstantPointerSuffixof_postLocalConstantPointerHungarianPrefixofOn
It identifies and/or transforms local constant pointer names like this:
Before:
void foo() { int const *local_Constant = 3; }
After:
void foo() { int const *pre_local_constant_post = 3; }
LocalPointerCaseโ when defined, the check ensures local pointer names conform to the selected casing.LocalPointerPrefixโ when defined, the check ensures local pointer names get prefixed with the given value (regardless of casing).LocalPointerIgnoredRegexpโ identifier naming checks won't be enforced for local pointer names matching this regular expression.LocalPointerSuffixโ when defined, the check ensures local pointer names get the suffix with the given value (regardless of casing).LocalPointerHungarianPrefixโ when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type. ๐ฆ
For example, using values of:
LocalPointerCaseoflower_caseLocalPointerPrefixofpre_LocalPointerSuffixof_postLocalPointerHungarianPrefixofOn
It identifies and/or transforms local pointer names like this:
Before:
void foo() { int *local_Constant; }
After:
void foo() { int *pre_local_constant_post; }
LocalVariableCaseโ when defined, the check ensures local variable names conform to the selected casing.LocalVariablePrefixโ when defined, the check ensures local variable names get prefixed with the given value (regardless of casing).LocalVariableIgnoredRegexpโ identifier naming checks won't be enforced for local variable names matching this regular expression.
For example, using values of:
LocalVariableCaseofCamelCaseLocalVariableIgnoredRegexpof\w{1,2}
It will exclude variables with a length less than or equal to 2 from the camel case check applied to the other variables. lowkey genius ๐ง
LocalVariableSuffixโ when defined, the check ensures local variable names get the suffix with the given value (regardless of casing).LocalVariableHungarianPrefixโ when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.
For example, using values of:
LocalVariableCaseoflower_caseLocalVariablePrefixofpre_LocalVariableSuffixof_postLocalVariableHungarianPrefixofOn
It identifies and/or transforms local variable names like this:
Before:
void foo() { int local_Constant; }
After:
void foo() { int pre_local_constant_post; }
MacroDefinitionCaseโ when defined, the check ensures macro definitions conform to the selected casing.MacroDefinitionPrefixโ when defined, the check ensures macro definitions get prefixed with the given value (regardless of casing).MacroDefinitionIgnoredRegexpโ identifier naming checks won't be enforced for macro definitions matching this regular expression.MacroDefinitionSuffixโ when defined, the check ensures macro definitions get the suffix with the given value (regardless of casing).
For example, using values of:
MacroDefinitionCaseoflower_caseMacroDefinitionPrefixofpre_MacroDefinitionSuffixof_post
It identifies and/or transforms macro definitions like this:
Before:
#define MY_MacroDefinition
After:
#define pre_my_macro_definition_post
Note: this will not warn on builtin macros or macros defined on the command line using the -D flag. ๐
MemberCaseโ when defined, the check ensures member names conform to the selected casing.MemberPrefixโ when defined, the check ensures member names get prefixed with the given value (regardless of casing).MemberIgnoredRegexpโ identifier naming checks won't be enforced for member names matching this regular expression.MemberSuffixโ when defined, the check ensures member names get the suffix with the given value (regardless of casing).MemberHungarianPrefixโ when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.
For example, using values of:
MemberCaseoflower_caseMemberPrefixofpre_MemberSuffixof_postMemberHungarianPrefixofOn
It identifies and/or transforms member names like this:
Before:
class Foo {
char MY_ConstMember_string[4];
}
After:
class Foo {
char pre_my_constmember_string_post[4];
}
MethodCaseโ when defined, the check ensures method names conform to the selected casing.MethodPrefixโ when defined, the check ensures method names get prefixed with the given value (regardless of casing).MethodIgnoredRegexpโ identifier naming checks won't be enforced for method names matching this regular expression.MethodSuffixโ when defined, the check ensures method names get the suffix with the given value (regardless of casing).
For example, using values of:
MethodCaseoflower_caseMethodPrefixofpre_MethodSuffixof_post
It identifies and/or transforms method names like this:
Before:
class Foo {
char MY_Method_string();
}
After:
class Foo {
char pre_my_method_string_post();
}
NamespaceCaseโ when defined, the check ensures namespace names conform to the selected casing.NamespacePrefixโ when defined, the check ensures namespace names get prefixed with the given value (regardless of casing).NamespaceIgnoredRegexpโ identifier naming checks won't be enforced for namespace names matching this regular expression.NamespaceSuffixโ when defined, the check ensures namespace names get the suffix with the given value (regardless of casing).
For example, using values of:
NamespaceCaseoflower_caseNamespacePrefixofpre_NamespaceSuffixof_post
It identifies and/or transforms namespace names like this:
Before:
namespace FOO_NS {
...
}
After:
namespace pre_foo_ns_post {
...
}
ParameterCaseโ when defined, the check ensures parameter names conform to the selected casing.ParameterPrefixโ when defined, the check ensures parameter names get prefixed with the given value (regardless of casing).ParameterIgnoredRegexpโ identifier naming checks won't be enforced for parameter names matching this regular expression.
๐ธ ParameterSuffix
When you set this, the check makes sure parameter names get the suffix you give it slapped on the end (regardless of casing, bestie โ case is not the vibe check here).
๐ ParameterHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type. iykyk ๐ซถ
So like, if you're rocking values of:
ParameterCaseoflower_caseParameterPrefixofpre_ParameterSuffixof_postParameterHungarianPrefixofOn
...then it identifies and/or transforms parameter names like this:
Before:
void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);
After:
void GLOBAL_FUNCTION(int pre_parameter_post, int const CONST_parameter);
Look at that glow-up ๐ญโจ
๐ฆ ParameterPackCase
When defined, the check makes sure parameter pack names conform to the selected casing. periodt.
๐ ParameterPackPrefix
When defined, the check makes sure parameter pack names get prefixed with the given value (regardless of casing).
๐
ParameterPackIgnoredRegexp
Identifier naming checks won't be enforced for parameter pack names matching this regular expression. So if your pack name matches, it's off the hook ๐
โญ ParameterPackSuffix
When defined, the check makes sure parameter pack names get the given suffix added on (regardless of casing).
For example using values of:
ParameterPackCaseoflower_caseParameterPackPrefixofpre_ParameterPackSuffixof_post
...it identifies and/or transforms parameter pack names like this:
Before:
template <typename... TYPE_parameters> {
void FUNCTION(int... TYPE_parameters);
}
After:
template <typename... TYPE_parameters> {
void FUNCTION(int... pre_type_parameters_post);
}
No cap, that's a full makeover ๐
๐ PointerParameterCase
When defined, the check makes sure pointer parameter names conform to the selected casing.
๐ PointerParameterPrefix
When defined, the check makes sure pointer parameter names get prefixed with the given value (regardless of casing).
๐ง PointerParameterIgnoredRegexp
Identifier naming checks won't be enforced for pointer parameter names matching this regular expression. Matching names get a pass, fr fr.
๐ฅ PointerParameterSuffix
When defined, the check makes sure pointer parameter names get the given suffix added (regardless of casing).
๐ PointerParameterHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type.
For example using values of:
PointerParameterCaseoflower_casePointerParameterPrefixofpre_PointerParameterSuffixof_postPointerParameterHungarianPrefixofOn
...it identifies and/or transforms pointer parameter names like this:
Before:
void FUNCTION(int *PARAMETER);
After:
void FUNCTION(int *pre_parameter_post);
The pointer stays, the naming slays ๐ซ
๐ PrivateMemberCase
When defined, the check makes sure private member names conform to the selected casing.
๐ PrivateMemberPrefix
When defined, the check makes sure private member names get prefixed with the given value (regardless of casing).
๐ฆ PrivateMemberIgnoredRegexp
Identifier naming checks won't be enforced for private member names matching this regular expression.
โจ PrivateMemberSuffix
When defined, the check makes sure private member names get the given suffix added (regardless of casing).
๐ธ PrivateMemberHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type.
For example using values of:
PrivateMemberCaseoflower_casePrivateMemberPrefixofpre_PrivateMemberSuffixof_postPrivateMemberHungarianPrefixofOn
...it identifies and/or transforms private member names like this:
Before:
class Foo {
private:
int Member_Variable;
}
After:
class Foo {
private:
int pre_member_variable_post;
}
Private but make it named ๐
๐ซถ PrivateMethodCase
When defined, the check makes sure private method names conform to the selected casing.
๐ PrivateMethodPrefix
When defined, the check makes sure private method names get prefixed with the given value (regardless of casing).
๐ PrivateMethodIgnoredRegexp
Identifier naming checks won't be enforced for private method names matching this regular expression.
โญ PrivateMethodSuffix
When defined, the check makes sure private method names get the given suffix added (regardless of casing).
For example using values of:
PrivateMethodCaseoflower_casePrivateMethodPrefixofpre_PrivateMethodSuffixof_post
...it identifies and/or transforms private method names like this:
Before:
class Foo {
private:
int Member_Method();
}
After:
class Foo {
private:
int pre_member_method_post();
}
๐ ProtectedMemberCase
When defined, the check makes sure protected member names conform to the selected casing.
๐ง ProtectedMemberPrefix
When defined, the check makes sure protected member names get prefixed with the given value (regardless of casing).
๐ ProtectedMemberIgnoredRegexp
Identifier naming checks won't be enforced for protected member names matching this regular expression.
๐ฅ ProtectedMemberSuffix
When defined, the check makes sure protected member names get the given suffix added (regardless of casing).
๐ ProtectedMemberHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type.
For example using values of:
ProtectedMemberCaseoflower_caseProtectedMemberPrefixofpre_ProtectedMemberSuffixof_postProtectedMemberHungarianPrefixofOn
...it identifies and/or transforms protected member names like this:
Before:
class Foo {
protected:
int Member_Variable;
}
After:
class Foo {
protected:
int pre_member_variable_post;
}
๐ซ ProtectedMethodCase
When defined, the check makes sure protected method names conform to the selected casing.
๐ธ ProtectedMethodPrefix
When defined, the check makes sure protected method names get prefixed with the given value (regardless of casing).
๐ฆ ProtectedMethodIgnoredRegexp
Identifier naming checks won't be enforced for protected method names matching this regular expression.
๐ ProtectedMethodSuffix
When defined, the check makes sure protected method names get the given suffix added (regardless of casing).
For example using values of:
ProtectedMethodCaseoflower_caseProtectedMethodPrefixofpre_ProtectedMethodSuffixof_post
...it identifies and/or transforms protect method names like this:
Before:
class Foo {
protected:
int Member_Method();
}
After:
class Foo {
protected:
int pre_member_method_post();
}
โญ PublicMemberCase
When defined, the check makes sure public member names conform to the selected casing.
๐ PublicMemberPrefix
When defined, the check makes sure public member names get prefixed with the given value (regardless of casing).
๐ซถ PublicMemberIgnoredRegexp
Identifier naming checks won't be enforced for public member names matching this regular expression.
โจ PublicMemberSuffix
When defined, the check makes sure public member names get the given suffix added (regardless of casing).
๐
PublicMemberHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type.
For example using values of:
PublicMemberCaseoflower_casePublicMemberPrefixofpre_PublicMemberSuffixof_postPublicMemberHungarianPrefixofOn
...it identifies and/or transforms public member names like this:
Before:
class Foo {
public:
int Member_Variable;
}
After:
class Foo {
public:
int pre_member_variable_post;
}
Public and looking iconic ๐
๐ PublicMethodCase
When defined, the check makes sure public method names conform to the selected casing.
๐ง PublicMethodPrefix
When defined, the check makes sure public method names get prefixed with the given value (regardless of casing).
๐ PublicMethodIgnoredRegexp
Identifier naming checks won't be enforced for public method names matching this regular expression.
๐ฅ PublicMethodSuffix
When defined, the check makes sure public method names get the given suffix added (regardless of casing).
For example using values of:
PublicMethodCaseoflower_casePublicMethodPrefixofpre_PublicMethodSuffixof_post
...it identifies and/or transforms public method names like this:
Before:
class Foo {
public:
int Member_Method();
}
After:
class Foo {
public:
int pre_member_method_post();
}
๐ ScopedEnumConstantCase
When defined, the check makes sure scoped enum constant names conform to the selected casing.
๐ ScopedEnumConstantPrefix
When defined, the check makes sure scoped enum constant names get prefixed with the given value (regardless of casing).
๐ ScopedEnumConstantIgnoredRegexp
Identifier naming checks won't be enforced for scoped enum constant names matching this regular expression.
๐ธ ScopedEnumConstantSuffix
When defined, the check makes sure scoped enum constant names get the given suffix added (regardless of casing).
๐ฆ ScopedEnumConstantHungarianPrefix
When enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type.
For example using values of:
ScopedEnumConstantCaseoflower_caseScopedEnumConstantPrefixofpre_ScopedEnumConstantSuffixof_postScopedEnumConstantHungarianPrefixofOn
Identifies and/or transforms enumeration constant names as follows:
Before:
enum class FOO { One, Two, Three };
After:
enum class FOO { pre_One_post, pre_Two_post, pre_Three_post };
StaticConstantCaseโ set it and the check makes sure static constant names match the casing you picked ๐StaticConstantPrefixโ set it and the check slaps the given value on the front of your static constant names (casing does not matter here, it just prepends) โจStaticConstantIgnoredRegexpโ identifier naming checks straight-up won't be enforced for static constant names that match this regular expression ๐ซถStaticConstantSuffixโ set it and the check adds the given value as a suffix on static constant names (regardless of casing) ๐StaticConstantHungarianPrefixโ when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on its declared type ๐
For example using values of:
StaticConstantCaseoflower_caseStaticConstantPrefixofpre_StaticConstantSuffixof_postStaticConstantHungarianPrefixofOn
Identifies and/or transforms static constant names as follows:
Before:
static unsigned const MyConstStatic_array[] = {1, 2, 3};
After:
static unsigned const pre_myconststatic_array_post[] = {1, 2, 3};
StaticVariableCaseโ set it and the check makes sure static variable names conform to the selected casing ๐ธStaticVariablePrefixโ set it and static variable names get prefixed with the given value (regardless of casing) ๐StaticVariableIgnoredRegexpโ identifier naming checks won't be enforced for static variable names matching this regular expression ๐StaticVariableSuffixโ set it and static variable names get the given value added as a suffix (regardless of casing) ๐ฆStaticVariableHungarianPrefixโ when enabled, the check ensures the declared identifier has a Hungarian notation prefix based on the declared type โญ
For example using values of:
StaticVariableCaseoflower_caseStaticVariablePrefixofpre_StaticVariableSuffixof_postStaticVariableHungarianPrefixofOn
Identifies and/or transforms static variable names as follows:
Before:
static unsigned MyStatic_array[] = {1, 2, 3};
After:
static unsigned pre_mystatic_array_post[] = {1, 2, 3};
StructCaseโ set it and the check makes sure struct names conform to the selected casing ๐งStructPrefixโ set it and struct names get prefixed with the given value (regardless of casing) โจStructIgnoredRegexpโ identifier naming checks won't be enforced for struct names matching this regular expression ๐ซถStructSuffixโ set it and struct names get the given value added as a suffix (regardless of casing) ๐
For example using values of:
StructCaseoflower_caseStructPrefixofpre_StructSuffixof_post
Identifies and/or transforms struct names as follows:
Before:
struct FOO {
FOO();
~FOO();
};
After:
struct pre_foo_post {
pre_foo_post();
~pre_foo_post();
};
TemplateParameterCaseโ set it and the check makes sure template parameter names conform to the selected casing ๐TemplateParameterPrefixโ set it and template parameter names get prefixed with the given value (regardless of casing) ๐TemplateParameterIgnoredRegexpโ identifier naming checks won't be enforced for template parameter names matching this regular expression ๐TemplateParameterSuffixโ set it and template parameter names get the given value added as a suffix (regardless of casing) ๐ฅ
For example using values of:
TemplateParameterCaseoflower_caseTemplateParameterPrefixofpre_TemplateParameterSuffixof_post
Identifies and/or transforms template parameter names as follows:
Before:
template <typename T> class Foo {};
After:
template <typename pre_t_post> class Foo {};
TemplateTemplateParameterCaseโ set it and the check makes sure template template parameter names conform to the selected casing ๐TemplateTemplateParameterPrefixโ set it and template template parameter names get prefixed with the given value (regardless of casing) ๐TemplateTemplateParameterIgnoredRegexpโ identifier naming checks won't be enforced for template template parameter names matching this regular expression ๐TemplateTemplateParameterSuffixโ set it and template template parameter names get the given value added as a suffix (regardless of casing) ๐ฆ
For example using values of:
TemplateTemplateParameterCaseoflower_caseTemplateTemplateParameterPrefixofpre_TemplateTemplateParameterSuffixof_post
Identifies and/or transforms template template parameter names as follows:
Before:
template <template <typename> class TPL_parameter, int COUNT_params,
typename... TYPE_parameters>
After:
template <template <typename> class pre_tpl_parameter_post, int COUNT_params,
typename... TYPE_parameters>
TypeAliasCaseโ set it and the check makes sure type alias names conform to the selected casing โจTypeAliasPrefixโ set it and type alias names get prefixed with the given value (regardless of casing) ๐TypeAliasIgnoredRegexpโ identifier naming checks won't be enforced for type alias names matching this regular expression ๐ซถTypeAliasSuffixโ set it and type alias names get the given value added as a suffix (regardless of casing) ๐
For example using values of:
TypeAliasCaseoflower_caseTypeAliasPrefixofpre_TypeAliasSuffixof_post
Identifies and/or transforms type alias names as follows:
Before:
using MY_STRUCT_TYPE = my_structure;
After:
using pre_my_struct_type_post = my_structure;
TypedefCaseโ set it and the check makes sure typedef names conform to the selected casing ๐ธTypedefPrefixโ set it and typedef names get prefixed with the given value (regardless of casing) ๐งTypedefIgnoredRegexpโ identifier naming checks won't be enforced for typedef names matching this regular expression ๐TypedefSuffixโ set it and typedef names get the given value added as a suffix (regardless of casing) โญ
For example using values of:
TypedefCaseoflower_caseTypedefPrefixofpre_TypedefSuffixof_post
Identifies and/or transforms typedef names as follows:
Before:
typedef int MYINT;
After:
typedef int pre_myint_post;
TypeTemplateParameterCaseโ set it and the check makes sure type template parameter names conform to the selected casing ๐TypeTemplateParameterPrefixโ set it and type template parameter names get prefixed with the given value (regardless of casing) ๐ฅTypeTemplateParameterIgnoredRegexpโ identifier naming checks won't be enforced for type template names matching this regular expression ๐TypeTemplateParameterSuffixโ set it and type template parameter names get the given value added as a suffix (regardless of casing) ๐
For example using values of:
TypeTemplateParameterCaseoflower_caseTypeTemplateParameterPrefixofpre_TypeTemplateParameterSuffixof_post
Identifies and/or transforms type template parameter names as follows:
Before:
template <template <typename> class TPL_parameter, int COUNT_params,
typename... TYPE_parameters>
After:
template <template <typename> class TPL_parameter, int COUNT_params,
typename... pre_type_parameters_post>
UnionCaseโ set it and the check makes sure union names conform to the selected casing ๐UnionPrefixโ set it and union names get prefixed with the given value (regardless of casing) ๐UnionIgnoredRegexpโ identifier naming checks won't be enforced for union names matching this regular expression ๐ฆUnionSuffixโ set it and union names get the given value added as a suffix (regardless of casing) โจ
For example using values of:
UnionCaseoflower_caseUnionPrefixofpre_UnionSuffixof_post
Identifies and/or transforms union names as follows:
Before:
union FOO {
int a;
char b;
};
After:
union pre_foo_post {
int a;
char b;
};
ValueTemplateParameterCaseโ set it and the check makes sure value template parameter names conform to the selected casing ๐ValueTemplateParameterPrefixโ set it and value template parameter names get prefixed with the given value (regardless of casing) ๐ValueTemplateParameterIgnoredRegexpโ identifier naming checks won't be enforced for value template parameter names matching this regular expression ๐ซถValueTemplateParameterSuffixโ set it and value template parameter names get the given value added as a suffix (regardless of casing) ๐
For example using values of:
ValueTemplateParameterCaseoflower_caseValueTemplateParameterPrefixofpre_ValueTemplateParameterSuffixof_post
Identifies and/or transforms value template parameter names as follows:
Before:
template <template <typename> class TPL_parameter, int COUNT_params,
typename... TYPE_parameters>
After:
template <template <typename> class TPL_parameter, int pre_count_params_post,
typename... TYPE_parameters>
VariableCaseโ set this and the check makes sure your variable names match whatever casing you picked. that's it. that's the whole job. ๐VariablePrefixโ set this and variable names get the value you give here slapped on the front (casing of the prefix doesn't matter, it just goes on there).VariableIgnoredRegexpโ variable names that match this regex get a full pass from the identifier naming checks. they're exempt. leave them alone bestie ๐ซถVariableSuffixโ set this and variable names get the given value tacked onto the end (again, the casing of it is irrelevant, it's just appended).VariableHungarianPrefixโ turn this on and the check makes sure your declared identifier has a Hungarian notation prefix, picked based on the type it was declared with. yes, based on the declared type. it's looking at your types, not your vibes โจ
So like, if you set:
VariableCasetolower_caseVariablePrefixtopre_VariableSuffixto_postVariableHungarianPrefixtoOn
then it'll find and/or transform your variable names and you get this whole glow-up:
Before:
unsigned MyVariable;
After:
unsigned pre_myvariable_post;
look at that. lowercase, prefix, suffix, all of it. she ate ๐
VirtualMethodCaseโ when defined, the check makes sure virtual method names conform to the casing you selected.VirtualMethodPrefixโ when defined, the check makes sure virtual method names get the given value added as a prefix (regardless of casing, bestie, we been over this).VirtualMethodIgnoredRegexpโ identifier naming checks won't be enforced for virtual method names that match this regular expression. they're off the hook ๐ธVirtualMethodSuffixโ when defined, the check makes sure virtual method names get the given value added as a suffix (again: regardless of casing).
For example, with these values:
VirtualMethodCaseoflower_caseVirtualMethodPrefixofpre_VirtualMethodSuffixof_post
it identifies and/or transforms virtual method names like so:
Before:
class Foo {
public:
virtual int MemberFunction();
}
After:
class Foo {
public:
virtual int pre_member_function_post();
}
iconic. the method got the full makeover ๐
๐ง The default mapping table of Hungarian Notation ๐ง
Okay so, Hungarian notation. Let's talk about it because it's genuinely kinda fascinating and also a little unhinged ngl.
In Hungarian notation, a variable name starts with a group of lower-case letters which are mnemonics for the type or purpose of that variable, followed by whatever name the programmer has chosen. That last part is sometimes distinguished as the given name. so you've got your little type-lore prefix gang and then the actual name living its life after them. ๐
The first character of the given name can be capitalized to separate it from the type indicators โ see also CamelCase, they're cousins. Otherwise the case of this character denotes scope. yeah. the case is carrying scope information. that's the whole system. iykyk ๐ฆ
Before we get into the option lists, bestie, you need to see the default mapping table ๐ โ this is the Hungarian Notation cheat sheet, the one that turns a Decl into its prefix string. And yes, you can absolutely have your own style in the config file. Slay ๐
The C/C++ primitive squad (primitive type โ prefix):
int8_tโi8int16_tโi16int32_tโi32int64_tโi64uint8_tโu8uint16_tโu16uint32_tโu32uint64_tโu64char8_tโc8char16_tโc16char32_tโc32floatโfdoubleโdcharโcboolโb_Boolโbintโisize_tโnshortโssignedโiunsignedโulongโllong longโllunsigned longโullong doubleโldptrdiff_tโpwchar_tโwcshort intโsishortโs
The signed/unsigned long-form girlies (the middle column, type โ prefix):
signed intโsisigned shortโsssigned short intโssisigned long long intโsllisigned long longโsllsigned long intโslisigned longโslsignedโsunsigned long long intโulliunsigned long longโullunsigned long intโuliunsigned longโulunsigned short intโusiunsigned shortโusunsigned intโuiunsigned charโucunsignedโulong long intโllilong doubleโldlong longโlllong intโlilongโlptrdiff_tโpvoidโnone
The Microsoft Windows icon set ๐โจ (Microsoft type โ prefix):
BOOLโbBOOLEANโbBYTEโbyCHARโcUCHARโucSHORTโsUSHORTโusWORDโwDWORDโdwDWORD32โdw32DWORD64โdw64LONGโlULONGโulULONG32โul32ULONG64โul64ULONGLONGโullHANDLEโhINTโiINT8โi8INT16โi16INT32โi32INT64โi64UINTโuiUINT8โu8UINT16โu16UINT32โu32UINT64โu64PVOIDโp
There are, like, a bunch more trivial options for Hungarian Notation too โ think of these as the little sub-groups that keep your config from becoming a chaotic group chat:
HungarianNotation.General.*โ options that aren't belonging to any specific Decl. The free spirits ๐ฆHungarianNotation.CString.*โ options for NULL-terminated strings.HungarianNotation.DerivedType.*โ options for derived types.HungarianNotation.PrimitiveType.*โ options for primitive types.HungarianNotation.UserDefinedType.*โ options for user-defined types.
๐ Options for Hungarian Notation ๐
HungarianNotation.General.TreatStructAsClassHungarianNotation.DerivedType.Array๐ธHungarianNotation.DerivedType.Pointer๐ธHungarianNotation.DerivedType.FunctionPointer๐ธHungarianNotation.CString.CharPointer๐HungarianNotation.CString.CharArray๐HungarianNotation.CString.WideCharPointer๐HungarianNotation.CString.WideCharArray๐HungarianNotation.PrimitiveType.*โจHungarianNotation.UserDefinedType.*โจ
HungarianNotation.General.TreatStructAsClass ๐
okay so when this one's defined, the check treats the naming of a struct exactly like it would a class, periodt. The default value is false.
HungarianNotation.DerivedType.Array ๐ฆ
When it's defined, the check makes sure the variable name gets the prefix added โ the given string, bestie. Default prefix is a.
HungarianNotation.DerivedType.Pointer ๐ฆ
When defined, the check makes sure the variable name adds the prefix with the given string, no cap. Default prefix is p.
HungarianNotation.DerivedType.FunctionPointer ๐ฆ
When defined, the check makes sure the variable name adds the prefix with the given string โ default prefix is fn.
Before:
// Array
int DataArray[2] = {0};
// Pointer
void *DataBuffer = NULL;
// FunctionPointer
typedef void (*FUNC_PTR)();
FUNC_PTR FuncPtr = NULL;
After:
// Array
int aDataArray[2] = {0};
// Pointer
void *pDataBuffer = NULL;
// FunctionPointer
typedef void (*FUNC_PTR)();
FUNC_PTR fnFuncPtr = NULL;
HungarianNotation.CString.CharPointer ๐
When defined, the check makes sure the variable name adds the prefix with the given string. Default prefix is sz.
HungarianNotation.CString.CharArray ๐
Same deal โ when defined, the variable name gets the prefix with the given string. Default prefix is sz.
HungarianNotation.CString.WideCharPointer ๐
When defined, the check makes sure the variable name adds the prefix with the given string. Default prefix is wsz.
HungarianNotation.CString.WideCharArray ๐
When defined, the check makes sure the variable name adds the prefix with the given string. Default prefix is wsz.
Before:
// CharPointer
const char *NamePtr = "Name";
// CharArray
const char NameArray[] = "Name";
// WideCharPointer
const wchar_t *WideNamePtr = L"Name";
// WideCharArray
const wchar_t WideNameArray[] = L"Name";
After:
// CharPointer
const char *szNamePtr = "Name";
// CharArray
const char szNameArray[] = "Name";
// WideCharPointer
const wchar_t *wszWideNamePtr = L"Name";
// WideCharArray
const wchar_t wszWideNameArray[] = L"Name";
HungarianNotation.PrimitiveType.* โญ
When defined, the check makes sure the variable name of the involved primitive types adds the prefix with the given string. The default prefixes live in the default mapping table, iykyk.
HungarianNotation.UserDefinedType.* โญ
When defined, the check makes sure the variable name of the involved primitive types adds the prefix with the given string. The default prefixes are defined in the default mapping table too.
Before:
int8_t ValueI8 = 0;
int16_t ValueI16 = 0;
int32_t ValueI32 = 0;
int64_t ValueI64 = 0;
uint8_t ValueU8 = 0;
uint16_t ValueU16 = 0;
uint32_t ValueU32 = 0;
uint64_t ValueU64 = 0;
float ValueFloat = 0.0;
double ValueDouble = 0.0;
ULONG ValueUlong = 0;
DWORD ValueDword = 0;
After:
int8_t i8ValueI8 = 0;
int16_t i16ValueI16 = 0;
int32_t i32ValueI32 = 0;
int64_t i64ValueI64 = 0;
uint8_t u8ValueU8 = 0;
uint16_t u16ValueU16 = 0;
uint32_t u32ValueU32 = 0;
uint64_t u64ValueU64 = 0;
float fValueFloat = 0.0;
double dValueDouble = 0.0;
ULONG ulValueUlong = 0;
DWORD dwValueDword = 0;
readability-implicit-bool-cast ๐
okay heads up: this check has been renamed to readability-implicit-bool-conversion. so yeah, glow-up era, it goes by a new name now ๐ซถ
readability-implicit-bool-conversion ๐ฅ
This one can be used to find implicit conversions between built-in types and booleans. Depending on your use case it might just be a readability thing, or โ and this is the scary part โ it can point to potential bugs that stay unnoticed because implicit conversions are sneaky like that ๐.
Here's a real-world example of a bug that was literally hiding behind an implicit bool conversion:
class Foo {
int m_foo;
public:
void setFoo(bool foo) { m_foo = foo; } // warning: implicit conversion bool -> int
int getFoo() { return m_foo; }
};
void use(Foo& foo) {
bool value = foo.getFoo(); // warning: implicit conversion int -> bool
}
This code is the result of an unsuccessful refactoring, where the type of m_foo changed from bool to int. The programmer forgot to change all the occurrences of bool, and the remaining code is no longer correct โ yet it still compiles with zero visible warnings ๐ญ iconic in the worst way.
In addition to issuing warnings, fix-it hints are provided to help solve the reported issues. This can be used for improving readability of code, like this:
void conversionsToBool() {
float floating;
bool boolean = floating;
// ^ propose replacement: bool boolean = floating != 0.0f;
int integer;
if (integer) {}
// ^ propose replacement: if (integer != 0) {}
int* pointer;
if (!pointer) {}
// ^ propose replacement: if (pointer == nullptr) {}
while (1) {}
// ^ propose replacement: while (true) {}
}
void functionTakingInt(int param);
void conversionsFromBool() {
bool boolean;
functionTakingInt(boolean);
// ^ propose replacement: functionTakingInt(static_cast<int>(boolean));
functionTakingInt(true);
// ^ propose replacement: functionTakingInt(1);
}
In general, the following conversion types are checked:
- integer expression/literal to boolean (conversion from a single bit bitfield to boolean is explicitly allowed, since there's no ambiguity / information loss in this case),
- floating expression/literal to boolean,
- pointer/pointer to member/nullptr/NULL to boolean,
- boolean expression/literal to integer (conversion from boolean to a single bit bitfield is explicitly allowed),
- boolean expression/literal to floating.
The rules for generating fix-it hints are:
- in case of conversions from other built-in type to bool, an explicit comparison is proposed so it's super clear what exactly is being compared:
bool boolean = floating;is changed tobool boolean = floating == 0.0f;,- for other types, appropriate literals are used (
0,0u,0.0f,0.0,nullptr),
- in case of negated expressions conversion to bool, the proposed replacement with comparison is simplified:
if (!pointer)is changed toif (pointer == nullptr),
- in case of conversions from bool to other built-in types, an explicit
static_cast(or a C-style cast since C23) is proposed to make it clear that a conversion is taking place:int integer = boolean;is changed toint integer = static_cast<int>(boolean);,
- if the conversion is performed on type literals, an equivalent literal is proposed, according to what type is actually expected, for example:
functionTakingBool(0);is changed tofunctionTakingBool(false);,functionTakingInt(true);is changed tofunctionTakingInt(1);,- for other types, appropriate literals are used (
false,true,0,1,0u,1u,0.0f,1.0f,0.0,1.0f).
Some additional accommodations are made for pre-C++11 dialects:
falseliteral conversion to pointer is detected,- instead of
nullptrliteral,0is proposed as replacement.
Occurrences of implicit conversions inside macros and template instantiations are deliberately ignored, because honestly it's not clear how to deal with such cases ๐
Options ๐
AllowIntegerConditionsโ Whentrue, the check will allow conditional integer conversions. Default isfalse.AllowPointerConditionsโ Whentrue, the check will allow conditional pointer conversions. Default isfalse.UseUpperCaseLiteralSuffixโ Whentrue, the replacements will use an uppercase literal suffix in the provided fixes. Default isfalse.
Example
uint32_t foo;
if (foo) {}
// ^ propose replacement default: if (foo != 0u) {}
// ^ propose replacement with option `UseUpperCaseLiteralSuffix`: if (foo != 0U) {}
readability-inconsistent-declaration-parameter-name ๐ฆ
Finds function declarations that differ in parameter names. lowkey a lifesaver tbh.
Example:
// in foo.hpp:
void foo(int a, int b, int c);
// in foo.cpp:
void foo(int d, int e, int f); // warning
This check should help enforce consistency in large projects, where it's constantly happening that a function definition gets refactored โ parameter names changed and all โ but its declaration in the header file doesn't get updated. With this check we can easily find and correct those inconsistencies, keeping declaration and definition always in sync ๐
Unnamed parameters are allowed and are not taken into account when comparing function declarations, like so:
void foo(int a);
void foo(int); // no warning
One name is also allowed to be a case-insensitive prefix/suffix of the other:
void foo(int count);
void foo(int count_input) { // no warning
int count = adjustCount(count_input);
}
To help with refactoring, in some cases fix-it hints are generated to align parameter names to a single naming convention. This works with the assumption that the function definition is the most up-to-date version, since it directly references parameter names in its body. Example:
void foo(int a); // warning and fix-it hint (replace "a" to "b")
int foo(int b) { return b + 2; } // definition with use of "b"
In the case of multiple redeclarations or function template specializations, a warning is issued for every redeclaration or specialization inconsistent with the definition or the first declaration seen in a translation unit. so basically nobody slips through ๐
Options ๐ธ
IgnoreMacrosโ If this option is set totrue(default istrue), the check will not warn about names declared inside macros.Strictโ If this option is set totrue(default isfalse), then names must match exactly (or be absent). no wiggle room, periodt.
โจ readability-isolate-declaration โจ
okay so bestie, this check catches local variable declarations that declare more than one variable, and it tries to refactor your code into one statement per declaration. iconic behavior honestly ๐
The automatic code-transformation will use the same indentation as the original for every created statement and add a line break after each statement. It keeps the order of the variable declarations consistent, too. no chaos allowed ๐
void f() {
int * pointer = nullptr, value = 42, * const const_ptr = &value;
// This declaration will be diagnosed and transformed into:
// int * pointer = nullptr;
// int value = 42;
// int * const const_ptr = &value;
}
The check excludes places where it is necessary or common to declare multiple variables in one statement and there is no other way supported in the language. Please note that structured bindings are not considered. lowkey important ๐ซถ
// It is not possible to transform this declaration and doing the declaration
// before the loop will increase the scope of the variable 'Begin' and 'End'
// which is undesirable.
for (int Begin = 0, End = 100; Begin < End; ++Begin);
if (int Begin = 42, Result = some_function(Begin); Begin == Result);
// It is not possible to transform this declaration because the result is
// not functionality preserving as 'j' and 'k' would not be part of the
// 'if' statement anymore.
if (SomeCondition())
int i = 42, j = 43, k = function(i,j);
๐ง Limitations ๐ง
Global variables and member variables are excluded. bye ๐
The check currently does not support the automatic transformation of member-pointer-types. it's giving "not yet, bestie" ๐ญ
struct S {
int a;
const int b;
void f() {}
};
void f() {
// Only a diagnostic message is emitted
int S::*p = &S::a, S::*const q = &S::a;
}
Furthermore, the transformation is very cautious when it detects various kinds of macros or preprocessor directives in the range of the statement. In this case the transformation will not happen to avoid unexpected side-effects due to macros. safety first โจ
#define NULL 0
#define MY_NICE_TYPE int **
#define VAR_NAME(name) name##__LINE__
#define A_BUNCH_OF_VARIABLES int m1 = 42, m2 = 43, m3 = 44;
void macros() {
int *p1 = NULL, *p2 = NULL;
// Will be transformed to
// int *p1 = NULL;
// int *p2 = NULL;
MY_NICE_TYPE p3, v1, v2;
// Won't be transformed, but a diagnostic is emitted.
int VAR_NAME(v3),
VAR_NAME(v4),
VAR_NAME(v5);
// Won't be transformed, but a diagnostic is emitted.
A_BUNCH_OF_VARIABLES
// Won't be transformed, but a diagnostic is emitted.
int Unconditional,
#if CONFIGURATION
IfConfigured = 42,
#else
IfConfigured = 0;
#endif
// Won't be transformed, but a diagnostic is emitted.
}
๐ readability-magic-numbers ๐
Detects magic numbers, integer or floating point literals that are embedded in code and not introduced via constants or symbols. no mysterious numbers allowed, periodt ๐ฅ
Many coding guidelines advise replacing the magic values with symbolic constants to improve readability. Here are a few references:
- Rule ES.45: Avoid "magic constants"; use symbolic constants in C++ Core Guidelines <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-magic>
- Rule 5.1.1 Use symbolic names instead of literal values in code in High Integrity C++ <https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions>
- Item 17 in "C++ Coding Standards: 101 Rules, Guidelines and Best Practices" by Herb Sutter and Andrei Alexandrescu
- Chapter 17 in "Clean Code - A handbook of agile software craftsmanship." by Robert C. Martin
- Rule 20701 in "TRAIN REAL TIME DATA PROTOCOL Coding Rules" by Armin-Hagen Weiss, Bombardier
- <http://wiki.c2.com/?MagicNumber>
Examples of magic values:
template<typename T, size_t N>
struct CustomType {
T arr[N];
};
struct OtherType {
CustomType<int, 30> container;
}
CustomType<int, 30> values;
double circleArea = 3.1415926535 * radius * radius;
double totalCharge = 1.08 * itemPrice;
int getAnswer() {
return -3; // FILENOTFOUND
}
for (int mm = 1; mm <= 12; ++mm) {
std::cout << month[mm] << '\n';
}
Example with magic values refactored:
template<typename T, size_t N>
struct CustomType {
T arr[N];
};
const size_t NUMBER_OF_ELEMENTS = 30;
using containerType = CustomType<int, NUMBER_OF_ELEMENTS>;
struct OtherType {
containerType container;
}
containerType values;
double circleArea = M_PI * radius * radius;
const double TAX_RATE = 0.08; // or make it variable and read from a file
double totalCharge = (1.0 + TAX_RATE) * itemPrice;
int getAnswer() {
return E_FILE_NOT_FOUND;
}
for (int mm = 1; mm <= MONTHS_IN_A_YEAR; ++mm) {
std::cout << month[mm] << '\n';
}
For integral literals by default only 0 and 1 (and -1) integer values are accepted without a warning. This can be overridden with the IgnoredIntegerValues option. Negative values are accepted if their absolute value is present in the IgnoredIntegerValues list. bestie, read that twice ๐ซถ
As a special case for integral values, all powers of two can be accepted without warning by enabling the IgnorePowersOf2IntegerValues option.
For floating point literals by default the 0.0 floating point value is accepted without a warning. The set of ignored floating point literals can be configured using the IgnoredFloatingPointValues option. For each value in that set, the given string value is converted to a floating-point value representation used by the target architecture. If a floating-point literal value compares equal to one of the converted values, then that literal is not diagnosed by this check. Because floating-point equality is used to determine whether to diagnose or not, the user needs to be aware of the details of floating-point representations for any values that cannot be precisely represented for their target architecture. floating point said "it's complicated" ๐ญ
For each value in the IgnoredFloatingPointValues set, both the single-precision form and double-precision form are accepted (for example, if 3.14 is in the set, neither 3.14f nor 3.14 will produce a warning).
Scientific notation is supported for both source code input and option. Alternatively, the check for the floating point numbers can be disabled for all floating point values by enabling the IgnoreAllFloatingPointValues option.
Since values 0 and 0.0 are so common as the base counter of loops, or initialization values for sums, they are always accepted without warning, even if not present in the respective ignored values list. iconic exception honestly โจ
๐ Options ๐
IgnoredIntegerValuesโ Semicolon-separated list of magic positive integers that will be accepted without a warning. Default values are{1, 2, 3, 4}, and0is accepted unconditionally.IgnorePowersOf2IntegerValuesโ Boolean value indicating whether to accept all powers-of-two integer values without warning. Default value isfalse.IgnoredFloatingPointValuesโ Semicolon-separated list of magic positive floating point values that will be accepted without a warning. Default values are{1.0, 100.0}and0.0is accepted unconditionally.IgnoreAllFloatingPointValuesโ Boolean value indicating whether to accept all floating point values without warning. Default value isfalse.IgnoreBitFieldsWidthsโ Boolean value indicating whether to accept magic numbers as bit field widths without warning. This is useful for example for register definitions which are generated from hardware specifications. Default value istrue.IgnoreTypeAliasesโ Boolean value indicating whether to accept magic numbers intypedeforusingdeclarations. Default value isfalse.IgnoreUserDefinedLiteralsโ Boolean value indicating whether to accept magic numbers in user-defined literals. Default value isfalse.
๐ฆ readability-make-member-function-const ๐ฆ
Finds non-static member functions that can be made const because the functions don't use this in a non-const way. slay, make it const ๐
This check tries to annotate methods according to logical constness <https://isocpp.org/wiki/faq/const-correctness#logical-vs-physical-state> (not physical constness). Therefore, it will suggest to add a const qualifier to a non-const method only if this method does something that is already possible through the public interface on a const pointer to the object:
- reading a public member variable
- calling a public const-qualified member function
- returning const-qualified
this - passing const-qualified
thisas a parameter.
This check will also suggest to add a const qualifier to a non-const method if this method uses private data and functions in a limited number of ways where logical constness and physical constness coincide:
- reading a member variable of builtin type
Specifically, this check will not suggest to add a const to a non-const method if the method reads a private member variable of pointer type because that allows to modify the pointee which might not preserve logical constness. For the same reason, it does not allow to call private member functions or member functions on private member variables. ngl, pointer privacy is real ๐ญ
In addition, this check ignores functions that
- are declared
virtual - contain a
const_cast - are templated or part of a class template
- have an empty body
- do not (implicitly) use
thisat all (seereadability-convert-member-functions-to-static <>).
The following real-world examples will be preserved by the check:
class E1 {
Pimpl &getPimpl() const;
public:
int &get() {
// Calling a private member function disables this check.
return getPimpl()->i;
}
...
};
class E2 {
public:
const int *get() const;
// const_cast disables this check.
S *get() {
return const_cast<int*>(const_cast<const C*>(this)->get());
}
...
};
After applying modifications as suggested by the check, running the check again might find more opportunities to mark member functions const. it's giving "const cascade" โจ
๐ readability-math-missing-parentheses ๐
Check for missing parentheses in mathematical expressions that involve operators of different priorities. math said "use parentheses, bestie" ๐
Like, parentheses in math expressions are literally there to clarify the order of operations โ especially when you've got operators with different priorities hangin' out together. Lengthy or multiline expressions can obscure that order and lead to coding errors. IDEs can help by highlighting parentheses, and explicitly using them also shows what the developer had in mind when they wrote the expression. Having them there reduces ambiguity and errors, promoting clearer and more maintainable code. ๐
Before:
int x = 1 + 2 * 3 - 4 / 5;
After:
int x = 1 + (2 * 3) - (4 / 5);
readability-misleading-indentation
Correct indentation helps you understand code, periodt. When the syntactical structure and the indentation of the code don't match, that mismatch can hide serious problems. Missing braces can also make it significantly harder to read the code, so it's important to use braces.
The way to avoid a dangling else is to always check that an else belongs to the if that begins in the same column.
You can omit braces when your inner part of, say, an if statement has only one statement in it. Although in that case you should begin the next statement in the same column with the if.
Examples:
// Dangling else:
if (cond1)
if (cond2)
foo1();
else
foo2(); // Wrong indentation: else belongs to if(cond2) statement.
// Missing braces:
if (cond1)
foo1();
foo2(); // Not guarded by if(cond1).
Limitations
Note that this check only works as expected when the tabs or spaces are used consistently and not mixed. ๐ซถ
readability-misplaced-array-index
This check warns for unusual array index syntax, bestie.
The following code has unusual array index syntax:
void f(int *X, int Y) {
Y[X] = 0;
}
becomes
void f(int *X, int Y) {
X[Y] = 0;
}
The check warns about such unusual syntax for readability reasons:
- There are programmers that are not familiar with this unusual syntax.
- It is possible that variables are mixed up.
readability-named-parameter
Find functions with unnamed arguments. ๐
The check implements the following rule originating in the Google C++ Style Guide:
<https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions>
All parameters should have the same name in both the function declaration and definition. If a parameter is not utilized, its name can be commented out in a function definition.
int doingSomething(int a, int b, int c);
int doingSomething(int a, int b, int /*c*/) {
// Ok: the third param is not used
return a + b;
}
Corresponding cpplint.py check name: readability/function.
Options
InsertPlainNamesInForwardDeclsโ If set totrue, the check will insert parameter names without comments for forward declarations only. Otherwise, the check will insert parameter names as comments (e.g.,/*param*/). Default isfalse.
readability-non-const-parameter
The check finds function parameters of a pointer type that could be changed to point to a constant type instead.
When const is used properly, so many mistakes can be avoided. Advantages when using const properly:
- prevent unintentional modification of data;
- get additional warnings such as using uninitialized data;
- make it easier for developers to see possible side effects.
This check is not strict about constness โ it only warns when the constness will make the function interface safer. โจ
// warning here; the declaration "const char *p" would make the function
// interface safer.
char f1(char *p) {
return *p;
}
// no warning; the declaration could be more const "const int * const p" but
// that does not make the function interface safer.
int f2(const int *p) {
return *p;
}
// no warning; making x const does not make the function interface safer
int f3(int x) {
return x;
}
// no warning; Technically, *p can be const ("const struct S *p"). But making
// *p const could be misleading. People might think that it's safe to pass
// const data to this function.
struct S { int *a; int *b; };
int f3(struct S *p) {
*(p->a) = 0;
}
// no warning; p is referenced by an lvalue.
void f4(int *p) {
int &x = *p;
}
readability-operators-representation
Enforces consistent token representation for invoked binary, unary and overloaded operators in C++ code. The check supports both traditional and alternative representations of operators, such as && and and, || and or, and so on.
In the realm of C++ programming, developers have the option to choose between two distinct representations for operators: traditional token representation and alternative token representation. Traditional tokens utilize symbols, such as &&, ||, and !, while alternative tokens employ more descriptive words like and, or, and not.
In the following mapping table, a comprehensive list of traditional and alternative tokens, along with their corresponding representations, is presented:
Token Representation Mapping Table
&&โand&=โand_eq&โbitand|โbitor~โcompl!โnot!=โnot_eq||โor|=โor_eq^โxor^=โxor_eq
Example
// Traditional Token Representation:
if (!a||!b)
{
// do something
}
// Alternative Token Representation:
if (not a or not b)
{
// do something
}
Options
Because each representation has its own distinct benefits and drawbacks, the default configuration doesn't enforce either. Explicit configuration is needed, ngl.
To configure the check to enforce Traditional Token Representation for all operators, set options to &&;&=;&;|;~;!;!=;||;|=;^;^=.
To configure the check to enforce Alternative Token Representation for all operators, set options to and;and_eq;bitand;bitor;compl;not;not_eq;or;or_eq;xor;xor_eq.
Developers do not need to enforce all operators, and can mix the representations as desired by specifying a semicolon-separated list of both traditional and alternative tokens in the configuration, such as and;||;not. ๐ซ
BinaryOperatorsโ This option allows you to specify a semicolon-separated list of binary operators for which you want to enforce specific token representation. The default value is empty string.OverloadedOperatorsโ This option allows you to specify a semicolon-separated list of overloaded operators for which you want to enforce specific token representation. The default value is empty string.
readability-qualified-auto
Adds pointer qualifications to auto-typed variables that are deduced to pointers.
LLVM Coding Standards <https://llvm.org/docs/CodingStandards.html#beware-unnecessary-copies-with-auto> advises to make it obvious if an auto typed variable is a pointer. This check will transform auto to auto * when the type is deduced to be a pointer.
for (auto Data : MutatablePtrContainer) {
change(*Data);
}
for (auto Data : ConstantPtrContainer) {
observe(*Data);
}
Would be transformed into:
for (auto *Data : MutatablePtrContainer) {
change(*Data);
}
for (const auto *Data : ConstantPtrContainer) {
observe(*Data);
}
Note const volatile qualified types will retain their const and volatile qualifiers. Pointers to pointers will not be fully qualified.
const auto Foo = cast<int *>(Baz1);
const auto Bar = cast<const int *>(Baz2);
volatile auto FooBar = cast<int *>(Baz3);
auto BarFoo = cast<int **>(Baz4);
Would be transformed into:
auto *const Foo = cast<int *>(Baz1);
const auto *const Bar = cast<const int *>(Baz2);
auto *volatile FooBar = cast<int *>(Baz3);
auto *BarFoo = cast<int **>(Baz4);
Options
AddConstToQualifiedโ When set totruethe check will addconstqualifiers to variables defined asauto *orauto &when applicable. Default value istrue. ๐ธ
auto Foo1 = cast<const int *>(Bar1);
auto *Foo2 = cast<const int *>(Bar2);
auto &Foo3 = cast<const int &>(Bar3);
If AddConstToQualified is set to false, it will be transformed into:
const auto *Foo1 = cast<const int *>(Bar1);
auto *Foo2 = cast<const int *>(Bar2);
auto &Foo3 = cast<const int &>(Bar3);
Otherwise it will be transformed into:
const auto *Foo1 = cast<const int *>(Bar1);
const auto *Foo2 = cast<const int *>(Bar2);
const auto &Foo3 = cast<const int &>(Bar3);
Note: in the LLVM alias, the default value is false. Don't forget that, bestie! ๐
AllowedTypes
A semicolon-separated list of type names to ignore when auto gets deduced to that type (or to a pointer to that type) ๐
Ok but here's the spicy bit โ this thing distinguishes type aliases from the original type, so if you specify e.g. my_int it will not suppress reports about int, even if it's defined as a typedef alias for int. The alias is its own whole personality, basically.
Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with suffix Ref, ref, Reference and reference โจ
And listen โ if a name in the list contains the sequence :: it gets matched against the qualified type name (i.e. namespace::Type), otherwise it's matched against only the type name (i.e. Type). E.g. to suppress reports for std::array iterators use the std::array<.*>::(const_)?iterator string.
The default is an empty string ๐
๐ readability-redundant-access-specifiers
Finds classes, structs, and unions containing redundant member (field and method) access specifiers. Like, the ones that are just standing there doing absolutely nothing ๐ฅ
Example
class Foo {
public:
int x;
int y;
public:
int z;
protected:
int a;
public:
int c;
}
In the example above, the second public declaration can be removed without any changes of behavior โ periodt, it's giving extra for no reason ๐
Options
CheckFirstDeclarationโ if set totrue, the check will also diagnose if the first access specifier declaration is redundant (e.g.privateinsideclass, orpublicinsidestructorunion). Default isfalse.
Example
struct Bar {
public:
int x;
}
If CheckFirstDeclaration option is enabled, a warning about redundant access specifier will be emitted, because public is the default member access for structs. Bestie it was already the vibe, you didn't have to say it ๐ซถ
๐ธ readability-redundant-casting
Detects explicit type casting operations that involve the same source and destination types, and subsequently recommends their removal ๐๏ธ
It covers a whole range of explicit casting operations, including static_cast, const_cast, C-style casts, and reinterpret_cast. Its primary objective is to enhance code readability and maintainability by eliminating unnecessary type casting. Iconic behavior, honestly.
int value = 42;
int result = static_cast<int>(value);
In this example, the static_cast<int>(value) is redundant, as it performs a cast from an int to another int. Same to same, no notes ๐
Casting operations involving constructor conversions, user-defined conversions, functional casts, type-dependent casts, casts between distinct type aliases that refer to the same underlying type, as well as bitfield-related casts and casts directly from lvalue to rvalue, are all disregarded by the check. So don't come for those, they're not invited to the party ๐
Options
IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.IgnoreTypeAliasesโ when set tofalse, the check will consider type aliases, and when set totrue, it will resolve all type aliases and operate on the underlying types. Default isfalse.
๐ readability-redundant-control-flow
This check looks for procedures (functions returning no value) with return statements at the end of the function. Such return statements are redundant. Like girl, we already knew you were done ๐ญ
Loop statements (for, while, do while) are checked for redundant continue statements at the end of the loop body too.
Examples
The following function f contains a redundant return statement:
extern void g();
void f() {
g();
return;
}
becomes
extern void g();
void f() {
g();
}
The following function k contains a redundant continue statement:
void k() {
for (int i = 0; i < 10; ++i) {
continue;
}
}
becomes
void k() {
for (int i = 0; i < 10; ++i) {
}
}
๐ฆ readability-redundant-declaration
Finds redundant variable and function declarations ๐
extern int X;
extern int X;
becomes
extern int X;
Such redundant declarations can be removed without changing program behavior. They can for instance be unintentional left overs from previous refactorings when code has been moved around (we've all been there). Having redundant declarations could in worst case mean that there are typos in the code that cause bugs โ highkey scary ngl ๐
Normally the code can be automatically fixed, clang-tidy can remove the second declaration. However there are 2 cases when you need to fix the code manually:
- When the declarations are in different header files;
- When multiple variables are declared together.
Options
IgnoreMacrosโ if set totrue, the check will not give warnings inside macros. Default istrue.
โญ readability-redundant-function-ptr-dereference
Finds redundant dereferences of a function pointer ๐
Before:
int f(int,int);
int (*p)(int, int) = &f;
int i = (**p)(10, 50);
After:
int f(int,int);
int (*p)(int, int) = &f;
int i = (*p)(10, 50);
One star too many, bestie. We love a glow-down โจ
๐ง readability-redundant-inline-specifier
Detects redundant inline specifiers on function and variable declarations ๐
Examples
constexpr inline void f() {}
In the example above the keyword inline is redundant since constexpr functions are implicitly inlined โ it was already inlined bestie, it said so ๐
class MyClass {
inline void myMethod() {}
};
In the example above the keyword inline is redundant since member functions defined entirely inside a class/struct/union definition are implicitly inlined.
Options
StrictModeโ if set totrue, the check will also flag functions and variables that already have internal linkage as redundant. Default isfalse.
๐ readability-redundant-member-init
Finds member initializations that are unnecessary because the same default constructor would be called if they were not present ๐
Example
// Explicitly initializing the member s and v is unnecessary.
class Foo {
public:
Foo() : s() {}
private:
std::string s;
std::vector<int> v {};
};
Options
IgnoreBaseInCopyConstructorsโ default isfalse.
When true, the check will ignore unnecessary base class initializations within copy constructors, since some compilers issue warnings/errors when base classes are not explicitly initialized in copy constructors. For example, gcc with -Wextra or -Werror=extra issues warning or error base class 'Bar' should be explicitly initialized in the copy constructor if Bar() were removed in the following example:
// Explicitly initializing member s and base class Bar is unnecessary.
struct Foo : public Bar {
// Remove s() below. If IgnoreBaseInCopyConstructors!=0, keep Bar().
Foo(const Foo& foo) : Bar(), s() {}
std::string s;
};
๐ readability-redundant-preprocessor
Finds potentially redundant preprocessor directives ๐ At the moment the following cases are detected:
#ifdef .. #endifpairs which are nested inside an outer pair with the same condition. For example:
#ifdef FOO
#ifdef FOO // inner ifdef is considered redundant
void f();
#endif
#endif
- Same for
#ifndef .. #endifpairs. For example:
#ifndef FOO
#ifndef FOO // inner ifndef is considered redundant
void f();
#endif
#endif
#ifndefinside an#ifdefwith the same condition:
#ifdef FOO
#ifndef FOO // inner ifndef is considered redundant
void f();
#endif
#endif
#ifdefinside an#ifndefwith the same condition:
#ifndef FOO
#ifdef FOO // inner ifdef is considered redundant
void f();
#endif
#endif
#if .. #endifpairs which are nested inside an outer pair with the same condition. For example:
#define FOO 4
#if FOO == 4
#if FOO == 4 // inner if is considered redundant
void f();
#endif
#endif
Nested same-condition pairs are giving "I said what I said" energy, and we only need one of those ๐ญ
๐ซถ readability-redundant-smartptr-get
Find and remove redundant calls to smart pointer's .get() method ๐ฏ
Examples
ptr.get()->Foo() ==> ptr->Foo()
*ptr.get() ==> *ptr
*ptr->get() ==> **ptr
if (ptr.get() == nullptr) ... => if (ptr == nullptr) ...
Options
IgnoreMacrosโ if this option is set totrue(default istrue), the check will not warn about calls inside macros.
๐ readability-redundant-string-cstr
Finds unnecessary calls to std::string::c_str() and std::string::data(). No cap, they were doing nothing ๐
Options
StringParameterFunctionsโ a semicolon-separated list of (fully qualified) function/method/operator names, with the requirement that any parameter currently accepting aconst char*input should also be able to acceptstd::stringinputs, or proper overload candidates that can do so should exist. This can be used to configure functions such asfmt::format,spdlog::logger::info, or wrappers around these and similar functions. The default value is the empty string.
๐ readability-redundant-string-init
Finds unnecessary string initializations ๐
Examples
// Initializing string with empty string literal is unnecessary.
std::string a = "";
std::string b("");
// becomes
std::string a;
std::string b;
// Initializing a string_view with an empty string literal produces an
// instance that compares equal to string_view().
std::string_view a = "";
std::string_view b("");
// becomes
std::string_view a;
std::string_view b;
Options
StringNamesโ default is::std::basic_string;::std::basic_string_view.
Semicolon-delimited list of class names to apply this check to. By default ::std::basic_string applies to std::string and std::wstring. Set to e.g. ::std::basic_string;llvm::StringRef;QString to perform this check on custom classes.
๐ readability-reference-to-constructed-temporary
Detects C++ code where a reference variable is used to extend the lifetime of a temporary object that has just been constructed ๐ฑ
This construction is often the result of multiple code refactorings or a lack of developer knowledge, leading to confusion or subtle bugs. In most cases, what the developer really wanted to do is create a new variable rather than extending the lifetime of a temporary object. The main character energy was misdirected, basically ๐ญ
Examples of problematic code include:
const std::string& str("hello");
struct Point { int x; int y; };
const Point& p = { 1, 2 };
okay so lowkey this is the part where we're wrapping up the tea on references โ in the first example, a const std::string& reference variable str gets assigned a temporary object made by the std::string("hello") constructor. In the second example, a const Point& reference variable p gets assigned an object constructed from an initializer list { 1, 2 }. Both of these examples extend the lifetime of the temporary object to the lifetime of the reference variable, which honestly makes it so hard to reason about and may lead to subtle bugs or straight-up misunderstanding ๐ญ
To avoid these issues, it is recommended to change the reference variable to a (const) value variable. periodt. ๐
readability-simplify-boolean-expr
Bestie. This check is looking for boolean expressions involving boolean constants and simplifies them to use the appropriate boolean expression directly (iconic behaviour tbh). It simplifies boolean expressions by application of DeMorgan's Theorem. โจ
Examples, aka the glow-up table:
if (b == true)โif (b)if (b == false)โif (!b)if (b && true)โif (b)if (b && false)โif (false)if (b || true)โif (true)if (b || false)โif (b)e ? true : falseโee ? false : trueโ!eif (true) t(); else f();โt();if (false) t(); else f();โf();if (e) return true; else return false;โreturn e;if (e) return false; else return true;โreturn !e;if (e) b = true; else b = false;โb = e;if (e) b = false; else b = true;โb = !e;if (e) return true; return false;โreturn e;if (e) return false; return true;โreturn !e;!(!a || b)โa && !b!(a || !b)โ!a && b!(!a || !b)โa && b!(!a && b)โa || !b!(a && !b)โ!a || b!(!a && !b)โa || b
The resulting expression e is modified as follows, and ngl this list is kind of the main character energy of the whole check:
- Unnecessary parentheses around the expression are removed.
- Negated applications of
!are eliminated. - Negated applications of comparison operators are changed to use the opposite condition.
- Implicit conversions of pointers, including pointers to members, to
boolare replaced with explicit comparisons tonullptrin C++11 orNULLin C++98/03. - Implicit casts to
boolare replaced with explicit casts tobool. - Object expressions with
explicit operator boolconversion operators are replaced with explicit casts tobool. - Implicit conversions of integral types to
boolare replaced with explicit comparisons to0.
Examples (yes, more of them, we're eating good today ๐):
- The ternary assignment
bool b = (i < 0) ? true : false;has redundant parentheses and becomesbool b = i < 0;. - The conditional return
if (!b) return false; return true;has an implied double negation and becomesreturn b;. - The conditional return
if (i < 0) return false; return true;becomesreturn i >= 0;.
The conditional return if (i != 0) return false; return true; becomes return i == 0;.
- The conditional return
if (p) return true; return false;has an implicit conversion of a pointer tobooland becomesreturn p != nullptr;.
The ternary assignment bool b = (i & 1) ? true : false; has an implicit conversion of i & 1 to bool and becomes bool b = (i & 1) != 0;.
- The conditional return
if (i & 1) return true; else return false;has an implicit conversion of an integer quantityi & 1tobooland becomesreturn (i & 1) != 0; - Given
struct X { explicit operator bool(); };, and an instancexofstruct X, the conditional returnif (x) return true; return false;becomesreturn static_cast<bool>(x);
โจ Options โจ
IgnoreMacrosโ iftrue, ignore boolean expressions originating from expanded macros. Default isfalse.ChainedConditionalReturnโ iftrue, conditional boolean return statements at the end of anif/else ifchain will be transformed. Default isfalse.ChainedConditionalAssignmentโ iftrue, conditional boolean assignments at the end of anif/else ifchain will be transformed. Default isfalse.SimplifyDeMorganโ iftrue, DeMorgan's Theorem will be applied to simplify negated conjunctions and disjunctions. Default istrue.SimplifyDeMorganRelaxedโ iftrue,SimplifyDeMorganwill also transform negated conjunctions and disjunctions where there is no negation on either operand. This option has no effect ifSimplifyDeMorganisfalse. Default isfalse.
When Enabled:
bool X = !(A && B)
bool Y = !(A || B)
Would be transformed to:
bool X = !A || !B
bool Y = !A && !B
readability-simplify-subscript-expr
This check simplifies subscript expressions, and honestly it's giving "why are you doing all that" energy. Currently this covers calling .data() and immediately doing an array subscript operation to obtain a single element, in which case simply calling operator[] suffices. ๐ฆ
Examples:
std::string s = ...;
char c = s.data()[i]; // char c = s[i];
โจ Options โจ
Typesโ the list of type(s) that triggers this check. Default is::std::basic_string;::std::basic_string_view;::std::vector;::std::array;::std::span
readability-static-accessed-through-instance
Checks for member expressions that access static members through instances, and replaces them with uses of the appropriate qualified-id. no cap, that's the whole vibe check ๐
Example:
The following code:
struct C {
static void foo();
static int x;
enum { E1 };
enum E { E2 };
};
C *c1 = new C();
c1->foo();
c1->x;
c1->E1;
c1->E2;
is changed to:
C *c1 = new C();
C::foo();
C::x;
C::E1;
C::E2;
The --fix commandline option provides default support for safe fixes, whereas --fix-notes enables fixes that may replace expressions with side effects, potentially altering the program's behavior.
โ ๏ธ bestie read that last bit again โ --fix-notes can change what your program actually does. side effects are not a joke, fr fr. ๐ญ
readability-static-definition-in-anonymous-namespace
Finds static function and variable definitions in anonymous namespace.
In this case, static is redundant, because anonymous namespace limits the visibility of definitions to a single translation unit. so it's literally doing nothing, bless its heart ๐
namespace {
static int a = 1; // Warning.
static const int b = 1; // Warning.
namespace inner {
static int c = 1; // Warning.
}
}
The check will apply a fix by removing the redundant static qualifier. slay ๐
readability-string-compare ๐ซถ
Okay bestie, this one catches people using .compare() when they literally just meant to check equality or inequality. It finds string comparisons that use the compare method.
So here's the tea: a super common mistake is calling the string's compare method instead of using the equality or inequality operators. The compare method is meant for sorting functions, so it returns a negative number, a positive number, or zero depending on the lexicographical relationship between the strings being compared. If an equality or inequality check can suffice, that's what's recommended ๐ โ it avoids the risk of misinterpreting the return value, and it keeps the code simpler. Bonus: string equality and inequality operators can also be faster than compare because of early termination. no cap โจ
Example ๐ธ
// The same rules apply to std::string_view.
std::string str1{"a"};
std::string str2{"b"};
// use str1 != str2 instead.
if (str1.compare(str2)) {
}
// use str1 == str2 instead.
if (!str1.compare(str2)) {
}
// use str1 == str2 instead.
if (str1.compare(str2) == 0) {
}
// use str1 != str2 instead.
if (str1.compare(str2) != 0) {
}
// use str1 == str2 instead.
if (0 == str1.compare(str2)) {
}
// use str1 != str2 instead.
if (0 != str1.compare(str2)) {
}
// Use str1 == "foo" instead.
if (str1.compare("foo") == 0) {
}
Those code examples above are literally the list of if-statements this check will give a warning for ๐ฅ. All of them use compare to check equality or inequality of two strings instead of using the correct operators. every single one. icon behavior to fix them all ๐
Options ๐
StringLikeClassesโ a string containing semicolon-separated names of string-like classes. By default it contains only::std::basic_stringand::std::basic_string_view. If a class from this list has acomparemethod similar to that ofstd::string, it'll be checked the same way ๐ฆ
Example โญ
struct CustomString {
public:
int compare (const CustomString& other) const;
}
CustomString str1;
CustomString str2;
// use str1 != str2 instead.
if (str1.compare(str2)) {
}
If StringLikeClasses contains CustomString, the check will suggest replacing compare with the equality operator. bestie it just wants you to use the right tool ๐
readability-suspicious-call-argument ๐
This one finds function calls where the arguments passed are provided out of order, based on the difference between the argument name and the parameter names of the function. it's giving detective energy ๐
Given a function call f(foo, bar); and a function signature void f(T tvar, U uvar), the arguments foo and bar are swapped if foo (the argument name) is more similar to uvar (the other parameter) than tvar (the parameter it's currently passed to) and bar is more similar to tvar than uvar ๐ซถ
Warnings might indicate either that the arguments are swapped, or that the names' cross-similarity might hinder code comprehension. either way, worth a peek ๐
Heuristics ๐ง
The following heuristics are implemented in the check. If any of the enabled heuristics deem the arguments to be provided out of order, a warning will be issued โจ
The heuristics themselves are implemented by considering pairs of strings, and are symmetric, so in the following there's no distinction on which string is the argument name and which string is the parameter name. fair and balanced ๐
Equality
The most trivial heuristic, which compares the two strings for case-insensitive equality. simple, iconic, does exactly what it says on the tin ๐
Abbreviation
Common abbreviations can be specified which will deem the strings similar if the abbreviated and the abbreviation stand together. For example, if src is registered as an abbreviation for source, then the following code example will be warned about ๐
void foo(int source, int x);
foo(b, src);
The abbreviations to recognise can be configured with the Abbreviations check option. This heuristic is case-insensitive. periodt ๐
Prefix
The prefix heuristic reports if one of the strings is a sufficiently long prefix of the other string, e.g. target to targetPtr. The similarity percentage is the length ratio of the prefix to the longer string โ in the previous example, it would be 6 / 9 = 66.66...%. math girlies rise ๐ญ
This heuristic can be configured with bounds. The default bounds are: below 25% dissimilar and above 30% similar. This heuristic is case-insensitive. ๐
Suffix
Analogous to the Prefix heuristic. In the case of oldValue and value compared, the similarity percentage is 8 / 5 = 62.5%. yeah the numbers are just Like That, don't @ me ๐
This heuristic can be configured with bounds. The default bounds are: below 25% dissimilar and above 30% similar. This heuristic is case-insensitive. ๐ธ
Substring
The substring heuristic combines the prefix and the suffix heuristic, and tries to find the longest common substring in the two strings provided. The similarity percentage is the ratio of the found longest common substring against the longer of the two input strings. For example, given val and rvalue, the similarity is 3 / 6 = 50%. If no characters are common in the two strings, 0%. brutal but fair ๐
This heuristic can be configured with bounds. The default bounds are: below 40% dissimilar and above 50% similar. This heuristic is case-insensitive. ๐ฆ
Levenshtein distance (as Levenshtein)
The Levenshtein distance (http://en.wikipedia.org/wiki/Levenshtein_distance) describes how many single-character changes (additions, changes, or removals) must be applied to transform one string into another. it's literally counting your typos ๐ฅ
The Levenshtein distance is translated into a similarity percentage by dividing it with the length of the longer string, and taking its complement with regards to 100%. For example, given something and anything, the distance is 4 edits, and the similarity percentage is 100% - 4 / 9 = 55.55...%. math is mathing โจ
This heuristic can be configured with bounds. The default bounds are: below 50% dissimilar and above 66% similar. This heuristic is case-sensitive. โ ๏ธ (yes, this one's the different one, don't forget!)
Jaro--Winkler distance (as JaroWinkler)
The Jaro--Winkler distance (http://en.wikipedia.org/wiki/JaroโWinkler_distance) is an edit distance like the Levenshtein distance. It's calculated from the amount of common characters that are sufficiently close to each other in position, and to-be-changed characters. The original definition of Jaro has been extended by Winkler to weigh prefix similarities more. The similarity percentage is expressed as an average of the common and non-common characters against the length of both strings. fancy ๐
This heuristic can be configured with bounds. The default bounds are: below 75% dissimilar and above 85% similar. This heuristic is case-insensitive. ๐
Sรธrensen--Dice coefficient (as Dice)
The Sรธrensen--Dice coefficient (http://en.wikipedia.org/wiki/SรธrensenโDice_coefficient) was originally defined to measure the similarity of two sets. Formally, the coefficient is calculated by dividing 2 * #(intersection) with #(set1) + #(set2), where #() is the cardinality function of sets. This metric is applied to strings by creating bigrams (substring sequences of length 2) of the two strings and using the set of bigrams for the two strings as the two sets. set theory crossover episode omg ๐
This heuristic can be configured with bounds. The default bounds are: below 60% dissimilar and above 70% similar. This heuristic is case-insensitive. ๐ซถ
Options ๐
MinimumIdentifierNameLengthโ sets the minimum required length the argument and parameter names need to have. Names shorter than this length will be ignored. Defaults to3. tiny names get sent home, sorry bestie ๐Abbreviationsโ for theAbbreviationheuristic (see here), this option configures the abbreviations in the"abbreviation=abbreviated_value"format. The option is a string, with each value joined by";".
By default, the following abbreviations are set:
addr=addressarr=arrayattr=attributebuf=buffercl=clientcnt=countcol=columncpy=copydest=destinationdist=distancedst=distanceelem=elementhght=heighti=indexidx=indexlen=lengthln=linelst=listnr=numbernum=numberpos=positionptr=pointerref=referencesrc=sourcesrv=serverstmt=statementstr=stringval=valuevar=variablevec=vectorwdth=width
The configuration options for each implemented heuristic (see above) is constructed dynamically. In the following, <HeuristicName> refers to one of the keys from the heuristics implemented. โญ
<HeuristicName>โTrueorFalse, whether a particular heuristic, such asEqualityorLevenshteinis enabled.
Defaults to True for every heuristic. all on by default, she's thorough ๐
<HeuristicName>DissimilarBelow,<HeuristicName>SimilarAboveโ a value between 0 and 100, expressing a percentage. The bounds set what percentage of similarity the heuristic must deduce for the two identifiers to be considered similar or dissimilar by the check.
Given arguments arg1 and arg2 passed to param1 and param2, respectively, the bounds check is performed in the following way: if the similarity of the currently passed argument order (arg1 to param1) is below the DissimilarBelow threshold, and the similarity of the suggested swapped order (arg1 to param2) is above the SimilarAbove threshold, the swap is reported. it's giving "hmm that's suspicious" ๐ต๏ธ
For the defaults of each heuristic, see above.
Name synthesis ๐ง
When comparing the argument names and parameter names, the following logic is used to gather the names for comparison:
- Parameter names are the identifiers as written in the source code. ๐
- Argument names are:
- If a variable is passed, the variable's name. ๐
- If a subsequent function call's return value is used as argument, the called function's name. ๐ฅ
- Otherwise, empty string. ๐ญ
Empty argument or parameter names are ignored by the heuristics. no name, no drama ๐
readability-uniqueptr-delete-release โจ
Replace delete <unique_ptr>.release() with <unique_ptr> = nullptr. The latter is shorter, simpler, and does not require use of raw pointer APIs. it's giving glow-up ๐ธ
std::unique_ptr<int> P;
delete P.release();
// becomes
std::unique_ptr<int> P;
P = nullptr;
Options ๐
PreferResetCallโ if true, refactor by calling theresetmember function instead of assigning tonullptr. Default value isfalse. your vibe, your choice ๐ซถ
โฆand the rest of that example lands like this:
std::unique_ptr<int> P;
delete P.release();
// becomes
std::unique_ptr<int> P;
P.reset();
๐ readability-uppercase-literal-suffix ๐
cert-dcl16-c redirects here as an alias for this check. By default, only the suffixes that begin with l (l, ll, lu, llu, but not u, ul, ull) are diagnosed by that alias. iykyk ๐
hicpp-uppercase-literal-suffix redirects here as an alias for this check too. slay โจ
This check detects when the integral literal or floating point (decimal or hexadecimal) literal has a non-uppercase suffix and provides a fix-it hint with the uppercase suffix. All valid combinations of suffixes are supported. No cap.
auto x = 1; // OK, no suffix.
auto x = 1u; // warning: integer literal suffix 'u' is not upper-case
auto x = 1U; // OK, suffix is uppercase.
...
โจ Options โจ
NewSuffixesโ Optionally, a list of the destination suffixes can be provided. When the suffix is found, a case-insensitive lookup in that list is made, and if a replacement is found that is different from the current suffix, then the diagnostic is issued. This allows for fine-grained control of what suffixes to consider and what their replacements should be. obsessed with the precision tbh ๐
๐ Example ๐
Given a list L;uL:
l->LLwill be kept as is.ul->uLUl->uLUL->uLuLwill be kept as is.ullwill be kept as is, since it is not in the list- and so on.
โจ Options (continued) โจ
IgnoreMacrosโ If this option is set to true (default is true), the check will not warn about literal suffixes inside macros. macros stay unbothered, periodt ๐ซถ
๐ฆ readability-use-anyofallof ๐ฆ
Finds range-based for loops that can be replaced by a call to std::any_of or std::all_of. In C++20 mode, suggests std::ranges::any_of or std::ranges::all_of. main character algorithm energy โจ
Example:
bool all_even(std::vector<int> V) {
for (int I : V) {
if (I % 2)
return false;
}
return true;
// Replace loop by
// return std::ranges::all_of(V, [](int I) { return I % 2 == 0; });
}
๐ธ readability-use-concise-preprocessor-directives ๐ธ
Finds uses of #if that can be simplified to #ifdef or #ifndef and, since C23 and C++23, uses of #elif that can be simplified to #elifdef or #elifndef:
#if defined(MEOW)
#if !defined(MEOW)
// becomes
#ifdef MEOW
#ifndef MEOW
Since C23 and C++23:
#elif defined(MEOW)
#elif !defined(MEOW)
// becomes
#elifdef MEOW
#elifndef MEOW
preprocessor glow-up fr fr ๐
โญ readability-use-std-min-max โญ
Replaces certain conditional statements with equivalent calls to std::min or std::max. Note: This may impact performance in critical code due to potential additional stores compared to the original if statement. real talk, keep that in mind bestie โ ๏ธ
Before:
void foo() {
int a = 2, b = 3;
if (a < b)
a = b;
}
After:
void foo() {
int a = 2, b = 3;
a = std::max(a, b);
}
๐ง zircon-temporary-objects ๐ง
Warns on construction of specific temporary objects in the Zircon kernel. If the object should be flagged โ yes, the manpage says it twice, we love a dramatic echo โ the fully qualified type name must be explicitly passed to the check. explicitly specified only, no guessing ๐
For example, given the list of classes "Foo" and "NS::Bar", all of the following will trigger the warning:
Foo();
Foo F = Foo();
func(Foo());
namespace NS {
Bar();
}
With the same list, the following will not trigger the warning:
Foo F; // Non-temporary construction okay
Foo F(param); // Non-temporary construction okay
Foo *F = new Foo(); // New construction okay
Bar(); // Not NS::Bar, so okay
NS::Bar B; // Non-temporary construction okay
Note that objects must be explicitly specified in order to be flagged, and so objects that inherit a specified object will not be flagged. inheritance does not automatically get you flagged, bestie ๐
This check matches temporary objects without regard for inheritance and so a prohibited base class type does not similarly prohibit derived class types.
class Derived : Foo {} // Derived is not explicitly disallowed
Derived(); // and so temporary construction is okay
โจ Options โจ
Namesโ a semi-colon-separated list of fully-qualified names of C++ classes that should not be constructed as temporaries. Default is empty. ๐ซถ
okay so next up is the big vibe check: the table of check names and whether each one offers fixes. The original has two columns โ Name and Offers fixes โ and since we're not doing tables here, bestie, you're getting it as a list. Every single name, no skips, fr fr. ๐
abseil-cleanup-ctadโ offers fixes: yes โจabseil-duration-additionโ offers fixes: yes โจabseil-duration-comparisonโ offers fixes: yes โจabseil-duration-conversion-castโ offers fixes: yes โจabseil-duration-divisionโ offers fixes: yes โจabseil-duration-factory-floatโ offers fixes: yes โจabseil-duration-factory-scaleโ offers fixes: yes โจabseil-duration-subtractionโ offers fixes: yes โจabseil-duration-unnecessary-conversionโ offers fixes: yes โจabseil-faster-strsplit-delimiterโ offers fixes: yes โจabseil-no-internal-dependenciesโ no fixes offered ๐abseil-no-namespaceโ no fixes offered ๐abseil-redundant-strcat-callsโ offers fixes: yes โจabseil-str-cat-appendโ offers fixes: yes โจabseil-string-find-startswithโ offers fixes: yes โจabseil-string-find-str-containsโ offers fixes: yes โจabseil-time-comparisonโ offers fixes: yes โจabseil-time-subtractionโ offers fixes: yes โจabseil-upgrade-duration-conversionsโ offers fixes: yes โจaltera-id-dependent-backward-branchโ no fixes offered ๐altera-kernel-name-restrictionโ no fixes offered ๐altera-single-work-item-barrierโ no fixes offered ๐altera-struct-pack-alignโ offers fixes: yes โจaltera-unroll-loopsโ no fixes offered ๐android-cloexec-acceptโ offers fixes: yes โจandroid-cloexec-accept4โ offers fixes: yes โจandroid-cloexec-creatโ offers fixes: yes โจandroid-cloexec-dupโ offers fixes: yes โจandroid-cloexec-epoll-createโ offers fixes: yes โจandroid-cloexec-epoll-create1โ offers fixes: yes โจandroid-cloexec-fopenโ offers fixes: yes โจandroid-cloexec-inotify-initโ offers fixes: yes โจandroid-cloexec-inotify-init1โ offers fixes: yes โจandroid-cloexec-memfd-createโ offers fixes: yes โจandroid-cloexec-openโ offers fixes: yes โจ
lowkey iconic how many of these hand you a fix automatically โ the abseil-* and android-cloexec-* girlies are carrying the whole team ๐๐ฅ
...and the table just keeps going, bestie ๐ญ we're deep in the check list now, alphabetical order is carrying us. Same deal as before: check name, then whatever's in that second column. If it's blank, it's blank โ I'm not gonna pretend otherwise ๐
android-cloexec-pipe<>โ Yesandroid-cloexec-pipe2<>โ Yesandroid-cloexec-socket<>โ Yesandroid-comparison-in-temp-failure-retry<>โ (blank)boost-use-ranges<>โ Yesboost-use-to-string<>โ Yesbugprone-argument-comment<>โ Yesbugprone-assert-side-effect<>โ (blank)bugprone-assignment-in-if-condition<>โ (blank)bugprone-bad-signal-to-kill-thread<>โ (blank)bugprone-bitwise-pointer-cast<>โ (blank)bugprone-bool-pointer-implicit-conversion<>โ Yesbugprone-branch-clone<>โ (blank)bugprone-capturing-this-in-member-variable<>โ (blank)bugprone-casting-through-void<>โ (blank)bugprone-chained-comparison<>โ (blank)bugprone-compare-pointer-to-member-virtual-function<>โ (blank)bugprone-copy-constructor-init<>โ Yesbugprone-crtp-constructor-accessibility<>โ Yesbugprone-dangling-handle<>โ (blank)bugprone-dynamic-static-initializers<>โ (blank)bugprone-easily-swappable-parameters<>โ (blank)bugprone-empty-catch<>โ (blank)bugprone-exception-escape<>โ (blank)bugprone-fold-init-type<>โ (blank)bugprone-forward-declaration-namespace<>โ (blank)bugprone-forwarding-reference-overload<>โ (blank)bugprone-implicit-widening-of-multiplication-result<>โ Yesbugprone-inaccurate-erase<>โ Yesbugprone-inc-dec-in-conditions<>โ (blank)bugprone-incorrect-enable-if<>โ Yesbugprone-incorrect-enable-shared-from-this<>โ Yesbugprone-incorrect-roundings<>โ (blank)bugprone-infinite-loop<>โ (blank)bugprone-integer-division<>โ (blank)bugprone-lambda-function-name<>โ (blank)bugprone-macro-parentheses<>โ Yes
okay the bugprone- block is long, like it's giving main character energy of the whole list honestly ๐ธ and we're only at macro-parentheses. Still more coming, don't you dare close the terminal ๐ซถ
okay still going through the list, still deep in the bugprone- era of our lives, no we're not done yet, hydrate and stay with me ๐ซถ โ the Yes at the end of a line is that column's value, blank means blank, keeping it 100% honest:
bugprone-macro-repeated-side-effectsโ noYeshere, just vibesbugprone-misleading-setter-of-referenceโ also noYes, it's giving "trust issues in setter form" nglbugprone-misplaced-operator-in-strlen-in-allocโYesโจbugprone-misplaced-pointer-arithmetic-in-allocโYes๐bugprone-misplaced-widening-castโ noYes, just the name doing all the emotional damagebugprone-move-forwarding-referenceโYes๐ฅbugprone-multi-level-implicit-pointer-conversionโ noYes; the name is literally three red flags in a trench coat ๐ญbugprone-multiple-new-in-one-expressionโ blank, noYesbugprone-multiple-statement-macroโ blank, noYesbugprone-narrowing-conversionsโ blank, noYesbugprone-no-escapeโ blank, noYesbugprone-non-zero-enum-to-bool-conversionโ blank, noYesbugprone-nondeterministic-pointer-iteration-orderโ blank, noYes(this name lowkey ate, lowkey terrifying)bugprone-not-null-terminated-resultโYes๐bugprone-optional-value-conversionโYesโจbugprone-parent-virtual-callโYes๐bugprone-pointer-arithmetic-on-polymorphic-objectโ blank, noYesbugprone-posix-returnโYes๐bugprone-redundant-branch-conditionโYes๐ธbugprone-reserved-identifierโYesโญbugprone-return-const-ref-from-parameterโ blank, noYesbugprone-shared-ptr-array-mismatchโYes๐ฆbugprone-signal-handlerโ blank, noYesbugprone-signed-char-misuseโ blank, noYesbugprone-sizeof-containerโ blank, noYesbugprone-sizeof-expressionโ blank, noYes(thesizeofsaga continues, bestie)bugprone-spuriously-wake-up-functionsโ blank, noYesbugprone-standalone-emptyโYes๐bugprone-string-constructorโYesโจbugprone-string-integer-assignmentโYes๐ฅbugprone-string-literal-with-embedded-nulโ blank, noYesbugprone-stringview-nullptrโYes๐bugprone-suspicious-enum-usageโ blank, noYesbugprone-suspicious-includeโ blank, noYesbugprone-suspicious-memory-comparisonโ blank, noYesbugprone-suspicious-memset-usageโYes๐bugprone-suspicious-missing-commaโ blank, noYes
and we're still not at the end of the bugprone- block, because of course we aren't ๐ญ keep scrolling with me ๐
bugprone-suspicious-realloc-usageโ ๐ the name alone is giving "we need to talk"bugprone-suspicious-semicolonโ โYesโ the classic "wait, why is there a semicolon there" jumpscare ๐bugprone-suspicious-string-compareโ โYesโ comparing strings suspiciously, we've all been there bestiebugprone-suspicious-stringview-data-usageโstringview+data+suspicious?? three red flags in one name ๐ฉbugprone-swapped-argumentsโ โYesโ when you pass(b, a)instead of(a, b)and everything explodes ๐ญbugprone-switch-missing-default-caseโ thedefault:erasure, truly iconicbugprone-tagged-union-member-countโ counting members like it's a group chat headcount ๐ซถbugprone-terminating-continueโ โYesโ acontinuewith main character energy that ends everything โจbugprone-throw-keyword-missingโthrowsaid "not me forgetting the keyword" ๐bugprone-too-small-loop-variableโ tiny loop variable, big problems ๐bugprone-unchecked-optional-accessโ nohas_value()check? bold of you, bestie ๐ฌbugprone-undefined-memory-manipulationโ literally undefined behaviour, we do not stan ๐ซbugprone-undelegated-constructorโ a constructor that refused to delegate, lowkey rudebugprone-unhandled-exception-at-newโnewthrowing and nobody catching? the audacitybugprone-unhandled-self-assignmentโx = xbut make it drama ๐bugprone-unintended-char-ostream-outputโ โYesโ printing acharwhen you meant something else, a whole vibe check failbugprone-unique-ptr-array-mismatchโ โYesโunique_ptrvs. arrays, the mismatched-friend-group energy ๐bugprone-unsafe-functionsโ the name is the warning, fr frbugprone-unused-local-non-trivial-variableโ a whole non-trivial local just sitting there, unused, unbothered ๐ธbugprone-unused-raiiโ โYesโ RAII that does nothing? the object said "I'm just here for the vibes" ๐งbugprone-unused-return-valueโ you got a return value back and just ignored it, coldbugprone-use-after-moveโ the ultimate "you already used that, bestie" ๐bugprone-virtual-near-missโ โYesโ a near miss onvirtual, so close and yet so cursedcert-dcl50-cppโ CERT rule, no default marker, just vibes โญcert-dcl58-cppโ another CERT baddie in thedclfamily ๐cert-env33-cโ CERT said "don't talk to the environment like that" (name only, no spoilers)cert-err33-cโerrfamily, 33 edition ๐cert-err34-cโ and here's 34, its siblingcert-err52-cppโ 52, C++, still noYesflagcert-err58-cppโ 58, present and accounted for โจcert-err60-cppโ closing out theerrrun with 60cert-flp30-cโflp= floating point business, we respect itcert-mem57-cppโ memory rule,mem57, very serious ๐cert-msc50-cppโmsc50, mysterious but validcert-msc51-cppโmsc51, its partner in crimecert-oop57-cppโ object-oriented programming rule 57, slay ๐cert-oop58-cppโ and 58 right after, iconic pairingconcurrency-mt-unsafeโ multithreading unsafe?? bestie, thread safety is not optional ๐ญconcurrency-thread-canceltype-asynchronousโ the longest name in the concurrency lineup and honestly? it ate ๐
So yeah, that's the run of checks โ the ones with Yes are flagged, the rest are just listed. Next batch coming right up, no cap ๐ซถ
ok so continuing the big list โ same deal as before, each one is a check name, and the ones with Yes riding shotgun are the ones flagged in that second column. no cap, here's the rest of them ๐
cppcoreguidelines-avoid-capturing-lambda-coroutinesโ the name is literally the whole vibe, bestie ๐cppcoreguidelines-avoid-const-or-ref-data-membersโ also fully self-explanatory, bless โจcppcoreguidelines-avoid-do-whileโ Yes (in the second column) ๐ซถcppcoreguidelines-avoid-gotoโ iconic name, no notes ๐cppcoreguidelines-avoid-non-const-global-variablesโ yes queen, say it louder ๐ธcppcoreguidelines-avoid-reference-coroutine-parametersโ coroutines again, she's everywhere ๐cppcoreguidelines-init-variablesโ Yes ๐cppcoreguidelines-interfaces-global-initโ ๐ฆcppcoreguidelines-macro-usageโ ๐งcppcoreguidelines-misleading-capture-default-by-valueโ Yes ๐cppcoreguidelines-missing-std-forwardโ ๐cppcoreguidelines-no-mallocโ ๐cppcoreguidelines-no-suspend-with-lockโ ๐ฅcppcoreguidelines-owning-memoryโ ๐cppcoreguidelines-prefer-member-initializerโ Yes โญcppcoreguidelines-pro-bounds-array-to-pointer-decayโ the way this name just keeps going ๐ญcppcoreguidelines-pro-bounds-constant-array-indexโ Yes ๐cppcoreguidelines-pro-bounds-pointer-arithmeticโ ๐ฅcppcoreguidelines-pro-type-const-castโ ๐cppcoreguidelines-pro-type-cstyle-castโ Yes ๐cppcoreguidelines-pro-type-member-initโ Yes ๐ซถcppcoreguidelines-pro-type-reinterpret-castโ ๐ฆcppcoreguidelines-pro-type-static-cast-downcastโ Yes โจcppcoreguidelines-pro-type-union-accessโ ๐ธcppcoreguidelines-pro-type-varargโ ๐cppcoreguidelines-rvalue-reference-param-not-movedโ long name supremacy, honestly ๐cppcoreguidelines-slicingโ ๐งcppcoreguidelines-special-member-functionsโ ๐cppcoreguidelines-use-enum-classโ Yes ๐cppcoreguidelines-virtual-class-destructorโ Yes โญdarwin-avoid-spinlockโ we're in darwin territory now, shifting gears ๐darwin-dispatch-once-nonstaticโ Yes ๐fuchsia-default-arguments-callsโ and now fuchsia, we contain multitudes ๐ฆfuchsia-default-arguments-declarationsโ Yes โจfuchsia-multiple-inheritanceโ ๐ธfuchsia-overloaded-operatorโ ๐fuchsia-statically-constructed-objectsโ last one in this batch, drink some water ๐
Okay, picking the checks table back up right where we left it โ that second column is literally just โis this one enabled by default?โ energy. Tiny table gremlin note: the original puts a lil <> after every check name in that first column; thatโs formatting noise, not part of the name, so the actual check names below stay clean ๐ซถ
Quick legend so weโre synced: Yes โ
means the default column says Yes, and (blank) ๐ means the default column is empty.
fuchsia-trailing-returnโ (blank)fuchsia-virtual-inheritanceโ (blank)google-build-explicit-make-pairโ (blank)google-build-namespacesโ (blank)google-build-using-namespaceโ (blank)google-default-argumentsโ (blank)google-explicit-constructorโYesโgoogle-global-names-in-headersโ (blank)google-objc-avoid-nsobject-newโ (blank)google-objc-avoid-throwing-exceptionโ (blank)google-objc-function-namingโ (blank)google-objc-global-variable-declarationโ (blank)google-readability-avoid-underscore-in-googletest-nameโ (blank)google-readability-castingโ (blank)google-readability-todoโ (blank)google-runtime-intโ (blank)google-runtime-operatorโ (blank)google-upgrade-googletest-caseโYesโhicpp-exception-baseclassโ (blank)hicpp-ignored-remove-resultโ (blank)hicpp-multiway-paths-coveredโ (blank)hicpp-no-assemblerโ (blank)hicpp-signed-bitwiseโ (blank)linuxkernel-must-check-errsโ (blank)llvm-header-guardโ (blank)llvm-include-orderโYesโllvm-namespace-commentโ (blank)llvm-prefer-isa-or-dyn-cast-in-conditionalsโYesโllvm-prefer-register-over-unsignedโYesโllvm-prefer-static-over-anonymous-namespaceโ (blank)llvm-twine-localโYesโllvmlibc-callee-namespaceโ (blank)llvmlibc-implementation-in-namespaceโ (blank)llvmlibc-inline-function-declโYesโllvmlibc-restrict-system-libc-headersโYesโmisc-confusable-identifiersโ (blank)misc-const-correctnessโYesโmisc-coroutine-hostile-raiiโ (blank)
okay so we're still deep in the check list and honestly this part is mostly just names and vibes, but every single one of these bad boys has to be accounted for, so let's go ๐โโ๏ธ๐จ
Quick thing before we dive in, bestie: the page is showing these as a big grid, and each row is a check with a little Yes hanging out next to some of them while others got left on read. I'm keeping the <> anchor stuff off because that's just how the links were wired up, not part of the actual check names, iykyk ๐
misc-definitions-in-headersโYesโจmisc-header-include-cycleโ noYeson this onemisc-include-cleanerโYes๐misc-misleading-bidirectionalโ noYesmisc-misleading-identifierโ noYesmisc-misplaced-constโ noYesmisc-new-delete-overloadsโ noYesmisc-no-recursionโ noYesmisc-non-copyable-objectsโ noYesmisc-non-private-member-variables-in-classesโ noYesmisc-redundant-expressionโYes๐ฅmisc-static-assertโYesโญmisc-throw-by-value-catch-by-referenceโ noYesmisc-unconventional-assign-operatorโ noYesmisc-uniqueptr-reset-releaseโYes๐ฆmisc-unused-alias-declsโYes๐ธmisc-unused-parametersโYes๐misc-unused-using-declsโYes๐ซถmisc-use-anonymous-namespaceโ noYesmisc-use-internal-linkageโYes๐modernize-avoid-bindโYesโจmodernize-avoid-c-arraysโ noYesmodernize-concat-nested-namespacesโYes๐modernize-deprecated-headersโYes๐modernize-deprecated-ios-base-aliasesโYes๐modernize-loop-convertโYes๐ฅmodernize-macro-to-enumโYesโญmodernize-make-sharedโYes๐modernize-make-uniqueโYes๐modernize-min-max-use-initializer-listโYes๐ฆmodernize-pass-by-valueโYes๐ธmodernize-raw-string-literalโYesโจmodernize-redundant-void-argโYes๐modernize-replace-auto-ptrโYes๐งmodernize-replace-disallow-copy-and-assign-macroโYes๐modernize-replace-random-shuffleโYes๐modernize-return-braced-init-listโYes๐ซถmodernize-shrink-to-fitโYes๐
like, look at all those misc- ones and then the whole modernize- glow-up era starting halfway down ๐ญ the naming convention really said "we contain multitudes" and honestly? iconic.
also not me noticing the blank ones just sitting there with no Yes, minding their own business, no flag, no drama, just existing ๐ they're valid too.
and yes, we're still not done โ the list continues, so don't you dare close the terminal yet ๐
okay bestie, we're still deep in the check list and there are so many more icons to get through ๐
same deal as before: the name is in code, and I'm telling you whether that second column says Yes or is just vibing empty. no cap, I'm not skipping a single one.
modernize-type-traits <>โ second column: Yes โจmodernize-unary-static-assert <>โ second column: Yes โจmodernize-use-auto <>โ second column: Yes โจmodernize-use-bool-literals <>โ second column: Yes โจmodernize-use-constraints <>โ second column: Yes โจmodernize-use-default-member-init <>โ second column: Yes โจmodernize-use-designated-initializers <>โ second column: Yes โจmodernize-use-emplace <>โ second column: Yes โจmodernize-use-equals-default <>โ second column: Yes โจmodernize-use-equals-delete <>โ second column: Yes โจmodernize-use-integer-sign-comparison <>โ second column: Yes โจmodernize-use-nodiscard <>โ second column: Yes โจmodernize-use-noexcept <>โ second column: Yes โจmodernize-use-nullptr <>โ second column: Yes โจmodernize-use-override <>โ second column: Yes โจmodernize-use-ranges <>โ second column: Yes โจmodernize-use-scoped-lock <>โ second column: Yes โจmodernize-use-starts-ends-with <>โ second column: Yes โจmodernize-use-std-format <>โ second column: Yes โจmodernize-use-std-numbers <>โ second column: Yes โจmodernize-use-std-print <>โ second column: Yes โจmodernize-use-trailing-return-type <>โ second column: Yes โจmodernize-use-transparent-functors <>โ second column: Yes โจmodernize-use-uncaught-exceptions <>โ second column: Yes โจmodernize-use-using <>โ second column: Yes โจmpi-buffer-deref <>โ second column: Yes โจmpi-type-mismatch <>โ second column: Yes โจobjc-assert-equals <>โ second column: Yes โจobjc-avoid-nserror-init <>โ second column: blank, bestie ๐ซถobjc-dealloc-in-category <>โ second column: blank ๐objc-forbidden-subclassing <>โ second column: blank ๐ซถobjc-missing-hash <>โ second column: blank ๐objc-nsdate-formatter <>โ second column: blank ๐ซถobjc-nsinvocation-argument-lifetime <>โ second column: Yes โจobjc-property-declaration <>โ second column: Yes โจobjc-super-self <>โ second column: Yes โจopenmp-exception-escape <>โ second column: blank ๐openmp-use-default-none <>โ second column: blank ๐ซถ
and that's where this lil' slice of the list wraps upโmore coming in the next part, so stay tuned, bestie ๐๐ฆ
okay we're still deep in this check list and honestly the table borders are doing the most, so let me just hand you the rows flat, one per line, exactly in order ๐
quick heads-up on format: the second column in this table is either Yes or literally nothing, and I'm not out here guessing what it stands for โ I'm just copying it faithfully, character for character. The little <> bit at the end of each name? Also staying exactly where the page put it. Not me editing the source, nope ๐
performance-avoid-endl <>โ Yes โperformance-enum-size <>โ blank ๐ซฅperformance-faster-string-find <>โ Yes โperformance-for-range-copy <>โ Yes โperformance-implicit-conversion-in-loop <>โ blank ๐ซฅperformance-inefficient-algorithm <>โ Yes โperformance-inefficient-string-concatenation <>โ blank ๐ซฅperformance-inefficient-vector-operation <>โ Yes โperformance-move-const-arg <>โ Yes โperformance-move-constructor-init <>โ blank ๐ซฅperformance-no-automatic-move <>โ blank ๐ซฅperformance-no-int-to-ptr <>โ blank ๐ซฅperformance-noexcept-destructor <>โ Yes โperformance-noexcept-move-constructor <>โ Yes โperformance-noexcept-swap <>โ Yes โperformance-trivially-destructible <>โ Yes โperformance-type-promotion-in-math-fn <>โ Yes โperformance-unnecessary-copy-initialization <>โ Yes โperformance-unnecessary-value-param <>โ Yes โ
and that's the whole performance- family, bestie โ every single one accounted for, nothing skipped, nothing merged โจ
then we slide straight into portability, which is giving "will this even compile on your friend's machine" energy ๐ฆ
portability-avoid-pragma-once <>โ blank ๐ซฅportability-restrict-system-includes <>โ Yes โportability-simd-intrinsics <>โ blank ๐ซฅportability-std-allocator-const <>โ blank ๐ซฅportability-template-virtual-member-function <>โ blank ๐ซฅ
five of 'em, that's the whole portability block, we love a short section ๐
and now the readability- era begins, aka the biggest vibe-check family in the whole list, and we're only seeing part of it because the page keeps going after this, so don't you dare think this is all of them ๐
readability-ambiguous-smartptr-reset-call <>โ Yes โreadability-avoid-const-params-in-decls <>โ Yes โreadability-avoid-nested-conditional-operator <>โ blank ๐ซฅreadability-avoid-return-with-void-value <>โ Yes โreadability-avoid-unconditional-preprocessor-if <>โ blank ๐ซฅreadability-braces-around-statements <>โ blank ๐ซฅreadability-const-return-type <>โ Yes โreadability-container-contains <>โ Yes โreadability-container-data-pointer <>โ Yes โreadability-container-size-empty <>โ Yes โreadability-convert-member-functions-to-static <>โ Yes โreadability-delete-null-pointer <>โ Yes โreadability-duplicate-include <>โ Yes โreadability-else-after-return <>โ Yes โ
okay lowkey the Yes column is absolutely feeding in this stretch โ eight Yes in a row at the end there, iconic behavior, no notes ๐
and yeah, we're cutting off mid-family at readability-else-after-return because that's literally where this chunk of the page ends. more rows incoming in the next bit โ do not close the terminal, we're not done ๐
okay bestie, we're literally mid-table right now so no re-intro, just vibes and check names ๐ the second column is the same deal as the rows above it โ it's either a big โ Yes or it's blank, and blank means blank, no cap. Ready? Slay, let's go ๐
readability-enum-initial-valueโ โ Yesreadability-function-cognitive-complexityโ (empty cell, no Yes here)readability-function-sizeโ (empty cell)readability-identifier-lengthโ (empty cell)readability-identifier-namingโ โ Yesreadability-implicit-bool-conversionโ โ Yesreadability-inconsistent-declaration-parameter-nameโ โ Yes (the way this name just keeps going ๐ญ)readability-isolate-declarationโ โ Yesreadability-magic-numbersโ (empty cell)readability-make-member-function-constโ โ Yesreadability-math-missing-parenthesesโ โ Yesreadability-misleading-indentationโ (empty cell)readability-misplaced-array-indexโ โ Yesreadability-named-parameterโ โ Yesreadability-non-const-parameterโ โ Yesreadability-operators-representationโ โ Yesreadability-qualified-autoโ โ Yesreadability-redundant-access-specifiersโ โ Yesreadability-redundant-castingโ โ Yesreadability-redundant-control-flowโ โ Yesreadability-redundant-declarationโ โ Yesreadability-redundant-function-ptr-dereferenceโ โ Yesreadability-redundant-inline-specifierโ โ Yesreadability-redundant-member-initโ โ Yesreadability-redundant-preprocessorโ (empty cell)readability-redundant-smartptr-getโ โ Yesreadability-redundant-string-cstrโ โ Yesreadability-redundant-string-initโ โ Yesreadability-reference-to-constructed-temporaryโ (empty cell)readability-simplify-boolean-exprโ โ Yesreadability-simplify-subscript-exprโ โ Yesreadability-static-accessed-through-instanceโ โ Yesreadability-static-definition-in-anonymous-namespaceโ โ Yes (iconic length, honestly)readability-string-compareโ โ Yesreadability-suspicious-call-argumentโ (empty cell)readability-uniqueptr-delete-releaseโ โ Yesreadability-uppercase-literal-suffixโ โ Yesreadability-use-anyofallofโ (empty cell)
and that's the whole readability family accounted for, no skips, no shortcuts ๐ซถ the blank ones are still legit checks btw, the cell is just literally empty in the table โ don't come for me, I'm just the messenger ๐ keep scrolling, there's more coming ๐ฅ
readability-use-concise-preprocessor-directivesโ offers fixes: Yes โจreadability-use-std-min-maxโ offers fixes: Yes โจzircon-temporary-objectsโ offers fixes: (nada, nothing, zilch โ blank cell bestie) ๐ซฅ
๐ Check aliases ๐
okay so this is the glow-up table โ every one of these lil alias names is lowkey just a redirect to a real check. Read it like "you type the first thing, it secretly is the second thing." iykyk ๐
cert-arr39-cโ redirects tobugprone-sizeof-expressionโ offers fixes: (blank)cert-con36-cโ redirects tobugprone-spuriously-wake-up-functionsโ offers fixes: (blank)cert-con54-cppโ redirects tobugprone-spuriously-wake-up-functionsโ offers fixes: (blank)cert-ctr56-cppโ redirects tobugprone-pointer-arithmetic-on-polymorphic-objectโ offers fixes: (blank)cert-dcl03-cโ redirects tomisc-static-assertโ offers fixes: Yes โจcert-dcl16-cโ redirects toreadability-uppercase-literal-suffixโ offers fixes: Yes โจcert-dcl37-cโ redirects tobugprone-reserved-identifierโ offers fixes: Yes โจcert-dcl51-cppโ redirects tobugprone-reserved-identifierโ offers fixes: Yes โจcert-dcl54-cppโ redirects tomisc-new-delete-overloadsโ offers fixes: (blank)cert-dcl59-cppโ redirects togoogle-build-namespacesโ offers fixes: (blank)cert-err09-cppโ redirects tomisc-throw-by-value-catch-by-referenceโ offers fixes: (blank)cert-err61-cppโ redirects tomisc-throw-by-value-catch-by-referenceโ offers fixes: (blank)cert-exp42-cโ redirects tobugprone-suspicious-memory-comparisonโ offers fixes: (blank)cert-fio38-cโ redirects tomisc-non-copyable-objectsโ offers fixes: (blank)
ngl the "Offers fixes" column is giving main character energy โ only a few of these aliases actually hand you a fix. The rest? You get the diagnosis, you write the fix yourself, bestie. character building fr fr ๐ญ
and yes, before you ask โ the alias names are written exactly like that, hyphens and all. don't get creative with them, they're not going to forgive you ๐
โฆand the big mapping table just keeps going, bestie, so here's the next chunk of rows exactly as they land ๐
cert-flp37-c <>โ maps tobugprone-suspicious-memory-comparison <>๐cert-int09-c <>โ maps toreadability-enum-initial-value <>โ and this one's got Yes sitting in that third column โจcert-msc24-c <>โ maps tobugprone-unsafe-functions <>๐ฆcert-msc30-c <>โ maps tocert-msc50-cpp <>๐cert-msc32-c <>โ maps tocert-msc51-cpp <>๐ธcert-msc33-c <>โ maps tobugprone-unsafe-functions <>(yep, same target ascert-msc24-cup there, no cap) ๐ญcert-msc54-cpp <>โ maps tobugprone-signal-handler <>๐cert-oop11-cpp <>โ maps toperformance-move-constructor-init <>๐ฅcert-oop54-cpp <>โ maps tobugprone-unhandled-self-assignment <>๐cert-pos44-c <>โ maps tobugprone-bad-signal-to-kill-thread <>๐cert-pos47-c <>โ maps toconcurrency-thread-canceltype-asynchronous <>๐งcert-sig30-c <>โ maps tobugprone-signal-handler <>๐cert-str34-c <>โ maps tobugprone-signed-char-misuse <>๐
okay now we're switching flavours โ these next ones go straight to the Clang Static Analyzer docs instead of another tidy check:
clang-analyzer-core.BitwiseShift <>โ Clang Static Analyzer core.BitwiseShift <https://clang.llvm.org/docs/analyzer/checkers.html#core-bitwiseshift> ๐ฆclang-analyzer-core.CallAndMessage <>โ Clang Static Analyzer core.CallAndMessage <https://clang.llvm.org/docs/analyzer/checkers.html#core-callandmessage> ๐clang-analyzer-core.DivideZero <>โ Clang Static Analyzer core.DivideZero <https://clang.llvm.org/docs/analyzer/checkers.html#core-dividezero> โจ
quick vibe check ๐ซถ โ those long URLs are written out exactly how they appear, hyphens and all, so don't go "fixing" the wrapping when you copy them.#core-bitwiseshift,#core-callandmessage,#core-dividezero. Character for character, bestie.
And yes, the table is still not done โ more rows coming ๐
clang-analyzer-core.NonNullParamCheckerโ the Clang Static Analyzer checkercore.NonNullParamChecker, and its docs live athttps://clang.llvm.org/docs/analyzer/checkers.html#core-nonnullparamchecker๐clang-analyzer-core.NullDereferenceโ the Clang Static Analyzer checkercore.NullDereference, docs right here:https://clang.llvm.org/docs/analyzer/checkers.html#core-nulldereference๐clang-analyzer-core.StackAddressEscapeโ the Clang Static Analyzer checkercore.StackAddressEscape, full writeup athttps://clang.llvm.org/docs/analyzer/checkers.html#core-stackaddressescapeโจclang-analyzer-core.UndefinedBinaryOperatorResultโ the Clang Static Analyzer checkercore.UndefinedBinaryOperatorResult, deets athttps://clang.llvm.org/docs/analyzer/checkers.html#core-undefinedbinaryoperatorresult๐ฅclang-analyzer-core.VLASizeโ the Clang Static Analyzer checkercore.VLASize, docs over athttps://clang.llvm.org/docs/analyzer/checkers.html#core-vlasize๐ธclang-analyzer-core.uninitialized.ArraySubscriptโ the Clang Static Analyzer checkercore.uninitialized.ArraySubscript, read up athttps://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-arraysubscript๐ฆclang-analyzer-core.uninitialized.Assignโ the Clang Static Analyzer checkercore.uninitialized.Assign, docs athttps://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-assign๐clang-analyzer-core.uninitialized.Branchโ the Clang Static Analyzer checkercore.uninitialized.Branch, all the info athttps://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-branch๐clang-analyzer-core.uninitialized.CapturedBlockVariableโ the Clang Static Analyzer checkercore.uninitialized.CapturedBlockVariable, docs athttps://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-capturedblockvariable๐งclang-analyzer-core.uninitialized.NewArraySizeโ the Clang Static Analyzer checkercore.uninitialized.NewArraySize, the whole spiel athttps://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-newarraysizeโญclang-analyzer-core.uninitialized.UndefReturnโ the Clang Static Analyzer checkercore.uninitialized.UndefReturn, docs athttps://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-undefreturn๐
bestie real talk: that last column on every one of these rows is justโฆ empty ๐ซถ so there's literally nothing extra to report there, no cap.
clang-analyzer-cplusplus.ArrayDelete<>โ Clang Static Analyzercplusplus.ArrayDeleteโ docs live athttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-arraydelete๐ (third column? blank, bestie, not a single character in it)clang-analyzer-cplusplus.InnerPointer<>โ Clang Static Analyzercplusplus.InnerPointerโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-innerpointerโจ (also blank in that last column, we stay consistent)clang-analyzer-cplusplus.Move<>โ Clang Static Analyzercplusplus.Moveโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-move๐ฆ (yup, empty third column again)clang-analyzer-cplusplus.NewDelete<>โ Clang Static Analyzercplusplus.NewDeleteโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-newdelete๐ฅ (blank, blank, blank โ iconic consistency)clang-analyzer-cplusplus.NewDeleteLeaks<>โ Clang Static Analyzercplusplus.NewDeleteLeaksโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-newdeleteleaks๐ (third column: nothing, as ever)clang-analyzer-cplusplus.PlacementNew<>โ Clang Static Analyzercplusplus.PlacementNewโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-placementnew๐ (third column, still empty, still serving nothing)clang-analyzer-cplusplus.SelfAssignment<>โ Clang Static Analyzercplusplus.SelfAssignmentโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-selfassignment๐ (blank third column, no notes, we move)clang-analyzer-cplusplus.StringChecker<>โ Clang Static Analyzercplusplus.StringCheckerโhttps://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-stringchecker๐ธ (third column empty, luv)clang-analyzer-deadcode.DeadStores<>โ Clang Static Analyzerdeadcode.DeadStoresโhttps://clang.llvm.org/docs/analyzer/checkers.html#deadcode-deadstores๐ง (third column: nope, nothing)clang-analyzer-fuchsia.HandleChecker<>โ Clang Static Analyzerfuchsia.HandleCheckerโhttps://clang.llvm.org/docs/analyzer/checkers.html#fuchsia-handlechecker๐ (third column blank, we're so serious about this)clang-analyzer-nullability.NullPassedToNonnull<>โ Clang Static Analyzernullability.NullPassedToNonnullโhttps://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullpassedtononnull๐ (third column empty, and the row is done)
ok so real talk: every single one of these has that little <> sitting next to the checker name, and every single one has an empty third column. Not me checking eleven times ๐ญ โ it's just how the table is. Don't go looking for a value there, there isn't one, periodt ๐
clang-analyzer-nullability.NullReturnedFromNonnull๐ โ Clang Static Analyzer, and the checker itself isnullability.NullReturnedFromNonnullโ full tea athttps://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullreturnedfromnonnullclang-analyzer-nullability.NullableDereferencedโจ โ Clang Static Analyzer, checkernullability.NullableDereferencedโ docs live athttps://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullabledereferencedclang-analyzer-nullability.NullablePassedToNonnull๐ โ Clang Static Analyzer, checkernullability.NullablePassedToNonnullโ receipts here:https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullablepassedtononnullclang-analyzer-nullability.NullableReturnedFromNonnull๐ฆ โ Clang Static Analyzer, checkernullability.NullableReturnedFromNonnullโ read all about it:https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullablereturnedfromnonnullclang-analyzer-optin.core.EnumCastOutOfRange๐ฅ โ Clang Static Analyzer, checkeroptin.core.EnumCastOutOfRangeโ the whole story athttps://clang.llvm.org/docs/analyzer/checkers.html#optin-core-enumcastoutofrange
bestie note: yes, the optin ones are literally opt-in โ the name is telling on itself, no cap ๐
clang-analyzer-optin.cplusplus.UninitializedObject๐ซถ โ Clang Static Analyzer, checkeroptin.cplusplus.UninitializedObjectโ details athttps://clang.llvm.org/docs/analyzer/checkers.html#optin-cplusplus-uninitializedobjectclang-analyzer-optin.cplusplus.VirtualCall๐ โ Clang Static Analyzer, checkeroptin.cplusplus.VirtualCallโ docs:https://clang.llvm.org/docs/analyzer/checkers.html#optin-cplusplus-virtualcallclang-analyzer-optin.mpi.MPI-Checker๐ โ Clang Static Analyzer, checkeroptin.mpi.MPI-Checkerโ everything athttps://clang.llvm.org/docs/analyzer/checkers.html#optin-mpi-mpi-checkerclang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker๐ โ Clang Static Analyzer, checkeroptin.osx.cocoa.localizability.EmptyLocalizationContextCheckerโ the link, and yes it's a whole mouthful:https://clang.llvm.org/docs/analyzer/checkers.html#optin-osx-cocoa-localizability-emptylocalizationcontextcheckerclang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker๐ธ โ Clang Static Analyzer, checkeroptin.osx.cocoa.localizability.NonLocalizedStringCheckerโ receipts athttps://clang.llvm.org/docs/analyzer/checkers.html#optin-osx-cocoa-localizability-nonlocalizedstringchecker
likeโฆ the cocoa localizability girlies really said "long name, longer URL" and honestly? iconic ๐ the list keeps going tho, stay with me ๐ง
clang-analyzer-optin.performance.GCDAntipatternโ your gurl the Clang Static Analyzeroptin.performance.GCDAntipatternchecker ๐ docs right here:<https://clang.llvm.org/docs/analyzer/checkers.html#optin-performance-gcdantipattern>clang-analyzer-optin.performance.Paddingโ that's the Clang Static Analyzeroptin.performance.Paddingchecker โจ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#optin-performance-padding>clang-analyzer-optin.portability.UnixAPIโ the Clang Static Analyzeroptin.portability.UnixAPIchecker ๐ฆ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#optin-portability-unixapi>clang-analyzer-optin.taint.TaintedAllocโ Clang Static Analyzeroptin.taint.TaintedAllocchecker, no cap ๐ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#optin-taint-taintedalloc>clang-analyzer-osx.APIโ Clang Static Analyzerosx.APIchecker, slay ๐ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-api>clang-analyzer-osx.NumberObjectConversionโ Clang Static Analyzerosx.NumberObjectConversionchecker ๐ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-numberobjectconversion>clang-analyzer-osx.ObjCPropertyโ Clang Static Analyzerosx.ObjCPropertychecker ๐ธ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-objcproperty>clang-analyzer-osx.SecKeychainAPIโ Clang Static Analyzerosx.SecKeychainAPIchecker ๐ฅ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-seckeychainapi>clang-analyzer-osx.cocoa.AtSyncโ Clang Static Analyzerosx.cocoa.AtSyncchecker ๐ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-atsync>clang-analyzer-osx.cocoa.AutoreleaseWriteโ Clang Static Analyzerosx.cocoa.AutoreleaseWritechecker ๐ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-autoreleasewrite>clang-analyzer-osx.cocoa.ClassReleaseโ Clang Static Analyzerosx.cocoa.ClassReleasechecker ๐ง docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-classrelease>clang-analyzer-osx.cocoa.Deallocโ Clang Static Analyzerosx.cocoa.Deallocchecker ๐ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-dealloc>clang-analyzer-osx.cocoa.IncompatibleMethodTypesโ Clang Static Analyzerosx.cocoa.IncompatibleMethodTypeschecker ๐ญ docs:<https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-incompatiblemethodtypes>clang-analyzer-osx.cocoa.Loopsโ Clang Static Analyzer'sosx.cocoa.Loopschecker ๐ bestie the name literally tells you what it's clocking. Full deets:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-loopsclang-analyzer-osx.cocoa.MissingSuperCallโ Clang Static Analyzerosx.cocoa.MissingSuperCallโจ and like, yes, it's giving "where is yoursupercall, ma'am" energy. Docs:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-missingsupercallclang-analyzer-osx.cocoa.NSAutoreleasePoolโ Clang Static Analyzerosx.cocoa.NSAutoreleasePool๐ซง iconic name, iconic checker. Link:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nsautoreleasepoolclang-analyzer-osx.cocoa.NSErrorโ Clang Static Analyzerosx.cocoa.NSError๐ errors but make it analyzer-core. Receipts:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nserrorclang-analyzer-osx.cocoa.NilArgโ Clang Static Analyzerosx.cocoa.NilArg๐ณ๏ธ ngl a whole checker devoted to nil args is so real of it. Docs:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nilargclang-analyzer-osx.cocoa.NonNilReturnValueโ Clang Static Analyzerosx.cocoa.NonNilReturnValue๐ธ the "bestie you promised a value" checker. Source of truth:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nonnilreturnvalueclang-analyzer-osx.cocoa.ObjCGenericsโ Clang Static Analyzerosx.cocoa.ObjCGenerics๐ generics but Objective-C, we love to see it. Docs:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-objcgenericsclang-analyzer-osx.cocoa.RetainCountโ Clang Static Analyzerosx.cocoa.RetainCount๐ the classic, the legend, the one everybody's heard of. Full page:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-retaincountclang-analyzer-osx.cocoa.RunLoopAutoreleaseLeakโ Clang Static Analyzerosx.cocoa.RunLoopAutoreleaseLeak๐ง "leak" right there in the name, no cap. Docs:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-runloopautoreleaseleakclang-analyzer-osx.cocoa.SelfInitโ Clang Static Analyzerosx.cocoa.SelfInit๐ช main character energy in init form. Receipts:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-selfinitclang-analyzer-osx.cocoa.SuperDeallocโ Clang Static Analyzerosx.cocoa.SuperDealloc๐ it's serving teardown realness. Docs:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-superdeallocclang-analyzer-osx.cocoa.UnusedIvarsโ Clang Static Analyzerosx.cocoa.UnusedIvars๐ง unused instance variables, caught in 4K. Link:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-unusedivarsclang-analyzer-osx.cocoa.VariadicMethodTypesโ Clang Static Analyzerosx.cocoa.VariadicMethodTypes๐ variadic types, the chaotic fave. Docs:https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-variadicmethodtypes
okay so that's the whole osx.cocoa.* block done, periodt ๐
keep scrolling bestie, there's more table where that came from ๐ฆ
clang-analyzer-osx.coreFoundation.CFErrorโ Clang Static Analyzer checker,osx.coreFoundation.CFErrorflavored. Docs live at https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cferror โจclang-analyzer-osx.coreFoundation.CFNumberโ Clang Static Analyzer,osx.coreFoundation.CFNumberedition. Receipts: https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cfnumberclang-analyzer-osx.coreFoundation.CFRetainReleaseโ Clang Static Analyzerosx.coreFoundation.CFRetainRelease, yes bestie it's a whole name. Docs: https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cfretainreleaseclang-analyzer-osx.coreFoundation.containers.OutOfBoundsโ Clang Static Analyzerosx.coreFoundation.containers.OutOfBounds, nested name and all ๐ซถ https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-containers-outofboundsclang-analyzer-osx.coreFoundation.containers.PointerSizedValuesโ Clang Static Analyzerosx.coreFoundation.containers.PointerSizedValues, iconic long name, no notes. https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-containers-pointersizedvaluesclang-analyzer-security.FloatLoopCounterโ Clang Static Analyzersecurity.FloatLoopCounter. Docs are right here: https://clang.llvm.org/docs/analyzer/checkers.html#security-floatloopcounterclang-analyzer-security.PutenvStackArrayโ Clang Static Analyzersecurity.PutenvStackArray. Yep, the anchor really ends in-c: https://clang.llvm.org/docs/analyzer/checkers.html#security-putenvstackarray-cclang-analyzer-security.SetgidSetuidOrderโ Clang Static Analyzersecurity.SetgidSetuidOrder, also with the-canchor (don't ask, just copy it exactly ๐ ): https://clang.llvm.org/docs/analyzer/checkers.html#security-setgidsetuidorder-cclang-analyzer-security.cert.env.InvalidPtrโ Clang Static Analyzersecurity.cert.env.InvalidPtr. https://clang.llvm.org/docs/analyzer/checkers.html#security-cert-env-invalidptrclang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandlingโ Clang Static Analyzersecurity.insecureAPI.DeprecatedOrUnsafeBufferHandling, a name that ate and left no crumbs ๐ญ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-deprecatedorunsafebufferhandlingclang-analyzer-security.insecureAPI.UncheckedReturnโ Clang Static Analyzersecurity.insecureAPI.UncheckedReturn. Docs: https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-uncheckedreturn
and yes, the third column on every single one of these is empty โ that's not me hiding something from you, it's literally blank in the source ๐
clang-analyzer-security.insecureAPI.bcmpโ the Clang Static Analyzersecurity.insecureAPI.bcmpchecker, bestie ๐ docs live at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bcmpclang-analyzer-security.insecureAPI.bcopyโ Clang Static Analyzersecurity.insecureAPI.bcopyโจ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bcopyclang-analyzer-security.insecureAPI.bzeroโ Clang Static Analyzersecurity.insecureAPI.bzero๐ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bzeroclang-analyzer-security.insecureAPI.decodeValueOfObjCTypeโ Clang Static Analyzersecurity.insecureAPI.decodeValueOfObjCType๐ฆ (and yeah, this one has no doc link listed, sorry bestie, that's just how the table crumbles ๐ญ)clang-analyzer-security.insecureAPI.getpwโ Clang Static Analyzersecurity.insecureAPI.getpw๐ธ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-getpwclang-analyzer-security.insecureAPI.getsโ Clang Static Analyzersecurity.insecureAPI.gets๐ iconic behaviour tbh, docs at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-getsclang-analyzer-security.insecureAPI.mkstempโ Clang Static Analyzersecurity.insecureAPI.mkstemp๐ซถ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-mkstempclang-analyzer-security.insecureAPI.mktempโ Clang Static Analyzersecurity.insecureAPI.mktemp๐ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-mktempclang-analyzer-security.insecureAPI.randโ Clang Static Analyzersecurity.insecureAPI.randโญ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-randclang-analyzer-security.insecureAPI.strcpyโ Clang Static Analyzersecurity.insecureAPI.strcpy๐ฅ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-strcpyclang-analyzer-security.insecureAPI.vforkโ Clang Static Analyzersecurity.insecureAPI.vfork๐ https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-vforkclang-analyzer-unix.APIโ Clang Static Analyzerunix.API๐ https://clang.llvm.org/docs/analyzer/checkers.html#unix-api
Also quick heads up โ every single one of these rows has a third column and that column is empty on all twelve, so likeโฆ nothing to report there, it's giving blank canvas ๐จ bestie we move on โจ
clang-analyzer-unix.BlockInCriticalSection<>โ Clang Static Analyzerunix.BlockInCriticalSectionโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-blockincriticalsectionclang-analyzer-unix.Errno<>โ Clang Static Analyzerunix.Errnoโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-errnoclang-analyzer-unix.Malloc<>โ Clang Static Analyzerunix.Mallocโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-mallocclang-analyzer-unix.MallocSizeof<>โ Clang Static Analyzerunix.MallocSizeofโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-mallocsizeofclang-analyzer-unix.MismatchedDeallocator<>โ Clang Static Analyzerunix.MismatchedDeallocatorโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocatorclang-analyzer-unix.StdCLibraryFunctions<>โ Clang Static Analyzerunix.StdCLibraryFunctionsโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-stdclibraryfunctionsclang-analyzer-unix.Stream<>โ Clang Static Analyzerunix.Streamโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-streamclang-analyzer-unix.Vfork<>โ Clang Static Analyzerunix.Vforkโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-vforkclang-analyzer-unix.cstring.BadSizeArg<>โ Clang Static Analyzerunix.cstring.BadSizeArgโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-cstring-badsizeargclang-analyzer-unix.cstring.NullArg<>โ Clang Static Analyzerunix.cstring.NullArgโ https://clang.llvm.org/docs/analyzer/checkers.html#unix-cstring-nullargclang-analyzer-webkit.NoUncountedMemberChecker<>โ Clang Static Analyzerwebkit.NoUncountedMemberCheckerโ https://clang.llvm.org/docs/analyzer/checkers.html#webkit-nouncountedmembercheckerclang-analyzer-webkit.RefCntblBaseVirtualDtor<>โ Clang Static Analyzerwebkit.RefCntblBaseVirtualDtorโ https://clang.llvm.org/docs/analyzer/checkers.html#webkit-refcntblbasevirtualdtorclang-analyzer-webkit.UncountedLambdaCapturesChecker<>โ Clang Static Analyzerwebkit.UncountedLambdaCapturesCheckerโ https://clang.llvm.org/docs/analyzer/checkers.html#webkit-uncountedlambdacaptureschecker
okay bestie we literally just ran through the whole unix.* and webkit.* analyzer squad ๐
and yeah, every single one of these is a checker name you can hand to the tools, paired with its Clang Static Analyzer description and the exact docs link ๐ โ click all thirteen of them, iykyk ๐ฆ
cppcoreguidelines-avoid-c-arrays <>โmodernize-avoid-c-arrays <>, third column: blank ๐ซถcppcoreguidelines-avoid-magic-numbers <>โreadability-magic-numbers <>, third column: blank โจcppcoreguidelines-c-copy-assignment-signature <>โmisc-unconventional-assign-operator <>, third column: blank ๐cppcoreguidelines-explicit-virtual-functions <>โmodernize-use-override <>, third column:Yes๐ฅcppcoreguidelines-macro-to-enum <>โmodernize-macro-to-enum <>, third column:Yes๐ฅcppcoreguidelines-narrowing-conversions <>โbugprone-narrowing-conversions <>, third column: blank ๐ธcppcoreguidelines-noexcept-destructor <>โperformance-noexcept-destructor <>, third column:Yes๐ฅcppcoreguidelines-noexcept-move-operations <>โperformance-noexcept-move-constructor <>, third column:Yes๐ฅcppcoreguidelines-noexcept-swap <>โperformance-noexcept-swap <>, third column:Yes๐ฅcppcoreguidelines-non-private-member-variables-in-classes <>โmisc-non-private-member-variables-in-classes <>, third column: blank ๐ฆcppcoreguidelines-use-default-member-init <>โmodernize-use-default-member-init <>, third column:Yes๐ฅfuchsia-header-anon-namespaces <>โgoogle-build-namespaces <>, third column: blank ๐google-readability-braces-around-statements <>โreadability-braces-around-statements <>, third column: blank ๐google-readability-function-size <>โreadability-function-size <>, third column: blank ๐google-readability-namespace-comments <>โllvm-namespace-comment <>, third column: blank ๐hicpp-avoid-c-arrays <>โmodernize-avoid-c-arrays <>, third column: blank ๐งhicpp-avoid-goto <>โcppcoreguidelines-avoid-goto <>, third column: blank ๐งhicpp-braces-around-statements <>โreadability-braces-around-statements <>, third column: blank ๐ง
okay so quick vibe check on what you're looking at bestie ๐ โ every single line up there is one alias mapping. The name on the left is the alias, the name on the right (after the arrow) is the actual check it points at, and the third column is just... either Yes or nothing at all. Ngl the Yes ones are giving main character energy while the blanks are just sitting there quietly ๐.
and yes, some checks show up more than once as a target โ modernize-avoid-c-arrays <> and readability-braces-around-statements <> both get hit twice from different aliases, which is completely normal, no cap. Multiple names, one actual check behind the curtain ๐ญ.
heads up: keep the names exactly as written, <> and all โ those little angle brackets are part of the row, not decoration on my end ๐
so yeah, that's the row-by-row, nothing skipped, nothing merged ๐ซถโจ
...and the alias parade keeps going, bestie ๐
same deal as before: the hicpp- name on the left, the check it actually maps to on the right, and a Yes when that row's marked Yes โจ
hicpp-deprecated-headersโmodernize-deprecated-headersโYes๐hicpp-explicit-conversionsโgoogle-explicit-constructorโYes๐ฅhicpp-function-sizeโreadability-function-sizeโ noYeson this one ๐ซถhicpp-invalid-access-movedโbugprone-use-after-moveโ also noYes๐ฆhicpp-member-initโcppcoreguidelines-pro-type-member-initโYes๐hicpp-move-const-argโperformance-move-const-argโYesโจhicpp-named-parameterโreadability-named-parameterโYes๐hicpp-new-delete-operatorsโmisc-new-delete-overloadsโ noYeshere ๐ธhicpp-no-array-decayโcppcoreguidelines-pro-bounds-array-to-pointer-decayโ noYes๐ญ (and yes, that target name is a whole mouthful, we don't make the rules)hicpp-no-mallocโcppcoreguidelines-no-mallocโ noYes๐hicpp-noexcept-moveโperformance-noexcept-move-constructorโYes๐hicpp-special-member-functionsโcppcoreguidelines-special-member-functionsโ noYesโญhicpp-static-assertโmisc-static-assertโYes๐งhicpp-undelegated-constructorโbugprone-undelegated-constructorโ noYes๐hicpp-uppercase-literal-suffixโreadability-uppercase-literal-suffixโYes๐hicpp-use-autoโmodernize-use-autoโYes๐ฅhicpp-use-emplaceโmodernize-use-emplaceโYes๐hicpp-use-equals-defaultโmodernize-use-equals-defaultโYes๐hicpp-use-equals-deleteโ redirects tomodernize-use-equals-deleteโ last column:Yeshicpp-use-noexceptโ redirects tomodernize-use-noexceptโ last column:Yeshicpp-use-nullptrโ redirects tomodernize-use-nullptrโ last column:Yeshicpp-use-overrideโ redirects tomodernize-use-overrideโ last column:Yeshicpp-varargโ redirects tocppcoreguidelines-pro-type-varargโ last column: blank, noYeshere bestie ๐ซฅllvm-else-after-returnโ redirects toreadability-else-after-returnโ last column:Yesllvm-qualified-autoโ redirects toreadability-qualified-autoโ last column:Yes
๐ง Clang-tidy IDE/Editor Integrations
Okay so here's the tea โ โ clang-tidy isn't just a standalone tool. Nope. It's literally integrated into a whole bunch of IDEs, code analyzers, and editors. She gets around. ๐
And the docs are like, super clear about their fave: use clangd โ <http://clangd.llvm.org/> โ because it integrates clang-tidy and it's available <https://clangd.llvm.org/installation.html#editor-plugins> in most major editors through plugins. Like Vim, Emacs, Visual Studio Code, Sublime Text and more <https://langserver.org/#implementations-client>. The way clangd just works everywhere? Obsessed. โจ
The following table shows the most well-known clang-tidy integrations in detail ๐
The feature columns are, in order: On-the-fly inspection, Check list configuration (GUI), Options to checks (GUI), Configuration via .clang-tidy files, and Custom clang-tidy binary. A + means yes she does that, a - means nope she doesn't. ๐
- A.L.E. for Vim
- On-the-fly inspection:
+ - Check list configuration (GUI):
- - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:- - Custom clang-tidy binary:
+
- On-the-fly inspection:
- Clang Power Tools for Visual Studio
- On-the-fly inspection:
- - Check list configuration (GUI):
+ - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
-
- On-the-fly inspection:
- Clangd
- On-the-fly inspection:
+ - Check list configuration (GUI):
- - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
-
- On-the-fly inspection:
- CLion IDE
- On-the-fly inspection:
+ - Check list configuration (GUI):
+ - Options to checks (GUI):
+ - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
+ - okay CLion ate that entire row, all pluses, main character energy fr fr ๐
- On-the-fly inspection:
- CodeChecker
- On-the-fly inspection:
- - Check list configuration (GUI):
- - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:- - Custom clang-tidy binary:
+
- On-the-fly inspection:
- CPPCheck
- On-the-fly inspection:
- - Check list configuration (GUI):
- - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:- - Custom clang-tidy binary:
- - all dashes bestie, she's not doing any of it ๐
- On-the-fly inspection:
- CPPDepend
- On-the-fly inspection:
- - Check list configuration (GUI):
- - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:- - Custom clang-tidy binary:
-
- On-the-fly inspection:
- Flycheck for Emacs
- On-the-fly inspection:
+ - Check list configuration (GUI):
- - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
+
- On-the-fly inspection:
- KDevelop IDE
- On-the-fly inspection:
- - Check list configuration (GUI):
+ - Options to checks (GUI):
+ - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
+
- On-the-fly inspection:
- Qt Creator IDE
- On-the-fly inspection:
+ - Check list configuration (GUI):
+ - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
+
- On-the-fly inspection:
- ReSharper C++ for Visual Studio
- On-the-fly inspection:
+ - Check list configuration (GUI):
+ - Options to checks (GUI):
- - Configuration via
.clang-tidyfiles:+ - Custom clang-tidy binary:
+
- On-the-fly inspection:
ngl the only thing to remember from this whole table:+= yes she supports it,-= nope, don't @ me. Every single+and-above is exactly what the original says ๐ซถ
- Syntastic for Vim โ
+,-,-,-,+ - Visual Assist for Visual Studio โ
+,+,-,-,-
(those are the last two rows of that big compatibility table, for the record ๐ )
IDEs
CLion https://www.jetbrains.com/clion/ 2017.2 and later integrates clang-tidy https://www.jetbrains.com/help/clion/clang-tidy-checks-support.html as an extension to the built-in code analyzer. Starting from 2018.2 EAP, CLion lets you use clang-tidy via Clangd. Inspections and applicable quick-fixes happen on the fly, and checks can be configured in standard command line format. In this integration you can switch to a clang-tidy binary that's different from the bundled one, pass the configuration in .clang-tidy files instead of using the IDE settings, and configure options for particular checks. Iconic behavior honestly ๐ฅ
KDevelop https://www.kdevelop.org/ with the kdev-clang-tidy https://github.com/KDE/kdev-clang-tidy/ plugin, starting from version 5.1, performs static analysis using clang-tidy. The plugin launches the clang-tidy binary from the specified location and parses its output to give you a list of issues. Slay.
QtCreator https://www.qt.io/ 4.6 integrates clang-tidy warnings into the editor diagnostics under the Clang Code Model https://doc.qt.io/qtcreator/creator-clang-codemodel.html. To employ clang-tidy inspection in QtCreator, you need to create a copy of one of the presets and choose the checks to be performed. Since QtCreator 4.7, project-wide analysis is possible with the Clang Tools https://doc.qt.io/qtcreator/creator-clang-tools.html analyzer.
MS Visual Studio https://visualstudio.microsoft.com/ can integrate clang-tidy by means of three different tools:
- The ReSharper C++ https://www.jetbrains.com/help/resharper/Clang_Tidy_Integration.html extension, version 2017.3 and later, gives you seamless clang-tidy integration: checks and quick-fixes run alongside native inspections. Apart from that, ReSharper C++ incorporates clang-tidy as a separate step of its code clean-up process.
- Visual Assist https://docs.wholetomato.com/default.asp?W761 build 2210 includes a subset of clang-tidy checklist to inspect the code as you edit.
- Another way to bring clang-tidy functionality to Visual Studio is the Clang Power Tools https://marketplace.visualstudio.com/items?itemName=caphyon.ClangPowerTools plugin, which includes most of the clang-tidy checks and runs them during compilation or as a separate step of code analysis.
Editors
Emacs24 https://www.gnu.org/s/emacs/, when expanded with the Flycheck https://github.com/ch1bo/flycheck-clang-tidy plugin, incorporates the clang-tidy inspection into the syntax analyzer. For Vim https://www.vim.org/, you can use Syntastic https://github.com/vim-syntastic/syntastic, which includes clang-tidy, or A.L.E. https://github.com/w0rp/ale, a lint engine that applies clang-tidy along with other linters. Bestie, your editor is covered, don't stress ๐ซถ
Analyzers
clang-tidy is integrated in CPPDepend https://www.cppdepend.com/cppdependv2018 starting from version 2018.1 and CPPCheck https://sourceforge.net/p/cppcheck/news/ 1.82. CPPCheck integration lets you import Visual Studio solutions and run the clang-tidy inspection on them. The CodeChecker https://github.com/Ericsson/codechecker application of version 5.3 or later, which also comes as a plugin https://github.com/Ericsson/CodeCheckerEclipsePlugin for Eclipse, supports clang-tidy as a static analysis instrument and allows you to use a custom clang-tidy binary. The way this ecosystem justโฆ absorbs everything ๐
Getting Involved
clang-tidy has several own checks and can run Clang static analyzer checks, but its real power? It's in the ability to easily write custom checks. That's the whole vibe โจ
Checks are organized in modules, which can be linked into clang-tidy with minimal or no code changes in clang-tidy. So clean. So low-effort.
Checks can plug into the analysis on the preprocessor level using PPCallbacks https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html or on the AST level using AST Matchers https://clang.llvm.org/docs/LibASTMatchers.html. When an error is found, checks can report them in a way similar to how Clang diagnostics work. A fix-it hint can be attached to a diagnostic message. Chef's kiss ๐
The interface provided by clang-tidy makes it easy to write useful and precise checks in just a few lines of code. If you have an idea for a good check, the rest of this document explains how to do this.
There are a few tools particularly useful when developing clang-tidy checks:
add_new_check.pyis a script to automate the process of adding a new check โ it will create the check, update the CMake file and create a test;rename_check.pydoes what the script name suggests, renames an existing check;pp-tracelogs method calls onPPCallbacksfor a source file and is invaluable in understanding the preprocessor mechanism;clang-queryis invaluable for interactive prototyping of AST matchers and exploration of the Clang AST;clang-checkhttps://clang.llvm.org/docs/ClangCheck.html with the-ast-dump(and optionally-ast-dump-filter) provides a convenient way to dump AST of a C++ program.
โ ๏ธ Heads up bestie: if CMake is configured withCLANG_TIDY_ENABLE_STATIC_ANALYZER=NO, clang-tidy will not be built with support for theclang-analyzer-*checks or thempi-*checks. So likeโฆ don't be surprised when they're missing ๐
Choosing the Right Place for your Check
If you have an idea of a check, you should decide whether it should be implemented as a:
- Clang diagnostic: if the check is generic enough, targets code patterns that most probably are bugs (rather than style or readability issues), can be implemented effectively and with an extremely low false positive rate, it may make a good Clang diagnostic.
- Clang static analyzer check: if the check requires some sort of control flow analysis, it should probably be implemented as a static analyzer check.
- clang-tidy check is a good choice for linter-style checks, checks that are related to a certain coding style, checks that address code readability, etc.
Basically: know your lane, pick your lane ๐
Preparing your Workspace
If you are new to LLVM development, you should read the Getting Started with the LLVM System https://llvm.org/docs/GettingStarted.html, Using Clang Tools https://clang.llvm.org/docs/ClangTools.html and How To Setup Clang Tooling For LLVM https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html documents to check out and build LLVM, Clang and Clang Extra Tools with CMake.
Once you are done, change to the llvm/clang-tools-extra directory, and let's start! ๐
When you configure the CMake build https://llvm.org/docs/GettingStarted.html#local-llvm-configuration, make sure you enable the clang and clang-tools-extra projects to build clang-tidy. Because your new check will have associated documentation, you'll also want to install Sphinx https://www.sphinx-doc.org/en/master/ and enable it in the CMake configuration. And to save build time of the core Clang libraries you may want to only enable the X86 target in the CMake configuration. Your laptop's fans will thank you ๐ญ
The Directory Structure
clang-tidy source code resides in the llvm/clang-tools-extra directory and is structured like this:
clang-tidy/ # Clang-tidy core.
|-- ClangTidy.h # Interfaces for users.
|-- ClangTidyCheck.h # Interfaces for checks.
|-- ClangTidyModule.h # Interface for clang-tidy modules.
|-- ClangTidyModuleRegistry.h # Interface for registering of modules.
...
|-- google/ # Google clang-tidy module.
|-+
|-- GoogleTidyModule.cpp
|-- GoogleTidyModule.h
...
|-- llvm/ # LLVM clang-tidy module.
|-+
|-- LLVMTidyModule.cpp
|-- LLVMTidyModule.h
...
|-- objc/ # Objective-C clang-tidy module.
|-+
|-- ObjCTidyModule.cpp
|-- ObjCTidyModule.h
...
|-- tool/ # Sources of the clang-tidy binary.
...
test/clang-tidy/ # Integration tests.
...
unittests/clang-tidy/ # Unit tests.
|-- ClangTidyTest.h
|-- GoogleModuleTest.cpp
|-- LLVMModuleTest.cpp
|-- ObjCModuleTest.cpp
...
Writing a clang-tidy Check
So you have an idea of a useful check for clang-tidy. Main character energy, honestly ๐
First, if you're not familiar with LLVM development, read through the Getting Started with the LLVM System https://llvm.org/docs/GettingStarted.html document for instructions on setting up your workflow, and the LLVM Coding Standards https://llvm.org/docs/CodingStandards.html document to familiarize yourself with the coding style used in the project. For code reviews we currently use LLVM Github https://github.com/llvm/llvm-project, though historically we used Phabricator. (RIP Phabricator, you had a good run ๐ฏ๏ธ)
Next, you need to decide which module the check belongs to. Modules are located in subdirectories of clang-tidy/ https://github.com/llvm/llvm-project/tree/main/clang-tools-extra/clang-tidy/ and contain checks targeting a certain aspect of code quality (performance, readability, etc.), a certain coding style or standard (Google, LLVM, CERT, etc.) or a widely used API (e.g. MPI). Their names are the same as the user-facing check group names described above #checks-groups-table.
After choosing the module and the name for the check, run the clang-tidy/add_new_check.py script to create the skeleton of the check and plug it to clang-tidy. It's the recommended way of adding new checks. No cap โ just run the script and let it cook ๐
Quick heads-up bestie: by default your shiny new check only wakes up for C++ code. If you want it to vibe under other language options, just use the --language parameter of the script. iykyk ๐
Say we're building a readability-awesome-function-names. We'd run:
$ clang-tidy/add_new_check.py readability awesome-function-names
And the add_new_check.py script goes off and does all this for you:
- creates the class for your check inside the specified module's directory, and registers it in the module and in the build system ๐ ๏ธ
- creates a lit test file in the
test/clang-tidy/directory - creates a documentation file and hooks it into
docs/clang-tidy/checks/list.rst
Ok so let's actually look at this check class definition in more detail:
...
#include "../ClangTidyCheck.h"
namespace clang::tidy::readability {
...
class AwesomeFunctionNamesCheck : public ClangTidyCheck {
public:
AwesomeFunctionNamesCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
};
} // namespace clang::tidy::readability
...
Real talk about that constructor: it takes the Name and Context parameters and you literally must forward them to the ClangTidyCheck constructor. No skipping, that's the whole assignment ๐ซถ
In our case the check needs to operate at the AST level, so it overrides the registerMatchers and check methods. If we wanted to analyze code at the preprocessor level instead, we'd be overriding registerPPCallbacks instead. Different beast entirely, bestie.
In registerMatchers we build an AST Matcher (peep AST Matchers https://clang.llvm.org/docs/LibASTMatchers.html for the whole lore) that finds the pattern in the AST we actually want to inspect. Whatever matches gets handed to the check method, which can dig deeper and report diagnostics. The way that whole pipeline works is honestly iconic โจ
using namespace ast_matchers;
void AwesomeFunctionNamesCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(functionDecl().bind("x"), this);
}
void AwesomeFunctionNamesCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
if (!MatchedDecl->getIdentifier() || MatchedDecl->getName().startswith("awesome_"))
return;
diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome")
<< MatchedDecl
<< FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
}
(If you want to see an example of a genuinely useful check, go look at clang-tidy/google/ExplicitConstructorCheck.h https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.h and clang-tidy/google/ExplicitConstructorCheck.cpp https://reviews.llvm.org/diffusion/L/browse/clang-tools-extra/trunk/clang-tidy/google/ExplicitConstructorCheck.cpp.)
Now โ if you need to interact with macros or preprocessor directives, that's when you override registerPPCallbacks. But heads up ๐ซฃ: the add_new_check.py script does not generate an override for this method in the starting point for your new check. You're adding it yourself, bestie.
๐ฑ Check development tips ๐ฑ
Writing your very first check can be lowkey daunting, especially if the LLVM and Clang code bases are still new territory for you. So here are some suggestions for orienting yourself in the codebase and working on your check incrementally โ small steps, no chaos ๐ธ
๐ Guide to useful documentation ๐
A bunch of the support classes built for LLVM get used by Clang too, like StringRef https://llvm.org/docs/ProgrammersManual.html#the-stringref-class and SmallVector https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h. These and other commonly used classes are described in the Important and useful LLVM APIs https://llvm.org/docs/ProgrammersManual.html#important-and-useful-llvm-apis and Picking the Right Data Structure for the Task https://llvm.org/docs/ProgrammersManual.html#picking-the-right-data-structure-for-a-task sections of the LLVM Programmer's Manual https://llvm.org/docs/ProgrammersManual.html. And ngl, you do not need to memorize all the details of these classes โ the generated doxygen documentation https://llvm.org/doxygen/ has everything whenever you need it. In the header LLVM/ADT/STLExtras.h https://llvm.org/doxygen/STLExtras_8h.html you'll find useful versions of the STL algorithms that operate on LLVM containers, like llvm::all_of https://llvm.org/doxygen/STLExtras_8h.html#func-members. ๐
Clang is implemented on top of LLVM and brings its own set of classes you'll be hanging out with while writing your check. When a check issues diagnostics and fix-its, those get associated with locations in the source code. Source code locations, source files, ranges of source locations and the SourceManager https://clang.llvm.org/doxygen/classclang_1_1SourceManager.html class are the mechanisms for describing those locations. These topics (and more) live in the "Clang" CFE Internals Manual https://clang.llvm.org/docs/InternalsManual.html. The doxygen-generated docs are your reference for Clang's internals, while that document is the guide for actual humans developing stuff. The topics in that manual that matter to a check developer are:
- The Clang "Basic" Library
https://clang.llvm.org/docs/InternalsManual.html#the-clang-basic-libraryโ all the info about diagnostics, fix-it hints and source locations ๐ - The Lexer and Preprocessor Library
https://clang.llvm.org/docs/InternalsManual.html#the-lexer-and-preprocessor-libraryโ all about tokens, lexing (turning characters into tokens) and the preprocessor - The AST Library
https://clang.llvm.org/docs/InternalsManual.html#the-ast-libraryโ how C++ source statements get represented as an abstract syntax tree (AST) ๐ณ
Most checks interact with C++ source code through the AST. Some checks interact with the preprocessor. Here's the flow, bestie: the input source file gets lexed and preprocessed, then parsed into the AST. Once the AST is fully constructed, the check runs โ applying the check's registered AST matchers against the AST and invoking the check with the set of matched nodes from the AST. Monitoring what the preprocessor does is detached from AST construction, but a check can collect info during preprocessing and use it later when nodes get matched by the AST. Fr fr, that's the whole lifecycle ๐ฆ
Every syntactic (and sometimes semantic) element of C++ source code is represented by different classes in the AST. You pick the portions of the AST you care about by composing AST matcher functions. And you're gonna want to study the AST Matcher Reference https://clang.llvm.org/docs/LibASTMatchersReference.html carefully to understand the relationship between the different matcher functions. No cap, that reference is the main character here ๐
๐ฎ Using the Transformer library ๐ฎ
The Transformer library lets you write a check that transforms source code by expressing the transformation as a RewriteRule. It gives you functions for composing edits to source code to create rewrite rules. So unless you actually need low-level source location manipulation, you may wanna consider writing your check with the Transformer library. The Clang Transformer Tutorial https://clang.llvm.org/docs/ClangTransformerTutorial.html describes the Transformer library in detail โจ
To use the Transformer library, make these changes to the code the add_new_check.py script generated:
- include
../utils/TransformerClangTidyCheck.hinstead of../ClangTidyCheck.h - change the base class of your check from
ClangTidyChecktoTransformerClangTidyCheck - delete the override of the
registerMatchersandcheckmethods in your check class ๐ - write a function that creates the
RewriteRulefor your check - call that function in your check's constructor to pass the rewrite rule to
TransformerClangTidyCheck's constructor
๐ง Developing your check incrementally ๐ง
The best way to develop your check is to start with the simple test cases and crank the complexity up incrementally. The test file the add_new_check.py script creates is your starting point for test cases. Rough outline of the process looks like this:
- Write a test case for your check.
- Prototype matchers on the test file using
clang-query. - Capture the working matchers in the
registerMatchersmethod. - Issue the necessary diagnostics and fix-its in the
checkmethod. - Add the necessary
CHECK-MESSAGESandCHECK-FIXESannotations to your test case to validate the diagnostics and fix-its. - Build the target
check-clang-toolsto confirm the test passes. - Repeat the process until all aspects of your check are covered by tests. ๐
The quickest way to prototype your matcher is using clang-query to interactively build up your matcher. For complicated matchers, build up a matching expression incrementally and use clang-query's let command to save named matching expressions โ it makes your matcher so much simpler:
clang-query> let c1 cxxRecordDecl()
clang-query> match c1
Alternatively, pressing the tab key after a previous matcher's open parentheses would also show which matchers can be chained with the previous matcher โ though some matchers that work may not be listed ๐ญ. Note that tab completion does not currently work on Windows. ๐
Just like breaking up a huge function into smaller chunks with intention-revealing names can help you understand a complex algorithm, breaking up a matcher into smaller matchers with intention-revealing names can help you understand a complicated matcher. Same energy, different layer ๐ซถ
Once you have a working clang-query matcher, the C++ API matchers will be the same or similar to your interactively constructed matcher (there can be cases where they differ slightly). You can use local variables to preserve your intention-revealing names that you applied to nested matchers. ๐
๐ Creating private matchers ๐
Sometimes you want to match a specific aspect of the AST that the existing AST matchers just... don't provide. And that's fine! You can create your own private matcher using the same infrastructure as the public matchers. A private matcher can simplify the processing in your check method by eliminating complex hand-crafted AST traversal of the matched nodes. Using the private matcher lets you select the desired portions of the AST directly in the matcher and refer to it by a bound name in the check method. Obsessed, honestly ๐ธ
๐งช Unit testing helper code ๐งช
Okay so private custom matchers are literally the perfect example of auxiliary support code for your check that can be tested with a unit test. Testing your matchers or other support classes by writing a unit test is way easier than writing a FileCheck integration test โ fr fr. The ASTMatchersTests target contains unit tests for the public AST matcher classes and is a great source of testing idioms for matchers. iykyk ๐
You can build the Clang-tidy unit tests by building the ClangTidyTests target. Test targets in LLVM and Clang are excluded from the "build all" style action of IDE-based CMake generators, so you need to explicitly build the target for the unit tests to actually be built. no cap.
๐ช Making your check robust ๐ช
Once youโve covered your check with the basic โhappy pathโ scenarios, youโll want to torture your check with as many edge cases as you can cover so itโs genuinely robust. Running your check on a large code base, such as Clang/LLVM, is a good way to catch things you forgot to account for in your matchers. However, the LLVM code base may be insufficient for testing purposes as it was developed against a particular set of coding styles and quality measures. The larger the corpus of code the check is tested against, the higher confidence the community will have in the checkโs efficacy and false positive rate. that part.
Some suggestions to ensure your check is robust:
- Create header files that contain code matched by your check.
- Validate that fix-its are properly applied to test header files with
clang-tidy. You will need to perform this test manually until automated support for checking messages and fix-its is added to thecheck_clang_tidy.pyscript. - Define macros that contain code matched by your check.
- Define template classes that contain code matched by your check.
- Define template specializations that contain code matched by your check.
- Test your check under both Windows and Linux environments.
- Watch out for high false positive rates. Ideally, a check would have no false positives, but given that matching against an AST is not control- or data flow- sensitive, a number of false positives are expected. The higher the false positive rate, the less likely the check will be adopted in practice. Mechanisms should be put in place to help the user manage false positives.
- There are two primary mechanisms for managing false positives: supporting a code pattern which allows the programmer to silence the diagnostic in an ad hoc manner and check configuration options to control the behavior of the check.
- Consider supporting a code pattern to allow the programmer to silence the diagnostic whenever such a code pattern can clearly express the programmerโs intent. For example, allowing an explicit cast to void to silence an unused variable diagnostic.
- Consider adding check configuration options to allow the user to opt into more aggressive checking behavior without burdening users for the common high-confidence cases.
๐ Documenting your check ๐
The add_new_check.py script creates entries in the release notes (https://clang.llvm.org/extra/ReleaseNotes.html), the list of checks and a new file for the check documentation itself. It is recommended that you have a concise summation of what your check does in a single sentence that is repeated in the release notes, as the first sentence in the doxygen comments in the header file for your check class and as the first sentence of the check documentation. Avoid the phrase "this check" in your check summation and check documentation. periodt.
If your check relates to a published coding guideline (C++ Core Guidelines, MISRA, etc.) or style guide, provide links to the relevant guideline or style guide sections in your check documentation.
Provide enough examples of the diagnostics and fix-its provided by the check so that a user can easily understand what will happen to their code when the check is run. If there are exceptions or limitations to your check, document them thoroughly. This will help users understand the scope of the diagnostics and fix-its provided by the check.
Building the target docs-clang-tools-html will run the Sphinx documentation generator and create documentation HTML files in the tools/clang/tools/extra/docs/html directory in your build tree. Make sure that your check is correctly shown in the release notes and the list of checks. Make sure that the formatting and structure of your checkโs documentation looks correct. โจ
๐ชช Registering your Check ๐ชช
(The add_new_check.py script takes care of registering the check in an existing module. If you want to create a new module or know the details, read on.)
The check should be registered in the corresponding module with a distinct name:
class MyModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<ExplicitConstructorCheck>(
"my-explicit-constructor");
}
};
Now we need to register the module in the ClangTidyModuleRegistry using a statically initialized variable:
static ClangTidyModuleRegistry::Add<MyModule> X("my-module",
"Adds my lint checks.");
When using LLVM build system, we need to use the following hack to ensure the module is linked into the clang-tidy binary:
Add this near the ClangTidyModuleRegistry::Add<MyModule> variable:
// This anchor is used to force the linker to link in the generated object file
// and thus register the MyModule.
volatile int MyModuleAnchorSource = 0;
And this to the main translation unit of the clang-tidy binary (or the binary you link the clang-tidy library in) clang-tidy/ClangTidyForceLinker.h:
// This anchor is used to force the linker to link the MyModule.
extern volatile int MyModuleAnchorSource;
static int MyModuleAnchorDestination = MyModuleAnchorSource;
โ๏ธ Configuring Checks โ๏ธ
If a check needs configuration options, it can access check-specific options using the Options.get<Type>("SomeOption", DefaultValue) call in the check constructor. In this case the check should also override the ClangTidyCheck::storeOptions method to make the options provided by the check discoverable. This method lets clang-tidy know which options the check implements and what the current values are (e.g. for the -dump-config command line option).
class MyCheck : public ClangTidyCheck {
const unsigned SomeOption1;
const std::string SomeOption2;
public:
MyCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
SomeOption1(Options.get("SomeOption1", -1U)),
SomeOption2(Options.get("SomeOption2", "some default")) {}
void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
Options.store(Opts, "SomeOption1", SomeOption1);
Options.store(Opts, "SomeOption2", SomeOption2);
}
...
Assuming the check is registered with the name "my-check", the option can then be set in a .clang-tidy file in the following way:
CheckOptions:
my-check.SomeOption1: 123
my-check.SomeOption2: 'some other value'
If you need to specify check options on a command line, you can use the inline YAML format:
$ clang-tidy -config="{CheckOptions: {a: b, x: y}}" ...
๐งช Testing Checks ๐งช
To run tests for clang-tidy, build the check-clang-tools target. For instance, if you configured your CMake build with the ninja project generator, use the command:
$ ninja check-clang-tools
clang-tidy checks can be tested using either unit tests or lit (https://llvm.org/docs/CommandGuide/lit.html) tests. Unit tests may be more convenient to test complex replacements with strict checks. Lit (https://llvm.org/docs/CommandGuide/lit.html) tests allow using partial text matching and regular expressions which makes them more suitable for writing compact tests for diagnostic messages.
The check_clang_tidy.py script provides an easy way to test both diagnostic messages and fix-its. It filters out CHECK lines from the test file, runs clang-tidy and verifies messages and fixes with two separate FileCheck (https://llvm.org/docs/CommandGuide/FileCheck.html) invocations: once with FileCheckโs directive prefix set to CHECK-MESSAGES, validating the diagnostic messages, and once with the directive prefix set to CHECK-FIXES, running against the fixed code (i.e., the code after generated fix-its are applied). In particular, CHECK-FIXES: can be used to check that code was not modified by fix-its, by checking that it is present unchanged in the fixed code. The full set of FileCheck (https://llvm.org/docs/CommandGuide/FileCheck.html) directives is available (e.g., CHECK-MESSAGES-SAME:, CHECK-MESSAGES-NOT:), though typically the basic CHECK forms (CHECK-MESSAGES and CHECK-FIXES) are sufficient for clang-tidy tests. Note that the FileCheck (https://llvm.org/docs/CommandGuide/FileCheck.html) documentation mostly assumes the default prefix (CHECK), and hence describes the directive as CHECK:, CHECK-SAME:, CHECK-NOT:, etc. Replace CHECK by either CHECK-FIXES or CHECK-MESSAGES for clang-tidy tests.
An additional check enabled by check_clang_tidy.py ensures that if CHECK-MESSAGES: is used in a file then every warning or error must have an associated CHECK in that file. Or, you can use CHECK-NOTES: instead, if you want to also ensure that all the notes are checked.
To use the check_clang_tidy.py script, put a .cpp file with the appropriate RUN line in the test/clang-tidy directory. Use CHECK-MESSAGES: and CHECK-FIXES: lines to write checks against diagnostic messages and fixed code.
Itโs advised to make the checks as specific as possible to avoid checks matching to incorrect parts of the input. Use [[@LINE+X]]/[[@LINE-X]] substitutions and distinct function and variable names in the test code.
Hereโs an example of a test using the check_clang_tidy.py script (the full source code is at test/clang-tidy/checkers/google/readability-casting.cpp (https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/test/clang-tidy/checkers/google/readability-casting.cpp)):
// RUN: %check_clang_tidy %s google-readability-casting %t
void f(int a) {
int b = (int)a;
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant cast to the same type [google-readability-casting]
// CHECK-FIXES: int b = a;
}
To check more than one scenario in the same test file, use -check-suffix=SUFFIX-NAME on the check_clang_tidy.py command line, or -check-suffixes=SUFFIX-NAME-1,SUFFIX-NAME-2,.... With -check-suffix[es]=SUFFIX-NAME you need to replace your CHECK-* directives with CHECK-MESSAGES-SUFFIX-NAME and CHECK-FIXES-SUFFIX-NAME.
Hereโs the example, bestie โ copy it exactly:
// RUN: %check_clang_tidy -check-suffix=USING-A %s misc-unused-using-decls %t -- -- -DUSING_A
// RUN: %check_clang_tidy -check-suffix=USING-B %s misc-unused-using-decls %t -- -- -DUSING_B
// RUN: %check_clang_tidy %s misc-unused-using-decls %t
...
// CHECK-MESSAGES-USING-A: :[[@LINE-8]]:10: warning: using decl 'A' {{.*}}
// CHECK-MESSAGES-USING-B: :[[@LINE-7]]:10: warning: using decl 'B' {{.*}}
// CHECK-MESSAGES: :[[@LINE-6]]:10: warning: using decl 'C' {{.*}}
// CHECK-FIXES-USING-A-NOT: using a::A;$
// CHECK-FIXES-USING-B-NOT: using a::B;$
// CHECK-FIXES-NOT: using a::C;$
There are many dark corners in the C++ language, and it may be difficult to make your check work perfectly in all cases, especially if it issues fix-it hints. The most frequent pitfalls are macros and templates:
- code written in a macro body/template definition may have a different meaning depending on the macro expansion/template instantiation;
- multiple macro expansions/template instantiations may result in the same code being inspected by the check multiple times (possibly, with different meanings, see 1), and the same warning (or a slightly different one) may be issued by the check multiple times; clang-tidy will deduplicate _identical_ warnings, but if the warnings are slightly different, all of them will be shown to the user (and used for applying fixes, if any);
- making replacements to a macro body/template definition may be fine for some macro expansions/template instantiations, but easily break some other expansions/instantiations.
If you need multiple files to exercise all the aspects of your check, it is recommended you place them in a subdirectory named for the check under the Inputs directory for the module containing your check. This keeps the test directory from getting cluttered.
If you need to validate how your check interacts with system header files, a set of simulated system header files is located in the checkers/Inputs/Headers directory. The path to this directory is available in a lit test with the variable %clang_tidy_headers.
๐งฉ Out-of-tree check plugins
Developing an out-of-tree check as a plugin largely follows the steps outlined above, including creating a new module and doing the hacks to register the module. The plugin is a shared library whose code lives outside the clang-tidy build system. Build and link this shared library against LLVM as done for other kinds of Clang plugins. If using CMake, use the keyword MODULE while invoking add_library or llvm_add_library.
The plugin can be loaded by passing -load to clang-tidy in addition to the names of the checks to enable.
$ clang-tidy --checks=-*,my-explicit-constructor -list-checks -load myplugin.so
There is no expectations regarding ABI and API stability, so the plugin must be compiled against the version of clang-tidy that will be loading the plugin.
The plugins can use threads, TLS, or any other facilities available to in-tree code which is accessible from the external headers.
Note that testing out-of-tree checks might involve getting llvm-lit from an LLVM installation compiled from source. See Getting Started with the LLVM System https://llvm.org/docs/GettingStarted.html for ways to do so.
Alternatively, get lit https://llvm.org/docs/CommandGuide/lit.html following the test-suite guide https://llvm.org/docs/TestSuiteGuide.html and get the FileCheck https://llvm.org/docs/CommandGuide/FileCheck.html binary, and write a version of check_clang_tidy.py https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/test/clang-tidy/check_clang_tidy.py to suit your needs.
๐ Running clang-tidy on LLVM
To test a check itโs best to try it out on a larger code base. LLVM and Clang are the natural targets as you already have the source code around. The most convenient way to run clang-tidy is with a compile command database; CMake can automatically generate one, for a description of how to enable it see How To Setup Clang Tooling For LLVM https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html. Once compile_commands.json is in place and a working version of clang-tidy is in PATH the entire code base can be analyzed with clang-tidy/tool/run-clang-tidy.py. The script executes clang-tidy with the default set of checks on every translation unit in the compile command database and displays the resulting warnings and errors. The script provides multiple configuration flags.
- The default set of checks can be overridden using the
-checksargument, taking the identical format as clang-tidy does. For example-checks=-*,modernize-use-overridewill run themodernize-use-overridecheck only. - To restrict the files examined you can provide one or more regex arguments that the file names are matched against.
run-clang-tidy.py clang-tidy/.*Check\.cppwill only analyze clang-tidy checks. It may also be necessary to restrict the header files that warnings are displayed from by using the-header-filterand-exclude-header-filterflags. They have the same behavior as the corresponding clang-tidy flags. - To apply suggested fixes
-fixcan be passed as an argument. This gathers all changes in a temporary directory and applies them. Passing-formatwill run clang-format over changed lines.
๐ On checks profiling
clang-tidy can collect per-check profiling info, and output it for each processed source file (translation unit).
To enable profiling info collection, use the -enable-check-profile argument. The timings will be output to stderr as a table. Example output:
$ clang-tidy -enable-check-profile -checks=-*,readability-function-size source.cpp
===-------------------------------------------------------------------------===
clang-tidy checks profiling
===-------------------------------------------------------------------------===
Total Execution Time: 1.0282 seconds (1.0258 wall clock)
---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name ---
0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) readability-function-size
0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) Total
It can also store that data as JSON files for further processing. Example output:
$ clang-tidy -enable-check-profile -store-check-profile=. -checks=-*,readability-function-size source.cpp
$ # Note that there won't be timings table printed to the console.
$ ls /tmp/out/
20180516161318717446360-source.cpp.json
$ cat 20180516161318717446360-source.cpp.json
{
"file": "/path/to/source.cpp",
"timestamp": "2018-05-16 16:13:18.717446360",
"profile": {
"time.clang-tidy.readability-function-size.wall": 1.0421266555786133e+00,
"time.clang-tidy.readability-function-size.user": 9.2088400000005421e-01,
"time.clang-tidy.readability-function-size.sys": 1.2418899999999974e-01
}
}
There is only one argument that controls profile storage:
-store-check-profile=<prefix>โ By default reports are printed in tabulated format to stderr. When this option is passed, these per-TU profiles are instead stored as JSON. If the prefix is not an absolute path, it is considered to be relative to the directory from where you have run clang-tidy. All.and..patterns in the path are collapsed, and symlinks are resolved.
Example: Letโs suppose you have a source file named example.cpp, located in the /source directory. Only the input filename is used, not the full path to the source file. Additionally, it is prefixed with the current timestamp.
- If you specify
-store-check-profile=/tmp, then the profile will be saved to/tmp/<ISO8601-like timestamp>-example.cpp.json - If you run clang-tidy from within
/foodirectory, and specify-store-check-profile=., then the profile will still be saved to/foo/<ISO8601-like timestamp>-example.cpp.json
๐ External Clang-Tidy Examples
๐ Introduction
This page provides examples of what people have done with clang-tidy that might serve as useful guides (or starting points) to develop your own checks. They may be helpful for necessary things such as how to write the CMakeLists.txt for an out-of-tree plugin of clang-tidy checks.
If you know of (or wrote!) a tool or project using clang-tidy, please share it on the Discourse forums (Clang Frontend category) https://discourse.llvm.org/c/clang/6 for wider visibility and open a pull-request on LLVM Github https://github.com/llvm/llvm-project to have it added here. Since the primary purpose of this page is to provide examples that can help developers, the listed projects should have code available.
As clang-tidy is using, for example, the AST Matchers and diagnostics of Clang, External Clang Examples https://clang.llvm.org/docs/ExternalClangExamples.html may also be useful to look at for such examples.
๐ ๏ธ List of projects and tools
https://github.com/coveooss/clang-tidy-plugin-examplesโ "This folder contains clang-tidy plugins."
clang-tidy is a clang-based C++ "linter" tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis. clang-tidy is modular and provides a convenient interface for writing new checks.
๐ง Using Clang-Tidy
clang-tidy is a LibTooling https://clang.llvm.org/docs/LibTooling.html-based tool, and itโs easier to work with if you set up a compile command database for your project (for an example of how to do this, see How To Setup Tooling For LLVM https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html). You can also specify compilation options on the command line after --:
$ clang-tidy test.cpp -- -Imy_project/include -DMY_DEFINES ...
If there are too many options or source files to specify on the command line, you can store them in a parameter file, and use clang-tidy with that parameters file:
$ clang-tidy @parameters_file
clang-tidy has its own checks and can also run Clang Static Analyzer checks. Each check has a name and the checks to run can be chosen using the -checks= option, which specifies a comma-separated list of positive and negative (prefixed with -) globs. Positive globs add subsets of checks, and negative globs remove them. For example,
$ clang-tidy test.cpp -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus*
okay so this line lowkey does the most: it disables all default checks (-*) and enables all clang-analyzer-* checks except for clang-analyzer-cplusplus* ones. slay ๐
The -list-checks option lists all the enabled checks. When used without -checks=, it shows checks enabled by default. Use -checks=* to see all available checks, or use any other value of -checks= to see which checks are enabled by that value. periodt โจ
There are currently the following groups of checks:
abseil-โ checks related to the Abseil library. ๐altera-โ checks related to OpenCL programming for FPGAs.android-โ checks related to Android.boost-โ checks related to the Boost library.bugprone-โ checks that target bug-prone code constructs.cert-โ checks related to CERT Secure Coding Guidelines.clang-analyzer-โ Clang Static Analyzer checks. ๐ฆconcurrency-โ checks related to concurrent programming (including threads, fibers, coroutines, etc.).cppcoreguidelines-โ checks related to C++ Core Guidelines.darwin-โ checks related to Darwin coding conventions.fuchsia-โ checks related to Fuchsia coding conventions.google-โ checks related to Google coding conventions.hicpp-โ checks related to High Integrity C++ Coding Standard.linuxkernel-โ checks related to the Linux Kernel coding conventions.llvm-โ checks related to the LLVM coding conventions.llvmlibc-โ checks related to the LLVM-libc coding standards.misc-โ the catch-all for checks that didnโt have a better category. ๐ซถmodernize-โ checks that advocate usage of modern language constructs (and rn โmodernโ means โC++11โ).mpi-โ checks related to MPI (Message Passing Interface).objc-โ checks related to Objective-C coding conventions.openmp-โ checks related to OpenMP API.performance-โ checks that target performance-related issues. ๐ฅportability-โ checks that target portability-related issues that donโt relate to any particular coding style.readability-โ checks that target readability-related issues that donโt relate to any particular coding style.zircon-โ checks related to Zircon kernel coding conventions.
Clang diagnostics are treated in a similar way as check diagnostics. Clang diagnostics are displayed by clang-tidy and can be filtered out using the -checks= option. However, the -checks= option does not affect compilation arguments, so it cannot turn on Clang warnings which are not already turned on in the build configuration. The -warnings-as-errors= option upgrades any warnings emitted under the -checks= flag to errors (but it does not enable any checks itself). ๐ญ
Clang diagnostics have check names starting with clang-diagnostic-. Diagnostics which have a corresponding warning option are named clang-diagnostic-<warning-option>, e.g. the Clang warning controlled by -Wliteral-conversion will be reported with check name clang-diagnostic-literal-conversion. iykyk โญ
The -fix flag instructs clang-tidy to fix found errors if supported by corresponding checks. ๐
An overview of all the command-line options:
$ clang-tidy --help
USAGE: clang-tidy [options] <source0> [... <sourceN>]
OPTIONS:
Generic Options:
--help - Display available options (--help-hidden for more)
--help-list - Display list of available options (--help-list-hidden for more)
--version - Display the version of this program
clang-tidy options:
--checks=<string>โ a comma-separated list of globs, each one optionally prefixed with-๐. Globs get processed in order of appearance in the list, so like, order actually matters here bestie. Globs with no-prefix add checks with matching names to the set, and globs with the-prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of theChecksoption in the.clang-tidyfile, if any exists.--config=<string>โ specifies a configuration in YAML/JSON format:
-config="{Checks: '*',
CheckOptions: {x: y}}"
When the value is empty, clang-tidy will attempt to find a file named .clang-tidy for each source file in its parent directories โจ.
--config-file=<string>โ specify the path of.clang-tidyor a custom config file, like--config-file=/some/path/myTidyConfigFile๐. This option internally works exactly the same way as--configafter reading the specified config file. Use either--config-fileor--config, not both (pick a lane, bestie ๐).--dump-configโ dumps the configuration in YAML format to stdout ๐. This option can be used along with a file name (and--if the file is outside of a project with a configured compilation database). The configuration used for this file will be printed. Use it along with-checks=*to include the configuration of all the checks ๐.--enable-check-profileโ enables per-check timing profiles and prints a report to stderr โฑ๏ธ.--enable-module-headers-parsingโ enables preprocessor-level module header parsing for C++20 and above, empowering specific checks to detect macro definitions within modules ๐. Heads up though: this feature may cause performance and parsing issues and is therefore considered experimental.
Experimental, babe. Don't come for me if it's slow. ๐
--exclude-header-filter=<string>โ a regular expression matching the names of the headers to exclude diagnostics from ๐ซ. Diagnostics from the main file of each translation unit are always displayed. Must be used together with--header-filter. Can be used together with-line-filter. This option overrides theExcludeHeaderFilterRegexoption in.clang-tidyfile, if any.--explain-configโ for each enabled check, explains where it's enabled ๐ โ i.e. in the clang-tidy binary, the command line, or a specific configuration file.--export-fixes=<filename>โ a YAML file to store suggested fixes in ๐พ. The stored fixes can be applied to the input source code withclang-apply-replacements.--extra-arg=<string>โ additional argument to append to the compiler command line.--extra-arg-before=<string>โ additional argument to prepend to the compiler command line.--fixโ apply suggested fixes ๐ฉน. Without-fix-errors, clang-tidy will bail out if any compilation errors were found.--fix-errorsโ apply suggested fixes even if compilation errors were found ๐ค. If compiler errors have attached fix-its, clang-tidy will apply them as well.--fix-notesโ if a warning has no fix, but a single fix can be found through an associated diagnostic note, apply the fix ๐๏ธ. Specifying this flag will implicitly enable the--fixflag.--format-style=<string>โ style for formatting code around applied fixes ๐ :'none'(default) turns off formatting'file'(literally'file', not a placeholder โ I said what I said) uses the.clang-formatfile in the closest parent directory'{ <json> }'specifies options inline, e.g.-format-style='{BasedOnStyle: llvm, IndentWidth: 8}''llvm','google','webkit','mozilla'
See clang-format documentation for the up-to-date information about formatting styles and options ๐. This option overrides the FormatStyle option in .clang-tidy file, if any.
--header-filter=<string>โ a regular expression matching the names of the headers to output diagnostics from ๐ธ. Diagnostics from the main file of each translation unit are always displayed. Can be used together with-line-filter. This option overrides theHeaderFilterRegexoption in.clang-tidyfile, if any.--line-filter=<string>โ list of files with line ranges to filter the warnings โ๏ธ. Can be used together with-header-filter. The format of the list is a JSON array of objects:
[
{"name":"file1.cpp","lines":[[1,3],[5,7]]},
{"name":"file2.h"}
]
--list-checksโ list all enabled checks and exit ๐. Use with-checks=*to list all available checks.--load=<pluginfilename>โ load the specified plugin ๐.-p <string>โ build path.--quietโ run clang-tidy in quiet mode ๐คซ. This suppresses printing statistics about ignored warnings and warnings treated as errors if the respective options are specified.--store-check-profile=<prefix>โ by default reports are printed in tabulated format to stderr ๐. When this option is passed, these per-TU profiles are instead stored as JSON.--system-headersโ display the errors from system headers ๐ฅ๏ธ. This option overrides theSystemHeadersoption in.clang-tidyfile, if any.--use-colorโ use colors in diagnostics ๐. If not set, colors will be used if the terminal connected to standard output supports colors. This option overrides theUseColoroption in.clang-tidyfile, if any.--verify-configโ check the config files to ensure each check and option is recognized โ .--vfsoverlay=<filename>โ overlay the virtual filesystem described by file over the real filesystem ๐๏ธ.--warnings-as-errors=<string>โ upgrades warnings to errors ๐. Same format as'-checks'. This option's value is appended to the value of theWarningsAsErrorsoption in.clang-tidyfile, if any.--allow-no-checksโ allow empty enabled checks ๐. This suppresses the"no checks enabled"error when disabling all of the checks.
okay so now the build-path era of this page ๐ read carefully, this part is important:
-p <build-path> is used to read a compile command database.
For example, it can be a CMake build directory in which a file named compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON CMake option to get this output) ๐. When no build path is specified, a search for compile_commands.json will be attempted through all parent paths of the first input file. See: https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an example of setting up Clang Tooling on a source tree ๐ฆ.
<source0> ... specify the paths of source files ๐. These paths are looked up in the compile command database. If the path of a file is absolute, it needs to point into CMake's source tree. If the path is relative, the current working directory needs to be in the CMake source tree and the file must be in a subdirectory of the current working directory. "./" prefixes in the relative files will be automatically removed, but the rest of a relative path must be a suffix of a path in the compile command database ๐.
๐ Parameters files ๐
A large number of options or source files can be passed as parameter files by use '@parameter-file' in the command line ๐จ.
๐ Configuration files ๐
okay so bestie, hereโs the config tea โ: clang-tidy attempts to read configuration for each source file from a .clang-tidy file located in the closest parent directory of the source file. The .clang-tidy file is specified in YAML format. If any configuration options have a corresponding command-line option, command-line option takes precedence. periodt.
The following configuration options may be used in a .clang-tidy file:
CheckOptionsโ List of key-value pairs defining check-specific options. Example:
CheckOptions:
some-check.SomeOption: 'some value'
Checksโ Same as--checks. Additionally, the list of globs can be specified as a list instead of a string.ExcludeHeaderFilterRegexโ Same as--exclude-header-filter.ExtraArgsโ Same as--extra-arg.ExtraArgsBeforeโ Same as--extra-arg-before.FormatStyleโ Same as--format-style.HeaderFileExtensionsโ File extensions to consider to determine if a given diagnostic is located in a header file.HeaderFilterRegexโ Same as--header-filter.ImplementationFileExtensionsโ File extensions to consider to determine if a given diagnostic is located in an implementation file.InheritParentConfigโ If this option is true in a config file, the configuration file in the parent directory (if any exists) will be taken and the current config file will be applied on top of the parent one.SystemHeadersโ Same as--system-headers.UseColorโ Same as--use-color.Userโ Specifies the name or e-mail of the user runningclang-tidy. This option is used, for example, to place the correct user name inTODO()comments in the relevant check.WarningsAsErrorsโ Same as--warnings-as-errors.
The effective configuration can be inspected using --dump-config:
$ clang-tidy --dump-config
---
Checks: '-*,some-check'
WarningsAsErrors: ''
HeaderFileExtensions: ['', 'h','hh','hpp','hxx']
ImplementationFileExtensions: ['c','cc','cpp','cxx']
HeaderFilterRegex: ''
FormatStyle: none
InheritParentConfig: true
User: user
CheckOptions:
some-check.SomeOption: 'some value'
...
๐ฆ Suppressing Undesired Diagnostics ๐ฆ
clang-tidy diagnostics are intended to call out code that does not adhere to a coding standard, or is otherwise problematic in some way. However, if the code is known to be correct, it may be useful to silence the warning. Some clang-tidy checks provide a check-specific way to silence the diagnostics, e.g. bugprone-use-after-move (see checks/bugprone/use-after-move.html) can be silenced by re-initializing the variable after it has been moved out, bugprone-string-integer-assignment (see checks/bugprone/string-integer-assignment.html) can be suppressed by explicitly casting the integer to char, readability-implicit-bool-conversion (see checks/readability/implicit-bool-conversion.html) can also be suppressed by using explicit casts, etc.
If a specific suppression mechanism is not available for a certain warning, or its use is not desired for some reason, clang-tidy has a generic mechanism to suppress diagnostics using NOLINT, NOLINTNEXTLINE, and NOLINTBEGIN ... NOLINTEND comments.
The NOLINT comment instructs clang-tidy to ignore warnings on the same line (it doesnโt apply to a function, a block of code or any other language construct; it applies to the line of code it is on). If introducing the comment on the same line would change the formatting in an undesired way, the NOLINTNEXTLINE comment allows suppressing clang-tidy warnings on the next line. The NOLINTBEGIN and NOLINTEND comments allow suppressing clang-tidy warnings on multiple lines (affecting all lines between the two comments).
All comments can be followed by an optional list of check names in parentheses (see below for the formal syntax). The list of check names supports globbing, with the same format and semantics as for enabling checks. Note: negative globs are ignored here, as they would effectively re-activate the warning.
For example:
class Foo {
// Suppress all the diagnostics for the line
Foo(int param); // NOLINT
// Consider explaining the motivation to suppress the warning
Foo(char param); // NOLINT: Allow implicit conversion from `char`, because <some valid reason>
// Silence only the specified checks for the line
Foo(double param); // NOLINT(google-explicit-constructor, google-runtime-int)
// Silence all checks from the `google` module
Foo(bool param); // NOLINT(google*)
// Silence all checks ending with `-avoid-c-arrays`
int array[10]; // NOLINT(*-avoid-c-arrays)
// Silence only the specified diagnostics for the next line
// NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int)
Foo(bool param);
// Silence all checks from the `google` module for the next line
// NOLINTNEXTLINE(google*)
Foo(bool param);
// Silence all checks ending with `-avoid-c-arrays` for the next line
// NOLINTNEXTLINE(*-avoid-c-arrays)
int array[10];
// Silence only the specified checks for all lines between the BEGIN and END
// NOLINTBEGIN(google-explicit-constructor, google-runtime-int)
Foo(short param);
Foo(long param);
// NOLINTEND(google-explicit-constructor, google-runtime-int)
// Silence all checks from the `google` module for all lines between the BEGIN and END
// NOLINTBEGIN(google*)
Foo(bool param);
// NOLINTEND(google*)
// Silence all checks ending with `-avoid-c-arrays` for all lines between the BEGIN and END
// NOLINTBEGIN(*-avoid-c-arrays)
int array[10];
// NOLINTEND(*-avoid-c-arrays)
};
The formal syntax of NOLINT, NOLINTNEXTLINE, and NOLINTBEGIN ... NOLINTEND is the following:
lint-comment:
lint-command
lint-command lint-args
lint-args:
( check-name-list )
check-name-list:
check-name
check-name-list , check-name
lint-command:
NOLINT
NOLINTNEXTLINE
NOLINTBEGIN
NOLINTEND
Note that whitespaces between NOLINT/NOLINTNEXTLINE/NOLINTBEGIN/NOLINTEND and the opening parenthesis are not allowed (in this case the comment will be treated just as NOLINT/NOLINTNEXTLINE/NOLINTBEGIN/NOLINTEND), whereas in the check names list (inside the parentheses), whitespaces can be used and will be ignored.
All NOLINTBEGIN comments must be paired by an equal number of NOLINTEND comments. Moreover, a pair of comments must have matching arguments -- for example, NOLINTBEGIN(check-name) can be paired with NOLINTEND(check-name) but not with NOLINTEND (zero arguments). clang-tidy will generate a clang-tidy-nolint error diagnostic if any NOLINTBEGIN/NOLINTEND comment violates these requirements.
Okay bestie, quick flag roll call so nobody comes for me in the replies: -s, -db, -prefix, -x โ exact spellings, all present, no cap ๐
(-db=yaml is the include-fixer database vibe, and -x c is the language-override vibe when your header is not C++).
-prefix=<header-path>โ prepend this given path to non-absolute file paths in the header list file. By default, headers are assumed to be relative to the header list file directory, so use-prefixwhen you want a different directory. ๐-module-map-path=<module-map-path>โ generate a module map and output it to the given file. See the description in Module Map Generation. โจ-problem-files-list=<problem-files-list-file-name>โ for use only with module map assistant. This is the input list of files that have problems with respect to modules. These will still be included in the generated module map, but will be marked as "excluded" headers. ๐ซถ-root-module=<root-name>โ put modules generated by the-module-map-pathoption in an enclosing module with the given name. See the description in Module Map Generation. ๐ธ-block-check-header-list-onlyโ limit the#include-inside-extern-or-namespace-blockcheck to only those headers explicitly listed in the header list. This is a work-around for avoiding error messages for private includes that purposefully get included inside blocks. ๐ง-no-coverage-checkโ don't do the coverage check for a module map. ๐-coverage-check-onlyโ only do the coverage check for a module map. ๐-display-file-listsโ display lists of good files (no compile errors), problem files, and a combined list with problem files preceded by a#. This can be used to quickly determine which files have problems. The latter combined list might be useful in starting to modularize a set of headers. You can start with a full list of headers, use-display-file-listsoption, and then use the combined list as your intermediate list, uncommenting-out headers as you fix them. ๐
modularize is a standalone tool that checks whether a set of headers provides the consistent definitions required to use modules. For example, it detects whether the same entity (say, a NULL macro or size_t typedef) is defined in multiple headers or whether a header produces different definitions under different circumstances. These conditions cause modules built from the headers to behave poorly, and should be fixed before introducing a module map. periodt ๐
modularize also has an assistant mode situation where it'll generate a module map file for you based on the header list you hand it โจ The file it spits out is a fully functional module map that you can totally use as a starting point for your module.modulemap file โ like a little starter kit, iykyk ๐
๐ฑ Getting Started
Wanna build from source? Slay, here's the roadmap:
- Go read
https://llvm.org/docs/GettingStarted.html(Getting Started with the LLVM System) andhttps://clang.llvm.org/docs/ClangTools.html(Clang Tools Documentation) for the deets on getting sources for LLVM, Clang, and Clang Extra Tools. - Getting Started with the LLVM System (
https://llvm.org/docs/GettingStarted.html) and Building LLVM with CMake (https://llvm.org/docs/CMake.html) give you the directions for how to build. And bestie, once all the sources are checked out into the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically. No cap ๐ซถ- If you're using CMake, you can also use the
modularizetarget to build just the modularize tool and its dependencies. Iconic efficiency.
- If you're using CMake, you can also use the
Before you keep going, take a peek at Modularize Usage to see how to actually invoke modularize, okay? ๐
๐ What Modularize Checks
Modularize will check for the following:
- Duplicate global type and variable definitions
- Duplicate macro definitions
- Macro instances,
'defined(macro)', or#if,#elif,#ifdef,#ifndefconditions that evaluate differently in a header ๐ญ #includedirectives inside'extern "C/C++" {}'or'namespace (name) {}'blocks- Module map header coverage completeness (in the case of a module map input only)
Modularize does normal C/C++ parsing, reporting normal errors and warnings like a good little tool, but it'll ALSO hit you with special error messages like these:
error: '(symbol)' defined at multiple locations:
(file):(row):(column)
(file):(row):(column)
error: header '(file)' has different contents depending on how it was included
That second one might be followed by messages like:
note: '(symbol)' in (file) at (row):(column) not always provided
Checks also run for macro expansions, defined(macro) expressions, and preprocessor conditional directives that evaluate inconsistently โ and those can produce error messages like this whole situation:
(...)/SubHeader.h:11:5:
#if SYMBOL == 1
^
error: Macro instance 'SYMBOL' has different values in this header,
depending on how it was included.
'SYMBOL' expanded to: '1' with respect to these inclusion paths:
(...)/Header1.h
(...)/SubHeader.h
(...)/SubHeader.h:3:9:
#define SYMBOL 1
^
Macro defined here.
'SYMBOL' expanded to: '2' with respect to these inclusion paths:
(...)/Header2.h
(...)/SubHeader.h
(...)/SubHeader.h:7:9:
#define SYMBOL 2
^
Macro defined here.
Checks also run for '#include' directives nested inside 'extern "C/C++" {}' or 'namespace (name) {}' blocks, and can produce an error message like this:
IncludeInExtern.h:2:3:
#include "Empty.h"
^
error: Include directive within extern "C" {}.
IncludeInExtern.h:1:1:
extern "C" {
^
The "extern "C" {}" block is here.
๐ Module Map Coverage Check
The coverage check uses the Clang library to read and parse the module map file. Starting at the module map file directory, or just the include paths if you specified them, it collects the names of all the files it considers headers (no extension, .h, or .inc โ and if you need more than that, go modify the isHeader function, bestie ๐ง). Then it compares the headers against those referenced in the module map, either explicitly named or implicitly named via an umbrella directory or umbrella file, as parsed by the ModuleMap object.
If headers are found that aren't referenced or covered by an umbrella directory or file, you get warning messages, and this program returns an error code of 1. If no problems are found, an error code of 0 is returned. Clean vibes only โจ
Heads up: in the case of umbrella headers, this tool invokes the compiler to preprocess the file, and uses a callback to collect the header files included by the umbrella header or any of its nested includes. If any front end options are needed for these compiler invocations, you can include them on the command line after the module map file argument. ๐ซก
Warning messages have the form:
warning: module.modulemap does not account for file: Level3A.h
And note that for the case of the module map referencing a file that does not exist, the module map parser in Clang will (at the time of this writing) display an error message. So like, don't be surprised ๐
Wanna limit the checks modularize does to just the module map coverage check? Use the -coverage-check-only option. Easy.
For example:
modularize -coverage-check-only module.modulemap
๐ง Module Map Generation
If you specify -module-map-path=<module map file>, modularize will output a module map based on the input header list. A module gets created for each header. And also โ if the header in the header list is a partial path, a nested module hierarchy gets created, where a module is made for each subdirectory component in the header path, with the header itself represented by the innermost module. If other headers use the same subdirectories, they get enclosed in these same modules too. It's giving organizational excellence ๐
For example, for the header list:
SomeTypes.h
SomeDecls.h
SubModule1/Header1.h
SubModule1/Header2.h
SubModule2/Header3.h
SubModule2/Header4.h
SubModule2.h
The following module map will be generated:
// Output/NoProblemsAssistant.txt
// Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt \
-root-module=Root NoProblemsAssistant.modularize
module SomeTypes {
header "SomeTypes.h"
export *
}
module SomeDecls {
header "SomeDecls.h"
export *
}
module SubModule1 {
module Header1 {
header "SubModule1/Header1.h"
export *
}
module Header2 {
header "SubModule1/Header2.h"
export *
}
}
module SubModule2 {
module Header3 {
header "SubModule2/Header3.h"
export *
}
module Header4 {
header "SubModule2/Header4.h"
export *
}
header "SubModule2.h"
export *
}
There's an optional -root-module=<root-name> option you can use to cause a root module to be created which encloses all the modules. Very main character energy ๐
And there's an optional -problem-files-list=<problem-file-name> that lets you input a list of files to be excluded โ maybe as a temporary stop-gap measure until you can actually fix the problem headers. We've all been there ๐
For example, with the same header list from above:
// Output/NoProblemsAssistant.txt
// Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt \
-root-module=Root NoProblemsAssistant.modularize
module Root {
module SomeTypes {
header "SomeTypes.h"
export *
}
module SomeDecls {
header "SomeDecls.h"
export *
}
module SubModule1 {
module Header1 {
header "SubModule1/Header1.h"
export *
}
module Header2 {
header "SubModule1/Header2.h"
export *
}
}
module SubModule2 {
module Header3 {
header "SubModule2/Header3.h"
export *
}
module Header4 {
header "SubModule2/Header4.h"
export *
}
header "SubModule2.h"
export *
}
}
Fair warning: headers with dependents will be ignored with a warning, because the Clang module mechanism doesn't support headers that rely on other headers being included first. That's just how it is, bestie ๐ญ
The module map format defines some keywords which can't be used in module names. So if a header has one of these names, an underscore ('_') will be prepended to the name. For example, if the header name is header.h, because header is a keyword, the module name will be _header. Sneaky little fix โจ
For a list of the module map keywords, please see: Lexical structure at https://clang.llvm.org/docs/Modules.html#lexical-structure
๐ PP-TRACE USER'S MANUAL ๐
okay bestie, meet pp-trace โ it's a standalone tool that traces preprocessor activity. It's also literally used as a test of Clang's PPCallbacks interface. What it does is run a given source file through the Clang preprocessor and then shows you selected info from the callback functions that get overridden in a PPCallbacks derivation (https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html). The output comes out in a high-level YAML format, which we describe down in pp-trace Output Format. ๐ซถ
โจ pp-trace Usage โจ
๐ Command Line Format ๐
pp-trace [<pp-trace-options>] <source-file> [-- <front-end-options>]
<pp-trace-options> is just a little place-holder for the options that are specific to pp-trace, and those are described right below in Command Line Options, don't worry.
<source-file> is the source file you want run through the preprocessor. main character energy, honestly.
<front-end-options> is the place-holder for the regular Clang Compiler Options (https://clang.llvm.org/docs/UsersManual.html#command-line-options) โ and heads up, these have to come after the <source-file>. no cap, order matters. ๐
๐ Command Line Options ๐
-callbacks <comma-separated-globs>โ this one takes a comma-separated list of globs that describe which callbacks should get traced. Globs are processed in order of appearance. Positive globs add the matched callbacks to the set, and negative globs (the ones with the-prefix) remove callbacks from the set. so it's giving โจ curate your own adventure โจ. Here's the whole menu of callbacks:FileChangedFileSkippedInclusionDirectivemoduleImportEndOfMainFileIdentPragmaDirectivePragmaCommentPragmaDetectMismatchPragmaDebugPragmaMessagePragmaDiagnosticPushPragmaDiagnosticPopPragmaDiagnosticPragmaOpenCLExtensionPragmaWarningPragmaWarningPushPragmaWarningPopMacroExpandsMacroDefinedMacroUndefinedDefinedSourceRangeSkippedIfElifIfdefIfndefElseEndif
-output <output-file>โ by default,pp-tracesends the trace info to stdout. Use this option when you'd rather send the trace info to a file instead. ๐
๐ pp-trace Output Format ๐
The pp-trace output is formatted as YAML โ peep https://yaml.org/ for the general YAML lore. It's arranged as a sequence of info about each callback call, and that includes the callback name plus the argument info, like for example:
---
- Callback: Name
Argument1: Value1
Argument2: Value2
(etc.)
...
And with real data it looks like this:
---
- Callback: FileChanged
Loc: "c:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-include.cpp:1:1"
Reason: EnterFile
FileType: C_User
PrevFID: (invalid)
(etc.)
- Callback: FileChanged
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-include.cpp:5:1"
Reason: ExitFile
FileType: C_User
PrevFID: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/Input/Level1B.h"
- Callback: EndOfMainFile
...
In all but one case (MacroDirective), the "Argument" scalars have the exact same name as the argument in the corresponding PPCallbacks callback function. iconic consistency. ๐
๐ฆ Callback Details ๐ฆ
The sections below describe the purpose and the output format for each callback, one at a time, no skipping. ๐
Click on the callback name in the section heading to see the Doxygen documentation for that callback.
The argument descriptions (laid out as cute little bullet lists here) describe the callback argument info that gets displayed.
The Argument Name field in most (but not all) cases is the same name as the callback function parameter.
The Argument Value Syntax field describes the values that will be displayed for the argument value. It uses an ad hoc representation that mixes literal and symbolic representations together. Enumeration member symbols are shown as the actual enum member in a (member1|member2|...) form. A name in parentheses can either be a place holder for the described value, or, confusingly, it might be a literal โ like (null) for a null pointer. Locations are shown as quoted only to avoid confusing the documentation generator. ๐ญ
The Clang C++ Type field is the type straight from the callback function declaration.
The description describes the argument, or what's displayed for it.
ngl, in some cases โ like when a structure pointer is an argument value โ only some key member or members are shown to represent the value, instead of trying to display literally every member of the structure. we keep it cute and manageable. ๐
๐ FileChanged Callback ๐
https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a7cc8cfaf34114fc65e92af621cd6464e
FileChanged gets called when the preprocessor enters or exits a file โ both the top level file being compiled and any #include directives. It will also get called as a result of a system header pragma, or during internal renaming of a file. multitasking queen. โจ
Argument descriptions:
Locโ Argument Value Syntax:"(file):(line):(col)"โ Clang C++ Type:SourceLocationโ the location of the directive.Reasonโ Argument Value Syntax:(EnterFile|ExitFile|SystemHeaderPragma|RenameFile)โ Clang C++ Type:PPCallbacks::FileChangeReasonโ the reason for the change.FileTypeโ Argument Value Syntax:(C_User|C_System|C_ExternCSystem)โ Clang C++ Type:SrcMgr::CharacteristicKindโ the include type.PrevFIDโ Argument Value Syntax:((file)|(invalid))โ Clang C++ Type:FileIDโ the previous file, if there is any.
Example:
- Callback: FileChanged
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-include.cpp:1:1"
Reason: EnterFile
FileType: C_User
PrevFID: (invalid)
๐ง FileSkipped Callback ๐ง
https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ab5b338a0670188eb05fa7685bbfb5128
FileSkipped gets called when a source file is skipped as the result of header guard optimization. efficiency icon. ๐ธ
Argument descriptions:
ParentFileโ Argument Value Syntax:("(file)" or (null))โ Clang C++ Type:const FileEntryโ the file that#includedthe skipped file.FilenameTokโ Argument Value Syntax:(token)โ Clang C++ Type:const Tokenโ the token inParentFilethat indicates the skipped file.FileTypeโ Argument Value Syntax:(C_User|C_System|C_ExternCSystem)โ Clang C++ Type:SrcMgr::CharacteristicKindโ the file type.
- Callback: FileSkipped
ParentFile: "/path/filename.h"
FilenameTok: "filename.h"
FileType: C_User
๐
InclusionDirective Callback
okay so InclusionDirective (doxygen link if you wanna go feral: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a557d9738c329793513a6f57d6b60de52) fires whenever an inclusion directive of any kind (#include</code>, #import</code>, etc.) has been processed โ and yes, that's regardless of whether the inclusion will actually result in an inclusion. Processed is processed, bestie. ๐ฆ
Argument descriptions, served fresh: โจ
HashLocโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocationโ the location of the'#'that starts the inclusion directive.IncludeTokโ value syntax(token), Clang C++ typeconst Tokenโ the token that indicates the kind of inclusion directive, e.g.,'include'or'import'.FileNameโ value syntax"(file)", Clang C++ typeStringRefโ the name of the file being included, as written in the source code.IsAngledโ value syntax(true|false), Clang C++ typeboolโ whether the file name was enclosed in angle brackets; otherwise, it was enclosed in quotes.FilenameRangeโ value syntax"(file)", Clang C++ typeCharSourceRangeโ the character range of the quotes or angle brackets for the written file name.Fileโ value syntax"(file)", Clang C++ typeconst FileEntryโ the actual file that may be included by this inclusion directive.SearchPathโ value syntax"(path)", Clang C++ typeStringRefโ contains the search path which was used to find the file in the file system.RelativePathโ value syntax"(path)", Clang C++ typeStringRefโ the path relative toSearchPath, at which the include file was found.Importedโ value syntax((module name)|(null)), Clang C++ typeconst Moduleโ the module, whenever an inclusion directive was automatically turned into a module import or null otherwise.
Example, no cap: ๐
- Callback: InclusionDirective
HashLoc: "D:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace/pp-trace-include.cpp:4:1"
IncludeTok: include
FileName: "Input/Level1B.h"
IsAngled: false
FilenameRange: "Input/Level1B.h"
File: "D:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace/Input/Level1B.h"
SearchPath: "D:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace"
RelativePath: "Input/Level1B.h"
Imported: (null)
๐ moduleImport Callback
moduleImport (doxygen: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#af32dcf1b8b7c179c7fcd3e24e89830fe) gets called when there was an explicit module-import syntax. that's it, that's the vibe check โ explicit module-import syntax in the building, so the callback shows up. โญ
Argument descriptions ๐ซถ โ like, here's the whole cast of characters for this callback, bestie:
ImportLocโ value syntax is"(file):(line):(col)", Clang C++ typeSourceLocation. It's the location of the import directive token ๐Pathโ value syntax is"(path)", Clang C++ typeModuleIdPath. These are the identifiers (and their locations) of the module "path" ๐งญImportedโ value syntax is((module name)|(null)), Clang C++ typeconst Module. This is the imported module itself, and lowkey it can be null if the import failed ๐ญ
Example, served fresh โจ:
- Callback: moduleImport
ImportLoc: "d:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-modules.cpp:4:2"
Path: [{Name: Level1B, Loc: "d:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace/pp-trace-modules.cpp:4:9"}, {Name: Level2B, Loc: "d:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace/pp-trace-modules.cpp:4:17"}]
Imported: Level2B
๐ธ EndOfMainFile Callback
https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a63e170d069e99bc1c9c7ea0f3bed8bcc
So EndOfMainFile gets called the moment the end of the main file is reached โ like the credits rolling on the main character's story ๐ฌ
Argument descriptions ๐ :
- (no arguments) โ literally nothing. Empty table energy. Iconic.
Example:
- Callback: EndOfMainFile
๐ฆ Ident Callback
https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a3683f1d1fa513e9b6193d446a5cc2b66
Ident fires when a #ident or #sccs directive is read. yes bestie, both of those ๐ซถ
Argument descriptions โจ:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocation. It's the location of the directive ๐strโ value syntax(name), Clang C++ typeconst std::string. This is the text of the directive ๐
Example:
- Callback: Ident
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-ident.cpp:3:1"
str: "$Id$"
๐ PragmaDirective Callback
https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a0a2d7a72c62184b3cbde31fb62c6f2f7
PragmaDirective gets called when it starts reading any pragma directive โ like, any one of them, no cap ๐ฅ
Argument descriptions ๐:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocation. The location of the directive ๐Introducerโ value syntax(PIK_HashPragma|PIK__Pragma|PIK___pragma), Clang C++ typePragmaIntroducerKind. It's the type of the pragma directive ๐
- Callback: PragmaDirective
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Introducer: PIK_HashPragma
๐ฌ PragmaComment Callback
okay so PragmaComment fires whenever a #pragma comment directive gets read. That's it, that's the whole vibe. (docs bestie: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ace0d940fc2c12ab76441466aab58dc37)
Argument descriptions โ the whole lineup, no skipping:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocationโ literally where the directive is. ๐Kindโ value syntax((name)|(null)), Clang C++ typeconst IdentifierInfoโ the comment kind symbol.Strโ value syntax(message directive), Clang C++ typeconst std::stringโ the comment message directive.
Example โ look how cute this output is:
- Callback: PragmaComment
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Kind: library
Str: kernel32.lib
๐ต๏ธ PragmaDetectMismatch Callback
PragmaDetectMismatch is called when a #pragma detect_mismatch directive is read. no cap, that's the whole thing. (docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ab11158c9149fb8ad8af1903f4a6cd65d)
Argument descriptions:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocationโ the location of the directive. ๐Nameโ value syntax"(name)", Clang C++ typeconst std::stringโ the name.Valueโ value syntax(string), Clang C++ typeconst std::stringโ the value.
Example:
- Callback: PragmaDetectMismatch
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Name: name
Value: value
๐ PragmaDebug Callback
PragmaDebug is called when a #pragma clang __debug directive is read. debug girlie behaviour fr fr ๐ซถ (docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a57cdccb6dcc07e926513ac3d5b121466)
Argument descriptions:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocationโ the location of the directive. ๐DebugTypeโ value syntax(string), Clang C++ typeStringRefโ indicates type of debug message.
Example:
- Callback: PragmaDebug
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
DebugType: warning
๐ข PragmaMessage Callback
PragmaMessage is called when a #pragma message directive is read. she's loud, she's iconic, she's a compiler telling you things ๐
(docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#abb42935d9a9fd8e2c4f51cfdc4ea2ae1)
Argument descriptions:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocationโ the location of the directive. ๐Namespaceโ value syntax(name), Clang C++ typeStringRefโ the namespace of the message directive.Kindโ value syntax(PMK_Message|PMK_Warning|PMK_Error), Clang C++ typePPCallbacks::PragmaMessageKindโ the type of the message directive.Strโ value syntax(string), Clang C++ typeStringRefโ the text of the message directive.Locโ"(file):(line):(col)", and its Clang C++ type isSourceLocation. Where the directive is at, duh ๐Namespaceโ(name), typeStringRef. The namespace name โจ
Example, and yes bestie copy it exactly:
- Callback: PragmaMessage
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Namespace: "GCC"
Kind: PMK_Message
Str: The message text.
๐ฉท PragmaDiagnosticPush Callback
PragmaDiagnosticPush https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a0f3ff19762baa38fe6c5c58022d32979
Okay so PragmaDiagnosticPush gets called when a #pragma gcc diagnostic push directive is read. That's it, that's the vibe ๐ซถ
Argument descriptions, so you know what's what:
Locโ"(file):(line):(col)", Clang C++ typeSourceLocation. The location of the directive ๐Namespaceโ(name), Clang C++ typeStringRef. Namespace name ๐ซ
Example:
- Callback: PragmaDiagnosticPush
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Namespace: "GCC"
๐ PragmaDiagnosticPop Callback
PragmaDiagnosticPop https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ac94d789873122221fba8d76f6c5ea45e
PragmaDiagnosticPop is called when a #pragma gcc diagnostic pop directive is read. Push's little sister, iconic behavior ๐
Argument descriptions:
Locโ"(file):(line):(col)", Clang C++ typeSourceLocation. The location of the directive ๐Namespaceโ(name), Clang C++ typeStringRef. Namespace name ๐ซ
Example:
- Callback: PragmaDiagnosticPop
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Namespace: "GCC"
๐ PragmaDiagnostic Callback
PragmaDiagnostic https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#afe7938f38a83cb7b4b25a13edfdd7bdd
PragmaDiagnostic is called when a #pragma gcc diagnostic directive is read. This one's the main character of the diagnostic pragma family, fr fr ๐
Argument descriptions โ four of them, stay with me:
Locโ"(file):(line):(col)", Clang C++ typeSourceLocation. The location of the directive ๐Namespaceโ(name), Clang C++ typeStringRef. Namespace name ๐ซmappingโ(0|MAP_IGNORE|MAP_WARNING|MAP_ERROR|MAP_FATAL), Clang C++ typediag::Severity. Mapping type ๐บ๏ธStrโ(string), Clang C++ typeStringRef. Warning/error name โ ๏ธ
Example โจ โ here's what it actually spit out for the callback we just walked through, so you can see the shape of it:
- Callback: PragmaDiagnostic
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Namespace: "GCC"
mapping: MAP_WARNING
Str: WarningName
PragmaOpenCLExtension Callback
Docs, if you're a nerd who likes reading source: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a92a20a21fadbab4e2c788f4e27fe07e7
So PragmaOpenCLExtension fires when an OpenCL extension gets either disabled or enabled via a pragma. That's the whole vibe check ๐
Argument descriptions ๐:
NameLocโ value syntax is"(file):(line):(col)", Clang C++ typeSourceLocationโ says the original: The location of the name.Nameโ value syntax is(name), Clang C++ typeconst IdentifierInfoโ Name symbol.StateLocโ value syntax is"(file):(line):(col)", Clang C++ typeSourceLocationโ The location of the state.Stateโ value syntax is(1|0), Clang C++ typeunsignedโ Enabled/disabled state.
Example โจ:
- Callback: PragmaOpenCLExtension
NameLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:10"
Name: Name
StateLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:18"
State: 1
PragmaWarning Callback
Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#aa17169d25fa1cf0a6992fc944d1d8730
PragmaWarning is the one that goes off when a #pragma warning directive gets read. Like, the compiler clocking in to tell you about warnings ๐
Argument descriptions ๐:
Locโ value syntax is"(file):(line):(col)", Clang C++ typeSourceLocationโ The location of the directive.WarningSpecโ value syntax is(string), Clang C++ typeStringRefโ The warning specifier.Idsโ value syntax is[(number)[, ...]], Clang C++ typeArrayRef<int>โ The warning numbers.
Example โจ:
- Callback: PragmaWarning
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
WarningSpec: disable
Ids: 1,2,3
PragmaWarningPush Callback
Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ae5626ef70502687a859f323a809ed0b6
PragmaWarningPush fires when a #pragma warning(push) directive is read. Pushing that warning state onto the stack, main character energy ๐ฆ
Argument descriptions ๐:
Locโ value syntax is"(file):(line):(col)", Clang C++ typeSourceLocationโ The location of the directive.Levelโ value syntax is(number), Clang C++ typeintโ Warning level.
Example โจ:
- Callback: PragmaWarningPush
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
Level: 1
PragmaWarningPop Callback
Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ac98d502af8811b8a6e7342d7cd2b3b95
PragmaWarningPop fires when a #pragma warning(pop) directive is read. And pop goes the warning state ๐ซง
Argument descriptions ๐:
Locโ value syntax is"(file):(line):(col)", Clang C++ typeSourceLocationโ The location of the directive.
Example โจ:
- Callback: PragmaWarningPop
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
MacroExpands Callback
Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a9bc725209d3a071ea649144ab996d515
OK so MacroExpands is called when ::HandleMacroExpandedIdentifier when a macro invocation is found. Obsessed, honestly โ this is the macro girlies' moment ๐ธ
Argument descriptions ๐:
MacroNameTokโ value syntax is(token), Clang C++ typeconst Tokenโ The macro name token.MacroDirectiveโ value syntax is(MD_Define|MD_Undefine|MD_Visibility), Clang C++ typeconst MacroDirectiveโ The kind of macro directive from theMacroDirectivestructure.Rangeโ value syntax is["(file):(line):(col)", "(file):(line):(col)"], Clang C++ typeSourceRangeโ The source range for the expansion.
โฆand bestie, this argument table is literally still going โ more rows incoming ๐
Argsโ the argument tokens! Value syntax is[(name)|(number)|<(token name)>[,...]], type isconst MacroArgs, and here's the tea ๐ซ: names and numbers are literal, and literally everything else is of the form'<'tokenName'>'. iykyk.
Example, bestie โ look at it, it's giving exactly what we just said ๐ :
- Callback: MacroExpands
MacroNameTok: X_IMPL
MacroDirective: MD_Define
Range: [(nonfile), (nonfile)]
Args: [a <plus> y, b]
๐ฆ MacroDefined Callback
MacroDefined gets called when a macro definition is seen. Like, the moment your #define walks in the room, this callback is already texting everyone about it ๐ญโจ
Argument descriptions, no cap:
MacroNameTokโ value syntax(token), Clang C++ typeconst Token. It's the macro name token. The name! Of the macro! ๐MacroDirectiveโ value syntax(MD_Define|MD_Undefine|MD_Visibility), Clang C++ typeconst MacroDirective. This is the kind of macro directive from theMacroDirectivestructure ๐ซ
Example, observe:
- Callback: MacroDefined
MacroNameTok: X_IMPL
MacroDirective: MD_Define
๐ธ MacroUndefined Callback
MacroUndefined gets called when a macro #undef is seen. yea, the undef girlies get their moment too ๐ซถ
Argument descriptions:
MacroNameTokโ value syntax(token), Clang C++ typeconst Token. The macro name token, same as always, iconic behaviour โญMacroDirectiveโ value syntax(MD_Define|MD_Undefine|MD_Visibility), Clang C++ typeconst MacroDirective. The kind of macro directive from theMacroDirectivestructure. Yes it's the same description as above, and yes I'm telling you again, because it's the same field ๐
Example:
- Callback: MacroUndefined
MacroNameTok: X_IMPL
MacroDirective: MD_Define
๐ Defined Callback
Defined gets called when the 'defined' operator is seen. That's the operator, bestie โ the one in #if defined(FOO). It's out here doing vibe checks on your macros ๐
Argument descriptions, all three of them:
MacroNameTokโ value syntax(token), Clang C++ typeconst Token. The macro name token ๐MacroDirectiveโ value syntax(MD_Define|MD_Undefine|MD_Visibility), Clang C++ typeconst MacroDirective. The kind of macro directive from theMacroDirectivestructure โจRangeโ value syntax["(file):(line):(col)", "(file):(line):(col)"], Clang C++ typeSourceRange. This is the source range for the directive โ i.e. exactly where it lives in your file, so you can point at it and go "there she is" ๐ฅ
And here's what that Defined callback actually spits out, bestie โ look at the MacroDirective: (null) though, it's giving "I have no directive and I'm at peace with that" ๐:
- Callback: Defined
MacroNameTok: MACRO
MacroDirective: (null)
Range: ["D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:5", "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:19"]
๐ฆ SourceRangeSkipped
SourceRangeSkipped is the callback that gets called when a source range is skipped. That's it, that's the whole job. It's the "you didn't read that part of the textbook" of callbacks ๐
Doxygen ref, for when you wanna get feral: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#abdb4ebe11610f079ac33515965794b46
Argument descriptions:
Rangeโ value syntax:["(file):(line):(col)", "(file):(line):(col)"]ยท Clang C++ type:SourceRangeยท the source range skipped โ๏ธ
Example, and it's literal:
- Callback: SourceRangeSkipped
Range: [":/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2", ":/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:9:2"]
๐ธ If
If fires the moment an #if shows up. Main character of the preprocessor, honestly ๐
Doxygen ref: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a645edcb0d6becbc6f256f02fd1287778
Argument descriptions:
Locโ value syntax:"(file):(line):(col)"ยท Clang C++ type:SourceLocationยท the location of the directive ๐ConditionRangeโ value syntax:["(file):(line):(col)", "(file):(line):(col)"]ยท Clang C++ type:SourceRangeยท the source range for the conditionConditionValueโ value syntax:(true|false)ยท Clang C++ type:boolยท the condition value โ
Example โ note the false, so that branch was NOT it:
- Callback: If
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2"
ConditionRange: ["D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:4", "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:9:1"]
ConditionValue: false
๐ Elif
Elif gets called when an #elif is seen. It's the "okay but what about this" of the preprocessor, and lowkey it has the most arguments out of this whole conditional squad ๐ซถ
Doxygen ref: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a180c9e106a28d60a6112e16b1bb8302a
Argument descriptions:
Locโ value syntax:"(file):(line):(col)"ยท Clang C++ type:SourceLocationยท the location of the directive ๐ConditionRangeโ value syntax:["(file):(line):(col)", "(file):(line):(col)"]ยท Clang C++ type:SourceRangeยท the source range for the conditionConditionValueโ value syntax:(true|false)ยท Clang C++ type:boolยท the condition valueIfLocโ value syntax:"(file):(line):(col)"ยท Clang C++ type:SourceLocationยท the location of the directive
Ngl bestie, peep thatLocandIfLochave the exact same description in the source. That's not me being lazy, that's verbatim. Copy-paste ate and left no crumbs ๐
Example โ see how IfLoc points back at the original #if on line 8? Told you, it remembers its roots:
- Callback: Elif
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:2"
ConditionRange: ["D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:4", "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:11:1"]
ConditionValue: false
IfLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2"
๐ Ifdef
Ifdef is called when an #ifdef is seen. The O.G. "does this macro exist" check โจ
Doxygen ref: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a0ce79575dda307784fd51a6dd4eec33d
Argument descriptions:
Locโ value syntax:"(file):(line):(col)"ยท Clang C++ type:SourceLocationยท the location of the directive ๐MacroNameTokโ value syntax:(token)ยท Clang C++ type:const Tokenยท the macro name token ๐ท๏ธMacroDirectiveโ value syntax:(MD_Define|MD_Undefine|MD_Visibility)ยท Clang C++ type:const MacroDirectiveยท the kind of macro directive from theMacroDirectivestructure
Example โ MD_Define spotted, so MACRO is out here living its truth:
- Callback: Ifdef
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-conditional.cpp:3:1"
MacroNameTok: MACRO
MacroDirective: MD_Define
๐ Ifndef
Ifndef is called when an #ifndef is seen โ the pessimist twin of Ifdef, checking that something isn't there ๐
Doxygen ref: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a767af69f1cdcc4cd880fa2ebf77ad3ad
Argument descriptions:
Locโ value syntax:"(file):(line):(col)"ยท Clang C++ type:SourceLocationยท the location of the directive ๐
โฆand the argument list keeps going, bestie โ don't you dare click away ๐
MacroNameTokโ argument value syntax(token), Clang C++ typeconst Token, description: the macro name token โจMacroDirectiveโ argument value syntax(MD_Define|MD_Undefine|MD_Visibility), Clang C++ typeconst MacroDirective, description: the kind of macro directive from theMacroDirectivestructure ๐
Example (copy it exactly, bestie, it's literal):
- Callback: Ifndef
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-conditional.cpp:3:1"
MacroNameTok: MACRO
MacroDirective: MD_Define
Else Callback ๐ฆ
The Else callback (doxygen: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ad57f91b6d9c3cbcca326a2bfb49e0314) fires the moment an #else shows up. Like, "we're switching lanes, bestie" energy ๐
Argument descriptions, no cap:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocation, description: the location of the else directive ๐IfLocโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocation, description: the location of the if directive ๐
Example, verbatim:
- Callback: Else
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:2"
IfLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2"
Endif Callback ๐
The Endif callback (doxygen: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#afc62ca1401125f516d58b1629a2093ce) is called when an #endif is seen. It's giving "we're done here, closing the group chat" โจ
Argument descriptions:
Locโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocation, description: the location of the endif directive ๐IfLocโ value syntax"(file):(line):(col)", Clang C++ typeSourceLocation, description: the location of the if directive ๐
Example:
- Callback: Endif
Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:2"
IfLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2"
๐ Building pp-trace ๐
So you wanna build it from source? Iconic. Here's the whole questline, fr fr:
- Go read Getting Started with the LLVM System (
https://llvm.org/docs/GettingStarted.html) and Clang Tools Documentation (https://clang.llvm.org/docs/ClangTools.html) โ that's where you learn how to get sources for LLVM, Clang, and Clang Extra Tools ๐ - Getting Started with the LLVM System (
https://llvm.org/docs/GettingStarted.html) and Building LLVM with CMake (https://llvm.org/docs/CMake.html) give you the directions for how to build ๐ง With sources all checked out into the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically. Literally automatic, slay.- If you're using CMake, you can also use the
pp-tracetarget to build just thepp-tracetool and its dependencies. Main character build, nobody else invited ๐
- If you're using CMake, you can also use the
CLANG-DOC ๐
๐ Contents ๐
- Clang-Doc
- Use
- Output
- Configuration
- Options
clang-doc is a tool for generating C and C++ documentation from source code and comments. ๐
Ok bestie, real talk: this tool is in a very early development stage, so you might encounter bugs and crashes. ๐ญ Submitting reports with information about how to reproduce the issue to the LLVM bug tracker at https://github.com/llvm/llvm-project/issues/ will definitely help the project. If you have any ideas or suggestions, put a feature request there too. ๐ซถ
โจ Use โจ
clang-doc is a LibTooling-based tool โ the LibTooling docs live at https://clang.llvm.org/docs/LibTooling.html โ and so it requires a compile command database for your project. For an example of how to do this, see How To Setup Tooling For LLVM at https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html. ๐ฆ
The tool will process a list of files by default:
$ clang-doc File1.cpp File2.cpp ... FileN.cpp
The tool can be also used with a compile commands database:
$ clang-doc --executor=all-TUs compile_commands.json
To select only a subset of files from the database, use the --filter flag:
$ clang-doc --executor=all-TUs --filter=File[0-9]+.cpp compile_commands.json
๐ธ Output ๐ธ
clang-doc produces a directory of documentation. One file is produced for each namespace and record in the project source code, containing all documentation โ including contained functions, methods, and enums โ for that item. ๐
The top-level directory is configurable through the output flag:
$ clang-doc --output=output/directory/ compile_commands.json
๐ง Configuration ๐ง
Configuration for clang-doc is currently limited to command-line options. In the future, it may develop the ability to use a configuration file, but no such efforts are currently in progress. โจ
๐ Options ๐
clang-doc offers the following options:
$ clang-doc --help
OVERVIEW: Generates documentation from source code and comments.
Example usage for files without flags (default):
$ clang-doc File1.cpp File2.cpp ... FileN.cpp
Example usage for a project using a compile commands database:
$ clang-doc --executor=all-TUs compile_commands.json
USAGE: clang-doc [options] <source0> [... <sourceN>]
OPTIONS:
โญ Generic Options โญ
-helpโ Display available options (-help-hiddenfor more).-help-listโ Display list of available options (-help-list-hiddenfor more).-versionโ Display the version of this program.
๐ฅ clang-doc options ๐ฅ
--doxygenโ Use only doxygen-style comments to generate docs.--extra-arg=<string>โ Additional argument to append to the compiler command line. Can be used several times.--extra-arg-before=<string>โ Additional argument to prepend to the compiler command line. Can be used several times.--format=<value>โ Format for outputted docs.=yamlโ Documentation in YAML format.=mdโ Documentation in MD format.=htmlโ Documentation in HTML format.
--ignore-map-errorsโ Continue if files are not mapped correctly.--output=<string>โ Directory for outputting generated files.-p <string>โ Build path.--project-name=<string>โ Name of project.--publicโ Document only public declarations.--repository=<string>โ URL of repository that hosts code. Used for links to definition locations.--source-root=<string>โ Directory where processed files are stored. Links to definition locations will only be generated if the file is in this dir.--stylesheets=<string>โ CSS stylesheets to extend the default styles.
๐จ The following flags should only be used ifformatis set tohtml:- repository - source-root - stylesheets๐จ
The Doxygen documentation describes the internal software that makes up the tools of clang-tools-extra โ not the external use of these tools. The Doxygen documentation contains no instructions about how to use the tools, only the APIs that make up the software. For usage instructions, please see the user's guide or reference manual for each tool. ๐
- Doxygen documentation โ
doxygen/annotated.html
Note: This documentation is generated directly from the source code with doxygen. Since the tools of clang-tools-extra are constantly under active development, what you're about to read is out of date! ๐
- Index โ
<> - Search Page โ
<>
๐ Author ๐
The Clang Team.
๐ Copyright ๐
2007-2026, The Clang Team
Ok that's the whole clang-doc tea, bestie โ signing off from page 21 of EXTRACLANGTOOLS(1), dated May 15, 2026. Slay and happy documenting ๐๐๐ฆ
๐ฆ where this page comes from
- fedora 44: clang21-tools-extra 21.1.8-5.fc44 ยท Apache-2.0 WITH LLVM-exception OR NCSA
- fedora 44: clang21-tools-extra 21.1.8-5.fc44 ยท Apache-2.0 WITH LLVM-exception OR NCSA
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms